拖拽链接以在新标签页中打开,拖拽文本以在新标签页中搜索。
当前为
// ==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 🎉`);
})();