您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Redmine shows friendly time in tickets
// ==UserScript== // @name Redmine Friendly Time // @namespace http://tampermonkey.net/ // @version 0.99.91 // @description Redmine shows friendly time in tickets // @author Massive Friendly Fire // @include http://redmine.m-games-ltd.com/* // @compatible firefox // @compatible chrome // @grant none // @homepageURL https://github.com/MassiveFriendlyFire/redmine-friendly-time#readme // @supportURL https://github.com/MassiveFriendlyFire/redmine-friendly-time/issues // ==/UserScript== //Description: //This script replaces time of update in redmine tickets //from inaccurate values e.g. "last updated about 2 hours" //to accurate values e.g "last updated 2 hours 13 minutes" (function () { //CORE var LOGGING_ENABLED = false; var MY_LOG = function (value) { if (LOGGING_ENABLED) { console.log(value); } }; var toastrCss = document.createElement('link'); toastrCss.href = 'https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css'; toastrCss.rel = 'stylesheet'; var toastrJs = document.createElement('script'); toastrJs.src = 'https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/js/toastr.min.js'; var FACss = document.createElement('link'); FACss.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css'; FACss.rel = 'stylesheet'; document.head.appendChild(toastrCss); document.head.appendChild(toastrJs); document.head.appendChild(FACss); //replace this regex if script is not working, it must match A title tags var mainRegex = /^(\d{2})\.(\d{2})\.(\d{4}) (\d{2}):(\d{2})$/; //define locale strings var ruStrings = ["дн.", "ч.", "мин.", "меньше 1 минуты", 'сек.']; var engStrings = ["days", "hour", "min", "right now"]; //default locale is russian var scriptStrings = ruStrings; //define CONSTS var ISSUE_PAUSED_STR = 'Приостановлена'; var ISSUE_IN_WORK_STR = 'В работе'; //DOCUMENT CONSTS var REDMINE_FORM_SUMBIT_ELEMENTS = document.getElementsByName('commit'); var ISSUE_STATUS_SELECT_OPTIONS_GROUP_ELEMENT = document.getElementById('issue_status_id'); var ISSUE_STATUS_STR_ELEMENT = document.getElementsByClassName('status attribute')[0].childNodes[1]; var EDIT_ISSUE_LINKS_PANEL_ELEMENT = document.getElementsByClassName('contextual')[1]; var EDIT_ISSUE_FORM_ELEMENT = document.getElementById('issue-form'); var EDIT_ISSUE_LABOUR_COSTS_ELEMENT = document.getElementById('time_entry_hours'); var EDIT_ISSUE_LABOUR_TYPE_SELECT_OPTIONS_GROUP_ELEMENT = document.getElementById('time_entry_activity_id'); var ISSUE_HISTORY_LIST_ELEMENT = document.getElementById('history'); var ISSUE_HISTORY_LAST_ELEMENT = getHistoryLastElement(ISSUE_HISTORY_LIST_ELEMENT); var MY_USER_ID = getMyUserId(document.getElementById('loggedas')); var ISSUE_HISTORY_LAST_ELEMENT_UPDATE_TIME = ISSUE_HISTORY_LAST_ELEMENT.children[0].children[0].children[3].title; //VARS var VO_links = document.getElementsByTagName("a"); var VO_milliseconds; var VO_days; var VO_hours; var VO_minutes; var VO_seconds; var VO_currentTime; var VO_labourCostsPrevValue; var VO_issuePaused = true; var VO_manualEdit = false; //define methods /** * format time to human readable * @param milliseconds * @returns {string} */ var formatMilliseconds = function (milliseconds) { var seconds = Math.round(milliseconds / 1000 % 60) - 1; var minutes = parseInt((milliseconds / (1000 * 60)) % 60); var hours = parseInt((milliseconds / (1000 * 60 * 60)) % 24); var days = parseInt(milliseconds / (1000 * 60 * 60 * 24)); minutes = (minutes < 10) ? "0" + minutes : minutes; var result = ""; if (days < 1 && hours < 1 && minutes < 1) { result = scriptStrings[3]; } else { if (days > 0) { result = days + " " + scriptStrings[0] + " "; } if (days === 0 && hours === 0) { result = minutes + " " + scriptStrings[2] + ' ' + seconds + ' ' + scriptStrings[4]; } else { result = result + hours + " " + scriptStrings[1] + " " + minutes + " " + scriptStrings[2]+ ' ' + seconds + ' ' + scriptStrings[4]; } } MY_LOG('result = ' + result); return result; }; /** * check if string is date and count time betweet date and current time * @param string * @returns {*} */ var getMillisecondsIfStringIsDate = function (string) { var matches = string.match(mainRegex); if (matches !== null) { var year = parseInt(matches[3], 10); var month = parseInt(matches[2], 10) - 1; // months are 0-11 var day = parseInt(matches[1], 10); var hour = parseInt(matches[4], 10); var minute = parseInt(matches[5], 10); var second = 0; //check first format: dd.mm.yyyy hh:mm var parsedDate = new Date(year, month, day, hour, minute, second); if (parsedDate.getFullYear() === year || parsedDate.getMonth() == month || parsedDate.getDate() === day || parsedDate.getHours() === hour || parsedDate.getMinutes() === minute) { return Math.abs(VO_currentTime - parsedDate); } //check second format: mm.dd.yyyy hh:mm parsedDate = new Date(year, day - 1, month + 1, hour, minute, second); if (parsedDate.getFullYear() === year || parsedDate.getMonth() == day - 1 || parsedDate.getDate() === month + 1 || parsedDate.getHours() === hour || parsedDate.getMinutes() === minute) { return Math.abs(VO_currentTime - parsedDate); } //not found } return null; }; /** * load last element from history of redmine issue changes * @returns {*} */ function getHistoryLastElement() { var last; if (ISSUE_HISTORY_LIST_ELEMENT.children.length > 0) { last = ISSUE_HISTORY_LIST_ELEMENT.children[ISSUE_HISTORY_LIST_ELEMENT.children.length - 1]; } else { MY_LOG('ERROR: Unable to get last history element'); return; } if (last.id.indexOf('change') === -1) { MY_LOG('ERROR: Last history element is not a change'); return; } return last; } /** * in redmine * @param loggedAsDiv * @returns {*} */ function getMyUserId(loggedAsDiv) { var splitted = loggedAsDiv.children[0].href.split('/'); if (splitted.length < 1) { MY_LOG('ERROR: Something wrong with "LOGGED AS" div. Unable to get current user id'); return; } return splitted[splitted.length - 1]; } /** * reload vars for time after last update */ function reloadIssueTimeVars() { VO_currentTime = new Date(); VO_milliseconds = getMillisecondsIfStringIsDate(ISSUE_HISTORY_LAST_ELEMENT_UPDATE_TIME); VO_minutes = parseInt((VO_milliseconds / (1000 * 60)) % 60); VO_hours = parseInt((VO_milliseconds / (1000 * 60 * 60)) % 24); VO_days = parseInt(VO_milliseconds / (1000 * 60 * 60 * 24)); VO_seconds = parseInt((VO_milliseconds / 1000) % 60); } /** * set status */ function prepareEditIssueStatus() { var findValue; var toggleFrom = ISSUE_PAUSED_STR; var toggleTo = ISSUE_IN_WORK_STR; reloadIssueStatus(); if (!VO_issuePaused) { var temp = toggleFrom; toggleFrom = toggleTo; toggleTo = temp; } for (var i = 0; i < ISSUE_STATUS_SELECT_OPTIONS_GROUP_ELEMENT.length; i++) { if (ISSUE_STATUS_SELECT_OPTIONS_GROUP_ELEMENT[i].innerHTML === toggleTo) { findValue = ISSUE_STATUS_SELECT_OPTIONS_GROUP_ELEMENT[i].value; break; } } ISSUE_STATUS_SELECT_OPTIONS_GROUP_ELEMENT.value = findValue; ISSUE_STATUS_SELECT_OPTIONS_GROUP_ELEMENT.style.background = 'lightgreen'; MY_LOG('toggleStatus'); } /** * set labour costs */ function prepareEditIssueLabourCosts() { if (VO_minutes === undefined) { reloadIssueTimeVars(); } if (VO_issuePaused) { return; } MY_LOG('prevlabcosts ' + VO_labourCostsPrevValue); if (!VO_manualEdit && VO_labourCostsPrevValue !== undefined && EDIT_ISSUE_LABOUR_COSTS_ELEMENT.value !== VO_labourCostsPrevValue) { EDIT_ISSUE_LABOUR_COSTS_ELEMENT.style.background = ''; EDIT_ISSUE_LABOUR_TYPE_SELECT_OPTIONS_GROUP_ELEMENT.value = ''; EDIT_ISSUE_LABOUR_TYPE_SELECT_OPTIONS_GROUP_ELEMENT.style.background = ''; VO_manualEdit = true; return; } if (VO_manualEdit) { return; } var hoursValue = VO_days * 24 + VO_hours + VO_minutes / 60 + VO_seconds / 3600; VO_labourCostsPrevValue = roundUpto(hoursValue, 5); EDIT_ISSUE_LABOUR_COSTS_ELEMENT.value = VO_labourCostsPrevValue; EDIT_ISSUE_LABOUR_COSTS_ELEMENT.style.background = 'lightgreen'; } /** * set labour type */ function prepareEditIssueLabourType() { if (VO_issuePaused) { return; } EDIT_ISSUE_LABOUR_TYPE_SELECT_OPTIONS_GROUP_ELEMENT.value = 9; EDIT_ISSUE_LABOUR_TYPE_SELECT_OPTIONS_GROUP_ELEMENT.style.background = 'lightgreen'; } /** * create link for toggle issue between work-paused */ function createTaskEasyToggleHref() { var ICON = document.createElement('i'); ICON.classList.add('fa'); var LINK = document.createElement('a'); LINK.id = 'easy-toggle-link'; LINK.href = '#'; var title1 = ' Взять в работу'; var title2 = ' Приостановить'; reloadIssueStatus(); if (VO_issuePaused) { ICON.classList.add('fa-play'); LINK.innerHTML = title1; } else { ICON.classList.add('fa-pause'); LINK.innerHTML = title2; } EDIT_ISSUE_LINKS_PANEL_ELEMENT.prepend(LINK); EDIT_ISSUE_LINKS_PANEL_ELEMENT.prepend(ICON); LINK.onclick = function () { easyTaskEasyToggleOnclickAction() }; } /** * action for toggle link */ function easyTaskEasyToggleOnclickAction() { MY_LOG('submitting form...'); if (isSimpleSubmitAllowed()) { if (!VO_issuePaused) { showNotificationPopup('Приостанавливаю задачу с трудозатратами: ' + formatMilliseconds(VO_milliseconds)); setTimeout(function() { EDIT_ISSUE_FORM_ELEMENT.submit(); }, 1500); } else { EDIT_ISSUE_FORM_ELEMENT.submit(); } } else { thereIsNotAllAreSimpleWithYourIssue(); } } /** * check all fields are good to easy change status * @returns {boolean} */ function isSimpleSubmitAllowed() { var userHrefValue = ISSUE_HISTORY_LAST_ELEMENT.children[0].children[0].children[2].href; var splitted = userHrefValue.split('/'); if (splitted.length < 1) { MY_LOG('ERROR: Something wrong with user href value'); return false; } var userId = splitted[splitted.length - 1]; MY_LOG('user id = ' + userId); if (userId !== MY_USER_ID) { MY_LOG('DEBUG: User Id mismatch'); return false; } if (VO_issuePaused) { return true; } var details = ISSUE_HISTORY_LAST_ELEMENT.children[0].children[1]; MY_LOG('details.children.length ' + details.children.length); if (details.children.length < 1) { MY_LOG('Details looks not like status changed message.. Check failed.'); return false; } for (var i = 0; i < details.children.length; i++) { var liElement = details.children[i]; if (liElement.tagName != 'LI') { MY_LOG('Unable to find status change in last history element... Check failed.'); return false; } if (liElement.children[0].innerHTML === 'Статус') { if (liElement.children[2].innerHTML === 'В работе') { MY_LOG('DEBUG: Issue In Work By You and You can Simply Submit'); return true; } if (VO_issuePaused) { MY_LOG('DEBUG: Issue Paused and You can Simply Submit'); return true; } } } MY_LOG('Unable to find status change in last history element... Check failed.'); return false; } /** * reload status */ function reloadIssueStatus() { MY_LOG('ISSUE_STATUS_STR_ELEMENT.innerHTML ' + ISSUE_STATUS_STR_ELEMENT.innerHTML); MY_LOG('ISSUE_PAUSED_STR ' + ISSUE_PAUSED_STR); MY_LOG('ISSUE_IN_WORK_STR ' + ISSUE_IN_WORK_STR); if (ISSUE_STATUS_STR_ELEMENT.innerHTML === ISSUE_PAUSED_STR) { MY_LOG('reloadIssueStatus ' + true); VO_issuePaused = true; } else if (ISSUE_STATUS_STR_ELEMENT.innerHTML === ISSUE_IN_WORK_STR) { MY_LOG('reloadIssueStatus ' + false); VO_issuePaused = false; } } /** * if not all good for easy toggle issue running this method * @returns {boolean} */ function thereIsNotAllAreSimpleWithYourIssue() { MY_LOG('notAllAreSimpleWithYourIssue'); showAndScrollTo("update", "issue_notes"); //REDMINE FUNCTION. showNotificationPopup('Не всё так просто с вашей задачей... Проверьте всё еще разок. Может быть длительность трудозатрат нужно изменить?'); return false; } /** * external notifications added in CORE section, enjoy * @param caption */ function showNotificationPopup(caption, noTimeout) { var timeout = 750; if (noTimeout) { timeout = 0; } setTimeout(function () { toastr.info(caption) }, timeout); } /** * math operation round to decimal places * @param number * @param upto * @returns {string} */ function roundUpto(number, upto) { return new Number(number).toFixed(upto); } function updateTimes() { for (var i = 0; i < VO_links.length; i++) { var milliseconds = getMillisecondsIfStringIsDate(VO_links[i].title); if (milliseconds !== null) { VO_links[i].innerHTML = formatMilliseconds(milliseconds); } } } //Run stage //iterate links and replace inner html if link matches date time reloadIssueTimeVars(); updateTimes(); createTaskEasyToggleHref(); //autochange options values for edit mode setTimeout(function () { setInterval(function() { reloadIssueTimeVars(); MY_LOG(VO_currentTime); updateTimes(); prepareEditIssueLabourCosts(); }, 25000); setInterval(function() { reloadIssueTimeVars(); prepareEditIssueLabourCosts(); }, 1500); reloadIssueTimeVars(); prepareEditIssueStatus(); prepareEditIssueLabourCosts(); prepareEditIssueLabourType(); }, 600); })();