--- layout: post title: "替换脚本" date: 2026-06-12 18:02:36 +08:00 categories: 拾光 随笔 时光存档 tags: 风正扬 --- // ==UserScript== // @name 保留文本替换-手机优化版 7.3.9(最终版) // @namespace https://viayoo.com/ // @version 7.3.9 // @description 7.3.9 最终定稿:修复 Base64 跨域、打印空白页、公众号漏替换、规则统计实时刷新;支持文本替换 / 查找替换 / 规则管理 / 文章保存 / 悬浮图片,专为移动端 & WebView 环境裁剪优化,无破坏性变更,可长期稳定使用。 // @author You // @run-at document-end // @match https://*/* // @match *://*/* // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // ==/UserScript== (function() { 'use strict'; // 脚本自身UI字体(悬浮球/菜单/弹窗,固定14px,不影响网页) let size = 14; // 1. 抓取网页原生真实字号 const originPageFont = parseInt(getComputedStyle(document.body).fontSize) || 16; // 2. 网页可调节字号:有本地记录用记录,无则用原生字号 // 读取本地保存的字号(带px) // 读取本地纯数字,无数据默认给一个合理值,避免 NaN let textSize = parseInt(localStorage.getItem('textSize')) || 16; const SIZE_STEP = 1; const MIN_SIZE = 1; const MAX_SIZE = 120; const LONG_DELAY = 250; const REPEAT_INTERVAL = 80; // 字体调节开关:默认关闭 let fontAdjustSwitch = GM_getValue('fontAdjustSwitch', false); // 全局锁定脚本面板字体(原有样式保留) const fixedFontStyle = document.createElement('style'); fixedFontStyle.id = 'tm-fixed-ui-font'; fixedFontStyle.textContent = ` /* 最高权重:保护所有 tm- 开头组件,覆盖三方全局规则 */ html body div[class^="tm-"], html body div[class^="tm-"] *, html body span[class^="tm-"], html body span[class^="tm-"] * { font-size: ${size}px !important; } /* 专项对抗:三方 span[style*="font-size:11px"] 全局匹配 */ html body .tm-floating-btn span[style*="font-size:11px"], html body .tm-mobile-menu span[style*="font-size:11px"], html body .tm-highlight span[style*="font-size:11px"], html body .tm-find-highlight span[style*="font-size:11px"] { font-size: ${size}px !important; } /* 专项对抗:三方 fixed / top+left 定位选择器 */ html body .tm-floating-btn[style*="position:fixed"], html body .tm-floating-btn[style*="position:fixed"] *, html body .tm-mobile-menu[style*="top:"][style*="left:"], html body .tm-mobile-menu[style*="top:"][style*="left:"] * { font-size: ${size}px !important; } /* 原有自身样式保留 */ html body .tm-floating-btn, html body .tm-floating-btn *, html body .tm-mobile-menu, html body .tm-mobile-menu *, html body .tm-find-overlay, html body .tm-find-overlay *, html body .tm-rule-manager, html body .tm-rule-manager *, html body .tm-replace-dialog-mask, html body .tm-replace-dialog-mask *, html body .tm-toast, html body .tm-toast *, html body .tm-highlight, html body .tm-highlight *, html body .tm-find-highlight, html body .tm-find-highlight *, html body .img-set-panel, html body .img-set-panel * { font-size: ${size}px !important; } html body .tm-mobile-menu [style*="font-size"] { font-size: ${size}px !important; } `; document.head.appendChild(fixedFontStyle); // ========== 下面保留你原本所有:状态变量、函数、创建悬浮球代码 ========== // (这里原样不动,继续写你原来的代码) // 状态变量 let isReplacing = false; let replaceRules = GM_getValue('saveReplaceRules', []); let isEnabled = GM_getValue('enabled', true); let highlightEnabled = GM_getValue('highlightEnabled', false); let highlightDuration = GM_getValue('highlightDuration', 5); let observer = null; let originalTextMap = new Map(); let highlightTimeoutMap = new Map(); let totalReplacedCount = 0; let isPrinting = false; const INLINE_TAGS = ['SPAN','STRONG','EM','B','I','U','A','FONT']; const INLINE_NODE_TAGS = ['SPAN', 'EM', 'STRONG', 'B', 'I', 'U', 'FONT']; // 规则计数全局变量 let activeRuleSet = new Set(); let enabledRuleCount = 0; // 查找功能全局变量 let findFuzzyEnable = GM_getValue('findFuzzyEnable', true); let findOverlay = null; let findHighlights = []; let currentFindIndex = -1; let replaceHistoryStack = []; let replaceAllBackup = []; let debounceTimer = null; let tapTimer = null; let longPressTimer = null; let isDragging = false; let isLongPress = false; let lastTapTime = 0; let isAllLongPress = false; let allPressTimer = null; // 标记自身面板class前缀,禁止替换 const SELF_PANEL_CLASS = [ 'tm-floating-btn', 'tm-toast', 'tm-mobile-menu', 'tm-rule-manager', 'tm-replace-dialog-mask', 'tm-replace-dialog-box', 'tm-highlight', 'tm-find-overlay', 'tm-find-highlight' ]; function isSelfPanelNode(node){ if(!node) return false; let cur = node; while(cur){ if(cur.className && typeof cur.className === 'string'){ const cls = cur.className; for(let c of SELF_PANEL_CLASS){ if(cls.includes(c)) return true; } } cur = cur.parentElement; } return false; } // 防抖函数 function debounce(func, wait = 20) { return function executedFunction(...args) { const later = () => { clearTimeout(debounceTimer); func(...args); }; clearTimeout(debounceTimer); debounceTimer = setTimeout(later, wait); }; } // 初始化监听器 function initObserver() { if (observer) { observer.disconnect(); observer.takeRecords(); } observer = new MutationObserver(debounce(mutations => { if (!isReplacing || !isEnabled) return; let processed = 0; for (const mutation of mutations) { if (processed > 2000) break; if (mutation.type === 'childList') { for (const node of mutation.addedNodes) { if (node.nodeType === 1 && !isSelfPanelNode(node)) { scan(node); processed++; } } } else if (mutation.type === 'characterData') { if(!isSelfPanelNode(mutation.target)) applyReplace(mutation.target); processed++; } } })); } // 判断正则格式 function isRegexRule(str) { return typeof str === 'string' && str.startsWith('/') && str.lastIndexOf('/') > 0; } // 安全解析正则 function getRegex(str) { try { const last = str.lastIndexOf('/'); const regStr = str.slice(1, last); const flag = str.slice(last + 1) || 'g'; if (regStr.length > 200) return null; return new RegExp(regStr, flag); } catch (e) { console.warn('正则解析失败:', str); return null; } } // 统计所有已启用的规则总数 function calcEnabledRuleTotal() { let count = 0; replaceRules.forEach(rule => { if (rule.enabled && rule.from && rule.from.trim() !== '') { count++; } }); enabledRuleCount = count; return count; } // 重置生效规则集合 function resetActiveRuleSet() { activeRuleSet.clear(); } // 保存原文 function saveOriginalText(node, text) { originalTextMap.set(node, text); } // 获取原文 function getOriginalText(node) { return originalTextMap.get(node) || node.textContent; } // 高亮显示 function highlightReplacedText(node, newText) { if (!highlightEnabled || !node.parentNode) return node; if (node.nodeType === 1 && node.classList?.contains('tm-highlight')) return node; if (node.parentNode?.classList?.contains('tm-highlight')) return node.parentNode; const span = document.createElement('span'); span.className = 'tm-highlight'; span.style.cssText = ` background-color: rgba(255, 235, 59, 0.3); border-radius: 2px; padding: 1px 2px; transition: background-color 0.3s; display: inline; `; if (node.nodeType === Node.TEXT_NODE) { span.textContent = newText || node.textContent; node.parentNode.replaceChild(span, node); } else { const clone = node.cloneNode(true); clone.textContent = newText || node.textContent; span.appendChild(clone); node.parentNode.replaceChild(span, node); } const durSecond = GM_getValue('highlightDuration', 5); const durMs = durSecond * 1000; if (durMs > 0) { const timeoutId = setTimeout(() => removeHighlight(span), durMs); highlightTimeoutMap.set(span, timeoutId); } return span; } // 移除高亮 function removeHighlight(span) { if (!span || !span.parentNode) return; if (highlightTimeoutMap.has(span)) { clearTimeout(highlightTimeoutMap.get(span)); highlightTimeoutMap.delete(span); } const textNode = document.createTextNode(span.textContent); span.parentNode.replaceChild(textNode, span); } // 清空全部高亮 function clearAllHighlights() { document.querySelectorAll('.tm-highlight').forEach(span => removeHighlight(span)); highlightTimeoutMap.clear(); } // 文本替换核心 function applyReplace(node) { if(isSelfPanelNode(node)) return; if (node.nodeType !== Node.TEXT_NODE || !isReplacing || !isEnabled) return; if (['SCRIPT', 'STYLE', 'NOSCRIPT'].includes(node.parentNode?.tagName)) return; if (node.parentNode?.classList?.contains('tm-highlight')) return; let originalText = node.textContent; let text = originalText; let changed = false; replaceRules.forEach((rule, ruleIdx) => { if (!rule.enabled || !rule.from) return; if (isRegexRule(rule.from)) { const regex = getRegex(rule.from); if (regex) { try { const newText = text.replace(regex, rule.to); if (newText !== text) { text = newText; changed = true; activeRuleSet.add(ruleIdx); } } catch (e) {} } } else { if (text.includes(rule.from)) { text = text.split(rule.from).join(rule.to); changed = true; activeRuleSet.add(ruleIdx); } } }); if (changed && !originalTextMap.has(node)) { saveOriginalText(node, originalText); totalReplacedCount++; highlightEnabled ? highlightReplacedText(node, text) : node.textContent = text; } } // 收集被行内标签拆分的连续文本节点组 function getInlineTextGroups(root) { const groups = []; let currentGroup = []; function traverse(el) { if (isSelfPanelNode(el)) return; if (['SCRIPT', 'STYLE', 'NOSCRIPT'].includes(el.tagName)) return; if (el.nodeType === Node.TEXT_NODE) { if (el.textContent.trim() === '') return; currentGroup.push(el); return; } if (!INLINE_NODE_TAGS.includes(el.tagName)) { if (currentGroup.length > 0) { groups.push([...currentGroup]); currentGroup = []; } } for (const child of el.childNodes) { traverse(child); } } traverse(root); if (currentGroup.length > 0) { groups.push([...currentGroup]); } return groups; } // 扫描页面文本【修复公众号漏替换】 function scan(element) { if(isSelfPanelNode(element)) return; if (!element || !isReplacing || !isEnabled) return; const textGroups = getInlineTextGroups(element); textGroups.forEach(group => { const hasProcessed = group.some(node => originalTextMap.has(node)); if (hasProcessed) return; let fullText = group.map(n => n.textContent).join(''); const originText = fullText; let totalMatch = 0; replaceRules.forEach((rule, ruleIdx) => { if (!rule.enabled || !rule.from) return; if (isRegexRule(rule.from)) { const reg = getRegex(rule.from); if (!reg) return; try { const matchArr = originText.match(reg); if (matchArr) { totalMatch += matchArr.length; activeRuleSet.add(ruleIdx); } fullText = fullText.replace(reg, rule.to); } catch (e) {} } else { const cnt = (originText.split(rule.from).length - 1); if (cnt > 0) { totalMatch += cnt; activeRuleSet.add(ruleIdx); } fullText = fullText.split(rule.from).join(rule.to); } }); if (totalMatch > 0) { totalReplacedCount += totalMatch; let ptr = 0; group.forEach(node => { const oldText = node.textContent; const len = oldText.length; const newText = fullText.slice(ptr, ptr + len); ptr += len; originalTextMap.set(node, oldText); if (highlightEnabled) { highlightReplacedText(node, newText); } else { node.textContent = newText; } }); } }); // 兜底独立文本节点 const walker = document.createTreeWalker( element, NodeFilter.SHOW_TEXT, { acceptNode: function(node) { if(isSelfPanelNode(node)) return NodeFilter.FILTER_REJECT; if (['SCRIPT', 'STYLE', 'NOSCRIPT'].includes(node.parentNode?.tagName)) { return NodeFilter.FILTER_REJECT; } if (originalTextMap.has(node)) return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; } }, false ); let nodeCount = 0; while (walker.nextNode() && nodeCount < 99999) { applyReplace(walker.currentNode); nodeCount++; } } // 启停替换 function startReplace() { resetActiveRuleSet(); calcEnabledRuleTotal(); if (isReplacing) { showToast("替换已在运行", "info"); return; } isReplacing = true; if (document.body) { scan(document.body); observer.observe(document.body, { childList: true, subtree: true, characterData: true }); } updateFloatingButton(); showToast("替换已开启", "success"); } function stopReplace() { if (!isReplacing) { showToast("替换已停止", "info"); return; } isReplacing = false; if (observer) observer.disconnect(); clearAllHighlights(); if (document.body) { const walker = document.createTreeWalker( document.body, NodeFilter.SHOW_TEXT, { acceptNode: function(node) { if(isSelfPanelNode(node)) return NodeFilter.FILTER_REJECT; if (['SCRIPT', 'STYLE', 'NOSCRIPT'].includes(node.parentNode?.tagName)) { return NodeFilter.FILTER_REJECT; } return NodeFilter.FILTER_ACCEPT; } }, false ); while (walker.nextNode()) { const node = walker.currentNode; const original = getOriginalText(node); if (original && node.textContent !== original) { node.textContent = original; } } } totalReplacedCount = 0; originalTextMap.clear(); resetActiveRuleSet(); updateFloatingButton(); showToast("替换已停止,恢复原文", "info"); } // 重新应用全部规则(修复版:无报错) function forceReapplyAllRules() { restoreOriginalKeepRules(); calcEnabledRuleTotal(); if (isReplacing && document.body) { scan(document.body); } showToast("已重载全部替换规则", 'success'); } function toggleReplace() { isReplacing ? stopReplace() : startReplace(); updateFloatingButton(); calcEnabledRuleTotal(); //1. 顶部开始/停止按钮实时改字 const menuBtn = document.querySelector('.main-switch-btn'); if (menuBtn) { const stat = isReplacing; menuBtn.innerHTML = `${stat?'⏸️':'▶️'}${stat?'停止替换':'开始替换'}`; } //2. 菜单里规则管理那行统计文字刷新 const ruleMenuText = document.querySelector('[dynamicText="true"]'); if(ruleMenuText){ const openCnt = calcEnabledRuleTotal(); const total = replaceRules.length; const closeCnt = total - openCnt; ruleMenuText.textContent = `🔧 规则管理 | 开启:${openCnt}条 | 关闭:${closeCnt}条 | 总计:${total}条 | 本页生效:${activeRuleSet.size}条`; } } // 高亮开关 function toggleHighlight() { highlightEnabled = !highlightEnabled; GM_setValue('highlightEnabled', highlightEnabled); if (!highlightEnabled) { clearAllHighlights(); } if (isEnabled && isReplacing && observer) { scan(document.body); } } // 恢复原文 保留规则【修复:不再stopReplace打断运行】 function restoreOriginalKeepRules() { if (!document.body) return; // 只恢复原文,不关闭替换开关 const walker = document.createTreeWalker( document.body, NodeFilter.SHOW_TEXT, { acceptNode: function(node) { if(isSelfPanelNode(node)) return NodeFilter.FILTER_REJECT; if (['SCRIPT', 'STYLE', 'NOSCRIPT'].includes(node.parentNode?.tagName)) { return NodeFilter.FILTER_REJECT; } return NodeFilter.FILTER_ACCEPT; } }, false ); while (walker.nextNode()) { const node = walker.currentNode; const original = getOriginalText(node); if (original && node.textContent !== original) { node.textContent = original; } } totalReplacedCount = 0; originalTextMap.clear(); clearAllHighlights(); resetActiveRuleSet(); showToast("已恢复原文,规则已保存", "success"); } // 恢复原文 + 清空所有规则 function restoreOriginalClearRules() { if (!document.body) return; const wasReplacing = isReplacing; if (wasReplacing) stopReplace(); replaceRules = []; GM_setValue('saveReplaceRules', []); originalTextMap.clear(); clearAllHighlights(); location.reload(); } // 纯字体大小调节(仅改 font-size,不缩放布局,悬浮球完全不受影响) function setTextSize(size) { size = Math.max(MIN_SIZE, Math.min(MAX_SIZE, size)); textSize = size; localStorage.setItem('textSize', textSize); let oldCss = document.getElementById('text-size-style'); if (oldCss) oldCss.remove(); const style = document.createElement('style'); style.id = 'text-size-style'; style.textContent = ` body, p, div, span, a, li, td, th, h1, h2, h3, h4, h5, h6, em, strong, b, i, u, label, input, textarea { font-size: ${size}px !important; } @media print { .tm-floating-btn, .no-print { display: none !important; } } `; document.head.appendChild(style); showToast(`当前字体:${size}px`, "info"); } // 缩小字体 function fontShrink() { setTextSize(textSize - SIZE_STEP); } // 放大字体 function fontEnlarge() { setTextSize(textSize + SIZE_STEP); } // 重置字体 function fontReset() { setTextSize(16); } // ========== 页面查找功能 ========== function closeFindOverlay() { if (findOverlay) { findOverlay.remove(); findOverlay = null; } findHighlights.forEach(span => { if (span && span.parentNode) { span.parentNode.replaceChild(document.createTextNode(span.textContent), span); } }); findHighlights = []; currentFindIndex = -1; } function getAllTextNodes(root) { let nodes = []; const walk = (el) => { if (isSelfPanelNode(el)) return; if (el.nodeType === Node.TEXT_NODE) { if (!['SCRIPT','STYLE','NOSCRIPT','IFRAME'].includes(el.parentNode?.tagName)) { nodes.push(el); } return; } for (let child of el.childNodes) walk(child); }; walk(root); return nodes; } // 最小化为圆点 function minimizePanel() { if (!findOverlay) return; if (findOverlay.isPanelMini) return; findOverlay.isPanelMini = true; findOverlay.oldPanelStyle = { left: findOverlay.style.left, top: findOverlay.style.top, transform: findOverlay.style.transform, width: findOverlay.style.width, maxWidth: findOverlay.style.maxWidth, borderRadius: findOverlay.style.borderRadius, height: findOverlay.style.height }; findOverlay.style.width = '40px'; findOverlay.style.height = '40px'; findOverlay.style.borderRadius = '50%'; findOverlay.style.left = 'calc(100% - 50px)'; findOverlay.style.top = 'calc(100% - 50px)'; findOverlay.style.transform = 'none'; Array.from(findOverlay.children).forEach(el => { el.style.visibility = 'hidden'; el.style.opacity = '0'; }); } // 还原正常面板 function restorePanel() { if (!findOverlay || !findOverlay.isPanelMini) return; findOverlay.isPanelMini = false; const st = findOverlay.oldPanelStyle; findOverlay.style.left = st.left; findOverlay.style.top = st.top; findOverlay.style.transform = st.transform; findOverlay.style.width = st.width; findOverlay.style.maxWidth = st.maxWidth; findOverlay.style.borderRadius = st.borderRadius; findOverlay.style.height = st.height; Array.from(findOverlay.children).forEach(el => { el.style.visibility = ''; el.style.opacity = ''; }); const contentWrap = findOverlay.querySelectorAll('div')[2]; const replaceRow = findOverlay.querySelectorAll('div')[3]; if (contentWrap) { contentWrap.style.flexWrap = 'nowrap'; contentWrap.style.overflowX = 'auto'; } if (replaceRow) { replaceRow.style.flexWrap = 'nowrap'; replaceRow.style.overflowX = 'auto'; } } function doFindInPage(keyword) { findHighlights.forEach(span => { if (span && span.parentNode) { span.parentNode.replaceChild(document.createTextNode(span.textContent), span); } }); findHighlights = []; currentFindIndex = -1; if (!keyword.trim()) { updateFindCount(0, 0); showToast("请输入查找内容", "warning"); return; } let reg; if (keyword.startsWith('/') && keyword.lastIndexOf('/') > 0) { const lastSlash = keyword.lastIndexOf('/'); const pattern = keyword.slice(1, lastSlash); const flags = keyword.slice(lastSlash + 1) || 'gi'; try { reg = new RegExp(pattern, flags); } catch (e) { showToast("正则格式错误", "error"); return; } } else { const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); if(findFuzzyEnable){ reg = new RegExp(escaped.split('').join('.*?'), 'gi'); }else{ reg = new RegExp(escaped, 'gi'); } } const textGroups = getInlineTextGroups(document.body); textGroups.forEach(group => { let fullText = group.map(n => n.textContent).join(''); let matches = []; let res; reg.lastIndex = 0; while ((res = reg.exec(fullText)) !== null) { matches.push({start: res.index, end: reg.lastIndex}); } if(matches.length===0) return; let ptr = 0; group.forEach(node => { const oldText = node.textContent; const len = oldText.length; const nodeStart = ptr; const nodeEnd = ptr + len; ptr += len; let inNodeMatches = matches.filter(m=>m.start < nodeEnd && m.end > nodeStart); if(inNodeMatches.length===0) return; let text = node.textContent; let frag = document.createDocumentFragment(); let last = 0; inNodeMatches.forEach(m=>{ const s = Math.max(m.start - nodeStart, 0); const e = Math.min(m.end - nodeStart, len); if(s>last) frag.append(document.createTextNode(text.slice(last,s))); const hitSpan = document.createElement('span'); hitSpan.className = 'tm-find-highlight'; hitSpan.style.cssText = 'background:#ffeb3b;color:#000;border-radius:2px;padding:0 2px;'; hitSpan.textContent = text.slice(s,e); frag.append(hitSpan); findHighlights.push(hitSpan); last = e; }); if(last < len) frag.append(document.createTextNode(text.slice(last))); node.parentNode.replaceChild(frag, node); }); }); const walker = document.createTreeWalker( document.body, NodeFilter.SHOW_TEXT, { acceptNode: function(node) { if(isSelfPanelNode(node)) return NodeFilter.FILTER_REJECT; if (['SCRIPT','STYLE','NOSCRIPT'].includes(node.parentNode?.tagName)) return NodeFilter.FILTER_REJECT; if (node.parentNode.classList?.contains('tm-find-highlight')) return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; } }, false ); let node; while(node = walker.nextNode()){ let text = node.textContent; let matches = []; let res; reg.lastIndex=0; while ((res = reg.exec(text)) !== null) { matches.push({start: res.index, end: reg.lastIndex}); } for (let i = matches.length - 1; i >= 0; i--) { const m = matches[i]; const preStr = text.substring(0, m.start); const hitStr = text.substring(m.start, m.end); const sufStr = text.substring(m.end); const highlightSpan = document.createElement('span'); highlightSpan.className = 'tm-find-highlight'; highlightSpan.style.cssText = 'background:#ffeb3b;color:#000;border-radius:2px;padding:0 2px;'; highlightSpan.textContent = hitStr; const fragment = document.createDocumentFragment(); if (preStr) fragment.append(document.createTextNode(preStr)); fragment.append(highlightSpan); if (sufStr) fragment.append(document.createTextNode(sufStr)); node.parentNode.replaceChild(fragment, node); findHighlights.push(highlightSpan); node = fragment.firstChild; } } const totalNum = findHighlights.length; updateFindCount(currentFindIndex+1, totalNum); if (totalNum === 0) { showToast(`未找到:${keyword}`, "info"); return; } currentFindIndex = 0; jumpToFind(0); showToast(`共找到 ${totalNum} 处`, "success"); } function doReplaceCurrent(findTxt, replaceTxt) { if (!findTxt.trim()) return showToast("请输入查找内容", "warning"); if (findHighlights.length === 0 || currentFindIndex < 0) { return showToast("暂无可替换内容", "warning"); } const escaped = findTxt.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const reg = new RegExp(escaped, 'gi'); const span = findHighlights[currentFindIndex]; replaceHistoryStack.push({ index: currentFindIndex, text: span.textContent }); const oldText = span.textContent; const newText = oldText.replace(reg, replaceTxt); if (oldText !== newText) { span.textContent = newText; showToast("替换完成,可继续替换或撤销", "success"); } jumpToFind(1); } function doReplaceAll(findTxt, replaceTxt) { let searchVal = findTxt.trim(); if (!searchVal) return showToast("请输入查找内容", "warning"); let highlights = document.querySelectorAll(".tm-find-highlight"); if (highlights.length <= 0) return showToast("暂无匹配内容", "warning"); let count = 0; replaceAllBackup = []; highlights.forEach(el => { let oldTxt = el.textContent; replaceAllBackup.push({node: el, old: oldTxt}); let newTxt = oldTxt.split(searchVal).join(replaceTxt); if (oldTxt !== newTxt) { el.textContent = newTxt; count++; } }); totalReplacedCount = count; showToast(`替换成功,共${count}处`, "success"); } function undoReplaceAll() { if (replaceAllBackup.length === 0) return showToast("无内容可还原", "info"); replaceAllBackup.forEach(item => { if(item.node) item.node.textContent = item.old; }); replaceAllBackup = []; showToast("已还原全部内容", "success"); } function jumpToFind(step) { const total = findHighlights.length; if (total === 0) return; currentFindIndex += step; if (currentFindIndex >= total) currentFindIndex = 0; else if (currentFindIndex < 0) currentFindIndex = total - 1; findHighlights.forEach((sp, i) => { sp.style.background = i === currentFindIndex ? '#ffc107' : '#ffeb3b'; }); findHighlights[currentFindIndex].scrollIntoView({ behavior: "smooth", block: "center" }); updateFindCount(currentFindIndex + 1, total); } function updateFindCount(cur, total) { if (!findOverlay) return; const countDom = findOverlay.querySelector('.tm-find-count'); if (countDom) { countDom.innerText = `${cur}/${total}`; countDom.style.display = total > 0 ? 'inline-block' : 'none'; } } function undoReplace() { if (replaceHistoryStack.length === 0) { showToast("没有可撤销的内容", "info"); return; } const lastRecord = replaceHistoryStack.pop(); const targetIndex = lastRecord.index; const originalText = lastRecord.text; const targetSpan = findHighlights[targetIndex]; if (!targetSpan) { showToast("无法定位到被替换的元素", "error"); return; } targetSpan.textContent = originalText; showToast("已撤销上一次替换", "success"); currentFindIndex = targetIndex; findHighlights.forEach((sp, i) => { sp.style.background = i === currentFindIndex ? '#ffc107' : '#ffeb3b'; }); findHighlights[currentFindIndex].scrollIntoView({ behavior: "smooth", block: "center" }); updateFindCount(currentFindIndex + 1, findHighlights.length); } function showFindOverlay() { if (findOverlay) return; const initialLeft = (window.innerWidth - 380) / 2; const initialTop = Math.max(15, window.innerHeight * 0.12); const overlay = document.createElement('div'); overlay.className = 'tm-find-overlay'; overlay.style.cssText = ` position: fixed; left: 50%; top:${initialTop}px; transform: translateX(-50%); width:96%; max-width:380px; background:rgba(255,255,255,0.50); backdrop-filter:blur(12px); border-radius:12px; box-shadow:0 4px 16px rgba(0,0,0,0.15); z-index:2147483647; display:flex; flex-direction:column; overflow:hidden; box-sizing:border-box !important; transition:all 0.2s ease; `; overlay.addEventListener('click', function(e) { const target = e.target; if(target.closest('.mini-btn') || target.closest('button')) return; if (this.isPanelMini) { e.stopPropagation(); restorePanel(); } }); const tabBar = document.createElement('div'); tabBar.style.cssText = `display:flex;border-bottom:1px solid #eee;`; const tabFind = document.createElement('div'); tabFind.textContent = '查找'; tabFind.style.cssText = `flex:1;padding:10px 0;text-align:center;font-size:14px;border-bottom:2px solid #007aff;color:#007aff;cursor:pointer;`; const tabReplace = document.createElement('div'); tabReplace.textContent = '替换'; tabReplace.style.cssText = `flex:1;padding:10px 0;text-align:center;font-size:14px;border-bottom:2px solid transparent;color:#333;cursor:pointer;`; let isReplaceMode = false; tabFind.onclick = () => { isReplaceMode = false; tabFind.style.cssText = `flex:1;padding:10px 0;text-align:center;font-size:14px;border-bottom:2px solid #007aff;color:#007aff;cursor:pointer;`; tabReplace.style.cssText = `flex:1;padding:10px 0;text-align:center;font-size:14px;border-bottom:2px solid transparent;color:#333;cursor:pointer;`; replaceRow.style.display = 'none'; }; tabReplace.onclick = () => { isReplaceMode = true; tabReplace.style.cssText = `flex:1;padding:10px 0;text-align:center;font-size:14px;border-bottom:2px solid #007aff;color:#007aff;cursor:pointer;`; tabFind.style.cssText = `flex:1;padding:10px 0;text-align:center;font-size:14px;border-bottom:2px solid transparent;color:#333;cursor:pointer;`; replaceRow.style.display = 'flex'; }; tabBar.append(tabFind, tabReplace); const dragBar = document.createElement('div'); dragBar.className = "dragBar"; dragBar.style.cssText = `height:22px;background:#f5f7fa;cursor:move;display:flex;align-items:center;justify-content:space-between;padding:0 10px;`; dragBar.innerHTML = `
− `; dragBar.querySelector('.mini-btn').onclick = function (e) { e.stopPropagation(); minimizePanel(); }; const contentWrap = document.createElement('div'); contentWrap.style.cssText = `display:flex;align-items:center;gap:6px;padding:8px 10px;flex-wrap:nowrap !important;overflow-x:auto;width:100%;box-sizing:border-box;`; const inputWrap = document.createElement('div'); inputWrap.style.cssText = `flex:1 1 180px;position:relative;min-width:80px;transition:flex 0.2s ease;`; const input = document.createElement('input'); input.id = "searchInput"; input.type = 'text'; input.placeholder = '查找内容'; input.style.cssText = `width:100%;padding:8px 28px 8px 10px;border:1px solid rgba(226,229,233,0.5);border-radius:8px;font-size:13px;background:rgba(255,255,255,0.4);backdrop-filter:blur(10px);box-sizing:border-box;transition: border-color 0.2s;`; input.addEventListener('focus', function(){ this.style.borderColor = '#409eff'; }); input.addEventListener('blur', function(){ this.style.borderColor = '#e2e5e9'; }); const clearBtn = document.createElement('span'); clearBtn.innerText = '×'; clearBtn.style.cssText = `position:absolute;right:8px;top:50%;transform:translateY(-50%);font-size:16px;color:#999;cursor:pointer;user-select:none;display:none;`; clearBtn.onclick = () => { input.value = ''; clearBtn.style.display = 'none'; countSpan.style.display = 'none'; inputWrap.style.flex = '1 1 180px'; updateFindCount(0,0); input.focus(); }; inputWrap.appendChild(input); inputWrap.appendChild(clearBtn); const countSpan = document.createElement('span'); countSpan.className = 'tm-find-count'; countSpan.innerText = '0/0'; countSpan.style.cssText = `font-size:13px;color:#555;min-width:40px;text-align:center;flex-shrink:0;display:none;`; const makeBtn = (txt, bg, color) => { const btn = document.createElement('button'); btn.innerText = txt; btn.style.cssText = `padding:5px 7px;border:none;border-radius:6px;background:${bg};color:${color};font-size:12px;white-space:nowrap;flex-shrink:0;`; return btn; }; const btnSearch = makeBtn('搜', '#3b82f6', '#fff'); const btnPrev = makeBtn('⤴', '#f1f5f9', '#333'); const btnNext = makeBtn('⤵', '#f1f5f9', '#333'); const btnClose = makeBtn('✕', '#ef4444', '#fff'); btnSearch.onclick = () => { const kw = input.value.trim(); doFindInPage(kw); if(kw){ countSpan.style.display = 'inline-block'; inputWrap.style.flex = '1 1 120px'; } }; input.onkeydown = e => { if (e.key === 'Enter') { const kw = input.value.trim(); doFindInPage(kw); if(kw){ countSpan.style.display = 'inline-block'; inputWrap.style.flex = '1 1 120px'; } } }; btnPrev.onclick = () => jumpToFind(-1); btnNext.onclick = () => jumpToFind(1); btnClose.onclick = closeFindOverlay; const fuzzySwitch = makeBtn(findFuzzyEnable ? '模糊🔛' : '精准🔚', findFuzzyEnable ? '#9366ff' : '#9ca3af', '#fff'); fuzzySwitch.onclick = () => { findFuzzyEnable = !findFuzzyEnable; GM_setValue('findFuzzyEnable', findFuzzyEnable); fuzzySwitch.innerText = findFuzzyEnable ? '模糊🔛' : '精准🔚'; fuzzySwitch.style.background = findFuzzyEnable ? '#9366ff' : '#9ca3af'; }; contentWrap.append(inputWrap, countSpan, fuzzySwitch, btnSearch, btnPrev, btnNext, btnClose); const replaceRow = document.createElement('div'); replaceRow.style.cssText = `display:none;align-items:center;gap:6px;padding:0 10px 8px;flex-wrap:nowrap !important;overflow-x:auto;width:100%;box-sizing:border-box;`; const replaceInputWrap = document.createElement('div'); replaceInputWrap.style.cssText = `flex:1;position:relative;`; const replaceInput = document.createElement('input'); replaceInput.id = "replaceInput"; replaceInput.type = 'text'; replaceInput.placeholder = '替换为'; replaceInput.style.cssText = `width:100%;padding:8px 28px 8px 10px;border:1px solid rgba(226,229,233,0.5);border-radius:8px;font-size:13px;background:rgba(255,255,255,0.4);backdrop-filter:blur(10px);box-sizing:border-box;transition: border-color 0.2s;`; replaceInput.addEventListener('focus', function(){ this.style.borderColor = '#9366ff'; }); replaceInput.addEventListener('blur', function(){ this.style.borderColor = '#e2e5e9'; }); const replaceClearBtn = document.createElement('span'); replaceClearBtn.innerText = '×'; replaceClearBtn.style.cssText = `position:absolute;right:8px;top:50%;transform:translateY(-50%);font-size:16px;color:#999;cursor:pointer;user-select:none;display:none;`; replaceInput.oninput = function(){ replaceClearBtn.style.display = this.value.trim() ? 'block' : 'none'; }; replaceClearBtn.onclick = function(){ replaceInput.value = ''; replaceClearBtn.style.display = 'none'; replaceInput.focus(); }; replaceInputWrap.appendChild(replaceInput); replaceInputWrap.appendChild(replaceClearBtn); const btnSwap = document.createElement('button'); btnSwap.style.cssText = ` padding:5px 7px; border:none; border-radius:6px; background: transparent; color:#8b5cf6; font-size:12px; white-space:nowrap; flex-shrink:0; display:flex; align-items:center; justify-content:center; cursor:pointer; `; btnSwap.innerHTML = ` `; btnSwap.onclick = swapInputText; const btnUndo = makeBtn('撤销', '#f59e0b', '#fff'); btnUndo.onclick = undoReplace; const btnReplaceCurrent = makeBtn('替换', '#007aff', '#fff'); btnReplaceCurrent.onclick = ()=>{ let findTxt = input.value.trim(); let replaceTxt = replaceInput.value.trim(); doReplaceCurrent(findTxt, replaceTxt); if(!findTxt) return; const exist = replaceRules.some(item => item.from === findTxt && item.to === replaceTxt); if(!exist){ replaceRules.push({ from: findTxt, to: replaceTxt, enabled: true }); GM_setValue('saveReplaceRules', replaceRules); showToast("已自动添加为本条规则", "success"); } }; const btnReplaceAll = makeBtn('全部', '#10b981', '#fff'); let pressTimer = null; let isLongTrigger = false; const LONG_TIME = 500; btnReplaceAll.addEventListener('touchstart', e => { e.preventDefault(); isLongTrigger = false; pressTimer = setTimeout(() => { isLongTrigger = true; undoReplaceAll(); }, LONG_TIME); }); btnReplaceAll.addEventListener('touchend', e => { e.preventDefault(); clearTimeout(pressTimer); if (!isLongTrigger) { let fTxt = input.value.trim(); let rTxt = replaceInput.value.trim(); doReplaceAll(fTxt, rTxt); if(!fTxt) return; let hasRule = replaceRules.some(v=>v.from===fTxt&&v.to===rTxt); if(!hasRule){ replaceRules.push({from:fTxt,to:rTxt,enabled:true}); GM_setValue('saveReplaceRules',replaceRules); showToast("已自动添加为本条规则","success"); } } }); btnReplaceAll.addEventListener('mousedown', () => { isLongTrigger = false; pressTimer = setTimeout(() => { isLongTrigger = true; undoReplaceAll(); }, LONG_TIME); }); btnReplaceAll.addEventListener('mouseup', () => { clearTimeout(pressTimer); if (!isLongTrigger) { let fTxt = input.value.trim(); let rTxt = replaceInput.value.trim(); doReplaceAll(fTxt, rTxt); if(!fTxt) return; let hasRule = replaceRules.some(v=>v.from===fTxt&&v.to===rTxt); if(!hasRule){ replaceRules.push({from:fTxt,to:rTxt,enabled:true}); GM_setValue('saveReplaceRules',replaceRules); showToast("已自动添加为本条规则","success"); } } }); btnReplaceAll.addEventListener('mouseleave', ()=>{ clearTimeout(pressTimer); }); replaceRow.append(replaceInputWrap, btnSwap, btnUndo, btnReplaceCurrent, btnReplaceAll); overlay.append(tabBar, dragBar, contentWrap, replaceRow); document.body.appendChild(overlay); findOverlay = overlay; let dragStartX, dragStartY, startLeft, startTop, dragging = false; const startDrag = (e) => { dragging = true; const t = e.touches ? e.touches[0] : e; dragStartX = t.clientX;dragStartY = t.clientY; startLeft = overlay.offsetLeft; startTop = overlay.offsetTop; }; const moveDrag = (e) => { if (!dragging) return; const t = e.touches ? e.touches[0] : e; let dx = t.clientX - dragStartX; let dy = t.clientY - dragStartY; let newLeft = startLeft + dx; let newTop = startTop + dy; const panelW = overlay.offsetWidth; const panelH = overlay.offsetHeight; newLeft = Math.max(0, Math.min(newLeft, window.innerWidth - panelW)); newTop = Math.max(0, Math.min(newTop, window.innerHeight - panelH)); overlay.style.left = newLeft + 'px'; overlay.style.top = newTop + 'px'; overlay.style.transform = 'none'; }; const endDrag = () => dragging = false; dragBar.addEventListener('mousedown', startDrag); dragBar.addEventListener('touchstart', startDrag); document.addEventListener('mousemove', moveDrag); document.addEventListener('touchmove', moveDrag); document.addEventListener('mouseup', endDrag); document.addEventListener('touchend', endDrag); setTimeout(() => input.focus(), 50); } function swapInputText(){ const searchInp = document.getElementById("searchInput"); const replaceInp = document.getElementById("replaceInput"); if(!searchInp || !replaceInp) return; let temp = searchInp.value; searchInp.value = replaceInp.value; replaceInp.value = temp; } // 保存当前文章 function saveCurrentArticle() { const mask = document.createElement("div"); mask.style.cssText = `position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:2147483650;display:flex;align-items:center;justify-content:center;padding:15px;`; const box = document.createElement("div"); box.style.cssText = `width:90%;max-width:360px;background:#fff;border-radius:12px;overflow:hidden;`; const titleBar = document.createElement("div"); titleBar.style.cssText = `padding:14px;text-align:center;font-size:15px;font-weight:bold;color:#222;border-bottom:1px solid #eee;`; titleBar.textContent = "选择保存方式"; const btnWrap1 = document.createElement("div"); btnWrap1.style.cssText = `padding:12px 12px 6px;display:flex;gap:8px;`; const btnWrap2 = document.createElement("div"); btnWrap2.style.cssText = `padding:6px 12px 12px;display:flex;gap:8px;`; const btnStyle = `flex:1;padding:11px;border:none;border-radius:8px;color:#fff;font-size:13px;`; const downloadTxtBtn = document.createElement("button"); downloadTxtBtn.style.cssText = btnStyle + "background:#3b82f6;"; downloadTxtBtn.textContent = "纯文本文件"; const copyTextBtn = document.createElement("button"); copyTextBtn.style.cssText = btnStyle + "background:#10b981;"; copyTextBtn.textContent = "复制全文"; // ==========改动开始:原来saveHtml按钮换成两个HTML按钮,删掉原saveHtmlBtn========== const btnWrap = document.createElement("div"); btnWrap.style.cssText = "display:flex;gap:10px;flex:1;"; const btnNormal = document.createElement("button"); btnNormal.style.cssText = "flex:1;padding:9px;border:none;border-radius:8px;background:#666;color:#fff;"; btnNormal.textContent = "普通HTML"; btnNormal.onclick = async () => { mask.remove(); try { const isWechat = !!document.querySelector('#js_content, .rich_media_content'); const floatSelector = [ '[style*="position:fixed"]','[style*="position:absolute"]', '.floating','.float','.fixed','.sticky','.popup','.modal', '.float-btn','.float-ball','.wx-float','.tool-float','.side-float', '.guide-float','.ads-float','.service-float','.kefu-float', '[class*="ad-"]','[id*="ad-"]','[class*="popup-"]','[id*="popup-"]', '.JQMA-btn-all','.JQMA-btn-del','.JQMA-inner-all','.JQMA-mark-innerBtn','.JQMA-btn-hrefAll','.JQMA-btn-hrefSpan', '.tm-floating-btn','.tm-mobile-menu','.tm-find-overlay','.tm-rule-manager','.tm-replace-dialog-mask', '[class*="float"],[class*="sniff"],[class*="parse"],[class*="download"],#sniffer-main-box,.circular-ring,.star-item' ].join(','); await new Promise(resolve => { let lastHeight = document.body.scrollHeight; let tries = 0; const interval = setInterval(() => { window.scrollTo(0, document.body.scrollHeight); document.querySelectorAll('img[data-src]').forEach(img => img.src = img.dataset.src); const newHeight = document.body.scrollHeight; if (newHeight === lastHeight || tries > 25) { clearInterval(interval); window.scrollTo(0, 0); setTimeout(resolve, 700); return; } lastHeight = newHeight; tries++; }, 300); }); const cloneDoc = document.cloneNode(true); cloneDoc.querySelectorAll("script, style, noscript, iframe").forEach(el => el.remove()); cloneDoc.querySelectorAll(floatSelector).forEach(el => el.remove()); if (isWechat) { cloneDoc.querySelectorAll( '.comment,.evaluate,.like,.recommend,.footer,.wx-footer,.msg-footer,.reply-box,.read-more,' + '.article-appreciate,#appreciate,.rich_media_tool_area,#comment_area,#content_bottom_area,.qr_code_pc_outer,.wx-float-window,' + 'p:empty,div:empty,span:empty,br,hr' ).forEach(el => el.remove()); } const htmlStr = `\n${cloneDoc.documentElement.outerHTML}`; const blob = new Blob([htmlStr], { type: "text/html;charset=utf-8" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `${title}.html`; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); showToast("纯净单文件HTML保存成功", "success"); } catch (e) { showToast("网页保存失败", "error"); } }; const btnBase64 = document.createElement("button"); btnBase64.style.cssText = "flex:1;padding:9px;border:none;border-radius:8px;background:#2589ff;color:#fff;"; btnBase64.textContent = "Base64内嵌HTML"; btnBase64.onclick = async () => { mask.remove(); try { const isWechat = !!document.querySelector('#js_content, .rich_media_content'); const floatSelector = [ '[style*="position:fixed"]','[style*="position:absolute"]', '.floating','.float','.fixed','.sticky','.popup','.modal', '.float-btn','.float-ball','.wx-float','.tool-float','.side-float', '.guide-float','.ads-float','.service-float','.kefu-float', '[class*="ad-"]','[id*="ad-"]','[class*="popup-"]','[id*="popup-"]', '.JQMA-btn-all','.JQMA-btn-del','.JQMA-inner-all','.JQMA-mark-innerBtn','.JQMA-btn-hrefAll','.JQMA-btn-hrefSpan', '.tm-floating-btn','.tm-mobile-menu','.tm-find-overlay','.tm-rule-manager','.tm-replace-dialog-mask', '[class*="float"],[class*="sniff"],[class*="parse"],[class*="download"],#sniffer-main-box,.circular-ring,.star-item' ].join(','); //=====替换开始:通用加载全部懒加载图片===== await new Promise(resolve => { let lastHeight = document.body.scrollHeight; let tries = 0; const interval = setInterval(() => { window.scrollTo(0, document.body.scrollHeight); const newHeight = document.body.scrollHeight; if (newHeight === lastHeight || tries > 30) { clearInterval(interval); window.scrollTo(0, 0); setTimeout(resolve, 800); return; } lastHeight = newHeight; tries++; }, 200); }); //批量补齐各类懒加载图片属性 const lazyAttrs = ['data-src','data-original','data-lazy','data-url','data-img','data-actualsrc']; document.querySelectorAll('img').forEach(img => { for(let attr of lazyAttrs){ let val = img.getAttribute(attr); if(val && !img.src.includes(val)){ img.src = val; break; } } //兼容srcset let srcset = img.getAttribute('srcset') || img.getAttribute('data-srcset'); if(srcset && (!img.src || img.src.startsWith('data:image'))){ img.src = srcset.split(' ')[0]; } }); await new Promise(r=>setTimeout(r,600)); //=====替换结束===== const cloneDoc = document.cloneNode(true); cloneDoc.querySelectorAll("script, style, noscript, iframe").forEach(el => el.remove()); cloneDoc.querySelectorAll(floatSelector).forEach(el => el.remove()); if (isWechat) { cloneDoc.querySelectorAll( '.comment,.evaluate,.like,.recommend,.footer,.wx-footer,.msg-footer,.reply-box,.read-more,' + '.article-appreciate,#appreciate,.rich_media_tool_area,#comment_area,#content_bottom_area,.qr_code_pc_outer,.wx-float-window,' + 'p:empty,div:empty,span:empty,br,hr' ).forEach(el => el.remove()); } let imgs = cloneDoc.querySelectorAll('img'); let pending = imgs.length; await new Promise(resolve => { if (pending === 0) resolve(); imgs.forEach(img => { let src = img.src; if (!src || src.startsWith('data:')) { pending--; if (pending === 0) resolve(); return; } const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); const tempImg = new Image(); tempImg.crossOrigin = 'anonymous'; tempImg.onload = () => { canvas.width = tempImg.width; canvas.height = tempImg.height; ctx.drawImage(tempImg, 0, 0); img.src = canvas.toDataURL('image/png'); pending--; if (pending === 0) resolve(); }; tempImg.onerror = () => { pending--; if (pending === 0) resolve(); }; tempImg.src = src; }); }); const htmlStr = `\n${cloneDoc.documentElement.outerHTML}`; const blob = new Blob([htmlStr], { type: "text/html;charset=utf-8" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `${title}.html`; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); showToast("纯净单文件HTML保存成功", "success"); } catch (e) { showToast("网页保存失败", "error"); } }; btnWrap.appendChild(btnNormal); btnWrap.appendChild(btnBase64); // ==========改动结束========== // 原导出PDF→改名:下载PDF const savePdfBtn = document.createElement("button"); savePdfBtn.style.cssText = btnStyle + "background:#ef4444;"; savePdfBtn.textContent = "下载PDF"; // 新增打印PDF按钮 const printPdfBtn = document.createElement("button"); printPdfBtn.style.cssText = btnStyle + "background:#2563eb;"; printPdfBtn.textContent = "打印PDF"; btnWrap.appendChild(savePdfBtn); btnWrap.appendChild(printPdfBtn); // 原来代码里 xxx.appendChild(savePdfBtn); // 改成 xxx.appendChild(btnWrap); const closeBtn = document.createElement("button"); closeBtn.style.cssText = `width:100%;padding:9px;border:none;border-top:1px solid #eee;background:#f8f8f8;color:#666;font-size:13px;`; closeBtn.textContent = "取消"; btnWrap1.appendChild(downloadTxtBtn); btnWrap1.appendChild(copyTextBtn); btnWrap2.appendChild(btnWrap); box.appendChild(titleBar); box.appendChild(btnWrap1); box.appendChild(btnWrap2); box.appendChild(closeBtn); mask.appendChild(box); document.body.appendChild(mask); const title = document.title?.trim() || '无标题文章'; let content = document.body.innerText.trim(); let saveText = `标题:${title}\n链接:${location.href}\n\n${content}`; downloadTxtBtn.onclick = () => { mask.remove(); const floatSelector = [ '[style*="position:fixed"]','[style*="position:absolute"]', '.floating','.float','.fixed','.sticky','.popup','.modal', '.float-btn','.float-ball','.wx-float','.tool-float','.side-float', '.guide-float','.ads-float','.service-float','.kefu-float', '[class*="ad-"]','[id*="ad-"]','[class*="popup-"]','[id*="popup-"]', '.JQMA-btn-all','.JQMA-btn-del','.JQMA-inner-all','.JQMA-mark-innerBtn','.JQMA-btn-hrefAll','.JQMA-btn-hrefSpan', '.tm-floating-btn','.tm-mobile-menu','.tm-find-overlay','.tm-rule-manager','.tm-replace-dialog-mask', '[class*="float"],[class*="sniff"],[class*="parse"],[class*="download"],#sniffer-main-box,.circular-ring,.star-item' ].join(','); const backStyle = []; document.querySelectorAll(floatSelector).forEach(el=>{ backStyle.push({dom:el,old:el.style.cssText}); el.style.cssText += ';display:none !important;'; }); // 重点:临时重新提取不含悬浮的正文 const tempText = `标题:${document.title?.trim()||"无标题"}\n链接:${location.href}\n\n${document.body.innerText.trim()}`; const blob = new Blob([tempText], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${title}.txt`; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); backStyle.forEach(item=>item.dom.style.cssText = item.old); showToast("纯文本保存成功!", "success"); }; copyTextBtn.onclick = async () => { mask.remove(); const floatSelector = [ '[style*="position:fixed"]','[style*="position:absolute"]', '.floating','.float','.fixed','.sticky','.popup','.modal', '.float-btn','.float-ball','.wx-float','.tool-float','.side-float', '.guide-float','.ads-float','.service-float','.kefu-float', '[class*="ad-"]','[id*="ad-"]','[class*="popup-"]','[id*="popup-"]', '.JQMA-btn-all','.JQMA-btn-del','.JQMA-inner-all','.JQMA-mark-innerBtn','.JQMA-btn-hrefAll','.JQMA-btn-hrefSpan', '.tm-floating-btn','.tm-mobile-menu','.tm-find-overlay','.tm-rule-manager','.tm-replace-dialog-mask', '[class*="float"],[class*="sniff"],[class*="parse"],[class*="download"],#sniffer-main-box,.circular-ring,.star-item' ].join(','); const backStyle = []; document.querySelectorAll(floatSelector).forEach(el => { backStyle.push({dom:el,oldCss:el.style.cssText}); el.style.cssText += ';display:none !important;'; }); // 临时重新获取干净文本 let cleanText = `标题:${document.title?.trim()||"无标题"}\n链接:${location.href}\n\n${document.body.innerText.trim()}`; try { await navigator.clipboard.writeText(cleanText); showToast("内容已复制", "success"); // 编辑面板 const editMask = document.createElement("div"); editMask.style.cssText = `position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:2147483652;display:flex;align-items:center;justify-content:center;padding:15px;`; const editBox = document.createElement("div"); editBox.style.cssText = `width:90%;max-width:350px;background:#fff;border-radius:12px;padding:15px;`; const editTitle = document.createElement("div"); editTitle.style.cssText = `font-size:15px;font-weight:500;margin-bottom:10px;text-align:center;`; editTitle.textContent = "编辑文本内容"; const textArea = document.createElement("textarea"); textArea.style.cssText = `width:100%;height:200px;padding:8px;border:1px solid #e5e7eb;border-radius:8px;box-sizing:border-box;font-size:13px;overflow-y:auto;overflow-x:hidden;resize:none;`; textArea.value = cleanText; const navBtnWrap = document.createElement("div"); navBtnWrap.style.cssText = "display:flex;gap:6px;margin:10px 0;"; const makeNavBtn = (txt)=>{let b=document.createElement("button");b.style.cssText="flex:1;padding:6px;border-radius:5px;border:none;background:#eef1f5;color:#222;font-size:12px;";b.textContent=txt;return b;}; const btnTop=makeNavBtn("顶部"),btnUp=makeNavBtn("上翻"),btnDown=makeNavBtn("下翻"),btnBottom=makeNavBtn("底部"); navBtnWrap.append(btnTop,btnUp,btnDown,btnBottom); btnTop.onclick=()=>textArea.scrollTop=0; btnBottom.onclick=()=>textArea.scrollTop=textArea.scrollHeight; btnUp.onclick=()=>textArea.scrollTop-=textArea.clientHeight*0.8; btnDown.onclick=()=>textArea.scrollTop+=textArea.clientHeight*0.8; const btnRow = document.createElement("div"); btnRow.style.cssText = `display:flex;gap:8px;margin-top:5px;`; const saveLocalBtn=document.createElement("button"); saveLocalBtn.style.cssText=`flex:1;padding:8px;border:none;border-radius:6px;background:#10b981;color:#fff;`; saveLocalBtn.textContent="重新保存文件"; const exitBtn=document.createElement("button"); exitBtn.style.cssText=`flex:1;padding:8px;border:1px solid #ddd;border-radius:6px;background:#fff;`; exitBtn.textContent="完成关闭"; btnRow.appendChild(saveLocalBtn);btnRow.appendChild(exitBtn); editBox.append(editTitle,textArea,navBtnWrap,btnRow); editMask.appendChild(editBox); document.body.appendChild(editMask); textArea.focus(); const restoreFloat=()=>{backStyle.forEach(v=>v.dom.style.cssText=v.oldCss);editMask.remove();}; exitBtn.onclick=restoreFloat; editMask.onclick=e=>e.target===editMask&&restoreFloat(); saveLocalBtn.onclick=function(){ const newTxt=textArea.value; let dropBox=this.parentElement.querySelector('.inner-save-dropdown'); if(dropBox){dropBox.remove();return;} dropBox=document.createElement('div');dropBox.className='inner-save-dropdown'; dropBox.style.cssText=`margin-top:6px;border-radius:8px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.12);background:#fff;`; const itemBase=`display:block;width:100%;padding:9px 12px;border:none;background:transparent;font-size:13px;text-align:center;cursor:pointer;color:#333;`; const btnJson=document.createElement('button'),btnTxt=document.createElement('button'),btnMd=document.createElement('button'); btnJson.style.cssText=itemBase;btnJson.textContent="JSON规则"; btnTxt.style.cssText=itemBase+"border-top:1px solid #f0f0f0;";btnTxt.textContent="TXT纯文本"; btnMd.style.cssText=itemBase+"border-top:1px solid #f0f0f0;";btnMd.textContent="Markdown"; [btnJson,btnTxt,btnMd].forEach(b=>{b.onmouseover=()=>b.style.background="#f5f7fa";b.onmouseout=()=>b.style.background="transparent";}); dropBox.append(btnJson,btnTxt,btnMd);this.after(dropBox); const down=(name,type)=>{dropBox.remove();const blob=new Blob([newTxt],{type});const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=name;a.click();a.remove();showToast("保存成功","success");}; btnJson.onclick=()=>down("规则.json","application/json"); btnTxt.onclick=()=>down("文本.txt","text/plain"); btnMd.onclick=()=>down("文档.md","text/markdown"); }; } catch (err) { backStyle.forEach(v=>v.dom.style.cssText=v.oldCss); const ta=document.createElement('textarea');ta.value=cleanText;ta.style.left="-9999px";document.body.appendChild(ta);ta.select();document.execCommand('copy');ta.remove(); showToast("内容复制完成", "success"); } }; // 4.【下载PDF按钮:原有老逻辑】 savePdfBtn.onclick = async () => { if (isPrinting) return; isPrinting = true; mask?.remove(); const isWechat = !!document.querySelector('#js_content, .rich_media_content'); // 超级选择器:类名 + 内联style双杀,追加手机助手悬浮控件 const floatSelector = [ '[style*="position:fixed"]','[style*="position:absolute"]', '.floating','.float','.fixed','.sticky','.popup','.modal', '.float-btn','.float-ball','.wx-float','.tool-float','.side-float', '.guide-float','.ads-float','.service-float','.kefu-float', '[class*="ad-"]','[id*="ad-"]','[class*="popup-"]','[id*="popup-"]', // 追加:手机助手全部悬浮按钮/面板 '.JQMA-btn-all','.JQMA-btn-del','.JQMA-inner-all','.JQMA-mark-innerBtn','.JQMA-btn-hrefAll','.JQMA-btn-hrefSpan' ].join(','); // 1. 前置:强制隐藏自身+第三方+手机助手悬浮,删DOM+清样式 const killAllFloat = () => { // 删自身原有按钮 document.querySelectorAll('.tm-floating-btn, #float-temp-btn').forEach(el => el.remove()); // 删 手机助手 所有悬浮控件 document.querySelectorAll('.JQMA-btn-all, .JQMA-btn-del, .JQMA-inner-all, .JQMA-mark-innerBtn, .JQMA-btn-hrefAll, .JQMA-btn-hrefSpan').forEach(el => el.remove()); // 删第三方(含内联fixed) document.querySelectorAll(floatSelector).forEach(el => { el.remove(); el.style.cssText = 'display:none !important; visibility:hidden !important; opacity:0 !important;'; }); // 绝杀:抓所有带fixed/absolute内联样式的元素 document.querySelectorAll('*').forEach(el => { if (el.style.position === 'fixed' || el.style.position === 'absolute') { el.remove(); } }); }; // 2. 防再生:打印前持续删3秒 const startKillInterval = () => { killAllFloat(); return setInterval(killAllFloat, 300); // 每300ms删一次 }; const killTimer = startKillInterval(); if (isWechat) { const filterUselessContent = () => { // 清理公众号冗余 document.querySelectorAll( '.comment, .evaluate, .like, .recommend, .footer, .wx-footer, .msg-footer, .reply-box, .read-more,' + '.article-appreciate, #appreciate, .rich_media_tool_area, #comment_area, #content_bottom_area,' + '.qr_code_pc_outer, .wx-float-window' ).forEach(el => el.remove()); document.querySelectorAll('p:empty, div:empty, span:empty, br, hr').forEach(el => el.remove()); document.body.style.cssText = 'margin:0 !important; padding:0 !important; padding-bottom:0 !important;'; }; const clearFixedHeight = () => { document.documentElement.style.cssText = 'height:auto !important; overflow:visible !important; margin:0 !important; padding:0 !important;'; document.body.style.cssText = 'height:auto !important; overflow:visible !important; margin:0 !important; padding:0 !important;'; document.querySelectorAll('.rich_media_content, #js_content').forEach(el => { el.style.cssText = 'height:auto !important; max-height:none !important; overflow:visible !important;'; el.style.pageBreakInside = 'avoid'; el.style.pageBreakAfter = 'avoid'; }); }; const scrollToBottom = () => { return new Promise(resolve => { let lastHeight = document.body.scrollHeight; let tries = 0; const interval = setInterval(() => { window.scrollTo(0, document.body.scrollHeight); document.querySelectorAll('img[data-src]').forEach(img => img.src = img.dataset.src); const newHeight = document.body.scrollHeight; if (newHeight === lastHeight || tries > 25) { clearInterval(interval); window.scrollTo(0, 0); setTimeout(resolve, 800); return; } lastHeight = newHeight; tries++; }, 300); }); }; filterUselessContent(); clearFixedHeight(); await scrollToBottom(); const printStyle = document.createElement('style'); printStyle.textContent = `@page { size: A4; margin: 5mm !important; orphans:1; widows:1; } @media print { html,body {height:auto !important; overflow:visible !important; margin:0 !important; padding:0 !important; zoom:0.95 !important;} .rich_media_content, #js_content {page-break-inside:avoid !important; margin:0 !important; padding:0 !important;} /* 终极样式绝杀:所有浮窗+手机助手控件全干掉 */ ${floatSelector}, .tm-floating-btn, #float-temp-btn, [style*="position:fixed"], [style*="position:absolute"] { display:none !important; visibility:hidden !important; opacity:0 !important; width:0 !important; height:0 !important; left:-9999px !important; top:-9999px !important; position:absolute !important; z-index:-99999 !important; transform:scale(0) !important; pointer-events:none !important; } p:empty,div:empty,span:empty,br,hr {display:none !important; height:0 !important;} }`; document.head.appendChild(printStyle); setTimeout(() => { clearInterval(killTimer); // 停止定时删除 killAllFloat(); // 最后再清一次 window.print(); setTimeout(() => { printStyle.remove(); location.reload(); }, 1500); }, 5000); } else { // ====================== 普通网页 整套逻辑 ====================== // 提前主动隐藏所有悬浮控件,避免缩放挤压变形 const hideAllFloatPanel = () => { // 原有助手悬浮控件 document.querySelectorAll( '.tm-floating-btn,.tm-mobile-menu,.JQMA-btn-all,.JQMA-btn-del,.JQMA-inner-all,.JQMA-mark-innerBtn' ).forEach(el => { el.style.display = 'none'; el.style.visibility = 'hidden'; }); // 布局美化、图片操作类悬浮框 + 所有固定绝对定位元素 document.querySelectorAll( '[style*="position:fixed"],[style*="position:absolute"],[style*="position:absolute"],[style*="position: fixed"],.floating,.fixed,.sticky,.popup,.modal' ).forEach(el => { el.style.display = 'none'; el.style.visibility = 'hidden'; el.style.opacity = '0'; }); }; // 1. 滚到底触发懒加载 const scrollToBottom = () => { return new Promise(resolve => { let lastHeight = document.body.scrollHeight; let tries = 0; const interval = setInterval(() => { window.scrollTo(0, document.body.scrollHeight); // 加载懒加载图片 document.querySelectorAll('img[data-src],img[data-original]').forEach(img => { const src = img.dataset.src || img.dataset.original; if (src) img.src = src; }); const newHeight = document.body.scrollHeight; if (newHeight === lastHeight || tries > 15) { clearInterval(interval); window.scrollTo(0, 0); resolve(); } lastHeight = newHeight; tries++; }, 300); }); }; // 2. 清固定高度、防止卡死 const clearFixedHeight = () => { document.documentElement.style.cssText = 'height:auto !important; overflow:visible !important;'; document.body.style.cssText = 'height:auto !important; overflow:visible !important;'; document.querySelectorAll('*[style]').forEach(el => { const s = el.style; if (s.height === '100vh' || s.height === '100%' || s.overflow === 'hidden') { el.style.cssText = ''; } }); document.querySelectorAll('div, section, main, .container, .wrapper, .content').forEach(el => { el.style.height = 'auto'; el.style.maxHeight = 'none'; el.style.minHeight = '0'; el.style.overflow = 'visible'; }); }; // 先执行隐藏、清高度、加载完整页面 hideAllFloatPanel(); clearFixedHeight(); await scrollToBottom(); // 3. 打印样式 纯CSS强化防空白页 const printHideStyle = document.createElement('style'); printHideStyle.id = 'tm-print-hide-float'; printHideStyle.textContent = ` @page { size: A4; margin: 10mm; zoom: 0.99; /* 强制单页延续,禁止自动分页 */ page-break: avoid !important; page-break-before: avoid !important; page-break-after: avoid !important; orphans: 1; widows: 1; } @media print { html, body { height: auto !important; max-height: none !important; overflow: visible !important; width: 210mm !important; margin: 0 auto !important; padding: 0 !important; font-family: "SimSun", "Microsoft YaHei", "Source Han Sans", sans-serif !important; background: #fff !important; /* 全局禁止分页 */ page-break-inside: avoid !important; page-break-before: avoid !important; page-break-after: avoid !important; } body { transform: scale(0.99) !important; transform-origin: top center !important; width: 100% !important; } /* 所有内容容器:彻底锁死分页 */ div, section, main, .container, .wrapper, .content { page-break-inside: avoid !important; page-break-before: avoid !important; page-break-after: avoid !important; margin: 0 !important; padding: 0 !important; } p, span, li, h1, h2, h3, h4, h5, h6 { color: #000 !important; margin: 0.2rem 0 !important; padding: 0 !important; page-break-inside: avoid !important; page-break-before: avoid !important; page-break-after: avoid !important; } /* 图片核心规则:图片之间绝对不分页 */ img { max-width: 95% !important; height: auto !important; display: block !important; margin: 0.2rem auto !important; padding: 0 !important; page-break-inside: avoid !important; page-break-before: avoid !important; page-break-after: avoid !important; } /* 清除所有隐形空白元素 */ p:empty, div:empty, span:empty, br, hr, .line, .divider { display: none !important; height: 0 !important; margin: 0 !important; padding: 0 !important; line-height: 0 !important; } /* 隐藏悬浮控件 */ .tm-floating-btn, .tm-mobile-menu, .JQMA-btn-all, .JQMA-btn-del, .JQMA-inner-all, .JQMA-mark-innerBtn, [style*="position:fixed"], [style*="position:absolute"], .floating,.fixed,.sticky,.popup,.modal { display: none !important; visibility: hidden !important; } } `; document.head.appendChild(printHideStyle); // 加长等待时间到5秒 setTimeout(() => { window.print(); setTimeout(() => { document.getElementById('tm-print-hide-float')?.remove(); isPrinting = false; }, 1500); }, 5000); } }; // ==========新按钮:打印PDF 完整逻辑(表格竖排修复版)========== printPdfBtn.onclick = async () => { if (isPrinting) return; isPrinting = true; mask?.remove(); // 识别是否为微信公众号文章页面 const isWechat = !!document.querySelector('#js_content, .rich_media_content'); if (isWechat) { // 隐藏悬浮控件、固定栏 const hideAllFloatPanel = () => { document.querySelectorAll( '.tm-floating-btn,.tm-mobile-menu,.JQMA-btn-all,.JQMA-btn-del,.JQMA-inner-all,.JQMA-mark-innerBtn' ).forEach(el => { el.style.display = 'none'; el.style.visibility = 'hidden'; }); document.querySelectorAll( '[style*="position:fixed"],[style*="position:absolute"],.floating,.fixed,.sticky,.popup,.modal' ).forEach(el => { el.style.display = 'none'; el.style.visibility = 'hidden'; el.style.opacity = '0'; }); }; // 过滤顶部多余线条、底部评价/留言、公众号菜单等无关区块 const filterUselessContent = () => { document.querySelectorAll('hr, .line, .divider, .top-bar, .header').forEach(el => { el.style.display = 'none'; }); document.querySelectorAll('.comment, .evaluate, .like, .recommend, .footer, .wx-footer, .msg-footer, .reply-box, .read-more').forEach(el => { el.style.display = 'none'; el.style.height = '0'; el.style.overflow = 'hidden'; }); }; // 滚动加载全部核心图文,强制渲染首屏 const scrollToBottom = () => { return new Promise(resolve => { let lastHeight = document.body.scrollHeight; let tries = 0; const interval = setInterval(() => { window.scrollTo(0, document.body.scrollHeight); const contentBox = document.querySelector('#js_content,.rich_media_content'); if(contentBox){ contentBox.scrollTop = contentBox.scrollHeight; } document.querySelectorAll('[class*="swiper"],[class*="slide"]').forEach(el => { el.style.overflow = 'visible'; el.style.width = 'auto'; el.style.display = 'block'; }); document.querySelectorAll('img[data-src]').forEach(img => { if(img.dataset.src) img.src = img.dataset.src; }); const newHeight = document.body.scrollHeight; if (newHeight === lastHeight || tries > 25) { clearInterval(interval); window.scrollTo(0, 0); setTimeout(resolve, 800); return; } lastHeight = newHeight; tries++; }, 300); }); }; // 清除样式限制,消除上下留白 const clearFixedHeight = () => { document.documentElement.style.cssText = 'height:auto !important; overflow:visible !important;'; document.body.style.cssText = 'height:auto !important; overflow:visible !important; margin:0 !important; padding:0 !important;'; // 强制清空底部留白(防空白页) document.body.style.marginBottom = '0'; document.body.style.paddingBottom = '0'; document.querySelectorAll('*[style]').forEach(el => { const s = el.style; if (s.height === '100vh' || s.overflow === 'hidden' || s.maxHeight) { el.style.height = ''; el.style.maxHeight = ''; el.style.overflow = ''; } }); document.querySelectorAll('div, section, main, .rich_media_content, #js_content').forEach(el => { el.style.height = 'auto'; el.style.maxHeight = 'none'; el.style.overflow = 'visible'; el.style.width = '100%'; el.style.maxWidth = 'none'; el.style.position = 'static'; el.style.display = 'block'; el.style.margin = '0'; el.style.padding = '0'; // 清空底部边距 el.style.marginBottom = '0'; el.style.paddingBottom = '0'; }); }; // 执行初始化操作 hideAllFloatPanel(); filterUselessContent(); clearFixedHeight(); await scrollToBottom(); // 👇👇👇 专门处理微信扫码/悬浮弹窗(解决打印时才冒出来的问题)👇👇👇 setTimeout(() => { // 隐藏所有微信弹窗、悬浮控件 document.querySelectorAll('.wx-float, .wx-popup, .wx-dialog, .weui-dialog, .modal, .float, .dialog, [class*="wechat"]').forEach(el => { el.style.display = 'none'; el.style.visibility = 'hidden'; el.style.opacity = '0'; el.remove(); }); // 针对“微信扫一扫可打开此内容”的弹窗 document.querySelectorAll('[style*="position:fixed"], [style*="position:absolute"]').forEach(el => { if(el.innerText.includes('微信扫一扫') || el.innerText.includes('使用完整服务')) { el.style.display = 'none'; el.remove(); } }); }, 300); // 删除空元素、换行标签,清除隐形占位,杜绝空白页 document.querySelectorAll('p:empty, div:empty, span:empty, br').forEach(el => { el.remove(); }); // 微信文章:强制加载所有懒加载图 document.querySelectorAll('img[data-src]').forEach(img => { if (img.dataset.src && (!img.src || img.src.startsWith('data:'))) { img.src = img.dataset.src; } }); // 图片基础样式 + 水平居中预处理(文字不居中) const contentBox = document.querySelector('#js_content,.rich_media_content'); if(contentBox){ contentBox.style.margin = '0 auto'; } document.querySelectorAll('img').forEach(img => { img.style.maxHeight = 'none'; img.style.maxWidth = '95%'; img.style.margin = '0.3rem auto'; img.style.display = 'block'; }); // 打印样式:修复表格竖排问题,恢复正常行列结构 const printHideStyle = document.createElement('style'); printHideStyle.id = 'tm-print-hide-float'; printHideStyle.textContent = ` @page { size: A4 portrait; margin: 8mm !important; orphans: 1; widows: 1; margin-bottom: 2mm !important; } @media print { html, body { zoom: 0.99 !important; } html, body { height: auto !important; max-height: none !important; overflow: visible !important; width: 100% !important; margin: 0 !important; padding: 0 !important; background: #fff !important; } /* 注意:这里排除了table相关元素,不强制display:block,避免表格结构被打散 */ .rich_media_content, .rich_media_content :not(table):not(thead):not(tbody):not(tr):not(th):not(td), #js_content, #js_content :not(table):not(thead):not(tbody):not(tr):not(th):not(td) { display: block !important; opacity: 1 !important; overflow: visible !important; height: auto !important; max-height: none !important; max-width: none !important; color: #000 !important; background: transparent !important; float: none !important; position: static !important; clip: auto !important; clip-path: none !important; -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; } .rich_media_content img, #js_content img { display: block !important; max-width: 95% !important; width: auto !important; height: auto !important; max-height: 96vh !important; object-fit: contain !important; margin: 0.3rem auto !important; } img { max-width: 95% !important; height: auto !important; display: block !important; margin: 0 auto !important; } p, span, li, h1, h2, h3, h4, h5, h6, em, strong { text-align: left !important; margin: 0.5rem 0 !important; line-height: 1.7 !important; color: #000 !important; visibility: visible !important; } ul, ol, li { display: block !important; margin-left: 1rem !important; visibility: visible !important; } body::before, body::after { content: none !important; display: none !important; } div, section, article, main, .rich_media_content { margin-bottom: 0 !important; padding-bottom: 0 !important; } /* ========== 表格修复:恢复正常行列结构 ========== */ table { display: table !important; width: 100% !important; max-width: 100% !important; border-collapse: collapse !important; table-layout: auto !important; margin: 0.8rem 0 !important; page-break-inside: avoid !important; } thead, tbody { display: table-header-group, table-row-group !important; } tr { display: table-row !important; page-break-inside: avoid !important; } th, td { display: table-cell !important; border: 1px solid #333 !important; padding: 0.4rem 0.5rem !important; text-align: center !important; vertical-align: middle !important; word-wrap: break-word !important; word-break: break-all !important; white-space: normal !important; } th { background-color: #2a5f8f !important; color: #fff !important; font-weight: bold !important; } td { text-align: left !important; } /* 表格内文本适配 */ table p, table span { margin: 0.1rem 0 !important; line-height: 1.5 !important; } /* 原有隐藏项 */ .tm-floating-btn, .JQMA-btn-all, .JQMA-btn-del, [style*="position:fixed"], .comment,.footer,.reply-box,.wx-footer,.msg-footer,.read-more { display: none !important; height: 0 !important; overflow: hidden !important; } p:empty, div:empty, span:empty, br, hr { display: none !important; height: 0 !important; overflow: hidden !important; margin: 0 !important; padding: 0 !important; } hr, .line, .divider, .comment, .evaluate, .footer, .wx-footer, .reply-box, .top-bar { display: none !important; } .tm-floating-btn, .tm-mobile-menu, .JQMA-btn-all, .JQMA-btn-del, .JQMA-inner-all, .JQMA-mark-innerBtn, [style*="position:fixed"], [style*="position:absolute"], .floating,.fixed,.sticky,.popup,.modal { display: none !important; visibility: hidden !important; } /* ========== 微信公众号专属:全覆盖隐藏多余模块 ========== */ /* 1. 打赏、点赞、在看、投票 */ .reward, .donate, .reward_box, .dianzan, .like, .like-area, .watch, .look_num, .vote, .vote-box, .praise, .post-like { display: none !important; height: 0 !important; overflow: hidden !important; margin: 0 !important; padding: 0 !important; } /* 2. 留言、评论、消息区 */ #comment, .comment-area, .comment-list, .comment-input, .msg-container, .message-box, .leave-message, .discuss { display: none !important; height: 0 !important; overflow: hidden !important; margin: 0 !important; padding: 0 !important; } /* 3. 作者信息、文章头部/尾部元数据 */ .author, .author-name, .author-desc, .article-info, .meta, .art_meta, .publish-time, .source, .copyright { display: none !important; height: 0 !important; overflow: hidden !important; margin: 0 !important; padding: 0 !important; } /* 4. 相关推荐、广告、公众号二维码 */ .related, .related-article, .recommend-list, .ad, .advert, .qrcode, .qr-code, .wechat-qr, .official-qrcode { display: none !important; height: 0 !important; overflow: hidden !important; margin: 0 !important; padding: 0 !important; } /* 5. 底部工具栏、分享、互动、导航栏 */ .tool-bar, .bottom-bar, .share, .share-box, .interact, .nav-bar, .footer-tool, .float-tool, .wx-tool { display: none !important; height: 0 !important; overflow: hidden !important; margin: 0 !important; padding: 0 !important; } /* 兜底:微信文章底部整块附加区域全部隐藏 */ #js_bottom, .rich_media_bottom, .article-bottom, .extra-box { display: none !important; height: 0 !important; overflow: hidden !important; margin: 0 !important; padding: 0 !important; } } `; document.head.appendChild(printHideStyle); setTimeout(() => { window.print(); setTimeout(() => { document.getElementById('tm-print-hide-float')?.remove(); isPrinting = false; }, 1500); }, 1800); }else { // ====================== 普通网页 整套逻辑 ====================== // 提前主动隐藏所有悬浮控件,避免缩放挤压变形 const hideAllFloatPanel = () => { // 原有助手悬浮控件 document.querySelectorAll( '.tm-floating-btn,.tm-mobile-menu,.JQMA-btn-all,.JQMA-btn-del,.JQMA-inner-all,.JQMA-mark-innerBtn' ).forEach(el => { el.style.display = 'none'; el.style.visibility = 'hidden'; }); // 布局美化、图片操作类悬浮框 + 所有固定绝对定位元素 document.querySelectorAll( '[style*="position:fixed"],[style*="position:absolute"],[style*="position:absolute"],[style*="position: fixed"],.floating,.fixed,.sticky,.popup,.modal' ).forEach(el => { el.style.display = 'none'; el.style.visibility = 'hidden'; el.style.opacity = '0'; }); }; // 1. 滚到底触发懒加载 const scrollToBottom = () => { return new Promise(resolve => { let lastHeight = document.body.scrollHeight; let tries = 0; const interval = setInterval(() => { window.scrollTo(0, document.body.scrollHeight); // 加载懒加载图片 document.querySelectorAll('img[data-src],img[data-original]').forEach(img => { const src = img.dataset.src || img.dataset.original; if (src) img.src = src; }); const newHeight = document.body.scrollHeight; if (newHeight === lastHeight || tries > 15) { clearInterval(interval); window.scrollTo(0, 0); resolve(); } lastHeight = newHeight; tries++; }, 300); }); }; // 2. 清固定高度、防止卡死 const clearFixedHeight = () => { document.documentElement.style.cssText = 'height:auto !important; overflow:visible !important;'; document.body.style.cssText = 'height:auto !important; overflow:visible !important;'; document.querySelectorAll('*[style]').forEach(el => { const s = el.style; if (s.height === '100vh' || s.height === '100%' || s.overflow === 'hidden') { el.style.cssText = ''; } }); document.querySelectorAll('div, section, main, .container, .wrapper, .content').forEach(el => { el.style.height = 'auto'; el.style.maxHeight = 'none'; el.style.minHeight = '0'; el.style.overflow = 'visible'; }); }; // 先执行隐藏、清高度、加载完整页面 hideAllFloatPanel(); clearFixedHeight(); await scrollToBottom(); // 3. 打印样式 纯CSS强化防空白页 const printHideStyle = document.createElement('style'); printHideStyle.id = 'tm-print-hide-float'; printHideStyle.textContent = ` @page { size: A4; margin: 10mm; zoom: 0.99; /* 强制单页延续,禁止自动分页 */ page-break: avoid !important; page-break-before: avoid !important; page-break-after: avoid !important; orphans: 1; widows: 1; } @media print { html, body { height: auto !important; max-height: none !important; overflow: visible !important; width: 210mm !important; margin: 0 auto !important; padding: 0 !important; font-family: "SimSun", "Microsoft YaHei", "Source Han Sans", sans-serif !important; background: #fff !important; /* 全局禁止分页 */ page-break-inside: avoid !important; page-break-before: avoid !important; page-break-after: avoid !important; } body { transform: scale(0.99) !important; transform-origin: top center !important; width: 100% !important; } /* 所有内容容器:彻底锁死分页 */ div, section, main, .container, .wrapper, .content { page-break-inside: avoid !important; page-break-before: avoid !important; page-break-after: avoid !important; margin: 0 !important; padding: 0 !important; } p, span, li, h1, h2, h3, h4, h5, h6 { color: #000 !important; margin: 0.2rem 0 !important; padding: 0 !important; page-break-inside: avoid !important; page-break-before: avoid !important; page-break-after: avoid !important; } /* 图片核心规则:图片之间绝对不分页 */ img { max-width: 95% !important; height: auto !important; display: block !important; margin: 0.2rem auto !important; padding: 0 !important; page-break-inside: avoid !important; page-break-before: avoid !important; page-break-after: avoid !important; } /* 清除所有隐形空白元素 */ p:empty, div:empty, span:empty, br, hr, .line, .divider { display: none !important; height: 0 !important; margin: 0 !important; padding: 0 !important; line-height: 0 !important; } /* 隐藏悬浮控件 */ .tm-floating-btn, .tm-mobile-menu, .JQMA-btn-all, .JQMA-btn-del, .JQMA-inner-all, .JQMA-mark-innerBtn, [style*="position:fixed"], [style*="position:absolute"], .floating,.fixed,.sticky,.popup,.modal { display: none !important; visibility: hidden !important; } } `; document.head.appendChild(printHideStyle); // 加长等待时间到5秒 setTimeout(() => { window.print(); setTimeout(() => { document.getElementById('tm-print-hide-float')?.remove(); isPrinting = false; }, 1500); }, 5000); } }; closeBtn.onclick = () => mask.remove(); mask.onclick = e => { if(e.target === mask) mask.remove(); }; } function updateRulesAndApply(newRules) { const oldRules = JSON.stringify(replaceRules); const newRulesStr = JSON.stringify(newRules); if (oldRules === newRulesStr) { showToast("规则无变化", "info"); return; } replaceRules = newRules; GM_setValue('saveReplaceRules', newRules); // 只存规则,无论开没开替换,都不自动重载、不打断正在替换 showToast("规则已保存,点重载规则生效", "success"); } function addMultipleRules(fromText, toText) { if (!fromText || !toText) { showToast("请填写完整", "error"); return false; } const fromLines = fromText.split('\n').map(line => line.trim()).filter(line => line); const toLines = toText.split('\n').map(line => line.trim()).filter(line => line); if (fromLines.length === 0 || toLines.length === 0 || fromLines.length !== toLines.length) { showToast("行数不一致或内容为空", "error"); return false; } const newRules = [...replaceRules]; let addedCount = 0; for (let i = 0; i < fromLines.length; i++) { const from = fromLines[i]; const to = toLines[i]; if (from && to && from !== to && !newRules.some(rule => rule.from === from && rule.to === to)) { newRules.push({ from, to, enabled: true }); addedCount++; } } if (addedCount === 0) { showToast("没有新增规则", "info"); return false; } updateRulesAndApply(newRules); showToast(`已添加 ${addedCount} 条规则`, "success"); return true; } function showToast(message, type = "info") { const colors = {success: "#10b981",info: "#3b82f6",warning: "#f59e0b",error: "#ef4444"}; const existing = document.querySelector('.tm-toast'); if (existing) existing.remove(); const toast = document.createElement('div'); toast.className = 'tm-toast'; toast.textContent = message; toast.style.cssText = ` position:fixed; top:auto; bottom:12%; left:50%; transform:translate(-50%,0); background:${colors[type]}; color:#fff; padding:12px 20px; border-radius:12px; font-size:15px; font-weight:bold; z-index:2147483647; opacity:0; transition:opacity 0.3s,transform 0.3s; box-shadow:0 4px 12px rgba(0,0,0,0.15); white-space:nowrap; max-width:80vw; overflow:hidden; text-overflow:ellipsis; `; document.body.appendChild(toast); requestAnimationFrame(()=>{toast.style.opacity='1';toast.style.transform='translate(-50%,0)';}); setTimeout(()=>{ toast.style.opacity='0';toast.style.transform='translate(-50%,-20px)'; setTimeout(()=>toast.remove(),300); },2000); } function createFloatingButton() { if (document.querySelector('.tm-floating-btn')) return; const savedPos = localStorage.getItem('tmFloatPos'); let initialPos = { x: window.innerWidth - 46, y: window.innerHeight / 2 }; if (savedPos) { try { const pos = JSON.parse(savedPos); initialPos = {x: Math.min(pos.x, window.innerWidth - 46),y: Math.min(pos.y, window.innerHeight - 50)}; } catch (e) {} } const btn = document.createElement('div'); btn.className = 'tm-floating-btn'; btn.style.cssText = ` position:fixed;left:${initialPos.x}px;top:${initialPos.y}px;width:46px;height:46px;border-radius:23px; background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);color:#fff;font-size:12px;font-weight:bold; display:flex;align-items:center;justify-content:center;z-index:2147483646;cursor:move; user-select:none;touch-action:none;box-shadow:0 4px 15px rgba(0,0,0,0.1); transition:all 0.25s cubic-bezier(0.4,0,0.2,1);border:2px solid rgba(255,255,255,0.2); opacity:0.28;transform:translateX(0) scale(1); `; const icon = document.createElement('div'); icon.textContent = isReplacing ? '🔁' : '⚪'; icon.style.cssText = 'font-size:20px !important;transition:transform 0.3s;'; btn.appendChild(icon); let lastScrollY = window.scrollY; let scrollTimer = null; const handleScroll = () => { clearTimeout(scrollTimer); const currentScrollY = window.scrollY; if (Math.abs(currentScrollY - lastScrollY) > 3) { btn.style.transform = 'translateX(36px) scale(0.5)'; btn.style.opacity = '0.14'; } scrollTimer = setTimeout(() => { btn.style.transform = 'translateX(0) scale(1)'; btn.style.opacity = '0.28'; }, 800); lastScrollY = currentScrollY; }; window.addEventListener('scroll', handleScroll, { passive: true }); document.addEventListener('touchmove', handleScroll, { passive: true }); window.updateFloatingButton = () => { icon.textContent = isReplacing ? '🔁' : '⚪'; btn.style.background = isReplacing ? 'linear-gradient(135deg, rgba(16,185,129,0.35) 0%, rgba(5,150,105,0.35) 100%)' : 'linear-gradient(135deg, rgba(102,126,234,0.35) 0%, rgba(118,75,162,0.35) 100%)'; }; let startX, startY, initialX, initialY; btn.addEventListener('touchstart', (e) => { e.preventDefault();e.stopPropagation(); const touch = e.touches[0]; startX = touch.clientX;startY = touch.clientY; initialX = parseInt(btn.style.left);initialY = parseInt(btn.style.top); isDragging = false; longPressTimer = setTimeout(()=>{isLongPress=true;showMobileMenu();},300); }); btn.addEventListener('touchmove', (e) => { e.preventDefault();e.stopPropagation(); if(longPressTimer) clearTimeout(longPressTimer); const touch = e.touches[0]; const deltaX = touch.clientX - startX; const deltaY = touch.clientY - startY; if(Math.abs(deltaX)>8||Math.abs(deltaY)>8){isDragging=true;isLongPress=false;} if(isDragging){ let newX = initialX + deltaX; let newY = initialY + deltaY; newX = Math.max(10, Math.min(newX, window.innerWidth - 46)); newY = Math.max(10, Math.min(newY, window.innerHeight - 46)); btn.style.left = newX+'px';btn.style.top=newY+'px'; btn.style.transform='scale(1.1)'; } }); btn.addEventListener('touchend', (e) => { e.preventDefault();e.stopPropagation(); if(longPressTimer) clearTimeout(longPressTimer); if(isDragging){ isDragging=false;btn.style.transform='scale(1)'; const rect = btn.getBoundingClientRect(); localStorage.setItem('tmFloatPos',JSON.stringify({x:rect.left,y:rect.top})); }else if(isLongPress){ isLongPress=false; }else{ const now = Date.now(); if(now-lastTapTime<400){clearTimeout(tapTimer);toggleReplace();} else{tapTimer=setTimeout(()=>openReplaceDialog(),220);} lastTapTime=now; } }); const removeScrollListener = () => { window.removeEventListener('scroll', handleScroll); document.removeEventListener('touchmove', handleScroll); }; btn.addEventListener('remove', removeScrollListener); document.body.appendChild(btn); updateFloatingButton(); } function showMobileMenu() { const existing = document.querySelector('.tm-mobile-menu'); if (existing) existing.remove(); calcEnabledRuleTotal(); const menu = document.createElement('div'); menu.className = 'tm-mobile-menu'; menu.style.cssText = `position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.7); z-index:2147483645;display:flex;flex-direction:column;justify-content:flex-end;backdrop-filter:blur(5px);`; const content = document.createElement('div'); content.style.cssText = `background:#fff;border-radius:14px 14px 0 0;padding:8px;max-height:50vh;margin:0 22px;overflow-y:auto;`; function renderMainBtn(){ const status = isReplacing; let btn = content.querySelector('.main-switch-btn'); if(!btn){btn=document.createElement('div');btn.className='main-switch-btn';content.prepend(btn);} btn.style.cssText = `display:flex;align-items:center;padding:9px;margin:3px 0;background:#f9fafb;border-radius:8px;font-size:13px;color:#1f2937;cursor:pointer;`; btn.innerHTML = `${status?'⏸️':'▶️'}${status?'停止替换':'开始替换'}`; btn.onclick = () => {toggleReplace();renderMainBtn();}; } renderMainBtn(); let highlightDuration = GM_getValue('highlightDuration', 5); function renderHighlightBtn() { let btn = content.querySelector('.highlight-switch-btn'); if (!btn) { btn = document.createElement('div'); btn.className = 'highlight-switch-btn'; content.appendChild(btn); } btn.style.cssText = `display:flex;align-items:center;justify-content:space-between;padding:9px;margin:3px 0;background:#f9fafb;border-radius:8px;font-size:13px;color:#1f2937;cursor:pointer;`; btn.innerHTML = `