Add Keyboard Shortcut for Generic Next/Previous Page

Add CTRL+ArrowLeft and CTRL+ArrowRight for generic next/previous page. It will click the last found link/button whose text starts/ends with e.g. "Next", "Prev", "Back", or "Previous".

As of 2022-01-03. See the latest version.

// ==UserScript==
// @name        Add Keyboard Shortcut for Generic Next/Previous Page
// @namespace   AddKeyboardShortcutForGenericNextPreviousPage
// @version     1.0.15
// @license     GNU AGPLv3
// @author      jcunews
// @description Add CTRL+ArrowLeft and CTRL+ArrowRight for generic next/previous page. It will click the last found link/button whose text starts/ends with e.g. "Next", "Prev", "Back", or "Previous".
// @website     https://greasyfork.org/en/users/85671-jcunews
// @include     *://*/*
// @include     *:*
// @grant       none
// ==/UserScript==

/*
The link/button text more specifically, are those which starts with (non case sesitive) "Next", "Prev", "Previous";
or ends with "Prev", "Back", "Previous", "Next". e.g. "Next", "> Next", "Next Page", "Prev", "< Prev", "< Previous", etc.
but not "< Prev Page" because the word "prev" or "previous" is not at the start/end of text.

This script doesn't take into account of links whose contents is an image rather than text, or whose text is a CSS text contents.

If next/previous navigation link is specified in the HTML metadata, it will be used as a priority.
*/

(function(rxPrev, rxNext, ts) {
  rxPrevious = /\u00ab|\b(?:back|prev(ious)?)\b/i;
  rxNext = /\u00bb|\bnext\b/i;
  rxCarousel = /carousel/i;

  addEventListener("keydown", function(ev, e) {

    function clickLink(rx, e, i, l, r, rx2) {
      rx2 = new RegExp("[a-z].*?" + rx.source, "i");
      e = document.querySelectorAll('a,button,input[type="button"],input[type="submit"]');
      for (i = e.length - 1; i >= 0; i--) {
        if (
          (
            ((e[i].tagName === "A") && rx.test(e[i].rel) && !rx2.test(e[i].rel)) ||
            ((e[i].tagName === "A") && Array.from(e[i].classList).some(cl => rx.test(cl) && !rx2.test(cl))) ||
            ((e[i].tagName === "INPUT") && rx.test(e[i].value) && !rx2.test(e[i].value)) ||
            (rx.test(e[i].textContent) && !rx2.test(e[i].textContent))
          ) && (!rxCarousel.test(e[i].className))
        ) {
          ev.preventDefault();
          e[i].click();
          return true;
        }
      }
      return false;
    }

    if (ev.ctrlKey && !ev.altKey && !ev.shiftKey) {
      if (document.activeElement && (
        (/^(INPUT|TEXTAREA)$/).test(document.activeElement.tagName) ||
        document.activeElement.isContentEditable)) return;
      switch (ev.key) {
        case "ArrowLeft": //previous
          if (e = document.querySelector('link[rel="prev"][href]')) {
            ev.preventDefault();
            location.href = e.href;
            return;
          }
          if (clickLink(rxPrevious)) return;
          break;
        case "ArrowRight": //next
          if (e = document.querySelector('link[rel="next"][href]')) {
            ev.preventDefault();
            location.href = e.href;
            return;
          }
          if (clickLink(rxNext)) return;
          break;
      }
    }
  }, true);
})();