Greasy Fork is available in English.

pingcodeHelper

hack pingcode

Autor
amos.cai
Dziennych instalacji
0
Wszystkich instalacji
33
Oceny
0 0 0
Wersja
3.25
Utworzono
26-10-2021
Zaktualizowano
06-06-2022
Licencja
Brak licencji
Dotyczy

// ==UserScript==
// @name pingcodeHelper
// @namespace http://tampermonkey.net/
// @version 3.25
// @description hack pingcode
// @author Amos
// @match https://onetoken.pingcode.com/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant GM_registerMenuCommand
// ==/UserScript==
/* globals jQuery, $, waitForKeyElements */
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 showArchiveList(toArchive,agileDetail=null){
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: 300px;right:200px;z-index:10000;height:500px; overflow:auto;padding:10px;width:850px"
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 = '编号'
idHeadEl.style="width:100px"
headerEl.appendChild(idHeadEl)
let titleHeadEl = document.createElement('td')
titleHeadEl.innerText = '标题'
titleHeadEl.style='width:500px'
headerEl.appendChild(titleHeadEl)
let stateHeadEl = document.createElement('td')
stateHeadEl.innerText = '状态'
headerEl.appendChild(stateHeadEl)
let lastUpdatedHeadEl = document.createElement('td')
lastUpdatedHeadEl.innerText = '最后更新'
lastUpdatedHeadEl.style="width:120px"
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(()=>{
if(agileDetail){
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')
let state = item.state_name?item.state_name:(item.hasOwnProperty('state_type')?([3,4].includes(item.state_type)?'已完成':'未完成'):'未知')
stateEl.innerText = state
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)
})
console.log(toArchive)
}
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)
}
}
}
showArchiveList(longSetTasks)
})
}, '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{
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{
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(hour1monthParts2.length){
return -1
}
if(monthParts1.lengthmonth2){
return 1
}
let [day1,leftTime1] = leftDay1.split('日')
let [day2,leftTime2] = leftDay2.split('日')
day1=parseInt(day1)
day2=parseInt(day2)
if(day1day2){
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
addArchiveBtn(itemData.child_ids,agileDetail,2)
addArchiveBtn(itemData.child_ids,agileDetail,10)
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 addArchiveBtn(idList,agileDetail,cutoffDays){
if(!agileDetail){
return
}
let itemListParent = agileDetail.querySelector('.sub-work-item-list')
if(!itemListParent){
return
}
let itemList = itemListParent.querySelectorAll('.work-items-list-item')
if(itemList.length ==0){
return
}
if(agileDetail.querySelector(`#archiveBtn${cutoffDays}`)===null){
let obj={btn:null,showFinished:true}
obj.btn = document.createElement('button')
obj.btn.textContent=`归档${cutoffDays}天前的子任务`
obj.btn.id=`archiveBtn${cutoffDays}`
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&&itemLastUpdated0){
showArchiveList(toArchive,agileDetail)
} 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;border-radius: 3px;"
let cancelBtn = document.createElement('button')
cancelBtn.style='margin-left:15px;cursor:pointer;border-radius: 3px;'
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 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 {
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...

})();