// ==UserScript== // @name 关键字高亮工具 // @name:en Keyword Highlighter // @namespace https://github.com/boomoodxf/keyword-highlighter // @version 1.0.0 // @description 支持多关键字高亮、历史记录、配置导入导出的通用工具。使用空格分隔多个关键字,按 Ctrl+Shift+H 快速启动。 // @description:en A powerful keyword highlighter with support for multiple keywords, history management, and config import/export. Press Ctrl+Shift+H to launch. // @author 小渣渣 // @license MIT // @homepage https://github.com/boomoodxf/keyword-highlighter // @supportURL https://github.com/boomoodxf/keyword-highlighter/issues // @match *://*/* // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @run-at document-end // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NCIgaGVpZ2h0PSI2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiBmaWxsPSIjRkZGRjAwIi8+PHRleHQgeD0iMzIiIHk9IjQyIiBmb250LXNpemU9IjM2IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LWZhbWlseT0iQXJpYWwiPkE8L3RleHQ+PC9zdmc+ // @downloadURL https://update.greasyfork.icu/scripts/574807/%E5%85%B3%E9%94%AE%E5%AD%97%E9%AB%98%E4%BA%AE%E5%B7%A5%E5%85%B7.user.js // @updateURL https://update.greasyfork.icu/scripts/574807/%E5%85%B3%E9%94%AE%E5%AD%97%E9%AB%98%E4%BA%AE%E5%B7%A5%E5%85%B7.meta.js // ==/UserScript== (function() { 'use strict'; // 配置管理 const CONFIG_KEY = 'keyword_highlighter_config'; const HISTORY_KEY = 'keyword_highlighter_history'; const MAX_HISTORY = 50; // 默认高亮颜色 const COLORS = [ '#FFFF00', '#00FFFF', '#FF00FF', '#00FF00', '#FFB6C1', '#FFA500', '#98FB98', '#DDA0DD' ]; // 存储当前高亮状态 let currentHighlights = []; let highlightEnabled = false; // 获取历史记录 function getHistory() { try { return JSON.parse(GM_getValue(HISTORY_KEY, '[]')); } catch (e) { return []; } } // 保存历史记录 function saveHistory(keywords) { if (!keywords || keywords.trim() === '') return; let history = getHistory(); const entry = { keywords: keywords, timestamp: new Date().toISOString(), date: new Date().toLocaleString('zh-CN') }; // 去重 history = history.filter(h => h.keywords !== keywords); history.unshift(entry); // 限制历史记录数量 if (history.length > MAX_HISTORY) { history = history.slice(0, MAX_HISTORY); } GM_setValue(HISTORY_KEY, JSON.stringify(history)); } // 清空历史记录 function clearHistory() { GM_setValue(HISTORY_KEY, '[]'); } // 导出配置 function exportConfig() { const config = { history: getHistory(), version: '1.0.0', exportDate: new Date().toISOString() }; const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `keyword-highlighter-config-${Date.now()}.json`; a.click(); URL.revokeObjectURL(url); } // 导出单个历史记录 function exportSingleHistory(entry) { const config = { keywords: entry.keywords, timestamp: entry.timestamp, date: entry.date, version: '1.0.0' }; const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `keyword-${entry.keywords.substring(0, 20).replace(/\s+/g, '-')}-${Date.now()}.json`; a.click(); URL.revokeObjectURL(url); } // 导入配置 function importConfig() { const input = document.createElement('input'); input.type = 'file'; input.accept = 'application/json'; input.onchange = (e) => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (event) => { try { const config = JSON.parse(event.target.result); if (config.history) { // 导入完整配置 GM_setValue(HISTORY_KEY, JSON.stringify(config.history)); alert('配置导入成功!'); } else if (config.keywords) { // 导入单个历史记录 saveHistory(config.keywords); alert('历史记录导入成功!'); } else { alert('配置文件格式错误!'); } } catch (err) { alert('配置文件解析失败:' + err.message); } }; reader.readAsText(file); }; input.click(); } // 高亮文本节点 function highlightTextNode(node, keywords) { if (node.nodeType !== Node.TEXT_NODE) return; if (!node.nodeValue || node.nodeValue.trim() === '') return; const parent = node.parentNode; if (!parent) return; // 跳过已经高亮的节点和特殊标签 if (parent.classList && parent.classList.contains('keyword-highlight')) return; const skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT']; if (skipTags.includes(parent.tagName)) return; const text = node.nodeValue; const fragment = document.createDocumentFragment(); let lastIndex = 0; let matched = false; // 构建正则表达式 const escapedKeywords = keywords.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') ); const regex = new RegExp(`(${escapedKeywords.join('|')})`, 'gi'); let match; regex.lastIndex = 0; while ((match = regex.exec(text)) !== null) { matched = true; // 添加匹配前的文本 if (match.index > lastIndex) { fragment.appendChild( document.createTextNode(text.substring(lastIndex, match.index)) ); } // 创建高亮元素 const span = document.createElement('span'); span.className = 'keyword-highlight'; span.textContent = match[0]; // 根据关键字索引分配颜色 const keywordIndex = keywords.findIndex(k => k.toLowerCase() === match[0].toLowerCase() ); span.style.backgroundColor = COLORS[keywordIndex % COLORS.length]; span.style.color = '#000'; span.style.padding = '0 2px'; span.style.borderRadius = '2px'; fragment.appendChild(span); lastIndex = regex.lastIndex; } if (matched) { // 添加剩余文本 if (lastIndex < text.length) { fragment.appendChild( document.createTextNode(text.substring(lastIndex)) ); } parent.replaceChild(fragment, node); } } // 遍历所有文本节点 function walkTextNodes(node, keywords) { if (node.nodeType === Node.TEXT_NODE) { highlightTextNode(node, keywords); } else { const children = Array.from(node.childNodes); children.forEach(child => walkTextNodes(child, keywords)); } } // 执行高亮 function performHighlight(keywordsStr) { if (!keywordsStr || keywordsStr.trim() === '') { alert('请输入要高亮的关键字!'); return; } // 先清除之前的高亮 clearHighlights(); // 解析关键字(空格分隔) const keywords = keywordsStr.split(/\s+/).filter(k => k.length > 0); if (keywords.length === 0) { alert('请输入有效的关键字!'); return; } currentHighlights = keywords; highlightEnabled = true; // 保存到历史记录 saveHistory(keywordsStr); // 执行高亮 walkTextNodes(document.body, keywords); alert(`已高亮 ${keywords.length} 个关键字`); } // 清除高亮 function clearHighlights() { const highlights = document.querySelectorAll('.keyword-highlight'); highlights.forEach(span => { const parent = span.parentNode; if (parent) { parent.replaceChild(document.createTextNode(span.textContent), span); parent.normalize(); // 合并相邻文本节点 } }); currentHighlights = []; highlightEnabled = false; } // 创建主界面 function showMainDialog() { const dialog = createDialog(); const content = dialog.querySelector('.dialog-content'); const mainContent = `
当前状态: ${highlightEnabled ? '已启用' : '未启用'}
提示: 使用空格分隔多个关键字
暂无历史记录
'; } else { historyHtml += '