Hacker News - Highlight and navigate original poster's comments

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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);
  }

})();