// ==UserScript== // @name 文本替换插件 // @namespace your-namespace // @version 2.0 // @description 在网页中替换文本,支持直接替换、多组替换规则和可靠的撤销功能 // @match *://*/* // @match file:///D:/Downloads/sheet.htm // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/488512/%E6%96%87%E6%9C%AC%E6%9B%BF%E6%8D%A2%E6%8F%92%E4%BB%B6.user.js // @updateURL https://update.greasyfork.icu/scripts/488512/%E6%96%87%E6%9C%AC%E6%9B%BF%E6%8D%A2%E6%8F%92%E4%BB%B6.meta.js // ==/UserScript== (function() { 'use strict'; let replacementRules = GM_getValue('replacementRules', []); let replacementHistory = []; GM_addStyle(` #textReplacerDialog { position: fixed; top: 20px; right: 20px; background-color: #f0f0f0; color: #333; padding: 15px; border: 1px solid #ccc; border-radius: 5px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); z-index: 9999; display: none; font-family: Arial, sans-serif; width: 300px; } #textReplacerDialog input { width: calc(100% - 10px); padding: 5px; margin: 5px 0; } #textReplacerDialog button { margin: 2px; padding: 5px 10px; background-color: #4CAF50; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 12px; } #textReplacerDialog button:hover { background-color: #45a049; } #rulesList { margin-top: 10px; max-height: 150px; overflow-y: auto; } .ruleItem { display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; font-size: 12px; } .ruleItem button { background-color: #f44336; padding: 2px 5px; font-size: 10px; } .ruleItem button:hover { background-color: #da190b; } `); const dialog = document.createElement('div'); dialog.id = 'textReplacerDialog'; dialog.innerHTML = `

文本替换工具

`; document.body.appendChild(dialog); const originalTextInput = document.getElementById('originalText'); const replacementTextInput = document.getElementById('replacementText'); const replaceButton = document.getElementById('replaceButton'); const addRuleButton = document.getElementById('addRuleButton'); const applyRulesButton = document.getElementById('applyRulesButton'); const undoButton = document.getElementById('undoButton'); const closeButton = document.getElementById('closeButton'); const rulesList = document.getElementById('rulesList'); replaceButton.addEventListener('click', function() { const original = originalTextInput.value.trim(); const replacement = replacementTextInput.value.trim(); if (original && replacement) { performReplace([{ original, replacement }]); } }); addRuleButton.addEventListener('click', function() { const original = originalTextInput.value.trim(); const replacement = replacementTextInput.value.trim(); if (original && replacement) { replacementRules.push({ original, replacement }); updateRulesList(); GM_setValue('replacementRules', replacementRules); originalTextInput.value = ''; replacementTextInput.value = ''; } }); applyRulesButton.addEventListener('click', function() { if (replacementRules.length > 0) { performReplace(replacementRules); } }); function updateRulesList() { rulesList.innerHTML = ''; replacementRules.forEach((rule, index) => { const ruleItem = document.createElement('div'); ruleItem.className = 'ruleItem'; ruleItem.innerHTML = ` ${rule.original} → ${rule.replacement} `; rulesList.appendChild(ruleItem); }); document.querySelectorAll('.deleteRule').forEach(button => { button.addEventListener('click', function() { const index = parseInt(this.getAttribute('data-index')); replacementRules.splice(index, 1); updateRulesList(); GM_setValue('replacementRules', replacementRules); }); }); } undoButton.addEventListener('click', function() { if (replacementHistory.length > 0) { const lastOperation = replacementHistory.pop(); restoreTextNodes(lastOperation); } }); closeButton.addEventListener('click', function() { dialog.style.display = 'none'; }); document.addEventListener('keydown', function(event) { if (event.ctrlKey && event.altKey && event.key === 'h') { dialog.style.display = dialog.style.display === 'none' ? 'block' : 'none'; } }); function performReplace(rules) { const snapshot = []; const elements = document.getElementsByTagName('*'); for (let element of elements) { for (let node of element.childNodes) { if (node.nodeType === 3) { let originalText = node.nodeValue; let newText = originalText; rules.forEach(rule => { newText = newText.replace(new RegExp(rule.original, 'g'), rule.replacement); }); if (newText !== originalText) { snapshot.push({ node: node, text: originalText }); node.nodeValue = newText; } } } } if (snapshot.length > 0) { replacementHistory.push(snapshot); } } function restoreTextNodes(snapshot) { snapshot.forEach(item => { if (item.node.parentNode) { item.node.nodeValue = item.text; } }); } updateRulesList(); })();