Slash To Search

Type Slash ('/') to focus on search input, customized for some websites only.

  1. // ==UserScript==
  2. // @name Slash To Search
  3. // @name:zh-CN 斜杠搜索 - Slash To Search
  4. // @namespace http://zhangmaimai.com/
  5. // @version 0.2
  6. // @description Type Slash ('/') to focus on search input, customized for some websites only.
  7. // @description:zh-CN 使用 斜杠 ('/') 聚焦输入框,仅针对部分网页定制。
  8. // @author Max
  9. // @license MIT
  10. // @match https://www.v2ex.com/*
  11. // @match https://stackoverflow.com/*
  12. // @match https://*.bilibili.com/*
  13. // @exclude https://message.bilibili.com/pages/nav/header_sync
  14. // @match https://www.douban.com/*
  15. // @match https://book.douban.com/*
  16. // @match https://*.wikipedia.org/*
  17. // @match https://gf.qytechs.cn/*
  18. // @exclude https://gf.qytechs.cn/*/*
  19. // @icon https://www.google.com/s2/favicons?sz=64&domain=gf.qytechs.cn
  20. // @grant none
  21. // @run-at document-idle
  22. // ==/UserScript==
  23. // @ts-check
  24.  
  25. const { hostname } = window.location
  26. const secondLevel = hostname.split('.').slice(1).join('.')
  27.  
  28. /** @typedef {Record<string, string>} StringMap */
  29.  
  30. /**
  31. * Map series of URLs with same selector
  32. * @param {string[]} urls
  33. * @param {string} selector
  34. */
  35. const urlToSameSelector = (urls, selector) => Object.fromEntries(urls.map(url => [url, selector]))
  36.  
  37. /**
  38. * Second level domain to a function to get selector by hostname
  39. * @type {Record<string, ()=>StringMap>}
  40. * */
  41. const getSelectorMap = {
  42. "bilibili.com": () => Object.assign({ "manga.bilibili.com": ".search-input" },
  43. urlToSameSelector([
  44. 'bilibili.com',
  45. 'www.bilibili.com',
  46. 't.bilibili.com',
  47. 'space.bilibili.com',
  48. ], '.nav-search-input')
  49. ),
  50. "v2ex.com": () => ({ 'www.v2ex.com': '#search', }),
  51. "stackoverflow.com": () => ({ 'stackoverflow.com': '.s-input' }),
  52. "douban.com": () => ({
  53. 'book.douban.com': '#inp-query',
  54. 'www.douban.com': '.inp input'
  55. }),
  56. "wikipedia.org": () => (urlToSameSelector([
  57. 'zh.wikipedia.org',
  58. 'en.wikipedia.org'
  59. ], '.cdx-text-input__input',
  60. )),
  61. "gf.qytechs.cn": () => ({
  62. "gf.qytechs.cn": ".home-search input"
  63. })
  64. }
  65.  
  66. /**
  67. * @param {string} selector
  68. * @param {HTMLElement | Document | Element} root
  69. * @param {number} timeout
  70. * @returns {Promise<Element>}
  71. */
  72. const isElementLoaded = async (selector, root = document, timeout = 1e4) => {
  73. const start = Date.now()
  74. while (root.querySelector(selector) === null) {
  75. if (Date.now() - start > timeout) throw new Error(`Timeout: ${timeout}ms exceeded`)
  76. await new Promise(resolve => requestAnimationFrame(resolve))
  77. }
  78. return /** @type {HTMLElement} */(root.querySelector(selector))
  79. }
  80.  
  81. /** @param {HTMLInputElement} search */
  82. const addSlashEvent = (search) => {
  83. const exceptActiveElement = ['INPUT', 'TEXTAREA']
  84. /** @type {(event: KeyboardEvent) => void } */
  85. const listener = (e) => {
  86. if (e.key !== '/' || exceptActiveElement.includes(document?.activeElement?.tagName || "")) return
  87. e.preventDefault()
  88. search.focus()
  89. }
  90. document.addEventListener('keydown', listener)
  91. }
  92.  
  93.  
  94. const main = async () => {
  95. const getSelector = () => {
  96. if (hostname in getSelectorMap) return getSelectorMap[hostname]()[hostname]
  97. if (!(secondLevel in getSelectorMap)) return
  98. const selectorMap = getSelectorMap[secondLevel]()
  99. return hostname in selectorMap ? selectorMap[hostname] : selectorMap['*']
  100. }
  101. const selector = getSelector()
  102. if (!selector) console.error(`No selector was found for url origin, downgrading to match <input> element with class contains "search"`)
  103. const searchElement = /** @type {HTMLDivElement?} */ (selector
  104. ? await isElementLoaded(selector)
  105. : await isElementLoaded('input[class*="search"]')
  106. )
  107. if (!searchElement || !(searchElement instanceof HTMLInputElement)) {
  108. throw Error(`Cannot detect search input element with selector ${selector}`)
  109. }
  110. addSlashEvent(searchElement)
  111. }
  112.  
  113. main()

QingJ © 2025

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