// ==UserScript== // @name 文字链接可点击 // @version 1.2 // @description URL文本转a标签,代码全部来自DeepSeek,可能存在bug // @author cangming99 // @match *://*/* // @grant none // @run-at document-idle // @license MIT // @namespace https://greasyfork.org/users/826934 // @downloadURL none // ==/UserScript== (function() { 'use strict'; // 配置中心(可根据需求调整) const CONFIG = { // 匹配规则 URL_REGEX: /(?:\b(?:https?|ftp|file):\/\/[^\s<>{}[\]"']+|www\.[^\s<>{}[\]"']+)\b/gi, // 性能防护 MAX_MATCHES: 20, // 单次最大匹配次数 CONTEXT_RANGE: 300, // 上下文截取范围 COOLDOWN: 50, // 冷却时间(ms) // 样式配置 STYLE_CLASS: 'uc-link', LINK_STYLE: { color: '#ff4500', 'text-decoration': 'underline dashed', 'cursor': 'pointer', 'position': 'relative' } }; // 状态控制器 let isProcessing = false; let lastSelection = null; // 样式隔离系统 const style = document.createElement('style'); style.textContent = ` .${CONFIG.STYLE_CLASS} { color: ${CONFIG.LINK_STYLE.color} !important; text-decoration: ${CONFIG.LINK_STYLE['text-decoration']} !important; cursor: ${CONFIG.LINK_STYLE.cursor} !important; position: ${CONFIG.LINK_STYLE.position}; } `; document.head.appendChild(style); // 选区保护机制 function saveSelection() { const sel = window.getSelection(); if (sel.rangeCount > 0) { lastSelection = sel.getRangeAt(0); } } function restoreSelection() { if (lastSelection) { const sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(lastSelection.cloneRange()); lastSelection = null; } } // 动态防护系统 const observer = new MutationObserver(mutations => { mutations.forEach(mutation => { if (!mutation.addedNodes) return; mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE) { node.querySelectorAll('a').forEach(a => { a.dataset.ucProtected = 'true'; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); // 智能转换核心 function smartConvert(event) { if (isProcessing) return; if (event.target.closest('a, [data-uc-protected]')) return; if (window.getSelection().toString().length > 0) return; const range = document.caretRangeFromPoint(event.clientX, event.clientY); if (!range || range.startContainer.nodeType !== Node.TEXT_NODE) return; const textNode = range.startContainer; const fullText = textNode.nodeValue; const clickPos = range.startOffset; // 智能范围限制 const startPos = Math.max(0, clickPos - CONFIG.CONTEXT_RANGE/2); const endPos = Math.min(fullText.length, clickPos + CONFIG.CONTEXT_RANGE/2); const contextText = fullText.slice(startPos, endPos); const localClickPos = clickPos - startPos; // 精准匹配 let matchCount = 0; let targetMatch = null; const regex = new RegExp(CONFIG.URL_REGEX.source, 'gi'); let match; while ((match = regex.exec(contextText)) !== null) { if (++matchCount > CONFIG.MAX_MATCHES) break; const globalStart = startPos + match.index; const globalEnd = globalStart + match[0].length; if (localClickPos >= match.index && localClickPos <= match.index + match[0].length) { targetMatch = { url: match[0], start: globalStart, end: globalEnd }; break; // 找到第一个匹配即退出 } } if (!targetMatch) return; // 执行转换 isProcessing = true; const { url, start, end } = targetMatch; requestAnimationFrame(() => { const a = document.createElement('a'); a.className = CONFIG.STYLE_CLASS; a.href = url.startsWith('www.') ? `http://${url}` : url; a.textContent = url; const before = document.createTextNode(fullText.slice(0, start)); const after = document.createTextNode(fullText.slice(end)); const parent = textNode.parentNode; parent.insertBefore(before, textNode); parent.insertBefore(a, textNode); parent.insertBefore(after, textNode); parent.removeChild(textNode); // 恢复流程 setTimeout(() => { restoreSelection(); isProcessing = false; }, CONFIG.COOLDOWN); event.stopImmediatePropagation(); event.preventDefault(); }); } // 事件监听优化 document.addEventListener('mousedown', saveSelection); document.addEventListener('mouseup', saveSelection); document.addEventListener('click', smartConvert, { capture: true, passive: false }); })();