🏠 Home 

คุณต้องเข้าสู่ระบบหรือลงทะเบียนก่อนดำเนินการต่อ

Text Highlight and Seek

Automatically highlight user-defined text with Seek function (2019-09-22)


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