Workflowy Todo Progress

待办任务进度显示器

  1. // ==UserScript==
  2. // @name Workflowy Todo Progress
  3. // @namespace https://gist.github.com/
  4. // @version 2024.12.03.001
  5. // @description 待办任务进度显示器
  6. // @author YYYYang
  7. // @match https://workflowy.com/*
  8. // @match https://*.workflowy.com/*
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function(){const style=document.createElement('style');style.textContent=`
  13. .bullet-counter {
  14. position: absolute;
  15. left: -56px;
  16. font-family: "JetBrains Mono", "Fira Code", "SF Mono", Consolas, monospace;
  17. font-size: 10px;
  18. color: #777;
  19. opacity: 0;
  20. pointer-events: none;
  21. user-select: none;
  22. display: inline-flex;
  23. align-items: right;
  24. justify-content: flex-end;
  25. background: rgba(0,0,0,0.03);
  26. border: 1px solid rgba(0,0,0,0.04);
  27. border-radius: 3px;
  28. padding: 0 4px;
  29. height: 15px;
  30. line-height: 15px;
  31. white-space: nowrap;
  32. transform: translateY(7px);
  33. }
  34.  
  35. .bullet-counter.has-content {
  36. opacity: 0.7;
  37. }
  38.  
  39. .bullet-counter .todonums {
  40. color: #666;
  41. font-weight: 600;
  42. }
  43.  
  44. .project {
  45. position: relative !important;
  46. }
  47. `;document.head.appendChild(style);function getSubtasks(item){if(!item)return{todoTypeNums:0};const SubNodes=item.getChildren();if(!SubNodes||SubNodes.length===0){return{todoTypeNums:0};}
  48. function countTodoType(groups){let count=0;let done=0;groups.forEach(group=>{if(group.data.layoutMode==="todo"){count++;if(group.data.isCompleted===true){done++;}}});return{done,count};}
  49. const result=countTodoType(SubNodes);const todoneNums=result.done;const todoTypeNums=result.count;return{todoneNums,todoTypeNums};}
  50. function updateCounter(bullet){if(!bullet||bullet.querySelector('.bullet-counter'))return;const content=bullet.querySelector('.content, .name');if(!content)return;const counter=document.createElement('span');counter.className='bullet-counter';bullet.style.position='relative';bullet.insertBefore(counter,bullet.firstChild);if(window.WF&&window.WF.getItemById){try{let itemId=bullet.getAttribute('projectid')||content.getAttribute('projectid')||bullet.getAttribute('data-projectid')||content.getAttribute('data-projectid');if(!itemId){const item=window.WF.currentItem();if(item){itemId=item.getId();}}
  51. if(itemId){const item=window.WF.getItemById(itemId);if(item){const counts=getSubtasks(item);if(counts.todoTypeNums>0){counter.innerHTML=`${counts.todoneNums}/<span class="todonums">${counts.todoTypeNums}</span>`;counter.classList.add('has-content');}else{counter.textContent='';}}}}catch(e){console.error('Error updating counter:',e);}}}
  52. function updateAllCounters(){const bullets=document.querySelectorAll('.project');bullets.forEach(updateCounter);}
  53. function waitForWF(){return new Promise((resolve)=>{const check=()=>{if(window.WF&&window.WF.rootItem&&window.WF.getItemById){resolve();}else{setTimeout(check,100);}};check();});}
  54. async function init(){await waitForWF();console.log('🍵 WF initialized, starting counter...');const observer=new MutationObserver((mutations)=>{let shouldUpdate=false;mutations.forEach(mutation=>{if(mutation.type==='childList'||(mutation.type==='attributes'&&mutation.attributeName==='class')){shouldUpdate=true;}});if(shouldUpdate){requestAnimationFrame(updateAllCounters);}});observer.observe(document.body,{childList:true,subtree:true,attributes:true,attributeFilter:['class','projectid','data-projectid']});updateAllCounters();}
  55. if(document.readyState==='complete'){init();}else{window.addEventListener('load',init);}
  56. setTimeout(init(),1000);})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址