Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
const historyJump_regexpOldid = new RegExp('\\&(amp;)?oldid\\=(\\d+)');
const historyJump_regexpDiff = new RegExp('\\&diff=(\\d+)');
const historyJump_regexpRevisionCount = new RegExp('\\((One|\\d+) intermediate revisions? by (one|\\d+|more than \\d+) users? not shown\\)');

const historyJump_regexpUnderscore = new RegExp('[_]', 'g');
const historyJump_regexpIso8601Char = new RegExp('[-T:Z]', 'g');

// options
var historyJump_replaceHistoryTab; // if relevant, hide and replace History tab
if (historyJump_replaceHistoryTab === undefined) historyJump_replaceHistoryTab = false;
var historyJump_alwaysDefaultDiff; // assume default range from revId1 to revId2, skip options
if (historyJump_alwaysDefaultDiff === undefined) historyJump_alwaysDefaultDiff = true;

var historyJump_minRevCount; // minimum number of revisions to display
if (historyJump_minRevCount === undefined) historyJump_minRevCount = 10;

// extracted from URL or body
var historyJump_revIds;
var historyJump_revCount;

// fetched from API
var historyJump_revIdToTimestamp;

function historyJump_showForm() {
  if (!wfSupportsAjax()) {
    jsMsg('<span class="error">Your browser does not seem to support AJAX, which is required for the historyJump script.</span>');
    return;
  }

  form = '<div id="historyJump_initialform">'+
    "<strong><a id='historyJump_link'>Relevant history</a></strong>" +
    '</div>';

  jsMsg(form);
  document.getElementById('historyJump_link').href = historyJump_calculateLink(historyJump_minRevCount);
}

function historyJump_getRevIdsFromUrl(url) {
  var revIds = new Array();
  match = historyJump_regexpOldid.exec(url);
  if (match != null) {
    revIds.push(match[2]);
  }
  match = historyJump_regexpDiff.exec(url);
  if (match != null) {
    revIds.push(match[1]);
  }
  return revIds;
}

function historyJump_getRevIdsFromDocument() {
  var revIds = new Array();
  eo = document.getElementById('mw-diff-otitle1');
  if (eo != null) {
    match = historyJump_regexpOldid.exec(eo.innerHTML);
    if (match != null) {
      revIds.push(match[2]);
    }
  }
  en = document.getElementById('mw-diff-ntitle1');
  if (en != null) {
    match = historyJump_regexpOldid.exec(en.innerHTML);
    if (match != null) {
      revIds.push(match[2]);
    }
  }
  return revIds;
}

/*
 * For permanent links and diffs, false indicates cross-page diff.
 * Special pages will usually return false.
 */
function comparePageNameAndFirstHeading() {
  return wgPageName.replace(historyJump_regexpUnderscore, ' ') == document.getElementById('firstHeading').lastChild.innerHTML;
}

function historyJump_calculateRevCount() {
  if (historyJump_revIds.length == 2) {
    if (historyJump_revIds[0] == historyJump_revIds[1]) {
      return 1;
    }
    if (!comparePageNameAndFirstHeading()) {
      // cross-page diff
      return -1;
    }
    body = document.getElementById('bodyContent');
    if (body != null) {
      match = historyJump_regexpRevisionCount.exec(body.innerHTML);
      if (match != null) {
        var count;
        if (match[1] == "One") {
          count = 1;
        } else {
          count = Number(match[1]);
        }
        return count + 2; // exclusive
      }
    }
    return 2; // adjacent revisions
  }
  return -1; // not known, usually a permanent link
}

function historyJump_injectDiffRangeLink() {
  if (historyJump_revCount <= 2) {
    return false;
  }

  body = document.getElementById('bodyContent');
  if (body != null) {
    match = historyJump_regexpRevisionCount.exec(body.innerHTML);
    if (match != null) {
      body.innerHTML = body.innerHTML.replace(match[0], '<a id="historyJump-diffrange">' + match[0] + '</a>');
      var dr = document.getElementById('historyJump-diffrange');
      dr.title = 'Show history range';
      dr.href = historyJump_calculateLink(0);
      return true;
    }
  }
  return false;
}

function historyJump_calculateLink(minRevCount) {
  var minTs = Number.MAX_VALUE;
  for (i = 0; i < historyJump_revIds.length; i++) {
    ts = historyJump_revIdToTimestamp[historyJump_revIds[i]];
    if (ts < minTs)
      minTs = ts;
  }
  minTs -= 1; // exclusive
  return wgScriptPath + '/index.php?title=' + encodeURIComponent(mw.config.get('wgPageName')) +
         '&dir=prev&offset=' + minTs +
         '&limit=' + Math.max(historyJump_revCount, minRevCount) + '&action=history';
}

function stripIso8601Formatting(dateIso) {
  return Number(dateIso.replace(historyJump_regexpIso8601Char, ''));
}

function historyJump_getTimestamps(revIds) {
  var revIdToTimestamp = new Object();
  if (revIds.length == 0) {
    return revIdToTimestamp;
  }
  revIdStr = '';
  for (i = 0; i < revIds.length; i++) {
    revIdStr += revIds[i] + '|';
  }
  revIdStr = revIdStr.substring(0, revIdStr.length-1);

  var req = sajax_init_object();
  req.open("GET", wgScriptPath + "/api.php?action=query&prop=revisions&rvprop=ids|timestamp&format=json&revids="+revIdStr, false);
  req.send(null);
  var response = eval('(' + req.responseText + ')');

  var pages = response['query']['pages'];
  var pageCount = 0;
  for (var pageId in pages) {
    pageCount++;
    revisions = pages[pageId]['revisions'];
    for (j = 0; j < revisions.length; j++) {
      revIdToTimestamp[revisions[j]['revid']] = stripIso8601Formatting(revisions[j]['timestamp']);
    }
  }
  revIdToTimestamp['pageCount'] = pageCount;

  delete req;
  return revIdToTimestamp;
}

function historyJump_testAndAddLink() {
  historyJump_revIds = historyJump_getRevIdsFromUrl(document.URL);
  if (historyJump_revIds.length < 2) {
    var revIds = historyJump_getRevIdsFromDocument();
    if (historyJump_revIds.length < revIds.length) {
      historyJump_revIds = revIds;
    }
  }
  historyJump_revCount = historyJump_calculateRevCount();

  if (historyJump_revIds.length > 0) {
    historyJump_revIdToTimestamp = historyJump_getTimestamps(historyJump_revIds);

    nextnode = null;
    if (historyJump_replaceHistoryTab) {
      // disable, hide History tab
      historyTab = document.getElementById('ca-history');
      historyTab.disabled = true;
      historyTab.style.display = 'none';
      nextnode = historyTab.nextSibling;
    }

    var link;
    if (historyJump_revCount > 0) {
      // historyJump_alwaysDefaultDiff disabled
      historyJump_injectDiffRangeLink();
    }
    if (link === undefined)
      link = "javascript:historyJump_showForm()";

    mw.util.addPortletLink("p-cactions", link, "History Jump", "ca-historyJump", "Jump to relevant history", null, nextnode);
  }
}

addOnloadHook(historyJump_testAndAddLink);