Text Highlighter - Dynamic

Highlights User-defined Text

  1. // ==UserScript==
  2. // @name Text Highlighter - Dynamic
  3. // @namespace erosman
  4. // @author erosman and Jefferson "jscher2000" Scher
  5. // @version 1.7mo
  6. // @description Highlights User-defined Text
  7. // @include https://greasyfork.org/*
  8. // @grant GM_registerMenuCommand
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // ==/UserScript==
  12.  
  13. /* --------- Note ---------
  14. This script highlights User-defined case-insensitive Text on a page.
  15.  
  16. TO INCLUDE SITES (only Greasy Fork is initially included):
  17.  
  18. Go to Add-ons - User Scripts ('Ctrl+ Shift + a' on Firefox)
  19. Click on the Script's Option
  20. Under User Settings Tab, Add Included/Excluded Pages that you want the script to run on
  21. Click OK
  22.  
  23. Setting Keywords & Highlight Style:
  24. Click on drop-down triangle next to the GreaseMonkey Icon
  25. User Scripts Commands...
  26.  
  27. Set Keywords
  28. Input keywords separated by comma
  29. Example: word 1,word 2,word 3
  30.  
  31. Set Highlight Style
  32. Input the Highlight Style (use proper CSS)
  33. Example: color: #f00; font-weight: bold; background-color: #ffe4b5;
  34.  
  35. Note: If you find that another script clashes with this script, set Text Highlighter to Execute first.
  36. Go to Add-ons - User Scripts ('Ctrl+ Shift + a' on Firefox)
  37. Right Click on the Script
  38. On the context menu click: Execute first
  39.  
  40. On Add-ons - User Scripts, you can also Click on the Execution Order (top Right) and
  41. change the execution order so that Text Highlighter runs before those scripts that clashes with it.
  42.  
  43.  
  44. --------- History ---------
  45. 1.7mo Added MutationObserver (Jefferson "jscher2000" Scher)
  46. 1.7 Changed script from matching whole words to do partial word match
  47. similar to browser's FIND + escaped RegEx Quantifiers in keywords
  48. 1.6 Code Improvement, using test()
  49. 1.5 Code Improvement
  50. 1.4 Code Improvement + Added support for non-English Words
  51. 1.3 Code Improvement, 10x speed increase
  52. 1.2 Added User Script Commands, script can now be auto-updated without losing User Data
  53. 1.1 Total Code rewrite, Xpath pattern
  54. 1.0 Initial release
  55.  
  56. */
  57.  
  58.  
  59. (function() { // anonymous function wrapper, used for error checking & limiting scope
  60. 'use strict';
  61. if (window.self !== window.top) { return; } // end execution if in a frame
  62. // setting User Preferences
  63. function setUserPref(varName, defaultVal, menuText, promtText, sep){
  64. GM_registerMenuCommand(menuText, function() {
  65. var val = prompt(promtText, GM_getValue(varName, defaultVal));
  66. if (val === null) { return; } // end execution if clicked CANCEL
  67. // prepare string of variables separated by the separator
  68. if (sep && val){
  69. var pat1 = new RegExp('\\s*' + sep + '+\\s*', 'g'); // trim space/s around separator & trim repeated separator
  70. var pat2 = new RegExp('(?:^' + sep + '+|' + sep + '+$)', 'g'); // trim starting & trailing separator
  71. val = val.replace(pat1, sep).replace(pat2, '');
  72. }
  73. val = val.replace(/\s{2,}/g, ' ').trim(); // remove multiple spaces and trim
  74. GM_setValue(varName, val);
  75. // Apply changes (immediately if there are no existing highlights, or upon reload to clear the old ones)
  76. if(!document.body.querySelector(".THmo")) THmo_doHighlight(document.body);
  77. else location.reload();
  78. });
  79. }
  80. // prepare UserPrefs
  81. setUserPref(
  82. 'keywords',
  83. 'word 1,word 2,word 3',
  84. 'Set Keywords',
  85. 'Set keywords separated by comma\t\t\t\t\t\t\t\r\n\r\nExample:\r\nword 1,word 2,word 3',
  86. ','
  87. );
  88. setUserPref(
  89. 'highlightStyle',
  90. 'color: #f00; background-color: #ffebcd;',
  91. 'Set Highlight Style',
  92. 'Set the Highlight Style (use proper CSS)\r\n\r\nExample:\r\ncolor: #f00; font-weight: bold; background-color: #ffe4b5;'
  93. );
  94. // Add MutationObserver to catch content added dynamically
  95. var THmo_MutOb = (window.MutationObserver) ? window.MutationObserver : window.WebKitMutationObserver;
  96. if (THmo_MutOb){
  97. var THmo_chgMon = new THmo_MutOb(function(mutationSet){
  98. mutationSet.forEach(function(mutation){
  99. for (var i=0; i<mutation.addedNodes.length; i++){
  100. if (mutation.addedNodes[i].nodeType == 1){
  101. THmo_doHighlight(mutation.addedNodes[i]);
  102. }
  103. }
  104. });
  105. });
  106. // attach chgMon to document.body
  107. var opts = {childList: true, subtree: true};
  108. THmo_chgMon.observe(document.body, opts);
  109. }
  110. // Main workhorse routine
  111. function THmo_doHighlight(el){
  112. var keywords = GM_getValue('keywords');
  113. if(!keywords) { return; } // end execution if not found
  114. var highlightStyle = GM_getValue('highlightStyle');
  115. if (!highlightStyle) highlightStyle = "color:#00f; font-weight:bold; background-color: #0f0;"
  116. var rQuantifiers = /[-\/\\^$*+?.()|[\]{}]/g;
  117. keywords = keywords.replace(rQuantifiers, '\\$&').split(',').join('|');
  118. var pat = new RegExp('(' + keywords + ')', 'gi');
  119. var span = document.createElement('span');
  120. // getting all text nodes with a few exceptions
  121. var snapElements = document.evaluate(
  122. './/text()[normalize-space() != "" ' +
  123. 'and not(ancestor::style) ' +
  124. 'and not(ancestor::script) ' +
  125. 'and not(ancestor::textarea) ' +
  126. 'and not(ancestor::code) ' +
  127. 'and not(ancestor::pre)]',
  128. el, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
  129. if (!snapElements.snapshotItem(0)) { return; } // end execution if not found
  130. for (var i = 0, len = snapElements.snapshotLength; i < len; i++) {
  131. var node = snapElements.snapshotItem(i);
  132. // check if it contains the keywords
  133. if (pat.test(node.nodeValue)) {
  134. // check that it isn't already highlighted
  135. if (node.className != "THmo" && node.parentNode.className != "THmo"){
  136. // create an element, replace the text node with an element
  137. var sp = span.cloneNode(true);
  138. sp.innerHTML = node.nodeValue.replace(pat, '<span style="' + highlightStyle + '" class="THmo">$1</span>');
  139. node.parentNode.replaceChild(sp, node);
  140. }
  141. }
  142. }
  143. }
  144. // first run
  145. THmo_doHighlight(document.body);
  146. })(); // end of anonymous function