// ==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/brian6932/ycombinator-keys/raw/branch/main/keyboard-watcher.user.js // @updateURL https://gitea.amine-bouabdallaoui.fr/brian6932/ycombinator-keys/raw/branch/main/keyboard-watcher.user.js // ==/UserScript== // jshint esversion: 11 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(), selected = -1 const // This is a Firefox only option. For some reason older versions of Firefox can't set outline: none. focusInvisible = { __proto__: null, focusVisible: false }, isComment = () => query[selected].classList.contains('comtr'), highlightSelected = () => { query[selected].style.boxShadow = '0px 0px 10px 4px rgba(0,0,0,0.73)' isComment() ? // 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() if (selected + 1 <= query.length) 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 'r': if (!isComment()) return globalThis.open(query[selected].querySelector('div.reply a').href, '_self') return case 'R': if (!isComment()) return globalThis.open(query[selected].querySelector('div.reply a').href) return case 'u': globalThis.open((isComment() ? query[selected] : query[selected].nextSibling).querySelector('a.hnuser').href, '_self') return case 'U': globalThis.open((isComment() ? query[selected] : 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 })