您需要先安装一个扩展,例如 篡改猴、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 2.1 // @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 // @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 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', areClassesAdded: false, arePrButtonsAdded: false, areInterfaceButtonsAdded: false, todoMonitorInitialized: false, todoTimer: undefined, isMouseMoved: false, prefs: GM_getValue('gitlabPrefs') != null ? JSON.parse(GM_getValue('gitlabPrefs')) : {}, reload: { lapsed: 0, timespan: 10000, title: document.title } }, properName = function(thisName) { var firstName = '', lastName = '', midName = ''; thisName = thisName .replace('https://gitlab.dell.com/', '') .replace(' - Dell Team', '') .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 .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(); page.setTamperIcon(); page.addButtons(); page.expandBreadcrumb(); page.setAvatars(); page.monitorTodos(); }, TIMEOUT); }, setPrefs: function() { 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) global.prefs.archivedMessages = []; if (global.prefs.archivedBroadcasts == null) global.prefs.archivedBroadcasts = []; if (global.prefs.closeTree == null) global.prefs.closeTree = false; }, addClasses: function () { if (!global.areClassesAdded) { global.areClassesAdded = true; // generic tm.addGlobalStyle('.fingery { margin:13px; cursor:pointer; }'); 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); }); }, setTamperIcon: function () { // Add Tampermonkey Icon with label to identify this script if($('.tamperlabel').length > 0) { if ($('.tamperlabel').prop('title').indexOf(global.scriptName) === -1) { $('.tamperlabel').prop('title', $('.tamperlabel').prop('title') + ' | ' + global.scriptName); } } else { $('body').append('<span class="tamperlabel" title="Tampermonkey scripts: ' + global.scriptName + '"><i class="fa fa-battery-three-quarters tamperNewIcon"></i></span>'); } var tamperAction = function () { page.erasePreferences(); alert('All Gitlab Mods memory has been reset.'); return false; }; $('.tamperlabel').unbind('click').click(tamperAction); }, 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); }, idTree = 'idTree', collapseClass = '.is-compare-versions-header', addCollapseButton = function() { var addCollapseButtonToDom = function () { buttonAnchor.after('<a id="' + idTree + '" class="btn btn-default append-right-8">Collapse All</a>'); $('#' + idTree).unbind('click').click(collapseAction); }, buttonAnchor = $(collapseClass), collapseAction = function () { _.each($('.diff-content'), (diff) => { if ($(diff).height() > 0) { $(diff).prev().click(); } }); $(this).remove(); }; addCollapseButtonToDom(); $('a:contains("Expand all")').unbind('click').click(addCollapseButtonToDom); }, 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); page.savePreferences(); } 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); page.savePreferences(); } 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); page.savePreferences(); } 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(); $('#' + idTree).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){ addCollapseButton(); }); } } 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); }); }); } }); }, savePreferences: function () { GM_setValue('gitlabPrefs', JSON.stringify(global.prefs)); }, erasePreferences: function() { GM_setValue('gitlabPrefs', JSON.stringify({})); }, 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; page.savePreferences(); } 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; } page.savePreferences(); } // update stored count if(todosCount !== global.prefs.todosCount) { global.prefs.todosCount = todosCount; global.prefs.reloadReminderCount = 0; global.prefs.reloadTimesReminded = 0; page.savePreferences(); } // 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); } } }; /* * 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(); } }); })();