Greasy Fork

中英文之间加空白

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

当前为 2023-12-13 提交的版本,查看 最新版本

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

// @version             0.7.3
// @author              CY Fung
// @namespace           UserScript
// @license             MIT
// @require             https://cdn.jsdelivr.net/gh/cyfung1031/userscript-supports@3b26dbc536646b7077fa4a2d0f8788d5f0b6fe78/library/pangu-lite.js

// @match               http://*/*
// @match               https://*/*
// @exclude             /^https?://\S+\.(txt|png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/
// @exclude             /^shttps?://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 = null;
        try {
          bodyDOM = document.body;
          let maxLoopCount = 16;
          while (!bodyDOM && --maxLoopCount >= 0) {
            await getRafPromise();
            bodyDOM = document.body;
          }
        } catch (e) {
          bodyDOM = null;
        }
  
        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
        };
        let observer;
        const callback = async () => {
          if (!observer) return;
          if (m33++ > 1e9) m33 = 9;
          let tid = m33;
          await getRafPromise();
          if (tid !== m33) return;
          let tmp = false;
          try {
            f77();
            await Promise.resolve();
            if (!observer) return;
            tmp = document.body;
          } catch (e) {
          }
          if (tmp != bodyDOM) {
            observer.takeRecords();
            observer.disconnect();
            if (tmp === false) {
              // Facebook - cross-frame error
              observer = null;
              bodyDOM = null;
            } else {
              bodyDOM = tmp;
              if (bodyDOM) {
                observer.observe(bodyDOM, config);
                callback();
              }
            }
          }
  
        };
        observer = new MutationObserver(callback);
        observer.observe(bodyDOM, config);
        callback();
  
      }
  
  
  
      Promise.resolve().then(() => {
  
        if (document.readyState !== 'loading') {
          onReady();
        } else {
          window.addEventListener("DOMContentLoaded", onReady, false);
        }
  
      });
  
    });
  
  
  })({ pangu });