// ==UserScript== // @name 让链接可点击 // @namespace https://viayoo.com/ // @version 2.0 // @description 不用再复制链接打开这么麻烦了,自动将文本中的URL转换为可点击的链接 // @author 谷花泰 // @homepageURL https://app.viayoo.com/addons/31 // @match *://*/* // @exclude *://example.com/* // @run-at document-start // @grant none // ==/UserScript== (function() { 'use strict'; /* 判断是否该执行 */ const key = encodeURIComponent('谷花泰:让链接可点击:执行判断'); if (window[key]) return; window[key] = true; class ClickLink { constructor() { this.init(); } init() { console.log('嘿嘿嘿'); this.url_regexp = /((https?:\/\/|www\.)[\x21-\x7e]+[\w\/=]|\w([\w._-])+@\w[\w\._-]+\.(com|cn|org|net|info|tv|cc|gov|edu)|(\w[\w._-]+\.(com|cn|org|net|info|tv|cc|gov|edu))(\/[\x21-\x7e]*[\w\/])?|ed2k:\/\/[\x21-\x7e]+\|\/|thunder:\/\/[\x21-\x7e]+=)/gi; this.urlPrefixes = ['http://', 'https://', 'ftp://', 'thunder://', 'ed2k://', 'mailto://', 'file://']; document.addEventListener("mouseover", this.clearLink.bind(this)); this.excludedTags = "a,svg,canvas,applet,input,button,area,pre,embed,frame,frameset,head,iframe,img,option,map,meta,noscript,object,script,style,textarea,code".split(","); this.xPath = "//text()[not(ancestor::" + this.excludedTags.join(') and not(ancestor::') + ")]"; this.startObserve(); setTimeout(this.linkMixInit.bind(this), 100); } clearLink(event) { let link = event.originalTarget || event.target; if (!(link && link.localName === "a" && link.className && link.className.indexOf("textToLink") !== -1)) { return; } let url = link.getAttribute("href"); for (let prefix of this.urlPrefixes) { if (url.indexOf(prefix) === 0) { return; } } if (url.indexOf('@') !== -1) { link.setAttribute("href", "mailto://" + url); } else { link.setAttribute("href", "http://" + url); } } setLink(candidate) { if (!candidate || (candidate.parentNode && candidate.parentNode.className && typeof candidate.parentNode.className.indexOf === "function" && candidate.parentNode.className.indexOf("textToLink") !== -1) || candidate.nodeName === "#cdata-section") { return; } let text = candidate.textContent.replace(this.url_regexp, '$1'); if (candidate.textContent && candidate.textContent.length === text.length) { return; } let span = document.createElement("span"); span.innerHTML = text; candidate.parentNode.replaceChild(span, candidate); } linkPack(result, start) { let startTime = Date.now(); while (start + 10000 < result.snapshotLength) { for (let i = start; i <= start + 10000; i++) { this.setLink(result.snapshotItem(i)); } start += 10000; if (Date.now() - startTime > 2500) return; } for (let i = start; i < result.snapshotLength; i++) { this.setLink(result.snapshotItem(i)); } } linkify(node) { let result = document.evaluate(this.xPath, node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); this.linkPack(result, 0); } linkFilter(node) { for (let tag of this.excludedTags) { if (tag === node.parentNode.localName.toLowerCase()) { return NodeFilter.FILTER_REJECT; } } return NodeFilter.FILTER_ACCEPT; } observePage(root) { const tW = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, { acceptNode: this.linkFilter.bind(this) }, false); while (tW.nextNode()) { this.setLink(tW.currentNode); } } startObserve() { this.observer = new MutationObserver(mutations => { for (let mutation of mutations) { if (mutation.type === "childList") { for (let node of mutation.addedNodes) { this.observePage(node); } } } }); } linkMixInit() { if (window !== window.top || window.document.title === "") return; this.linkify(document.body); this.observer.observe(document.body, { childList: true, subtree: true }); } } try { new ClickLink(); } catch (err) { console.log('via插件:让链接可点击:加载失败', err); } })();