DuckDuckGo优化

便捷返回顶部/跨引擎一键搜

当前为 2025-04-12 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name DuckDuckGo Optimization
  3. // @name:zh-CN DuckDuckGo优化
  4. // @name:zh-TW DuckDuckGo優化
  5. // @description Double Click To Return The Top / Shortcuts To Other Search Engines
  6. // @description:zh-CN 便捷返回顶部/跨引擎一键搜
  7. // @description:zh-TW 便捷返回頂部/跨引擎一鍵搜
  8. // @version 1.0.0
  9. // @icon https://raw.githubusercontent.com/MiPoNianYou/UserScripts/refs/heads/main/Icons/DuckDuckGoOptimization.svg
  10. // @author 念柚
  11. // @namespace https://github.com/MiPoNianYou/UserScripts
  12. // @supportURL https://github.com/MiPoNianYou/UserScripts
  13. // @license GPL-3.0
  14. // @match https://duckduckgo.com/*
  15. // @grant GM_addStyle
  16. // ==/UserScript==
  17.  
  18. (function () {
  19. "use strict";
  20.  
  21. const RightAreaRatio = 0.2;
  22. const InteractiveElementsSelector =
  23. 'a, button, input, select, textarea, [role="button"], [tabindex]:not([tabindex="-1"])';
  24. document.addEventListener(
  25. "dblclick",
  26. function (Event) {
  27. const WindowWidth = window.innerWidth;
  28. const TriggerBoundary = WindowWidth * (1 - RightAreaRatio);
  29. if (
  30. Event.clientX > TriggerBoundary &&
  31. !Event.target.closest(InteractiveElementsSelector)
  32. ) {
  33. window.scrollTo({
  34. top: 0,
  35. behavior: "smooth",
  36. });
  37. }
  38. },
  39. { passive: true }
  40. );
  41.  
  42. const SearchButtonStyle = `
  43. .SearchBtnGroup {
  44. display: flex;
  45. flex-wrap: wrap;
  46. gap: 12px;
  47. margin: 12px auto;
  48. max-width: 800px
  49. justify-content: center;
  50. }
  51. .SearchBtn {
  52. padding: 10px 20px;
  53. background: rgba(255,255,255,0.1);
  54. border: 1px solid rgba(255,255,255,0.2);
  55. border-radius: 24px;
  56. color: #e6e6e6;
  57. font-family: inherit;
  58. font-size: 14px;
  59. cursor: pointer;
  60. transition: all 0.2s;
  61. min-width: 120px;
  62. text-align: center;
  63. flex-grow: 1;
  64. flex-basis: 120px;
  65. }
  66. .SearchBtn:hover {
  67. background: rgba(255,255,255,0.15);
  68. border-color: rgba(255,255,255,0.3);
  69. box-shadow: 0 1px 2px rgba(0,0,0,0.2);
  70. }
  71. @media (prefers-color-scheme: light) {
  72. .SearchBtn {
  73. background: #f8f9fa;
  74. border-color: #ddd;
  75. color: #444;
  76. }
  77. .SearchBtn:hover {
  78. background: #f1f3f4;
  79. border-color: #dadce0;
  80. }
  81. }
  82. `;
  83.  
  84. GM_addStyle(SearchButtonStyle);
  85.  
  86. function CreateSearchButtons() {
  87. const SearchForm = document.querySelector("#search_form");
  88. if (!SearchForm || document.querySelector(".SearchBtnGroup")) return;
  89.  
  90. const BtnGroup = document.createElement("div");
  91. BtnGroup.className = "SearchBtnGroup";
  92.  
  93. const EngineList = [
  94. { Name: "Google", Url: "https://www.google.com/search?q=" },
  95. { Name: "Bing", Url: "https://www.bing.com/search?q=" },
  96. { Name: "Baidu", Url: "https://www.baidu.com/s?wd=" },
  97. ];
  98.  
  99. EngineList.forEach((Engine) => {
  100. const Button = document.createElement("button");
  101. Button.className = "SearchBtn";
  102. Button.textContent = `使用 ${Engine.Name} 搜索`;
  103. Button.type = "button";
  104. Button.addEventListener("click", (Event) => {
  105. Event.preventDefault();
  106. const Query = encodeURIComponent(
  107. document.querySelector("#search_form_input").value
  108. );
  109. window.open(`${Engine.Url}${Query}`, "_blank");
  110. });
  111. BtnGroup.appendChild(Button);
  112. });
  113.  
  114. SearchForm.parentNode.insertBefore(BtnGroup, SearchForm.nextSibling);
  115. }
  116.  
  117. const Observer = new MutationObserver(() => {
  118. if (!document.querySelector(".SearchBtnGroup")) CreateSearchButtons();
  119. });
  120. Observer.observe(document.body, { subtree: true, childList: true });
  121. window.addEventListener("DOMContentLoaded", CreateSearchButtons);
  122. })();