中英文之間加空白

自動替你在網頁中所有的中文字和半形的英文、數字、符號之間插入空白,讓文字變得美觀好看。(pangu, 盤古之白)

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

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

// @version             0.6.17
// @author              CY Fung
// @namespace           UserScript
// @license             MIT
// @require             https://cdn.jsdelivr.net/gh/cyfung1031/pangu.js@97ce676cb1b501fd17180dde131926348f8f53a1/dist/web/pangu.js

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

// @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;

  function isBrave() {
    const ua = window.navigator.userAgent.toLowerCase();
    const isChrome = /chrome|crios/.test(ua) && ! /edge|opr\//.test(ua)
    const isBrave = isChrome && !window.google && (typeof IdleDetector === 'undefined' || typeof navigator.brave !== 'undefined');
    return isBrave
  }

  /** @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 = frameId;
        const blobURL = typeof webkitCancelAnimationFrame === 'function' ? (frame.src = URL.createObjectURL(new Blob([], { type: 'text/html' }))) : null; // avoid Brave Crash
        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
        if (blobURL) Promise.resolve().then(() => URL.revokeObjectURL(blobURL));

        removeIframeFn = (setTimeout) => {
          const removeIframeOnDocumentReady = (e) => {
            e && win.removeEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
            e = n;
            n = win = removeIframeFn = 0;
            setTimeout ? setTimeout(() => e.remove(), 200) : e.remove();
          }
          if (!setTimeout || 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
      try {
        const { requestAnimationFrame, setTimeout, clearTimeout } = fc;
        const res = { requestAnimationFrame, setTimeout, 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) {
        if (removeIframeFn) removeIframeFn();
        return null;
      }
    } catch (e) {
      console.warn(e);
      return null;
    }
  };

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

    const { requestAnimationFrame } = __CONTEXT__;

    let rafPromise = null;

    const getRafPromise = () => rafPromise || (rafPromise = new Promise(resolve => {
      requestAnimationFrame(hRes => {
        rafPromise = null;
        resolve(hRes);
      });
    }));

    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);
          }
        }
      });

    }

    const delayForSiteContentReady = (location.hostname.endsWith('nga.cn') || location.pathname.includes('/code')) ? getRafPromise : () => 0;

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

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

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

      if (await delayForSiteContentReady() !== 0) await new Promise(r => setTimeout(r, 177));

      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 getRafPromise();
        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 });

QingJ © 2025

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