Automatically highlight user-defined text with Seek function (2019-09-22)
// ==UserScript== // @name Text Highlight and Seek // @author erosman and Jefferson "jscher2000" Scher // @namespace JeffersonScher // @version 2.4.0 // @description Automatically highlight user-defined text with Seek function (2019-09-22) // @include https://greasyfork.org/* // @include https://openuserjs.org/* // @include http*://www.jeffersonscher.com/* // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM.setValue // @grant GM_getValue // @grant GM.getValue // @grant GM_getResourceURL // @grant GM.getResourceUrl // @copyright Copyright 2019 Jefferson Scher. Portions created by erosman. // @license BSD-3-clause // @resource mycon http://www.jeffersonscher.com/gm/src/gfrk-THS-ver240.png // ==/UserScript== var script_about = "https://greasyfork.org/scripts/13007-text-highlight-and-seek"; /* --------- Note --------- TO INCLUDE SITES (only Greasy Fork and OpenUserJS are initially included): Go to Add-ons - User Scripts (Ctrl+Shift+a/Cmd+Shift+a on Firefox Windows/Mac) Click on the Script's Option Under User Settings Tab, Add Included/Excluded Pages that you want the script to run on Click OK Note from erosman: If you find that another script #####es with this script, set Text Highlight and Seek to Execute first. Go to Add-ons - User Scripts ('Ctrl+ Shift + a' on Firefox) Right Click on the Script On the context menu click: Execute first On Add-ons - User Scripts, you can also Click on the Execution Order (top Right) and change the execution order so that Text Highlight and Seek runs before those scripts that #####es with it. */ var hlframe, hlobjDefault, kwhieditstyle, hljson, hlobj, hlkeys, kwold, hlold, hlbtnvis, hlprecode, hlnextset, hbtndisp; var GM4 = (typeof GM_getValue === "undefined") ? true : false; async function THS_init(){ if (!GM4){ hlframe = GM_getValue("hlframe", ""); // get iframe pref } else { hlframe = await GM.getValue("hlframe", ""); } if (hlframe == ""){ hlframe = "none"; if (!GM4){ GM_setValue("hlframe", hlframe); } else { await GM.setValue("hlframe", hlframe); } } if ((window.self !== window.top) && (hlframe != "any")) { // framed page if (hlframe == "none") return; if (hlframe == "same") { console.log(window.self.location.hostname + " vs " + window.top.location.hostname); } } // sample keyword+style object to get started hlobjDefault = { "set100" : { keywords : "scripts|script", type : "string", hlpat : "", textcolor : "rgb(0,0,0)", backcolor : "rgb(255,255,128)", fontweight : "inherit", custom : "", enabled : "true", visible : "true", updated : "" }, "set099" : { keywords : "site", type : "word", hlpat : "", textcolor : "rgb(0,0,0)", backcolor : "rgb(255,192,255)", fontweight : "inherit", custom : "", enabled : "true", visible : "true", updated : "" }, "set098" : { keywords : "^October \\d{1,2}", type : "regex", hlpat : "", textcolor : "rgb(0,0,0)", backcolor : "rgb(192,255,255)", fontweight : "inherit", custom : "", enabled : "true", visible : "true", updated : "" } }; kwhieditstyle = ["rgb(0,0,255)","rgb(255,255,0)","inherit",""]; // read pref storage: keyword-style sets if (!GM4){ hljson = GM_getValue("kwstyles"); } else { hljson = await GM.getValue("kwstyles"); } if (!hljson || hljson.length == 0){ hlobj = hlobjDefault; // check for legacy preferences if (!GM4){ kwold = GM_getValue("keywords"); } else { kwold = await GM.getValue("keywords"); } if (kwold) if(kwold.length > 0) { hlobj.set100.keywords = kwold.split(',').join('|'); } if (!GM4){ hlold = GM_getValue("highlightStyle"); } else { hlold = await GM.getValue("highlightStyle"); } if (hlold) if(hlold.length > 0) { // really should try to parse this, but for now... hlobj.set100.custom = hlold; } // save starting values hljson = JSON.stringify(hlobj); if (!GM4){ GM_setValue("kwstyles", hljson); } else { await GM.setValue("kwstyles", hljson); } } else { hlobj = JSON.parse(hljson); } // global keys array hlkeys = Object.keys(hlobj); // read/set other prefs if (!GM4){ hlbtnvis = GM_getValue("hlbtnvis", ""); } else { hlbtnvis = await GM.getValue("hlbtnvis", ""); } if (hlbtnvis == ""){ hlbtnvis = "on"; if (!GM4){ GM_setValue("hlbtnvis", hlbtnvis); } else { await GM.setValue("hlbtnvis", hlbtnvis); } } if (!GM4){ hlprecode = GM_getValue("hlprecode", ""); } else { hlprecode = await GM.getValue("hlprecode", ""); } if (hlprecode == ""){ hlprecode = true; if (!GM4){ GM_setValue("hlprecode", hlprecode); } else { await GM.setValue("hlprecode", hlprecode); } } if (!GM4){ hlnextset = GM_getValue("hlnextset", ""); } else { hlnextset = await GM.getValue("hlnextset", ""); } if (hlnextset == ""){ hlnextset = 101; if (!GM4){ GM_setValue("hlnextset", hlnextset); } else { await GM.setValue("hlnextset", hlnextset); } } // Inject CSS insertCSS(hlkeys); // first run THmo_doHighlight(document.body,null); // Add MutationObserver to catch content added dynamically var THmo_MutOb = (window.MutationObserver) ? window.MutationObserver : window.WebKitMutationObserver; if (THmo_MutOb){ var THmo_chgMon = new THmo_MutOb(function(mutationSet){ mutationSet.forEach(function(mutation){ for (var i=0; i<mutation.addedNodes.length; i++){ if (mutation.addedNodes[i].nodeType == 1){ THmo_doHighlight(mutation.addedNodes[i],null); } } }); }); // attach chgMon to document.body var opts = {childList: true, subtree: true}; THmo_chgMon.observe(document.body, opts); } // Set up top highlight/seek bar var kwhibar = document.createElement("div"); kwhibar.id = "thdtopbar"; if (hlbtnvis == "on") var btnchk = " checked=\"checked\""; else var btnchk = ""; if (hlprecode) var btnprecode = " checked=\"checked\""; else var btnprecode = ""; kwhibar.innerHTML = "<form id=\"thdtopform\" onsubmit=\"return false\"><p id=\"thdtopbarhome\"><a href=\"" + script_about + "\" target=\"_blank\" title=\"Go to script install page\">JS</a></p>" + "<div id=\"thdtopcurrent\"><p id=\"thdtopkeywords\" title=\"Click to View, Edit, Seek, or Add Keywords\">Click to manage keyword/highlight sets • <em>Add New Set</em></p>" + "<div id=\"thdtopdrop\" style=\"display:none;\"><div id=\"thdtable\"><table cellspacing=\"0\"><tbody id=\"kwhitbod\"></tbody></table></div><p><button id=\"btnkwhiadd\">Add New Set</button>" + "<span style=\"float:right\"><button id=\"btnkwhiexport\">Export Sets</button> <button id=\"btnkwhiimport\">Import Sets</button> <button id=\"thdtopdropclose\">X</button></span></p></div></div>" + "<div id=\"thdtopfindbuttons\"><button title=\"First match\" thdaction=\"f\"><b>l</b>◀</button> <button title=\"Previous match\" thdaction=\"p\">◀</button> <span id=\"thdseekdesc\">Seek</span> <button title=\"Next match\" thdaction=\"n\">▶</button> <button title=\"Last match\" thdaction=\"l\">▶<b>l</b></button><div id=\"thdseekfail\"></div></div>" + "<div id=\"thdtopoptions\"><div>Options</div><ul><li><label title=\"Float a button in the upper right corner of the document to quickly access this panel\"><input type=\"checkbox\" id=\"chkhbtn\"" + btnchk + "> Show H button</label></li><li><label title=\"Highlight matches in <pre> and <code> tags\"><input type=\"checkbox\" id=\"chkprecode\"" + btnprecode + "> Match in pre/code</label></li><li><label style=\"padding-left:4px\">Framed pages:</label><br><select id=\"hlframeselect\" size=\"3\"><option value=\"none\">No highlighting</option><option value=\"same\">Same site only</option>" + "<option value=\"any\">Any site</option></select></li><li><button id=\"btnthsreread\" title=\"Update from and apply stored settings\" disabled>Re-Read Saved Prefs</button></li></ul></div>" + "<button class=\"btnkwhiclose\" onclick=\"document.getElementById('thdtopbar').style.display='none';document.getElementById('thdtopspacer').style.display='none';return false;\" style=\"float:right\">X</button></form>" + "<style type=\"text/css\">#thdtopbar{position:fixed;top:0;left:0;height:26px;width:100%;padding:0;color:#024;background:#ddd;font-family:sans-serif;font-size:16px;line-height:16px;border-bottom:1px solid #024;z-index:2500;display:none} " + "#thdtopbar,#thdtopbar *{box-sizing:content-box;} #thdtopform{display:block;position:relative;float:left;width:100%;margin:0;border:none;} " + "#thdtopbarhome,#thdtopcurrent,#thdtopfindbuttons,#thdtopoptions{float:left;top:0;left:0;margin:0;padding:5px 8px 4px;border-right:1px solid #fff;font-size:16px;} " + "#thdtopbarhome{width:22px;text-align:center;overflow:hidden;} #thdtopbarhome a{display:block;} #thdtopbarhome a img{display:block;border:none;border-radius:3px;padding:3px;margin:-3px 0 -4px 0;background-color:#fff} " + "#thdtopfindbuttons{padding-bottom:1px;position:relative} #thdtopfindbuttons button{margin:-5px 0 -2px 0;width:28px;height:18px;color:#024;background:#f0f0f0;border:1px solid #024;border-radius:4px;padding:1px 3px;} " + "#thdtopfindbuttons button:hover{background:#ffa;} #thdseekdesc{cursor:pointer} #thdtopkeywords{margin:0;width:500px;cursor:pointer;} #thdtopkeywords em{padding: 0 2px;} #thdtopkeywords em:hover{background:#ffa;}" + "#thdseekfail{display:none;position:absolute;top:30px;left:15px;z-index:2001;width:200px;color:#f8f8f8;background:#b00;border-radius:6px;text-align:center;font-size:12px;padding:3px}" + "#thdtopkeywords span{display:inline-block;width:100%;overflow:hidden;text-overflow:ellipsis;} #thdtable{max-height:600px;overflow-y:auto;overflow-x:hidden} " + "#thdtopdrop{position:absolute;top:26px;left:38px;width:500px;margin:0 -1px 0 -1px;padding:0 8px 8px 8px;background:#ddd;border:1px solid #024;border-top:none;border-radius:0 0 6px 6px;} " + "#thdtopdrop table{width:100%;background:#fff;border-top:1px solid #000;border-left:1px solid #000;table-layout:fixed} " + "#thdtopdrop td{padding:4px 4px; vertical-align:top;border-right:1px solid #000;border-bottom:1px solid #000;} #thdtopdrop td div{word-wrap:break-word} #thdtopdrop p{margin-top:8px;margin-bottom:0;} " + "#thdtopoptions{position:relative;width:160px;height:26px;padding:0 8px;} #thdtopoptions > div{padding:5px 0 4px;} " + "#thdtopoptions ul{position:absolute;top:26px;left:0;width:160px;margin:0 -1px 0 -1px;padding:0 8px 8px 8px;background:#ddd;border:1px solid #024;border-top:none;border-radius:0 0 6px 6px;list-style:none;} " + "#thdtopoptions li{width:100%;float:left;padding:2px 0;} #thdtopoptions ul{display:none;} #thdtopoptions:hover ul{display: block;border:1px solid #024;border-top:none;} #thdtopoptions li:hover{background:#eee;}" + ".btnkwhiclose{float:right;font-size:11px;margin-top:2px;} .thdtype{color:#ccc;float:right;font-size:12px;padding-top:8px;} #thdtopbar label{font-weight:normal;display:inline;margin:0} #hlframeselect{margin:3px 0 3px 4px;border-radius:4px}</style>"; document.body.appendChild(kwhibar); // Attach event handlers document.getElementById("thdtopkeywords").addEventListener("click",thddroptoggle,false); document.getElementById("kwhitbod").addEventListener("click",kwhiformevent,false); document.getElementById("kwhitbod").addEventListener("dblclick",kwhiformevent,false); document.getElementById("btnkwhiadd").addEventListener("click",kwhinewset,false); document.getElementById("btnkwhiexport").addEventListener("click",kwhiexport,false); document.getElementById("btnkwhiimport").addEventListener("click",kwhiimport,false); document.getElementById("thdtopfindbuttons").addEventListener("click",thdseek,false); document.getElementById("chkhbtn").addEventListener("click",kwhihbtn,false); document.getElementById("chkprecode").addEventListener("click",kwhiprecode,false); document.getElementById("btnthsreread").addEventListener("click",thsreread,false); document.getElementById("thdtopdropclose").addEventListener("click",kwhitopdropclose,false); // frame options document.getElementById("hlframeselect").addEventListener("change",thsframeselect,false); setthsframeopts(); // Add spacer at top of body var divsp = document.createElement("div"); divsp.id = "thdtopspacer"; divsp.setAttribute("style","clear:both;display:none"); divsp.style.height = parseInt(27 - parseInt(window.getComputedStyle(document.body,null).getPropertyValue("margin-top"))) + "px"; document.body.insertBefore(divsp, document.body.childNodes[0]); // Switch JS text to icon var JSBTN = document.createElement("img"); if (!GM4){ JSBTN.src = GM_getResourceURL("mycon"); } else { /* asynchronous*/ JSBTN.src = await GM.getResourceUrl("mycon"); } document.querySelector("#thdtopbar a").textContent = ""; document.querySelector("#thdtopbar a").appendChild(JSBTN); // Add menu item if (!GM4) GM_registerMenuCommand("Show Text Highlight and Seek Bar - View, Edit, Add Keywords and Styles", editKW); // Inject H button if (hlbtnvis == "off") hbtndisp = ' style="display:none"'; else hbtndisp = ''; var dNew = document.createElement("div"); dNew.innerHTML = '<button id="btnshowkwhi"' + hbtndisp + '>H</button><style type="text/css">#btnshowkwhi{position:fixed;top:4px;right:4px;opacity:0.2;' + 'color:#000;background-color:#ffa;font-weight:bold;font-size:12px;border:1px solid #ccc;border-radius:4px;padding:2px 3px;z-index:1999;min-width:22px;min-height:22px}' + '#btnshowkwhi:hover{opacity:0.8}@media print{#btnshowkwhi{display:none;}}</style>'; document.body.appendChild(dNew); document.getElementById("btnshowkwhi").addEventListener("click",editKW,false); // Set up add/edit form var kwhied = document.createElement("div"); kwhied.id = "kwhiedit"; kwhied.innerHTML = "<form onsubmit=\"return false;\"><p style=\"margin-top:0\"><b>Edit/Add Keywords/Highlighting</b>" + "<span class=\"btnkwhiclose\"><button id=\"btnkwhimax\" title=\"Maximize dialog size\">^</button> " + "<button onclick=\"document.getElementById('kwhiedit').style.display='none'; return false;\" title=\"Close dialog\">X</button></span>" + "</p><p>List longer forms of a word first to match both in full. Example: \"children|child\" will highlight both, but \"child|children\" " + "will only highlight child, it won't expand the selection to children.</p>" + "<table cellspacing=\"0\" style=\"table-layout:fixed\"><tbody><tr kwhiset=\"new\"><td style=\"width:calc(100% - 464px)\">" + "<p contenteditable=\"true\" style=\"border:1px dotted #000;word-wrap:break-word;display:block!important\" class=\"\">placeholder</p>" + "<p style=\"margin-top:2em\">Match type: <select id=\"kwhipattype\"><option value=\"string\" selected>Anywhere in a word</option>" + "<option value=\"word\">\"Whole\" words only</option><option value=\"regex\">Regular Expression (advanced)</option></select></p></td>" + "<td style=\"width:416px\" id=\"stylecontrols\"><p><span>Text color:</span> <input id=\"txtcolorinput\" type=\"color\" value=\"#000000\" title=\"Pop up color picker\"> " + "R:<input id=\"txtr\" type=\"number\" min=\"0\" max=\"255\" step=\"1\" style=\"width:3.25em\" value=\"0\"> " + "G:<input id=\"txtg\" type=\"number\" min=\"0\" max=\"255\" step=\"1\" style=\"width:3.25em\" value=\"0\"> " + "B:<input id=\"txtb\" type=\"number\" min=\"0\" max=\"255\" step=\"1\" style=\"width:3.25em\" value=\"0\"> " + "<button id=\"btntxtreset\">Reset</button></p>" + "<p><span>Background:</span> <input id=\"bkgcolorinput\" type=\"color\" value=\"#ffff80\" title=\"Pop up color picker\"> " + "R:<input id=\"bkgr\" type=\"number\" min=\"0\" max=\"255\" step=\"1\" style=\"width:3.25em\" value=\"255\"> " + "G:<input id=\"bkgg\" type=\"number\" min=\"0\" max=\"255\" step=\"1\" style=\"width:3.25em\" value=\"255\"> " + "B:<input id=\"bkgb\" type=\"number\" min=\"0\" max=\"255\" step=\"1\" style=\"width:3.25em\" value=\"128\"> <button id=\"btnbkgreset\">Reset</button></p>" + "<p><span>Font-weight:</span> <select id=\"fwsel\"><option value=\"inherit\" selected>inherit</option>" + "<option value=\"bold\"><b>bold</b></option><option value=\"normal\">not bold</option></select></p><p><span>Custom:</span> <input type=\"text\" " + "id=\"kwhicustom\" style=\"width:55%\"> <button id=\"kwhicustomapply\">Apply</button></p></td></tr></tbody></table>" + "<p><button id=\"btnkwhisave\">Save Changes</button> <button id=\"btnkwhicancel\">Discard Changes</button> " + "<button id=\"btnkwhiremove\">Hide Set</button> <button id=\"btnkwhirevert\" disabled>Revert Last Keyword Edit</button></p></form><style type=\"text/css\">" + "#kwhiedit{position:fixed;top:1px;left:150px;width:800px;height:400px;border:1px solid #000;border-radius:6px;padding:1em;color:#000;" + "background:#fafafa;z-index:2501;display:none} #kwhiedit table{width:100%;background:#fff;border-top:1px solid #000;" + "border-left:1px solid #000;} #kwhiedit td{padding:0 12px; vertical-align:top;border-right:1px solid #000;border-bottom:1px solid #000;}" + "#kwhiedit td p{margin-top:12px;} #stylecontrols>p>span{display:inline-block;width:6.5em;} " + "#stylecontrols input[type=\"color\"]{padding:0; width:24px; height:1.25em; border:none;}</style><style type=\"text/css\" id=\"kwhiedittemp\"></style></div>"; document.body.appendChild(kwhied); // Attach event handlers document.getElementById("btnkwhisave").addEventListener("click",kwhisavechg,false); document.getElementById("btnkwhicancel").addEventListener("click",kwhicancel,false); document.getElementById("btnkwhiremove").addEventListener("click",kwhiremove,false); document.getElementById("btnkwhirevert").addEventListener("click",kwhirevert,false); document.getElementById("stylecontrols").addEventListener("input",updatestyle,false); document.getElementById("stylecontrols").addEventListener("change",updatecolor,false); document.getElementById("btntxtreset").addEventListener("click",kwhicolorreset,false); document.getElementById("btnbkgreset").addEventListener("click",kwhicolorreset,false); document.getElementById("fwsel").addEventListener("change",kwhifwchg,false); document.getElementById("kwhicustomapply").addEventListener("click",kwhicustom,false); document.getElementById("btnkwhimax").addEventListener("click",kwhimaxrestore,false); // Context menu options -- do not replace any existing menu! if (!document.body.hasAttribute("contextmenu") && "contextMenu" in document.documentElement){ var cmenu = document.createElement("menu"); cmenu.id = "THDcontext"; cmenu.setAttribute("type", "context"); cmenu.innerHTML = '<menu label="Text Highlight and Seek">' + '<menuitem id="THDshowbar" label="Show bar"></menuitem>' + '<menuitem id="THDenableset" label="Enable matching set"></menuitem>' + '<menuitem id="THDdisableset" label="Disable this set"></menuitem>' + '<menuitem id="THDnewset" label="Add new set"></menuitem>' + '</menu>'; document.body.appendChild(cmenu); document.getElementById("THDshowbar").addEventListener("click",editKW,false); document.getElementById("THDenableset").addEventListener("click",cmenuEnable,false); document.getElementById("THDdisableset").addEventListener("click",cmenuDisable,false); document.getElementById("THDnewset").addEventListener("click",cmenuNewset,false); // attach menu and create event for filtering document.body.setAttribute("contextmenu", "THDcontext"); document.body.addEventListener("contextmenu",cmenuFilter,false); } if (!GM4) GM_registerMenuCommand("TEST ONLY - flush keyword sets for Text Highlight and Seek", flushData); } THS_init(); // Main workhorse routine function THmo_doHighlight(el,subset){ if (subset) var keyset = subset; else var keyset = hlkeys; for (var j = 0; j < keyset.length; ++j) { var hlset = keyset[j]; if (hlobj[hlset].visible == "true" && hlobj[hlset].enabled == "true"){ var hlkeywords = hlobj[hlset].keywords; if (hlkeywords.length > 0) { if (hlobj[hlset].type != "regex"){ var rQuantifiers = /[-\/\\^$*+?.()[\]{}]/g; hlkeywords = hlkeywords.replace(rQuantifiers, '\\$&'); if (hlobj[hlset].type == "word"){ hlkeywords = "\\b" + hlkeywords.replace(/\|/g, "\\b|\\b") + "\\b"; } } //console.log("hlset:"+hlset+"\nhlkeywords:"+hlkeywords); var pat = new RegExp('(' + hlkeywords + ')', 'gi'); var span = document.createElement('thdfrag'); span.setAttribute("thdcontain","true"); // getting all text nodes with a few exceptions if (hlprecode){ var snapElements = document.evaluate( './/text()[normalize-space() != "" ' + 'and not(ancestor::style) ' + 'and not(ancestor::script) ' + 'and not(ancestor::textarea) ' + 'and not(ancestor::div[@id="thdtopbar"]) ' + 'and not(ancestor::div[@id="kwhiedit"]) ' + 'and not(parent::thdfrag[@txhidy15])]', el, null, XPathR###lt.UNORDERED_NODE_SNAPSHOT_TYPE, null); } else { var snapElements = document.evaluate( './/text()[normalize-space() != "" ' + 'and not(ancestor::style) ' + 'and not(ancestor::script) ' + 'and not(ancestor::textarea) ' + 'and not(ancestor::pre) ' + 'and not(ancestor::code) ' + 'and not(ancestor::div[@id="thdtopbar"]) ' + 'and not(ancestor::div[@id="kwhiedit"]) ' + 'and not(parent::thdfrag[@txhidy15])]', el, null, XPathR###lt.UNORDERED_NODE_SNAPSHOT_TYPE, null); } if (!snapElements.snapshotItem(0)) { break; } for (var i = 0, len = snapElements.snapshotLength; i < len; i++) { var node = snapElements.snapshotItem(i); // check if it contains the keywords if (pat.test(node.nodeValue)) { // create an element, replace the text node with an element var sp = span.cloneNode(true); sp.innerHTML = node.nodeValue.replace(/</g, '<').replace(/>/g, '>').replace(pat, '<thdfrag class="THmo '+hlset+'" txhidy15="'+hlset+'">$1</thdfrag>'); node.parentNode.replaceChild(sp, node); // try to un-nest containers if (sp.parentNode.hasAttribute("thdcontain")) sp.outerHTML = sp.innerHTML; } } } } } } function insertCSS(setkeys){ for (var j = 0; j < setkeys.length; ++j){ var hlset = setkeys[j]; if (hlobj[hlset].visible == "true"){ var rule = "."+hlset+"{display:inline!important;"; if (hlobj[hlset].textcolor.length > 0) rule += "color:"+hlobj[hlset].textcolor+";"; if (hlobj[hlset].backcolor.length > 0) rule += "background-color:"+hlobj[hlset].backcolor+";"; if (hlobj[hlset].fontweight.length > 0) rule += "font-weight:"+hlobj[hlset].fontweight+";"; if (hlobj[hlset].custom.length > 0) rule += hlobj[hlset].custom+";"; rule += "}"; var setrule = document.querySelector('style[hlset="' + hlset +'"]'); if (!setrule){ var s = document.createElement("style"); s.type = "text/css"; s.setAttribute("hlset", hlset); s.appendChild(document.createTextNode(rule)); document.body.appendChild(s); } else { setrule.innerHTML = rule; } } } } function editKW(e){ refreshSetList(); // show form document.getElementById("thdtopbar").style.display = "block"; document.getElementById("thdtopspacer").style.display = "block"; } function thdDropSetList(e){ refreshSetList(); document.getElementById("thdtopdrop").style.display = "block"; } function thddroptoggle(e){ if (document.getElementById("thdtopdrop").style.display == "none"){ thdDropSetList(); if (e.target.nodeName == "EM") kwhinewset(); } else if (e.target.nodeName == "EM"){ kwhinewset(); } else { document.getElementById("thdtopdrop").style.display = "none"; } } function refreshSetList(e){ // clear old rows from form document.getElementById("kwhitbod").innerHTML = ""; // populate data - hlobj is global for (var j = 0; j < hlkeys.length; ++j){ var hlset = hlkeys[j]; if (hlobj[hlset].visible == "true"){ if (hlobj[hlset].enabled == "true") var strchk = ' checked=\"checked\"'; else var strchk = ''; var newrow = document.createElement("tr"); var thdtypenote = ''; newrow.setAttribute("kwhiset", hlset); if(hlobj[hlset].type != "string"){ thdtypenote = '<span class="thdtype">' + hlobj[hlset].type + '</span>'; } if (j == 0){ newrow.innerHTML = '<td style=\"width:286px\"><div class=\"' + hlset + '\">' + hlobj[hlset].keywords + '</div>' + thdtypenote + '</td>' + '<td style=\"width:195px\"><button kwhiset=\"' + hlset + '\" title=\"Bring matches into view\">Seek</button> ' + '<button kwhiset=\"' + hlset + '\">Edit</button> <label><input type=\"checkbox\" kwhiset=\"' + hlset + '\"' + strchk + '"> Enabled </label></td>'; } else { newrow.innerHTML = '<td><div class=\"' + hlset + '\">' + hlobj[hlset].keywords + '</div>' + thdtypenote + '</td>' + '<td><button kwhiset=\"' + hlset + '\" title=\"Bring matches into view\">Seek</button> ' + '<button kwhiset=\"' + hlset + '\">Edit</button> <label><input type=\"checkbox\" kwhiset=\"' + hlset + '\"' + strchk + '"> Enabled </label></td>'; } document.getElementById("kwhitbod").appendChild(newrow); } } } async function kwhiformevent(e){ if (e.target.nodeName == "INPUT"){ // Enabled checkbox var hlsetnum = e.target.getAttribute("kwhiset"); kwhienabledisable(hlsetnum, e.target.checked); } if (e.target.nodeName == "BUTTON"){ // Call up edit form or find bar var hlset = e.target.getAttribute('kwhiset'); if (e.target.textContent == "Edit"){ // need to cancel in-place editor if it's open kwhicancelipe(hlset); // set set number attribute document.querySelector('#kwhiedit tr').setAttribute('kwhiset', hlset); // set class for keywords document.querySelector('#kwhiedit td:nth-of-type(1) p:nth-of-type(1)').className = hlset; // enter placeholder text & type document.querySelector('#kwhiedit td:nth-of-type(1) p:nth-of-type(1)').textContent = hlobj[hlset].keywords; document.getElementById("kwhipattype").selectedIndex = 0; if (hlobj[hlset].type == "word") document.getElementById("kwhipattype").selectedIndex = 1; if (hlobj[hlset].type == "regex") document.getElementById("kwhipattype").selectedIndex = 2; // set style editing to default and override with set rules kwhieditstyle = ["rgb(0,0,255)","rgb(255,255,0)","inherit",""]; // defaults if (hlobj[hlset].textcolor.length > 0) kwhieditstyle[0] = hlobj[hlset].textcolor; if (hlobj[hlset].backcolor.length > 0) kwhieditstyle[1] = hlobj[hlset].backcolor; if (hlobj[hlset].fontweight.length > 0) kwhieditstyle[2] = hlobj[hlset].fontweight; if (hlobj[hlset].custom.length > 0) kwhieditstyle[3] = hlobj[hlset].custom; kwhiShowEditForm(); } if (e.target.textContent == "Seek"){ // need to cancel in-place editor if it's open kwhicancelipe(hlset); // Enable set if not currently enabled (2.3.5) var chkbx = e.target.parentNode.querySelector('input[type="checkbox"]'); if (!chkbx.checked) chkbx.click(); // Populate current seek set to #thdtopkeywords var divDataTD = e.target.parentNode.previousElementSibling; document.getElementById("thdtopkeywords").innerHTML = "<i>Seeking:</i> " + divDataTD.firstChild.outerHTML; // Store set to seek in #thdtopfindbuttons document.getElementById("thdtopfindbuttons").setAttribute("thdseek", hlset); // Close Keyword Sets form document.getElementById('thdtopdrop').style.display='none'; // Send click event to the "seek first" button document.getElementById('thdtopfindbuttons').children[0].click(); } if (e.target.textContent == "Save"){ // Check and save in-place keyword edit // get set number attribute var hlset = e.target.getAttribute("kwhiset"); var kwtext = document.querySelector('div.'+hlset+' p.'+hlset).textContent; if (kwtext == hlobj[hlset].keywords){ // Nothing to save, cancel the edit kwhicancelipe(hlset); return; } // Save keyword changes WITHOUT user confirmation hlobj[hlset].prevkeyw = hlobj[hlset].keywords; hlobj[hlset].prevtype = hlobj[hlset].type; if (hlobj[hlset].type != "regex") hlobj[hlset].keywords = kwtext; else{ hlobj[hlset].keywords = kwtext.replace(/\\/g, "\\"); hlobj[hlset].hlpat = ""; //TODOLATER } // Set updated date/time hlobj[hlset].updated = (new Date()).toJSON(); // Persist the object hljson = JSON.stringify(hlobj); if (!GM4){ GM_setValue("kwstyles", hljson); } else { await GM.setValue("kwstyles", hljson); } // Update CSS rule and parent form insertCSS([hlset]); refreshSetList(); // Unhighlight, re-highlight, close in-place editor unhighlight(hlset); THmo_doHighlight(document.body,[hlset]); kwhicancelipe(hlset); } if (e.target.textContent == "Cancel"){ // Revert in-place editor // get set number attribute var hlset = e.target.getAttribute("kwhiset"); kwhicancelipe(hlset); } if (e.target.textContent == "Revert"){ // Restore previous keywords // get set number attribute var hlset = e.target.getAttribute("kwhiset"); // gray the button document.getElementById('thsrevert' + hlset).setAttribute('disabled', 'disabled'); // get the previous keywords (if any) if (hlobj[hlset].prevkeyw && hlobj[hlset].prevkeyw != '') var kwtext = hlobj[hlset].prevkeyw; if (!kwtext || kwtext == ''){ // uh-oh alert('Unable to undo, sorry!'); document.getElementById('thsrevert' + hlset).setAttribute('disabled', 'disabled'); return; } // Save keyword changes WITHOUT user confirmation hlobj[hlset].keywords = kwtext; hlobj[hlset].type = hlobj[hlset].prevtype; hlobj[hlset].prevkeyw = ''; hlobj[hlset].prevtype = ''; // Set updated date/time hlobj[hlset].updated = (new Date()).toJSON(); // Persist the object hljson = JSON.stringify(hlobj); if (!GM4){ GM_setValue("kwstyles", hljson); } else { await GM.setValue("kwstyles", hljson); } // Update CSS rule and parent form insertCSS([hlset]); refreshSetList(); // Unhighlight, re-highlight unhighlight(hlset); THmo_doHighlight(document.body,[hlset]); } } if (e.type == "dblclick" && e.target.nodeName == "DIV"){ // Set up in-place quick editor if (e.target.children.length == 0) { // Ignore the double-click if the editor was already set up var hlset = e.target.className; e.target.innerHTML = '<p class="' + hlset +'" contenteditable="true" style="border:1px dotted #000">' + e.target.textContent + '</p>' + '<p style="background-color:#fff;font-size:0.8em"><button kwhiset="' + hlset + '" title="Update keywords for this set" style="font-size:0.8em">' + 'Save</button> <button kwhiset="' + hlset + '" title="Keep saved keywords" style="font-size:0.8em">Cancel</button> <button kwhiset="' + hlset + '" id="thsrevert' + hlset + '" title="Revert last edit" style="font-size:0.8em" disabled>Revert</button></p>'; var rng = document.createRange(); rng.selectNodeContents(e.target.children[0]); var sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(rng); if (hlobj[hlset].prevkeyw && hlobj[hlset].prevkeyw != '') { document.getElementById('thsrevert' + hlset).removeAttribute('disabled'); document.getElementById('thsrevert' + hlset).setAttribute('title','Revert to "' + hlobj[hlset].prevkeyw + '"'); } } } } async function kwhienabledisable(hlsetnum,enable){ if (enable == false) { // Update object and persist to GM storage hlobj[hlsetnum].enabled = "false"; hljson = JSON.stringify(hlobj); if (!GM4){ GM_setValue("kwstyles", hljson); } else { await GM.setValue("kwstyles", hljson); } // Unhighlight unhighlight(hlsetnum); // Clear seek info from bar if this set is there var seekset = document.getElementById("thdtopfindbuttons").getAttribute("thdseek"); if (seekset){ if(seekset.indexOf("|") > -1) seekset = seekset.split("|")[0]; if (hlsetnum == seekset){ document.getElementById("thdtopfindbuttons").setAttribute("thdseek",""); document.getElementById("thdseekdesc").textContent = "Seek"; document.getElementById("thdtopkeywords").innerHTML = "Click to manage keyword/highlight sets • <em>Add New Set</em>"; } } } else { // Update object and persist to GM storage hlobj[hlsetnum].enabled = "true"; hljson = JSON.stringify(hlobj); if (!GM4){ GM_setValue("kwstyles", hljson); } else { await GM.setValue("kwstyles", hljson); } // Highlight THmo_doHighlight(document.body,[hlsetnum]); } } function kwhinewset(e,kwtext){ // call up new set form // set set number attribute document.querySelector('#kwhiedit tr').setAttribute('kwhiset', 'new'); // clear class for keywords document.querySelector('#kwhiedit td:nth-of-type(1) p:nth-of-type(1)').className = ""; // enter placeholder text & default type if (kwtext) document.querySelector('#kwhiedit td:nth-of-type(1) p:nth-of-type(1)').textContent = kwtext; else document.querySelector('#kwhiedit td:nth-of-type(1) p:nth-of-type(1)').textContent = "larry|moe|curly"; document.getElementById("kwhipattype").selectedIndex = 0; // set style editing to defaults kwhieditstyle = ["rgb(0,0,255)","rgb(255,255,0)","inherit",""]; kwhiShowEditForm(); } function kwhiShowEditForm(){ var rule = "#stylecontrols>p>span{"; if (kwhieditstyle[0].length > 0) rule += "color:"+kwhieditstyle[0]+";"; if (kwhieditstyle[1].length > 0) rule += "background-color:"+kwhieditstyle[1]+";"; if (kwhieditstyle[2].length > 0) rule += "font-weight:"+kwhieditstyle[2]+";"; if (kwhieditstyle[3].length > 0) rule += kwhieditstyle[3]+";"; document.getElementById("kwhiedittemp").innerHTML = rule + "}"; populateRGB("txt",kwhieditstyle[0]); populateRGB("bkg",kwhieditstyle[1]); document.getElementById("fwsel").value = kwhieditstyle[2]; document.getElementById("kwhicustom").value = kwhieditstyle[3]; updateColorInputs(); // default the reversion button to disabled var rbtn = document.getElementById("btnkwhirevert"); rbtn.setAttribute('disabled','disabled'); if (rbtn.hasAttribute('kwhiset')) rbtn.removeAttribute('kwhiset'); rbtn.setAttribute('title',''); // show form document.getElementById("kwhiedit").style.display = "block"; // check for possible reversion option var hlset = document.querySelector('#kwhiedit td:nth-of-type(1) p:nth-of-type(1)').className; if (hlset != "" && hlobj[hlset].prevkeyw && hlobj[hlset].prevkeyw != '') { rbtn.removeAttribute('disabled'); rbtn.setAttribute('kwhiset',hlset); rbtn.setAttribute('title','Revert to "' + hlobj[hlset].prevkeyw + '"'); } } function kwhiexport(e){ prompt("JSON data\nPress Ctrl+c or right-click to copy\n ", JSON.stringify(hlobj)); } async function kwhiimport(e){ var txtImport = prompt("Paste in the exported data and click OK to start parsing", ""); try{ var objImport = JSON.parse(txtImport); } catch(err){ alert("Sorry, data does not appear to be in the proper format. Here's the error according to Firefox: \n\n"+err+"\n\nHopefully you can resolve that!"); return; } var keysImport = Object.keys(objImport); // Compare for duplicate set numbers var keysString = "|" + hlkeys.join("|") + "|"; var counter = 0; for (var j = 0; j < keysImport.length; ++j){ if(keysString.indexOf("|"+keysImport[j]+"|") > -1) counter++; } if (counter > 0){ var arc = prompt("Detected "+counter+" of "+keysImport.length+" set numbers to be imported already exist. Do you want to:\nAdd these sets [A]\nReplace existing sets [R]\nTotally replace all existing sets [T]\nCancel the import [C]?","A"); if (!arc) return; if (arc.length == 0) return; if (arc.toLowerCase() == "c") return; if (arc.toLowerCase() == "t"){ if(!confirm("Total replacement has no error checking. Click OK to confirm total replacement, or click Cancel to only Replace matching sets.")) arc = "R"; } } else { var arc = "A"; } if (arc.toLowerCase() == "t"){ // Total replacement hlobj = JSON.parse(txtImport); // Update the global key array hlkeys = Object.keys(hlobj); // Persist the object hljson = JSON.stringify(hlobj); if (!GM4){ GM_setValue("kwstyles", hljson); } else { await GM.setValue("kwstyles", hljson); } // Apply to page (see below) } else { for (var j = 0; j < keysImport.length; ++j){ // Add/replace individual sets var impset = keysImport[j]; if(keysString.indexOf("|"+impset+"|") > -1 && arc.toLowerCase() == "r"){ // replace var hlset = impset; hlobj[hlset].keywords = objImport[impset].keywords || "keywords|not|found"; hlobj[hlset].type = objImport[impset].type || "string"; hlobj[hlset].hlpat = objImport[impset].hlpat || ""; hlobj[hlset].textcolor = objImport[impset].textcolor || "rgb(0,0,255)"; hlobj[hlset].backcolor = objImport[impset].backcolor || "rgb(255,255,0)"; hlobj[hlset].fontweight = objImport[impset].fontweight || "inherit"; hlobj[hlset].custom = objImport[impset].custom || ""; hlobj[hlset].enabled = objImport[impset].enabled || "true"; hlobj[hlset].visible = objImport[impset].visible || "true"; hlobj[hlset].updated = (new Date()).toJSON(); } else { // add if(keysString.indexOf("|"+impset+"|") > -1 && arc.toLowerCase() == "a"){ // create a new set number instead var hlset = "set" + hlnextset; hlnextset += 1; if (!GM4){ GM_setValue("hlnextset",hlnextset); } else { await GM.setValue("hlnextset",hlnextset); } } else { var hlset = impset; } // add the set hlobj[hlset] = { keywords : objImport[impset].keywords || "keywords|not|found", type: objImport[impset].type || "string", hlpat : objImport[impset].hlpat || "", textcolor : objImport[impset].textcolor || "rgb(0,0,255)", backcolor : objImport[impset].backcolor || "rgb(255,255,0)", fontweight : objImport[impset].fontweight || "inherit", custom : objImport[impset].custom || "", enabled : objImport[impset].enabled || "true", visible : objImport[impset].visible || "true", updated : objImport[impset].updated || "" } } // Update the global key array hlkeys = Object.keys(hlobj); // Persist the object hljson = JSON.stringify(hlobj); if (!GM4){ GM_setValue("kwstyles", hljson); } else { await GM.setValue("kwstyles", hljson); } } } // TODO: Could an error prevent reaching this point, for example, if the import object is missing properties due to bad editing? // Update CSS rule and command bar list insertCSS(hlkeys); refreshSetList(); // Unhighlight all, re-highlight all, close dialog unhighlight(null); THmo_doHighlight(document.body); } async function kwhihbtn(e){ if (e.target.checked == false){ hlbtnvis = "off"; if (!GM4){ GM_setValue("hlbtnvis",hlbtnvis); } else { await GM.setValue("hlbtnvis",hlbtnvis); } document.getElementById("btnshowkwhi").style.display = "none"; } else { hlbtnvis = "on"; if (!GM4){ GM_setValue("hlbtnvis",hlbtnvis); } else { await GM.setValue("hlbtnvis",hlbtnvis); } document.getElementById("btnshowkwhi").style.display = ""; } } async function kwhiprecode(e){ if (e.target.checked == false){ // Update var, persist the preference, unhighlight, rehighlight hlprecode = false; if (!GM4){ GM_setValue("hlprecode",hlprecode); } else { await GM.setValue("hlprecode",hlprecode); } unhighlight(null); THmo_doHighlight(document.body); } else { // Update var, persist the preference, rehighlight hlprecode = true; if (!GM4){ GM_setValue("hlprecode",hlprecode); } else { await GM.setValue("hlprecode",hlprecode); } THmo_doHighlight(document.body); } } function kwhicancelipe(setno){ // clean up in-place editor(s) if (setno && setno != ''){ var kwdiv = document.querySelector('#kwhitbod .'+setno); if (kwdiv){ kwdiv.innerHTML = hlobj[setno].keywords; return; } } else { // Check 'em all var divs = document.querySelector('#kwhitbod div'); for (var n=0; n<divs.length; n++){ if (divs[n].children.length > 0 && divs[n].className != '') kwhicancelipe(divs[n].className); } } } function kwhitopdropclose(e){ kwhicancelipe(''); document.getElementById('thdtopdrop').style.display='none'; } function thsreread(e){ //TODO } async function thsframeselect(e){ var selopt = e.target.options[e.target.selectedIndex].value; if (hlframe != selopt) { hlframe = selopt; if (!GM4){ GM_setValue("hlframe",hlframe); } else { await GM.setValue("hlframe",hlframe); } setthsframeopts(); } } function setthsframeopts(){ var sel = document.getElementById("hlframeselect"); if (hlframe == "none"){ sel.options[0].selected = true; sel.options[0].setAttribute("selected","selected"); } else { sel.options[0].selected = false; if (sel.options[0].hasAttribute("selected")) sel.options[0].removeAttribute("selected"); } if (hlframe == "same"){ sel.options[1].selected = true; sel.options[1].setAttribute("selected","selected"); } else { sel.options[1].selected = false; if (sel.options[1].hasAttribute("selected")) sel.options[1].removeAttribute("selected"); } if (hlframe == "any"){ sel.options[2].selected = true; sel.options[2].setAttribute("selected","selected"); } else { sel.options[2].selected = false; if (sel.options[2].hasAttribute("selected")) sel.options[2].removeAttribute("selected"); } } function thdseek(e){ if (e.target.nodeName == "DIV") return; // ignore background clicks var seekset = e.currentTarget.getAttribute("thdseek"); if (!seekset){ // user needs to select a set to seek in thdDropSetList(); } else { var seekparams = seekset.split("|"); var seekmatches = document.querySelectorAll('thdfrag[txhidy15="'+seekparams[0]+'"]'); // Update or add total size of set; FIGURE OUT LATER: what if this changed?? seekparams[1] = seekmatches.length; if (seekmatches.length > 0){ if (e.target.nodeName == "SPAN"){ // re-scroll to the current reference thdshow(seekmatches[parseInt(seekparams[2])]); } else { // BUTTON var seekaction = e.target.getAttribute("thdaction"); if (!seekaction) seekaction = "f"; if (seekparams.length == 3){ // User has seeked in this set switch (seekaction){ case "f": seekparams[2] = 0; var rtn = thdshow(seekmatches[parseInt(seekparams[2])]); if (rtn == false) seekagain("n"); break; case "p": if (parseInt(seekparams[2]) > 0) { seekparams[2] = parseInt(seekparams[2]) - 1; var rtn = thdshow(seekmatches[parseInt(seekparams[2])]); if (rtn == false){ if (parseInt(seekparams[2]) > 0) seekagain("p"); else seekfailnotc("No previous match visible"); } } else { seekfailnotc("Already reached first match"); } break; case "n": if (parseInt(seekparams[2]) < (seekmatches.length-1)) { seekparams[2] = parseInt(seekparams[2]) + 1; var rtn = thdshow(seekmatches[parseInt(seekparams[2])]); if (rtn == false){ if (parseInt(seekparams[2]) < (seekmatches.length-1)) seekagain("n"); else seekfailnotc("No later match visible"); } } else { seekparams[2] = (seekmatches.length-1); // in case it's too high, fix that here seekfailnotc("Already reached last match"); } break; case "l": seekparams[2] = (seekmatches.length-1); var rtn = thdshow(seekmatches[parseInt(seekparams[2])]); if (rtn == false) seekagain("p"); break; } } else { seekparams[2] = 0; thdshow(seekmatches[parseInt(seekparams[2])]); } document.getElementById("thdtopfindbuttons").setAttribute("thdseek", seekparams.join("|")); document.getElementById("thdseekdesc").textContent = (parseInt(seekparams[2])+1) + " of " + seekparams[1]; } } else { document.getElementById("thdseekdesc").textContent = "0 of 0"; } } } function thdshow(elt){ // this could be much prettier with animation! TODO: outline/box? elt.scrollIntoView(); var rect = elt.getClientRects()[0]; if (rect){ // scroll down one inch to avoid many fixed headers if (rect.top < 96) window.scroll(0, window.scrollY-96); return true; } else { // match is not visible return false; } } function seekagain(dir){ switch (dir){ case "p": seekfailnotc("Hidden, trying previous match..."); window.setTimeout(function(){document.querySelector('button[thdaction="p"]').click();},250); break; case "n": seekfailnotc("Hidden, trying next match..."); window.setTimeout(function(){document.querySelector('button[thdaction="n"]').click();},250); break; } } var evttimer; function seekfailnotc(txt){ var sfdiv = document.getElementById("thdseekfail"); sfdiv.textContent = txt; sfdiv.style.display = "block"; if (evttimer) window.clearTimeout(evttimer); evttimer = window.setTimeout(function(){document.getElementById("thdseekfail").style.display="none";}, 800); } function unhighlight(setnum){ if (setnum) var tgts = document.querySelectorAll('thdfrag[txhidy15="' + setnum + '"]'); else var tgts = document.querySelectorAll('thdfrag[txhidy15]'); // remove ALL for (var i=0; i<tgts.length; i++){ // Check for co-extant parent(s) to remove potentially stranded <span>s var parnode = tgts[i].parentNode, parpar = parnode.parentNode, tgtspan; if (parnode.hasAttribute("thdcontain") && parnode.innerHTML == tgts[i].outerHTML){ parnode.outerHTML = tgts[i].textContent.replace(/</g, '<').replace(/>/g, '>'); tgtspan = parpar; } else { tgts[i].outerHTML = tgts[i].textContent.replace(/</g, '<').replace(/>/g, '>'); tgtspan = parnode; } tgtspan.normalize(); if (tgtspan.hasAttribute("thdcontain")){ parnode = tgtspan.parentNode; if (parnode){ if (parnode.hasAttribute("thdcontain") && parnode.innerHTML == tgtspan.outerHTML && tgtspan.querySelectorAll('thdfrag[txhidy15]').length == 0){ parnode.outerHTML = tgtspan.innerHTML; } else if (parnode.innerHTML == tgtspan.outerHTML && tgtspan.querySelectorAll('thdfrag[txhidy15]').length == 0) { parnode.innerHTML = tgtspan.innerHTML; } } } } } async function kwhisavechg(e){ // Update object, regenerate CSS if applicable, apply to document var hlset = document.querySelector('#kwhiedit td:nth-of-type(1) p:nth-of-type(1)').className; var kwtext = document.querySelector('#kwhiedit td:nth-of-type(1) p:nth-of-type(1)').textContent; if (hlset == ""){ // create a new set number var hlset = "set" + hlnextset; hlnextset += 1; if (!GM4){ GM_setValue("hlnextset",hlnextset); } else { await GM.setValue("hlnextset",hlnextset); } // add the set if (document.getElementById("kwhipattype").value == "regex"){ kwtext = kwtext.replace(/\\/g, "\\"); var hlpattxt = ""; //TODOLATER } else { var hlpattxt = ""; } hlobj[hlset] = { keywords : kwtext, type : document.getElementById("kwhipattype").value, hlpat : hlpattxt, textcolor : kwhieditstyle[0], backcolor : kwhieditstyle[1], fontweight : kwhieditstyle[2], custom : kwhieditstyle[3], enabled : "true", visible : "true", updated : "" } // Update the global key array hlkeys = Object.keys(hlobj); } else { var oldtype = hlobj[hlset].type; hlobj[hlset].type = document.getElementById("kwhipattype").value; // Save keyword changes after user confirmation if (kwtext != hlobj[hlset].keywords){ if (confirm("Save updated keywords (and other changes)?")){ hlobj[hlset].prevkeyw = hlobj[hlset].keywords; hlobj[hlset].prevtype = oldtype; if (hlobj[hlset].type != "regex"){ hlobj[hlset].keywords = kwtext; } else { hlobj[hlset].keywords = kwtext.replace(/\\/g, "\\"); hlobj[hlset].hlpat = ""; //TODOLATER } } else return; } // Save style changes without confirmation hlobj[hlset].textcolor = kwhieditstyle[0]; hlobj[hlset].backcolor = kwhieditstyle[1]; hlobj[hlset].fontweight = kwhieditstyle[2]; hlobj[hlset].custom = kwhieditstyle[3]; // Set updated date/time hlobj[hlset].updated = (new Date()).toJSON(); } // Persist the object hljson = JSON.stringify(hlobj); if (!GM4){ GM_setValue("kwstyles", hljson); } else { await GM.setValue("kwstyles", hljson); } // Update CSS rule and parent form insertCSS([hlset]); refreshSetList(); // Unhighlight, re-highlight, close dialog unhighlight(hlset); THmo_doHighlight(document.body,[hlset]) document.getElementById('kwhiedit').style.display='none'; } function kwhicancel(e){ // Close dialog (fields will be refresh if it is opened again) document.getElementById('kwhiedit').style.display='none'; } async function kwhiremove(e){ var hlset = document.querySelector('#kwhiedit td:nth-of-type(1) p:nth-of-type(1)').className; if (hlset == ""){ alert("This set has not been saved and therefore does not need to be hidden, you can just close the dialog to discard it."); } else { if (confirm("Are you sure you want to hide this set instead of editing it to your own liking?")){ hlobj[hlset].visible = "false"; hlobj[hlset].updated = (new Date()).toJSON(); // Persist the object hljson = JSON.stringify(hlobj); if (!GM4){ GM_setValue("kwstyles", hljson); } else { await GM.setValue("kwstyles", hljson); } // Update set list, remove highlighting, close form refreshSetList(); unhighlight(hlset); document.getElementById('kwhiedit').style.display='none'; } } } async function kwhirevert(e){ // get set number attribute var hlset = e.target.getAttribute("kwhiset"); // gray the button e.target.setAttribute('disabled', 'disabled'); // get the previous keywords (if any) if (hlobj[hlset].prevkeyw && hlobj[hlset].prevkeyw != '') var kwtext = hlobj[hlset].prevkeyw; if (!kwtext || kwtext == ''){ // uh-oh alert('Unable to undo, sorry!'); return; } // Save keyword changes WITHOUT user confirmation hlobj[hlset].keywords = kwtext; hlobj[hlset].type = hlobj[hlset].prevtype; hlobj[hlset].prevkeyw = ''; hlobj[hlset].prevtype = ''; // Set updated date/time hlobj[hlset].updated = (new Date()).toJSON(); // Persist the object hljson = JSON.stringify(hlobj); if (!GM4){ GM_setValue("kwstyles", hljson); } else { await GM.setValue("kwstyles", hljson); } // Update CSS rule and parent form insertCSS([hlset]); refreshSetList(); // Unhighlight, re-highlight unhighlight(hlset); THmo_doHighlight(document.body,[hlset]); // Refresh the keywords and type document.querySelector('#kwhiedit td:nth-of-type(1) p:nth-of-type(1)').textContent = hlobj[hlset].keywords; document.getElementById("kwhipattype").selectedIndex = 0; if (hlobj[hlset].type == "word") document.getElementById("kwhipattype").selectedIndex = 1; if (hlobj[hlset].type == "regex") document.getElementById("kwhipattype").selectedIndex = 2; } function kwhicolorreset(e){ // what set is this? var set = document.querySelector('#kwhiedit tr').getAttribute('kwhiset'); // check which button, reset the RGB if (e.target.id == "btntxtreset"){ if (set == "new"){ kwhieditstyle[0] = "rgb(0,0,255)"; } else { kwhieditstyle[0] = hlobj[set].textcolor; } populateRGB("txt",kwhieditstyle[0]); setdivstyle(["txt"]); } if (e.target.id == "btnbkgreset"){ if (set == "new"){ kwhieditstyle[1] = "rgb(255,255,0)"; } else { kwhieditstyle[1] = hlobj[set].backcolor; } populateRGB("bkg",kwhieditstyle[1]); setdivstyle(["bkg"]); } e.target.blur(); } function populateRGB(prop,stylestring){ var rgbvals = stylestring.substr(stylestring.indexOf("(")+1); rgbvals = rgbvals.substr(0,rgbvals.length-1).split(","); document.getElementById(prop+"r").value = parseInt(rgbvals[0]); document.getElementById(prop+"g").value = parseInt(rgbvals[1]); document.getElementById(prop+"b").value = parseInt(rgbvals[2]); } async function updatestyle(e){ // validate value and apply change var tgt; if (e.id != undefined) tgt = e; else tgt = e.target; if (tgt.id.indexOf("colorinput") > -1){ // let's wait for the change event to fire before updating } else if (tgt.id.indexOf("txt") == 0 || tgt.id.indexOf("bkg") == 0){ if (isNaN(tgt.value)){ alert("Please only use values between 0 and 255"); return; } if (parseInt(tgt.value) != tgt.value){ tgt.value = parseInt(tgt.value); } if (tgt.value < 0){ tgt.value = 0; } if (tgt.value > 255){ tgt.value = 255; } if (tgt.id.indexOf("txt") == 0) setdivstyle(["txt"]); if (tgt.id.indexOf("bkg") == 0) setdivstyle(["bkg"]); } else { if (tgt.id == "kwhicustom") return; console.log("updatestyle on "+tgt.id); } } function setdivstyle(props){ for (var i=0; i<props.length; i++){ switch (props[i]){ case "txt": kwhieditstyle[0] = "rgb(" + document.getElementById("txtr").value + "," + document.getElementById("txtg").value + "," + document.getElementById("txtb").value + ")"; break; case "bkg": kwhieditstyle[1] = "rgb(" + document.getElementById("bkgr").value + "," + document.getElementById("bkgg").value + "," + document.getElementById("bkgb").value + ")"; break; default: console.log("default?"); } } var rule = "#stylecontrols>p>span{"; if (kwhieditstyle[0].length > 0) rule += "color:"+kwhieditstyle[0]+";"; if (kwhieditstyle[1].length > 0) rule += "background-color:"+kwhieditstyle[1]+";"; if (kwhieditstyle[2].length > 0) rule += "font-weight:"+kwhieditstyle[2]+";"; if (kwhieditstyle[3].length > 0) rule += kwhieditstyle[3]+";"; document.getElementById("kwhiedittemp").innerHTML = rule + "}"; updateColorInputs(); } async function updateColorInputs(){ document.getElementById('txtcolorinput').value = '#' + ('0' + parseInt(document.getElementById("txtr").value).toString(16)).slice(-2) + ('0' + parseInt(document.getElementById("txtg").value).toString(16)).slice(-2) + ('0' + parseInt(document.getElementById("txtb").value).toString(16)).slice(-2); document.getElementById('bkgcolorinput').value = '#' + ('0' + parseInt(document.getElementById("bkgr").value).toString(16)).slice(-2) + ('0' + parseInt(document.getElementById("bkgg").value).toString(16)).slice(-2) + ('0' + parseInt(document.getElementById("bkgb").value).toString(16)).slice(-2); } async function updatecolor(e){ // duplicate colors to RBG input boxes if (e.target.id.indexOf("colorinput") > -1){ var hexcolor = e.target.value; var prefix = e.target.id.slice(0,3); document.getElementById(prefix + 'r').value = parseInt(hexcolor.slice(1,3), 16); document.getElementById(prefix + 'g').value = parseInt(hexcolor.slice(3,5), 16); document.getElementById(prefix + 'b').value = parseInt(hexcolor.slice(5,7), 16); updatestyle(document.getElementById(prefix + 'r')); } } function kwhifwchg(e){ kwhieditstyle[2] = e.target.value; setdivstyle([]); } function kwhicustom(e){ kwhieditstyle[3] = document.getElementById("kwhicustom").value; setdivstyle([]); } async function kwhimaxrestore(e){ var el = document.getElementById('kwhiedit'); if (e.target.textContent == '^'){ e.target.textContent = '_'; e.target.setAttribute('title', 'Restore normal dialog size'); el.style.left = '1px'; el.style.width = 'calc(100% - 3px - 2em)'; el.style.height = 'calc(100% - 4px - 2em)'; } else { e.target.textContent = '^'; e.target.setAttribute('title', 'Maximize dialog size'); el.style.left = ''; el.style.width = ''; el.style.height = ''; } } function cmenuFilter(e){ document.getElementById("THDenableset").setAttribute("disabled","disabled"); document.getElementById("THDenableset").setAttribute("THDtext",""); document.getElementById("THDdisableset").setAttribute("disabled","disabled"); document.getElementById("THDdisableset").setAttribute("THDset",""); var s = window.getSelection(); if (s.isCollapsed) document.getElementById("THDnewset").setAttribute("THDtext",""); else document.getElementById("THDnewset").setAttribute("THDtext",s.getRangeAt(0).toString().trim()); if (e.target.hasAttribute('txhidy15')){ document.getElementById("THDdisableset").removeAttribute("disabled"); document.getElementById("THDdisableset").setAttribute("THDset",e.target.getAttribute('txhidy15')); } else { document.getElementById("THDdisableset").setAttribute("disabled","disabled"); if (!s.isCollapsed){ document.getElementById("THDenableset").removeAttribute("disabled"); document.getElementById("THDenableset").setAttribute("THDtext",s.getRangeAt(0).toString().trim()); } } } function cmenuEnable(e){ var kw = e.target.getAttribute("THDtext").toLowerCase(); var toggled = false; for (var j = 0; j < hlkeys.length; ++j){ var hlset = hlkeys[j]; var kwlist = "|" + hlobj[hlset].keywords.toLowerCase() + "|"; if(kwlist.indexOf("|" + kw + "|") > -1){ if (hlobj[hlset].enabled == "true") break; // already enabled kwhienabledisable(hlset,true); refreshSetList(); toggled = true; break; } } if (toggled == false){ if (document.getElementById("thdtopbar").style.display != "block") editKW(); if (document.getElementById("thdtopdrop").style.display != "block") thdDropSetList(); } } function cmenuDisable(e){ kwhienabledisable(e.target.getAttribute("THDset"),false); refreshSetList(); } function cmenuNewset(e){ //TODO - if there's a selection, get it into the form kwhinewset(e,e.target.getAttribute("THDtext")); } // TESTING ONLY async function flushData(){ if (!GM4){ GM_setValue("kwstyles", ""); } else { await GM.setValue("kwstyles", ""); } }