Skip to content

Instantly share code, notes, and snippets.

@defr
Last active November 16, 2015 16:41
Show Gist options
  • Select an option

  • Save defr/bd3e0e1b0724a1e08121 to your computer and use it in GitHub Desktop.

Select an option

Save defr/bd3e0e1b0724a1e08121 to your computer and use it in GitHub Desktop.
greasemonkey
// ==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