WF ToolBox

将各种Workflowy插件集成

目前為 2024-11-12 提交的版本,檢視 最新版本

// ==UserScript==
// @name WF ToolBox
// @description 将各种Workflowy插件集成
// @namespace github.com/openstyles/stylus
// @version 2024-11-12-Ver0.1
// @author YYYYang
// @license MIT
// @match *://*/*
// @grant GM_setClipboard
// @grant GM_notification
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @noframes
// ==/UserScript==


// 整体工具箱菜单代码带来 @稻米鼠(https://gf.qytechs.cn/zh-CN/users/36055)的【洗白白插件】

/* 弹出通知 */
const dmsCLNotification = function (text) {
  GM_notification(text, 'Success! ', 'data:image/png;base64,iVxxxxxxxxxxxxxxxCYII=');
};

/** 获取是否显示页面工具栏 **/
let isShowPageBar = GM_getValue('SHow_page_bar', true);

GM_registerMenuCommand('显示/隐藏页面工具条', () => {
  GM_setValue('SHow_page_bar', !isShowPageBar);
  isShowPageBar = GM_getValue('SHow_page_bar', true);
  alert(
    '页面工具条已被设置为【' +
    (isShowPageBar ? '显示' : '隐藏') +
    '】,仅在此后新打开页面中生效。'
  );
});



// ————————————————————————————


