Text Highlight and Seek

Automatically highlight user-defined text with Seek function (2017-08-19)

当前为 2017-12-05 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Text Highlight and Seek
  3. // @author erosman and Jefferson "jscher2000" Scher
  4. // @namespace JeffersonScher
  5. // @version 2.2.0
  6. // @description Automatically highlight user-defined text with Seek function (2017-08-19)
  7. // @include https://greasyfork.org/*
  8. // @include https://openuserjs.org/*
  9. // @grant GM_registerMenuCommand
  10. // @grant GM_setValue
  11. // @grant GM_getValue
  12. // @grant GM_getResourceURL
  13. // @copyright Copyright 2017 Jefferson Scher. Portions created by erosman.
  14. // @license BSD 3-clause
  15. // @resource mycon http://www.jeffersonscher.com/gm/src/gfrk-THS-ver220.png
  16. // ==/UserScript==
  17. var script_about = "https://greasyfork.org/scripts/13007-text-highlight-and-seek";
  18.  
  19. /* --------- Note ---------
  20. TO INCLUDE SITES (only Greasy Fork and OpenUserJS are initially included):
  21.  
  22. Go to Add-ons - User Scripts (Ctrl+Shift+a/Cmd+Shift+a on Firefox Windows/Mac)
  23. Click on the Script's Option
  24. Under User Settings Tab, Add Included/Excluded Pages that you want the script to run on
  25. Click OK
  26.  
  27. Note from erosman: If you find that another script clashes with this script, set Text Highlight and Seek to Execute first.
  28. Go to Add-ons - User Scripts ('Ctrl+ Shift + a' on Firefox)
  29. Right Click on the Script
  30. On the context menu click: Execute first
  31.  
  32. On Add-ons - User Scripts, you can also Click on the Execution Order (top Right) and
  33. change the execution order so that Text Highlight and Seek runs before those scripts that clashes with it.
  34. */
  35.  
  36. (function() { // anonymous function wrapper, used for error checking & limiting scope
  37. 'use strict';
  38.  
  39. var hlframe = GM_getValue("hlframe"); // get iframe pref
  40. if (!hlframe){
  41. hlframe = "none";
  42. GM_setValue("hlframe",hlframe);
  43. }
  44. if ((window.self !== window.top) && (hlframe != "any")) { // framed page
  45. if (hlframe == "none") return;
  46. if (hlframe == "same") {
  47. console.log(window.self.location.hostname + " vs " + window.top.location.hostname);
  48. }
  49. }
  50.  
  51. // sample keyword+style object to get started
  52. var hlobjDefault = {
  53. "set100" : {
  54. keywords : "scripts|script",
  55. type : "string",
  56. hlpat : "",
  57. textcolor : "rgb(0,0,0)",
  58. backcolor : "rgb(255,255,128)",
  59. fontweight : "inherit",
  60. custom : "",
  61. enabled : "true",
  62. visible : "true",
  63. updated : ""
  64. },
  65. "set099" : {
  66. keywords : "site",
  67. type : "word",
  68. hlpat : "",
  69. textcolor : "rgb(0,0,0)",
  70. backcolor : "rgb(255,192,255)",
  71. fontweight : "inherit",
  72. custom : "",
  73. enabled : "true",
  74. visible : "true",
  75. updated : ""
  76. },
  77. "set098" : {
  78. keywords : "^October \\d{1,2}",
  79. type : "regex",
  80. hlpat : "",
  81. textcolor : "rgb(0,0,0)",
  82. backcolor : "rgb(192,255,255)",
  83. fontweight : "inherit",
  84. custom : "",
  85. enabled : "true",
  86. visible : "true",
  87. updated : ""
  88. }
  89. };
  90. var kwhieditstyle = ["rgb(0,0,255)","rgb(255,255,0)","inherit",""];
  91.  
  92. // read pref storage: keyword-style sets
  93. var hljson = GM_getValue("kwstyles");
  94. if (!hljson || hljson.length == 0){
  95. var hlobj = hlobjDefault;
  96. // check for legacy preferences
  97. var kwold = GM_getValue("keywords");
  98. if (kwold) if(kwold.length > 0) {
  99. hlobj.set100.keywords = kwold.split(',').join('|');
  100. }
  101. var hlold = GM_getValue("highlightStyle");
  102. if (hlold) if(hlold.length > 0) {
  103. // really should try to parse this, but for now...
  104. hlobj.set100.custom = hlold;
  105. }
  106. // save starting values
  107. hljson = JSON.stringify(hlobj);
  108. GM_setValue("kwstyles",hljson);
  109. } else {
  110. var hlobj = JSON.parse(hljson);
  111. }
  112. // global keys array
  113. var hlkeys = Object.keys(hlobj);
  114.  
  115. // read/set other prefs
  116. var hlbtnvis = GM_getValue("hlbtnvis");
  117. if (!hlbtnvis){
  118. hlbtnvis = "on";
  119. GM_setValue("hlbtnvis",hlbtnvis);
  120. }
  121. var hlprecode = GM_getValue("hlprecode");
  122. if (hlprecode == undefined){
  123. hlprecode = true;
  124. GM_setValue("hlprecode",hlprecode);
  125. }
  126. var hlnextset = GM_getValue("hlnextset");
  127. if (!hlnextset){
  128. hlnextset = 101;
  129. GM_setValue("hlnextset",hlnextset);
  130. }
  131. // Inject CSS
  132. function insertCSS(setkeys){
  133. for (var j = 0; j < setkeys.length; ++j){
  134. var hlset = setkeys[j];
  135. if (hlobj[hlset].visible == "true"){
  136. var rule = "."+hlset+"{display:inline!important;";
  137. if (hlobj[hlset].textcolor.length > 0) rule += "color:"+hlobj[hlset].textcolor+";";
  138. if (hlobj[hlset].backcolor.length > 0) rule += "background-color:"+hlobj[hlset].backcolor+";";
  139. if (hlobj[hlset].fontweight.length > 0) rule += "font-weight:"+hlobj[hlset].fontweight+";";
  140. if (hlobj[hlset].custom.length > 0) rule += hlobj[hlset].custom+";";
  141. rule += "}";
  142. var setrule = document.querySelector('style[hlset="' + hlset +'"]');
  143. if (!setrule){
  144. var s = document.createElement("style");
  145. s.type = "text/css";
  146. s.setAttribute("hlset", hlset);
  147. s.appendChild(document.createTextNode(rule));
  148. document.body.appendChild(s);
  149. } else {
  150. setrule.innerHTML = rule;
  151. }
  152. }
  153. }
  154. }
  155. insertCSS(hlkeys);
  156.  
  157. // Main workhorse routine
  158. function THmo_doHighlight(el,subset){
  159. if (subset) var keyset = subset;
  160. else var keyset = hlkeys;
  161. for (var j = 0; j < keyset.length; ++j) {
  162. var hlset = keyset[j];
  163. if (hlobj[hlset].visible == "true" && hlobj[hlset].enabled == "true"){
  164. var hlkeywords = hlobj[hlset].keywords;
  165. if (hlkeywords.length > 0) {
  166. if (hlobj[hlset].type != "regex"){
  167. var rQuantifiers = /[-\/\\^$*+?.()[\]{}]/g;
  168. hlkeywords = hlkeywords.replace(rQuantifiers, '\\$&');
  169. if (hlobj[hlset].type == "word"){
  170. hlkeywords = "\\b" + hlkeywords.replace(/\|/g, "\\b|\\b") + "\\b";
  171. }
  172. }
  173. //console.log("hlset:"+hlset+"\nhlkeywords:"+hlkeywords);
  174. var pat = new RegExp('(' + hlkeywords + ')', 'gi');
  175. var span = document.createElement('thdfrag');
  176. span.setAttribute("thdcontain","true");
  177. // getting all text nodes with a few exceptions
  178. if (hlprecode){
  179. var snapElements = document.evaluate(
  180. './/text()[normalize-space() != "" ' +
  181. 'and not(ancestor::style) ' +
  182. 'and not(ancestor::script) ' +
  183. 'and not(ancestor::textarea) ' +
  184. 'and not(ancestor::div[@id="thdtopbar"]) ' +
  185. 'and not(ancestor::div[@id="kwhiedit"]) ' +
  186. 'and not(parent::thdfrag[@txhidy15])]',
  187. el, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
  188. } else {
  189. var snapElements = document.evaluate(
  190. './/text()[normalize-space() != "" ' +
  191. 'and not(ancestor::style) ' +
  192. 'and not(ancestor::script) ' +
  193. 'and not(ancestor::textarea) ' +
  194. 'and not(ancestor::pre) ' +
  195. 'and not(ancestor::code) ' +
  196. 'and not(ancestor::div[@id="thdtopbar"]) ' +
  197. 'and not(ancestor::div[@id="kwhiedit"]) ' +
  198. 'and not(parent::thdfrag[@txhidy15])]',
  199. el, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
  200. }
  201.  
  202. if (!snapElements.snapshotItem(0)) { break; }
  203.  
  204. for (var i = 0, len = snapElements.snapshotLength; i < len; i++) {
  205. var node = snapElements.snapshotItem(i);
  206. // check if it contains the keywords
  207. if (pat.test(node.nodeValue)) {
  208. // create an element, replace the text node with an element
  209. var sp = span.cloneNode(true);
  210. sp.innerHTML = node.nodeValue.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(pat, '<thdfrag class="THmo '+hlset+'" txhidy15="'+hlset+'">$1</thdfrag>');
  211. node.parentNode.replaceChild(sp, node);
  212. // try to un-nest containers
  213. if (sp.parentNode.hasAttribute("thdcontain")) sp.outerHTML = sp.innerHTML;
  214. }
  215. }
  216. }
  217. }
  218. }
  219. }
  220. // first run
  221. THmo_doHighlight(document.body,null);
  222. // Add MutationObserver to catch content added dynamically
  223. var THmo_MutOb = (window.MutationObserver) ? window.MutationObserver : window.WebKitMutationObserver;
  224. if (THmo_MutOb){
  225. var THmo_chgMon = new THmo_MutOb(function(mutationSet){
  226. mutationSet.forEach(function(mutation){
  227. for (var i=0; i<mutation.addedNodes.length; i++){
  228. if (mutation.addedNodes[i].nodeType == 1){
  229. THmo_doHighlight(mutation.addedNodes[i],null);
  230. }
  231. }
  232. });
  233. });
  234. // attach chgMon to document.body
  235. var opts = {childList: true, subtree: true};
  236. THmo_chgMon.observe(document.body, opts);
  237. }
  238.  
  239. // Set up top highlight/seek bar
  240. var kwhibar = document.createElement("div");
  241. kwhibar.id = "thdtopbar";
  242. if (hlbtnvis == "on") var btnchk = " checked=\"checked\"";
  243. else var btnchk = "";
  244. if (hlprecode) var btnprecode = " checked=\"checked\"";
  245. else var btnprecode = "";
  246. kwhibar.innerHTML = "<form id=\"thdtopform\" onsubmit=\"return false\"><p id=\"thdtopbarhome\"><a href=\"" + script_about + "\" target=\"_blank\" title=\"Go to script install page\">JS</a></p>" +
  247. "<div id=\"thdtopcurrent\"><p id=\"thdtopkeywords\" title=\"Click to View, Edit, Seek, or Add Keywords\">Click here to manage keyword/highlight sets</p>" +
  248. "<div id=\"thdtopdrop\" style=\"display:none;\"><div id=\"thdtable\"><table cellspacing=\"0\"><tbody id=\"kwhitbod\"></tbody></table></div><p><button id=\"btnkwhiadd\">Add New Set</button>" +
  249. "<span style=\"float:right\"><button id=\"btnkwhiexport\">Export Sets</button> <button id=\"btnkwhiimport\">Import Sets</button> <button id=\"thdtopdropclose\">X</button></span></p></div></div>" +
  250. "<div id=\"thdtopfindbuttons\"><button title=\"First match\" thdaction=\"f\"><b>l</b>&#x25c0;</button> <button title=\"Previous match\" thdaction=\"p\">&#x25c0;</button> <span id=\"thdseekdesc\">Seek</span> <button title=\"Next match\" thdaction=\"n\">&#x25b6;</button> <button title=\"Last match\" thdaction=\"l\">&#x25b6;<b>l</b></button><div id=\"thdseekfail\"></div></div>" +
  251. "<div id=\"thdtopoptions\"><div>Options</div><ul><li><label title=\"Float a button in the upper right corner of the document to quickly access this panel\"><input type=\"checkbox\" id=\"chkhbtn\"" + btnchk +
  252. "> Show H button</label></li><li><label title=\"Highlight matches in &lt;pre&gt; and &lt;code&gt; tags\"><input type=\"checkbox\" id=\"chkprecode\"" + btnprecode +
  253. "> Match in pre/code</label></li><li><label style=\"padding-left:4px\">Framed pages:</label><br><select id=\"hlframeselect\" size=\"3\"><option value=\"none\">No highlighting</option><option value=\"same\">Same site only</option>" +
  254. "<option value=\"any\">Any site</option></select></li><li><button id=\"btnthsreread\" title=\"Update from and apply stored settings\" disabled>Re-Read Saved Prefs</button></li></ul></div>" +
  255. "<button class=\"btnkwhiclose\" onclick=\"document.getElementById('thdtopbar').style.display='none';document.getElementById('thdtopspacer').style.display='none';return false;\" style=\"float:right\">X</button></form>" +
  256. "<style type=\"text/css\">#thdtopbar{position:fixed;top:0;left:0;height:26px;width:100%;padding:0;color:#024;background:#ddd;font-family:sans-serif;font-size:16px;line-height:16px;border-bottom:1px solid #024;z-index:2500;display:none} " +
  257. "#thdtopbar,#thdtopbar *{box-sizing:content-box;} #thdtopform{display:block;position:relative;float:left;width:100%;margin:0;border:none;} " +
  258. "#thdtopbarhome,#thdtopcurrent,#thdtopfindbuttons,#thdtopoptions{float:left;top:0;left:0;margin:0;padding:5px 8px 4px;border-right:1px solid #fff;font-size:16px;} " +
  259. "#thdtopbarhome{width:22px;text-align:center;overflow:hidden;} #thdtopbarhome a{display:block;} #thdtopbarhome a img{display:block;border:none;border-radius:3px;padding:3px;margin:-3px 0 -4px 0;background-color:#fff} " +
  260. "#thdtopfindbuttons{padding-bottom:1px;position:relative} #thdtopfindbuttons button{margin:-5px 0 -2px 0;width:28px;height:18px;color:#024;background:#f0f0f0;border:1px solid #024;border-radius:4px;padding:1px 3px;} " +
  261. "#thdtopfindbuttons button:hover{background:#ffa;} #thdseekdesc{cursor:pointer} #thdtopkeywords{margin:0;width:500px;cursor:pointer;} " +
  262. "#thdseekfail{display:none;position:absolute;top:30px;left:15px;z-index:2001;width:200px;color:#f8f8f8;background:#b00;border-radius:6px;text-align:center;font-size:12px;padding:3px}" +
  263. "#thdtopkeywords span{display:inline-block;width:100%;overflow:hidden;text-overflow:ellipsis;} #thdtable{max-height:600px;overflow-y:auto;overflow-x:hidden} " +
  264. "#thdtopdrop{position:absolute;top:26px;left:38px;width:500px;margin:0 -1px 0 -1px;padding:0 8px 8px 8px;background:#ddd;border:1px solid #024;border-top:none;border-radius:0 0 6px 6px;} " +
  265. "#thdtopdrop table{width:100%;background:#fff;border-top:1px solid #000;border-left:1px solid #000;table-layout:fixed} " +
  266. "#thdtopdrop td{padding:4px 4px; vertical-align:top;border-right:1px solid #000;border-bottom:1px solid #000;} #thdtopdrop td div{word-wrap:break-word} #thdtopdrop p{margin-top:8px;margin-bottom:0;} " +
  267. "#thdtopoptions{position:relative;width:160px;height:26px;padding:0 8px;} #thdtopoptions > div{padding:5px 0 4px;} " +
  268. "#thdtopoptions ul{position:absolute;top:26px;left:0;width:160px;margin:0 -1px 0 -1px;padding:0 8px 8px 8px;background:#ddd;border:1px solid #024;border-top:none;border-radius:0 0 6px 6px;list-style:none;} " +
  269. "#thdtopoptions li{width:100%;float:left;padding:2px 0;} #thdtopoptions ul{display:none;} #thdtopoptions:hover ul{display: block;border:1px solid #024;border-top:none;} #thdtopoptions li:hover{background:#eee;}" +
  270. ".btnkwhiclose{float:right;font-size:11px;margin-top:2px;} .thdtype{color:#ccc;float:right;font-size:12px;padding-top:8px;} #thdtopbar label{font-weight:normal;display:inline;margin:0} #hlframeselect{margin:3px 0 3px 4px;border-radius:4px}</style>";
  271. document.body.appendChild(kwhibar);
  272. // Attach event handlers
  273. document.getElementById("thdtopkeywords").addEventListener("click",thddroptoggle,false);
  274. document.getElementById("kwhitbod").addEventListener("click",kwhiformevent,false);
  275. document.getElementById("kwhitbod").addEventListener("dblclick",kwhiformevent,false);
  276. document.getElementById("btnkwhiadd").addEventListener("click",kwhinewset,false);
  277. document.getElementById("btnkwhiexport").addEventListener("click",kwhiexport,false);
  278. document.getElementById("btnkwhiimport").addEventListener("click",kwhiimport,false);
  279. document.getElementById("thdtopfindbuttons").addEventListener("click",thdseek,false);
  280. document.getElementById("chkhbtn").addEventListener("click",kwhihbtn,false);
  281. document.getElementById("chkprecode").addEventListener("click",kwhiprecode,false);
  282. document.getElementById("btnthsreread").addEventListener("click",thsreread,false);
  283. document.getElementById("thdtopdropclose").addEventListener("click",kwhitopdropclose,false);
  284. // frame options
  285. document.getElementById("hlframeselect").addEventListener("change",thsframeselect,false);
  286. setthsframeopts();
  287. // Add spacer at top of body
  288. var divsp = document.createElement("div");
  289. divsp.id = "thdtopspacer";
  290. divsp.setAttribute("style","clear:both;display:none");
  291. divsp.style.height = parseInt(27 - parseInt(window.getComputedStyle(document.body,null).getPropertyValue("margin-top"))) + "px";
  292. document.body.insertBefore(divsp, document.body.childNodes[0]);
  293. // Switch JS text to icon
  294. var JSBTN = document.createElement("img");
  295. JSBTN.src = GM_getResourceURL("mycon");
  296. document.querySelector("#thdtopbar a").textContent = "";
  297. document.querySelector("#thdtopbar a").appendChild(JSBTN);
  298. // Add menu item
  299. GM_registerMenuCommand("Show Text Highlight and Seek Bar - View, Edit, Add Keywords and Styles", editKW);
  300. // Inject H button
  301. if (hlbtnvis == "off") var hbtndisp = ' style="display:none"';
  302. else hbtndisp = '';
  303. var dNew = document.createElement("div");
  304. dNew.innerHTML = '<button id="btnshowkwhi"' + hbtndisp + '>H</button><style type="text/css">#btnshowkwhi{position:fixed;top:4px;right:4px;opacity:0.2;' +
  305. 'color:#000;background-color:#ffa;font-weight:bold;font-size:12px;border:1px solid #ccc;border-radius:4px;padding:2px 3px;z-index:1999;min-width:22px;min-height:22px}' +
  306. '#btnshowkwhi:hover{opacity:0.8}@media print{#btnshowkwhi{display:none;}}</style>';
  307. document.body.appendChild(dNew);
  308. document.getElementById("btnshowkwhi").addEventListener("click",editKW,false);
  309. function editKW(e){
  310. refreshSetList();
  311. // show form
  312. document.getElementById("thdtopbar").style.display = "block";
  313. document.getElementById("thdtopspacer").style.display = "block";
  314. }
  315. function thdDropSetList(e){
  316. refreshSetList();
  317. document.getElementById("thdtopdrop").style.display = "block";
  318. }
  319. function thddroptoggle(e){
  320. if (document.getElementById("thdtopdrop").style.display == "none") thdDropSetList();
  321. else document.getElementById("thdtopdrop").style.display = "none";
  322. }
  323. function refreshSetList(e){
  324. // clear old rows from form
  325. document.getElementById("kwhitbod").innerHTML = "";
  326. // populate data - hlobj is global
  327. for (var j = 0; j < hlkeys.length; ++j){
  328. var hlset = hlkeys[j];
  329. if (hlobj[hlset].visible == "true"){
  330. if (hlobj[hlset].enabled == "true") var strchk = ' checked=\"checked\"';
  331. else var strchk = '';
  332. var newrow = document.createElement("tr");
  333. var thdtypenote = '';
  334. newrow.setAttribute("kwhiset", hlset);
  335. if(hlobj[hlset].type != "string"){
  336. thdtypenote = '<span class="thdtype">' + hlobj[hlset].type + '</span>';
  337. }
  338. if (j == 0){
  339. newrow.innerHTML = '<td style=\"width:286px\"><div class=\"' + hlset + '\">' + hlobj[hlset].keywords + '</div>' + thdtypenote + '</td>' +
  340. '<td style=\"width:195px\"><button kwhiset=\"' + hlset + '\" title=\"Bring matches into view\">Seek</button> ' +
  341. '<button kwhiset=\"' + hlset + '\">Edit</button> <label><input type=\"checkbox\" kwhiset=\"' + hlset +
  342. '\"' + strchk + '"> Enabled </label></td>';
  343. } else {
  344. newrow.innerHTML = '<td><div class=\"' + hlset + '\">' + hlobj[hlset].keywords + '</div>' + thdtypenote + '</td>' +
  345. '<td><button kwhiset=\"' + hlset + '\" title=\"Bring matches into view\">Seek</button> ' +
  346. '<button kwhiset=\"' + hlset + '\">Edit</button> <label><input type=\"checkbox\" kwhiset=\"' + hlset +
  347. '\"' + strchk + '"> Enabled </label></td>';
  348. }
  349. document.getElementById("kwhitbod").appendChild(newrow);
  350. }
  351. }
  352. }
  353. function kwhiformevent(e){
  354. if (e.target.nodeName == "INPUT"){ // Enabled checkbox
  355. var hlsetnum = e.target.getAttribute("kwhiset");
  356. kwhienabledisable(hlsetnum, e.target.checked);
  357. }
  358. if (e.target.nodeName == "BUTTON"){ // Call up edit form or find bar
  359. var hlset = e.target.getAttribute('kwhiset');
  360. if (e.target.textContent == "Edit"){
  361. // need to cancel in-place editor if it's open
  362. kwhicancelipe(hlset);
  363. // set set number attribute
  364. document.querySelector('#kwhiedit tr').setAttribute('kwhiset', hlset);
  365. // set class for keywords
  366. document.querySelector('#kwhiedit td:nth-of-type(1) p:nth-of-type(1)').className = hlset;
  367. // enter placeholder text & type
  368. document.querySelector('#kwhiedit td:nth-of-type(1) p:nth-of-type(1)').textContent = hlobj[hlset].keywords;
  369. document.getElementById("kwhipattype").selectedIndex = 0;
  370. if (hlobj[hlset].type == "word") document.getElementById("kwhipattype").selectedIndex = 1;
  371. if (hlobj[hlset].type == "regex") document.getElementById("kwhipattype").selectedIndex = 2;
  372. // set style editing to default and override with set rules
  373. kwhieditstyle = ["rgb(0,0,255)","rgb(255,255,0)","inherit",""]; // defaults
  374. if (hlobj[hlset].textcolor.length > 0) kwhieditstyle[0] = hlobj[hlset].textcolor;
  375. if (hlobj[hlset].backcolor.length > 0) kwhieditstyle[1] = hlobj[hlset].backcolor;
  376. if (hlobj[hlset].fontweight.length > 0) kwhieditstyle[2] = hlobj[hlset].fontweight;
  377. if (hlobj[hlset].custom.length > 0) kwhieditstyle[3] = hlobj[hlset].custom;
  378. kwhiShowEditForm();
  379. }
  380. if (e.target.textContent == "Seek"){
  381. // need to cancel in-place editor if it's open
  382. kwhicancelipe(hlset);
  383. // Populate current seek set to #thdtopkeywords
  384. var divDataTD = e.target.parentNode.previousElementSibling;
  385. document.getElementById("thdtopkeywords").innerHTML = "<i>Seeking:</i> " + divDataTD.firstChild.outerHTML;
  386. // Store set to seek in #thdtopfindbuttons
  387. document.getElementById("thdtopfindbuttons").setAttribute("thdseek", hlset);
  388. // Close Keyword Sets form
  389. document.getElementById('thdtopdrop').style.display='none';
  390. // Send click event to the "seek first" button
  391. document.getElementById('thdtopfindbuttons').children[0].click();
  392. }
  393. if (e.target.textContent == "Save"){ // Check and save in-place keyword edit
  394. // get set number attribute
  395. var hlset = e.target.getAttribute("kwhiset");
  396. var kwtext = document.querySelector('div.'+hlset+' p.'+hlset).textContent;
  397. if (kwtext == hlobj[hlset].keywords){ // Nothing to save, cancel the edit
  398. kwhicancelipe(hlset);
  399. return;
  400. }
  401. // Save keyword changes WITHOUT user confirmation
  402. hlobj[hlset].prevkeyw = hlobj[hlset].keywords;
  403. hlobj[hlset].prevtype = hlobj[hlset].type;
  404. if (hlobj[hlset].type != "regex") hlobj[hlset].keywords = kwtext;
  405. else{
  406. hlobj[hlset].keywords = kwtext.replace(/\\/g, "\\");
  407. hlobj[hlset].hlpat = ""; //TODOLATER
  408. }
  409. // Set updated date/time
  410. hlobj[hlset].updated = (new Date()).toJSON();
  411. // Persist the object
  412. hljson = JSON.stringify(hlobj);
  413. GM_setValue("kwstyles",hljson);
  414. // Update CSS rule and parent form
  415. insertCSS([hlset]);
  416. refreshSetList();
  417. // Unhighlight, re-highlight, close in-place editor
  418. unhighlight(hlset);
  419. THmo_doHighlight(document.body,[hlset]);
  420. kwhicancelipe(hlset);
  421. }
  422. if (e.target.textContent == "Cancel"){ // Revert in-place editor
  423. // get set number attribute
  424. var hlset = e.target.getAttribute("kwhiset");
  425. kwhicancelipe(hlset);
  426. }
  427. if (e.target.textContent == "Revert"){ // Restore previous keywords
  428. // get set number attribute
  429. var hlset = e.target.getAttribute("kwhiset");
  430. // gray the button
  431. document.getElementById('thsrevert' + hlset).setAttribute('disabled', 'disabled');
  432. // get the previous keywords (if any)
  433. if (hlobj[hlset].prevkeyw && hlobj[hlset].prevkeyw != '') var kwtext = hlobj[hlset].prevkeyw;
  434. if (!kwtext || kwtext == ''){ // uh-oh
  435. alert('Unable to undo, sorry!');
  436. document.getElementById('thsrevert' + hlset).setAttribute('disabled', 'disabled');
  437. return;
  438. }
  439. // Save keyword changes WITHOUT user confirmation
  440. hlobj[hlset].keywords = kwtext;
  441. hlobj[hlset].type = hlobj[hlset].prevtype;
  442. hlobj[hlset].prevkeyw = '';
  443. hlobj[hlset].prevtype = '';
  444. // Set updated date/time
  445. hlobj[hlset].updated = (new Date()).toJSON();
  446. // Persist the object
  447. hljson = JSON.stringify(hlobj);
  448. GM_setValue("kwstyles",hljson);
  449. // Update CSS rule and parent form
  450. insertCSS([hlset]);
  451. refreshSetList();
  452. // Unhighlight, re-highlight
  453. unhighlight(hlset);
  454. THmo_doHighlight(document.body,[hlset]);
  455. }
  456. }
  457. if (e.type == "dblclick" && e.target.nodeName == "DIV"){ // Set up in-place quick editor
  458. if (e.target.children.length == 0) { // Ignore the double-click if the editor was already set up
  459. var hlset = e.target.className;
  460. e.target.innerHTML = '<p class="' + hlset +'" contenteditable="true" style="border:1px dotted #000">' + e.target.textContent + '</p>' +
  461. '<p style="background-color:#fff;font-size:0.8em"><button kwhiset="' + hlset + '" title="Update keywords for this set" style="font-size:0.8em">' +
  462. 'Save</button> <button kwhiset="' + hlset + '" title="Keep saved keywords" style="font-size:0.8em">Cancel</button> <button kwhiset="' +
  463. hlset + '" id="thsrevert' + hlset + '" title="Revert last edit" style="font-size:0.8em" disabled>Revert</button></p>';
  464. var rng = document.createRange();
  465. rng.selectNodeContents(e.target.children[0]);
  466. var sel = window.getSelection();
  467. sel.removeAllRanges();
  468. sel.addRange(rng);
  469. if (hlobj[hlset].prevkeyw && hlobj[hlset].prevkeyw != '') {
  470. document.getElementById('thsrevert' + hlset).removeAttribute('disabled');
  471. document.getElementById('thsrevert' + hlset).setAttribute('title','Revert to "' + hlobj[hlset].prevkeyw + '"');
  472. }
  473. }
  474. }
  475. }
  476. function kwhienabledisable(hlsetnum,enable){
  477. if (enable == false) {
  478. // Update object and persist to GM storage
  479. hlobj[hlsetnum].enabled = "false";
  480. hljson = JSON.stringify(hlobj);
  481. GM_setValue("kwstyles",hljson);
  482. // Unhighlight
  483. unhighlight(hlsetnum);
  484. // Clear seek info from bar if this set is there
  485. var seekset = document.getElementById("thdtopfindbuttons").getAttribute("thdseek");
  486. if (seekset){
  487. if(seekset.indexOf("|") > -1) seekset = seekset.split("|")[0];
  488. if (hlsetnum == seekset){
  489. document.getElementById("thdtopfindbuttons").setAttribute("thdseek","");
  490. document.getElementById("thdseekdesc").textContent = "Seek";
  491. document.getElementById("thdtopkeywords").innerHTML = "Click here to manage keyword/highlight sets";
  492. }
  493. }
  494. } else {
  495. // Update object and persist to GM storage
  496. hlobj[hlsetnum].enabled = "true";
  497. hljson = JSON.stringify(hlobj);
  498. GM_setValue("kwstyles",hljson);
  499. // Highlight
  500. THmo_doHighlight(document.body,[hlsetnum]);
  501. }
  502. }
  503. function kwhinewset(e,kwtext){ // call up new set form
  504. // set set number attribute
  505. document.querySelector('#kwhiedit tr').setAttribute('kwhiset', 'new');
  506. // clear class for keywords
  507. document.querySelector('#kwhiedit td:nth-of-type(1) p:nth-of-type(1)').className = "";
  508. // enter placeholder text & default type
  509. if (kwtext) document.querySelector('#kwhiedit td:nth-of-type(1) p:nth-of-type(1)').textContent = kwtext;
  510. else document.querySelector('#kwhiedit td:nth-of-type(1) p:nth-of-type(1)').textContent = "larry|moe|curly";
  511. document.getElementById("kwhipattype").selectedIndex = 0;
  512. // set style editing to defaults
  513. kwhieditstyle = ["rgb(0,0,255)","rgb(255,255,0)","inherit",""];
  514. kwhiShowEditForm();
  515. }
  516. function kwhiShowEditForm(){
  517. var rule = "#stylecontrols>p>span{";
  518. if (kwhieditstyle[0].length > 0) rule += "color:"+kwhieditstyle[0]+";";
  519. if (kwhieditstyle[1].length > 0) rule += "background-color:"+kwhieditstyle[1]+";";
  520. if (kwhieditstyle[2].length > 0) rule += "font-weight:"+kwhieditstyle[2]+";";
  521. if (kwhieditstyle[3].length > 0) rule += kwhieditstyle[3]+";";
  522. document.getElementById("kwhiedittemp").innerHTML = rule + "}";
  523. populateRGB("txt",kwhieditstyle[0]);
  524. populateRGB("bkg",kwhieditstyle[1]);
  525. document.getElementById("fwsel").value = kwhieditstyle[2];
  526. document.getElementById("kwhicustom").value = kwhieditstyle[3];
  527. // default the reversion button to disabled
  528. var rbtn = document.getElementById("btnkwhirevert");
  529. rbtn.setAttribute('disabled','disabled');
  530. if (rbtn.hasAttribute('kwhiset')) rbtn.removeAttribute('kwhiset');
  531. rbtn.setAttribute('title','');
  532. // show form
  533. document.getElementById("kwhiedit").style.display = "block";
  534. // check for possible reversion option
  535. var hlset = document.querySelector('#kwhiedit td:nth-of-type(1) p:nth-of-type(1)').className;
  536. if (hlset != "" && hlobj[hlset].prevkeyw && hlobj[hlset].prevkeyw != '') {
  537. rbtn.removeAttribute('disabled');
  538. rbtn.setAttribute('kwhiset',hlset);
  539. rbtn.setAttribute('title','Revert to "' + hlobj[hlset].prevkeyw + '"');
  540. }
  541. }
  542. function kwhiexport(e){
  543. prompt("JSON data\nPress Ctrl+c or right-click to copy\n ", JSON.stringify(hlobj));
  544. }
  545. function kwhiimport(e){
  546. var txtImport = prompt("Paste in the exported data and click OK to start parsing", "");
  547. try{
  548. var objImport = JSON.parse(txtImport);
  549. } catch(err){
  550. 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!");
  551. return;
  552. }
  553. var keysImport = Object.keys(objImport);
  554. // Compare for duplicate set numbers
  555. var keysString = "|" + hlkeys.join("|") + "|";
  556. var counter = 0;
  557. for (var j = 0; j < keysImport.length; ++j){
  558. if(keysString.indexOf("|"+keysImport[j]+"|") > -1) counter++;
  559. }
  560. if (counter > 0){
  561. 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");
  562. if (!arc) return;
  563. if (arc.length == 0) return;
  564. if (arc.toLowerCase() == "c") return;
  565. if (arc.toLowerCase() == "t"){
  566. if(!confirm("Total replacement has no error checking. Click OK to confirm total replacement, or click Cancel to only Replace matching sets.")) arc = "R";
  567. }
  568. } else {
  569. var arc = "A";
  570. }
  571. if (arc.toLowerCase() == "t"){ // Total replacement
  572. hlobj = JSON.parse(txtImport);
  573. // Update the global key array
  574. hlkeys = Object.keys(hlobj);
  575. // Persist the object
  576. hljson = JSON.stringify(hlobj);
  577. GM_setValue("kwstyles",hljson);
  578. // Apply to page (see below)
  579. } else {
  580. for (var j = 0; j < keysImport.length; ++j){ // Add/replace individual sets
  581. var impset = keysImport[j];
  582. if(keysString.indexOf("|"+impset+"|") > -1 && arc.toLowerCase() == "r"){ // replace
  583. var hlset = impset;
  584. hlobj[hlset].keywords = objImport[impset].keywords || "keywords|not|found";
  585. hlobj[hlset].type = objImport[impset].type || "string";
  586. hlobj[hlset].hlpat = objImport[impset].hlpat || "";
  587. hlobj[hlset].textcolor = objImport[impset].textcolor || "rgb(0,0,255)";
  588. hlobj[hlset].backcolor = objImport[impset].backcolor || "rgb(255,255,0)";
  589. hlobj[hlset].fontweight = objImport[impset].fontweight || "inherit";
  590. hlobj[hlset].custom = objImport[impset].custom || "";
  591. hlobj[hlset].enabled = objImport[impset].enabled || "true";
  592. hlobj[hlset].visible = objImport[impset].visible || "true";
  593. hlobj[hlset].updated = (new Date()).toJSON();
  594. } else { // add
  595. if(keysString.indexOf("|"+impset+"|") > -1 && arc.toLowerCase() == "a"){
  596. // create a new set number instead
  597. var hlset = "set" + hlnextset;
  598. hlnextset += 1;
  599. GM_setValue("hlnextset",hlnextset);
  600. } else {
  601. var hlset = impset;
  602. }
  603. // add the set
  604. hlobj[hlset] = {
  605. keywords : objImport[impset].keywords || "keywords|not|found",
  606. type: objImport[impset].type || "string",
  607. hlpat : objImport[impset].hlpat || "",
  608. textcolor : objImport[impset].textcolor || "rgb(0,0,255)",
  609. backcolor : objImport[impset].backcolor || "rgb(255,255,0)",
  610. fontweight : objImport[impset].fontweight || "inherit",
  611. custom : objImport[impset].custom || "",
  612. enabled : objImport[impset].enabled || "true",
  613. visible : objImport[impset].visible || "true",
  614. updated : objImport[impset].updated || ""
  615. }
  616. }
  617. // Update the global key array
  618. hlkeys = Object.keys(hlobj);
  619. // Persist the object
  620. hljson = JSON.stringify(hlobj);
  621. GM_setValue("kwstyles",hljson);
  622. }
  623. }
  624. // TODO: Could an error prevent reaching this point, for example, if the import object is missing properties due to bad editing?
  625. // Update CSS rule and command bar list
  626. insertCSS(hlkeys);
  627. refreshSetList();
  628. // Unhighlight all, re-highlight all, close dialog
  629. unhighlight(null);
  630. THmo_doHighlight(document.body);
  631. }
  632. function kwhihbtn(e){
  633. if (e.target.checked == false){
  634. hlbtnvis = "off";
  635. GM_setValue("hlbtnvis",hlbtnvis);
  636. document.getElementById("btnshowkwhi").style.display = "none";
  637. } else {
  638. hlbtnvis = "on";
  639. GM_setValue("hlbtnvis",hlbtnvis);
  640. document.getElementById("btnshowkwhi").style.display = "";
  641. }
  642. }
  643. function kwhiprecode(e){
  644. if (e.target.checked == false){
  645. // Update var, persist the preference, unhighlight, rehighlight
  646. hlprecode = false;
  647. GM_setValue("hlprecode",hlprecode);
  648. unhighlight(null);
  649. THmo_doHighlight(document.body);
  650. } else {
  651. // Update var, persist the preference, rehighlight
  652. hlprecode = true;
  653. GM_setValue("hlprecode",hlprecode);
  654. THmo_doHighlight(document.body);
  655. }
  656. }
  657. function kwhicancelipe(setno){
  658. // clean up in-place editor(s)
  659. if (setno && setno != ''){
  660. var kwdiv = document.querySelector('#kwhitbod .'+setno);
  661. if (kwdiv){
  662. kwdiv.innerHTML = hlobj[setno].keywords;
  663. return;
  664. }
  665. } else { // Check 'em all
  666. var divs = document.querySelector('#kwhitbod div');
  667. for (var n=0; n<divs.length; n++){
  668. if (divs[n].children.length > 0 && divs[n].className != '') kwhicancelipe(divs[n].className);
  669. }
  670. }
  671. }
  672. function kwhitopdropclose(e){
  673. kwhicancelipe('');
  674. document.getElementById('thdtopdrop').style.display='none';
  675. }
  676. function thsreread(e){
  677. //TODO
  678. }
  679. function thsframeselect(e){
  680. var selopt = e.target.options[e.target.selectedIndex].value;
  681. if (hlframe != selopt) {
  682. hlframe = selopt;
  683. GM_setValue("hlframe",hlframe);
  684. setthsframeopts();
  685. }
  686. }
  687. function setthsframeopts(){
  688. var sel = document.getElementById("hlframeselect");
  689. if (hlframe == "none"){
  690. sel.options[0].selected = true;
  691. sel.options[0].setAttribute("selected","selected");
  692. } else {
  693. sel.options[0].selected = false;
  694. if (sel.options[0].hasAttribute("selected")) sel.options[0].removeAttribute("selected");
  695. }
  696. if (hlframe == "same"){
  697. sel.options[1].selected = true;
  698. sel.options[1].setAttribute("selected","selected");
  699. } else {
  700. sel.options[1].selected = false;
  701. if (sel.options[1].hasAttribute("selected")) sel.options[1].removeAttribute("selected");
  702. }
  703. if (hlframe == "any"){
  704. sel.options[2].selected = true;
  705. sel.options[2].setAttribute("selected","selected");
  706. } else {
  707. sel.options[2].selected = false;
  708. if (sel.options[2].hasAttribute("selected")) sel.options[2].removeAttribute("selected");
  709. }
  710. }
  711. function thdseek(e){
  712. if (e.target.nodeName == "DIV") return; // ignore background clicks
  713. var seekset = e.currentTarget.getAttribute("thdseek");
  714. if (!seekset){ // user needs to select a set to seek in
  715. thdDropSetList();
  716. } else {
  717. var seekparams = seekset.split("|");
  718. var seekmatches = document.querySelectorAll('thdfrag[txhidy15="'+seekparams[0]+'"]');
  719. // Update or add total size of set; FIGURE OUT LATER: what if this changed??
  720. seekparams[1] = seekmatches.length;
  721. if (seekmatches.length > 0){
  722. if (e.target.nodeName == "SPAN"){ // re-scroll to the current reference
  723. thdshow(seekmatches[parseInt(seekparams[2])]);
  724. } else { // BUTTON
  725. var seekaction = e.target.getAttribute("thdaction");
  726. if (!seekaction) seekaction = "f";
  727. if (seekparams.length == 3){ // User has seeked in this set
  728. switch (seekaction){
  729. case "f":
  730. seekparams[2] = 0;
  731. var rtn = thdshow(seekmatches[parseInt(seekparams[2])]);
  732. if (rtn == false) seekagain("n");
  733. break;
  734. case "p":
  735. if (parseInt(seekparams[2]) > 0) {
  736. seekparams[2] = parseInt(seekparams[2]) - 1;
  737. var rtn = thdshow(seekmatches[parseInt(seekparams[2])]);
  738. if (rtn == false){
  739. if (parseInt(seekparams[2]) > 0) seekagain("p");
  740. else seekfailnotc("No previous match visible");
  741. }
  742. } else {
  743. seekfailnotc("Already reached first match");
  744. }
  745. break;
  746. case "n":
  747. if (parseInt(seekparams[2]) < (seekmatches.length-1)) {
  748. seekparams[2] = parseInt(seekparams[2]) + 1;
  749. var rtn = thdshow(seekmatches[parseInt(seekparams[2])]);
  750. if (rtn == false){
  751. if (parseInt(seekparams[2]) < (seekmatches.length-1)) seekagain("n");
  752. else seekfailnotc("No later match visible");
  753. }
  754. } else {
  755. seekparams[2] = (seekmatches.length-1); // in case it's too high, fix that here
  756. seekfailnotc("Already reached last match");
  757. }
  758. break;
  759. case "l":
  760. seekparams[2] = (seekmatches.length-1);
  761. var rtn = thdshow(seekmatches[parseInt(seekparams[2])]);
  762. if (rtn == false) seekagain("p");
  763. break;
  764. }
  765. } else {
  766. seekparams[2] = 0;
  767. thdshow(seekmatches[parseInt(seekparams[2])]);
  768. }
  769. document.getElementById("thdtopfindbuttons").setAttribute("thdseek", seekparams.join("|"));
  770. document.getElementById("thdseekdesc").textContent = (parseInt(seekparams[2])+1) + " of " + seekparams[1];
  771. }
  772. } else {
  773. document.getElementById("thdseekdesc").textContent = "0 of 0";
  774. }
  775. }
  776. }
  777. function thdshow(elt){ // this could be much prettier with animation!
  778. elt.scrollIntoView();
  779. var rect = elt.getClientRects()[0];
  780. if (rect){ // scroll down if behind the control bar
  781. if (rect.top < 27) window.scroll(0, window.scrollY-27);
  782. return true;
  783. } else { // match is not visible
  784. return false;
  785. }
  786. }
  787. function seekagain(dir){
  788. switch (dir){
  789. case "p":
  790. seekfailnotc("Hidden, trying previous match...");
  791. window.setTimeout(function(){document.querySelector('button[thdaction="p"]').click();},250);
  792. break;
  793. case "n":
  794. seekfailnotc("Hidden, trying next match...");
  795. window.setTimeout(function(){document.querySelector('button[thdaction="n"]').click();},250);
  796. break;
  797. }
  798. }
  799. var evttimer;
  800. function seekfailnotc(txt){
  801. var sfdiv = document.getElementById("thdseekfail");
  802. sfdiv.textContent = txt;
  803. sfdiv.style.display = "block";
  804. if (evttimer) window.clearTimeout(evttimer);
  805. evttimer = window.setTimeout(function(){document.getElementById("thdseekfail").style.display="none";}, 800);
  806. }
  807. function unhighlight(setnum){
  808. if (setnum) var tgts = document.querySelectorAll('thdfrag[txhidy15="' + setnum + '"]');
  809. else var tgts = document.querySelectorAll('thdfrag[txhidy15]'); // remove ALL
  810. for (var i=0; i<tgts.length; i++){
  811. // Check for co-extant parent(s) to remove potentially stranded <span>s
  812. var parnode = tgts[i].parentNode, parpar = parnode.parentNode, tgtspan;
  813. if (parnode.hasAttribute("thdcontain") && parnode.innerHTML == tgts[i].outerHTML){
  814. parnode.outerHTML = tgts[i].textContent.replace(/</g, '&lt;').replace(/>/g, '&gt;');
  815. tgtspan = parpar;
  816. } else {
  817. tgts[i].outerHTML = tgts[i].textContent.replace(/</g, '&lt;').replace(/>/g, '&gt;');
  818. tgtspan = parnode;
  819. }
  820. tgtspan.normalize();
  821. if (tgtspan.hasAttribute("thdcontain")){
  822. parnode = tgtspan.parentNode;
  823. if (parnode){
  824. if (parnode.hasAttribute("thdcontain") && parnode.innerHTML == tgtspan.outerHTML && tgtspan.querySelectorAll('thdfrag[txhidy15]').length == 0){
  825. parnode.outerHTML = tgtspan.innerHTML;
  826. } else if (parnode.innerHTML == tgtspan.outerHTML && tgtspan.querySelectorAll('thdfrag[txhidy15]').length == 0) {
  827. parnode.innerHTML = tgtspan.innerHTML;
  828. }
  829. }
  830. }
  831. }
  832. }
  833. // Set up add/edit form
  834. var kwhied = document.createElement("div");
  835. kwhied.id = "kwhiedit";
  836. kwhied.innerHTML = "<form onsubmit=\"return false;\"><p style=\"margin-top:0\"><b>Edit/Add Keywords/Highlighting</b>" +
  837. "<button class=\"btnkwhiclose\" onclick=\"document.getElementById('kwhiedit').style.display='none'; return false;\">X</button>" +
  838. "</p><p>List longer forms of a word first to match both in full. Example: \"children|child\" will highlight both, but \"child|children\" " +
  839. "will only highlight child, it won't expand the selection to children.</p>" +
  840. "<table cellspacing=\"0\" style=\"table-layout:fixed\"><tbody><tr kwhiset=\"new\"><td style=\"width:45%\">" +
  841. "<p contenteditable=\"true\" style=\"border:1px dotted #000;word-wrap:break-word;display:block!important\" class=\"\">placeholder</p>" +
  842. "<p style=\"margin-top:2em\">Match type: <select id=\"kwhipattype\"><option value=\"string\" selected>Anywhere in a word</option>" +
  843. "<option value=\"word\">\"Whole\" words only</option><option value=\"regex\">Regular Expression (advanced)</option></select></p></td>" +
  844. "<td style=\"width:55%\" id=\"stylecontrols\"><p><span>Text color:</span> R:<input id=\"txtr\" type=\"number\" min=\"0\" max=\"255\" step=\"1\" " +
  845. "style=\"width:4em\" value=\"0\"> G:<input id=\"txtg\" type=\"number\" min=\"0\" max=\"255\" step=\"1\" " +
  846. "style=\"width:4em\" value=\"0\"> B:<input id=\"txtb\" type=\"number\" min=\"0\" max=\"255\" step=\"1\" " +
  847. "style=\"width:4em\" value=\"0\"> <button id=\"btntxtreset\">Reset</button></p><p><span>Background:</span> R:<input id=\"bkgr\" " +
  848. "type=\"number\" min=\"0\" max=\"255\" step=\"1\" style=\"width:4em\" value=\"255\"> G:<input id=\"bkgg\" " +
  849. "type=\"number\" min=\"0\" max=\"255\" step=\"1\" style=\"width:4em\" value=\"255\"> B:<input id=\"bkgb\" " +
  850. "type=\"number\" min=\"0\" max=\"255\" step=\"1\" style=\"width:4em\" value=\"128\"> <button id=\"btnbkgreset\">Reset</button>" +
  851. "</p><p><span>Font-weight:</span> <select id=\"fwsel\"><option value=\"inherit\" selected>inherit</option>" +
  852. "<option value=\"bold\"><b>bold</b></option><option value=\"normal\">not bold</option></select></p><p><span>Custom:</span> <input type=\"text\" " +
  853. "id=\"kwhicustom\" style=\"width:55%\"> <button id=\"kwhicustomapply\">Apply</button></p></td></tr></tbody></table>" +
  854. "<p><button id=\"btnkwhisave\">Save Changes</button> <button id=\"btnkwhicancel\">Discard Changes</button> " +
  855. "<button id=\"btnkwhiremove\">Hide Set</button> <button id=\"btnkwhirevert\" disabled>Revert Last Keyword Edit</button></p></form><style type=\"text/css\">" +
  856. "#kwhiedit{position:fixed;top:1px;left:150px;width:800px;height:400px;border:1px solid #000;border-radius:6px;padding:1em;color:#000;" +
  857. "background:#fafafa;z-index:2501;display:none} #kwhiedit table{width:100%;background:#fff;border-top:1px solid #000;" +
  858. "border-left:1px solid #000;} #kwhiedit td{padding:0 16px; vertical-align:top;border-right:1px solid #000;border-bottom:1px solid #000;}" +
  859. "#stylecontrols>p>span{display:inline-block;width:6.5em;}</style><style type=\"text/css\" id=\"kwhiedittemp\"></style></div>";
  860. document.body.appendChild(kwhied);
  861. // Attach event handlers
  862. document.getElementById("btnkwhisave").addEventListener("click",kwhisavechg,false);
  863. document.getElementById("btnkwhicancel").addEventListener("click",kwhicancel,false);
  864. document.getElementById("btnkwhiremove").addEventListener("click",kwhiremove,false);
  865. document.getElementById("btnkwhirevert").addEventListener("click",kwhirevert,false);
  866. document.getElementById("stylecontrols").addEventListener("input",updatestyle,false);
  867. document.getElementById("btntxtreset").addEventListener("click",kwhicolorreset,false);
  868. document.getElementById("btnbkgreset").addEventListener("click",kwhicolorreset,false);
  869. document.getElementById("fwsel").addEventListener("change",kwhifwchg,false);
  870. document.getElementById("kwhicustomapply").addEventListener("click",kwhicustom,false);
  871.  
  872. function kwhisavechg(e){
  873. // Update object, regenerate CSS if applicable, apply to document
  874. var hlset = document.querySelector('#kwhiedit td:nth-of-type(1) p:nth-of-type(1)').className;
  875. var kwtext = document.querySelector('#kwhiedit td:nth-of-type(1) p:nth-of-type(1)').textContent;
  876. if (hlset == ""){
  877. // create a new set number
  878. var hlset = "set" + hlnextset;
  879. hlnextset += 1;
  880. GM_setValue("hlnextset",hlnextset);
  881. // add the set
  882. if (document.getElementById("kwhipattype").value == "regex"){
  883. kwtext = kwtext.replace(/\\/g, "\\");
  884. var hlpattxt = ""; //TODOLATER
  885. } else {
  886. var hlpattxt = "";
  887. }
  888. hlobj[hlset] = {
  889. keywords : kwtext,
  890. type : document.getElementById("kwhipattype").value,
  891. hlpat : hlpattxt,
  892. textcolor : kwhieditstyle[0],
  893. backcolor : kwhieditstyle[1],
  894. fontweight : kwhieditstyle[2],
  895. custom : kwhieditstyle[3],
  896. enabled : "true",
  897. visible : "true",
  898. updated : ""
  899. }
  900. // Update the global key array
  901. hlkeys = Object.keys(hlobj);
  902. } else {
  903. var oldtype = hlobj[hlset].type;
  904. hlobj[hlset].type = document.getElementById("kwhipattype").value;
  905. // Save keyword changes after user confirmation
  906. if (kwtext != hlobj[hlset].keywords){
  907. if (confirm("Save updated keywords (and other changes)?")){
  908. hlobj[hlset].prevkeyw = hlobj[hlset].keywords;
  909. hlobj[hlset].prevtype = oldtype;
  910. if (hlobj[hlset].type != "regex"){
  911. hlobj[hlset].keywords = kwtext;
  912. } else {
  913. hlobj[hlset].keywords = kwtext.replace(/\\/g, "\\");
  914. hlobj[hlset].hlpat = ""; //TODOLATER
  915. }
  916. } else return;
  917. }
  918. // Save style changes without confirmation
  919. hlobj[hlset].textcolor = kwhieditstyle[0];
  920. hlobj[hlset].backcolor = kwhieditstyle[1];
  921. hlobj[hlset].fontweight = kwhieditstyle[2];
  922. hlobj[hlset].custom = kwhieditstyle[3];
  923. // Set updated date/time
  924. hlobj[hlset].updated = (new Date()).toJSON();
  925. }
  926. // Persist the object
  927. hljson = JSON.stringify(hlobj);
  928. GM_setValue("kwstyles",hljson);
  929. // Update CSS rule and parent form
  930. insertCSS([hlset]);
  931. refreshSetList();
  932. // Unhighlight, re-highlight, close dialog
  933. unhighlight(hlset);
  934. THmo_doHighlight(document.body,[hlset])
  935. document.getElementById('kwhiedit').style.display='none';
  936. }
  937. function kwhicancel(e){
  938. // Close dialog (fields will be refresh if it is opened again)
  939. document.getElementById('kwhiedit').style.display='none';
  940. }
  941. function kwhiremove(e){
  942. var hlset = document.querySelector('#kwhiedit td:nth-of-type(1) p:nth-of-type(1)').className;
  943. if (hlset == ""){
  944. alert("This set has not been saved and therefore does not need to be hidden, you can just close the dialog to discard it.");
  945. } else {
  946. if (confirm("Are you sure you want to hide this set instead of editing it to your own liking?")){
  947. hlobj[hlset].visible = "false";
  948. hlobj[hlset].updated = (new Date()).toJSON();
  949. // Persist the object
  950. hljson = JSON.stringify(hlobj);
  951. GM_setValue("kwstyles",hljson);
  952. // Update set list, remove highlighting, close form
  953. refreshSetList();
  954. unhighlight(hlset);
  955. document.getElementById('kwhiedit').style.display='none';
  956. }
  957. }
  958. }
  959. function kwhirevert(e){
  960. // get set number attribute
  961. var hlset = e.target.getAttribute("kwhiset");
  962. // gray the button
  963. e.target.setAttribute('disabled', 'disabled');
  964. // get the previous keywords (if any)
  965. if (hlobj[hlset].prevkeyw && hlobj[hlset].prevkeyw != '') var kwtext = hlobj[hlset].prevkeyw;
  966. if (!kwtext || kwtext == ''){ // uh-oh
  967. alert('Unable to undo, sorry!');
  968. return;
  969. }
  970. // Save keyword changes WITHOUT user confirmation
  971. hlobj[hlset].keywords = kwtext;
  972. hlobj[hlset].type = hlobj[hlset].prevtype;
  973. hlobj[hlset].prevkeyw = '';
  974. hlobj[hlset].prevtype = '';
  975. // Set updated date/time
  976. hlobj[hlset].updated = (new Date()).toJSON();
  977. // Persist the object
  978. hljson = JSON.stringify(hlobj);
  979. GM_setValue("kwstyles",hljson);
  980. // Update CSS rule and parent form
  981. insertCSS([hlset]);
  982. refreshSetList();
  983. // Unhighlight, re-highlight
  984. unhighlight(hlset);
  985. THmo_doHighlight(document.body,[hlset]);
  986. // Refresh the keywords and type
  987. document.querySelector('#kwhiedit td:nth-of-type(1) p:nth-of-type(1)').textContent = hlobj[hlset].keywords;
  988. document.getElementById("kwhipattype").selectedIndex = 0;
  989. if (hlobj[hlset].type == "word") document.getElementById("kwhipattype").selectedIndex = 1;
  990. if (hlobj[hlset].type == "regex") document.getElementById("kwhipattype").selectedIndex = 2;
  991. }
  992. function kwhicolorreset(e){
  993. // what set is this?
  994. var set = document.querySelector('#kwhiedit tr').getAttribute('kwhiset');
  995. // check which button, reset the RGB
  996. if (e.target.id == "btntxtreset"){
  997. if (set == "new"){
  998. kwhieditstyle[0] = "rgb(0,0,255)";
  999. } else {
  1000. kwhieditstyle[0] = hlobj[set].textcolor;
  1001. }
  1002. populateRGB("txt",kwhieditstyle[0]);
  1003. setdivstyle(["txt"]);
  1004. }
  1005. if (e.target.id == "btnbkgreset"){
  1006. if (set == "new"){
  1007. kwhieditstyle[1] = "rgb(255,255,0)";
  1008. } else {
  1009. kwhieditstyle[1] = hlobj[set].backcolor;
  1010. }
  1011. populateRGB("bkg",kwhieditstyle[1]);
  1012. setdivstyle(["bkg"]);
  1013. }
  1014. e.target.blur();
  1015. }
  1016. function populateRGB(prop,stylestring){
  1017. var rgbvals = stylestring.substr(stylestring.indexOf("(")+1);
  1018. rgbvals = rgbvals.substr(0,rgbvals.length-1).split(",");
  1019. document.getElementById(prop+"r").value = parseInt(rgbvals[0]);
  1020. document.getElementById(prop+"g").value = parseInt(rgbvals[1]);
  1021. document.getElementById(prop+"b").value = parseInt(rgbvals[2]);
  1022. }
  1023. function updatestyle(e){
  1024. // validate value and apply change
  1025. if (e.target.id.indexOf("txt") == 0 || e.target.id.indexOf("bkg") == 0){
  1026. if (isNaN(e.target.value)){
  1027. alert("Please only use values between 0 and 255");
  1028. return;
  1029. }
  1030. if (parseInt(e.target.value) != e.target.value){
  1031. e.target.value = parseInt(e.target.value);
  1032. }
  1033. if (e.target.value < 0){
  1034. e.target.value = 0;
  1035. }
  1036. if (e.target.value > 255){
  1037. e.target.value = 255;
  1038. }
  1039. if (e.target.id.indexOf("txt") == 0) setdivstyle(["txt"]);
  1040. if (e.target.id.indexOf("bkg") == 0) setdivstyle(["bkg"]);
  1041. } else {
  1042. if (e.target.id == "kwhicustom") return;
  1043. console.log("updatestyle on "+e.target.id);
  1044. }
  1045. }
  1046. function setdivstyle(props){
  1047. for (var i=0; i<props.length; i++){
  1048. switch (props[i]){
  1049. case "txt":
  1050. kwhieditstyle[0] = "rgb(" + document.getElementById("txtr").value + "," +
  1051. document.getElementById("txtg").value + "," + document.getElementById("txtb").value + ")";
  1052. break;
  1053. case "bkg":
  1054. kwhieditstyle[1] = "rgb(" + document.getElementById("bkgr").value + "," +
  1055. document.getElementById("bkgg").value + "," + document.getElementById("bkgb").value + ")";
  1056. break;
  1057. default:
  1058. console.log("default?");
  1059. }
  1060. }
  1061. var rule = "#stylecontrols>p>span{";
  1062. if (kwhieditstyle[0].length > 0) rule += "color:"+kwhieditstyle[0]+";";
  1063. if (kwhieditstyle[1].length > 0) rule += "background-color:"+kwhieditstyle[1]+";";
  1064. if (kwhieditstyle[2].length > 0) rule += "font-weight:"+kwhieditstyle[2]+";";
  1065. if (kwhieditstyle[3].length > 0) rule += kwhieditstyle[3]+";";
  1066. document.getElementById("kwhiedittemp").innerHTML = rule + "}";
  1067. }
  1068. function kwhifwchg(e){
  1069. kwhieditstyle[2] = e.target.value;
  1070. setdivstyle([]);
  1071. }
  1072. function kwhicustom(e){
  1073. kwhieditstyle[3] = document.getElementById("kwhicustom").value;
  1074. setdivstyle([]);
  1075. }
  1076. // Context menu options -- do not replace any existing menu!
  1077. if (!document.body.hasAttribute("contextmenu") && "contextMenu" in document.documentElement){
  1078. var cmenu = document.createElement("menu");
  1079. cmenu.id = "THDcontext";
  1080. cmenu.setAttribute("type", "context");
  1081. cmenu.innerHTML = '<menu label="Text Highlight and Seek">' +
  1082. '<menuitem id="THDshowbar" label="Show bar"></menuitem>' +
  1083. '<menuitem id="THDenableset" label="Enable matching set"></menuitem>' +
  1084. '<menuitem id="THDdisableset" label="Disable this set"></menuitem>' +
  1085. '<menuitem id="THDnewset" label="Add new set"></menuitem>' +
  1086. '</menu>';
  1087. document.body.appendChild(cmenu);
  1088. document.getElementById("THDshowbar").addEventListener("click",editKW,false);
  1089. document.getElementById("THDenableset").addEventListener("click",cmenuEnable,false);
  1090. document.getElementById("THDdisableset").addEventListener("click",cmenuDisable,false);
  1091. document.getElementById("THDnewset").addEventListener("click",cmenuNewset,false);
  1092. // attach menu and create event for filtering
  1093. document.body.setAttribute("contextmenu", "THDcontext");
  1094. document.body.addEventListener("contextmenu",cmenuFilter,false);
  1095. }
  1096. function cmenuFilter(e){
  1097. document.getElementById("THDenableset").setAttribute("disabled","disabled");
  1098. document.getElementById("THDenableset").setAttribute("THDtext","");
  1099. document.getElementById("THDdisableset").setAttribute("disabled","disabled");
  1100. document.getElementById("THDdisableset").setAttribute("THDset","");
  1101. var s = window.getSelection();
  1102. if (s.isCollapsed) document.getElementById("THDnewset").setAttribute("THDtext","");
  1103. else document.getElementById("THDnewset").setAttribute("THDtext",s.getRangeAt(0).toString().trim());
  1104. if (e.target.hasAttribute('txhidy15')){
  1105. document.getElementById("THDdisableset").removeAttribute("disabled");
  1106. document.getElementById("THDdisableset").setAttribute("THDset",e.target.getAttribute('txhidy15'));
  1107. } else {
  1108. document.getElementById("THDdisableset").setAttribute("disabled","disabled");
  1109. if (!s.isCollapsed){
  1110. document.getElementById("THDenableset").removeAttribute("disabled");
  1111. document.getElementById("THDenableset").setAttribute("THDtext",s.getRangeAt(0).toString().trim());
  1112. }
  1113. }
  1114. }
  1115. function cmenuEnable(e){
  1116. var kw = e.target.getAttribute("THDtext").toLowerCase();
  1117. var toggled = false;
  1118. for (var j = 0; j < hlkeys.length; ++j){
  1119. var hlset = hlkeys[j];
  1120. var kwlist = "|" + hlobj[hlset].keywords.toLowerCase() + "|";
  1121. if(kwlist.indexOf("|" + kw + "|") > -1){
  1122. if (hlobj[hlset].enabled == "true") break; // already enabled
  1123. kwhienabledisable(hlset,true);
  1124. refreshSetList();
  1125. toggled = true;
  1126. break;
  1127. }
  1128. }
  1129. if (toggled == false){
  1130. if (document.getElementById("thdtopbar").style.display != "block") editKW();
  1131. if (document.getElementById("thdtopdrop").style.display != "block") thdDropSetList();
  1132. }
  1133. }
  1134. function cmenuDisable(e){
  1135. kwhienabledisable(e.target.getAttribute("THDset"),false);
  1136. refreshSetList();
  1137. }
  1138. function cmenuNewset(e){
  1139. //TODO - if there's a selection, get it into the form
  1140. kwhinewset(e,e.target.getAttribute("THDtext"));
  1141. }
  1142.  
  1143. // TESTING ONLY
  1144. function flushData(){
  1145. GM_setValue("kwstyles", "");
  1146. }
  1147. GM_registerMenuCommand("TEST ONLY - flush keyword sets for Text Highlight and Seek", flushData);
  1148.  
  1149. })(); // end of anonymous function