Google & YouTube - Universal Exact Search Hotkey

Surround selected text with quotation marks using NumpadAdd key

  1. // ==UserScript==
  2. // @name Google & YouTube - Universal Exact Search Hotkey
  3. // @namespace GYESH
  4. // @version 6.0
  5. // @description Surround selected text with quotation marks using NumpadAdd key
  6. // @match *://*/*
  7. // @exclude https://www.google.ca/maps/*
  8. // @run-at document-start
  9. // @grant none
  10. // @author drhouse
  11. // @license CC-BY-NC-SA-4.0
  12. // @icon https://www.google.com/s2/favicons?sz=64&domain=google.com
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. function surroundWithQuotes(element) {
  19. if (element.isContentEditable) {
  20. const selection = window.getSelection();
  21. const range = selection.getRangeAt(0);
  22. const selectedText = range.toString();
  23. if (selectedText) {
  24. const quotedText = `"${selectedText}"`;
  25. range.deleteContents();
  26. range.insertNode(document.createTextNode(quotedText));
  27. range.setStart(range.startContainer, range.startOffset + 1);
  28. range.setEnd(range.endContainer, range.endOffset - 1);
  29. selection.removeAllRanges();
  30. selection.addRange(range);
  31. }
  32. } else {
  33. const start = element.selectionStart;
  34. const end = element.selectionEnd;
  35. const selectedText = element.value.substring(start, end);
  36. if (selectedText) {
  37. const quotedText = `"${selectedText}"`;
  38. element.value = element.value.substring(0, start) + quotedText + element.value.substring(end);
  39. element.selectionStart = start + 1;
  40. element.selectionEnd = start + quotedText.length - 1;
  41. }
  42. }
  43. }
  44.  
  45. function handleKeyPress(event) {
  46. if ((event.key === '+' && event.location === KeyboardEvent.DOM_KEY_LOCATION_NUMPAD) ||
  47. (event.key === 'Add' && event.location === KeyboardEvent.DOM_KEY_LOCATION_NUMPAD)) {
  48. let activeElement = document.activeElement;
  49. // Special handling for Google search
  50. if (window.location.hostname.includes('google.com')) {
  51. activeElement = document.querySelector('input[name="q"]') || activeElement;
  52. }
  53.  
  54. if (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA' || activeElement.isContentEditable) {
  55. surroundWithQuotes(activeElement);
  56. event.preventDefault();
  57. event.stopPropagation();
  58. }
  59. }
  60. }
  61.  
  62. function initScript() {
  63. document.addEventListener('keydown', handleKeyPress, true);
  64. // Mutation observer to handle dynamically added elements
  65. const observer = new MutationObserver((mutations) => {
  66. mutations.forEach((mutation) => {
  67. if (mutation.type === 'childList') {
  68. mutation.addedNodes.forEach((node) => {
  69. if (node.nodeType === Node.ELEMENT_NODE) {
  70. if (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA' || node.isContentEditable) {
  71. node.addEventListener('keydown', handleKeyPress, true);
  72. }
  73. }
  74. });
  75. }
  76. });
  77. });
  78.  
  79. observer.observe(document.body, { childList: true, subtree: true });
  80. }
  81.  
  82. if (document.readyState === 'loading') {
  83. document.addEventListener('DOMContentLoaded', initScript);
  84. } else {
  85. initScript();
  86. }
  87. })();