Google Search Sidebar

Add a foldable sidebar and scroll bottoms on Google Search for easier information filtering

目前為 2025-02-24 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Google Search Sidebar
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.3
  5. // @description Add a foldable sidebar and scroll bottoms on Google Search for easier information filtering
  6. // @author Kamiya Minoru
  7. // @icon https://www.google.com/favicon.ico
  8. // @match https://www.google.com/*
  9. // @match https://www.google.com.tw/*
  10. // @match https://www.google.co.jp/*
  11. // @grant none
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. const userLang = navigator.language || navigator.userLanguage;
  19.  
  20. const i18n = {
  21. 'zh-TW': {
  22. languageSection: '語言過濾 …', timeSection: '時間過濾 …', advancedSearch: '進階搜尋',
  23. languages: { any: '不限語言搜尋', zhTW: '以繁體中文搜尋', zh: '以中文搜尋', ja: '以日文搜尋', en: '以英文搜尋' },
  24. times: { any: '不限時間', hour: '過去1小時', day: '過去1天', week: '過去1週', month: '過去1個月', months3: '過去3個月', year: '過去1年', years3: '過去3年' }
  25. },
  26. 'ja': {
  27. languageSection: ' 言語フィルター …', timeSection: ' 期間フィルター', advancedSearch: '検索オプション',
  28. languages: { any: '言語指定なし', zhTW: '繁体中国語で検索', zh: '中国語で検索', ja: '日本語で検索', en: '英語で検索' },
  29. times: { any: '期間指定なし', hour: '1時間以内', day: '24時間以内', week: '1週間以内', month: '1か月以内', months3: '3か月以内', year: '1年以内', years3: '3年以内' }
  30. },
  31. 'en': {
  32. languageSection: 'Language Filter …', timeSection: 'Time Filter', advancedSearch: 'Advanced Search',
  33. languages: { any: 'Any Language', zhTW: 'Traditional Chinese', zh: 'All Chinese', ja: 'Japanese', en: 'English' },
  34. times: { any: 'Any Time', hour: 'Past Hour', day: 'Past 24 Hours', week: 'Past Week', month: 'Past Month', months3: 'Past 3 Months', year: 'Past Year', years3: 'Past 3 Years' }
  35. }
  36. };
  37.  
  38. function getLocale() {
  39. if (i18n[userLang]) { return i18n[userLang]; }
  40. const primaryLang = userLang.split('-')[0];
  41. if (i18n[primaryLang]) { return i18n[primaryLang]; }
  42. return i18n['en'];
  43. }
  44.  
  45. const locale = getLocale();
  46.  
  47. const languageFilters = [
  48. { text: locale.languages.any, param: '&lr=' },
  49. { text: locale.languages.zhTW, param: '&lr=lang_zh-TW' },
  50. { text: locale.languages.zh, param: '&lr=lang_zh' },
  51. { text: locale.languages.ja, param: '&lr=lang_ja' },
  52. { text: locale.languages.en, param: '&lr=lang_en' }
  53. ];
  54.  
  55. const timeFilters = [
  56. { text: locale.times.any, param: '&tbs=' },
  57. { text: locale.times.hour, param: '&tbs=qdr:h' },
  58. { text: locale.times.day, param: '&tbs=qdr:d' },
  59. { text: locale.times.week, param: '&tbs=qdr:w' },
  60. { text: locale.times.month, param: '&tbs=qdr:m' },
  61. { text: locale.times.months3, param: '&tbs=qdr:m3' },
  62. { text: locale.times.year, param: '&tbs=qdr:y' },
  63. { text: locale.times.years3, param: '&tbs=qdr:y3' }
  64. ];
  65.  
  66. function createAdvancedSearchLink() {
  67. const link = document.createElement('a');
  68. link.textContent = locale.advancedSearch;
  69. link.href = getAdvancedSearchUrl();
  70. link.style.cssText = `display: block; color: #1a73e8; text-decoration: none; font-size: 14px; padding: 5px; margin-top: 0px; background: #ecedef; border-radius: 8px; text-align: center; transition: background-color 0.2s;`;
  71. link.addEventListener('mouseover', () => { link.style.backgroundColor = '#1a73e8'; link.style.color = '#FFFFFF'; });
  72. link.addEventListener('mouseout', () => { link.style.backgroundColor = '#ecedef'; link.style.color = '#1a73e8'; });
  73. return link;
  74. }
  75.  
  76. function createFilterSection(title, filters, collapsible = false, defaultExpanded = false) {
  77. const section = document.createElement('div');
  78. section.style.cssText = `margin: 3px 0; padding: 3px; background: #ecedef; border-radius: 8px;`;
  79.  
  80. const titleElement = document.createElement('h3');
  81. titleElement.textContent = title;
  82. titleElement.style.cssText = `margin: 0 0 2px 0; padding-top: 5px; font-size: 14px; color: #202124; font-weight: 500; cursor: ${collapsible ? 'pointer' : 'default'}; text-align: center;`;
  83. section.appendChild(titleElement);
  84.  
  85. const linkContainer = document.createElement('div');
  86. linkContainer.style.cssText = `display: flex; flex-direction: column; gap: 3px; ${collapsible && !defaultExpanded ? 'display: none;' : ''}`;
  87.  
  88. filters.forEach(filter => {
  89. const link = document.createElement('a');
  90. link.textContent = filter.text;
  91. link.href = getCurrentUrlWithParam(filter.param);
  92. link.style.cssText = `color: #888888; text-decoration: none; font-size: 12px; padding: 2px 2px; border-radius: 8px; transition: none;`;
  93.  
  94. const urlParams = new URL(window.location.href).searchParams;
  95. if (urlParams.get('lr') === filter.param.replace('&lr=', '') || urlParams.get('tbs') === filter.param.replace('&tbs=', '')) {
  96. link.style.color = '#1a73e8';
  97. link.style.fontWeight = 'bold';
  98. }
  99.  
  100. link.addEventListener('mouseover', () => {
  101. link.style.backgroundColor = '#1a73e8';
  102. link.style.color = '#FFFFFF';
  103. });
  104. link.addEventListener('mouseout', () => {
  105. if (urlParams.get('lr') !== filter.param.replace('&lr=', '') && urlParams.get('tbs') !== filter.param.replace('&tbs=', '')) {
  106. link.style.color = '#888888';
  107. link.style.backgroundColor = 'transparent';
  108. }
  109. });
  110. linkContainer.appendChild(link);
  111. });
  112. section.appendChild(linkContainer);
  113.  
  114. if (collapsible) {
  115. titleElement.addEventListener('click', () => {
  116. linkContainer.style.display = linkContainer.style.display === 'none' ? 'flex' : 'none';
  117. });
  118. }
  119.  
  120. return section;
  121. }
  122.  
  123. function getCurrentUrlWithParam(param) {
  124. const url = new URL(window.location.href);
  125. const existingParams = new URLSearchParams(url.search);
  126.  
  127. if (param.includes('lr=')) {
  128. existingParams.delete('lr');
  129. }
  130. if (param.includes('tbs=')) {
  131. existingParams.delete('tbs');
  132. }
  133. if (param) {
  134. const cleanParam = param.startsWith('&') ? param.substring(1) : param;
  135. const [key, value] = cleanParam.split('=');
  136. if (value) {
  137. existingParams.set(key, value);
  138. } else {
  139. // 不要添加空白的參數
  140. existingParams.delete(key);
  141. }
  142. }
  143.  
  144. url.search = existingParams.toString();
  145. return url.toString();
  146. }
  147.  
  148. function getAdvancedSearchUrl() {
  149. const currentParams = new URLSearchParams(window.location.search);
  150. const q = currentParams.get('q') || '';
  151. const lr = currentParams.get('lr') || '';
  152. const tbs = currentParams.get('tbs') || '';
  153. const udm = currentParams.get('udm');
  154.  
  155. let url;
  156. if (udm === '2' || udm === '6') {
  157. url = new URL('https://www.google.com/advanced_image_search');
  158. } else if (udm === '7') {
  159. url = new URL('https://www.google.com/advanced_video_search');
  160. } else {
  161. url = new URL('https://www.google.com/advanced_search');
  162. }
  163.  
  164. url.searchParams.set('q', q);
  165. url.searchParams.set('lr', lr);
  166. url.searchParams.set('tbs', tbs);
  167.  
  168. return url.toString();
  169. }
  170.  
  171. function createScrollButton(innerHTML, onClick) {
  172. var button = document.createElement('div');
  173. button.innerHTML = innerHTML;
  174. button.style.width = '30px'; button.style.height = '34px'; button.style.cursor = 'pointer'; button.style.backgroundColor = 'transparent'; button.style.color = 'grey';
  175. button.style.textAlign = 'center'; button.style.fontSize = '30px'; button.style.borderRadius = '0px'; button.style.userSelect = 'none'; button.style.marginBottom = '0px';
  176. button.style.opacity = '0.2'; button.style.transition = 'all 0s'; button.style.filter = 'drop-shadow(0 0 0 grey)'; button.addEventListener('click', onClick);
  177. button.addEventListener('mouseover', function() { button.style.opacity = '1'; button.style.filter = 'drop-shadow(0 0 2px grey)'; button.style.color = '#FFF'; });
  178. button.addEventListener('mouseout', function() { button.style.opacity = '0.1'; button.style.filter = 'drop-shadow(0 0 0 grey)'; button.style.color = 'grey'; });
  179. return button;
  180. }
  181.  
  182. function addScrollButtonsToSidebar() {
  183. const sidebar = document.querySelector('div[style*="position: fixed"]');
  184. if (!sidebar) return;
  185. const scrollContainer = document.createElement('div');
  186. scrollContainer.style.cssText = `position: absolute; top: 3px; left: 130px; display: flex; flex-direction: column; align-items: center; margin-top: 0px;`;
  187.  
  188. scrollContainer.appendChild(createScrollButton('⇑', function() { window.scrollTo({ top: 0, behavior: 'instant' }); }));
  189. scrollContainer.appendChild(createScrollButton('⇡', function() { window.scrollBy({ top: -window.innerHeight, behavior: 'instant' }); }));
  190. scrollContainer.appendChild(createScrollButton('⇣', function() { window.scrollBy({ top: window.innerHeight, behavior: 'instant' }); }));
  191. scrollContainer.appendChild(createScrollButton('⇓', function() { window.scrollTo({ top: document.body.scrollHeight, behavior: 'instant' }); }));
  192. sidebar.appendChild(scrollContainer);
  193. }
  194.  
  195. function observeUrlChanges() {
  196. let lastUrl = location.href;
  197. new MutationObserver(() => {
  198. const currentUrl = location.href;
  199. if (currentUrl !== lastUrl) {
  200. lastUrl = currentUrl;
  201. setupSidebar();
  202. }
  203. }).observe(document, { subtree: true, childList: true });
  204. }
  205.  
  206. function setupSidebar() {
  207. const existingSidebar = document.querySelector('div[style*="position: fixed"]');
  208. if (existingSidebar) {
  209. existingSidebar.remove();
  210. }
  211.  
  212. const searchResults = document.getElementById('search');
  213. if (searchResults) {
  214. const sidebar = document.createElement('div');
  215. sidebar.style.cssText = `position: fixed; top: 23px; left: 7px; width: 160px; z-index: 1000;`;
  216. sidebar.appendChild(createFilterSection(locale.languageSection, languageFilters, true, window.location.href.includes('&lr=')));
  217. sidebar.appendChild(createFilterSection(locale.timeSection, timeFilters, true, window.location.href.includes('&tbs=')));
  218. sidebar.appendChild(createAdvancedSearchLink());
  219. document.body.appendChild(sidebar);
  220.  
  221. addScrollButtonsToSidebar();
  222. }
  223. }
  224.  
  225. observeUrlChanges();
  226. setupSidebar();
  227. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址