Last active
November 16, 2015 16:41
-
-
Save defr/bd3e0e1b0724a1e08121 to your computer and use it in GitHub Desktop.
greasemonkey
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Redmine Toggl Integration | |
| // @namespace http://ows.fr/redmine-toggl | |
| // @description Fournit un lien Toggl sur les tâches Redmine. | |
| // @grant GM_xmlhttpRequest | |
| // @include http://redmine.ows.fr/issues/* | |
| // ==/UserScript== | |
| /* | |
| Version 1.0 | |
| Release notes: | |
| - Affiche un lien à côté de "Mettre à jour" pour activer le timer Toggl | |
| si vous êtes connecté sur www.toggl.com | |
| - Ce lien est vert quand la tâche n'est pas en cours ("Start timer") | |
| - Ce lien est rouge quand la tâche est en cours ("Stop timer") | |
| - Si vous n'êtes pas connecté à toggl, | |
| un lien "Toggl log in" s'affiche et vous renvoie vers la connexion | |
| - L'affichage du lien dépend de la tâche active côté Toggl | |
| - Dès que l'on clique sur "Start timer", Toggl active un timer avec comme | |
| description "Tâche #XXXXX → Titre de la tâche" + sélection du projet adéquat | |
| - Dès que l'on clique sur "Stop timer", Toggl arrête la tâche | |
| - Si l'on clique sur "Start timer" alors qu'une tâche est déjà en cours côté Toggl, | |
| cette dernière va être arrêtée et la nouvelle commence | |
| - Si l'on clique sur "Update", ça pré-rempli les champs Statut, Assigné à, | |
| % réalisé et Sprint et met le focus sur les notes | |
| - Au scroll de la page, le titre "Tâche #XXXXX" et les liens "Start Toggl", | |
| "Mettre à jour"... passent en position fixé pour pouvoir | |
| commencer ou arrêter de n'importe où le timer | |
| INSTALLATION: | |
| FIREFOX: | |
| - Installer https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/ | |
| - Faites glisser ce user script dans Firefox | |
| CHROME: | |
| - Installer https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo?hl=en | |
| - Aller sur chrome://extensions/ | |
| - Cliquez sur "Options" de ce module | |
| - CLiquer sur l'onglet "Ajouter" qui se trouve en haut à droite | |
| - Y copier ce user script | |
| - Enregistrer avec l'icone "disquette" | |
| - Il devrait appaître dans l'onglet "Userscripts installés" | |
| */ | |
| function $(s, elem) { | |
| elem = elem || document; | |
| return elem.querySelector(s); | |
| } | |
| function createButton(params, text, className) { | |
| var button = document.createElement('button'), | |
| buttonText = text; | |
| button.className = 'icon ' + className; | |
| if (params.isStarted) { | |
| button.classList.add('active'); | |
| buttonText = 'Stop timer'; | |
| } | |
| button.appendChild(document.createTextNode(buttonText)); | |
| return button; | |
| } | |
| var title = $('h2'), | |
| links = $('#content .contextual'), | |
| sidebar = $('#sidebar'), | |
| content = $('#content'), | |
| positionRight = window.innerWidth - (content.offsetLeft + content.offsetWidth) - 2, | |
| togglbutton = { | |
| createTimerButton: function (params) { | |
| var button = createButton(params, 'Start timer', 'toggl'); | |
| button.addEventListener('click', function (e) { | |
| var opts, | |
| buttonText = 'Start timer'; | |
| if ('active' === button.classList.item(2)) { | |
| button.classList.remove('active'); | |
| TogglService.stopTimeEntry(); | |
| } | |
| else { | |
| button.classList.add('active'); | |
| buttonText = 'Stop timer'; | |
| opts = { | |
| type: 'timeEntry', | |
| projectId: params.projectId, | |
| description: params.description, | |
| projectName: params.projectName, | |
| billable: true | |
| }; | |
| TogglService.createTimeEntry(opts); | |
| } | |
| button.innerHTML = buttonText; | |
| }); | |
| return button; | |
| }, | |
| createUpdateButton: function (params) { | |
| var buttonUpdate = createButton(params, 'Update', 'update'); | |
| buttonUpdate.addEventListener('click', function (e) { | |
| var url = $('.author .user').href, | |
| urlSplit = url.split('/'); | |
| $('.icon.icon-edit').click(); | |
| $('#issue_status_id option[value="6"]').selected = 'selected'; | |
| $('#issue_assigned_to_id option[value="' + urlSplit[4] + '"]').selected = 'selected'; | |
| $('#issue_done_ratio option[value="100"]').selected = 'selected'; | |
| $('#issue_custom_field_values_23 option[value="Doing"]').selected = 'selected'; | |
| $('#issue_notes').focus(); | |
| }); | |
| return buttonUpdate; | |
| }, | |
| }, | |
| TogglService = { | |
| user: null, | |
| curEntryId: null, | |
| apiUrl: 'https://www.toggl.com/api/v8', | |
| fetchUser: function () { | |
| GM_xmlhttpRequest({ | |
| method: 'GET', | |
| url: TogglService.apiUrl + '/me?with_related_data=true', | |
| onload: function (response) { | |
| var markup, | |
| markup2, | |
| css = document.createElement('style'), | |
| styles = ''; | |
| if (200 === response.status) { | |
| var projectMap = {}, | |
| resp = JSON.parse(response.responseText); | |
| if (resp.data.projects) { | |
| resp.data.projects.forEach(function (project) { | |
| if (!project.server_deleted_at) { | |
| projectMap[project.name] = project.id; | |
| } | |
| }); | |
| } | |
| TogglService.user = resp.data; | |
| TogglService.user.projectMap = projectMap; | |
| var projectElem = $('h1'), | |
| description = $('#content h2').textContent + ' → ' + $('.subject h3').textContent, | |
| idLastTask = TogglService.user.time_entries.length - 1, | |
| lastTask = TogglService.user.time_entries[idLastTask], | |
| isStarted = false; | |
| if (lastTask.duration < 0 && lastTask.description === description) { | |
| isStarted = true; | |
| TogglService.curEntryId = lastTask['id']; | |
| } | |
| markup = togglbutton.createTimerButton({ | |
| description: description, | |
| projectName: projectElem && projectElem.textContent.split(' » ').pop(), | |
| isStarted: isStarted | |
| }); | |
| markup2 = togglbutton.createUpdateButton({}); | |
| } | |
| else { | |
| markup = document.createElement('a'); | |
| markup.href = 'http://www.toggl.com/login'; | |
| markup.target = '_blank'; | |
| markup.className = 'icon toggl'; | |
| markup.appendChild(document.createTextNode('Toggl log in')); | |
| } | |
| $('#content > .contextual').insertBefore(markup2, $('#content > .contextual').firstChild); | |
| $('#content > .contextual').insertBefore(markup, $('#content > .contextual').firstChild); | |
| styles += '#sidebar.fixed {position: fixed;right: 8px;top: 0;}'; | |
| styles += '.links, .h2 {background:#FFF;border-bottom: 1px solid #000;line-height: 20px;padding: 5px;position: fixed;top: 0;}'; | |
| styles += '.h2 {border-left: 1px solid #000;width: 30%}'; | |
| styles += '.links {border-right: 1px solid #000;margin: 0;right: ' + positionRight + 'px;text-align: right;width: 51%}'; | |
| styles += 'button.icon {border: none;cursor: pointer;}'; | |
| styles += '.toggl.active {color: red;}'; | |
| styles += '.toggl {color: #1ab351;padding: 0 0 0 20px;background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAArFJREFUOI1lkk1IVGEUhp/vu/fmjKPSDBGE2TjVIgnB6XeGKIrAUCpbBFEkBWVrhaIWLQoKN0GLIgtr0SKN6IfElWmIRVFoM2VqDuMPhZUmk+aUd+Z652thjqO9u3M473nPOe8RLEJH3c1hI9ztNbo/kjM2jq1pTHkLMLZu5mJzU1VT6N3tzHqRGXTXnFFGSyvOicnFfVFA3FvAh/Xrwkfqb/n/axAqr7CXdvdImUqlCdryPNR0EjVlpnNqicH7wKahA433VgPIWeXTajEZXSOv5iiOPQGUmFeTSYvityHfg7PnQgD6y7pbw8a1OubICIFRlI/hX4Oe7wVLgQZmWyep8TgC0EyTot7+EgCtalXhVc+Hj2llo7gQT+0FBALpcmLHfuLcXUqWv4jEmy4wLQQgYhN4goF8qf8jA6ALco8dwooM8OvGXZAa9miMidor6L61ZO30z64HGIkEe/0bq2Tu2I/5o63MY8n6En4/eYg9GgchQAiS4Shmx2ucweAC+xLfvqGnpJxPahogUPYMpFJMv2jDiowgUgqVTCJ0Y6HvUiJ/rcxPx/bIBDOfB8jeVYp0GMTvPCPR0YO2dgWO7QHMcFd6BQW4fF50ffNG+BRBAMpMMXX3Hu7z5xBZBn/anyOzXeQcOYoyf2O2vk1PYObm8DT0rl77Pjk5smOpZ78Rn7XI/jqOFekja8sGXPsP4tgSJNn5msnrd7C/xGaFgMS2ANcHohcFQMPhylDw1ZsSOTMz/0iGQFuejbJs1JiZuTnTHjf9JyrDFdXV/vRNHpXvG/T3RnzSshYeKgMKMN1uhirKhsouX1o950YaDSdOhYqjgyWuwWG0uc/8R0w6HXwvXBVrWeZ+fOl+Y1WmnQtQEQicrK08Xv8z/B41Ogq6ju4rpLmvp7450t8QjkbbM+v/Av9sFNxqULBbAAAAAElFTkSuQmCC) no-repeat left center;}'; | |
| styles += 'button.toggl {font-size: 1.2em;}'; | |
| styles += '.update {background: #fff;color: #169;font-size: 1em;}'; | |
| styles += '.update:hover {color: #B73535;}'; | |
| css.appendChild(document.createTextNode(styles)); | |
| $('head').appendChild(css); | |
| } | |
| }); | |
| }, | |
| createTimeEntry: function (timeEntry) { | |
| var start = new Date(), | |
| entry = { | |
| time_entry: { | |
| start: start.toISOString(), | |
| description: timeEntry.description, | |
| wid: TogglService.user.default_wid, | |
| pid: timeEntry.projectId || null, | |
| billable: timeEntry.billable || false, | |
| duration: - (start.getTime() / 1000), | |
| created_with: timeEntry.createdWith || 'TogglButton' | |
| } | |
| }; | |
| if (undefined !== timeEntry.projectName) { | |
| entry.time_entry.pid = TogglService.user.projectMap[timeEntry.projectName]; | |
| } | |
| GM_xmlhttpRequest({ | |
| url: TogglService.apiUrl + '/time_entries', | |
| method: 'POST', | |
| data: JSON.stringify(entry), | |
| headers: { | |
| 'Authorization': 'Basic ' + btoa(TogglService.user.api_token + ':api_token') | |
| }, | |
| onload: function (response) { | |
| var responseData, entryId; | |
| responseData = JSON.parse(response.responseText); | |
| entryId = responseData && responseData.data && responseData.data.id; | |
| TogglService.curEntryId = entryId; | |
| } | |
| }) | |
| }, | |
| stopTimeEntry: function (entryId) { | |
| entryId = entryId || TogglService.curEntryId; | |
| if (!entryId) { | |
| return; | |
| } | |
| GM_xmlhttpRequest({ | |
| method: 'PUT', | |
| url: TogglService.apiUrl + '/time_entries/' + entryId + '/stop', | |
| headers: { | |
| 'Authorization': 'Basic ' + btoa(TogglService.user.api_token + ':api_token') | |
| } | |
| }); | |
| }, | |
| }; | |
| TogglService.fetchUser(); | |
| window.addEventListener('scroll', function (e) { | |
| if (window.scrollY > title.offsetTop) { | |
| if (null === $('.h2')) { | |
| // Fixed. | |
| title.classList.add('h2'); | |
| links.classList.add('links'); | |
| sidebar.classList.add('fixed'); | |
| } | |
| } | |
| else { | |
| // Reset. | |
| title.classList.remove('h2'); | |
| links.classList.remove('links'); | |
| sidebar.classList.remove('fixed'); | |
| } | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment