Greasy Fork

中英文之间加空白

自动替你在网页中所有的中文字和半形的英文、数字、符号之间插入空白,让文字变得美观好看。(pangu, 盤古之白)

当前为 2023-07-22 提交的版本,查看 最新版本

// ==UserScript==
// @name                中英文之间加空白
// @name:zh-TW          中英文之間加空白

// @version             0.6.3
// @author              CY Fung
// @namespace           UserScript
// @license             MIT
// @require             https://cdn.jsdelivr.net/gh/cyfung1031/pangu.js@545926ee906bd6c002f8d93afdb467606bec92f6/dist/web/pangu.js

// @match               http://*/*
// @match               https://*/*
// @exclude             /^https?://\S+\.(txt|png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/

// @icon                https://github.com/cyfung1031/userscript-supports/raw/main/icons/blank-letter.png
// @grant               GM_setValue
// @grant               unsafeWindow
// @run-at              document-start
// @allFrames           true
// @inject-into         content

// @description         自动替你在网页中所有的中文字和半形的英文、数字、符号之间插入空白,让文字变得美观好看。(pangu, 盤古之白)
// @description:zh-TW   自動替你在網頁中所有的中文字和半形的英文、數字、符號之間插入空白,讓文字變得美觀好看。(pangu, 盤古之白)

// ==/UserScript==


((__CONTEXT__) => {

  const { pangu } = __CONTEXT__;

  const win = typeof unsafeWindow !== 'undefined' ? unsafeWindow: (this instanceof Window ? this : window);

  // Create a unique key for the script and check if it is already running
  const hkey_script = 'depcyxozwnig';
  if (win[hkey_script]) throw new Error('Duplicated Userscript Calling'); // avoid duplicated scripting
  win[hkey_script] = true;

  /** @type {globalThis.PromiseConstructor} */
  const Promise = (async () => { })().constructor; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve.
  const cleanContext = async (win) => {
    const waitFn = requestAnimationFrame; // shall have been binded to window
    try {
      let mx = 16; // MAX TRIAL
      const frameId = 'vanillajs-iframe-v1'
      let frame = document.getElementById(frameId);
      let removeIframeFn = null;
      if (!frame) {
        frame = document.createElement('iframe');
        frame.id = 'vanillajs-iframe-v1';
        frame.sandbox = 'allow-same-origin'; // script cannot be run inside iframe but API can be obtained from iframe
        let n = document.createElement('noscript'); // wrap into NOSCRPIT to avoid reflow (layouting)
        n.appendChild(frame);
        while (!document.documentElement && mx-- > 0) await new Promise(waitFn); // requestAnimationFrame here could get modified by YouTube engine
        const root = document.documentElement;
        root.appendChild(n); // throw error if root is null due to exceeding MAX TRIAL
        removeIframeFn = (setTimeout) => {
          const removeIframeOnDocumentReady = (e) => {
            e && win.removeEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
            win = null;
            setTimeout(() => {
              n.remove();
              n = null;
            }, 200);
          }
          if (document.readyState !== 'loading') {
            removeIframeOnDocumentReady();
          } else {
            win.addEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
          }
        }
      }
      while (!frame.contentWindow && mx-- > 0) await new Promise(waitFn);
      const fc = frame.contentWindow;
      if (!fc) throw "window is not found."; // throw error if root is null due to exceeding MAX TRIAL
      const { requestAnimationFrame, setInterval, setTimeout, clearInterval, clearTimeout } = fc;
      const res = { requestAnimationFrame, setInterval, setTimeout, clearInterval, clearTimeout };
      for (let k in res) res[k] = res[k].bind(win); // necessary
      if (removeIframeFn) Promise.resolve(res.setTimeout).then(removeIframeFn);
      return res;
    } catch (e) {
      console.warn(e);
      return null;
    }
  };

  cleanContext(win).then(__CONTEXT__ => {
    if (!__CONTEXT__) return null;

    const { requestAnimationFrame } = __CONTEXT__;


    class Mutex {

      constructor() {
        this.p = Promise.resolve()
      }

      lockWith(f) {
        this.p = this.p.then(() => new Promise(f).catch(console.warn))
      }

    }

    let busy = false;

    const mutex = new Mutex();

    function executor(f) {
      mutex.lockWith(unlock => {
        if (busy) {
          unlock();
          return;
        }
        busy = true;
        Promise.resolve().then(() => {
          f();
        }).then(() => {
          busy = false;
        }).then(unlock);
      });
    }

    let mzk = null;
    document.addEventListener('DOMNodeInserted', function (e) {
      if (!busy) {
        if (mzk === null) mzk = e.target;
        else mzk = true;
      }
    }, { capture: false, passive: true });

    function f77() {

      executor(() => {
        if (mzk) {
          let t = mzk;
          mzk = null;
          pangu.spacingPageTitle();
          const node = t === true ? document.body : t;
          if ((node instanceof Node) && node.isConnected) {
            pangu.spacingNode(node);
          }
        }
      });

    }

    async function onReady() {
      window.removeEventListener("DOMContentLoaded", onReady, false);

      let bodyDOM = document.body;
      let maxLoopCount = 16;

      while (!bodyDOM && --maxLoopCount >= 0) {
        await new Promise(requestAnimationFrame);
        bodyDOM = document.body;
      }
      if (!bodyDOM) return;

      executor(() => {
        pangu.spacingPageTitle();
        pangu.spacingPageBody();
      });

      let m33 = 0;
      const config = {
        childList: true,
        subtree: true
      };
      const callback = async () => {
        if (m33++ > 1e9) m33 = 9;
        let tid = m33;
        await new Promise(requestAnimationFrame);
        if (tid !== m33) return;
        f77();
        await Promise.resolve();
        let tmp = document.body;
        if (tmp != bodyDOM) {
          bodyDOM = tmp;
          observer.takeRecords();
          observer.disconnect();
          if (bodyDOM) {
            observer.observe(bodyDOM, config);
            callback();
          }
        }

      };
      const observer = new MutationObserver(callback);
      observer.observe(bodyDOM, config);
      callback();

    }



    Promise.resolve().then(() => {

      if (document.readyState !== 'loading') {
        onReady();
      } else {
        window.addEventListener("DOMContentLoaded", onReady, false);
      }

    });

  });


})({ pangu });