Draggy

拖拽链接以在新标签页中打开,拖拽文本以在新标签页中搜索。

当前为 2024-10-02 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Draggy
// @name:zh-CN   Draggy
// @namespace    http://tampermonkey.net/
// @version      0.1.1
// @description  Drag a link to open in a new tab; drag a piece of text to search in a new tab.
// @description:zh-CN 拖拽链接以在新标签页中打开,拖拽文本以在新标签页中搜索。
// @author       PRO-2684
// @match        *://*/*
// @run-at       document-start
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @license      gpl-3.0
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_addValueChangeListener
// @require      https://update.greasyfork.org/scripts/470224/1456932/Tampermonkey%20Config.js
// ==/UserScript==
(function () {
    "use strict";
    const { name, version } = GM.info.script;
    const configDesc = {
        $default: {
            autoClose: false,
        },
        debug: {
            name: "Debug mode",
            title: "Enables debug mode.",
            type: "bool",
            value: false,
        },
        searchEngine: {
            name: "Search engine",
            title: "Search engine used when dragging text. Use {} as a placeholder for the URL-encoded query.",
            type: "string",
            value: "https://www.google.com/search?q={}",
        },
        maxLength: {
            name: "Maximum text length",
            title: "Maximum length of the search term. If the length of the search term exceeds this value, it will be truncated. Set to 0 to disable this feature.",
            type: "int_range-0-1000",
            value: 100,
        },
        maxTimeDelta: {
            name: "Maximum time delta",
            title: "Maximum time difference between esc/drop and dragend events to consider them as separate user gesture. Usually there's no need to change this value.",
            type: "int_range-1-100",
            value: 10,
        },
    };
    // TODO: ESC to cancel; drag distance threshold & circle overlay
    const config = new GM_config(configDesc, { immediate: false });
    /**
     * Last time a drop event occurred.
     * @type {number}
     */
    let lastDrop = 0;
    /**
     * Logs the given arguments if debug mode is enabled.
     * @param {...any} args The arguments to log.
     */
    function log(...args) {
        if (config.get("debug")) {
            console.log(`[${name}]`, ...args);
        }
    }
    /**
     * Searches for the given keyword.
     * @param {string} keyword The keyword to search for.
     */
    function search(keyword) {
        const searchEngine = config.get("searchEngine");
        const maxLength = config.get("maxLength");
        const truncated = maxLength > 0 ? keyword.slice(0, maxLength) : keyword;
        const url = searchEngine.replace("{}", encodeURIComponent(truncated));
        log(`Searching for "${truncated}" using "${url}"`);
        window.open(url, "_blank");
    }
    document.addEventListener("drop", (e) => {
        lastDrop = e.timeStamp;
        log("Drop event at", e.timeStamp);
    }, { passive: true });
    document.addEventListener("dragend", (e) => {
        if (e.timeStamp - lastDrop <= config.get("maxTimeDelta")) { // The gesture has been recognized as a drop and thus handled.
            log("Draggy interrupted by drop event");
            return;
        }
        if (e.dataTransfer.dropEffect !== "none" || e.dataTransfer.effectAllowed !== "uninitialized" || e.defaultPrevented) { // Only process drops that were not handled by the browser or other scripts.
            log("Draggy interrupted by other handler(s)");
            return;
        }
        log("Draggy starts processing...");
        e.preventDefault();
        const selectedText = window.getSelection()?.toString();
        if (selectedText) {
            search(selectedText);
            return;
        }
        const link = e.target.closest("a[href]");
        const href = link?.getAttribute("href");
        if (!href || href.startsWith("javascript:") || href === "#") {
            log("Draggy can't find selected text or a valid link");
            return;
        }
        window.open(link.href, "_blank");
    }, { passive: false });
    log(`${version} initialized successfully 🎉`);
})();