DDG Shortcuts

Adds DuckDuckGo keyboard shortcuts to other search engines

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         DDG Shortcuts
// @version      1.2.1
// @description  Adds DuckDuckGo keyboard shortcuts to other search engines
// @author       ReimarPB
// @match        *://*/*
// @namespace    https://reimarpb.com/userscripts
// @grant        none
// @noframes
// ==/UserScript==

(function() {

	// The CSS selectors for each search engine to get different parts of the page
	var selectors = {
		google: {
			input:            "input[name=q]",
			tab:              ".hdtb-mitem a, .hdtb-mitem.hdtb-msel, .T47uwc > a, .T47uwc > span",
			currentTab:       ".hdtb-mitem.hdtb-msel, .rQEFy.NZmxZe",
			resultHoverElem:  ".LC20lb.MBeuO.DKV0Md",
			resultHoverClass: "AraNOb",
			resultLink:       ".yuRUbf a",
		},
		startpage: {
			input:            "#q",
			tab:              ".inline-nav-menu__link__post-link, .css-dkelex",
			currentTab:       ".inline-nav-menu__link__post-link.inline-nav-menu__link__active, .css-156jy2m button",
			resultHoverElem:  ".w-gl__result",
			resultHoverClass: "linkHover",
			resultLink:       ".w-gl__result-title.result-link",
		},
		brave: {
			input:            "#searchbox",
			tab:              ".tab-item > a",
			currentTab:       ".tab-item.active > a",
			resultLink:       ".result-header",
		},
		searx: {
			input:            "#q",
			tab:              ".category > label, #categories > label",
			currentTab:       ".category input[type=checkbox]:checked + label, #categories input[type=checkbox]:checked + label",
			resultLink:       ".result > h3 > a, .result_header > a",
		}
	};

	// Get selectors for this search engine
	if (location.host.indexOf("www.google.") === 0 && location.pathname.indexOf("/search") === 0)
		selectors = selectors.google;
	else if (location.host === "www.startpage.com" && location.pathname == "/sp/search")
		selectors = selectors.startpage;
	else if (location.host === "search.brave.com" && ["/search", "/images", "/news", "/videos"].indexOf(location.pathname) !== -1)
		selectors = selectors.brave;
	else if (window.searx || window.searxng) {
		selectors = selectors.searx;
		document.getElementById("q").blur(); // Remove autofocus
	} else return;

	var tabs = document.querySelectorAll(selectors.tab);

	var resultIndex = -1;
	var tabIndex = [].indexOf.call(tabs, document.querySelector(selectors.currentTab));

	document.addEventListener("keydown", function(event) {

		// Return if input is focused
		if (document.querySelector("input:focus")) return;

		var preventDefault = true;
		if (selectors.resultHoverElem) var resultHoverElems = document.querySelectorAll(selectors.resultHoverElem);
		var resultLinks = document.querySelectorAll(selectors.resultLink);
		var searchField = document.querySelector(selectors.input);
		var currentResult = resultLinks[resultIndex === -1 ? 0 : resultIndex]; // Default to first result if none selected

		switch (event.key) {

			// Focus search field
			case "/":
			case "h":
				searchField.focus();
				searchField.select();
				break;

			// Domain search
			case "d":
				searchField.value = searchField.value
					.replace(/site:\S+/g, "")
					.replace(/\s+$/, "")
					+ " site:" + currentResult.host;
				searchField.form.submit();
				break;

			// Open link
			case "Enter":
			case "l":
			case "o":
				// Allow CTRL + Enter
				if (event.ctrlKey) {
					preventDefault = false;
					break;
				}
				location.href = currentResult.href;
				break;

			// Open link in new tab
			case "'":
			case "v":
				open(currentResult.href, "_blank");
				break;

			// Scroll to top
			case "t":
				scrollTo({ top: 0 });
				resultIndex = -1;
				break;

			// Move between results
			case "ArrowUp":
			case "k":
				if (resultIndex === 0) resultLinks[resultIndex--].blur();
				if (resultLinks[resultIndex - 1]) resultLinks[--resultIndex].focus();
				resultUpdated();
				break;
			case "ArrowDown":
			case "j":
				if (resultLinks[resultIndex + 1]) resultLinks[++resultIndex].focus();
				resultUpdated();
				break;
			case "m":
				resultIndex = 0;
				resultUpdated();
				break;

			// Change tabs
			case "ArrowLeft":
				if (tabs[tabIndex - 1]) tabs[tabIndex - 1].click();
				break;
			case "ArrowRight":
				if (tabs[tabIndex + 1]) tabs[tabIndex + 1].click();
				break;

			default:
				preventDefault = false;
		}

		if (preventDefault) event.preventDefault();

		// Called when the user presses down/up
		function resultUpdated() {

			// Remove old hover effect
			if (selectors.resultHoverClass) {
				var old = document.querySelector("." + selectors.resultHoverClass + selectors.resultHoverElem);
				if (old) old.classList.remove(selectors.resultHoverClass);
			}

			if (resultIndex >= 0) {
				// Scroll to result
				var elem = resultLinks[resultIndex], top = 0;
				do {
					top += elem.offsetTop || 0;
					elem = elem.offsetParent;
				} while(elem)
				scrollTo({ top: top - innerHeight / 2 });
				// Add hover effect
				if (resultHoverElems) resultHoverElems[resultIndex].classList.add(selectors.resultHoverClass);
			}
		}

	});

})();