Reddit: Keyboard comment navigation

Adds keyboard shortcuts for moving through the comments on a post — next and previous comment, parent, next thread — taking over reddit's own j/k.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name         Reddit: Keyboard comment navigation
// @namespace    https://github.com/jshute96/userscripts
// @version      1.1.1
// @description  Adds keyboard shortcuts for moving through the comments on a post — next and previous comment, parent, next thread — taking over reddit's own j/k.
// @author       Jeff Shute <[email protected]>
// @license      MIT
// @match        https://www.reddit.com/*
// @require      https://update.greasyfork.org/scripts/592123/1907419/keyboard-shortcuts.js
// @require      https://update.greasyfork.org/scripts/592124/1907421/keyboard-comment-nav.js
// @grant        none
// @run-at       document-idle
// @noframes
// ==/UserScript==

(function () {
  'use strict';

  const TAG = '[reddit nav]';

  if (window.__redditNavLoaded) {
    console.log(TAG, 'already loaded; skipping duplicate run');
    return;
  }
  window.__redditNavLoaded = true;

  console.log(TAG, 'initializing');

  function thingId(c) {
    return c.getAttribute('thingid') || '?';
  }

  CommentNav.create({
    tag: TAG,

    // Reddit binds its own j/k. Capture phase plus
    // stopImmediatePropagation on handled keys is what beats it.
    capture: true,

    // @match is the whole site so the script survives SPA navigation
    // into a thread; this gate decides whether to act.
    enabled: () => /\/comments\//.test(location.pathname),

    comments: () => [...document.querySelectorAll('shreddit-comment')],

    // Reddit ids each comment's body div `<thingid>-comment-rtjson-content`.
    // The shreddit-comment element itself wraps the entire subtree
    // (body + nested children + reply box) and stays intersecting the
    // viewport long after the text has scrolled past.
    body: c => document.getElementById(`${thingId(c)}-comment-rtjson-content`) || c,

    id: thingId,

    // Nested shreddit-comments live in their parent's light DOM (they
    // get slotted into the shadow DOM for rendering), so the light-DOM
    // ancestor chain is the comment tree.
    parentOf: c => c.parentElement?.closest('shreddit-comment') || null,

    // Reddit's top banner is sticky. It sets scroll-margin-top on
    // depth-0 comments itself, but not on nested replies or the
    // comments header, so we compute the offset for everything.
    //
    // Reddit declares the height as --shreddit-header-height, but on
    // <shreddit-app> rather than the document root — reading it off
    // :root silently yields "" and falls through to the default. Both
    // are checked so a future move back to :root also works; measured
    // against the live <reddit-header-large> the declared 56px is
    // accurate to a pixel.
    headerOffset: () => {
      for (const el of [document.querySelector('shreddit-app'),
                        document.documentElement]) {
        if (!el) continue;
        const n = parseFloat(getComputedStyle(el)
          .getPropertyValue('--shreddit-header-height').trim());
        if (Number.isFinite(n)) return n + 8;
      }
      return 56 + 8;
    },

    commentsTop: () => document.querySelector('shreddit-comments-sort-dropdown')
      || document.querySelector('shreddit-comment-tree-stats')
      || document.querySelector('shreddit-comment-tree'),
  });
})();