// ==UserScript== // @name ycombinator-keys // @namespace https://gitea.amine-bouabdallaoui.fr/AmineB/ycombinator-keys // @license gpl-3.0-only // @match http*://news.ycombinator.com/* // @icon https://gitea.amine-bouabdallaoui.fr/AmineB/ycombinator-keys/raw/branch/main/icons/48.png // @grant none // @version 4 // @author AmineB // @description Ycombinator keyboard nav. // @downloadURL https://gitea.amine-bouabdallaoui.fr/AmineB/ycombinator-keys/raw/branch/main/keyboard-watcher.js // @updateURL https://gitea.amine-bouabdallaoui.fr/AmineB/ycombinator-keys/raw/branch/main/keyboard-watcher.js // ==/UserScript== // jshint esversion: 11 let selected = -1 globalThis.document.styleSheets[0].insertRule('tbody tr.athing>td.title>span.titleline>a:focus,tbody tr.athing.comtr a.togg.clicky:focus{outline: none}') const requery = () => globalThis.document.querySelectorAll('tbody tr.athing') let query = requery() const // This is a Firefox only option. For some reason older versions of Firefox can't set outline: none. focusInvisible = { __proto__: null, focusVisible: false }, highlightSelected = () => { query[selected].style.boxShadow = '0px 0px 10px 4px rgba(0,0,0,0.73)' globalThis.location.pathname === '/item' ? // Drawing the boxShadow on this selector isn't visible enough. query[selected].querySelector('a.togg.clicky')?.focus(focusInvisible) : // Focuses link without re-query. query[selected].lastChild.firstChild.firstChild.focus(focusInvisible) }, keydown = event => { switch (event.key) { case 'j': if (selected + 1 === query.length) return if (selected !== -1) query[selected].style.boxShadow = '' while (selected < query.length && query[++selected].classList.contains('noshow')); highlightSelected() return case 'k': if (selected <= 0) return query[selected].style.boxShadow = '' while (selected >= 0 && query[--selected].classList.contains('noshow')); highlightSelected() return case 'h': if (globalThis.location.pathname === '/hidden') for (const selector of query[selected].nextSibling.querySelectorAll('a')) if (selector.innerText === 'un-hide') { selector.click() break } else query[selected].nextSibling.querySelector('a.clicky.hider')?.click() highlightSelected() return case 'c': globalThis.open('https://news.ycombinator.com/item?id=' + query[selected].id, '_self') return case 'C': globalThis.open('https://news.ycombinator.com/item?id=' + query[selected].id) return case 'u': globalThis.open(globalThis.location.pathname === '/item' ? query[selected].querySelector('a.hnuser').href : query[selected].nextSibling.querySelector('a.hnuser').href, '_self') return case 'U': globalThis.open(globalThis.location.pathname === '/item' ? query[selected].querySelector('a.hnuser').href : query[selected].nextSibling.querySelector('a.hnuser').href) } }, listen = () => globalThis.document.addEventListener('keydown', keydown) listen() // This allows for compatibility with automatic pagination scripts. new globalThis.MutationObserver(mutations => { for (const mutation of mutations) if (mutation.type === 'childList') { globalThis.document.removeEventListener('keydown', keydown) query = requery() return listen() } }).observe(globalThis.document.body, { __proto__: null, childList: true, subtree: true })