// ==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 // @downloadURL none // ==/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 clashes 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 clashes 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

JS

" + "

Click to manage keyword/highlight sets • Add New Set

" + "

" + "

" + "
Seek
" + "
Options
" + "" + ""; 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 = ''; 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 = "

Edit/Add Keywords/Highlighting" + "  " + "" + "

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.

" + "" + "
" + "

placeholder

" + "

Match type:

Text color: " + "R: " + "G: " + "B: " + "

" + "

Background: " + "R: " + "G: " + "B:

" + "

Font-weight:

Custom:

" + "

" + "

"; 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 = '' + '' + '' + '' + '' + ''; 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, XPathResult.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, XPathResult.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(pat, '$1'); 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 = '' + hlobj[hlset].type + ''; } if (j == 0){ newrow.innerHTML = '
' + hlobj[hlset].keywords + '
' + thdtypenote + '' + ' ' + ' '; } else { newrow.innerHTML = '
' + hlobj[hlset].keywords + '
' + thdtypenote + '' + ' ' + ' '; } 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 = "Seeking: " + 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 = '

' + e.target.textContent + '

' + '

'; 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 • Add New Set"; } } } 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 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; is 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, '>'); tgtspan = parpar; } else { tgts[i].outerHTML = tgts[i].textContent.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 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", ""); } }