您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds single-key hotkeys that jump to specific text (or anchors) on a page.
// ==UserScript== // @name Jump to Text // @namespace https://github.com/theborg3of5/Userscripts/ // @version 1.8 // @description Adds single-key hotkeys that jump to specific text (or anchors) on a page. // @author Gavin Borg // @require https://greasyfork.org/scripts/28536-gm-config/code/GM_config.js?version=184529 // @match https://greasyfork.org/en/scripts/395551-jump-to-text // @grant GM_registerMenuCommand // @grant GM_getValue // @grant GM_setValue // ==/UserScript== var configOpen = false; // Whether the config window is open, for our close-config hotkey (Escape). var config = GM_config; var maxNumHotkeys = 15; // How many hotkeys are configurable per site. var contextMenuOpen = false; var contextTimeout = 2000; // 2 seconds - how long to wait before assuming the context menu was closed. (function() { 'use strict'; initConfig(getMatchingSite()); document.oncontextmenu = contextMenuOpened; document.onkeyup = keyPressed; })(); function initConfig(site) { var siteClean = cleanSite(site); // Build the fields for each of the available hotkeys var fields = {}; for (var i = 1; i <= maxNumHotkeys; i++) { fields[keyField(i)] = { label: "Key(s) to press:", 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 ).", type: "text", labelPos: "above" }; fields[anchorNameField(i)] = { label: "Anchor name:", title: "The name or id of the anchor to jump to", type: "text" }; fields[textField(i)] = { label: "Text to jump to:", title: "We'll jump to the first instance of this text on the page. Ignored if anchor name is specified.", type: "text" }; } config.init({ id: 'JumpToTextConfig' + siteClean, title: "Jump to Text Config for: " + site, fields: fields, events: { 'open': function() { configOpen = true; }, 'close': function() { configOpen = false; }, 'save': function() { config.close(); } } }); // Add a menu item to the menu to launch the config GM_registerMenuCommand('Configure hotkeys for this site', () => { config.open(); }) } function cleanSite(site) { return site.replace(/[\*/:\?\.]/g, ""); // Drop */:?. characters from site for use in ID } function keyField(index) { return "Keys_" + index; } function anchorNameField(index) { return "AnchorName_" + index; } function textField(index) { return "Text_" + index; } function contextMenuOpened() { contextMenuOpen = true; // There's not a "oncontextmenuclosed" event, so we just have to take a guess and wait that long. setTimeout(function() { contextMenuOpen = false; }, contextTimeout); } function keyPressed(e) { //console.log("Key caught: " + e.key); // Ignore keys while the context menu is open if(contextMenuOpen) { return; } console.log("Context menu open: " + contextMenuOpen); // Special cases: Ctrl+Comma (,) triggers config, Escape closes it if (e.ctrlKey && e.key === ",") { configOpen = true; config.open(); } if (e.key === "Escape" && configOpen) { config.close(); } // Otherwise, only single-button hotkeys are supported if (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey) { return; } // If there's a matching anchor name, jump to that anchor by updating the URL hash. var anchorName = getAnchorNameForKey(e.key); //console.log("Anchor name found: " + anchorName); if(anchorName !== "") { // Make sure the anchor name starts with a hash (because that's how it's formatted in window.location.hash) if (!anchorName.startsWith("#")) { anchorName = "#" + anchorName; } // 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. if (window.location.hash == anchorName) { window.location.hash = ""; } window.location.hash = anchorName; return; } // Otherwise try to find the first instance of the configured text var text = getTextForKey(e.key); //console.log("Text found: " + text); if(text !== "") { var firstElement = document.evaluate("//*[contains(text(), '" + text + "')]", document).iterateNext(); if(firstElement) { firstElement.scrollIntoView(); return; } } } function getMatchingSite() { // Get sites that user has chosen to include or match (because that's what hotkeys are keyed to, not direct URLs) var sites = GM_info.script.options.override.use_matches; sites.concat(GM_info.script.options.override.use_includes); // Find matching site var currentURL = window.location.href; for (var site of sites) { // 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. var siteRegex = new RegExp(site.replace(/\*/g, "[^ ]*")); // Replace * wildcards with regex-style [^ ]* wildcards if (siteRegex.test(currentURL)) { return site; // First match always wins } } } function getAnchorNameForKey(key) { for (var i = 1; i <= maxNumHotkeys; i++) { var keyAry = config.get(keyField(i).split(" ")); if (keyAry.includes(key)) { return config.get(anchorNameField(i)); } } return ""; } function getTextForKey(key) { for (var i = 1; i <= maxNumHotkeys; i++) { var keyAry = config.get(keyField(i).split(" ")); if (keyAry.includes(key)) { return config.get(textField(i)); } } return ""; }