// ==UserScript== // @name 牢高亮 // @namespace http://tampermonkey.net/ // @version 2.8.1 // @description 高亮 // @author Hanabi // @match *://*/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @run-at document-end // ==/UserScript== (function() { 'use strict'; // ===================== 全局常量与变量 ===================== let tooltip = null; let domChangeTimer = null; let isHighlightEnabled = true; let configPanel = null; let configMask = null; let HIGHLIGHT_GROUPS = []; let mutationObserver = null; // ===================== 样式定义 ===================== GM_addStyle(` /* 高亮样式(仅添加背景色,不修改元素布局) */ .custom-highlight { background: var(--highlight-bg-color) !important; color: var(--highlight-text-color) !important; padding: 0 1px !important; border-radius: 2px !important; cursor: help !important; box-sizing: content-box !important; display: inline !important; } /* 气泡备注样式 - 新增skip-highlight类,排除高亮处理 */ .highlight-tooltip.skip-highlight { position: fixed; z-index: 999999; padding: 8px 12px; background: #333; color: #fff; font-size: 12px; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.2); max-width: 280px; word-wrap: break-word; pointer-events: none; opacity: 0; transition: opacity 0.2s; } .highlight-tooltip.skip-highlight::before { content: ''; position: absolute; bottom: -6px; left: 10px; width: 0; height: 0; border-left: 6px solid transparent; border-right: 6px solid transparent; border-top: 6px solid #333; } /* 禁用状态样式 */ body.highlight-disabled .custom-highlight { background: transparent !important; color: inherit !important; cursor: default !important; } body.highlight-disabled .highlight-tooltip { display: none !important; } /* 配置面板样式 */ .highlight-config-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 99999999; background: #fff; border: 1px solid #e0e0e0; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.2); padding: 20px; width: 80%; max-width: 700px; max-height: 80vh; overflow-y: auto; display: none; } .highlight-config-panel.show { display: block; } .config-panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid #f0f0f0; } .config-panel-title { font-size: 18px; font-weight: 600; color: #333; } .config-header-actions { display: flex; gap: 8px; } .import-export-btn { padding: 4px 8px; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; background: #607d8b; color: #fff; } .import-export-btn:hover { background: #506978; } .config-close-btn { background: #f5f5f5; border: none; border-radius: 4px; width: 28px; height: 28px; cursor: pointer; font-size: 16px; display: flex; align-items: center; justify-content: center; } .config-close-btn:hover { background: #e0e0e0; } .group-item { border: 1px solid #e0e0e0; border-radius: 6px; padding: 16px; margin-bottom: 16px; background: #fafafa; } .group-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px; } .group-info { flex: 1; } .group-name-input { width: 200px; padding: 4px 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; margin-bottom: 4px; } .group-remark-input { width: 300px; padding: 4px 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; color: #666; margin-bottom: 4px; } .color-group { display: flex; align-items: center; gap: 8px; margin-top: 4px; } .group-bgcolor-input { width: 60px; height: 28px; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; } .group-textcolor-input { width: 60px; height: 28px; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; } .group-actions { display: flex; gap: 4px; } .group-delete-btn { padding: 4px 8px; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; background: #f44336; color: #fff; } .group-delete-btn:hover { background: #d32f2f; } .config-item { margin-bottom: 16px; } .config-label { display: block; margin-bottom: 8px; font-size: 14px; color: #333; font-weight: 500; } .highlight-textarea { width: 100%; height: 100px; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; resize: vertical; line-height: 1.5; } .config-tip { font-size: 12px; color: #999; margin-top: 4px; } .add-group-btn { padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; background: #2196f3; color: #fff; margin: 8px 0; } .add-group-btn:hover { background: #1976d2; } .config-save-btn { display: block; width: 100%; padding: 8px 0; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; background: #4caf50; color: #fff; margin-top: 16px; } .config-save-btn:hover { background: #43a047; } .config-mask { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 99999998; display: none; } .config-mask.show { display: block; } .empty-tip { text-align: center; color: #999; padding: 40px 0; font-size: 14px; } #import-config-input { display: none; } /* 应急悬浮按钮样式(备用方案) */ .highlight-emergency-btn { position: fixed; bottom: 20px; right: 20px; z-index: 99999999; width: 50px; height: 50px; border-radius: 50%; background: #2196f3; color: white; border: none; font-size: 20px; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,0.2); display: flex; align-items: center; justify-content: center; } .highlight-emergency-btn:hover { background: #1976d2; } `); // ===================== 核心:读取本地配置 ===================== function loadLocalConfig() { try { const savedGroups = GM_getValue('highlightGroups', []); const savedEnabled = GM_getValue('highlightEnabled', true); // 兼容旧配置,补充字体颜色字段 HIGHLIGHT_GROUPS = Array.isArray(savedGroups) ? savedGroups.map(group => ({ ...group, textColor: group.textColor || "#000000" // 默认字体色为黑色 })) : []; isHighlightEnabled = typeof savedEnabled === 'boolean' ? savedEnabled : true; document.body.classList.toggle('highlight-disabled', !isHighlightEnabled); console.log('✅ 配置读取成功', { groups: HIGHLIGHT_GROUPS.length, enabled: isHighlightEnabled }); } catch (e) { console.error('❌ 读取配置失败', e); HIGHLIGHT_GROUPS = []; isHighlightEnabled = true; } } // ===================== 配置导入导出 ===================== function exportConfig() { const exportData = { version: '2.8', createTime: new Date().toISOString(), highlightEnabled: isHighlightEnabled, highlightGroups: HIGHLIGHT_GROUPS }; const jsonStr = JSON.stringify(exportData, null, 2); const blob = new Blob([jsonStr], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `高亮配置_${new Date().getTime()}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); alert('配置导出成功!'); } function importConfig(file) { if (!file || (!file.type.includes('json') && !file.name.endsWith('.json'))) { alert('请选择JSON格式的配置文件!'); return; } const reader = new FileReader(); reader.onload = function(e) { try { const importData = JSON.parse(e.target.result); if (!importData.highlightGroups || !Array.isArray(importData.highlightGroups)) { throw new Error('缺少高亮分组数据'); } // 兼容导入的配置,补充字体颜色字段 HIGHLIGHT_GROUPS = importData.highlightGroups.map(group => ({ ...group, textColor: group.textColor || "#000000", color: group.color || "#ff9800" })).filter(group => { if (!group.groupName || !Array.isArray(group.words)) return false; return true; }); if (typeof importData.highlightEnabled === 'boolean') { isHighlightEnabled = importData.highlightEnabled; GM_setValue('highlightEnabled', isHighlightEnabled); document.body.classList.toggle('highlight-disabled', !isHighlightEnabled); } GM_setValue('highlightGroups', HIGHLIGHT_GROUPS); renderConfigPanel(); clearAllHighlight(); initHighlight(true); alert('配置导入成功!'); } catch (error) { alert(`导入失败:${error.message}`); console.error('导入错误:', error); } }; reader.readAsText(file); } function triggerImport() { let importInput = document.getElementById('import-config-input'); if (!importInput) { importInput = document.createElement('input'); importInput.id = 'import-config-input'; importInput.type = 'file'; importInput.accept = '.json'; document.body.appendChild(importInput); importInput.addEventListener('change', function(e) { if (this.files.length > 0) { importConfig(this.files[0]); this.value = ''; } }); } importInput.click(); } // ===================== 配置面板 ===================== function initConfigPanelEvents() { const closeBtn = configPanel.querySelector('.config-close-btn'); if (closeBtn) { closeBtn.removeEventListener('click', hideConfigPanel); closeBtn.addEventListener('click', hideConfigPanel); } const exportBtn = configPanel.querySelector('.export-btn'); if (exportBtn) { exportBtn.removeEventListener('click', exportConfig); exportBtn.addEventListener('click', exportConfig); } const importBtn = configPanel.querySelector('.import-btn'); if (importBtn) { importBtn.removeEventListener('click', triggerImport); importBtn.addEventListener('click', triggerImport); } configPanel.querySelectorAll('.add-group-btn').forEach(btn => { btn.removeEventListener('click', addGroup); btn.addEventListener('click', addGroup); }); configPanel.querySelectorAll('.group-delete-btn').forEach((btn, index) => { btn.removeEventListener('click', () => deleteGroup(index)); btn.addEventListener('click', () => deleteGroup(index)); }); const saveBtn = configPanel.querySelector('.config-save-btn'); if (saveBtn) { saveBtn.removeEventListener('click', saveConfig); saveBtn.addEventListener('click', saveConfig); } } function createConfigPanel() { if (configPanel && configMask) return; // 创建遮罩层 configMask = document.createElement('div'); configMask.className = 'config-mask'; configMask.addEventListener('click', hideConfigPanel); document.body.appendChild(configMask); // 创建配置面板 configPanel = document.createElement('div'); configPanel.className = 'highlight-config-panel'; document.body.appendChild(configPanel); renderConfigPanel(); } function renderConfigPanel() { if (HIGHLIGHT_GROUPS.length === 0) { configPanel.innerHTML = `