自动添加空格,在 CJK (中日繁)字符和英文字母之间自动添加空格

在 CJK (中日繁)字符和英文字母之间自动添加空格,考虑了代码块、动态追加内容、动态更新 DOM 等情况。

Version vom 15.03.2023. Aktuellste Version

// ==UserScript==
// @name         自动添加空格,在 CJK (中日繁)字符和英文字母之间自动添加空格
// @namespace    pangu-userscript
// @version      1.0.1
// @license      MIT
// @description  在 CJK (中日繁)字符和英文字母之间自动添加空格,考虑了代码块、动态追加内容、动态更新 DOM 等情况。
// @match        http*://*/*
// @grant        none
// ==/UserScript==

const panguCdnLink = 'https://unpkg.com/[email protected]/dist/browser/pangu.min.js'

function loadScript(url) {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.src = url;
    script.onload = () => resolve();
    script.onerror = () => reject(new Error(`Failed to load script ${url}`));
    document.body.appendChild(script);
  });
}

async function init() {
  try {
    await loadScript(panguCdnLink);

    // 处理当前 DOM 树中的节点
    processTextNodes(document.body);

    // 创建 MutationObserver 对象,监视 DOM 树的变化
    const observer = new MutationObserver((mutationsList) => {
      // 遍历 MutationRecord 列表,查找新增的 DOM 节点
      for (const mutation of mutationsList) {
        for (const addedNode of mutation.addedNodes) {
          // 如果新增的节点是元素节点,则处理它的子节点
          if (addedNode.nodeType === Node.ELEMENT_NODE) {
            processTextNodes(addedNode);
          }
        }
      }
    });

    // 配置 MutationObserver,监视子树中的节点添加、删除、属性变化等情况
    const config = { childList: true, subtree: true, attributes: true, attributeOldValue: true };
    observer.observe(document.body, config);
  } catch (err) {
    console.error(err);
  }
}

if (/[^\u0000-\u00ff]/.test(document.body.innerText)) {
  init();
}

function processTextNodes(rootNode) {
  const walker = document.createTreeWalker(
    rootNode,
    NodeFilter.SHOW_TEXT,
    { acceptNode: (node) => /[^\s]/.test(node.textContent) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT },
  );

  let node;
  while (node = walker.nextNode()) {
    if (shouldSkipNode(node)) {
      continue;
    }

    const text = node.textContent;
    if (/[^\u0000-\u00ff]/.test(text)) {
      node.textContent = pangu.spacing(text);
    }
  }
}

function shouldSkipNode(node) {
  const parentTagName = node.parentNode.tagName;
  if (parentTagName === 'CODE' || parentTagName === 'PRE') {
    return true;
  }

  if (parentTagName === 'A') {
    const href = node.parentNode.getAttribute('href');
    if (href && href.startsWith('javascript:')) {
      return true;
    }
  }

  return false;
}