// ==UserScript== // @name MiGerritPlus // @namespace thbeliefNameSpace // @icon https://cnbj1.fds.api.xiaomi.com/info-app-webfile/common-resource/ico/favicon.ico // @version 1.5.0 // @description some extention for miui gerrit // @author thbelief // @match *://gerrit.pt.mioffice.cn/* // @require https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_deleteValue // @grant GM_xmlhttpRequest // @grant GM_notification // @grant GM_setClipboard // @grant GM_addStyle // @grant GM_getResourceText // @grant GM_getResourceURL // @grant GM.setValue // @grant GM.getValue // @grant GM.registerMenuCommand // @grant GM.deleteValue // @grant GM.xmlHttpRequest // @grant GM.notification // @grant GM.setClipboard // @grant unsafeWindow // @run-at document-end // @license AGPL // @downloadURL none // ==/UserScript== (function () { 'use strict' // code region start /** * some config */ var TAG = "MiGerritPlus" // control is print log var isDebug = true var dashBoardSelf = "https://gerrit.pt.mioffice.cn/dashboard/self" var intervalTime = 150 var checkboxInsertIndex = 3 // need hide group array var hideGroupArray = new Array("Your Turn", "Incoming reviews", "CCed on", "Recently closed") print("origin start") toastr().info("welcome to use " + TAG) loadCss() $(document).ready(function () { main() }) // cur url var curHerf = window.location.href // reload because not success sometimes var isNeedReloadMain = false // save checkbox and change var selectChangeMap = new Map() // copyButton var copyButton = getCopyButton() var packagingButton = getPackagingButton() /** * History and window.hashchange is not role * so use interval */ setInterval(function () { if (curHerf != window.location.href || isNeedReloadMain) { isNeedReloadMain = false; curHerf = window.location.href print("cur url = " + curHerf) if (curHerf != dashBoardSelf) { copyButton.remove() packagingButton.remove() print("Not dashBoardSelf.Remove copy button") } if (curHerf === dashBoardSelf) { main() } } }, intervalTime); function loadCss(){ var css = ".toast-title{font-weight:bold}.toast-message{-ms-word-wrap:break-word;word-wrap:break-word}.toast-message a,.toast-message label{color:#fff}.toast-message a:hover{color:#ccc;text-decoration:none}.toast-close-button{position:relative;right:-0.3em;top:-0.3em;float:right;font-size:20px;font-weight:bold;color:#fff;-webkit-text-shadow:0 1px 0 #fff;text-shadow:0 1px 0 #fff;opacity:.8;-ms-filter:alpha(opacity=80);filter:alpha(opacity=80)}.toast-close-button:hover,.toast-close-button:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.4;-ms-filter:alpha(opacity=40);filter:alpha(opacity=40)}button.toast-close-button{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.toast-top-center{top:0;right:0;width:100%}.toast-bottom-center{bottom:0;right:0;width:100%}.toast-top-full-width{top:0;right:0;width:100%}.toast-bottom-full-width{bottom:0;right:0;width:100%}.toast-top-left{top:12px;left:12px}.toast-top-right{top:12px;right:12px}.toast-bottom-right{right:12px;bottom:12px}.toast-bottom-left{bottom:12px;left:12px}#toast-container{position:fixed;z-index:999999}#toast-container *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}#toast-container>div{position:relative;overflow:hidden;margin:0 0 6px;padding:15px 15px 15px 50px;width:300px;-moz-border-radius:3px 3px 3px 3px;-webkit-border-radius:3px 3px 3px 3px;border-radius:3px 3px 3px 3px;background-position:15px center;background-repeat:no-repeat;-moz-box-shadow:0 0 12px #999;-webkit-box-shadow:0 0 12px #999;box-shadow:0 0 12px #999;color:#fff;opacity:.8;-ms-filter:alpha(opacity=80);filter:alpha(opacity=80)}#toast-container>:hover{-moz-box-shadow:0 0 12px #000;-webkit-box-shadow:0 0 12px #000;box-shadow:0 0 12px #000;opacity:1;-ms-filter:alpha(opacity=100);filter:alpha(opacity=100);cursor:pointer}#toast-container.toast-top-center>div,#toast-container.toast-bottom-center>div{width:300px;margin:auto}#toast-container.toast-top-full-width>div,#toast-container.toast-bottom-full-width>div{width:96%;margin:auto}.toast{background-color:#030303}.toast-success{background-color:#51a351}.toast-error{background-color:#bd362f}.toast-info{background-color:#2f96b4}.toast-warning{background-color:#f89406}.toast-progress{position:absolute;left:0;bottom:0;height:4px;background-color:#000;opacity:.4;-ms-filter:alpha(opacity=40);filter:alpha(opacity=40)}@media all and (max-width:240px){#toast-container>div{padding:8px 8px 8px 50px;width:11em}#toast-container .toast-close-button{right:-0.2em;top:-0.2em}}@media all and (min-width:241px) and (max-width:480px){#toast-container>div{padding:8px 8px 8px 50px;width:18em}#toast-container .toast-close-button{right:-0.2em;top:-0.2em}}@media all and (min-width:481px) and (max-width:768px){#toast-container>div{padding:15px 15px 15px 50px;width:25em}}" GM_addStyle(css) } function print(content) { if (!isDebug) { return; } console.log(TAG + ": " + content) } function main() { print("main") var rootDom = document.body.querySelector("gr-app").shadowRoot.getElementById("app-element").shadowRoot.getRootNode(); var mainHeader = rootDom.querySelector("gr-main-header").shadowRoot.getRootNode() // insert copy button insertClipIcon(mainHeader.querySelector(".links")) // remove footer var footerDom = rootDom.querySelector("footer") if (footerDom != null) { print("remove footer") footerDom.remove() } var mainDom = rootDom.querySelector("main") if (mainDom === null) { print("main is null") return } var changeDom = mainDom.querySelector("gr-dashboard-view") if (changeDom === null) { isNeedReloadMain = true return } var changeList = changeDom.shadowRoot.getRootNode().querySelector("gr-change-list").shadowRoot.getRootNode().querySelectorAll("gr-change-list-section") // cardArray is per change card var cardArray = new Array() for (var i = 0; i < changeList.length; i++) { cardArray[i] = changeList[i].shadowRoot.getRootNode() } if (cardArray.length === 0) { toastr().warning("operate fail.try to retry") isNeedReloadMain = true } print("cardArray = " + cardArray) for (var i = 0; i < cardArray.length; i++) { var curGroupDom = cardArray[i].querySelector(".section-name") var groupHeaderDom = cardArray[i].querySelector(".groupHeader") // hide which need to hide group if (curGroupDom != null && hideGroupArray.includes(curGroupDom.innerText)) { print("remove " + curGroupDom.innerText) if (groupHeaderDom !== null) { groupHeaderDom.remove() } var groupContentDom = cardArray[i].querySelector(".groupContent") if (groupContentDom !== null) { groupContentDom.remove() } // remove noChanges item var noChangesDom = cardArray[i].querySelector(".noChanges") if (noChangesDom !== null) { noChangesDom.remove() } // remove group title if (i != 0) { var groupTitleDom = cardArray[i].querySelector(".groupTitle") if (groupTitleDom !== null) { groupTitleDom.remove() } } } var groupContentDom = cardArray[i].querySelector(".groupContent") if (groupContentDom !== null) { var groupTitleDom = groupContentDom.querySelector(".groupTitle") if(groupTitleDom.querySelectorAll(".subject").length <= 1){ insertCheckBoxTitle(groupTitleDom) var list = groupContentDom.querySelectorAll("gr-change-list-item") insertCheckBox(list) } } } toastr().success("operate success") } /** * insert checkbox title * @param list */ function insertCheckBoxTitle(groupTitle) { var tdList = groupTitle.querySelectorAll("td") groupTitle.insertBefore(createCheckBoxTitle(), tdList[checkboxInsertIndex]) } /** * create checkbox title * @returns */ function createCheckBoxTitle() { var td = document.createElement("td") td.className = "subject" td.innerText = "Select" return td } /** * insert checkbox to every change * @param {*} list */ function insertCheckBox(list) { for (var i = 0; i < list.length; i++) { var tdList = list[i].shadowRoot.getRootNode().querySelectorAll("td") var cellNumberNode = list[i].shadowRoot.getRootNode().querySelector(".number") var titleNode = list[i].shadowRoot.getRootNode().querySelector(".content") var repoNode = list[i].shadowRoot.getRootNode().querySelector(".fullRepo") var branchNode = list[i].shadowRoot.getRootNode().querySelector(".branch") const change = new Change(cellNumberNode.querySelector("a").innerText, titleNode.innerText, cellNumberNode.querySelector("a").href, repoNode.innerText, branchNode.querySelector("a").innerText) list[i].shadowRoot.getRootNode().insertBefore(createCheckBox(change), tdList[checkboxInsertIndex]) } } /** * create checkbox * @param {*} change * @returns */ function createCheckBox(change) { var td = document.createElement("td") var input = document.createElement("input") input.type = "checkbox" input.setAttribute("style", "width:20px;height:20px;") input.onclick = function () { selectChangeMap.get(this).setIsChecked(this.checked) //console.log(selectChangeMap.get(this)) } td.setAttribute("display", "table-cell") td.setAttribute("vertical-align", "middle") td.setAttribute("white-space", "nowrap") td.appendChild(input) selectChangeMap.set(input, change) return td } /** * insert copy button * @param {*} links */ function insertClipIcon(links) { if (links == null) { return } var button = links.querySelector(".copyToClipboard") // just need one if (button == null) { links.appendChild(copyButton) links.appendChild(packagingButton) } } /** * get changes which checked by myselef * @returns */ function getCheckedChange() { var changes = new Array() var index = 0 selectChangeMap.forEach(function (value, key) { if (value.isChecked()) { key.checked = false changes[index++] = value } }) return changes } function getPackagingButton(){ var button = getBaseButton() button.innerHTML = "package" var li = document.createElement("li") li.appendChild(button) return li } function getCopyButton(){ var button = getBaseButton() button.innerHTML = "copy" button.onclick = function () { var array = getCheckedChange() if (array.length == 0) { print("not select any change") return } var result = ""; for (var i = 0; i < array.length; i++) { var extra = "" if (i != 0) { extra += "\n" } extra += "index = " + i + "\n" result += extra + array[i].getString() if (i != array.length - 1) { result += "\n" } } toastr().success("copy selected changes") print("copy \n" + result) GM_setClipboard(result) } var li = document.createElement("li") li.appendChild(button) return li } function getBaseButton(){ var targetButton = document.createElement("gr-button") var paperButton = document.createElement("paper-button") targetButton.setAttribute("class", "copyToClipboard") targetButton.setAttribute("role", "button") targetButton.setAttribute("aria-disabled", "false") targetButton.appendChild(paperButton) targetButton.setAttribute("style","padding:8px;") return targetButton } /** * change class * save information about change */ class Change { constructor(id, title, url, repo, branch) { this.id = id.trim() this.title = title.trim() this.url = url.trim() this.repo = repo.trim() this.branch = branch.trim() this.ischecked = false } isChecked() { return this.ischecked } setIsChecked(ischecked) { this.ischecked = ischecked } getString() { var result = "ChangeId: " + this.id + "\nTitle: " + this.title + "\nRepo: " + this.repo + "\nBranch: " + this.branch + "\nUrl: " + this.url return result } } function toastr() { var $container; var listener; var toastId = 0; var toastType = { error: 'error', info: 'info', success: 'success', warning: 'warning' }; var toastr = { clear: clear, remove: remove, error: error, getContainer: getContainer, info: info, options: {}, subscribe: subscribe, success: success, version: '2.1.1', warning: warning }; var previousToast; return toastr; //////////////// function error(message, title, optionsOverride) { return notify({ type: toastType.error, iconClass: getOptions().iconClasses.error, message: message, optionsOverride: optionsOverride, title: title }); } function getContainer(options, create) { if (!options) { options = getOptions(); } $container = $('#' + options.containerId); if ($container.length) { return $container; } if (create) { $container = createContainer(options); } return $container; } function info(message, title, optionsOverride) { return notify({ type: toastType.info, iconClass: getOptions().iconClasses.info, message: message, optionsOverride: optionsOverride, title: title }); } function subscribe(callback) { listener = callback; } function success(message, title, optionsOverride) { return notify({ type: toastType.success, iconClass: getOptions().iconClasses.success, message: message, optionsOverride: optionsOverride, title: title }); } function warning(message, title, optionsOverride) { return notify({ type: toastType.warning, iconClass: getOptions().iconClasses.warning, message: message, optionsOverride: optionsOverride, title: title }); } function clear($toastElement, clearOptions) { var options = getOptions(); if (!$container) { getContainer(options); } if (!clearToast($toastElement, options, clearOptions)) { clearContainer(options); } } function remove($toastElement) { var options = getOptions(); if (!$container) { getContainer(options); } if ($toastElement && $(':focus', $toastElement).length === 0) { removeToast($toastElement); return; } if ($container.children().length) { $container.remove(); } } // internal functions function clearContainer (options) { var toastsToClear = $container.children(); for (var i = toastsToClear.length - 1; i >= 0; i--) { clearToast($(toastsToClear[i]), options); } } function clearToast ($toastElement, options, clearOptions) { var force = clearOptions && clearOptions.force ? clearOptions.force : false; if ($toastElement && (force || $(':focus', $toastElement).length === 0)) { $toastElement[options.hideMethod]({ duration: options.hideDuration, easing: options.hideEasing, complete: function () { removeToast($toastElement); } }); return true; } return false; } function createContainer(options) { $container = $('
') .attr('id', options.containerId) .addClass(options.positionClass) .attr('aria-live', 'polite') .attr('role', 'alert'); $container.appendTo($(options.target)); return $container; } function getDefaults() { return { tapToDismiss: true, toastClass: 'toast', containerId: 'toast-container', debug: false, showMethod: 'fadeIn', //fadeIn, slideDown, and show are built into jQuery showDuration: 300, showEasing: 'swing', //swing and linear are built into jQuery onShown: undefined, hideMethod: 'fadeOut', hideDuration: 1000, hideEasing: 'swing', onHidden: undefined, extendedTimeOut: 1000, iconClasses: { error: 'toast-error', info: 'toast-info', success: 'toast-success', warning: 'toast-warning' }, iconClass: 'toast-info', positionClass: 'toast-top-right', timeOut: 5000, // Set timeOut and extendedTimeOut to 0 to make it sticky titleClass: 'toast-title', messageClass: 'toast-message', target: 'body', closeHtml: '', newestOnTop: true, preventDuplicates: false, progressBar: false }; } function publish(args) { if (!listener) { return; } listener(args); } function notify(map) { var options = getOptions(); var iconClass = map.iconClass || options.iconClass; if (typeof (map.optionsOverride) !== 'undefined') { options = $.extend(options, map.optionsOverride); iconClass = map.optionsOverride.iconClass || iconClass; } if (shouldExit(options, map)) { return; } toastId++; $container = getContainer(options, true); var intervalId = null; var $toastElement = $(''); var $titleElement = $(''); var $messageElement = $(''); var $progressElement = $(''); var $closeElement = $(options.closeHtml); var progressBar = { intervalId: null, hideEta: null, maxHideTime: null }; var response = { toastId: toastId, state: 'visible', startTime: new Date(), options: options, map: map }; personalizeToast(); displayToast(); handleEvents(); publish(response); if (options.debug && console) { console.log(response); } return $toastElement; function personalizeToast() { setIcon(); setTitle(); setMessage(); setCloseButton(); setProgressBar(); setSequence(); } function handleEvents() { $toastElement.hover(stickAround, delayedHideToast); if (!options.onclick && options.tapToDismiss) { $toastElement.click(hideToast); } if (options.closeButton && $closeElement) { $closeElement.click(function (event) { if (event.stopPropagation) { event.stopPropagation(); } else if (event.cancelBubble !== undefined && event.cancelBubble !== true) { event.cancelBubble = true; } hideToast(true); }); } if (options.onclick) { $toastElement.click(function () { options.onclick(); hideToast(); }); } } function displayToast() { $toastElement.hide(); $toastElement[options.showMethod]( {duration: options.showDuration, easing: options.showEasing, complete: options.onShown} ); if (options.timeOut > 0) { intervalId = setTimeout(hideToast, options.timeOut); progressBar.maxHideTime = parseFloat(options.timeOut); progressBar.hideEta = new Date().getTime() + progressBar.maxHideTime; if (options.progressBar) { progressBar.intervalId = setInterval(updateProgress, 10); } } } function setIcon() { if (map.iconClass) { $toastElement.addClass(options.toastClass).addClass(iconClass); } } function setSequence() { if (options.newestOnTop) { $container.prepend($toastElement); } else { $container.append($toastElement); } } function setTitle() { if (map.title) { $titleElement.append(map.title).addClass(options.titleClass); $toastElement.append($titleElement); } } function setMessage() { if (map.message) { $messageElement.append(map.message).addClass(options.messageClass); $toastElement.append($messageElement); } } function setCloseButton() { if (options.closeButton) { $closeElement.addClass('toast-close-button').attr('role', 'button'); $toastElement.prepend($closeElement); } } function setProgressBar() { if (options.progressBar) { $progressElement.addClass('toast-progress'); $toastElement.prepend($progressElement); } } function shouldExit(options, map) { if (options.preventDuplicates) { if (map.message === previousToast) { return true; } else { previousToast = map.message; } } return false; } function hideToast(override) { if ($(':focus', $toastElement).length && !override) { return; } clearTimeout(progressBar.intervalId); return $toastElement[options.hideMethod]({ duration: options.hideDuration, easing: options.hideEasing, complete: function () { removeToast($toastElement); if (options.onHidden && response.state !== 'hidden') { options.onHidden(); } response.state = 'hidden'; response.endTime = new Date(); publish(response); } }); } function delayedHideToast() { if (options.timeOut > 0 || options.extendedTimeOut > 0) { intervalId = setTimeout(hideToast, options.extendedTimeOut); progressBar.maxHideTime = parseFloat(options.extendedTimeOut); progressBar.hideEta = new Date().getTime() + progressBar.maxHideTime; } } function stickAround() { clearTimeout(intervalId); progressBar.hideEta = 0; $toastElement.stop(true, true)[options.showMethod]( {duration: options.showDuration, easing: options.showEasing} ); } function updateProgress() { var percentage = ((progressBar.hideEta - (new Date().getTime())) / progressBar.maxHideTime) * 100; $progressElement.width(percentage + '%'); } } function getOptions() { return $.extend({}, getDefaults(), toastr.options); } function removeToast($toastElement) { if (!$container) { $container = getContainer(); } if ($toastElement.is(':visible')) { return; } $toastElement.remove(); $toastElement = null; if ($container.children().length === 0) { $container.remove(); previousToast = undefined; } } } // code region end })();