ycombinator-keys/keyboard-watcher.user.js
2025-04-20 04:24:31 -04:00

104 lines
3.6 KiB
JavaScript

// ==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('.reply a').href, '_self')
return
case 'R':
if (!isComment())
return
globalThis.open(query[selected].querySelector('.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 })