Jump to Text

Adds single-key hotkeys that jump to specific text (or anchors) on a page.

目前为 2020-05-01 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Jump to Text
  3. // @namespace https://github.com/theborg3of5/Userscripts/
  4. // @version 1.4
  5. // @description Adds single-key hotkeys that jump to specific text (or anchors) on a page.
  6. // @author Gavin Borg
  7. // @require https://greasyfork.org/scripts/28536-gm-config/code/GM_config.js?version=184529
  8. // @match https://greasyfork.org/en/scripts/395551-jump-to-text
  9. // @grant GM_registerMenuCommand
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // ==/UserScript==
  13.  
  14. var configOpen = false; // Whether the config window is open, for our close-config hotkey (Escape).
  15. var config = GM_config;
  16. var maxNumHotkeys = 15; // How many hotkeys are configurable per site.
  17.  
  18. (function() {
  19. 'use strict';
  20.  
  21. var site = getMatchingSite();
  22. var siteClean = cleanSite(site);
  23. initConfig(site);
  24.  
  25. document.onkeyup = function(e) {
  26. //console.log("Key caught: " + e.key);
  27.  
  28. // Special cases: Ctrl+Comma (,) triggers config, Escape closes it
  29. if (e.ctrlKey && e.key === ",") {
  30. configOpen = true;
  31. config.open();
  32. }
  33. if (e.key === "Escape" && configOpen) {
  34. config.close();
  35. }
  36.  
  37. // Otherwise, only single-button hotkeys are supported
  38. if (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey) { return; }
  39.  
  40. // If there's a matching anchor name, jump to that anchor by updating the URL hash.
  41. var anchorName = getAnchorNameForKey(siteClean, e.key);
  42. //console.log("Anchor name found: " + anchorName);
  43. if(anchorName !== "") {
  44. // Make sure the anchor name starts with a hash (because that's how it's formatted in window.location.hash)
  45. if (!anchorName.startsWith("#")) {
  46. anchorName = "#" + anchorName;
  47. }
  48.  
  49. // If the URL is already pointed to the spot we're interested in, remove it so we can re-add it and jump there again.
  50. if (window.location.hash == anchorName) {
  51. window.location.hash = "";
  52. }
  53.  
  54. window.location.hash = anchorName;
  55. return;
  56. }
  57.  
  58. // Otherwise try to find the first instance of the configured text
  59. var text = getTextForKey(siteClean, e.key);
  60. //console.log("Text found: " + text);
  61. if(text !== "") {
  62. var firstElement = document.evaluate("//*[contains(text(), '" + text + "')]").iterateNext();
  63. if(firstElement) {
  64. firstElement.scrollIntoView();
  65. return;
  66. }
  67. }
  68. }
  69. })();
  70.  
  71. function getMatchingSite() {
  72. // Get sites that user has chosen to include or match (because that's what hotkeys are keyed to, not direct URLs)
  73. var sites = GM_info.script.options.override.use_matches;
  74. sites.concat(GM_info.script.options.override.use_includes);
  75.  
  76. // Find matching site
  77. var currentURL = window.location.href;
  78. for (var site of sites) {
  79. // Use a RegExp to determine which of the user's includes/matches is currently open, since we allow different hotkeys/anchors per each of those.
  80. var siteRegex = new RegExp(site.replace(/\*/g, "[^ ]*")); // Replace * wildcards with regex-style [^ ]* wildcards
  81. if (siteRegex.test(currentURL)) {
  82. return site; // First match always wins
  83. }
  84. }
  85. }
  86.  
  87. function initConfig(site) {
  88. var siteClean = cleanSite(site);
  89.  
  90. // Build the fields for each of the available hotkeys
  91. var fields = {};
  92. for (var i = 1; i <= maxNumHotkeys; i++) {
  93. fields[keyField(siteClean, i)] = {
  94. label: "Key(s) to press:",
  95. title: "The single key(s) to press to jump to this anchor/text. To have multiple keys jump to the same place, separate keys with a space (i.e. \"a r\" for both \"a\" and \"r\" keys ).",
  96. type: "text",
  97. labelPos: "above"
  98. };
  99. fields[anchorNameField(siteClean, i)] = {
  100. label: "Anchor name:",
  101. title: "The name or id of the anchor to jump to",
  102. type: "text"
  103. };
  104. fields[textField(siteClean, i)] = {
  105. label: "Text to jump to:",
  106. title: "We'll jump to the first instance of this text on the page. Ignored if anchor name is specified.",
  107. type: "text"
  108. };
  109. }
  110.  
  111. config.init({
  112. id: 'JumpToTextConfig' + siteClean,
  113. title: "Jump to Text Config for: " + site,
  114. fields: fields,
  115. events: {
  116. 'open': function() { configOpen = true; },
  117. 'close': function() { configOpen = false; },
  118. 'save': function() { config.close(); }
  119. }
  120. });
  121.  
  122. // Add a menu item to the menu to launch the config
  123. GM_registerMenuCommand('Configure hotkeys for this site', () => {
  124. config.open();
  125. })
  126. }
  127.  
  128. function cleanSite(site) {
  129. return site.replace(/[\*/:]/g, ""); // Drop */: characters from site for use in ID
  130. }
  131.  
  132. function getAnchorNameForKey(site, key) {
  133. for (var i = 1; i <= maxNumHotkeys; i++) {
  134. var keyAry = config.get(keyField(site, i).split(" "));
  135. if (keyAry.includes(key)) {
  136. return config.get(anchorNameField(site, i));
  137. }
  138. }
  139.  
  140. return "";
  141. }
  142. function getTextForKey(site, key) {
  143. for (var i = 1; i <= maxNumHotkeys; i++) {
  144. var keyAry = config.get(keyField(site, i).split(" "));
  145. if (keyAry.includes(key)) {
  146. return config.get(textField(site, i));
  147. }
  148. }
  149.  
  150. return "";
  151. }
  152.  
  153. // We use the site as part of these IDs so that the configuration is separate per site.
  154. function keyField(site, index) {
  155. return site + "_Keys_" + index;
  156. }
  157. function anchorNameField(site, index) {
  158. return site + "_AnchorName_" + index;
  159. }
  160. function textField(site, index) {
  161. return site + "_Text_" + index;
  162. }