Greasy Fork

MiGerritPlus

some extention for miui gerrit

目前为 2022-08-05 提交的版本。查看 最新版本

// ==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

// ==/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 = $('<div/>')
                .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: '<button type="button">&times;</button>',
                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 = $('<div/>');
            var $titleElement = $('<div/>');
            var $messageElement = $('<div/>');
            var $progressElement = $('<div/>');
            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
})();