Keyboard navigation for LetsRun.com
// ==UserScript==
// @name LRC Keyboard Nav
// @namespace https://habs.sdf.org
// @description Keyboard navigation for LetsRun.com
// @match https://www.letsrun.com/forum*
// @version 1.1
// @grant none
// @license AGPLv3
// ==/UserScript==
(() => {
const keys = {
UP: 'k',
DOWN: 'j',
LEFT: 'h',
RIGHT: 'l',
NEXT: 'p',
BACK: 'y',
}
const VERT = [keys.UP, keys.DOWN];
const HORZ = [keys.LEFT, keys.RIGHT];
const DIRS = [...VERT, ...HORZ];
const BORDER = '3px solid red';
const mode = {
'/forum': 'thread',
'/forum/flat_read.php': 'post',
}[window.location.pathname];
const selector = {
'thread': '.thread',
'post': '.forum-post-container',
};
if (!mode) return;
let headerHeight;
const items = document.querySelectorAll(selector[mode]);
let selIdx = 0;
items[selIdx].style.border = BORDER;
document.addEventListener('keypress', e => {
if (!headerHeight) headerHeight = document.querySelector('.page-container').getBoundingClientRect().height;
if (document.activeElement.tagName === 'INPUT' || document.activeElement.classList.contains('rx-editor')) return;
const [ voteUp, voteDown ] = items[selIdx].querySelectorAll('.forum-post-vote-button');
if (VERT.includes(e.key)) items[selIdx].style.border = '';
switch (e.key) {
case keys.UP: selIdx--; break;
case keys.DOWN: selIdx++; break;
case keys.RIGHT:
if (mode === 'thread') window.location.href = items[selIdx].querySelector('.subject > a').href;
else voteDown.click();
break;
case keys.LEFT: if (mode === 'post') voteUp.click(); break;
case keys.NEXT: [...document.querySelectorAll('.dbr-paginated-thread')].at(-1).click(); break;
case keys.BACK: window.location.href = 'https://www.letsrun.com/forum'; break;
}
if (VERT.includes(e.key)) {
if (selIdx < 0) selIdx = 0;
if (selIdx >= items.length) selIdx = items.length - 1;
items[selIdx].style.border = BORDER;
const rect = items[selIdx].getBoundingClientRect();
if (rect.top < headerHeight) { items[selIdx].scrollIntoView(true); window.scrollBy(0, -headerHeight); }
if (rect.bottom > window.innerHeight) items[selIdx].scrollIntoView(false);
}
});
})();