pingcodeHelper

hack pingcode

目前為 2022-04-28 提交的版本,檢視 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         pingcodeHelper
// @namespace    http://tampermonkey.net/
// @version      3.19
// @description  hack pingcode
// @author       Amos
// @match        https://onetoken.pingcode.com/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant      GM_registerMenuCommand
// ==/UserScript==
/* globals jQuery, $, waitForKeyElements */
GM_registerMenuCommand('Archive tasks done before 2 months', function() { 
    get_work_items().then(items=>{
        let longSetTasks=[]
        for(let item of items){
            if(item.state_type===3||item.state_type===4){
                // console.log('checking',(Date.now()-item.updated_at*1000)/24/3600000,item)
                if(Date.now()-item.updated_at*1000>24*60*3600*1000){
                    longSetTasks.push(item)
                }
            }
        }
        // let toArchiveItems=longSetTasks.slice(0,2)
        // console.log(toArchiveItems)
        archiveItems(longSetTasks).then(()=>{
            alert(`共归档${longSetTasks.length}个任务`)
        }).catch(e=>{
            alert(e)
        })
    })
}, 'r');
function get_work_items(pageSize=1000,pageIndex=0){
    return new Promise((resolve,reject)=>{
        $.ajax({
            url: 'https://onetoken.pingcode.com/api/agile/work-items',
            dataType: "json",
            data: {ps:pageSize,pi:pageIndex},
            async: true,
            cache: false,
            timeout: 30000,
            success:  (res)=> {
                if(res.code===200){
                    let data=res.data
                    let dataList=data.value
                    if(data.page_index<data.page_count-1){
                        get_work_items(pageSize,pageIndex+1).then(items=>{
                            let concatList=dataList.concat(items)
                            resolve(concatList)
                        })
                    } else {
                        resolve(dataList)
                    }
                } else{
                    reject()
                }
            },
            error: (request, status, error)=> {
                reject(error)
            },
            type: "GET"
        });
    })
}
function archiveItems(items,index=0){
    return new Promise((resolve,reject)=>{
        if(items.length===0){
            resolve()
            return
        }
        let item=items[index]
        $.ajax({
            url: `https://onetoken.pingcode.com/api/agile/work-items/${item._id}/archive`,
            dataType: "json",
            data: {},
            async: true,
            cache: false,
            timeout: 10000,
            success: function (data) {
                if(index<items.length-1){
                    archiveItems(items,index+1).then(res=>{
                        resolve()
                    })
                } else {
                    resolve()
                }
            },
            error: function (request, status, error) {
                reject(index)
            },
            type: "PUT"
        });
    })
    
}
 
 
// $.ajax({
//     url: 'https://onetoken.pingcode.com/api/agile/work-items/60e29290793b014b8ffbdeba/archive',
//     dataType: "json",
//     data: {},
//     async: true,
//     cache: false,
//     timeout: 30000,
//     success: function (data) {
//         // my success stuff
//     },
//     error: function (request, status, error) {
//         // my error stuff
//     },
//     type: "PUT"
// });
 
var pageURLCheckTimer = setInterval(
    function () {
        if (this.lastPathStr !== location.pathname ||
            this.lastQueryStr !== location.search ||
            this.lastPathStr === null ||
            this.lastQueryStr === null
        ) {
            this.lastPathStr = location.pathname;
            this.lastQueryStr = location.search;
            gmMain();
        }
    }, 222
);
 
function gmMain() {
    setTimeout(function () {
        let x = document.querySelector("#app-host-container > app-agile-root > app-agile-actual-root > agile-global > agile-global-query-detail > thy-header > div.layout-header-content > div > thy-nav > a.styx-secondary-nav-link.nav-link > span")
        if(x){
            console.log(x);
            console.log(x.textContent.trim());
            document.title = x.textContent.trim();
        }
        
    }, 3000);
}
function refreshUnread(){
    let authorization = localStorage.getItem('authorization')
    if(authorization){
        fetch('https://iris.pingcode.com/api/iris/notifications/n-unreads?t=1636023609632',{headers:{authorization: authorization}})
          .then(res => res.json())
          .then(res=>{
            if(res.code===200){
                let unreadSize=res.data.value
                let currentTitle=document.head.querySelector('title').textContent
                if(currentTitle.startsWith('(')){
                    let endIndex = currentTitle.indexOf(')')
                    if(endIndex>=0){
                        let newTitle=`(${unreadSize})${currentTitle.substring(endIndex+1)}`
                        document.head.querySelector('title').textContent=newTitle
                    }
                    
                }
            }
          })
    }
}
function hideYearJump(){
    let prevYear=document.querySelector(".thy-calendar-prev-year-btn")
    let nextYear=document.querySelector(".thy-calendar-next-year-btn")
    if(prevYear){
        prevYear.style.display='none'
    }
    if(nextYear){
        nextYear.style.display='none'
    }
}
 
