您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds colored sections, extra functionality, and avatars to Gitlab. For avatars, detects locally-running image server to use for replacements of avatars. Use http-server (https://www.npmjs.com/package/http-server) for node.js for simple image hosting. Recommend image size of 100x100.
当前为
// ==UserScript== // @name Gitlab Mods // @namespace COMDSPDSA // @version 3.3 // @description Adds colored sections, extra functionality, and avatars to Gitlab. For avatars, detects locally-running image server to use for replacements of avatars. Use http-server (https://www.npmjs.com/package/http-server) for node.js for simple image hosting. Recommend image size of 100x100. // @author Dan Overlander // @include */gitlab.dell.com* // @require https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js // @require https://greasyfork.org/scripts/23115-tampermonkey-support-library/code/Tampermonkey%20Support%20Library.js?version=698066 // @require https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.2/underscore-min.js // @require https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment-with-locales.min.js // @grant GM_setValue // @grant GM_getValue // ==/UserScript== // Since v03.20: Moving .fingery class to global script. Moving out setTamperIcon // Since v03.10: Made comparisonSize font alteration more inclusive. Filtering out "Americas" from avatar names. Removes vertical margin from fingery class // Since v03.00: Set title TFS/PT scan to use case-insensivite checks // Since v02.20: Links TFS and PT values in title to their respective URLs. Adds Font+/- button for comparison text // Since v02.10: Since Gitlab removed their Expand All button (?!), this adds it back in... // Since v02.00: Altered the logic of colorizing discussions to search for a more universal pass-fail value. Changes Discussions buttons from icon to text (and is therefore larger) // Since v01.81: Removed old coffee cup button code. Added Collapse All Button with diff-collapsing function. Bug tweaks. // Since v01.80: Some conversations were not being colorized. // Since v01.75: Removes coffee-cup as it is native functionality, now. Yay Gitlab! Fixes a name-finding method that sometimes forgot to add a comma after spaces, for avatar images. // Since v01.7: Fixes the click functionality of the tamperlabel, also makes it look clickable // Since v01.6: Adds ability to hide header messages. Adds ability to erase Gitlab Mods memory by clicking on Tampermonkey icon (on this page, a battery in the lower-right corner) // Since v01.5: Added a conditional close-footer button, close broadcast message button. Adds memory states for these buttons (only for messages already seen!) as well as the close-tree (coffee) button. Expands breadcrumb dropdown. // Since v01.4: Linked the notifications to the TODO page. Removed a log statement. Resets TODO page title when timer runs out if reload is cancelled. Slows down reminder by 1 hour (up to 8 times) because coming back to your computer after an extended period and receiving TONS of notifications is annoying // Since v01.3: Re-enables the Tampericon // Since v01.2: Adds "hide tree" button (looks like a coffee cup) // Since v01.1: Updates document title with countdown timer indication. Adds hourly reminder of existing TODOs. Removes conversation button from non-conversation pages. // Since v01.0: Added the countdown indicator to the page title for TODO indication; cancelling reverts the title to original // Since v00.0: init, copying from GIT Avatars script /* * tm is an object included via @require from DorkForce's Tampermonkey Assist script */ (function() { 'use strict'; var TIMEOUT = 750, avatarHost = 'localhost:8080/', // for setPhoto pingPhoto = '!none', // pinging for setPhoto imageExt = '.png', global = { scriptName: 'Gitlab Mods', prefsName: 'gitlabPrefs', areClassesAdded: false, arePrButtonsAdded: false, areInterfaceButtonsAdded: false, todoMonitorInitialized: false, titlesLinked: false, todoTimer: undefined, isMouseMoved: false, prefs: undefined, reload: { lapsed: 0, timespan: 10000, title: document.title }, defaultLineSize: '12px', smallLineSize: '10px' }, properName = function(thisName) { var firstName = '', lastName = '', midName = ''; thisName = thisName .replace('https://gitlab.dell.com/', '') .replace(' - Dell Team', '') .replace('\'s avatar', '') .replace('@', '') .replace(/@/g, '') .replace(/\//g, '') .replace(/_/g, '-'); firstName = thisName.substring(0, thisName.indexOf('-')); lastName = thisName.substring(thisName.indexOf('-')+1, thisName.length); if ((firstName.length === 0 || lastName.length === 0) && thisName.indexOf(',') < 0) { return; } if (thisName.indexOf(',') < 0) { thisName = lastName + ', ' + firstName; } if (thisName.indexOf('-') > 0) { midName = thisName.substring(0, thisName.indexOf('-')); thisName = thisName.substring(thisName.indexOf('-')+1, thisName.length); thisName = thisName + ' ' + midName; } thisName = thisName .replace(/(\r\n\t|\n|\r\t)/gm,'') // no line breaks or tabs .replace(/\s\s/, '') // no double spaces .replace(/ ,/, ',') // no spaces before commas .replace(/\%20/, '') // no %20 characters .replace(/Americas/g, '') .trim(); // seriously, no extra spaces thisName = thisName.replace(',', ', ').replace(' ', ' '); // there's probably a less-stupid way of REALLY making sure it's always "COMMA SPACE" return thisName; }, updateImg = function(img, thisName) { if (thisName != null) { if (thisName !== ', ') { $(img).prop('src', 'http://' + avatarHost + thisName + imageExt); } else { tm.log('updateImg: invalid user name for ' + img.src + ': ' + thisName + '(' + thisName.length + ' chars)'); } } }, page = { initialize: function () { setTimeout(function () { page.setPrefs(); page.addClasses(); page.adjustStyles(); tm.setTamperIcon(global.scriptName, global.prefsName, global.prefs); page.addButtons(); page.expandBreadcrumb(); page.setAvatars(); page.monitorTodos(); page.linkWorkItems(); }, TIMEOUT); }, setPrefs: function() { global.prefs = GM_getValue(global.prefsName) != null ? JSON.parse(GM_getValue(global.prefsName)) : {}; if (global.prefs.todosCount == null) global.prefs.todosCount = '0'; if (global.prefs.todosTimestamp == null) global.prefs.todosTimestamp = moment(); if (global.prefs.reloadTimesReminded == null) global.prefs.reloadTimesReminded = '0'; if (global.prefs.reloadReminderCount == null) global.prefs.reloadReminderCount = '0'; if (global.prefs.archivedMessages == null || typeof global.prefs.archivedMessages === 'string') global.prefs.archivedMessages = []; if (global.prefs.archivedBroadcasts == null || typeof global.prefs.archivedMessages === 'string') global.prefs.archivedBroadcasts = []; if (global.prefs.comparisonSize == null) global.prefs.comparisonSize = global.defaultLineSize; $('.line, .line span').css('font-size', global.prefs.comparisonSize); }, addClasses: function () { if (!global.areClassesAdded) { global.areClassesAdded = true; // generic tm.addGlobalStyle('.tamperlabel { cursor: pointer; }'); tm.addGlobalStyle('.btn-headerly { padding:3px 10px; height:30px; margin-top:5px; background:#414187; border-color:darkslategray; }'); tm.addGlobalStyle('.btn-headerly:hover { background-color:steelblue; border-color:black; }'); // colored backgrounds tm.addGlobalStyle('.merge-request-tabs-container {background:bisque; }'); tm.addGlobalStyle('.merge-request-tabs {background:burlywood; }'); tm.addGlobalStyle('.mr-widget-content {background:cornflowerblue; }'); // UI-sizing tm.addGlobalStyle('.approvals-required-text .avatar {width:24px; height:24px; }'); tm.addGlobalStyle('.note-text ul {margin:0px !important; }'); tm.addGlobalStyle('.system-note {font-size:0.7em; padding:0; margin:10px 0px 0px 0px; }'); tm.addGlobalStyle('.timeline-entry:hover {background:aliceblue; }'); tm.addGlobalStyle('.note-text .gfm-merge_request {background:aliceblue; padding:10px; float:right; position:relative; top:-23px; margin-bottom:-28px; }'); tm.addGlobalStyle('.tamperNewIcon {position:relative; top:-10px; }'); } }, adjustStyles: function() { var lePass = 'palegreen', leFail = 'orange'; $('.discussion-notes .timeline-content:contains("Gitbot")').each(function(discussion) { if ($(this).closest('.card').find('button:contains("Resolve discussion")').length > 0) { $(this).css('background-color', leFail); } else { if ($(this).text().indexOf('Resolved') > 0) { $(this).css('background-color', lePass); } } }); $('.timeline-entry .discussion-header').each(function(discussion) { // $(this).closest('.timeline-entry :contains("Resolve discussion")').find('.discussion-header').css('background-color', leFail); $(this).closest('.timeline-entry :contains("Resolved")').css('background-color', lePass); }); }, addButtons: function() { var idConversations = 'idConversations', conversationsClass = '.header-new', addConversationsButton = function () { var buttonAnchor = $(conversationsClass), conversationsAction = function () { $('html, body').animate({ scrollTop: $('.approvals-components').offset().top -100 }, 500); return false; }; buttonAnchor.before('<a id="' + idConversations + '" class="btn btn-default btn-headerly">Discussions</a>'); $('#' + idConversations).click(conversationsAction); }, addCollapseExpandButtonToDom = function(bId, bAnchor, bAction, bText) { bAnchor.after('<a id="' + bId + '" class="btn btn-default append-right-8">' + bText + '</a>'); $('#' + bId).unbind('click').click(bAction); }, idCollapse = 'idCollapse', collapseClass = '.is-compare-versions-header', addCollapseButton = function() { var addCollapseButtonToDom = function () { addCollapseExpandButtonToDom(idCollapse, buttonAnchor, collapseAction, 'Collapse All'); }, buttonAnchor = $(collapseClass), collapseAction = function () { _.each($('.diff-content'), (diff) => { if ($(diff).height() > 0) { $(diff).prev().click(); } }); $(this).remove(); addExpandButton(); }; addCollapseButtonToDom(); $('a:contains("Expand all")').hide(); }, idExpand = 'idExpand', expandClass = '.is-compare-versions-header', addExpandButton = function() { var addExpandButtonToDom = function () { addCollapseExpandButtonToDom(idExpand, buttonAnchor, expandAction, 'Expand All'); }, buttonAnchor = $(expandClass), expandAction = function () { _.each($('.diff-collapsed'), (diff) => { $(diff).prev().click(); }); $(this).remove(); addCollapseButton(); }; addExpandButtonToDom(); }, idComparisonFont = 'idComparisonFont', comparisonFontClass = '.is-compare-versions-header', addComparisonFontButton = function() { var buttonAnchor = $(comparisonFontClass), comparisonFontAction = function () { var buttonText = $('#' + idComparisonFont).text(); if (global.prefs.comparisonSize === global.defaultLineSize) { global.prefs.comparisonSize = global.smallLineSize; $('#' + idComparisonFont).text(buttonText.replace('-', '+')); } else { global.prefs.comparisonSize = global.defaultLineSize; $('#' + idComparisonFont).text(buttonText.replace('+', '-')); // actual setting of font size done during monitoring of page, so mouse movement can trigger it } tm.savePreferences(global.prefsName,global.prefs); }; buttonAnchor.after('<a id="' + idComparisonFont + '" class="btn btn-default append-right-8">Font -</a>'); $('#' + idComparisonFont).unbind('click').click( comparisonFontAction); if (global.prefs.comparisonSize !== global.defaultLineSize) { // to fix UI if button was previously saved in alternate setting global.prefs.comparisonSize = global.defaultLineSize; comparisonFontAction(); } }, idHeader = 'idHeader', headerClass = '.header-message', addHeaderButton = function() { var buttonAnchor = $(headerClass), headerAction = function () { var msg = $(headerClass + ' p').text(); if (!_.contains(global.prefs.archivedMessages, msg)) { global.prefs.archivedMessages.push(msg); tm.savePreferences(global.prefsName,global.prefs); } buttonAnchor.hide(); // special for header $('.navbar').css('top', '0px'); $('.nav-sidebar').css('top', '40px'); $('.content-wrapper').css('margin-top', '40px'); return false; }; buttonAnchor.prepend('<i id="' + idHeader + '" class="fa fa-times outlined fingery"></i>'); $('#' + idHeader).click(headerAction); // if a previously-hidden header message is showing again if ($(headerClass).css('display') !== 'none') { _.each(global.prefs.archivedMessages, function(msg) { if ($(headerClass + ' p').text() == msg) { headerAction(); } }); } }, idFooter = 'idFooter', footerClass = '.footer-message', addFooterButton = function() { var buttonAnchor = $(footerClass), footerAction = function () { var msg = $(footerClass + ' p').text(); if (!_.contains(global.prefs.archivedMessages, msg)) { global.prefs.archivedMessages.push(msg); tm.savePreferences(global.prefsName,global.prefs); } buttonAnchor.hide(); return false; }; buttonAnchor.prepend('<i id="' + idFooter + '" class="fa fa-times outlined fingery"></i>'); $('#' + idFooter).click(footerAction); // if a previously-hidden footer message is showing again if ($(footerClass).css('display') !== 'none') { _.each(global.prefs.archivedMessages, function(msg) { if ($(footerClass + ' p').text() == msg) { footerAction(); } }); } }, idBroadcast = 'idBroadcast', broadcastClass = '.broadcast-message', addBroadcastButton = function() { var buttonAnchor = $(broadcastClass), broadcastAction = function () { var msg = $(broadcastClass + ' p').text(); if (!_.contains(global.prefs.archivedBroadcasts, msg)) { global.prefs.archivedBroadcasts.push(msg); tm.savePreferences(global.prefsName,global.prefs); } buttonAnchor.hide(); return false; }; buttonAnchor.prepend('<i id="' + idBroadcast + '" class="fa fa-times outlined fingery"></i>'); $('#' + idBroadcast).click( broadcastAction); // if a previously-hidden broadcast message is showing again if ($( broadcastClass).css('display') !== 'none') { _.each(global.prefs.archivedBroadcasts, function(msg) { if ($( broadcastClass + ' p').text() == msg) { broadcastAction(); } }); } }; if (document.URL.indexOf('merge_requests') < 0) { $('#' + idConversations).remove(); $('#' + idCollapse).remove(); $('#' + idExpand).remove(); global.arePrButtonsAdded = false; } else { if (!global.arePrButtonsAdded) { global.arePrButtonsAdded = true; tm.getContainer({ 'el': conversationsClass, 'max': 100, 'spd': 1000 }).then(function($container){ addConversationsButton(); }); tm.getContainer({ 'el': collapseClass, 'max': 100, 'spd': 1000 }).then(function($container){ addComparisonFontButton(); addCollapseButton(); }); // tm.getContainer({ // shares same container as collapse button; dont need // 'el': comparisonFontClass, // 'max': 100, // 'spd': 1000 // }).then(function($container){ // addComparisonFontButton(); // }); } } if (!global.areInterfaceButtonsAdded) { global.areInterfaceButtonsAdded = true; tm.getContainer({ 'el': headerClass, 'max': 100, 'spd': 1000 }).then(function($container){ addHeaderButton(); }); tm.getContainer({ 'el': footerClass, 'max': 100, 'spd': 1000 }).then(function($container){ addFooterButton(); }); tm.getContainer({ 'el': broadcastClass, 'max': 100, 'spd': 1000 }).then(function($container){ addBroadcastButton(); }); } }, expandBreadcrumb: function() { $('.breadcrumbs .dropdown li').prependTo('.breadcrumbs-list .dropdown'); $('.breadcrumbs-list .dropdown button').hide(); }, setAvatars: function () { tm.ping(avatarHost + pingPhoto + imageExt, function callback (response) { if (response === 'responded') { var avatarArray = [], thisName = 'none'; tm.getContainer({ 'el': '.participants-author' }).then(function($container){ _.each($('.participants-author img'), function (img) { thisName = properName( $(img).next().find('div').text() ); updateImg(img, thisName); }); }); tm.getContainer({ 'el': '.issuable-meta .author-link' }).then(function($container){ _.each($('.issuable-meta .author-link img'), function (img) { thisName = properName( $(img).next().text() ); updateImg(img, thisName); }); }); tm.getContainer({ 'el': '.author-link.inline' }).then(function($container){ _.each($('.author-link.inline img'), function (img) { thisName = properName( $(img).parent().prop('title') ); updateImg(img, thisName); }); }); tm.getContainer({ 'el': '.user-avatar-link' }).then(function($container){ _.each($('.user-avatar-link img'), function (img) { thisName = properName( $(img).prop('alt') ); updateImg(img, thisName); }); }); tm.getContainer({ 'el': '.header-user-dropdown-global' }).then(function($container){ _.each($('.header-user-dropdown-global img'), function (img) { thisName = properName( $(img).parent().prop('href') ); updateImg(img, thisName); }); }); tm.getContainer({ 'el': '.avatar-cell' }).then(function($container){ _.each($('.avatar-cell img'), function (img) { thisName = properName( $(img).prop('title') ); updateImg(img, thisName); }); }); tm.getContainer({ 'el': '.avatar-holder' }).then(function($container){ _.each($('.avatar-holder img'), function (img) { thisName = properName( $(img).parent().parent().next().find('.cover-title').text() ); updateImg(img, thisName); }); }); tm.getContainer({ 'el': '.list-item-name' }).then(function($container){ _.each($('.list-item-name img'), function (img) { thisName = properName( $(img).next().find('.member').text() ); updateImg(img, thisName); }); }); tm.getContainer({ 'el': '.user-popover' }).then(function($container){ _.each($('.user-popover img'), function (img) { thisName = properName( $(img).parent().parent().next().find('h5').text() ); updateImg(img, thisName); }); }); tm.getContainer({ 'el': '.system-note-image' }).then(function($container){ _.each($('.system-note-image img'), function (img) { thisName = properName( $(img).parent().prop('href') ); updateImg(img, thisName); }); }); } }); }, monitorTodos: function () { if (document.URL.indexOf('todos') < 0) return; // leave if not on the TODO page var indicateSecondsTimer, todosCount = $('.todos-count').text().replace(/(\r\n\t|\n|\r\t)/gm,''), notificationIcon = 'https://gitlab.dell.com/assets/favicon-7901bd695fb93edb07975966062049829afb56cf11511236e61bcf425070e36e.png', indicateSeconds = function() { var secondsRemaining = (Math.floor(global.reload.timespan/1000) - global.reload.lapsed); document.title = todosCount + ' : (' + secondsRemaining + ')'; global.reload.lapsed++; if (secondsRemaining > 0) { indicateSecondsTimer = setTimeout(indicateSeconds, 1000); } else { document.title = global.reload.title; } }, triggerNotification = function(notifyMessage) { var notification = new Notification(notifyMessage, { icon: notificationIcon, body: todosCount }); notification.onclick = function () { window.open('https://gitlab.dell.com/dashboard/todos'); }; } if (!global.todoMonitorInitialized) { global.todoMonitorInitialized = true; global.reload.lapsed = 0; var duration = moment.duration(moment().diff(global.prefs.todosTimestamp)), hours = duration.asHours(); if (Number(todosCount) > 0 && hours > 1) { global.prefs.reloadTimesReminded = Number(global.prefs.reloadTimesReminded) + 1; tm.savePreferences(global.prefsName,global.prefs); } if (Notification.permission === 'granted' && todosCount !== global.prefs.todosCount) { // trigger notification on TODO count change triggerNotification('TODOs on GitLab:'); } else if (Number(todosCount) > 0 && hours > 1 && Number(global.prefs.reloadTimesReminded) > Number(global.prefs.reloadReminderCount)) { // trigger notification on hourly timeout if there ARE any global.prefs.reloadTimesReminded = 0; global.prefs.reloadReminderCount = Number(global.prefs.reloadReminderCount) + 1; triggerNotification('Reminder- GitLab TODOs:'); global.prefs.todosTimestamp = moment(); if (Number(global.prefs.reloadReminderCount) > 8) { global.prefs.reloadReminderCount = 0; global.prefs.reloadTimesReminded = 0; } tm.savePreferences(global.prefsName,global.prefs); } // update stored count if(todosCount !== global.prefs.todosCount) { global.prefs.todosCount = todosCount; global.prefs.reloadReminderCount = 0; global.prefs.reloadTimesReminded = 0; tm.savePreferences(global.prefsName,global.prefs); } // Reload in X seconds global.todoTimer = setTimeout(function() {window.location.reload(false);}, global.reload.timespan); var buttonAnchor = $('.page-title'); var buttonCancelReload = 'Cancel-Reload'; var timeoutElement = function () { clearTimeout(global.todoTimer); clearTimeout(indicateSecondsTimer); $('#' + buttonCancelReload).css('display', 'none'); document.title = global.reload.title; return false; }; buttonAnchor.after('<button id="' + buttonCancelReload + '" style="margin-left:50px; border-radius:15px; border:0px; background:lightgoldenrodyellow; padding:5px 15px; ">' + buttonCancelReload + '</button>'); $('#' + buttonCancelReload).click(timeoutElement); var buttonNotifyMe = 'Notify-Me'; var notifyMe = function() { if (Notification.permission !== "granted") Notification.requestPermission(); else { var notification = new Notification('Permission Granted', { icon: notificationIcon, body: "Notifications have been allowed.", }); // notification.onclick = function () { // window.open("http://stackoverflow.com/a/13328397/1269037"); // }; } $('#' + buttonNotifyMe).css('display', 'none'); return false; } if (Notification.permission !== 'granted') { buttonAnchor.after('<button id="' + buttonNotifyMe + '" style="margin-left:50px; border-radius:15px; border:0px; background:lightgrey; padding:5px 15px; ">' + buttonNotifyMe + '</button>'); $('#' + buttonNotifyMe).click(notifyMe); } setTimeout(indicateSeconds, 1000); } }, linkWorkItems: function () { if (!global.titlesLinked) { global.titlesLinked = true; var linkText, linkHref, titleText = $('h2.title').text(); if (titleText.toUpperCase().indexOf('PT#') > -1) { linkText = titleText.match(/PT#[0-9]*/gi).toString().split(','); linkText.forEach(thisLink => { linkHref = '<a href="https://www.pivotaltracker.com/n/projects/2210391/stories/' + thisLink.replace(/PT\#/gi, '') + '" target="blank">' + thisLink + '</a>'; titleText = titleText.replace(thisLink, linkHref); }); } if (titleText.toUpperCase().indexOf('TFS#') > -1) { linkText = titleText.match(/TFS#[0-9]*/gi).toString().split(','); linkText.forEach(thisLink => { linkHref = '<a href="http://tfs2.dell.com:8080/tfs/eDell/eDellPrograms/_workitems?id=' + thisLink.replace(/TFS\#/gi, '') + '" target="blank">' + thisLink + '</a>'; titleText = titleText.replace(thisLink, linkHref); }); } $('h2.title').html(titleText); } } }; /* * Global functions */ function initScript () { tm.getContainer({ 'el': '.content', 'max': 100, 'spd': 1000 }).then(function($container){ page.initialize(); }); } initScript(); $(document).mousemove(function(e) { if (!global.isMouseMoved) { global.isMouseMoved = true; setTimeout(function() { global.isMouseMoved = false; }, TIMEOUT * 2); initScript(); } }); })();