diff --git a/integrationList.json b/integrationList.json index f90d033..0a8513a 100644 --- a/integrationList.json +++ b/integrationList.json @@ -4,6 +4,31 @@ "script": ["asana.js"], "isActiveInProd": true }, + { + "matches": ["*://*.todoist.com/app*"], + "script": ["todoist.js"], + "isActiveInProd": true + }, + { + "matches": ["*://*.azure.com/*"], + "script": ["azure.js"], + "isActiveInProd": true + }, + { + "matches": ["*://*.zendesk.com/*"], + "script": ["zendesk.js"], + "isActiveInProd": true + }, + { + "matches": ["*://*.gitlab.com/*"], + "script": ["gitlab.js"], + "isActiveInProd": true + }, + { + "matches": ["*://*.freshdesk.com/*"], + "script": ["freshdesk.js"], + "isActiveInProd": true + }, { "matches": ["*://docs.google.com/*"], "script": ["google-docs.js"], diff --git a/manifest.json b/manifest.json index e80cfef..4581593 100644 --- a/manifest.json +++ b/manifest.json @@ -83,16 +83,6 @@ "scripts/config.js", "scripts/TokenManager.js", "scripts/ApiService.js", "scripts/common.js", "scripts/TimerBase.js","scripts/TimerButton.js", "scripts/third_party/insight.ly.js"] }, - { - "matches": [ - "*://*.todoist.com/app*" - ], - "css": ["styles/common.css", "styles/font-awesome.css", "styles/bootstrap.min.css", "styles/jquery-ui.structure.min.css", "styles/jquery-ui.theme.min.css", - "styles/sites/todoist.css"], - "js": ["scripts/lib/jquery-2.1.1.min.js", "scripts/lib/jquery-ui.min.js", "scripts/lib/moment.min.js", "scripts/lib/moment-range.min.js", - "scripts/config.js", "scripts/TokenManager.js", "scripts/ApiService.js", "scripts/common.js", "scripts/TimerBase.js","scripts/TimerButton.js", - "scripts/third_party/todoist.js"] - }, { "matches": [ "*://*.wrike.com/workspace.htm*" diff --git a/package.json b/package.json index e3a4a81..8e7fca8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "timecamp-chrome-extension", - "version": "2.52.0", + "version": "2.52.37", "description": "TimeCamp Google Chrome Time Tracking Extension", "main": "index.js", "scripts": { diff --git a/scripts-2.0/style/freshdesk.scss b/scripts-2.0/style/freshdesk.scss new file mode 100644 index 0000000..cca09e9 --- /dev/null +++ b/scripts-2.0/style/freshdesk.scss @@ -0,0 +1,6 @@ +.tc-button.freshdesk { + &__task-details { + height: 100%; + margin-left: 5px; + } +} \ No newline at end of file diff --git a/scripts-2.0/style/gitlab.scss b/scripts-2.0/style/gitlab.scss new file mode 100644 index 0000000..a31c28b --- /dev/null +++ b/scripts-2.0/style/gitlab.scss @@ -0,0 +1,6 @@ +.tc-button.gitlab { + &__task-details { + margin: auto; + margin-right: 13px; + } +} \ No newline at end of file diff --git a/scripts-2.0/style/main.scss b/scripts-2.0/style/main.scss index 1d1ace1..f7f5dae 100644 --- a/scripts-2.0/style/main.scss +++ b/scripts-2.0/style/main.scss @@ -71,12 +71,14 @@ @import "focuster"; @import "fogbugz"; @import "freedcamp"; +@import "freshdesk"; @import "freshrelease"; @import "freshservice"; @import "gemini"; @import "getflow"; @import "gingkoapp"; @import "gogs"; +@import "gitlab"; @import "google-classroom"; @import "google-docs"; @import "google-inbox"; diff --git a/scripts-2.0/third_party/azure.js b/scripts-2.0/third_party/azure.js new file mode 100644 index 0000000..6e9906d --- /dev/null +++ b/scripts-2.0/third_party/azure.js @@ -0,0 +1,46 @@ +tcbutton.render('.work-item-form-headerContent:not(.tc,.flex-row)', {observe: true}, function (elem) { + if ($('.tc-button', elem)) { + return false; + } + var link, itemId, description, project, tagNames; + itemId = () => $('.work-item-form-id > span', elem).textContent; + description = () => $('.work-item-form-title input', elem).value; + project = $("input[aria-label='TimeCamp Project']") ? $("input[aria-label='TimeCamp Project']").value : $(".navigation-container .project-item .text-ellipsis").textContent; + tagNames = () => Array.from($$(".tags-items-container .tag-item:not(.tags-add-button) .tag-box")).map(e => e.innerText); + link = tcbutton.createTimerLink({ + description: () => "#" + itemId() + " " + description(), + projectName: project, + taskName: () => description(), + }); + link.style.display = "block"; + link.style.paddingTop = "0"; + link.style.paddingBottom = "0"; + link.style.marginBottom = "10px"; + link.style.cursor = 'pointer'; + link.style.width = 'fit-content'; + elem.appendChild(link); + }); + + tcbutton.render('.work-item-form-header:not(.tc,.flex-row)', {observe: true}, function (elem) { + if ($('.tc-button', elem)) { + return false; + } + var link, itemId, description, project; + itemId = () => $('.work-item-form-header > .body-xl', elem).textContent; + description = () => $('.work-item-title-textfield input', elem).value; + project = $("input[aria-label='TimeCamp Project']") ? $("input[aria-label='TimeCamp Project']").value : $(".navigation-container .project-item .text-ellipsis").textContent; + link = tcbutton.createTimerLink({ + description: () => "#" + itemId() + " " + description(), + projectName: project, + taskName: () => description() + }); + link.style.display = "block"; + link.style.paddingTop = "0"; + link.style.paddingBottom = "0"; + link.style.marginBottom = "10px"; + link.style.cursor = 'pointer'; + link.style.width = 'fit-content'; + + elem.appendChild(link); + }); + \ No newline at end of file diff --git a/scripts-2.0/third_party/freshdesk.js b/scripts-2.0/third_party/freshdesk.js new file mode 100644 index 0000000..951cbfa --- /dev/null +++ b/scripts-2.0/third_party/freshdesk.js @@ -0,0 +1,47 @@ +'use strict'; +const FRESHDESK = 'freshdesk' + +// Freshdesk mint (late 2018) +tcbutton.render('.page-actions__left:not(.tc)', { observe: true }, elem => { + if ($('.tc-button', elem)) { + return false; + } + const descriptionElem = $('.ticket-subject-heading'); + + // if there's no description element it's overview page, don't show + if (!descriptionElem) { + return false; + } + + const descriptionSelector = () => { + const ticketNumber = $('.breadcrumb__item.active').textContent.trim(); + // Remove other buttons/controls from the ticket subject + const subjectElement = $('.ticket-subject-heading').cloneNode(true); + for (const child of subjectElement.children) { + subjectElement.removeChild(child); + } + + return `${ticketNumber} ${subjectElement.textContent.trim()}`; + }; + + const link = tcbutton.createTimerLink({ + className: FRESHDESK, + additionalClasses: [FRESHDESK + '__task-details'], + description: descriptionSelector, + tags: () => { + const tagList = $('.ticket-tags ul'); + if (!tagList || + !tagList.children || + !tagList.children.length) { return []; } + + return [...tagList.querySelectorAll('li')] + .map(listItem => { + const content = listItem.querySelector('.tag-options'); + const tag = content ? content.textContent : ''; + return tag.trim().replace(/[\r\n\t]/ig, ''); /* UI has strange characters in the markup, let's avoid it */ + }); + } + }); + + elem.appendChild(link); +}); diff --git a/scripts-2.0/third_party/gitlab.js b/scripts-2.0/third_party/gitlab.js new file mode 100644 index 0000000..adaa7e8 --- /dev/null +++ b/scripts-2.0/third_party/gitlab.js @@ -0,0 +1,91 @@ +'use strict'; +const GITLAB = 'gitlab'; + +tcbutton.render( + '.issue-details .detail-page-description:not(.tc)', + { observe: false }, + function (elem) { + const prefix = [getId()].filter(Boolean) + .map(function (id) { return "#" + id; }) + .join(''); + const description = [prefix, getTitle(elem)].filter(Boolean).join(' '); + + insertButton($('.detail-page-header-actions'), description, true); + insertButton($('.time_tracker'), description); + } +); + +tcbutton.render( + '.merge-request > .detail-page-header:not(.tc)', + { observe: false }, + function (elem) { + const prefix = [getId()].filter(Boolean) + .map(function (id) { return "MR" + id + "::"; }) + .join(''); + const description = [prefix, getTitle(elem)].filter(Boolean).join(' '); + + insertButton($('.detail-page-header-actions'), description, true); + insertButton($('.time_tracker'), description); + } +); + +/** + * @param $el + * @param {String} description + * @param {boolean} prepend + */ +function insertButton($el, description, prepend = false) { + const link = tcbutton.createTimerLink({ + className: GITLAB, + additionalClasses: [GITLAB + '__task-details'], + description: description, + tags: tagsSelector, + projectName: getProjectSelector + }); + + if (prepend) { + $el.parentElement.insertBefore(link, $el); + } else { + $el.parentElement.appendChild(link, $el); + } +} + +function getTitle(parent) { + const el = parent.querySelector('.title'); + + return el ? el.textContent.trim() : ''; +} + +function getId() { + const pathname = window.location.pathname; + const pattern = /-\/(issues|merge_requests)\/(?\d+)/; + const result = pattern.test(pathname) + ? pathname.match(pattern) + : { groups: { id: '' } }; + const id = result.groups.id; + + return id; +} + +function getProjectSelector() { + const $el = $('.title .project-item-select-holder') || $('.breadcrumbs-list li:nth-last-child(3) .breadcrumb-item-text'); + return $el ? $el.textContent.trim() : ''; +} + +function tagsSelector() { + // GitLab 13.5 + const nodeList = document.querySelectorAll('div.labels span[data-qa-label-name]'); + + if (!nodeList) { + return []; + } + + const tags = []; + + for (const node of Object.values(nodeList)) { + const tagName = node.getAttribute('data-qa-label-name'); + tags.push(tagName); + } + + return tags; +} diff --git a/scripts-2.0/third_party/zendesk.js b/scripts-2.0/third_party/zendesk.js new file mode 100644 index 0000000..bacccd1 --- /dev/null +++ b/scripts-2.0/third_party/zendesk.js @@ -0,0 +1,107 @@ +'use strict'; + +// Zendesk new UI Jul 2021 +tcbutton.render( + '.omni-conversation-pane>div>div:first-child:not(.tc)', + { observe: true }, + (elem) => { + if ($('.tc-button', elem)) { + return false; + } + const getProject = () => { + const title = document.querySelector('title'); + return title ? title.textContent.trim() : ''; + }; + + const getDescription = () => { + const ticketId = document.querySelector('header div[data-selected=true]').attributes['data-entity-id'].value || '' + + const input = elem.querySelector('input[aria-label=Subject]'); + const title = (input ? input.value : '').trim(); + + return [`#${ticketId}`, title].filter(Boolean).join(' '); + }; + + const link = tcbutton.createTimerLink({ + buttonType: 'minimal', + className: 'zendesk--2021', + description: getDescription, + projectName: getProject + }); + + elem.appendChild(link); + } +); + +// Zendesk pre-2021 +tcbutton.render( + '.pane_header:not(.tc)', + { observe: true }, + function (elem) { + if ($('.tc-button', elem)) { + return false; + } + let description; + const projectName = $('title').textContent; + + const titleFunc = function () { + const titleElem = $('.editable .ember-view input', elem); + const ticketNum = location.href.match(/tickets\/(\d+)/); + + if (titleElem !== null) { + description = titleElem.value.trim(); + } + + if (ticketNum) { + description = '#' + ticketNum[1].trim() + ' ' + description; + } + return description; + }; + + const link = tcbutton.createTimerLink({ + className: 'zendesk', + description: titleFunc, + projectName: projectName && projectName.split(' - ').shift() + }); + + // Check for strange duplicate buttons. Don't know why this happens in Zendesk. + if (elem.querySelector('.tc-button')) { + elem.removeChild(elem.querySelector('.tc-button')); + } + + elem.insertBefore(link, elem.querySelector('.btn-group')); + } +); + +const getDescription = () => { + const ticketNum = location.href.match(/tickets\/(\d+)/); + + if (!ticketNum) return null; + const id = ticketNum[1].trim(); + const titleElem = document.querySelector( + `[data-side-conversations-anchor-id="${id}"] input[aria-label="Subject"]` + ); + if (!titleElem) return null; + + return '#' + id + ' ' + titleElem.value.trim(); +}; + +tcbutton.render( + '[data-test-id="customer-context-tab-navigation"]', + { observe: true }, + function (elem) { + // Manual check for existence in this SPA. + if ($('.tc-button', elem)) { + return false; + } + // If we can't get the description on this pass, let's skip and wait for the next one + if (!getDescription()) return; + + const link = tcbutton.createTimerLink({ + className: 'zendesk-agent-ws', + description: getDescription + }); + + elem.prepend(link); + } +); \ No newline at end of file