const Find_Replace = () => {
  // alert("查找与替换");

  (function FR_2_4() {
    function toastMsg(str, sec, err) {
      WF.showMessage(str, err);
      setTimeout(WF.hideMessage, (sec || 3) * 1000);
    }
    function applyToEachItem(functionToApply, parent) {
      functionToApply(parent);
      for (let child of parent.getChildren()) {
        applyToEachItem(functionToApply, child);
      }
    }
    function findMatchingItems(itemPredicate, parent) {
      const matches = [];
      function addIfMatch(item) {
        if (itemPredicate(item)) {
          matches.push(item);
        }
      }
      applyToEachItem(addIfMatch, parent);
      return matches;
    }
    function editableItemWithVisibleMatch(item) {
      const isVisible = WF.completedVisible() || !item.isWithinCompleted();
      return item.data.search_result && item.data.search_result.matches && isVisible && !item.isReadOnly()
    }
    const escapeForRegExp = str => str.replace(/[-\[\]{}()*+?.,\\^$|#]/g, "\\$&");
    function countMatches(items, rgx) {
      let matchCount = 0;
      items.forEach(item => {
        let result = item.data.search_result;
        if (result.nameMatches) {
          let nameMatch = item.getName().match(rgx);
          if (nameMatch) matchCount += nameMatch.length;
        }
        if (result.noteMatches) {
          let noteMatch = item.getNote().match(rgx);
          if (noteMatch) matchCount += noteMatch.length;
        }
      });
      return matchCount;
    }
    const htmlEscTextForContent = str => str.replace(/&/g, "&amp;").replace(/>/g, "&gt;").replace(/</g, "&lt;").replace(/\u00A0/g, "&nbsp;");
    function replaceMatches(items, rgx, r) {
      WF.editGroup(function () {
        items.forEach(item => {
          let result = item.data.search_result;
          if (result.nameMatches) WF.setItemName(item, item.getName().replace(rgx, htmlEscTextForContent(r)));
          if (result.noteMatches) WF.setItemNote(item, item.getNote().replace(rgx, htmlEscTextForContent(r)));
        });
      });
      r === "" ? WF.clearSearch() : WF.search(tQuery.replace(find, r));
    }
    const htmlEscText = str => str.replace(/&/g, "&amp;").replace(/>/g, "&gt;").replace(/</g, "&lt;").replace(/"/g, "&quot;");
    function getColors() {
      const p = document.querySelector(".page.active");
      return p ? `color:${getComputedStyle(p).color};background:${getComputedStyle(p).backgroundColor};` : "";
    }
    function showFindReplaceDialog(BODY, TITLE, aCount, cCount, searchValue) {
      const addButton = (num, name) => `<button type="button" class="btnX" id="btn${num.toString()}">${name}</button>`;
      const boxStyle = `#inputBx{${getColors()}width:95%;height:20px;display:block;margin-top:5px;border:1px solid #ccc;border-radius:4px;padding:4px}`;
      const btnStyle = `.btnX{font-size:18px;background-color:gray;border:2px solid;border-radius:20px;color:#fff;padding:5px 15px;margin-top:16px;margin-right:16px}.btnX:focus,.btnX:hover{border-color:#c4c4c4;background-color:steelblue}`;
      const box = `<div><b>Replace:</b><input value="${htmlEscText(searchValue)}" id="inputBx" type="text" spellcheck="false"></div>`;
      const buttons = addButton(1, `Replace: All (${aCount})`) + addButton(2, `Replace: Match Case (${cCount})`);
      WF.showAlertDialog(`<style>${boxStyle + btnStyle}</style><div>${BODY}</div>${box}<div>${buttons}</div>`, TITLE);
      const intervalId = setInterval(function () {
        let inputBx = document.getElementById("inputBx");
        if (inputBx) {
          clearInterval(intervalId);
          let userInput;
          const btn1 = document.getElementById("btn1");
          const btn2 = document.getElementById("btn2");
          inputBx.select();
          inputBx.addEventListener("keyup", function (event) {
            if (event.key === "Enter") {
              btn1.click();
            }
          });
          btn1.onclick = function () {
            userInput = inputBx.value;
            WF.hideDialog();
            setTimeout(function () {
              replaceMatches(Matches, rgx_gi, userInput)
            }, 50);
          };
          btn2.onclick = function () {
            userInput = inputBx.value;
            WF.hideDialog();
            setTimeout(function () {
              replaceMatches(Matches, rgx_g, userInput)
            }, 50);
          };
        }
      }, 50);
    }
    if (!WF.currentSearchQuery()) {
      return void toastMsg('请先使用搜索框键入关键词  <a href="https://workflowy.com/s/findreplace-bookmark/ynKNSb5dA77p2siT" target="_blank">⇒ 更多信息请查看</a>', 4, true);
    }
    const tQuery = WF.currentSearchQuery().trim();
    const Matches = findMatchingItems(editableItemWithVisibleMatch, WF.currentItem());
    const isQuoted = tQuery.match(/(")(.+)(")/);
    const find = isQuoted ? isQuoted[2] : tQuery.includes(" ") ? false : tQuery;
    if (find === false) {
      if (confirm('The search contains at least one space.\n\n1. Press OK to convert your search to "exact match".\n\n2. Activate Find/Replace again.')) {
        WF.search('"' + tQuery + '"');
      }
      return;
    }
    const title = "Find/Replace";
    const modeTxt = isQuoted ? "Exact Match, " : "Single Word/Tag, ";
    const compTxt = `Completed: ${WF.completedVisible() ? "Included" : "Excluded"}`;
    const findTxt = isQuoted ? isQuoted[0] : tQuery;
    const body = `<p><b>Mode:</b><br>${modeTxt + compTxt}</p><p><b>Find:</b><br>${htmlEscText(findTxt)}</p>`;
    const findRgx = escapeForRegExp(htmlEscTextForContent(find));
    const rgx_gi = new RegExp(findRgx, "gi");
    const rgx_g = new RegExp(findRgx, "g");
    const allCount = countMatches(Matches, rgx_gi);
    const caseCount = countMatches(Matches, rgx_g);
    if (allCount > 0) {
      showFindReplaceDialog(body, title, allCount, caseCount, find);
    } else {
      WF.showAlertDialog(`${body}<br><br><i>No matches found.</i>`, title);
    }
  })();

};

const WF_Sort = () => {
  // alert("WF节点排序");

  (function sortWF_4_0(maxChildren = 1000) {
    function toastMsg(str, sec, err) {
      WF.showMessage(str, err);
      setTimeout(WF.hideMessage, (sec || 2) * 1000);
    }
    function sortAndMove(items, reverse) {
      WF.hideDialog();
      setTimeout(() => {
        items.sort((a, b) => reverse ? b.getNameInPlainText().localeCompare(a.getNameInPlainText()) : a.getNameInPlainText().localeCompare(b.getNameInPlainText()));
        WF.editGroup(() => {
          items.forEach((item, i) => {
            if (item.getPriority() !== i) WF.moveItems([item], parent, i);
          });
        });
        // set focus to parent after sort
        WF.editItemName(parent);
        toastMsg(`Sorted ${reverse ? "Z-A." : "A-Z."}`, 1)
      }, 50);
    }
    const htmlEscText = str => str.replace(/&/g, "&amp;").replace(/>/g, "&gt;").replace(/</g, "&lt;").replace(/"/g, "&quot;");
    function showSortDialog(bodyHtml, title) {
      const addButton = (num, name) => `<button type="button" class="btnX" id="btn${num.toString()}">${name}</button>`;
      const style = '.btnX{font-size:18px;background-color:gray;border:2px solid;border-radius:20px;color:#fff;padding:5px 15px;margin-top:16px;margin-right:16px}.btnX:focus,.btnX:hover{border-color:#c4c4c4;background-color:steelblue}';
      const buttons = addButton(1, "A-Z") + addButton(2, "Z-A");
      WF.showAlertDialog(`<style>${htmlEscText(style)}</style><div>${bodyHtml}</div><div>${buttons}</div>`, title);
      const intervalId = setInterval(function () {
        let btn1 = document.getElementById("btn1");
        if (btn1) {
          clearInterval(intervalId);
          const btn2 = document.getElementById("btn2");
          btn1.focus();
          btn1.onclick = function () { sortAndMove(children) };
          btn2.onclick = function () { sortAndMove(children, true) };
        }
      }, 50);
    }
    if (WF.currentSearchQuery()) return void toastMsg("Sorting is disabled when search is active.", 4, true);
    const parent = WF.currentItem();
    if (parent.isReadOnly()) return void toastMsg("Parent is read only and cannot be sorted.", 4, true);
    const children = parent.getChildren();
    if (children.length < 2) return void toastMsg("Nothing to sort.", 4, true);
    if (children.length > maxChildren) return void toastMsg(`Sorting more than ${maxChildren} children upsets the WorkFlowy gods, and has been disabled.`, 5, true);
    const sortInfo = `Sort <b>${children.length}</b> children?`;
    showSortDialog(sortInfo, parent.getNameInPlainText());
  })();

};

const NodeWord_Count = () => {
  // alert("节点与字数统计");

  function e(a) {
    let b = a.getName().trim().length, c = a.getNote().trim().length;
    // console.log(b, c, a.getName());
    for (
      let f of a.getChildren()
    )
      a = e(f), b += a[0], c += a[1];
    return [b, c]
  }

  let d = WF.currentItem(), [g, h] = e(d);

  WF.showMessage(
    `🔘【笔记总节点 <b>${d.getNumDescendants()}</b>
      ,一级根节点 <b>${d.getChildren().length}</b>】
      &nbsp;&nbsp;&nbsp;&nbsp;🅰️【文本字数 <b>${g}</b>₩
      ,注释字数 <b>${h}</b>₩】`
  )

};

if (isShowPageBar) {

  /** 添加样式 **/
  GM_addStyle(`
  #dms-link-cleaner {
  width: 100%;
  position: fixed;
  left: 0;
  bottom: 0;
  z-index: 99999999;
  pointer-events: none;
}
#dms-link-cleaner * {
  pointer-events: auto;
}
#dms-lc-button {
  position: relative;
  margin: 0 auto;
  width: 44px;
  height: 20px;
  color: rgba(0, 0, 0, .4);
  font-size: 20px;
  line-height: 13px;
  cursor: pointer;
  text-align: center;
  border: 1px solid #AAA;
  border-radius: 16px 16px 0 0;
  background-color: rgba(255, 255, 255, .3);
  box-shadow: 0 0 5px rgba(0, 0, 0, .1);
}
#dms-lc-button:hover {
  color: rgba(0, 0, 0, .8);
  background-color: rgba(255, 255, 255, 0.8);
}
#dms-lc-panel {
  display: none;
  border-top: 5px solid #65adff;
  background-color: #FFF;
  box-shadow: 0 0 5px rgba(0, 0, 0, .1);
}
#dms-lc-panel > #dms-lc-panel-content {
  display: flex;
  justify-content: center;
  align-items: center;
  flex: 1 1 none;
  flex-wrap: wrap;
  width: 100%;
  max-width: 800px;
  margin: 0 auto;
  padding: 16px 0;
  text-align: center;
  position: relative;
}
#dms-lc-panel > #dms-lc-panel-content > .dms-lc-button {
  position: relative;
  padding: 8px 16px;
  margin: 0 8px 0 0;
  font-size: 16px;
  line-height: 1.2em;
  font-weight: lighter;
  border: 1px solid #65adff;
  border-radius: 8px;
  cursor: pointer;
}
#dms-lc-panel > #dms-lc-panel-content > .dms-lc-button:hover {
  border: 1px solid #0062d1;
  background-color: #0062d1;
  color: #FFF;
  font-weight: normal;
}
#dms-lc-panel > #dms-lc-panel-content > .dms-lc-button:hover::before {
  content: attr(data-tip);
  background-color: rgba(0, 0, 0, .9);
  border-radius:3px;
  color: #fff;
  padding: 10px;
  position: absolute;
  width: calc(100% + 20px);
  left: 50%;
  bottom: calc(100% + 10px);
  margin-left: calc(-50% - 20px);
  white-space: pre;
}
#dms-lc-panel > #dms-lc-panel-content > .dms-lc-button:hover::after {
  content: "";
  position: absolute;
  width: 0;
  height: 0;
  left: calc(50% - 8px);
  top: -10px;
  border-top: 8px solid rgba(0, 0, 0, .8);
  border-right: 8px solid transparent;
  border-left: 8px solid transparent;
}
#dms-lc-panel > #dms-lc-panel-content > .dms-lc-hr {
  width: 100%;
  margin: 5px 0;
}
#dms-lc-panel > #dms-lc-panel-content > #dmsCLButtonCoffee {
  padding: 0;
  margin: 0;
}
#dms-lc-panel > #dms-lc-panel-content > #dmsCLButtonCoffee > svg {
  width: 35px;
  height: 35px;
}
#dms-lc-panel > #dms-lc-panel-content > #dms-lc-qrcode {
  display: none;
  width: 100%;

  position: absolute;
  left: 0;
  bottom: calc(100% + 24px);
  padding: 16px;
  color: #333;
  font-size: 18px;
  line-height: 1.2em;
  border: 1px solid #CCC;
  border-radius: 12px 12px 0 0;
  background-color: #FFF;
  box-shadow: 0 6px 36px 5px rgba(0, 0, 0, .16);
}
#dms-lc-panel > #dms-lc-panel-content > #dms-lc-qrcode > img {
  width: 30%;
  max-width: 180px;
}
  `);

  /** 添加界面 **/
  const dmsLCPopPanel = document.createElement('div');
  dmsLCPopPanel.id = 'dms-link-cleaner';
  dmsLCPopPanel.innerHTML = `<div id="dms-lc-button">
  ︽
</div>
<div id="dms-lc-panel">
  <div id="dms-lc-panel-content">
    <div class="dms-lc-button" id="dmsCLFR" data-tip="查找与替换">
      查找与替换
    </div>
    <div class="dms-lc-button" id="dmsCLST" data-tip="WF节点排序">
      WF节点排序
    </div>
    <div class="dms-lc-button" id="dmsCLNWC" data-tip="节点与字数统计">
      节点与字数统计
    </div> 
    <div class="dms-lc-hr"></div>
    <div class="dms-lc-button" id="dmsCLButtonTitle" data-tip="预留功能1">
      预留功能1
    </div>
    <div class="dms-lc-button" id="dmsCLButtonPure" data-tip="预留功能2">
      预留功能2
    </div>
    <div class="dms-lc-button" id="dmsCLButtonCopyTitle" data-tip="预留功能3">
      预留功能3
    </div>
    <div class="dms-lc-button" id="dmsCLButtonCopyLink" data-tip="预留功能4">
      预留功能4
    </div>
    <div class="dms-lc-hr"></div>
    <div class="dms-lc-button" id="dmsCLButtonCleanAll" data-tip="预留功能5">
      预留功能5
    </div>
    <div class="dms-lc-button" id="dmsCLButtonLink" data-tip="联系作者">Issues</div>
    <div class="dms-lc-button" id="dmsCLButtonCoffee" data-tip="Coffee">
      <svg t="1539507279741" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1618" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M237.717333 320L277.333333 597.333333h469.333334l39.616-277.333333z" fill="#FFC107" p-id="1619"></path><path d="M832 320v-53.333333a32 32 0 0 0-32-32h-576A32 32 0 0 0 192 266.666667V320h640z" fill="#795548" p-id="1620"></path><path d="M280.384 618.666667L320 896h384l39.616-277.333333z" fill="#77574F" p-id="1621"></path><path d="M512 597.333333m-106.666667 0a106.666667 106.666667 0 1 0 213.333334 0 106.666667 106.666667 0 1 0-213.333334 0Z" fill="#77574F" p-id="1622"></path><path d="M426.666667 597.333333c0-15.616 4.501333-30.058667 11.84-42.666666h-167.253334l15.232 106.666666h169.621334A84.778667 84.778667 0 0 1 426.666667 597.333333zM585.493333 554.666667c7.338667 12.608 11.84 27.050667 11.84 42.666666a84.778667 84.778667 0 0 1-29.44 64h169.621334l15.232-106.666666h-167.253334zM768 128H256l-21.333333 106.666667h554.666666z" fill="#4E342E" p-id="1623"></path><path d="M512 448a149.333333 149.333333 0 1 1 0 298.666667 149.333333 149.333333 0 0 1 0-298.666667m0-21.333333c-94.101333 0-170.666667 76.565333-170.666667 170.666666s76.565333 170.666667 170.666667 170.666667 170.666667-76.565333 170.666667-170.666667-76.565333-170.666667-170.666667-170.666666z" fill="#5D4037" p-id="1624"></path><path d="M512 448a149.333333 149.333333 0 1 0 0 298.666667 149.333333 149.333333 0 0 0 0-298.666667z m0 234.666667a85.333333 85.333333 0 1 1 0-170.666667 85.333333 85.333333 0 0 1 0 170.666667z" fill="#FFF3E0" p-id="1625"></path></svg>
    </div>
    <div id="dms-lc-qrcode">
      Workflowy ToolBox<br />
      (Demo)
      <hr />
      敬请期待……
    </div>
  </div>
</div>`;

  document.body.insertBefore(
    dmsLCPopPanel,
    document.body.lastChild.nextSibling
  );

  /** 事件响应函数 **/

  /* 定义元素 */

  const FR = document.getElementById('dmsCLFR');
  const ST = document.getElementById('dmsCLST');
  const NWC = document.getElementById('dmsCLNWC');



  const button = document.getElementById('dms-lc-button');
  const panel = document.getElementById('dms-lc-panel');

  const qrcode = document.getElementById('dms-lc-qrcode');

  const buttonTitle = document.getElementById('dmsCLButtonTitle');
  const buttonPure = document.getElementById('dmsCLButtonPure');

  const buttonCopyT = document.getElementById('dmsCLButtonCopyTitle');
  const buttonCopyL = document.getElementById('dmsCLButtonCopyLink');

  const buttonCleanLink = document.getElementById('dmsCLButtonCleanAll');

  const buttonLink = document.getElementById('dmsCLButtonLink');
  const buttonCoffee = document.getElementById('dmsCLButtonCoffee');

  /**
   * 面板切换
   */
  const dmsLCToggleEl = function (el) {
    const elStyle = getComputedStyle(el, '');
    if (elStyle.display === 'none') {
      el.style.display = 'block';
    } else {
      el.style.display = '';
    }
  };

  /** 添加监听器 **/
  /* 面板切换按钮 */
  button.addEventListener(
    'click',
    () => {
      dmsLCToggleEl(panel);
    },
    false
  );
  /* 买咖啡 */
  buttonCoffee.addEventListener(
    'click',
    () => {
      dmsLCToggleEl(qrcode);
    },
    false
  );






  // 查找与替换 https://rawbytz.github.io/find-replace/
  FR.addEventListener('click', Find_Replace, false);

  // WF节点排序 https://rawbytz.github.io/sort/
  ST.addEventListener('click', WF_Sort, false);

  // 节点与字数统计 @rawbytz @seyee
  NWC.addEventListener('click', NodeWord_Count, false);



  /* 支持链接 */
  buttonLink.addEventListener('click', goToSupport, false);
  /* 净化并复制标题和链接 */
  buttonTitle.addEventListener('click', getCleanUrlAndTitle, false);
  /* 净化并复制链接 */
  buttonPure.addEventListener('click', getCleanUrl, false);
  /* 复制当前链接和标题 */
  buttonCopyT.addEventListener('click', getUrlAndTitle, false);
  /* 复制当前链接 */
  buttonCopyL.addEventListener('click', getUrlOnly, false);
  /* 清理整个页面 */
  buttonCleanLink.addEventListener('click', cleanAllPage, false);


  /* 全屏隐藏按钮 */
  document.addEventListener('fullscreenchange', function (event) {
    if (document.fullscreenElement) {
      button.style.display = 'none';
    } else {
      button.style.display = '';
    }
  });


}

QingJ © 2025

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