function bestEffortUUID() {
  let ts = new Date().getTime()
  let hexDigits = '0123456789abcdef'
  let uuid = ts + '-'
  for (let i = 0; i < 8; i++) {
    uuid += hexDigits.substr(Math.floor(Math.random() * 0x10), 1)
  }
  return uuid
}
let currentOrder = 'original'

function initBtn(){
    let btnHead = document.querySelector('.layout-header-operation')
    if(btnHead){
        let exitsBtn = btnHead.querySelector('#notifications-order')
        if(!exitsBtn){
            let btn = document.createElement('span')
            btn.textContent = currentOrder
            btn.style = 'color:#4598e6;cursor:pointer;padding-left:10px;padding-right:5px'
            btn.id = 'notifications-order'
            btn.addEventListener('click',()=>{
                toggleOrder()
            })
            btnHead.insertBefore(btn,btnHead.firstChild)
        }
    }
}

function toggleOrder(){
    currentOrder = currentOrder==='original' ? 'group by task':'original'
    let orderBtn = document.querySelector('#notifications-order')
    if(orderBtn){
        orderBtn.textContent = currentOrder
    }
    setChildrenByOrder()
}

function groupChildNodeById(childrenNodes){
    let nodesMap = {}
    childrenNodes.forEach(child=>{
        let id = getId(child)
        if(!(id in nodesMap)){
            nodesMap[id] = {nodes:[child],lastNode:child}
        } else {
            nodesMap[id].nodes.push(child)
        }
    })
    let groupedNodes = []
    for(let id in nodesMap) {
        groupedNodes.push({...nodesMap[id]})
    }
    groupedNodes.sort((group1,group2)=>{
        let time1 = group1.lastNode.querySelector('.notice-card-pilot').textContent
        let time2 = group2.lastNode.querySelector('.notice-card-pilot').textContent
        return -compareTime(time1,time2)
    })
    let sortedNodes=[]
    groupedNodes.forEach(groupedNode=>{
        groupedNode.nodes.forEach(node=>{
            sortedNodes.push(node)
        })
    })
    return sortedNodes
}

function setChildrenByOrder(){
    let parent = document.querySelector('app-notice-entire-list')?.querySelector('.thy-card-content')
    if(!parent) {
        return
    }
    let cardNodes = parent.querySelectorAll('app-notice-card')
    let childrenNodes = []
    for(let child of cardNodes){
        childrenNodes.push(child)
    }
    let sortedChildren
    if(currentOrder==='original'){
        sortedChildren = getChildrenByTime(childrenNodes)
    } else{
        sortedChildren = groupChildNodeById(childrenNodes)
    }
    sortedChildren.forEach(child=>{
        parent.removeChild(child)
    }) 
    let childrenLen = sortedChildren.length
    while(childrenLen--){
        parent.insertBefore(sortedChildren[childrenLen],parent.firstChild)
    }
}

function getChildrenByTime(children){
    children.sort((child1,child2)=>{
        let time1 = child1.querySelector('.notice-card-pilot').textContent
        let time2 = child2.querySelector('.notice-card-pilot').textContent
        return -compareTime(time1,time2)
    })
    return children
}

function compareTime(time1,time2) {
    let monthParts1 = time1.split('月')
    let monthParts2 = time2.split('月')
    if(monthParts1.length===1&&monthParts2.length===1){
        let hour1 = Number(time1.split('小时')[0])
        let hour2 = Number(time2.split('小时')[0])
        if(hour1>hour2){
            return -1
        }
        if(hour1<hour2){
            return 1
        }
        return 0
    }
    if(monthParts1.length>monthParts2.length){
        return -1
    }
    if(monthParts1.length<monthParts2.length){
        return 1
    }
    let [month1,leftDay1] = time1.split('月')
    let [month2,leftDay2] = time2.split('月')
    month1=parseInt(month1)
    month2=parseInt(month2)
    if(month1<month2){
        return -1
    }
    if(month1>month2){
        return 1
    }
    let [day1,leftTime1] = leftDay1.split('日')
    let [day2,leftTime2] = leftDay2.split('日')
    day1=parseInt(day1)
    day2=parseInt(day2)
    if(day1<day2){
        return -1
    }
    if(day1>day2){
        return 1
    }
    time1 = leftTime1.split(' ')[1]
    time2 = leftTime2.split(' ')[1]
    return time1.localeCompare(time2)
}

