NoMouseGoogle

Shortcut for Google search results. j/k to move focus, enter/l/h to open in current/new/background tab, n/p to go to next/previous page.

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

  1. // ==UserScript==
  2. // @name NoMouseGoogle
  3. // @namespace com.gmail.fujifruity.greasemonkey
  4. // @version 1.13
  5. // @description Shortcut for Google search results. j/k to move focus, enter/l/h to open in current/new/background tab, n/p to go to next/previous page.
  6. // @author fujifruity
  7. // @include https://www.google.com/search*
  8. // @include https://www.google.co.*/search*
  9. // @grant GM.openInTab
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. {
  14. const tag = "noMouseGoogleCurrentItem"
  15. const items = [...document.querySelectorAll('#rso div[data-hveid][data-ved][lang], #rso video-voyager>div')]
  16. const findCurrentItem = () => items.find(e => e.hasAttribute(tag))
  17. const currentItemHref = () => findCurrentItem().querySelector('a').href
  18. const openInNewTab = inBackground => GM.openInTab(currentItemHref(), inBackground)
  19. const openInThisTab = () => window.open(currentItemHref(), "_self")
  20. .filter(e => e.offsetParent != null /* is visible */)
  21. const moveCursor = step => {
  22. const currentItem = findCurrentItem()
  23. const r = currentItem.getBoundingClientRect();
  24. const inScreen = (0 < r.top && r.top < window.innerHeight
  25. || 0 < r.bottom && r.bottom < window.innerHeight)
  26. if (!inScreen) {
  27. const dist = e => {
  28. const r = e.getBoundingClientRect()
  29. return Math.abs(window.innerHeight - (r.top + r.bottom))
  30. }
  31. const nearestItem = items.reduce((acc, e) => dist(acc) < dist(e) ? acc : e)
  32. select(nearestItem)
  33. return
  34. }
  35. const nextIdx = (items.indexOf(currentItem) + step + items.length) % items.length
  36. select(items[nextIdx])
  37. }
  38. const highlight = e => {
  39. const isDarkTheme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
  40. e.style.backgroundColor = isDarkTheme ? '#2a2a2a' : 'WhiteSmoke'
  41. }
  42. const select = item => {
  43. // Deselect current item.
  44. const currentItem = findCurrentItem()
  45. if (currentItem) {
  46. currentItem.style.backgroundColor = null
  47. currentItem.removeAttribute(tag)
  48. }
  49. // Select the item.
  50. item.setAttribute(tag, '')
  51. highlight(item)
  52. item.scrollIntoView({ behavior: "smooth", block: "center" })
  53. }
  54.  
  55. // Select the first item without scrolling.
  56. items[0].setAttribute(tag, '')
  57. highlight(items[0])
  58.  
  59. window.addEventListener('keydown', event => {
  60. if (event.target.tagName == "INPUT" || event.ctrlKey || event.altKey || event.metaKey) return
  61. if (event.key == 'j') moveCursor(+1)
  62. if (event.key == 'k') moveCursor(-1)
  63. if (event.key == 'l') openInNewTab(false)
  64. if (event.key == 'h') openInNewTab(true)
  65. if (event.key == 'Enter') openInThisTab()
  66. if (event.key == 'n') document.querySelector('#pnnext')?.click()
  67. if (event.key == 'p') document.querySelector('#pnprev')?.click()
  68. })
  69.  
  70. }