Greasy Fork is available in English.

Hacker News - Highlight and navigate original poster's comments

Highlight original poster comments on Hackers News and navigate them with the keyboard

// ==UserScript==
// @name        Hacker News - Highlight and navigate original poster's comments
// @description Highlight original poster comments on Hackers News and navigate them with the keyboard
// @namespace   valacar
// @author      valacar
// @include     https://news.ycombinator.com/item?id=*
// @version     0.3
// @grant       none
// @noframes
// @license     MIT
// @compatible  firefox Firefox
// @compatible  chrome Chrome
// ==/UserScript==

(function() {
  'use strict';

  const ENABLE_KEYBOARD_NAVIGATION = true;
  const VERTICAL_CENTER_PERCENT = 0.25; // percentage from the top

  function appendStyle(cssString) {
    const parent = document.head || document.documentElement;
    if (parent) {
      const style = document.createElement("style");
      style.setAttribute("type", "text/css");
      style.textContent = cssString;
      parent.appendChild(style);
    }
  }

  appendStyle(
`
.originalPoster,
.opPostCountInfo
{
  background: #ffc;
}
.currentHighlight
{
  background: linear-gradient(to right, #ffc 66%, transparent);
}
`);

  const posterUserName = document.querySelector('.subtext a').textContent;
  let opPostCount = 0;

  if (posterUserName) {
    const commentUserLinks = document.querySelectorAll('.comhead a');

    if (commentUserLinks) {
      for (let i = 0; i < commentUserLinks.length; i++) {
        let commentUserName = commentUserLinks[i].textContent;
        if (posterUserName === commentUserName) {
          opPostCount++;
          commentUserLinks[i].classList.add('originalPoster');
          commentUserLinks[i].id = 'op-post-' + opPostCount;
        }
      }
    }
  }

  if (opPostCount > 0) {
    const newSpan = document.createElement('span');
    newSpan.textContent = ' (' + opPostCount + ' by original poster)';
    newSpan.className = 'opPostCountInfo';

    if (ENABLE_KEYBOARD_NAVIGATION) {
      newSpan.setAttribute('title', "Use the left/right arrow keys\n(or n/N, or n/p) to scroll to OP comments.");
      newSpan.setAttribute('style', "cursor: help;");
    }

    const subText = document.querySelectorAll('.subtext a[href^="item?id="')[1];
    subText.appendChild(newSpan);
  }

  // http://stackoverflow.com/questions/8922107/javascript-scrollintoview-middle-alignment
  Element.prototype.documentOffsetTop = function () {
    return this.offsetTop + (this.offsetParent ? this.offsetParent.documentOffsetTop() : 0);
  };

  function ScrollToID(id) {
    const target = document.getElementById(id);
    const top = target.documentOffsetTop() - (window.innerHeight * VERTICAL_CENTER_PERCENT);
    window.scrollTo(0, top);
    const oldCurrent = document.querySelector(".currentHighlight");
    if (oldCurrent) {
      oldCurrent.classList.remove("currentHighlight");
    }
    // XXX: not the best idea to blindly go back two parents
    target.parentNode.parentNode.classList.add("currentHighlight");
  }

  if (ENABLE_KEYBOARD_NAVIGATION) {
    let num = 0;

    window.addEventListener("keydown", function (event) {
      // Should do nothing if the key event was already consumed.
      if (event.defaultPrevented) {
        return;
      }

      // don't break alt-left and alt-right history navigation
      // and exit if textarea is in focus
      if (event.altKey || /(input|textarea)/i.test(document.activeElement.nodeName)) {
        return;
      }

      switch (event.key) {
        case "n":
        case "ArrowRight":
        case "Right":
          if (num < opPostCount) {
            num++;
          }
          break;

        case "N":
        case "p":
        case "ArrowLeft":
        case "Left":
          if (num > 1) {
            num--;
          }
          break;

        default:
          return; // Quit when this doesn't handle the key event.
      }

      // Consume the event for suppressing "double action".
      event.preventDefault();

      //console.log('num = ' + num + ' focus = ' + document.activeElement.tagName);
      ScrollToID("op-post-" + num);

    }, true);
  }

})();