Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions integrationList.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
10 changes: 0 additions & 10 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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*"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
6 changes: 6 additions & 0 deletions scripts-2.0/style/freshdesk.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.tc-button.freshdesk {
&__task-details {
height: 100%;
margin-left: 5px;
}
}
6 changes: 6 additions & 0 deletions scripts-2.0/style/gitlab.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.tc-button.gitlab {
&__task-details {
margin: auto;
margin-right: 13px;
}
}
2 changes: 2 additions & 0 deletions scripts-2.0/style/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
46 changes: 46 additions & 0 deletions scripts-2.0/third_party/azure.js
Original file line number Diff line number Diff line change
@@ -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);
});

47 changes: 47 additions & 0 deletions scripts-2.0/third_party/freshdesk.js
Original file line number Diff line number Diff line change
@@ -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);
});
91 changes: 91 additions & 0 deletions scripts-2.0/third_party/gitlab.js
Original file line number Diff line number Diff line change
@@ -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)\/(?<id>\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;
}
107 changes: 107 additions & 0 deletions scripts-2.0/third_party/zendesk.js
Original file line number Diff line number Diff line change
@@ -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);
}
);