Greasy Fork

Greasy Fork is available in English.

替换文本

可影响输入框中的内容,支持自定义设置

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         替换文本
// @license      MIT
// @namespace    https://github.com/laiyoi/GM_scripts
// @version      1.0.4
// @description  可影响输入框中的内容,支持自定义设置
// @author       laiyoi
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_openInTab
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/sweetalert2.all.min.js
// ==/UserScript==

// 默认字典,如果没有保存过,则使用这个
let dictionary = GM_getValue("dictionary", {});

// 是否影响输入框
let affectInput = GM_getValue('setting_affect_input', true);

// 统计字典替换成功的次数
let settingSuccessTimes = GM_getValue('setting_success_times', 0);

// 显示设置框
function showSettingBox() {
  let html = `
    <div style="font-size: 1em;">
      <label class="panai-setting-label">
        影响输入框的替换
        <input type="checkbox" id="S-Affect-Input" ${affectInput ? 'checked' : ''} class="panai-setting-checkbox">
      </label>
      <h3>自定义替换词典</h3>
      <div>
        <label>关键词:</label>
        <input type="text" id="key" placeholder="输入关键词" />
      </div>
      <div>
        <label>替换文本:</label>
        <input type="text" id="value" placeholder="输入替换文本" />
      </div>
      <button id="addEntry">添加替换</button>
      <div>
        <h4>当前替换项</h4>
        <ul id="dictionaryList"></ul>
      </div>
      <div>
        <button id="importSettings">导入设置</button>
        <button id="exportSettings">导出设置</button>
      </div>
    </div>
  `;

  Swal.fire({
    title: '字典替换配置',
    html,
    icon: 'info',
    showCloseButton: true,
    confirmButtonText: '保存',
    footer: '<div style="text-align: center;font-size: 1em;">助手免费开源,Powered by <a href="https://www.example.com">example</a></div>',
    customClass: 'panai-setting-box'
  }).then((res) => {
    if (res.isConfirmed) {
      // 保存字典设置
      GM_setValue('setting_affect_input', document.getElementById('S-Affect-Input').checked);
      GM_setValue("dictionary", dictionary);
      res.isConfirmed && history.go(0);
    }
  });

  const keyInput = document.getElementById("key");
  const valueInput = document.getElementById("value");
  const dictionaryList = document.getElementById("dictionaryList");
  const addButton = document.getElementById("addEntry");
  const affectInputCheckbox = document.getElementById("S-Affect-Input");

  // 更新显示的字典列表
  function updateDictionaryList() {
    dictionaryList.innerHTML = "";
  
    // Create a wrapper for the dictionary list to make it scrollable
    const scrollWrapper = document.createElement("div");
    scrollWrapper.style.maxHeight = "300px"; // Limit the height of the dictionary list
    scrollWrapper.style.overflowY = "auto"; // Enable vertical scrolling if content overflows
    scrollWrapper.style.paddingRight = "5px"; // Add some space for scrollbar
  
    for (const [key, value] of Object.entries(dictionary)) {
      const listItem = document.createElement("li");
      
      // Compact display: use a shorter format
      listItem.textContent = `${key} → ${value}`;
  
      // Create delete button
      const deleteButton = document.createElement("button");
      deleteButton.textContent = "删除";
      deleteButton.style.marginLeft = "10px";
      deleteButton.style.fontSize = "0.8em"; // Reduce button size
      deleteButton.addEventListener("click", () => {
        delete dictionary[key];
        updateDictionaryList(); // 更新显示的字典列表
      });
  
      // Append delete button and the list item
      listItem.appendChild(deleteButton);
  
      // Style list items for more compact display
      listItem.style.display = "flex"; // Use flexbox for compact layout
      listItem.style.justifyContent = "space-between"; // Space between text and delete button
      listItem.style.marginBottom = "5px"; // Reduce spacing between items
  
      scrollWrapper.appendChild(listItem); // Add list item to the scrollable container
    }
  
    dictionaryList.appendChild(scrollWrapper); // Add the scrollable wrapper to the dictionary list container
  }

  // 添加替换项
  addButton.addEventListener("click", () => {
    const key = keyInput.value.trim();
    const value = valueInput.value.trim();

    if (key && value) {
      dictionary[key] = value;
      keyInput.value = "";
      valueInput.value = "";
      updateDictionaryList();
    }
  });

  // 导出设置为JSON(不包含 affectInput)
  document.getElementById("exportSettings").addEventListener("click", () => {
    const settingsJSON = JSON.stringify({ dictionary }); // 只导出字典
    const blob = new Blob([settingsJSON], { type: "application/json" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = "settings.json";
    a.click();
  });

  // 导入设置(不影响 affectInput)
  document.getElementById("importSettings").addEventListener("click", () => {
    Swal.fire({
      title: '选择导入文件',
      input: 'file',
      inputAttributes: {
        accept: '.json',
        'aria-label': 'Upload your settings'
      },
      showCancelButton: true,
    }).then((result) => {
      if (result.isConfirmed && result.value) {
        const file = result.value;
        const reader = new FileReader();
        
        reader.onload = function(event) {
          try {
            const importedSettings = JSON.parse(event.target.result);

            // 校验导入内容是否包含字典
            if (importedSettings.hasOwnProperty('dictionary')) {
              // 合并导入的字典到现有字典中
              dictionary = { ...dictionary, ...importedSettings.dictionary };

              updateDictionaryList(); // 更新显示的字典列表
              Swal.fire('设置已成功导入!');
            } else {
              throw new Error('导入的文件格式不正确');
            }
          } catch (error) {
            Swal.fire('导入失败', `错误信息:${error.message}`, 'error');
          }
        };

        reader.onerror = function() {
          Swal.fire('导入失败', '文件读取错误,请确保文件格式正确', 'error');
        };

        reader.readAsText(file);
      }
    });
  });

  // 初始化页面显示字典
  updateDictionaryList();
  affectInputCheckbox.checked = GM_getValue('setting_affect_input', true);
}

// 替换页面中的文本
function replacer(str) {
  const dictionary_ = {
    '湖人': '科比',
    '豆包': '董斌',
    '超越': '超载',
  }
  // prereplace
  for (const [key_, value_] of Object.entries(dictionary_)) {
    const regex_ = new RegExp(key_, 'g');
    str = str.replace(regex_, value_);
  }

  for (const [key, value] of Object.entries(dictionary)) {
    const regex = new RegExp(key, 'g');
    str = str.replace(regex, value);
  }
  return str;
}

const elementToMatch = [
  "title",
  "h1",
  "h2",
  "h3",
  "h4",
  "h5",
  "h6",
  "p",
  "article",
  "section",
  "blockquote",
  "li",
  "a",
  "CC",
  "span",
];

// 替换页面中的文本内容
function replace(root) {
  requestIdleCallback(() => {
    root
      .querySelectorAll(
        elementToMatch
          .concat(elementToMatch.map((name) => name + " *"))
          .concat(affectInput ? ["input"] : [])
          .join(",")
    ).forEach((candidate) => {
      if (!candidate.closest('.panai-setting-box')) { // 排除设置页面的内容
        if (candidate.nodeName === "INPUT" && affectInput) {
          candidate.value = replacer(candidate.value);
        } else if (candidate.textContent && candidate.textContent == candidate.innerHTML.trim()) {
          candidate.textContent = replacer(candidate.textContent);
        } else if (Array.from(candidate.childNodes).filter((c) => c.nodeName == "BR")) {
          Array.from(candidate.childNodes).forEach((maybeText) => {
            if (maybeText.nodeType === Node.TEXT_NODE) {
              maybeText.textContent = replacer(maybeText.textContent);
            }
          });
        }
      }
    });
  });
}

/**
 * @param {Element} root
 */
async function afterDomLoaded(root) {
  if (!root) return;

  const fn = () => {
    replace(root);
    root.querySelectorAll("*").forEach(async (node) => {
      if (node.shadowRoot) {
        await afterDomLoaded(node.shadowRoot);
      }
    });
  };

  while (document.readyState === "loading") {
    await new Promise((r) => setTimeout(r, 1000));
  }
  fn();
}

// 初始执行
afterDomLoaded(document);
setInterval(() => afterDomLoaded(document), 2500);

// 注册菜单命令
GM_registerMenuCommand('⚙️ 设置', () => {
  showSettingBox();
});