// ==UserScript==
// @name Text Highlight and Seek
// @author erosman and Jefferson "jscher2000" Scher
// @namespace JeffersonScher
// @version 2.0.2
// @description Automatically highlight user-defined text with Seek function (2015-10-11)
// @include https://greasyfork.org/*
// @include https://openuserjs.org/*
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_getResourceURL
// @copyright Copyright 2015 Jefferson Scher. Portions created by erosman.
// @license BSD 3-clause
// @resource mycon http://www.jeffersonscher.com/gm/src/gfrk-THS-ver202.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 Highlighter 2015 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 Highlighter runs before those scripts that clashes with it.
*/
(function() { // anonymous function wrapper, used for error checking & limiting scope
'use strict';
if (window.self !== window.top) { return; } // end execution if in a frame ; maybe this can be relaxed someday?
// sample keyword+style object to get started
var 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 : ""
}
};
var kwhieditstyle = ["rgb(0,0,255)","rgb(255,255,0)","inherit",""];
// read pref storage: keyword-style sets
var hljson = GM_getValue("kwstyles");
if (!hljson || hljson.length == 0){
var hlobj = hlobjDefault;
// check for legacy preferences
var kwold = GM_getValue("keywords");
if (kwold) if(kwold.length > 0) {
hlobj.set100.keywords = kwold.split(',').join('|');
}
var hlold = 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);
GM_setValue("kwstyles",hljson);
} else {
var hlobj = JSON.parse(hljson);
}
// global keys array
var hlkeys = Object.keys(hlobj);
// read/set other prefs
var hlbtnvis = GM_getValue("hlbtnvis");
if (!hlbtnvis){
hlbtnvis = "on";
GM_setValue("hlbtnvis",hlbtnvis);
}
var hlprecode = GM_getValue("hlprecode");
if (!hlprecode){
hlprecode = true;
GM_setValue("hlprecode",hlprecode);
}
var hlnextset = GM_getValue("hlnextset");
if (!hlnextset){
hlnextset = 101;
GM_setValue("hlnextset",hlnextset);
}
// Inject CSS
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;
}
}
}
}
insertCSS(hlkeys);
// 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;
}
}
}
}
}
}
// 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
" +
"" +
"";
document.body.appendChild(kwhibar);
// Attach event handlers
document.getElementById("thdtopkeywords").addEventListener("click",thddroptoggle,false);
document.getElementById("kwhitbod").addEventListener("click",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);
// 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");
JSBTN.src = GM_getResourceURL("mycon");
document.querySelector("#thdtopbar a").textContent = "";
document.querySelector("#thdtopbar a").appendChild(JSBTN);
// Add menu item
GM_registerMenuCommand("Show Text Highlighter Bar - View, Edit, Add Keywords and Styles", editKW);
// Inject H button
if (hlbtnvis == "off") var hbtndisp = ' style="display:none"';
else hbtndisp = '';
var dNew = document.createElement("div");
dNew.innerHTML = '';
document.body.appendChild(dNew);
document.getElementById("btnshowkwhi").addEventListener("click",editKW,false);
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();
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);
}
}
}
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"){
// 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"){
// 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();
}
}
}
function kwhienabledisable(hlsetnum,enable){
if (enable == false) {
// Update object and persist to GM storage
hlobj[hlsetnum].enabled = "false";
hljson = JSON.stringify(hlobj);
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 here to manage keyword/highlight sets";
}
}
} else {
// Update object and persist to GM storage
hlobj[hlsetnum].enabled = "true";
hljson = JSON.stringify(hlobj);
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];
// show form
document.getElementById("kwhiedit").style.display = "block";
}
function kwhiexport(e){
prompt("JSON data\nPress Ctrl+c or right-click to copy\n ", JSON.stringify(hlobj));
}
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);
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;
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);
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);
}
function kwhihbtn(e){
if (e.target.checked == false){
hlbtnvis = "off";
GM_setValue("hlbtnvis",hlbtnvis);
document.getElementById("btnshowkwhi").style.display = "none";
} else {
hlbtnvis = "on";
GM_setValue("hlbtnvis",hlbtnvis);
document.getElementById("btnshowkwhi").style.display = "";
}
}
function kwhiprecode(e){
if (e.target.checked == false){
// Update var, persist the preference, unhighlight, rehighlight
hlprecode = false;
GM_setValue("hlprecode",hlprecode);
unhighlight(null);
THmo_doHighlight(document.body);
} else {
// Update var, persist the preference, rehighlight
hlprecode = true;
GM_setValue("hlprecode",hlprecode);
THmo_doHighlight(document.body);
}
}
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!
elt.scrollIntoView();
var rect = elt.getClientRects()[0];
if (rect){ // scroll down if behind the control bar
if (rect.top < 27) window.scroll(0, window.scrollY-27);
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;
}
}
}
}
}
// Set up add/edit form
var kwhied = document.createElement("div");
kwhied.id = "kwhiedit";
kwhied.innerHTML = "";
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("stylecontrols").addEventListener("input", updatestyle, 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);
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;
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 {
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)?")){
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);
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';
}
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);
GM_setValue("kwstyles",hljson);
// Update set list, remove highlighting, close form
refreshSetList();
unhighlight(hlset);
document.getElementById('kwhiedit').style.display='none';
}
}
}
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]);
}
function updatestyle(e){
// validate value and apply change
if (e.target.id.indexOf("txt") == 0 || e.target.id.indexOf("bkg") == 0){
if (isNaN(e.target.value)){
alert("Please only use values between 0 and 255");
return;
}
if (parseInt(e.target.value) != e.target.value){
e.target.value = parseInt(e.target.value);
}
if (e.target.value < 0){
e.target.value = 0;
}
if (e.target.value > 255){
e.target.value = 255;
}
if (e.target.id.indexOf("txt") == 0) setdivstyle(["txt"]);
if (e.target.id.indexOf("bkg") == 0) setdivstyle(["bkg"]);
} else {
if (e.target.id == "kwhicustom") return;
console.log("updatestyle on "+e.target.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 + "}";
}
function kwhifwchg(e){
kwhieditstyle[2] = e.target.value;
setdivstyle([]);
}
function kwhicustom(e){
kwhieditstyle[3] = document.getElementById("kwhicustom").value;
setdivstyle([]);
}
// 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);
}
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
function flushData(){
GM_setValue("kwstyles", "");
}
GM_registerMenuCommand("TEST ONLY - flush keyword sets for Text Highlighter 2015", flushData);
})(); // end of anonymous function