Similar behavior to RES
/*
==UserScript==
@name kbin nav
@match https://kbin.social/*
@version 1.0
==/UserScript==
*/
var css = [
".selected-dark {",
" background-color: darkslategray;",
" color: white;",
"}",
"",
".selected-light {",
" background-color: antiquewhite;",
"}"
].join("\n");
if (typeof GM_addStyle != "undefined") {
GM_addStyle(css);
} else if (typeof PRO_addStyle != "undefined") {
PRO_addStyle(css);
} else if (typeof addStyle != "undefined") {
addStyle(css);
} else {
var node = document.createElement("style");
node.type = "text/css";
node.appendChild(document.createTextNode(css));
var heads = document.getElementsByTagName("head");
if (heads.length > 0) {
heads[0].appendChild(node);
} else {
// no head yet, stick it whereever
document.documentElement.appendChild(node);
}
}
const selectedClass = document.body.classList.toString().includes("light") ? "selected-light" : "selected-dark";
let currentEntryIndex = 0;
let entries = document.querySelectorAll(".entry,.post,.comment");
let numEntries = entries.length;
const resizeObserver = new ResizeObserver(observerEntries => {
init();
});
resizeObserver.observe(document.body);
function init() {
entries = document.querySelectorAll(".entry,.post,.comment");
numEntries = entries.length;
if (entries && numEntries > 0) {
// If jumping to comment from anchor link
if (window.location.hash) {
const anchoredEntry = Array.from(entries).findIndex(entry => entry.id === window.location.hash.substring(1));
if (anchoredEntry > -1) {
selectEntry(anchoredEntry);
}
}
Array.from(entries).forEach(entry => {
entry.removeEventListener('click', handleClick, true);
entry.addEventListener('click', handleClick, true);
});
document.removeEventListener("keydown", handleKeydown);
document.addEventListener("keydown", handleKeydown);
}
}
function handleKeydown(event) {
if (["TEXTAREA", "INPUT"].indexOf(event.target.tagName) > -1) {
return;
}
let selectEntryIndex;
// j pressed
if (event.code === "KeyJ") {
// if shift key also pressed
if (event.shiftKey) {
let nextSameLevelEntry = getNextEntrySameLevel(currentEntryIndex);
if (nextSameLevelEntry) {
selectEntryIndex = nextSameLevelEntry;
}
} else {
if (currentEntryIndex + 1 < numEntries) {
selectEntryIndex = currentEntryIndex + 1;
}
}
}
// k pressed
if (event.code === "KeyK") {
// if shift key also pressed
if (event.shiftKey) {
let prevSameLevelEntry = getPrevEntrySameLevel(currentEntryIndex);
if (prevSameLevelEntry >= 0) {
selectEntryIndex = prevSameLevelEntry;
}
} else {
if (currentEntryIndex - 1 >= 0) {
selectEntryIndex = currentEntryIndex - 1;
}
}
}
if (selectEntryIndex >= 0) {
selectEntry(selectEntryIndex);
if (
entries[selectEntryIndex].getBoundingClientRect().top < 0 ||
entries[selectEntryIndex].getBoundingClientRect().bottom > window.innerHeight
) {
const y = entries[selectEntryIndex].getBoundingClientRect().top + window.pageYOffset - (document.getElementById("header").offsetHeight + 10);
window.scrollTo({
top: y
});
}
}
// x pressed
if (event.code === "KeyX") {
let previewButton = entries[currentEntryIndex].getElementsByClassName("show-preview");
if (previewButton.length > 0) {
previewButton[0].click();
}
}
}
function getNextEntrySameLevel(i) {
const parentId = entries[i].getAttribute("data-subject-parent-value");
let siblingIndex = i + 1;
while (siblingIndex < numEntries) {
if (entries[siblingIndex].getAttribute("data-subject-parent-value") === parentId) {
return siblingIndex;
}
// If no more child entries of same level, go to next root entry
const siblingParentId = entries[siblingIndex].getAttribute("data-subject-parent-value");
if (!siblingParentId || siblingParentId === "") {
return siblingIndex;
}
siblingIndex = siblingIndex + 1;
}
}
function getPrevEntrySameLevel(i) {
const parentId = entries[i].getAttribute("data-subject-parent-value");
let siblingIndex = i - 1;
while (siblingIndex >= 0) {
if (entries[siblingIndex].getAttribute("data-subject-parent-value") === parentId) {
return siblingIndex;
}
// If no more child entries of same level, go to next root entry
const siblingParentId = entries[siblingIndex].getAttribute("data-subject-parent-value");
if (!siblingParentId || siblingParentId === "") {
return siblingIndex;
}
siblingIndex = siblingIndex - 1;
}
}
function handleClick(event) {
const elementIndex = Array.from(entries).indexOf(event.currentTarget);
// Deselect if already selected, also ignore if clicking on any link/button
if (elementIndex === currentEntryIndex && entries[currentEntryIndex].classList.contains(selectedClass) &&
!(
event.target.parentElement.tagName.toLowerCase() === "button" || event.target.tagName.toLowerCase() === "button" ||
event.target.parentElement.tagName.toLowerCase() === "a" || event.target.tagName.toLowerCase() === "a"
)
) {
entries[currentEntryIndex].classList.remove(selectedClass);
} else {
selectEntry(elementIndex);
}
}
function selectEntry(i) {
entries[currentEntryIndex].classList.remove(selectedClass);
currentEntryIndex = i;
entries[currentEntryIndex].classList.add(selectedClass);
}


Updated so that comments are clickable and shift+j/k will take you to the next/previous comments of the same level if available. Added ability to navigate entries too. Only scroll into view if needed.