// ==UserScript== // @name HjklNavigation // @namespace com.gmail.fujifruity.greasemonkey // @version 1.5.2 // @description Shortcuts for Google Search result. j/k to move focus, l/h to open in new/background tab. // @author fujifruity // @include https://www.google.com/search* // @grant GM.openInTab // @license MIT // @downloadURL none // ==/UserScript== { const items = [...document.querySelectorAll('#rso .g')].filter(e => e.className == 'g') const open = isBackground => { const url = findCurrentItem().querySelector('a').href GM.openInTab(url, isBackground) } const tag = "hjklNavigationCurrentItem" const findCurrentItem = () => items.find(e => e.hasAttribute(tag)) const moveCursor = step => { const currentItem = findCurrentItem() const r = currentItem.getBoundingClientRect(); const isVisible = 0 < r.top && r.top < window.innerHeight || 0 < r.bottom && r.bottom < window.innerHeight if (!isVisible) { const dist = e => { const r = e.getBoundingClientRect() return Math.abs(window.innerHeight - (r.top + r.bottom)) } const nearest = items.reduce((acc,e) => dist(acc) < dist(e) ? acc : e ) select(nearest) return } const nextIdx = (items.indexOf(currentItem) + step + items.length) % items.length select(items[nextIdx]) } const highlight = e => { e.style.backgroundColor = 'lightyellow' e.setAttribute(tag, '') } const select = item => { // deselect current item const currentItem = findCurrentItem() currentItem?.setAttribute('style', "background-color: null;") currentItem?.removeAttribute(tag) highlight(item) item.scrollIntoView({ behavior: "smooth", block: "center" }) } highlight(items[0]) window.addEventListener('keydown', event => { if (event.target.tagName == "INPUT" || event.ctrlKey || event.altKey) return switch (event.key) { case 'j': { moveCursor(+1) break } case 'k': { moveCursor(-1) break } case 'l': { open(false) break } case 'h': { open(true) break } } }) }