// ==UserScript== // @name 网页文本转链接 // @description 高性能文本转链接方案,支持动态内容 // @version 1.0 // @author WJ // @match *://*/* // @exclude https://*.bing.com/* // @exclude https://*.baidu.com/* // @license MIT // @grant none // @run-at document-idle // @namespace https://greasyfork.org/users/914996 // @downloadURL none // ==/UserScript== (() => { // 1. 注入样式 document.head.insertAdjacentHTML( 'beforeend', '' ); // 2. URL 正则与常量 const tlds = [ 'app','aero','aer','art','asia','beer','biz','cat','cc','chat','ci','cloud', 'club','cn','com','cool','coop','co','dev','edu','email','fit','fun','gov', 'group','hk','host','icu','info','ink','int','io','jobs','kim','love','ltd', 'luxe','me','mil','mobi','moe','museum','name','net','nl','network','one', 'online','org','plus','post','press','pro','red','ren','run','ru','shop', 'site','si','space','store','tech','tel','top','travel','tv','tw','uk','us', 'video','vip','wang','website','wiki','wml','work','ws','xin','xyz','yoga','zone' ].join('|'); const urlRegex = new RegExp( String.raw`\b[\w.:/?=%&#-]{3,}\.(?:${tlds})(?!\w)[\w.:/?=%&#-]*|` + String.raw`(?:(?:https?:\/\/)|(?:www\.|wap\.))[\w.:/?=%&#-@+~=]{3,250}\.[\w]{2,6}\b[\w.:/?=%&#-@+~=]*`, 'gi' ); // 3. 工具函数 - 增强元素跳过检测 const skipTags = new Set(['A','SCRIPT','STYLE','TEXTAREA','BUTTON','SELECT','OPTION','CODE','PRE','INPUT']); const PROCESSED = 'data-url-processed'; const shouldSkip = (el) => !el || skipTags.has(el.tagName) || el.isContentEditable || !!el.closest?.('[contenteditable], input, textarea, select, code, pre'); // 4. 节点处理函数 - 使用文档片段高效替换 const processNode = (root) => { if (!root || root.hasAttribute?.(PROCESSED) || shouldSkip(root)) return; root.setAttribute(PROCESSED, 'true'); const walker = document.createTreeWalker( root, NodeFilter.SHOW_TEXT, { acceptNode: (n) => shouldSkip(n.parentElement) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT } ); const tasks = []; for (let node; (node = walker.nextNode());) { const original = node.textContent ?? ''; const replaced = original.replace(urlRegex, (match) => { const href = /^\w+:\/\//.test(match) ? match : `https://${match}`; return `${match}`; }); replaced !== original && tasks.push({ node, replaced }); } // 使用文档片段批量替换 for (const { node, replaced } of tasks) { const frag = document.createRange().createContextualFragment(replaced); node.replaceWith(frag); } }; // 5. 观察器 - 使用IntersectionObserver实现懒加载 const io = new IntersectionObserver((entries) => { for (const { isIntersecting, target } of entries) { if (isIntersecting) { io.unobserve(target); requestIdleCallback?.(() => processNode(target),{ timeout: 1000 }); } } }); // 6. MutationObserver - 处理动态添加的内容 const mo = new MutationObserver((mutations) => { for (const { addedNodes } of mutations) { for (const node of addedNodes) { if (node.nodeType === 1 && !node.hasAttribute?.(PROCESSED)) { io.observe(node); } } } }); // 7. 初始化 document.querySelectorAll('body > *').forEach((el) => io.observe(el)); mo.observe(document.body, { childList: true, subtree: true }); })();