Illuvitar

Vimium-style Highlight All Elements

Sep 2nd, 2025
57
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 19.05 KB | None | 0 0
  1.  
  2.  
  3. // ==UserScript==
  4. // @name Vimium Highlight All Elements - Complete Coverage
  5. // @namespace http://tampermonkey.net/
  6. // @version 3.0
  7. // @description Highlight text in ALL elements throughout the document
  8. // @author You
  9. // @match *://*/*
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. const style = document.createElement('style');
  17. style.textContent = `
  18. .vimium-highlight {
  19. background-color: #ffff00 !important;
  20. color: #000000 !important;
  21. font-weight: bold !important;
  22. padding: 0px 1px !important;
  23. border-radius: 2px !important;
  24. box-shadow: 0 0 2px rgba(0,0,0,0.2) !important;
  25. display: inline !important;
  26. }
  27. .vimium-notification {
  28. position: fixed;
  29. top: 20px;
  30. right: 20px;
  31. background: #333;
  32. color: white;
  33. padding: 8px 12px;
  34. border-radius: 4px;
  35. z-index: 10000;
  36. font-family: -apple-system, BlinkMacSystemFont, sans-serif;
  37. font-size: 13px;
  38. box-shadow: 0 3px 10px rgba(0,0,0,0.3);
  39. transition: opacity 0.3s ease;
  40. }
  41.  
  42. /* Special styling for different element types */
  43. title .vimium-highlight {
  44. /* Title tag highlights */
  45. background-color: #ff9999 !important;
  46. }
  47.  
  48. input .vimium-highlight {
  49. /* Input value highlights */
  50. background-color: #99ff99 !important;
  51. }
  52. `;
  53. document.head.appendChild(style);
  54.  
  55. function escapeRegExp(string) {
  56. return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  57. }
  58.  
  59. function highlightAllMatches(searchTerm) {
  60. clearHighlights();
  61.  
  62. if (!searchTerm.trim()) return;
  63.  
  64. const searchLower = searchTerm.toLowerCase();
  65.  
  66. // FIXED: Scan entire document, not just body
  67. const walker = document.createTreeWalker(
  68. document.documentElement, // Scan entire document including <head>
  69. NodeFilter.SHOW_TEXT,
  70. {
  71. acceptNode: function(node) {
  72. // Skip already highlighted content
  73. if (node.parentElement?.classList?.contains('vimium-highlight')) {
  74. return NodeFilter.FILTER_REJECT;
  75. }
  76.  
  77. // IMPROVED: Only exclude truly problematic elements
  78. const parentTag = node.parentElement?.tagName?.toLowerCase();
  79.  
  80. // Only skip script and style - allow everything else
  81. if (['script', 'style'].includes(parentTag)) {
  82. return NodeFilter.FILTER_REJECT;
  83. }
  84.  
  85. // IMPROVED: Check if element is visible (optional - can be disabled)
  86. const parent = node.parentElement;
  87. if (parent) {
  88. const style = window.getComputedStyle(parent);
  89. // Skip completely hidden elements
  90. if (style.display === 'none' || style.visibility === 'hidden') {
  91. return NodeFilter.FILTER_REJECT;
  92. }
  93. }
  94.  
  95. // Accept if text contains search term
  96. return node.textContent.toLowerCase().includes(searchLower) ?
  97. NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
  98. }
  99. },
  100. false
  101. );
  102.  
  103. const textNodes = [];
  104. let node;
  105.  
  106. while (node = walker.nextNode()) {
  107. textNodes.push(node);
  108. }
  109.  
  110. // IMPROVED: Also handle form element values
  111. handleFormElements(searchTerm);
  112.  
  113. // IMPROVED: Handle SVG text elements
  114. handleSVGText(searchTerm);
  115.  
  116. // Process regular text nodes
  117. let processedCount = 0;
  118. textNodes.forEach(textNode => {
  119. try {
  120. if (highlightInTextNode(textNode, searchTerm)) {
  121. processedCount++;
  122. }
  123. } catch (e) {
  124. console.warn('Error processing text node:', e, textNode);
  125. }
  126. });
  127.  
  128. const highlightCount = document.querySelectorAll('.vimium-highlight').length;
  129. const message = `Found ${textNodes.length} text nodes, processed ${processedCount}, highlighted ${highlightCount} matches for "${searchTerm}"`;
  130.  
  131. showNotification(highlightCount > 0 ?
  132. `Highlighted ${highlightCount} matches` :
  133. `No matches found for "${searchTerm}"`
  134. );
  135. console.log(message);
  136. }
  137.  
  138. // NEW: Handle form elements (input, textarea values)
  139. function handleFormElements(searchTerm) {
  140. const searchLower = searchTerm.toLowerCase();
  141.  
  142. // Handle input elements
  143. const inputs = document.querySelectorAll('input[type="text"], input[type="search"], input[type="url"], input[type="email"]');
  144. inputs.forEach(input => {
  145. if (input.value.toLowerCase().includes(searchLower)) {
  146. highlightInputValue(input, searchTerm);
  147. }
  148. });
  149.  
  150. // Handle textarea elements
  151. const textareas = document.querySelectorAll('textarea');
  152. textareas.forEach(textarea => {
  153. if (textarea.value.toLowerCase().includes(searchLower)) {
  154. highlightTextareaValue(textarea, searchTerm);
  155. }
  156. });
  157. }
  158.  
  159. // NEW: Handle SVG text elements
  160. function handleSVGText(searchTerm) {
  161. const searchLower = searchTerm.toLowerCase();
  162.  
  163. const svgTexts = document.querySelectorAll('text, tspan');
  164. svgTexts.forEach(svgText => {
  165. if (svgText.textContent.toLowerCase().includes(searchLower)) {
  166. // SVG text highlighting is more complex - add a simple background rect
  167. highlightSVGText(svgText, searchTerm);
  168. }
  169. });
  170. }
  171.  
  172. function highlightInputValue(input, searchTerm) {
  173. // For input elements, we can't directly highlight the value
  174. // Instead, we add a visual indicator or tooltip
  175. const searchLower = searchTerm.toLowerCase();
  176. const value = input.value;
  177.  
  178. if (value.toLowerCase().includes(searchLower)) {
  179. // Add a highlight class to the input
  180. input.classList.add('vimium-input-highlight');
  181.  
  182. // Store original style to restore later
  183. if (!input.dataset.originalBorder) {
  184. input.dataset.originalBorder = input.style.border;
  185. input.dataset.originalBackground = input.style.backgroundColor;
  186. }
  187.  
  188. // Apply highlight styling
  189. input.style.border = '2px solid #ffff00';
  190. input.style.backgroundColor = '#ffffcc';
  191. }
  192. }
  193.  
  194. function highlightTextareaValue(textarea, searchTerm) {
  195. // Similar to input - highlight the element itself
  196. const searchLower = searchTerm.toLowerCase();
  197. const value = textarea.value;
  198.  
  199. if (value.toLowerCase().includes(searchLower)) {
  200. textarea.classList.add('vimium-textarea-highlight');
  201.  
  202. if (!textarea.dataset.originalBorder) {
  203. textarea.dataset.originalBorder = textarea.style.border;
  204. textarea.dataset.originalBackground = textarea.style.backgroundColor;
  205. }
  206.  
  207. textarea.style.border = '2px solid #ffff00';
  208. textarea.style.backgroundColor = '#ffffcc';
  209. }
  210. }
  211.  
  212. function highlightSVGText(svgElement, searchTerm) {
  213. // For SVG, add a background rect behind the text
  214. svgElement.classList.add('vimium-svg-highlight');
  215.  
  216. // Store original style
  217. if (!svgElement.dataset.originalFill) {
  218. svgElement.dataset.originalFill = svgElement.style.fill || '';
  219. }
  220.  
  221. // Apply highlight
  222. svgElement.style.fill = '#000000';
  223.  
  224. // Try to add background (complex for SVG)
  225. const bbox = svgElement.getBBox();
  226. const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
  227. rect.setAttribute('x', bbox.x - 1);
  228. rect.setAttribute('y', bbox.y - 1);
  229. rect.setAttribute('width', bbox.width + 2);
  230. rect.setAttribute('height', bbox.height + 2);
  231. rect.setAttribute('fill', '#ffff00');
  232. rect.classList.add('vimium-svg-background');
  233.  
  234. svgElement.parentNode.insertBefore(rect, svgElement);
  235. }
  236.  
  237. function highlightInTextNode(textNode, searchTerm) {
  238. const text = textNode.textContent;
  239. const parent = textNode.parentNode;
  240.  
  241. if (!text || !parent) return false;
  242.  
  243. const searchLower = searchTerm.toLowerCase();
  244. const textLower = text.toLowerCase();
  245.  
  246. const parts = [];
  247. let lastIndex = 0;
  248. let startIndex = 0;
  249. let hasMatches = false;
  250.  
  251. while ((startIndex = textLower.indexOf(searchLower, lastIndex)) !== -1) {
  252. hasMatches = true;
  253.  
  254. // Add text before match
  255. if (startIndex > lastIndex) {
  256. parts.push({
  257. type: 'text',
  258. content: text.substring(lastIndex, startIndex)
  259. });
  260. }
  261.  
  262. // Add matched term
  263. parts.push({
  264. type: 'highlight',
  265. content: text.substring(startIndex, startIndex + searchTerm.length)
  266. });
  267.  
  268. lastIndex = startIndex + searchTerm.length;
  269. }
  270.  
  271. // Add remaining text
  272. if (lastIndex < text.length) {
  273. parts.push({
  274. type: 'text',
  275. content: text.substring(lastIndex)
  276. });
  277. }
  278.  
  279. if (hasMatches) {
  280. const fragment = document.createDocumentFragment();
  281.  
  282. parts.forEach(part => {
  283. if (part.type === 'highlight' && part.content) {
  284. const mark = document.createElement('mark');
  285. mark.className = 'vimium-highlight';
  286. mark.textContent = part.content;
  287. fragment.appendChild(mark);
  288. } else if (part.type === 'text' && part.content) {
  289. fragment.appendChild(document.createTextNode(part.content));
  290. }
  291. });
  292.  
  293. try {
  294. parent.replaceChild(fragment, textNode);
  295. return true;
  296. } catch (e) {
  297. console.warn('Error replacing text node:', e);
  298. return false;
  299. }
  300. }
  301.  
  302. return false;
  303. }
  304.  
  305. function clearHighlights() {
  306. // Clear regular text highlights
  307. const highlights = document.querySelectorAll('.vimium-highlight');
  308. let clearCount = highlights.length;
  309.  
  310. highlights.forEach(highlight => {
  311. const parent = highlight.parentNode;
  312. if (parent) {
  313. try {
  314. parent.replaceChild(document.createTextNode(highlight.textContent), highlight);
  315. parent.normalize();
  316. } catch (e) {
  317. console.warn('Error clearing highlight:', e);
  318. }
  319. }
  320. });
  321.  
  322. // Clear form element highlights
  323. const inputHighlights = document.querySelectorAll('.vimium-input-highlight');
  324. inputHighlights.forEach(input => {
  325. input.classList.remove('vimium-input-highlight');
  326. if (input.dataset.originalBorder !== undefined) {
  327. input.style.border = input.dataset.originalBorder;
  328. input.style.backgroundColor = input.dataset.originalBackground;
  329. delete input.dataset.originalBorder;
  330. delete input.dataset.originalBackground;
  331. }
  332. });
  333.  
  334. const textareaHighlights = document.querySelectorAll('.vimium-textarea-highlight');
  335. textareaHighlights.forEach(textarea => {
  336. textarea.classList.remove('vimium-textarea-highlight');
  337. if (textarea.dataset.originalBorder !== undefined) {
  338. textarea.style.border = textarea.dataset.originalBorder;
  339. textarea.style.backgroundColor = textarea.dataset.originalBackground;
  340. delete textarea.dataset.originalBorder;
  341. delete textarea.dataset.originalBackground;
  342. }
  343. });
  344.  
  345. // Clear SVG highlights
  346. const svgHighlights = document.querySelectorAll('.vimium-svg-highlight');
  347. svgHighlights.forEach(svg => {
  348. svg.classList.remove('vimium-svg-highlight');
  349. if (svg.dataset.originalFill !== undefined) {
  350. svg.style.fill = svg.dataset.originalFill;
  351. delete svg.dataset.originalFill;
  352. }
  353. });
  354.  
  355. const svgBackgrounds = document.querySelectorAll('.vimium-svg-background');
  356. svgBackgrounds.forEach(bg => bg.remove());
  357.  
  358. if (clearCount > 0 || inputHighlights.length > 0 || textareaHighlights.length > 0 || svgHighlights.length > 0) {
  359. const totalCleared = clearCount + inputHighlights.length + textareaHighlights.length + svgHighlights.length;
  360. showNotification(`Cleared ${totalCleared} highlights`);
  361. }
  362. }
  363.  
  364. function showNotification(message) {
  365. const existing = document.querySelector('.vimium-notification');
  366. if (existing) existing.remove();
  367.  
  368. const notification = document.createElement('div');
  369. notification.className = 'vimium-notification';
  370. notification.textContent = message;
  371. document.body.appendChild(notification);
  372.  
  373. setTimeout(() => {
  374. if (notification.parentNode) {
  375. notification.style.opacity = '0';
  376. setTimeout(() => {
  377. if (notification.parentNode) {
  378. notification.remove();
  379. }
  380. }, 300);
  381. }
  382. }, 3000);
  383. }
  384.  
  385. // IMPROVED: Debug function to show what elements are being scanned
  386. function debugElementScan(searchTerm) {
  387. const searchLower = searchTerm.toLowerCase();
  388. const results = {
  389. textNodes: 0,
  390. inputElements: 0,
  391. svgElements: 0,
  392. hiddenElements: 0,
  393. matchingNodes: 0
  394. };
  395.  
  396. // Scan all text nodes
  397. const walker = document.createTreeWalker(
  398. document.documentElement,
  399. NodeFilter.SHOW_TEXT,
  400. {
  401. acceptNode: function(node) {
  402. results.textNodes++;
  403.  
  404. if (node.parentElement?.classList?.contains('vimium-highlight')) {
  405. return NodeFilter.FILTER_REJECT;
  406. }
  407.  
  408. const parentTag = node.parentElement?.tagName?.toLowerCase();
  409. if (['script', 'style'].includes(parentTag)) {
  410. return NodeFilter.FILTER_REJECT;
  411. }
  412.  
  413. const parent = node.parentElement;
  414. if (parent) {
  415. const style = window.getComputedStyle(parent);
  416. if (style.display === 'none' || style.visibility === 'hidden') {
  417. results.hiddenElements++;
  418. return NodeFilter.FILTER_REJECT;
  419. }
  420. }
  421.  
  422. if (node.textContent.toLowerCase().includes(searchLower)) {
  423. results.matchingNodes++;
  424. console.log('Matching text node:', {
  425. text: node.textContent.substring(0, 50),
  426. parent: node.parentElement?.tagName,
  427. parentClass: node.parentElement?.className
  428. });
  429. return NodeFilter.FILTER_ACCEPT;
  430. }
  431.  
  432. return NodeFilter.FILTER_REJECT;
  433. }
  434. },
  435. false
  436. );
  437.  
  438. while (walker.nextNode()) {
  439. // Just iterate to trigger acceptNode
  440. }
  441.  
  442. // Check form elements
  443. const allInputs = document.querySelectorAll('input, textarea');
  444. allInputs.forEach(input => {
  445. results.inputElements++;
  446. if (input.value && input.value.toLowerCase().includes(searchLower)) {
  447. console.log('Matching form element:', {
  448. type: input.tagName,
  449. value: input.value.substring(0, 50),
  450. id: input.id,
  451. name: input.name
  452. });
  453. }
  454. });
  455.  
  456. // Check SVG elements
  457. const allSVG = document.querySelectorAll('text, tspan');
  458. allSVG.forEach(svg => {
  459. results.svgElements++;
  460. if (svg.textContent.toLowerCase().includes(searchLower)) {
  461. console.log('Matching SVG element:', {
  462. text: svg.textContent,
  463. tag: svg.tagName
  464. });
  465. }
  466. });
  467.  
  468. console.log('Debug scan results:', results);
  469. showNotification(`Debug: ${results.textNodes} text nodes, ${results.matchingNodes} matches, ${results.inputElements} form elements, ${results.svgElements} SVG elements`);
  470. }
  471.  
  472. // Keyboard bindings
  473. document.addEventListener('keydown', function(e) {
  474. if (e.ctrlKey && e.shiftKey && e.key === 'H') {
  475. e.preventDefault();
  476. e.stopPropagation();
  477.  
  478. const searchTerm = prompt('Highlight all occurrences (searches entire document):');
  479. if (searchTerm !== null) {
  480. if (searchTerm.trim()) {
  481. highlightAllMatches(searchTerm);
  482. } else {
  483. clearHighlights();
  484. }
  485. }
  486. }
  487.  
  488. if (e.ctrlKey && e.shiftKey && e.key === 'C') {
  489. e.preventDefault();
  490. e.stopPropagation();
  491. clearHighlights();
  492. }
  493.  
  494. // DEBUG: Ctrl+Shift+D for debug scan
  495. if (e.ctrlKey && e.shiftKey && e.key === 'D') {
  496. e.preventDefault();
  497. e.stopPropagation();
  498.  
  499. const searchTerm = prompt('Debug scan for term:');
  500. if (searchTerm?.trim()) {
  501. debugElementScan(searchTerm);
  502. }
  503. }
  504.  
  505. if (e.key === 'Escape') {
  506. const highlights = document.querySelectorAll('.vimium-highlight, .vimium-input-highlight, .vimium-textarea-highlight, .vimium-svg-highlight');
  507. if (highlights.length > 0) {
  508. e.preventDefault();
  509. e.stopPropagation();
  510. clearHighlights();
  511. }
  512. }
  513. });
  514.  
  515. // Debug utilities
  516. window.vimiumHighlight = {
  517. highlight: highlightAllMatches,
  518. clear: clearHighlights,
  519. debug: debugElementScan,
  520. test: function(term) {
  521. console.log('Testing comprehensive highlight for:', term);
  522. highlightAllMatches(term);
  523. }
  524. };
  525.  
  526. })();
  527.  
Tags: greasemonkey
Advertisement
Add Comment
Please, Sign In to add comment