// ==UserScript==
// @name Text Highlight and Seek
// @author erosman and Jefferson "jscher2000" Scher
// @namespace JeffersonScher
// @version 2.0.3
// @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-ver203.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.
*/
(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 == undefined){
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 Highlight and Seek 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 Highlight and Seek", flushData);
})(); // end of anonymous function