您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Library for Toggl-Button scripts used on platforms like drupal, github, youtrack and others.
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.org/scripts/2670/247800/TogglLibrary.js
/*------------------------------------------------------------------------ * JavaScript Library for Toggl-Button for GreaseMonkey * * (c) Jürgen Haas, PARAGON Executive Services GmbH * Version: 1.9 * * @see https://gitlab.paragon-es.de/toggl-button/core *------------------------------------------------------------------------ */ function TogglButtonGM(selector, renderer) { var $activeApiUrl = null, $apiUrl = "https://www.toggl.com/api/v7", $newApiUrl = "https://www.toggl.com/api/v8", $legacyApiUrl = "https://new.toggl.com/api/v8", $triedAlternative = false, $addedDynamicListener = false, $api_token = null, $default_wid = null, $clientMap = {}, $projectMap = {}, $instances = {}; init(selector, renderer); function init(selector, renderer, apiUrl) { var timeNow = new Date().getTime(), timeAuth = GM_getValue('_authenticated', 0); apiUrl = apiUrl || $newApiUrl; $api_token = GM_getValue('_api_token', false); if ($api_token && (timeNow - timeAuth) < (6*60*60*1000)) { $activeApiUrl = GM_getValue('_api_url', $newApiUrl); $default_wid = GM_getValue('_default_wid', 0); $clientMap = JSON.parse(GM_getValue('_clientMap', {})); $projectMap = JSON.parse(GM_getValue('_projectMap', {})); if ($activeApiUrl == $legacyApiUrl) { // See issue #22. $activeApiUrl = $newApiUrl; GM_setValue('_api_url', $activeApiUrl); } render(selector, renderer); return; } var headers = {}; if ($api_token) { headers = { "Authorization": "Basic " + btoa($api_token + ':api_token') }; } $activeApiUrl = apiUrl; GM_xmlhttpRequest({ method: "GET", url: apiUrl + "/me?with_related_data=true", headers: headers, onload: function(result) { if (result.status === 200) { var resp = JSON.parse(result.responseText); $clientMap[0] = 'No Client'; if (resp.data.clients) { resp.data.clients.forEach(function (client) { $clientMap[client.id] = client.name; }); } if (resp.data.projects) { resp.data.projects.forEach(function (project) { if ($clientMap[project.cid] == undefined) { project.cid = 0; } if (project.active) { $projectMap[project.id] = { id: project.id, cid: project.cid, name: project.name, billable: project.billable }; } }); } GM_setValue('_authenticated', new Date().getTime()); GM_setValue('_api_token', resp.data.api_token); GM_setValue('_api_url', $activeApiUrl); GM_setValue('_default_wid', resp.data.default_wid); GM_setValue('_clientMap', JSON.stringify($clientMap)); GM_setValue('_projectMap', JSON.stringify($projectMap)); $api_token = resp.data.api_token; $default_wid = resp.data.default_wid; render(selector, renderer); } else if (!$triedAlternative) { $triedAlternative = true; if (apiUrl === $apiUrl) { init(selector, renderer, $newApiUrl); } else if (apiUrl === $newApiUrl) { init(selector, renderer, $apiUrl); } } else if ($api_token) { // Delete the API token and try again GM_setValue('_api_token', false); $triedAlternative = false; init(selector, renderer, $newApiUrl); } else { var wrapper = document.createElement('div'), content = createTag('div', 'content'), link = createLink('login', 'a', 'https://new.toggl.com/', 'Login'); GM_addStyle(GM_getResourceText('togglStyle')); link.setAttribute('target', '_blank'); wrapper.setAttribute('id', 'toggl-button-auth-failed'); content.appendChild(document.createTextNode('Authorization to your Toggl account failed!')); content.appendChild(link); wrapper.appendChild(content); document.querySelector('body').appendChild(wrapper); } } }); } function render(selector, renderer) { if (selector == null) { return; } var i, len, elems = document.querySelectorAll(selector + ':not(.toggl)'); for (i = 0, len = elems.length; i < len; i += 1) { elems[i].classList.add('toggl'); $instances[i] = new TogglButtonGMInstance(renderer(elems[i])); } if (!$addedDynamicListener) { $addedDynamicListener = true; document.addEventListener('TogglButtonGMUpdateStatus', function() { GM_xmlhttpRequest({ method: "GET", url: $activeApiUrl + "/time_entries/current", headers: { "Authorization": "Basic " + btoa($api_token + ':api_token') }, onload: function (result) { if (result.status === 200) { var resp = JSON.parse(result.responseText), data = resp.data || false; for (i in $instances) { $instances[i].checkCurrentLinkStatus(data); } } } }); }); window.addEventListener('focus', function() { document.dispatchEvent(new CustomEvent('TogglButtonGMUpdateStatus')); }); if (selector !== 'body') { document.body.addEventListener('DOMSubtreeModified', function () { setTimeout(function () { render(selector, renderer); }, 1000); }); } } } this.clickLinks = function() { for (i in $instances) { $instances[i].clickLink(); } }; this.getCurrentTimeEntry = function(callback) { GM_xmlhttpRequest({ method: "GET", url: $activeApiUrl + "/time_entries/current", headers: { "Authorization": "Basic " + btoa($api_token + ':api_token') }, onload: function (result) { if (result.status === 200) { var resp = JSON.parse(result.responseText), data = resp.data || false; if (data) { callback(data.id, true); } } } }); }; this.stopTimeEntry = function(entryId, asCallback) { if (entryId == null) { if (asCallback) { return; } this.getCurrentTimeEntry(this.stopTimeEntry); return; } GM_xmlhttpRequest({ method: "PUT", url: $activeApiUrl + "/time_entries/" + entryId + "/stop", headers: { "Authorization": "Basic " + btoa($api_token + ':api_token') }, onload: function () { document.dispatchEvent(new CustomEvent('TogglButtonGMUpdateStatus')); } }); }; function TogglButtonGMInstance(params) { var $curEntryId = null, $isStarted = false, $link = null, $generalInfo = null, $buttonTypeMinimal = false, $projectSelector = window.location.host, $projectId = null, $projectSelected = false, $projectSelectElem = null, $stopCallback = null, $tags = params.tags || []; this.checkCurrentLinkStatus = function (data) { var started, updateRequired = false; if (!data) { if ($isStarted) { updateRequired = true; started = false; } } else { if ($generalInfo != null) { if (!$isStarted || ($curEntryId != null && $curEntryId != data.id)) { $curEntryId = data.id; $isStarted = false; } } if ($curEntryId == data.id) { if (!$isStarted) { updateRequired = true; started = true; } } else { if ($isStarted) { updateRequired = true; started = false; } } } if (updateRequired) { if (!started) { $curEntryId = null; } if ($link != null) { updateLink(started); } if ($generalInfo != null) { if (data) { var projectName = 'No project', clientName = 'No client'; if (data.pid !== undefined) { if ($projectMap[data.pid] == undefined) { GM_setValue('_authenticated', 0); window.location.reload(); return; } projectName = $projectMap[data.pid].name; clientName = $clientMap[$projectMap[data.pid].cid]; } var content = createTag('div', 'content'), contentClient = createTag('div', 'client'), contentProject = createTag('div', 'project'), contentDescription = createTag('div', 'description'); contentClient.innerHTML = clientName; contentProject.innerHTML = projectName; contentDescription.innerHTML = data.description; content.appendChild(contentClient); content.appendChild(contentProject); content.appendChild(contentDescription); while ($generalInfo.firstChild) { $generalInfo.removeChild($generalInfo.firstChild); } $generalInfo.appendChild(content); } updateGeneralInfo(started); } } }; this.clickLink = function (data) { $link.dispatchEvent(new CustomEvent('click')); }; createTimerLink(params); function createTimerLink(params) { GM_addStyle(GM_getResourceText('togglStyle')); if (params.generalMode !== undefined && params.generalMode) { $generalInfo = document.createElement('div'); $generalInfo.id = 'toggl-button-gi-wrapper'; $generalInfo.addEventListener('click', function (e) { e.preventDefault(); $generalInfo.classList.toggle('collapsed'); }); document.querySelector('body').appendChild($generalInfo); document.dispatchEvent(new CustomEvent('TogglButtonGMUpdateStatus')); return; } if (params.projectIds !== undefined) { $projectSelector += '-' + params.projectIds.join('-'); } if (params.stopCallback !== undefined) { $stopCallback = params.stopCallback; } updateProjectId(); $link = createLink('toggl-button'); $link.classList.add(params.className); if (params.buttonType === 'minimal') { $link.classList.add('min'); $link.removeChild($link.firstChild); $buttonTypeMinimal = true; } $link.addEventListener('click', function (e) { var opts = ''; e.preventDefault(); if ($isStarted) { stopTimeEntry(); } else { var billable = false; if ($projectId != undefined && $projectId > 0) { billable = $projectMap[$projectId].billable; } opts = { $projectId: $projectId || null, billable: billable, description: invokeIfFunction(params.description), createdWith: 'TogglButtonGM - ' + params.className }; createTimeEntry(opts); } return false; }); // new button created - reset state $isStarted = false; // check if our link is the current time entry and set the state if it is checkCurrentTimeEntry({ $projectId: $projectId, description: invokeIfFunction(params.description) }); document.querySelector('body').classList.add('toggl-button-available'); if (params.targetSelectors == undefined) { var wrapper, existingWrapper = document.querySelectorAll('#toggl-button-wrapper'), content = createTag('div', 'content'); content.appendChild($link); content.appendChild(createProjectSelect()); if (existingWrapper.length > 0) { wrapper = existingWrapper[0]; while (wrapper.firstChild) { wrapper.removeChild(wrapper.firstChild); } wrapper.appendChild(content); } else { wrapper = document.createElement('div'); wrapper.id = 'toggl-button-wrapper'; wrapper.appendChild(content); document.querySelector('body').appendChild(wrapper); } } else { var elem = params.targetSelectors.context || document; if (params.targetSelectors.link != undefined) { elem.querySelector(params.targetSelectors.link).appendChild($link); } if (params.targetSelectors.projectSelect != undefined) { elem.querySelector(params.targetSelectors.projectSelect).appendChild(createProjectSelect()); } } return $link; } function createTimeEntry(timeEntry) { var start = new Date(); GM_xmlhttpRequest({ method: "POST", url: $activeApiUrl + "/time_entries", headers: { "Authorization": "Basic " + btoa($api_token + ':api_token') }, data: JSON.stringify({ time_entry: { start: start.toISOString(), description: timeEntry.description, wid: $default_wid, pid: timeEntry.$projectId || null, billable: timeEntry.billable || false, duration: -(start.getTime() / 1000), tags: $tags, created_with: timeEntry.createdWith || 'TogglButtonGM' } }), onload: function (res) { var responseData, entryId; responseData = JSON.parse(res.responseText); entryId = responseData && responseData.data && responseData.data.id; $curEntryId = entryId; document.dispatchEvent(new CustomEvent('TogglButtonGMUpdateStatus')); } }); } function checkCurrentTimeEntry(params) { GM_xmlhttpRequest({ method: "GET", url: $activeApiUrl + "/time_entries/current", headers: { "Authorization": "Basic " + btoa($api_token + ':api_token') }, onload: function (result) { if (result.status === 200) { var resp = JSON.parse(result.responseText); if (resp == null) { return; } if (params.description === resp.data.description) { $curEntryId = resp.data.id; updateLink(true); } } } }); } function stopTimeEntry(entryId) { entryId = entryId || $curEntryId; if (!entryId) { return; } GM_xmlhttpRequest({ method: "PUT", url: $activeApiUrl + "/time_entries/" + entryId + "/stop", headers: { "Authorization": "Basic " + btoa($api_token + ':api_token') }, onload: function (result) { $curEntryId = null; document.dispatchEvent(new CustomEvent('TogglButtonGMUpdateStatus')); if (result.status === 200) { var resp = JSON.parse(result.responseText), data = resp.data || false; if (data) { if ($stopCallback !== undefined && $stopCallback !== null) { var currentdate = new Date(); $stopCallback((currentdate.getTime() - (data.duration * 1000)), data.duration); } } } } }); } function createTag(name, className, innerHTML) { var tag = document.createElement(name); tag.className = className; if (innerHTML) { tag.innerHTML = innerHTML; } return tag; } function createLink(className, tagName, linkHref, linkText) { // Param defaults tagName = tagName || 'a'; linkHref = linkHref || '#'; linkText = linkText || 'Start timer'; var link = createTag(tagName, className); if (tagName === 'a') { link.setAttribute('href', linkHref); } link.appendChild(document.createTextNode(linkText)); return link; } function updateGeneralInfo(started) { if (started) { $generalInfo.classList.add('active'); } else { $generalInfo.classList.remove('active'); } $isStarted = started; } function updateLink(started) { var linkText, color = ''; if (started) { document.querySelector('body').classList.add('toggl-button-active'); $link.classList.add('active'); color = '#1ab351'; linkText = 'Stop timer'; } else { document.querySelector('body').classList.remove('toggl-button-active'); $link.classList.remove('active'); linkText = 'Start timer'; } $isStarted = started; $link.setAttribute('style', 'color:'+color+';'); if (!$buttonTypeMinimal) { $link.innerHTML = linkText; } $projectSelectElem.disabled = $isStarted; } function updateProjectId(id) { id = id || GM_getValue($projectSelector, 0); $projectSelected = (id != 0); if (id <= 0) { $projectId = null; } else { $projectId = id; } if ($projectSelectElem != undefined) { $projectSelectElem.value = id; $projectSelectElem.disabled = $isStarted; } GM_setValue($projectSelector, id); if ($link != undefined) { if ($projectSelected) { $link.classList.remove('hidden'); } else { $link.classList.add('hidden'); } } } function invokeIfFunction(trial) { if (trial instanceof Function) { return trial(); } return trial; } function createProjectSelect() { var pid, wrapper = createTag('div', 'toggl-button-project-select'), noneOptionAdded = false, noneOption = document.createElement('option'), emptyOption = document.createElement('option'), resetOption = document.createElement('option'); $projectSelectElem = createTag('select'); // None Option to indicate that a project should be selected first if (!$projectSelected) { noneOption.setAttribute('value', '0'); noneOption.text = '- First select a project -'; $projectSelectElem.appendChild(noneOption); noneOptionAdded = true; } // Empty Option for tasks with no project emptyOption.setAttribute('value', '-1'); emptyOption.text = 'No Project'; $projectSelectElem.appendChild(emptyOption); var optgroup, project, clientMap = []; for (pid in $projectMap) { //noinspection JSUnfilteredForInLoop project = $projectMap[pid]; if (clientMap[project.cid] == undefined) { optgroup = createTag('optgroup'); optgroup.label = $clientMap[project.cid]; clientMap[project.cid] = optgroup; $projectSelectElem.appendChild(optgroup); } else { optgroup = clientMap[project.cid]; } var option = document.createElement('option'); option.setAttribute('value', project.id); option.text = project.name; optgroup.appendChild(option); } // Reset Option to reload settings and projects from Toggl resetOption.setAttribute('value', 'RESET'); resetOption.text = 'Reload settings'; $projectSelectElem.appendChild(resetOption); $projectSelectElem.addEventListener('change', function () { if ($projectSelectElem.value == 'RESET') { GM_setValue('_authenticated', 0); window.location.reload(); return; } if (noneOptionAdded) { $projectSelectElem.removeChild(noneOption); noneOptionAdded = false; } updateProjectId($projectSelectElem.value); }); updateProjectId($projectId); wrapper.appendChild($projectSelectElem); return wrapper; } } }