function getId(node){
    let cardObj = node.querySelector('.notice-card-object')
    if(cardObj){
        let idNode = cardObj.querySelector('.text-muted')
        if(idNode){
            return idNode.textContent
        }
    }
    return ''
}
 
(function() {
    'use strict';
    // let showFinished=true
    //let btn=null
    
    let cnt=0
    changePriorityWidth()
    setInterval(()=>{
        shortcutContainerAdjustment()
        addHideChildrenLogic()
        cnt++
        
        if(cnt%60===0){
            refreshUnread()
        }
        addArchiveChildrenLogic()
        hideYearJump()
        addCreateChildrenLogic()
        initBtn()
    },1000)
// type 4 -task
// https://onetoken.pingcode.com/api/agile/work-item POST
// due: {date: 1647964799, with_time: 0}
function createSubTask(parentId,projectId,title,type,due){
    let data = {parent_id:parentId,project_id:projectId,title,due,type}
    $.ajax({
        url: `https://onetoken.pingcode.com/api/agile/work-item`,
        dataType: "json",
        contentType: "application/json; charset=utf-8",
        data:JSON.stringify(data),
        async: true,
        cache: false,
        timeout: 30000,
        success:  (res)=> {
        },
        error: (request, status, error)=> {
            console.log(error)
        },
        type: "POST"
    });
}
function addCreateBtn(agileDetail,id){
    if(agileDetail){
        let itemListParent = agileDetail.querySelector('.sub-work-item-list')
        if(itemListParent){
            if(agileDetail.querySelector('#createChildrenBtn')===null){
                let obj={btn:null,showFinished:true}
                obj.btn = document.createElement('div')
                obj.btn.textContent='批量创建子任务'
                obj.btn.id='createChildrenBtn'
                obj.btn.style='color:#aaa;cursor:pointer;width:120px;'
                agileDetail.insertBefore(obj.btn, agileDetail.firstChild)
                obj.btn.addEventListener('click',()=>{
                    showConfirm('确定批量创建子任务?',()=>{
                        $.ajax({
                            url: `https://onetoken.pingcode.com/api/agile/work-items/${id}`,
                            dataType: "json",
                            async: true,
                            cache: false,
                            timeout: 30000,
                            success:  (res)=> {
                                if(res.code===200){
                                    let data=res.data
                                    let itemData=data.value
                                    createSubTask(id,itemData.project_id,'产品方案',4,itemData.due)
                                    createSubTask(id,itemData.project_id,'前端开发',4,itemData.due)
                                    createSubTask(id,itemData.project_id,'后端开发',4,itemData.due)
                                    createSubTask(id,itemData.project_id,'测试',4,itemData.due)
                                    createSubTask(id,itemData.project_id,'产品验收',4,itemData.due)
                                    alert('已批量生成子任务,请刷新后查看')
                                } else{
                                    console.log('code is not 200, but',res.code)
                                }
                            },
                            error: (request, status, error)=> {
                                console.log(error)
                            },
                            type: "GET"
                        });
                    })
                })
            }
            
        }
    }
}
function getAgileDetail(){
    let allAgileDetails = document.querySelectorAll('.agile-work-item-detail-work-item')
    let agileDetail=null
    if(allAgileDetails.length>0){
        agileDetail=allAgileDetails[allAgileDetails.length-1]
    }
    else{
        let allAgileDetailChildren = document.querySelectorAll('.agile-work-item-detail-children')
        if(allAgileDetailChildren.length>0){
            agileDetail=allAgileDetailChildren[allAgileDetailChildren.length-1]
        }
    }
    return agileDetail
}
function getAgileLastChild(){
    let allAgileDetailChildren = document.querySelectorAll('.agile-work-item-detail-children')
    if(allAgileDetailChildren.length>0){
        return allAgileDetailChildren[allAgileDetailChildren.length-1]
    }
    return null
}
function addCreateChildrenLogic(){
    let agileDetail=getAgileLastChild()
    if(agileDetail===null||agileDetail.querySelector('#createChildrenBtn')!==null){
        return
    }
    let containers = document.querySelectorAll('.thy-dialog-container')
    if(containers.length>0){
        let currentContainer = containers[containers.length-1]
        let statusEl = currentContainer.querySelector('.thy-label-emboss-status')
        if(statusEl){
            let taskType=statusEl
            .querySelector('.font-size-sm')?.innerHTML
            if(taskType!=='用户故事'){
                return
            }
            if(!currentContainer.querySelector('thy-list-item')){
                let currentId = currentContainer.id.split('-')[0]
                addCreateBtn(agileDetail,currentId)
                
            }
        }
        
    }
}
function addArchiveChildrenLogic(){
    let agileDetail=getAgileDetail()
    if(agileDetail===null||agileDetail.querySelector('#archiveBtn')!==null){
        return
    }
    let containers = document.querySelectorAll('.thy-dialog-container')
    if(containers.length>0){
        let currentContainer = containers[containers.length-1]
        let currentId = currentContainer.id.split('-')[0]
        $.ajax({
            url: `https://onetoken.pingcode.com/api/agile/work-items/${currentId}`,
            dataType: "json",
            async: true,
            cache: false,
            timeout: 30000,
            success:  (res)=> {
                if(res.code===200){
                    let data=res.data
                    let itemData=data.value
                    if(itemData.child_ids.length>=40){
                        addArchiveBtn(itemData.child_ids,agileDetail,15)
                    } else if(itemData.child_ids.length>=15){
                        addArchiveBtn(itemData.child_ids,agileDetail,30)
                    }
                } else{
                    console.log('code is not 200, but',res.code)
                }
            },
            error: (request, status, error)=> {
                console.log(error)
            },
            type: "GET"
        });
    }
}
function dateFormat(fmt, date) {
    let ret;
    const opt = {
        "Y+": date.getFullYear().toString(),        // 年
        "m+": (date.getMonth() + 1).toString(),     // 月
        "d+": date.getDate().toString(),            // 日
        "H+": date.getHours().toString(),           // 时
        "M+": date.getMinutes().toString(),         // 分
        "S+": date.getSeconds().toString()          // 秒
    };
    for (let k in opt) {
        ret = new RegExp("(" + k + ")").exec(fmt);
        if (ret) {
            fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
        };
    };
    return fmt;
}
function addArchiveBtn(idList,agileDetail,cutoffDays){
    if(agileDetail){
        let itemListParent = agileDetail.querySelector('.sub-work-item-list')
        if(itemListParent){
            let itemList = itemListParent.querySelectorAll('.work-items-list-item')
            if(itemList.length>0){
                if(agileDetail.querySelector('#archiveBtn')===null){
                    let obj={btn:null,showFinished:true}
                    obj.btn = document.createElement('div')
                    obj.btn.textContent=`归档${cutoffDays}天前的子任务`
                    obj.btn.id='archiveBtn'
                    obj.btn.style='color:#aaa;cursor:pointer'
                    agileDetail.insertBefore(obj.btn, agileDetail.firstChild)
                    obj.btn.addEventListener('click',()=>{
                        getItemList(idList).then(workItems=>{
                            let toArchive = []
                            let cutoffTime = Date.now()-cutoffDays*24*3600*1000
                            workItems.forEach(item=>{
                                let isWorkEnd = item.state_type===3||item.state_type===4
                                let itemLastUpdated = item.updated_at*1000
                                if(isWorkEnd&&itemLastUpdated<cutoffTime){
                                    toArchive.push(item)
                                }
                            })
                            if(toArchive.length>0){
                                showArchiveList(agileDetail,toArchive)
                            } else {
                                alert('无可归档任务')
                            }
                        })
                    })
                }
            }
        }
    }
}
let confirmObj= {callback:()=>{}}
function showConfirm(message,callback){
    let popupWin = document.querySelector('#pingcode-confirm-window')
    if(!popupWin){
        popupWin = document.createElement('div')
        popupWin.id='pingcode-confirm-window'
        popupWin.style = "background-color:gray;border-radius: 5px;word-wrap: break-word;position: absolute;top: 100px;max-width:400px;left: 40%;z-index:10000;height:auto; overflow:auto;padding:10px;"
        let titleEl = document.createElement('h4')
        titleEl.innerText=message
        popupWin.appendChild(titleEl)
        let btnDiv = document.createElement('div')
        btnDiv.style='margin-top:10px;'
        popupWin.appendChild(btnDiv)
        let confirmBtn = document.createElement('button')
        confirmBtn.innerText='确定'
        confirmBtn.style="cursor:pointer"
        let cancelBtn = document.createElement('button')
        cancelBtn.style='margin-left:15px;cursor:pointer;'
        cancelBtn.innerText='取消'
        btnDiv.appendChild(confirmBtn)
        btnDiv.appendChild(cancelBtn)
        cancelBtn.addEventListener('click',()=>{
            popupWin.style.display='none'
        })
        confirmBtn.addEventListener('click',()=>{
            confirmObj.callback()
            popupWin.style.display='none'
        })
        document.body.appendChild(popupWin)
    }
    popupWin.style.display=''
    popupWin.querySelector('h4').innerText=message
    confirmObj.callback = callback
}
function showArchiveList(agileDetail,toArchive){
    let popupWin = document.querySelector('#pingcode-archive-table')
    if(popupWin){
        document.body.removeChild(popupWin)
    }
    popupWin = document.createElement('div')
    popupWin.style = "background-color:gray;position: absolute;top: 100px;left: 400px;z-index:10000;height:500px; overflow:auto;padding:10px;"
    let titleEl = document.createElement('h4')
    titleEl.innerText=`确定归档以下${toArchive.length}个任务?`
    popupWin.appendChild(titleEl)
    let notifyRoot = document.createElement('div')
    popupWin.appendChild(notifyRoot)
    notifyRoot.style="height:400px; overflow:auto;"
    let table = document.createElement('table')
    table.id = 'pingcode-archive-table'
    table.style='border:1px'
    table.cellspacing="0"
    table.cellpadding="1"
    table.border="1"
    let headerEl = document.createElement('tr')
    let idHeadEl = document.createElement('td')
    idHeadEl.innerText = '编号'
    headerEl.appendChild(idHeadEl)
    let titleHeadEl = document.createElement('td')
    titleHeadEl.innerText = '标题'
    headerEl.appendChild(titleHeadEl)
    let stateHeadEl = document.createElement('td')
    stateHeadEl.innerText = '状态'
    headerEl.appendChild(stateHeadEl)
    let lastUpdatedHeadEl = document.createElement('td')
    lastUpdatedHeadEl.innerText = '最后更新'
    headerEl.appendChild(lastUpdatedHeadEl)
    table.appendChild(headerEl)
    notifyRoot.appendChild(table)
    document.body.appendChild(popupWin)
    let btnDiv = document.createElement('div')
    btnDiv.style='margin-top:10px;'
    popupWin.appendChild(btnDiv)
    let confirmBtn = document.createElement('button')
    confirmBtn.innerText='确定'
    let cancelBtn = document.createElement('button')
    cancelBtn.style='margin-left:15px;'
    cancelBtn.innerText='取消'
    btnDiv.appendChild(confirmBtn)
    btnDiv.appendChild(cancelBtn)
    cancelBtn.addEventListener('click',()=>{
        document.body.removeChild(popupWin)
    })
    confirmBtn.addEventListener('click',()=>{
        archiveItems(toArchive).then(()=>{
            alert(`成功归档${toArchive.length}个任务`)
        }).catch(size=>{
            alert(`成功归档${size}个任务,失败${toArchive.length-size}个`)
        }).finally(()=>{
            let archiveBtn = agileDetail.querySelector('#archiveBtn')
            if(archiveBtn){
                agileDetail.removeChild(archiveBtn)
            }
        })
        // console.log(archiveItems)
        document.body.removeChild(popupWin)
    })
    toArchive.forEach(item=>{
        let trEl = document.createElement('tr')
        table.appendChild(trEl)
        let identifierEl = document.createElement('td')
        identifierEl.innerText = item.whole_identifier
        trEl.appendChild(identifierEl)
        let titleEl = document.createElement('td')
        titleEl.innerText = item.title
        trEl.appendChild(titleEl)
        let stateEl = document.createElement('td')
        stateEl.innerText = item.state_name
        trEl.appendChild(stateEl)
        let lastUpdatedEl = document.createElement('td')
        lastUpdatedEl.innerText = dateFormat('YYYY-mm-dd HH:MM',new Date(item.updated_at*1000))
        trEl.appendChild(lastUpdatedEl)
    })
    
}
function getItemList(idList){
    let promises = []
    idList.forEach(id=>{
        promises.push(getWorkItem(id))
    })
    return Promise.all(promises)
}
function getWorkItem(id){
    return new Promise((resolve,reject)=>{
        $.ajax({
            url: `https://onetoken.pingcode.com/api/agile/work-items/${id}`,
            dataType: "json",
            async: true,
            cache: false,
            timeout: 30000,
            success:  (res)=> {
                if(res.code===200){
                    let data=res.data
                    let itemData=data.value
                    itemData.state_name = data.references.lookups.states[0].name
                    resolve(itemData)
                } else{
                    reject()
                }
            },
            error: (request, status, error)=> {
                reject(error)
            },
            type: "GET"
        });
    })
}
 
function checkIfArchive(id,cutoffDays){
    let cutoffTime = Date.now()-cutoffDays*24*3600*1000
    $.ajax({
        url: `https://onetoken.pingcode.com/api/agile/work-items/${id}`,
        dataType: "json",
        async: true,
        cache: false,
        timeout: 30000,
        success:  (res)=> {
            if(res.code===200){
                let data=res.data
                let itemData=data.value
                let isWorkEnd = itemData.state_type===3||itemData.state_type===4
                let itemLastUpdated = itemData.updated_at*1000
                if(isWorkEnd&&itemLastUpdated<cutoffTime){
                    $.ajax({
                        url: `https://onetoken.pingcode.com/api/agile/work-items/${id}/archive`,
                        dataType: "json",
                        data: {},
                        async: true,
                        cache: false,
                        timeout: 10000,
                        success: function (data) {
                        },
                        error: function (request, status, error) {
                            console.log(error)
                        },
                        type: "PUT"
                    });
                } else {
                    console.log('skip',id)
                }
            } else{
                console.log(res.code)
            }
        },
        error: (request, status, error)=> {
            console.log(error)
        },
        type: "GET"
    });
}
function addHideChildrenLogic(){
    let agileDetail = getAgileDetail()
    if(agileDetail){
        let itemListParent = agileDetail.querySelector('.sub-work-item-list')
        if(itemListParent){
            let itemList = itemListParent.querySelectorAll('.work-items-list-item')
            if(itemList.length>0){
                if(agileDetail.querySelector('#displayBtn')===null){
                    let obj={btn:null,showFinished:true}
                    obj.btn = document.createElement('span')
                    obj.btn.textContent=obj.showFinished?'隐藏已完成':'显示已完成'
                    obj.btn.id='displayBtn'
                    obj.btn.style='color:#aaa;cursor:pointer'
                    agileDetail.insertBefore(obj.btn, agileDetail.firstChild)
                    obj.btn.addEventListener('click',()=>{
                        toggleShowFinished(agileDetail,obj)
                    })
                }
            }
        }
    }
}
function changePriorityWidth(){
    var sheet = document.createElement('style')
    sheet.innerHTML = ".agile .agile-work-items-list-item-container .work-item-priority {width: 8px;}";
    document.body.appendChild(sheet);
}
 
function isEnd(workItem){
    let status=workItem.querySelector('.flexible-text-container').textContent
    return status==='关闭'||status==='已完成'
}
function shortcutContainerAdjustment(){
    let shortcutContainer=document.querySelector('.shortcut-container')
    if(shortcutContainer){
        let tableContent = document.querySelector('.styx-table-content')
        if(tableContent){
            if(shortcutContainer.offsetHeight>0){
                tableContent.style='margin-bottom:28px'
            }
            else{
                tableContent.style='margin-bottom:0px'
            }
        }
    }
}
 
 
function toggleShowFinished(agileDetail,obj){
    obj.showFinished=!obj.showFinished
    obj.btn.textContent=obj.showFinished?'隐藏已完成':'显示已完成'
    if(obj.showFinished){
        let workItems=agileDetail.querySelectorAll('.work-item-info')
        for(let item of workItems){
            item.parentElement.parentElement.style='display:block'
        }
        
    } else {           
        let workItems=agileDetail.querySelectorAll('.work-item-info')
        for(let item of workItems){
            let status=item.querySelector('.flexible-text-container').textContent
            if(status==='关闭'||status==='已完成'||status==='已修复'||status==='已拒绝'){
                item.parentElement.parentElement.style='display:none'
            }
        }
    }
}
    // Your code here...
    
})();