// ==UserScript==
// @name DuckDuckGo Optimization
// @name:zh-CN DuckDuckGo优化
// @name:zh-TW DuckDuckGo優化
// @description Double Click To Return The Top / Shortcuts To Other Search Engines
// @description:zh-CN 便捷返回顶部/跨引擎一键搜
// @description:zh-TW 便捷返回頂部/跨引擎一鍵搜
// @version 1.1.1
// @icon https://raw.githubusercontent.com/MiPoNianYou/UserScripts/refs/heads/main/Icons/DuckDuckGoOptimizationIcon.svg
// @author 念柚
// @namespace https://github.com/MiPoNianYou/UserScripts
// @supportURL https://github.com/MiPoNianYou/UserScripts/issues
// @license GPL-3.0
// @match https://duckduckgo.com/*
// @grant GM_addStyle
// ==/UserScript==
(function () {
"use strict";
const RightAreaRatio = 0.2;
const InteractiveElementsSelector =
'a, button, input, select, textarea, [role="button"], [tabindex]:not([tabindex="-1"])';
const SearchFormSelector = "#search_form";
const SearchInputSelector = "#search_form_input";
const SearchBtnGroupClass = "SearchBtnGroup";
const SearchBtnClass = "SearchBtn";
const DebounceDelay = 250;
document.addEventListener(
"dblclick",
function (Event) {
const WindowWidth = window.innerWidth;
const TriggerBoundary = WindowWidth * (1 - RightAreaRatio);
if (
Event.clientX > TriggerBoundary &&
!Event.target.closest(InteractiveElementsSelector)
) {
window.scrollTo({
top: 0,
behavior: "smooth",
});
}
},
{ passive: true }
);
const SearchButtonStyle = `
.${SearchBtnGroupClass} {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 12px auto;
padding: 0 10px;
max-width: 800px;
justify-content: center;
}
.${SearchBtnClass} {
padding: 8px 16px;
border-radius: 8px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s ease, border-color 0.2s ease;
min-width: 110px;
text-align: center;
flex-grow: 1;
flex-basis: 110px;
box-sizing: border-box;
border: 1px solid transparent;
background-color: rgba(60, 60, 60, 0.8);
color: #f5f5f7;
border-color: rgba(85, 85, 85, 0.9);
}
.${SearchBtnClass}:hover {
background-color: rgba(75, 75, 75, 0.9);
border-color: rgba(100, 100, 100, 0.9);
}
`;
GM_addStyle(SearchButtonStyle);
const EngineList = [
{ Name: "Google", Url: "https://www.google.com/search?q=" },
{ Name: "Bing", Url: "https://www.bing.com/search?q=" },
{ Name: "Baidu", Url: "https://www.baidu.com/s?wd=" },
];
function Debounce(Func, Wait) {
let Timeout;
return function ExecutedFunction(...Args) {
const Later = () => {
clearTimeout(Timeout);
Func(...Args);
};
clearTimeout(Timeout);
Timeout = setTimeout(Later, Wait);
};
}
function CreateSearchButtons() {
const SearchForm = document.querySelector(SearchFormSelector);
const SearchInput = SearchForm?.querySelector(SearchInputSelector);
if (
!SearchForm ||
!SearchInput ||
document.querySelector(`.${SearchBtnGroupClass}`)
) {
return;
}
const BtnGroup = document.createElement("div");
BtnGroup.className = SearchBtnGroupClass;
EngineList.forEach((Engine) => {
const Button = document.createElement("button");
Button.className = SearchBtnClass;
Button.textContent = `使用 ${Engine.Name} 搜索`;
Button.type = "button";
Button.addEventListener("click", (Event) => {
Event.preventDefault();
const Query = SearchInput.value.trim();
if (Query) {
const SearchUrl = `${Engine.Url}${encodeURIComponent(Query)}`;
window.open(SearchUrl, "_blank", "noopener,noreferrer");
}
});
BtnGroup.appendChild(Button);
});
SearchForm.parentNode.insertBefore(BtnGroup, SearchForm.nextSibling);
}
const DebouncedCreateSearchButtons = Debounce(
CreateSearchButtons,
DebounceDelay
);
function CheckAndMaybeCreateButtons() {
const SearchFormExists = document.querySelector(SearchFormSelector);
const ButtonsExist = document.querySelector(`.${SearchBtnGroupClass}`);
if (SearchFormExists && !ButtonsExist) {
DebouncedCreateSearchButtons();
}
}
const Observer = new MutationObserver(CheckAndMaybeCreateButtons);
Observer.observe(document.body, {
childList: true,
subtree: true,
});
if (document.readyState === "loading") {
window.addEventListener("DOMContentLoaded", CheckAndMaybeCreateButtons);
} else {
CheckAndMaybeCreateButtons();
}
})();