Jump to Text

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

目前为 2020-03-14 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Jump to Text
  3. // @namespace https://github.com/theborg3of5/Userscripts/
  4. // @version 1.3
  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.  
  17. (function() {
  18. 'use strict';
  19.  
  20. var site = getMatchingSite();
  21. initConfig(site);
  22.  
  23. document.onkeyup = function(e) {
  24. //console.log("Key caught: " + e.key);
  25.  
  26. // Special cases: Ctrl+Comma (,) triggers config, Escape closes it
  27. if (e.ctrlKey && e.key === ",") {
  28. configOpen = true;
  29. config.open();
  30. }
  31. if (e.key === "Escape" && configOpen) {
  32. config.close();
  33. }
  34.  
  35. // Otherwise, only single-button hotkeys are supported
  36. if (e.shiftKey || e.ctrlKey || e.altKey) { return; }
  37.  
  38. // If there's a matching anchor name, jump to that anchor by updating the URL hash.
  39. var anchorName = getAnchorNameForKey(site, e.key);
  40. //console.log("Anchor name found: " + anchorName);
  41. if(anchorName !== "") {
  42. // Make sure the anchor name starts with a hash (because that's how it's formatted in window.location.hash)
  43. if (!anchorName.startsWith("#")) {
  44. anchorName = "#" + anchorName;
  45. }
  46.  
  47. // 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.
  48. if (window.location.hash == anchorName) {
  49. window.location.hash = "";
  50. }
  51.  
  52. window.location.hash = anchorName;
  53. return;
  54. }
  55.  
  56. // Otherwise try to find the first instance of the configured text
  57. var text = getTextForKey(site, e.key);
  58. //console.log("Text found: " + text);
  59. if(text !== "") {
  60. var firstElement = document.evaluate("//*[contains(text(), '" + text + "')]").iterateNext();
  61. if(firstElement) {
  62. firstElement.scrollIntoView();
  63. return;
  64. }
  65. }
  66. }
  67. })();
  68.  
  69. function getMatchingSite() {
  70. // Get sites that user has chosen to include or match (because that's what hotkeys are keyed to, not direct URLs)
  71. var sites = GM_info.script.options.override.use_matches;
  72. sites.concat(GM_info.script.options.override.use_includes);
  73.  
  74. // Find matching site
  75. var currentURL = window.location.href;
  76. for (var site of sites) {
  77. // 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.
  78. var siteRegex = new RegExp(site.replace(/\*/g, "[^ ]*")); // Replace * wildcards with regex-style [^ ]* wildcards
  79. if (siteRegex.test(currentURL)) {
  80. return site; // First match always wins
  81. }
  82. }
  83. }
  84.  
  85. function initConfig(site) {
  86. // Build the fields for each of the 10 available hotkeys
  87. var fields = {};
  88. for (var i = 1; i <= 10; i++) {
  89. fields[keyField(site, i)] = {
  90. label: "Key(s) to press:",
  91. 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 ).",
  92. type: "text",
  93. labelPos: "above"
  94. };
  95. fields[anchorNameField(site, i)] = {
  96. label: "Anchor name:",
  97. title: "The name or id of the anchor to jump to",
  98. type: "text"
  99. };
  100. fields[textField(site, i)] = {
  101. label: "Text to jump to:",
  102. title: "We'll jump to the first instance of this text on the page. Ignored if anchor name is specified.",
  103. type: "text"
  104. };
  105. }
  106.  
  107. config.init({
  108. id: 'JumpToTextConfig',
  109. title: "Jump to Text Config for: " + site,
  110. fields: fields,
  111. events: {
  112. 'open': function() { configOpen = true; },
  113. 'close': function() { configOpen = false; }
  114. }
  115. });
  116.  
  117. // Add a menu item to the menu to launch the config
  118. GM_registerMenuCommand('Configure hotkeys for this site', () => {
  119. config.open();
  120. })
  121. }
  122.  
  123. function getAnchorNameForKey(site, key) {
  124. for (var i = 1; i <= 10; i++) {
  125. var keyAry = config.get(keyField(site, i).split(" "));
  126. if (keyAry.includes(key)) {
  127. return config.get(anchorNameField(site, i));
  128. }
  129. }
  130.  
  131. return "";
  132. }
  133. function getTextForKey(site, key) {
  134. for (var i = 1; i <= 10; i++) {
  135. var keyAry = config.get(keyField(site, i).split(" "));
  136. if (keyAry.includes(key)) {
  137. return config.get(textField(site, i));
  138. }
  139. }
  140.  
  141. return "";
  142. }
  143.  
  144. // We use the site as part of these IDs so that the configuration is separate per site.
  145. function keyField(site, index) {
  146. return site + "_Keys_" + index;
  147. }
  148. function anchorNameField(site, index) {
  149. return site + "_AnchorName_" + index;
  150. }
  151. function textField(site, index) {
  152. return site + "_Text_" + index;
  153. }