您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add custom tooltip to words based on a list of word and toolip text (configurable from the GM menu). The Shift+Win / Shift+Command / Shift+Super will highlight all words which have custom tooltip. To use, sites must be manually added via the script configuration.
// ==UserScript== // @name Word Tooltip // @namespace https://greasyfork.org/en/users/85671-jcunews // @version 1.0.1 // @license AGPLv3 // @author jcunews // @description Add custom tooltip to words based on a list of word and toolip text (configurable from the GM menu). The Shift+Win / Shift+Command / Shift+Super will highlight all words which have custom tooltip. To use, sites must be manually added via the script configuration. // @reference https://www.reddit.com/r/software/comments/ouxp0l/need_chrome_extension_that_reports_text_based_on/ // @match https://specific-site.com/* // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_setValue // ==/UserScript== /* Warning: this script affects performance, since it has to check each word in the web page text. It is highly recommended to enable it only on specific sites. */ ((def, dat, drx, erx, obs, a) => { function processNode(node, i) { switch (node.nodeType) { case Node.ELEMENT_NODE: if (!["OPTION", "WORD"].includes(node.tagName)) { for (i = node.childNodes.length - 1; i >= 0; i--) processNode(node.childNodes[i]); } break; case Node.TEXT_NODE: processTextNode(node) } } function processTextNode(node, m, e) { drx.lastIndex = 0; if (m = drx.exec(node.data)) { if (m.index) { //middle node = node.splitText(m.index); } else { //start if (m[0].length < node.data.length) { //partial m = node.splitText(m[0].length); } else { //whole (e = document.createElement("WORD")).textContent = node.data; e.title = dat[m[0].toLowerCase()]; node.replaceWith(e) } //else: already processed } } } function undo() { obs.disconnect(); document.body.querySelectorAll("word").forEach((e, p) => { p = e.parentNode; e.replaceWith(e.firstChild); p.normalize(); }); obs.observe(document.body, {childList: true, subtree: true, characterData: true}); } function parseData(dat, a, z) { a = []; try { dat = dat.split("\n").reduce((r, s, i, k, v) => { if ((s = s.trim()) && (s[0] !== "#")) { if ((i = s.indexOf("=")) >= 0) { k = s.substr(0, i).trim().toLowerCase(); v = s.substr(i + 1).replace(/\\n/g, "\n"); if (r[k] === undefined) { r[k] = v.trim(); a.push(k.replace(erx, '\\$1')); } } else throw 1; } return r }, {}); return [dat, new RegExp("\\b(?:" + a.join("|") + ")\\b", "gi")] } catch(z) { alert(`Invalid word list format. Each line should contain the text word/phrase followed by a '=' character, then followed by the tooltip word/phrase. New lines in toolip can be specified as '\\n'. If there are duplicate text words/phrases (case-insensitive), only the last one will be effective. Empty/blank line and lines which start with '#' are ignored. e.g. something=description something else=some explanation #comments more something = line1\\nline2 `); return null } } def = `\ #Each line should contain the text word/phrase followed by #a '=' character, then followed by the tooltip word/phrase. # #New lines in toolip can be specified as '\\n'. # #If there are duplicate text words/phrases (case-insensitive), #only the last one will be effective. # #Empty/blank lines and lines which start with '#' are ignored. #common ftp=File Transfer Protocol html = Hyper Text Markup Language http = Hyper Text Transfer Protocol https = Hyper Text Transfer Protocol (Secure) gif = Graphic Interchange Format iso = International Standard Organization jpeg = Joint Photographic Experts Group mpeg = Moving Picture Experts Group png = Portable Network Graphics text = Text\\n(duh...) url = Uniform Resource Locator #technical ascii = American Standard Code for Information Interchange avc = Advanced Video Coding css = Cascading Style Sheet dom = Document Object Model json = JavaScript Object Notation md5 = Message-Digest 5 algorithm mime = Multipurpose Internet Mail Extensions pgp = Pretty Good Privacy sha = Secure Hash Algorithm uri = Uniform Resource Identifier utf = Unicode Transformation Format`; if ((dat = GM_getValue("wordList")) === undefined) GM_setValue("wordList", dat = def); erx = /([\\\/\'*+?|()\[\]{}.^$])/g; a = parseData(dat); dat = a[0]; drx = a[1]; if (document.head) { (a = document.createElement("STYLE")).innerHTML = '.wtshow word{background:#00d;color:#ff0}'; document.documentElement.append(a) addEventListener("keydown", ev => { if ((ev.key === "OS") && ev.shiftKey) document.body.classList.add("wtshow") }, true); addEventListener("keyup", ev => { if (ev.key === "OS") document.body.classList.remove("wtshow") }, true); GM_registerMenuCommand("Edit Word List", (e) => { (e = document.createElement("DIV")).innerHTML = `<style> #wtUjs{position:fixed;left:0;top:0;right:0;bottom:0;background:#0007;font:unset;font-family:sans-serif;cursor:pointer} #wtUjsPop{transform:translateY(-50%);margin:40vh auto 0 auto;border:#007 solid .2em;padding:.5em;width:50vw;background:#fff;color:#000;cursor:auto} #wtUjsTxt{box-sizing:border-box;width:100%;height:40vh;resize:none} #wtUjsBtns{margin:1em 0 .5em 0;text-align:center} #wtUjsBtns>button{margin:0 1em;width:5.2em} </style> <div id=wtUjsPop> <textarea id=wtUjsTxt></textarea> <div id=wtUjsBtns> <button id=wtUjsExp>Export...</button> <button id=wtUjsImp>Import...</button> <button id=wtUjsDef>Default</button> <button id=wtUjsRev>Revert</button> <button id=wtUjsOk>OK</button> <button id=wtUjsCancel>Cancel</button> </div> </div>`; e.id = "wtUjs"; e.onclick = (ev, e) => { switch (ev.target.id) { case "wtUjsExp": (e = document.createElement("A")).download = "wordList.txt"; e.href = URL.createObjectURL(new Blob([navigator.platform === "Win32" ? wtUjsTxt.value.replace(/\n/g, "\r\n") : wtUjsTxt.value], {type: "text/plain"})); e.click(); setTimeout(u => URL.revokeObjectURL(u), 10000, e.href); break; case "wtUjsImp": (e = document.createElement("INPUT")).type = "file"; e.onchange = r => { r = new FileReader; r.onload = () => { if (parseData(r = r.result.replace(/\r\n/g, "\n"))) { wtUjsTxt.value = r; wtUjsTxt.focus(); } }; r.readAsText(e.files[0]); }; e.click(); break; case "wtUjsDef": wtUjsTxt.value = def; wtUjsTxt.focus(); break; case "wtUjsRev": wtUjsTxt.value = GM_getValue("wordList"); wtUjsTxt.focus(); break; case "wtUjsOk": if (e = parseData(wtUjsTxt.value)) { dat = e[0]; drx = e[1]; GM_setValue("wordList", wtUjsTxt.value); undo(); processNode(document.body); wtUjs.remove(); } break; case "wtUjs": case "wtUjsCancel": wtUjs.remove() } }; e.querySelector("#wtUjsTxt").value = GM_getValue("wordList"); document.documentElement.append(e); wtUjsTxt.focus(); }); (obs = new MutationObserver(recs => recs.forEach(rec => { if (rec.type === "childList") { rec.addedNodes.forEach(processNode) } else processTextNode(rec.target); }))).observe(document.body, {childList: true, subtree: true, characterData: true}); processNode(document.body); } })();