skybreak / JIRA Quick Navigation Ext

// ==UserScript==
// @name JIRA Quick Navigation Ext
// @description A GD JIRA quick navigation extension. 
// @version 1.41
// @author Darren Teng
// @license MIT
// @include *
// @match https://pd.nextestate.com/*
// @run-at document-end
// @grant none
// ==/UserScript==

(function () {
    //'use strict';
    console.log('Tampermonkey JIRA ext loaded...');
    String.prototype.replaceAll = function (search, replacement) {
        var target = this;
        return target.replace(new RegExp(search, 'g'), replacement);
    };
    window.extUtil = {
        isTop: true,
        pannelIsOpen: true,
        pageInfo: null,
        render() {
            this.id = Date.now();
            this['pannel' + this.id] = {};
            this.pageInfo = this.getPageInfo();
            this.isDivReady(0, 60, () => {
                this.renderPannel();
                this.renderRocket();
                this.hideApprovals();
            });
        },
        handleDetailPage() {
            if (extUtil.pageInfo.page == 'story-detail') {
                extUtil.listenActivityClick();
            }
        },
        isDivReady(tryTime, maxTryTime, callback) {
            console.log('check isDivReady')
            tryTime++;
            if (tryTime > maxTryTime) {
                console.log('isDivReady exceed max try ' + maxTryTime);
                return;
            }
            if (this.pageInfo.scrollElement) {
                console.log('div is ready.');
                callback && callback();
            }
            else {
                setTimeout(() => {
                    this.loadScollBox(this.pageInfo.scrollWinId);
                    this.isDivReady(tryTime, maxTryTime, callback);
                }, 50);
            }
        },
        showHidedRow(id) {
            (function (e, id) {
                console.log('showId:' + id);
                console.log(e);
                debugger
                var ele = document.getElementById(id);
                if (ele.display != 'none') {
                    e.target.innerText = 'Hide';
                    ele.style.display = 'none';
                } else {
                    e.target.innerText = 'Show';
                    ele.style.display = 'block';
                }
            })(e, id);
        },
        hideApprovals() {
           setTimeout(()=>{
               var content = document.querySelector('#approvalsmodule .mod-content');
               if(content.offsetHeight>10){
                   var approvalTitle = document.querySelector('#approvalsmodule_heading .toggle-title');
                   approvalTitle.click();
                   approvalTitle.innerText="Approvals (click to toggle hide/show)";
               }
           },100);
        },
        renderPannel() {
            var items = this.getPageHeaders();
            var renderFunc = () => {
                items = this.getPageHeaders();
                items = this.filterQuickLinks(items);
                var htmlContent = this.buildTab(items);
                //render pannel
                var pannelDiv = document.createElement('div');
                pannelDiv.innerHTML = htmlContent;
                document.getElementsByTagName('body')[0].appendChild(pannelDiv);
                this.pageInfo.onPannelReady && this.pageInfo.onPannelReady();
                $('.collspanClz').on('click', function () {
                    extUtil.toggolePannel();
                });
                //default show the panel. so if it set to not display the panel, hide it.
                if (!this.pannelIsOpen) {
                    extUtil.pannelIsOpen = true;
                    extUtil.toggolePannel();
                }
            }
            if (!items || !items.length) {
                var firstRetryTime = 500;
                console.log('No items found, delay ' + firstRetryTime + ' ms and retry.')
                extUtil.delayDo(firstRetryTime, renderFunc);
            }
            else {
                renderFunc();
            }
        },
        changeRocketDirection(fire, rocket) {
            if (extUtil.isTop && (extUtil.direction == "up")) {
                extUtil.direction = "down";
                fire.classList.remove("burn");
                rocket.classList.add("rotate");
            }

            if (extUtil.isBottom && (extUtil.direction == "down" || !extUtil.direction)) {
                extUtil.direction = "up";
                fire.classList.remove("burn");
                rocket.classList.remove("rotate");
            }
        },
        updateRocketStatus(ele) {
            extUtil.isTop = ele.scrollTop <= 100;
            extUtil.isBottom = ele.clientHeight + ele.scrollTop >= ele.scrollHeight;
        },
        renderRocket() {
            //render rocket
            var rocket = document.createElement('div');
            rocket.setAttribute("class", "rocketOutbox rotate");
            rocket.innerHTML = this.buildRocket();
            document.getElementsByTagName('body')[0].appendChild(rocket);
            var fire = document.querySelector(".fire");
            document.getElementsByClassName('spaceship')[0].onclick = function () {
                fire.classList.add("burn");
                const pageInfo = extUtil.pageInfo;
                if (extUtil.direction == "up") {
                    //go to top
                    pageInfo.scrollElement.scrollTo(0, 0);
                }
                else {
                    //go to bottom
                    var ele = pageInfo.scrollElement;
                    if (!ele) return;
                    pageInfo.scrollElement.scrollTo(0, 100000);
                }
                extUtil.updateRocketStatus(extUtil.pageInfo.scrollElement);
                extUtil.changeRocketDirection(fire, rocket);
            };
            var scrollWindow = document.querySelector(this.pageInfo.scrollWinId);
            var d = extUtil.debounce(() => {
                console.log('isRocketStatusChanged?');
                extUtil.updateRocketStatus(scrollWindow);
                if (extUtil.isTop || extUtil.isBottom) {
                    fire.classList.add("burn");
                    setTimeout(() => {
                        extUtil.changeRocketDirection(fire, rocket);
                        console.log('isRocketStatusChanged:true');
                    }, 300);
                }
            }, 100);
            scrollWindow.addEventListener('scroll', () => {
                d();
                console.log('scroll...');
            });
        },
        debounce(func, time, tag) {
            var store = {};
            return function () {
                var context = this;
                var args = arguments;
                tag = tag ? tag : func.name;
                var timeoutId = store[tag];
                if (timeoutId) {
                    clearTimeout(timeoutId);
                }
                store[tag] = setTimeout(() => {
                    func(args);
                }, time);
            }
        },
        filterQuickLinks(items) {
            if (items && this.pageInfo) {
                items.forEach(v => {
                    if (this.pageInfo.list && this.pageInfo.list.length > 0) {
                        var it = this.pageInfo.list.find(p => p.name == v.text.trim() || (p.id && ("#" + p.id) == v.link));
                        if (it) {
                            v.order = it.order;
                            v.desc = it.desc;
                            v.type = it.type;
                            v.text = it.desc ? it.desc : v.text;
                        };
                    }
                    else {
                        v.order = 0;
                        v.desc = v.text;
                        v.type = "left";
                    }
                })
            }
            return items;
        },
        loadScollBox(selecter) {
            this.pageInfo.scrollElement = document.querySelector(selecter);
        },
        getPageInfo() {
            var url = window.location.href;
            var isBacklogBoardViewPage = (url) => {
                return /pd\.nextestate\.com\/secure\/RapidBoard\.jspa\?.*/.test(url) && url.indexOf('view=planning') < 0;;
            }
            var isStoryDetailPage = (url) => {
                return /pd\.nextestate\.com\/browse\/.*/.test(url);
            }
            var isBacklogListViewPage = (url) => {
                return /pd\.nextestate\.com\/secure\/RapidBoard\.jspa\?.*/.test(url) && url.indexOf('view=planning') > 0;
            }
            var isReportPage = (url) => {
                return url.indexOf('pd\.nextestate\.com/projects/') > -1 && url.indexOf('selectedItem=com.atlassian.jira.jira-projects-plugin:report-page') >= 0;
            }
            var isReleasePage = (url) => {
                return url.indexOf('pd\.nextestate\.com/projects/') > -1 && url.indexOf('selectedItem=com.atlassian.jira.jira-projects-plugin:release-page') >= 0;
            }
            //event listener for tab change between backlog list view and backlog board view.
            var onStateChange = () => {
                console.log('onStateChange...');
                if (!extUtil.hasAddedPushState) {
                    var _wr = function (type) {
                        var orig = history[type];
                        return function () {
                            var rv = orig.apply(this, arguments);
                            var e = new Event(type);
                            e.arguments = arguments;
                            window.dispatchEvent(e);
                            return rv;
                        };
                    };
                    history.pushState = _wr('pushState');
                    history.replaceState = _wr('replaceState');
                    window.addEventListener('pushState', function (e) {
                        var span = (Date.now() - extUtil.id) / 1000;
                        //only re-render the panel if timespan larger than 1s.
                        if (span > 1) {
                            setTimeout(() => {
                                console.log('re-render pannel...');
                                var extPopupPage = document.querySelector('.extPopupPage');
                                if (extPopupPage && extPopupPage.parentNode) {
                                    extPopupPage.parentNode.remove();
                                }
                                var rocket = document.querySelector('.rocketOutbox');
                                if (rocket) rocket.remove();
                                setTimeout(() => extUtil.render(), 100);
                            }, 200);
                        }
                        else {
                            console.log('pannel' + extUtil.id + ' hasReRenderedPanel.');
                        }
                    });
                    extUtil.hasAddedPushState = true;
                }
            }
            var map = [{
                page: "story-detail",
                isCurrentPage: isStoryDetailPage,
                scrollWinId: ".issue-view",
                listItemFilter: {
                    itemTextFilter: "", // the filter to get the text content. if it's empty, just use the box as the text container.
                    itemBoxFilter: ".mod-header" // the filter to navigate to 
                },
                buildFastLinkFunc: this.buildFastLinkForDetailPage,
                onPannelReady: this.handleDetailPage,
                list: [{
                    type: "left",
                    name: "Details",
                    desc: "Details",
                    order: 1
                },
                {
                    type: "left",
                    name: "Description",
                    desc: "Description",
                    order: 2
                },
                {
                    type: "left",
                    name: "Approvals",
                    desc: "Approvals",
                    order: 3
                },
                {
                    type: "left",
                    name: "Smart Checklist",
                    desc: "Smart Checklist",
                    order: 4
                },
                {
                    type: "left",
                    name: "Attachments",
                    desc: "Attachments",
                    order: 5,
                    id: "attachmentmodule_heading"
                },
                {
                    type: "left",
                    name: "Issue Links",
                    desc: "Issue Links",
                    id:"linkingmodule_heading",
                    order: 6
                },
                {
                    type: "left",
                    name: "Sub-Tasks",
                    desc: "Sub-Tasks",
                    order: 7
                },
                {
                    type: "left",
                    name: "Activity",
                    desc: "Activity",
                    order: 8
                },
                {
                    type: "right",
                    name: "People",
                    desc: "People",
                    order: 1
                },
                {
                    type: "right",
                    name: "Dates",
                    desc: "Dates",
                    order: 2
                },
                {
                    type: "fast",
                    name: "Comments",
                    desc: "Comments",
                    order: 2,
                    id: "comment-tabpanel"
                },
                {
                    type: "fast",
                    name: "History",
                    desc: "History",
                    order: 2,
                    id: "changehistory-tabpanel"
                },
                {
                    type: "fast",
                    name: "Story Points",
                    desc: "Story Points",
                    order: 3,
                    id: "customfield_10002",
                    onclick: (id) => {
                        extUtil.popupToEdit(id, "'Story point' not found, try again.");
                    }
                },
                {
                    type: "fast",
                    name: "Dev Lead",
                    desc: "Dev Lead",
                    order: 4,
                    id: "customfield_13211",
                    onclick: (id) => {
                        extUtil.popupToEdit(id, "'Development Lead' not found, try again.");
                    }
                },
                {
                    type: "fast",
                    name: "QA Engineer",
                    desc: "QA Engineer",
                    order: 5,
                    id: "customfield_13213",
                    onclick: (id) => {
                        extUtil.popupToEdit(id, "'QA Engineer' not found, try again.");
                    }
                },
                {
                    type: "fast",
                    name: "Dev ETA",
                    desc: "Dev ETA",
                    order: 5,
                    id: "customfield_13400",
                    onclick: (id) => {
                        extUtil.popupToEdit(id, "'Development ETA' not found, try again.");
                    }
                },
                ]
            },
            {
                page: "backlog-list-view",
                isCurrentPage: isBacklogListViewPage,
                scrollWinId: "#ghx-backlog",
                listItemFilter: {
                    itemTextFilter: ".ghx-name",
                    itemBoxFilter: ".ghx-backlog-container"
                },
                additionalCss: `.extPopupPage{ overflow-y: scroll;}
        .rocketOutbox {right:10px !important;}
        .extContent li {
          width: 240px;
        }
        .extContent {
          width: 240px;
        }`,
                onPannelReady: onStateChange,
                list: []
            },
            {
                page: "backlog-board-view",
                isCurrentPage: isBacklogBoardViewPage,
                scrollWinId: "#ghx-pool-column", //"ghx-rabid",
                listItemFilter: {
                    itemTextFilter: "span[role=button]",
                    itemBoxFilter: ".ghx-swimlane-header"
                },
                additionalCss: `.extPopupPage{ overflow-y: scroll;}`,
                onPannelReady: onStateChange,
                list: []
            },
            {
                page: "report",
                isCurrentPage: isReportPage,
                scrollWinId: "report-page",
                list: []
            },
            {
                page: "release",
                isCurrentPage: isReleasePage,
                scrollWinId: "release-page",
                list: []
            },
            ];
            var pageInfo = null;
            map.forEach((p) => {
                if (p.isCurrentPage(url)) {
                    pageInfo = p;
                    pageInfo.scrollElement = document.querySelector(p.scrollWinId);
                    return;
                }
            });
            return pageInfo;
        },
        getPageHeaders() {
            var headers = document.querySelectorAll(this.pageInfo.listItemFilter.itemBoxFilter);
            var items = [];
            const exculedeIds = ['dnd-metadata_heading'];
            var autoId = 100;
            headers.forEach(node => {
                if (exculedeIds.indexOf(node.id) > -1) return;
                if (!node.id) {
                    node.id = "autoId" + autoId++;
                    node.setAttribute('id', node.id);
                }
                var textEle = this.pageInfo.listItemFilter.itemTextFilter ? node.querySelector(this.pageInfo.listItemFilter.itemTextFilter) : node;
                items.push({
                    link: '#' + node.id,
                    text: textEle ? textEle.innerText : "-",
                    anchor: '^'
                });
            });
            return items;
        },
        changeSelectedFastlinkTab(id) {
            document.querySelectorAll('.extFastLinks li').forEach(p => {
                p.setAttribute('class', p.getAttribute('class').replaceAll('fastlink_actived', ''));
            });
            var selectedItems = document.getElementsByClassName('fast_' + id);
            if (selectedItems && selectedItems[0]) {

                var selectedItemClzs = selectedItems[0].getAttribute("class");
                selectedItems[0].setAttribute("class", selectedItemClzs + ' fastlink_actived')
            }
        },
        delayDo(time, callback) {
            setTimeout(function () {
                callback && callback();
            }, time <= 0 ? 10 : time);
        },
        buildRocket() {
            //#region
            var style = `<style>
      .rotate{transform: rotate(180deg);}
      .rocketOutbox {
        /*border: 1px solid green;*/
        width: 90px;
        position: fixed;
        right: 30px;
        bottom: 200px;
        height: 90px;
        z-index: 999;
        zoom: 0.7;
      }
      .spaceship {
        zoom:0.3;
        width: 300px;
        height: 300px;
        display: flex;
        justify-content: center;
        align-items: center;
        position: absolute;
        transition: all 2s ease;
        cursor:pointer;
        z-index:999;
        animation: float 2s ease infinite alternate;
        }
        @keyframes float {
        0% {
          transform: translateY(10px);
        }
        100% {
          transform: translateY(0px);
        }
        }
        .spaceship.launch {
        bottom: 120%;
        animation: launch 3s ease;
        }
        @keyframes launch {
        0% {
          bottom: 130px;
          transform: translatex(2px);
        }
        10% {
          transform: translatex(-2px);
        }
        20% {
          transform: translatex(2px);
        }
        30% {
          transform: translatex(-2px);
        }
        60% {
          transform: translatex(0px);
          bottom: 150px;
        }
        100% {
          bottom: 120%;
        }
        }
        .spaceship.land {
        bottom: 130px;
        animation: land 3s ease;
        }
        @keyframes land {
        0% {
          bottom: 120%;
        }
        50% {
          bottom: 180px;
        }
        100% {
          bottom: 130px;
        }
        }
        .spaceship .spaceshipBody {
        width: 35%;
        height: 80%;
        background-color: white;
        border-bottom-left-radius: 100%;
        border-bottom-right-radius: 100%;
        border-top-left-radius: 100%;
        border-top-right-radius: 100%;
        display: flex;
        justify-content: center;
        position: relative;
        z-index: 1;
        box-shadow: inset 0px -173px 0px -80px white, inset 0px -213px 0px -60px #e4e4e4;
        }
        .spaceship .spaceshipBody:before {
        content: "";
        position: absolute;
        width: calc(100% - 36px);
        height: 42%;
        background-color: inherit;
        bottom: -30px;
        transform: perspective(10em) rotateX(-50deg);
        border-bottom-left-radius: 50px;
        border-bottom-right-radius: 50px;
        box-shadow: inset 0px -33px 0px 0px rgba(0, 0, 0, 0.1);
        }
        .spaceship .spaceshipBody:after {
        content: "";
        position: absolute;
        width: 45%;
        height: 40px;
        background-color: #f95959;
        bottom: -20px;
        transform: perspective(10em) rotateX(-50deg);
        border-bottom-left-radius: 50px;
        border-bottom-right-radius: 50px;
        z-index: -1;
        }
        .spaceship .spaceshipBody .spaceshipTop {
        width: 100%;
        height: 240px;
        border-radius: 100%;
        overflow: hidden;
        position: relative;
        box-shadow: inset -12px 17px 0px -7px rgba(0, 0, 0, 0.15);
        }
        .spaceship .spaceshipBody .spaceshipTop:before {
        content: "";
        background-color: #4ba3b7;
        position: absolute;
        width: 100%;
        height: 100px;
        left: calc(50% - 54%);
        border-radius: 100%;
        top: -55px;
        border: 2px solid white;
        box-shadow: inset -18px 56px 0px 3px rgba(0, 0, 0, 0.1), 0px 0px 0px 6px #f95959;
        }
        .spaceship .spaceshipBody .spaceshipWindows {
        display: flex;
        justify-content: center;
        align-items: center;
        width: 300px;
        height: 300px;
        position: absolute;
        }
        .spaceship .spaceshipBody .spaceshipWindows span {
        background-color: #ace7ef;
        box-shadow: inset -4px 4px 0px 0px rgba(0, 0, 0, 0.3), inset 0px 0px 0px 2px white;
        border: 4px solid #f95959;
        z-index: 2;
        position: absolute;
        border-radius: 100%;
        overflow: hidden;
        }
        .spaceship .spaceshipBody .spaceshipWindows span:before {
        position: absolute;
        content: "";
        background-color: white;
        width: 200%;
        height: 100%;
        transform: rotate(45deg);
        opacity: 0.4;
        }
        .spaceship .spaceshipBody .spaceshipWindows span:nth-child(1) {
        width: 30px;
        height: 30px;
        top: 23%;
        }
        .spaceship .spaceshipBody .spaceshipWindows span:nth-child(1):before {
        top: 10px;
        right: 0px;
        }
        .spaceship .spaceshipBody .spaceshipWindows span:nth-child(2) {
        width: 45px;
        height: 45px;
        top: 40%;
        }
        .spaceship .spaceshipBody .spaceshipWindows span:nth-child(2):before {
        top: 12px;
        right: 0px;
        }
        .spaceship .spaceshipBottom {
        display: flex;
        justify-content: center;
        align-items: center;
        width: 300px;
        height: 300px;
        position: absolute;
        }
        .spaceship .spaceshipBottom span {
        background-color: #4ba3b7;
        border-radius: 10px;
        position: absolute;
        overflow: hidden;
        }
        .spaceship .spaceshipBottom span:before {
        content: "";
        position: absolute;
        background-color: white;
        width: 2px;
        height: 120%;
        border-radius: 20px;
        }
        .spaceship .spaceshipBottom span:nth-child(1) {
        width: 15px;
        height: 80px;
        z-index: 2;
        bottom: 2%;
        box-shadow: inset -5px -3px 0px 0px rgba(0, 0, 0, 0.18);
        }
        .spaceship .spaceshipBottom span:nth-child(1):before {
        display: none;
        }
        .spaceship .spaceshipBottom span:nth-child(2) {
        width: 50px;
        height: 130px;
        left: 32%;
        bottom: 6%;
        transform: perspective(10em) rotateX(60deg) translateZ(-1px);
        box-shadow: inset -5px -3px 0px 0px rgba(0, 0, 0, 0.2);
        }
        .spaceship .spaceshipBottom span:nth-child(2):before {
        left: 0px;
        border-right: 2px solid #f95959;
        }
        .spaceship .spaceshipBottom span:nth-child(3) {
        width: 50px;
        height: 130px;
        right: 32%;
        bottom: 6%;
        transform: perspective(10em) rotateX(60deg) translateZ(-1px);
        box-shadow: inset -5px -3px 0px 0px rgba(0, 0, 0, 0.2);
        }
        .spaceship .spaceshipBottom span:nth-child(3):before {
        right: 0px;
        border-left: 2px solid #f95959;
        }
        .spaceship .fire {
        display: flex;
        justify-content: center;
        align-items: center;
        width: 300px;
        height: 100px;
        position: absolute;
        bottom: -50px;
        }
        .spaceship .fire.burn span {
        border-radius: 50px;
        top: 0;
        position: absolute;
        background-color: #ffd460;
        height: inherit;
        animation: fire 0.8s ease infinite alternate;
        }
        .spaceship .fire.burn span:nth-child(1) {
        width: 6px;
        height: 40px;
        left: 44%;
        transform: translateY(27px);
        box-shadow: inset 0px -7px 10px #ea5455, inset 0px -19px 10px #ffc175, 0px -7px 10px #ea5455;
        animation-delay: 0.2s;
        }
        .spaceship .fire.burn span:nth-child(1):after {
        position: absolute;
        content: "";
        width: 4px;
        height: 60%;
        border-radius: 50px;
        background-color: #ffd460;
        bottom: 0;
        transform: translate(8px, 15px);
        box-shadow: inset 0px -5px 10px #ea5455, inset 0px -19px 10px #ffc175, 0px -7px 10px #ea5455;
        }
        .spaceship .fire.burn span:nth-child(2) {
        width: 10px;
        height: 60px;
        left: calc(50% - 8px);
        transform: translateY(35px);
        box-shadow: inset 0px -10px 10px #ea5455, inset 0px -30px 10px #ffc175, 0px -7px 10px #ea5455;
        }
        .spaceship .fire.burn span:nth-child(2):after {
        position: absolute;
        content: "";
        width: 10px;
        height: 100%;
        border-radius: 10px;
        background-color: #ffd460;
        top: 0;
        transform: translate(-6px, -25px);
        box-shadow: inset 0px -5px 10px #ea5455, inset 0px -15px 10px #ffc175, 0px -7px 10px #ea5455;
        }
        .spaceship .fire.burn span:nth-child(3) {
        width: 10px;
        height: 40px;
        right: 45%;
        transform: translateY(27px);
        box-shadow: inset 0px -5px 10px #ea5455, inset 0px -30px 10px #ffc175, 0px -7px 10px #ea5455;
        animation-delay: 0.4s;
        }
        .spaceship .fire.burn span:nth-child(3):after {
        position: absolute;
        content: "";
        width: 6px;
        height: 180%;
        border-radius: 10px;
        background-color: #ffd460;
        top: 0;
        transform: translate(-6px, -15px);
        box-shadow: inset 0px -5px 10px #ea5455, inset 0px -20px 10px #ffc175, 0px -7px 10px #ea5455;
        }
        @keyframes fire {
        0% {
          height: 10px;
          bottom: 0;
          50% {
          top: 0;
          }
          100% {
          height: 20px;
          bottom: 0;
          }
        }
        }
        .spaceship .fire.burn .glow {
        position: absolute;
        width: 0px;
        height: 0px;
        border-radius: 100%;
        box-shadow: 0px 0px 50px 20px #ea5455;
        opacity: 1;
        animation: glow 0.8s ease infinite alternate;
        }
        @keyframes glow {
        0% {
          box-shadow: 0px 0px 50px 20px #ea5455;
        }
        100% {
          box-shadow: 0px 0px 50px 25px #ea5455;
        }
        }
        .shadow {
        width: 50px;
        height: 10px;
        background-color: black;
        position: absolute;
        border-radius: 100%;
        opacity: 0.2;
        top: 96px;
        z-index: 99;
        left:25px;
        animation: shadow 2s ease infinite alternate;
        transition: all 0.5s ease;
        }
        @keyframes shadow {
        0% {
          width: 40px;
        }
        100% {
          width: 46px;
        }
        }
        ${navigator.userAgent.indexOf('Firefox') > -1 ? `.spaceship{
        -moz-transform: scale(0.3) !important;
        margin-left: -104px  !important;
        margin-top: -104px !important;
        }`: ''}
        </style>`;
            var html = `
      <div class="spaceship">
      <div class="spaceshipBody">
        <div class="spaceshipTop"></div>
        <div class="spaceshipWindows"> <span></span> <span></span> </div>
      </div>
      <div class="spaceshipBottom"> <span></span> <span></span> <span></span> </div>
      <div class="fire"> <span></span> <span></span> <span></span>
        <div class="glow"></div>
      </div>
      </div><div class="shadow" style="opacity: 0.2;"></div>`;
            //#endregion
            return style + html;
        },
        buildFastLinkForDetailPage(template, data) {
            if (data) {
                var content = data.map(d => `<li class='fast_${d.id}'><a href='javascript:void(0)' class='fastNavItem' onclick='extUtil.fastLinkClick("${d.id}")' fastid='"${d.id}"'>${d.desc}</a></li>`)
                return template.replace('$content', content.join(' '));
            }
            return template;
        },
        buildTab(items) {
            var templateCss = `<style>
      .extPopupPage {
        position: fixed;
        top: 200px;
        right: 20px;
        opacity: 0.8;
        background-color: #ece4f5;
        border-radius: 4px;
        width: 300px;
        height: 240px;
        z-index: 999;
      }

      .extTitle{
        font-weight:bold;
        margin:10px;
      }
  
      .extContent {
        width: 140px;
        float: left;
      }
      .ext-list-left{
        padding-left: 10px;  
      }
      .ext-list-right{
        width: 140px;
        padding-left: 10px;  
      }
      .extContent li {
        width: 140px;
        list-style-type: none;
      }
      .extFastLinks {
        width: 290px;
        float: left;
        background: #e2e0e0;
          padding: 4px;
      }
      .extFastLinks li{
        list-style-type: none;
        float: left;
        width: 80px;
      }
      .fastlink_actived a{
        color: green;
        font-weight:bold;
      }

      .left {
        width: 50%;
        float: left;
      }
      .collspanClz{
        background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+CiAgPHBhdGggZD0iTTcuNyAzLjdMMy40IDhsNC4zIDQuM2MuNC40LjQgMSAwIDEuNC0uNC40LTEgLjQtMS40IDBsLTUtNWMtLjQtLjQtLjQtMSAwLTEuNGw1LTVjLjQtLjQgMS0uNCAxLjQgMCAuNC40LjQgMSAwIDEuNHptNyAwTDEwLjQgOGw0LjMgNC4zYy40LjQuNCAxIDAgMS40LS40LjQtMSAuNC0xLjQgMGwtNS01Yy0uNC0uNC0uNC0xIDAtMS40bDUtNWMuNC0uNCAxLS40IDEuNCAwIC40LjQuNCAxIDAgMS40eiIgZmlsbD0iIzUwNWY3OSIvPgo8L3N2Zz4K);
        width: 24px;
        position: absolute;
        height: 24px;
        cursor: pointer;
        font-weight: bold;
        text-align: center;
        color: green;
        top: 206px;
        right: 36px;
                z-index: 1000;
                line-height:36px;
      }
      .collspanOpen{
        transform: rotate(0deg);
      }
      .collspanClose{
        transform: rotate(180deg);
      }
      ${this.pageInfo.additionalCss ? this.pageInfo.additionalCss : ""}
      </style>`;
            var htmlContentTemplate = `
      <div class="extPopupPage">
        <div class="extPannel">
        <p class='extTitle'>Quick links</p>
          $content
          $fastContent
        </div>
      </div>
      <div class='collspanClz ${this.pannelIsOpen ? 'collspanClose' : 'collspanOpen'}' title="Click to show/hide the quick nav panel."></div>
      `;

            var htmlLeftListContentTemplate = `
      <ul class="extContent ext-list-left">
        $items
      </ul>`;

            var itemLeftItemContentTemplate = `
      <li class="extitem">
        <div class="">$item</div>
      </li>`;

            var htmlRightListContentTemplate = `
      <ul class="extContent ext-list-right">
        $items
      </ul>`;

            var itemRightItemContentTemplate = `
      <li class="extitem">
        <div class="">$item</div>
      </li>`;

            var htmlFastTemplate = `<ul class='extFastLinks'>$content</ul>`;

            var leftList = items.filter(p => p.type == "left").sort((a, b) => a - b);
            var rightList = items.filter(p => p.type == "right").sort((a, b) => a - b);
            var fastList = this.pageInfo.list.filter(p => p.type == "fast").sort((a, b) => a - b);
            var textLeft = leftList && leftList.map(i => {
                return itemLeftItemContentTemplate.replace('$item', `<a href='javascript:void(0);' class='link${i.anchor}' onclick='extUtil.anchorLinkClick("${i.link}")'>${i.text}</a>\r\n`);
            }).join('');
            var textRight = rightList && rightList.map(i => {
                return itemRightItemContentTemplate.replace('$item', `<a href='javascript:void(0);' class='link${i.anchor}' onclick='extUtil.anchorLinkClick("${i.link}")'>${i.text}</a>\r\n`);
            }).join('');

            htmlFastTemplate = this.pageInfo.buildFastLinkFunc ? this.pageInfo.buildFastLinkFunc(htmlFastTemplate, fastList) : "";
            this.injectFuncs = this.injectFuncs ? this.injectFuncs : [];
            this.injectFuncs.push({
                selector: '.extFastLinks li',
                func: function clickFastLinkItem() {
                    var id = this.getAttribute('fastid')
                    id && document.getElementById(id).click();
                }
            });

            var content = htmlLeftListContentTemplate.replace('$items', textLeft) + htmlRightListContentTemplate.replace('$items', textRight);
            text = templateCss + htmlContentTemplate.replace('$content', content).replace('$fastContent', htmlFastTemplate);
            return text;
        },
        popupToEdit(id, errorMsg) {
            var edit = document.getElementById('edit-issue');
            if (edit) {
                edit.click();
                setTimeout(function () {
                    var ele = document.getElementById(id);
                    if (!ele) {
                        alert(errorMsg)
                        return;
                    }
                    ele.scrollIntoView();
                    ele.focus();
                    extUtil.shrinkDiv(id);
                }, 1400);
            }
        },
        fastLinkClick(id) {
            id = id.replace("#", '');
            var curEle = document.getElementById(id);
            curEle && curEle.scrollIntoView();
            if (document.querySelector('#issue-tabs .active-tab').id == id) {
                console.log('same id, no action')
                return;
            }
            if (!document.getElementById(id) && !document.getElementById(id + '-val')) {
                //id == 'rowForcustomfield_10002' && !document.getElementById(id)
                var item = extUtil.pageInfo.list.find(i => i.id == id);
                if (item && item.onclick) {
                    item.onclick(id);
                }
            }
            else {
                //check the actual id
                var ele = document.querySelector('#' + id + ' a');
                //check extend id 
                if (!ele) {
                    ele = document.querySelector('#' + id + '-val');
                }
                ele && ele.click();
                extUtil.shrinkDiv(id);
            }
            console.log('click ' + id)
        },
        anchorLinkClick(anchor) {
            var id = anchor.replace('#', '');
            document.getElementById(id).scrollIntoView();
            this.shrinkDiv(id);
        },
        shrinkDiv(id) {
            var ele = document.getElementById(id);
            if (!ele) return;
            ele.style.border = '3px solid green';
            this.delayDo(200, function () {
                ele.style.border = '2px solid green';
                extUtil.delayDo(100, function () {
                    ele.style.border = 'none';
                })
            });
        },
        toggolePannel() {
            var $elPannel = $(document.querySelector('.extPopupPage'));
            var $elCollspan = $(document.querySelector('.collspanClz'));
            if (this.pannelIsOpen) {
                $elPannel.hide();
                $elCollspan.removeClass('collspanClose');
                $elCollspan.addClass('collspanOpen');
            }
            else {
                $elPannel.show();
                $elCollspan.removeClass('collspanOpen');
                $elCollspan.addClass('collspanClose');
            }
            this.pannelIsOpen = !this.pannelIsOpen;
        },
        delayDo(time, callback) {
            setTimeout(function () {
                callback && callback();
            }, time <= 0 ? 10 : time);
        },
        listenActivityClick() {
            $('#activitymodule').on('click', function (e) {
                extUtil.changeSelectedFastlinkTab(e.srcElement.id)
                console.log('try to postMessage to content.js');
            })
        }
    };
    setTimeout(() => {
        extUtil.render();
    }, 100);
})();