MiGerritPlus

some extention for miui gerrit

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         MiGerritPlus
// @namespace    thbeliefNameSpace
// @icon         https://cnbj1.fds.api.xiaomi.com/info-app-webfile/common-resource/ico/favicon.ico
// @version      2.0.0
// @description  some extention for miui gerrit
// @author       thbelief
// @match        *://gerrit.pt.mioffice.cn/*
// @match        *://corgi.pt.miui.com/*
// @require      https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js
// @resource css2 https://www.layuicdn.com/layui-v2.5.6/css/layui.css
// @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_openInTab
// @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 = false
    var dashBoardSelf = "https://gerrit.pt.mioffice.cn/dashboard/self"
    var gerritTask = "https://corgi.pt.miui.com/build/tiny/task?copyId="
    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()
    loadLayui()
    // cur url
    var curHerf = window.location.href
    // reload because not success sometimes
    var isNeedReloadMain = false
    // save checkbox and change
    var selectChangeMap = new Map()
    // main header buttons
    var buttonArray = new Array(getCopyButton(), getPackagingButton(), getPackagingSettingsButton(), getResetButton())
    // settings config table
    var configs = new Array()
    const TABLE_DATA = "TABLE_DATA"
    if (GM_getValue(TABLE_DATA) != null) {
        configs = GM_getValue(TABLE_DATA)
    }
    const PACKAGE_CHANGE_ID = "changeIds"

    window.onload = function () {
        var changeIds = GM_getValue(PACKAGE_CHANGE_ID)
        sleep(300).then(() => {
            if (changeIds != null) {
                GM_setValue(PACKAGE_CHANGE_ID, null)
                if (curHerf.indexOf(gerritTask) !== -1) {
                    var badData = document.body.querySelectorAll('.del-btn')
                    for (var i = 0; i < badData.length; i++) {
                        badData[i].click()
                    }
                    var insertButton = document.body.querySelector('.insert-btn')
                    var inputChangeId = insertButton.parentNode.parentNode.querySelector('input')
                    var taskName = '';
                    for (var change of changeIds) {
                        taskName += change['name'] + " "
                        tryInput(change['id'], inputChangeId)
                        insertButton.click()
                    }
                    var go = document.body.querySelector('.bottom-btn-box').querySelector('button')
                    go.click()
                    sleep(300).then(() => {
                        var dialog = document.body.querySelector(".el-dialog")
                        var dialogInput = dialog.querySelector('input')
                        dialogInput.value = taskName
                        dialog.querySelectorAll('button')[2].click()
                        window.location.href = "about:blank"
                        window.close()
                    })
                }
            }
        });
        main()
    }
    /**
     * 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) {
                for (var i = 0; i < buttonArray.length; i++) {
                    buttonArray[i].remove()
                }
                print("Not dashBoardSelf.Remove button")
            }
            main()
        }
    }, intervalTime);

    function sleep(time) {
        return new Promise((resolve) => setTimeout(resolve, time));
    }
    function tryInput(text, tempInput) {
        var evt = new InputEvent('input', {
            inputType: 'insertText',
            data: text,
            dataTransfer: null,
            isComposing: false
        });
        tempInput.value = text;
        tempInput.dispatchEvent(evt);
    }
    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 loadLayui() {
        var jsUrl = "https://www.layuicdn.com/layui-v2.5.6/layui.all.js"
        var cssUrl = "https://www.layuicdn.com/layui-v2.5.6/css/layui.css"
        var link = document.createElement("link")
        link.setAttribute("ref", "stylesheet")
        link.setAttribute("type", "text/css")
        link.setAttribute("href", cssUrl)
        document.head.appendChild(link)
        var script = document.createElement("script")
        script.setAttribute("src", jsUrl)
        document.body.appendChild(script)
        var css2 = GM_getResourceText("css2")
        GM_addStyle(css2)

    }
    function print(content) {
        if (!isDebug) {
            return;
        }
        console.log(TAG + ": " + content)
    }
    function packageTask() {
        toastr().info("test")
    }
    function main() {
        if (curHerf !== dashBoardSelf) {
            return
        }
        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.setAttribute("style", "padding-top:5px;padding-bottom:5px;padding-left:10px;padding-right:10px;")
        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) {
            for (var i = 0; i < buttonArray.length; i++) {
                links.appendChild(buttonArray[i])
            }
        }
    }
    /**
     * 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.click()
                changes[index++] = value
            }
        })
        return changes
    }


    function getAllChanges() {
        var changes = new Array()
        var index = 0
        selectChangeMap.forEach(function (value, key) {
            changes[index++] = value
        })
        return changes
    }

    function getPackagingButton() {
        var button = getBaseButton()
        button.innerHTML = "package"
        button.onclick = function () {
            var array = getCheckedChange()
            if (array.length == 0) {
                toastr().error("not selected")
                print("not select any change")
                return
            }
            var datas = new Array()

            var curRepo = array[0].getRepo()
            var curBranch = array[0].getBranch()
            for (var item of array) {
                if (curRepo !== item.getRepo() || curBranch !== item.getBranch()) {
                    toastr().error("must simple repo and branch")
                    return;
                }
                datas[datas.length] = item.getData()
            }
            layer.open({
                type: 1,
                area: ['1000px', '500px'],
                title: "package",
                content: '<div style="padding:30px"><table id="table" class="layui-table" lay-filter="table"></table><form class="layui-form layui-form-pane" action=""><div class="layui-form-item"><label class="layui-form-label">CopyTask</label><div class="layui-input-block"><select name="copyTask" lay-verify="required" lay-filter="copyTask"></select></div></div><div class="layui-form-item"><button id="package" class="layui-btn" lay-submit lay-filter="package">package</button><button id ="reset" type="reset" class="layui-btn layui-btn-primary">reset</button></div></form></div>'
            })
            document.body.querySelector("#reset").click()
            loadSelectChanges(datas)
            var formItemArray = document.body.querySelectorAll(".layui-form-item")
            var copyTaskDom = formItemArray[0].querySelector("select")
            copyTaskDom.options.length = 0
            for (item of configs) {
                if (item['repo'] !== curRepo || item['branch'] !== curBranch) {
                    continue
                }
                copyTaskDom.add(new Option(item['name'], item['copyTaskId']))
            }
            var form = layui.form;
            form.render()
            form.on('submit(package)', function (data) {
                var copyTask = data.field['copyTask']
                if (copyTask === '') {
                    toastr().error("not select copyTask")
                } else {
                    GM_setValue(PACKAGE_CHANGE_ID, array)
                    GM_openInTab(gerritTask + copyTask)
                    layer.closeAll('page')
                }
                return false;
            });
        }
        var li = document.createElement("li")
        li.appendChild(button)
        return li
    }

    function getPackagingSettingsButton() {
        var button = getBaseButton()
        button.innerHTML = "settings"
        button.onclick = function () {
            // var div = document.createElement("div");
            // div.innerHTML = '<form class="layui-form" action=""><div class="layui-form-item"><label class="layui-form-label">输入框</label><div class="layui-input-block"><input type="text" name="title" required  lay-verify="required" placeholder="请输入标题" autocomplete="off" class="layui-input"></div></div><div class="layui-form-item"><label class="layui-form-label">密码框</label><div class="layui-input-inline"><input type="password" name="password" required lay-verify="required" placeholder="请输入密码" autocomplete="off" class="layui-input"></div><div class="layui-form-mid layui-word-aux">辅助文字</div></div><div class="layui-form-item"><label class="layui-form-label">选择框</label><div class="layui-input-block"><select name="city" lay-verify="required"><option value=""></option><option value="0">北京</option><option value="1">上海</option><option value="2">广州</option><option value="3">深圳</option><option value="4">杭州</option></select></div></div><div class="layui-form-item"><label class="layui-form-label">复选框</label><div class="layui-input-block"><input type="checkbox" name="like[write]" title="写作"><input type="checkbox" name="like[read]" title="阅读" checked><input type="checkbox" name="like[dai]" title="发呆"></div></div><div class="layui-form-item"><label class="layui-form-label">开关</label><div class="layui-input-block"><input type="checkbox" name="switch" lay-skin="switch"></div></div><div class="layui-form-item"><label class="layui-form-label">单选框</label><div class="layui-input-block"><input type="radio" name="sex" value="男" title="男"><input type="radio" name="sex" value="女" title="女" checked></div></div><div class="layui-form-item layui-form-text"><label class="layui-form-label">文本域</label><div class="layui-input-block"><textarea name="desc" placeholder="请输入内容" class="layui-textarea"></textarea></div></div><div class="layui-form-item"><div class="layui-input-block"><button class="layui-btn" lay-submit lay-filter="formDemo">立即提交</button><button type="reset" class="layui-btn layui-btn-primary">重置</button></div></div></form>'
            // console.log( div.childNodes[0])
            // document.body.appendChild( div.childNodes[0])
            layer.open({
                type: 1,
                area: ['1000px', '500px'],
                title: "settings",
                content: '<div style="padding:30px"><table id="table" class="layui-table" lay-filter="table"></table><form class="layui-form layui-form-pane" action=""><div class="layui-form-item"><label class="layui-form-label">Name</label><div class="layui-input-block"><input type="text" name="name" required lay-verify="required" placeholder="please input name" autocomplete="off"                    class="layui-input"></div></div><div class="layui-form-item"><label class="layui-form-label">Repo</label><div class="layui-input-block"><select name="repo" lay-verify="required" lay-filter="repo"></select></div></div><div class="layui-form-item"><label class="layui-form-label">Branch</label><div class="layui-input-block"><select name="branch" lay-verify="required" lay-filter="branch"></select></div></div><div class="layui-form-item layui-form-text"><label class="layui-form-label">CopyTaskId</label><div class="layui-input-block"><textarea name="copyTaskId" placeholder="please input task id" class="layui-textarea"></textarea></div></div><div class="layui-form-item"><div class="layui-input-block"><button id="add" class="layui-btn" lay-submit lay-filter="add">sure to add config</button><button id="reset" type="reset" class="layui-btn layui-btn-primary">reset form</button></div></div></form></div>'
            })
            loadConfigs()
            document.body.querySelector("#reset").click()
            // class use . id use #
            var formItemArray = document.body.querySelectorAll(".layui-form-item")
            var nameDom = formItemArray[0].querySelector("input")
            var repoDom = formItemArray[1].querySelector("select")
            var branchDom = formItemArray[2].querySelector("select")
            var taskidDom = formItemArray[3].querySelector("textarea")

            var allChange = getAllChanges()
            if (allChange.length > 0) {
                selectRepo(allChange[0].getRepo(), true)
            }

            var form = layui.form;
            form.on('select(repo)', function (data) {
                selectRepo(data.value, false)
            });
            form.on('submit(add)', function (data) {
                configs[configs.length] = data.field
                GM_setValue(TABLE_DATA, configs)
                loadConfigs()
                return false;
            });
        }
        var li = document.createElement("li")
        li.appendChild(button)
        return li
    }

    function getResetButton() {
        var button = getBaseButton()
        button.innerHTML = "Reset"
        button.onclick = function () {
            GM_deleteValue(TABLE_DATA, null)
            GM_deleteValue(PACKAGE_CHANGE_ID, null)
            configs = new Array();
            toastr().success("clear all configs")
        }
        var li = document.createElement("li")
        li.appendChild(button)
        return li
    }

    function loadSelectChanges(datas) {
        var table = layui.table;
        // id, title, repo, branch
        print(datas)
        table.render({
            elem: '#table'
            , title: "config"
            , height: 200
            , data: datas
            , page: true
            , cols: [[
                { field: 'id', title: 'Id' }
                , { field: 'title', title: 'Title' }
                , { field: 'repo', title: 'Repo' }
                , { field: 'branch', title: 'Branch' }
            ]]
        });
    }

    function loadConfigs() {
        var table = layui.table;
        table.render({
            elem: '#table'
            , title: "config"
            , height: 200
            , data: configs
            , page: true
            , cols: [[
                { field: 'name', title: 'Name' }
                , { field: 'repo', title: 'Repo' }
                , { field: 'branch', title: 'Branch' }
                , { field: 'copyTaskId', title: 'CopyTaskId' }
            ]]
        });
        table.on('row', function(obj){
            configs.map((value,i) =>{
                if(value['name'] == obj.data['name']){
                    configs.splice(i,1)
                }
            })
            GM_setValue(TABLE_DATA, configs)
            obj.del();
            toastr().info("delete config")
        });
    }

    function selectRepo(repo, isFirst) {
        var formItemArray = document.body.querySelectorAll(".layui-form-item")
        var repoDom = formItemArray[1].querySelector("select")
        var branchDom = formItemArray[2].querySelector("select")
        branchDom.options.length = 0;
        var changes = getAllChanges()
        var tempMap = new Map()
        for (var change of changes) {
            var tempArray = new Array()
            if (!tempMap.has(change.getRepo())) {
                tempArray[0] = change.getBranch()
            } else {
                tempArray = tempMap.get(change.getRepo())
                var isHave = false
                for (var i of tempArray) {
                    if (i === change.getBranch()) {
                        isHave = true
                    }
                }
                if (!isHave) {
                    tempArray[tempArray.length] = change.getBranch()
                }
            }
            tempMap.set(change.getRepo(), tempArray)
        }
        tempMap.forEach(function (value, key) {
            var repoOption = createOption(key, key)
            if (isFirst) {
                repoDom.add(repoOption)
            }
            if (repo === key) {
                for (var item of value) {
                    branchDom.add(createOption(item, item))
                }
            }
        })
        var form = layui.form;
        form.render()
    }

    function createOption(value, text) {
        return new Option(text, value)
    }

    function getCopyButton() {
        var button = getBaseButton()
        button.innerHTML = "copy"
        button.onclick = function () {
            var array = getCheckedChange()
            if (array.length == 0) {
                toastr().error("not selected")
                print("not select any change")
                return
            }
            var result = "";
            for (var i = 0; i < array.length; i++) {
                var extra = ""
                if (i != 0) {
                    extra += "\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
    }

    function removeArray(array, item) {
        var index = 0
        for (a in array) {
            if (item === a) {
                break;
            }
            index = index + 1
        }
        array.splice(index, 1)
    }

    class setting {
        constructor(name, repo, branch, taskid) {
            this.name = name
            this.repo = repo
            this.branch = branch
            this.taskid = taskid
        }

        getName() {
            return this.name
        }

        getrepo() {
            return this.repo
        }

        getBranch() {
            return this.branch
        }

        getTaskId() {
            return this.taskid
        }
    }

    /**
     * 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
        }

        getId() {
            return this.id
        }

        isChecked() {
            return this.ischecked
        }

        setIsChecked(ischecked) {
            this.ischecked = ischecked
        }

        getRepo() {
            return this.repo
        }

        getBranch() {
            return this.branch
        }

        getString() {
            var result = "ChangeId: " + this.id + " Title: " + this.title + " Repo: " + this.repo + " Branch: " + this.branch + "\nUrl: " + this.url
            return result
        }

        getData() {
            var result = { 'id': this.id, 'title': this.title, 'repo': this.repo, 'branch': this.branch }
            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
})();