DDG Shortcuts

Adds DuckDuckGo keyboard shortcuts to other search engines

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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);
			}
		}

	});

})();