Add repo/branch links to GitHub's "Comparing Changes" page

This adds a link to the fork's branch on the GitHub "Comparing changes" page (AKA the "Create a Pull Request" page). See https://stackoverflow.com/questions/77623282/how-do-i-get-my-remote-branch-url-from-the-github-create-pull-request-page for more details.

// ==UserScript==
// @name         Add repo/branch links to GitHub's "Comparing Changes" page
// @namespace    http://tampermonkey.net/
// @version      0.8
// @description  This adds a link to the fork's branch on the GitHub "Comparing changes" page (AKA the "Create a Pull Request" page). See https://stackoverflow.com/questions/77623282/how-do-i-get-my-remote-branch-url-from-the-github-create-pull-request-page for more details.
// @author       DanKaplanSES
// @match        https://github.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=github.com
// @require      https://code.jquery.com/jquery-3.7.1.min.js
// @grant        none
// @license MIT
// ==/UserScript==

// Official update link: https://greasyfork.org/en/scripts/481652/versions/new

const browseRepoHtml = `<div class="browse-repo"><a href="#" target="_blank">Browse Repo</a></div>`;
const browseBranchHtml = `<div class="browse-branch"><a href="#" target="_blank">Browse Branch</a></div>`;

jQuery.noConflict(true)(function ($) {
  function getEvents(jQueryElement) {
    const element = jQueryElement.get(0);
    const elemEvents = $._data(element, "events");
    const allDocEvnts = $._data(document, "events");
    function equalEvents(evt1, evt2) {
      return evt1.guid === evt2.guid;
    }

    for (let evntType in allDocEvnts) {
      if (allDocEvnts.hasOwnProperty(evntType)) {
        const evts = allDocEvnts[evntType];
        for (let i = 0; i < evts.length; i++) {
          if ($(element).is(evts[i].selector)) {
            if (elemEvents == null) {
              elemEvents = {};
            }
            if (!elemEvents.hasOwnProperty(evntType)) {
              elemEvents[evntType] = [];
            }
            if (
              !elemEvents[evntType].some(function (evt) {
                return equalEvents(evt, evts[i]);
              })
            ) {
              elemEvents[evntType].push(evts[i]);
            }
          }
        }
      }
    }
    return elemEvents;
  }

  $.fn.onFirst = function (name, fn) {
    this.bind(name, fn);
    for (let i = 0, _len = this.length; i < _len; i++) {
      let elem = this[i];
      let handlers = $._data(elem).events[name.split(".")[0]];
      handlers.unshift(handlers.pop());
    }
  };

  let hrefs = [];

  function modifyWhenReady(modificationFunction, isReady) {
    function modifyIfReady() {
      if (isReady()) {
        modificationFunction();
      }
    }
    modifyIfReady();
    setInterval(modifyIfReady, 1500);
  }

  function modifyRangeEditor() {
    hrefs = [];
    $(`.range-cross-repo-pair details`).wrap(
      `<div class="details-container"></div>`
    );

    const tempHrefs = [];
    $(`.details-container`).each(function (index) {
      const containerType =
        $(this).find(`summary.branch`).length > 0 ? `branch` : `repo`;
      const html = containerType === `repo` ? browseRepoHtml : browseBranchHtml;
      const href = hrefString(containerType, index);
      const containerContentsHidden = $(this).find(`details`).is(`:hidden`);
      $(this)
        .css({ display: containerContentsHidden ? `none` : `inline-block` })
        .append(html)
        .css({ "text-align": `center` })
        .find(`a`)
        .attr({ id: `browse-link-${index}`, href });
      tempHrefs.push(href);
    });

    $(`.range-editor .pre-mergability, .range-editor .d-inline-block`).css({
      "vertical-align": `top`,
    });

    hrefs = tempHrefs; // Only assigned when the loop has finished. This ensures modifyFiles only runs when all hrefs exist.
  }

  function hrefString(containerType, index) {
    switch (containerType) {
      case "repo":
        return `/` + $(`.css-truncate-target`)[index].textContent;
      case "branch":
        return (
          `/` +
          $(`.css-truncate-target`)[index - 1].textContent +
          `/tree/` +
          $(`.css-truncate-target`)[index].textContent
        );
      default:
        throw new Error(`Unexpected containerType: ${containerType}`);
    }
  }

  function modifyFiles() {
    const hrefOfBranchWithChanges = hrefs[hrefs.length - 1];
    hrefs = [];
    $(`#files a.Link--primary`).each(function (index) {
      const hrefToFile = $(this).attr("title");
      const detailsMenu = $(this).parents(`.file-header`).find(`details-menu`);

      const viewFileButton = detailsMenu.find(`a:contains('View file')`);
      const viewFileHref = hrefOfBranchWithChanges + "/" + hrefToFile;
      viewFileButton.replaceWith(
        $(
          `<a href="${viewFileHref}" class="pl-5 dropdown-item btn-link" target="_blank">View file</a>`
        )
      );

      const editFileButton = detailsMenu.find(`button:contains('Edit file')`);
      const editFileHref =
        hrefOfBranchWithChanges.replace(/\/tree\//, `/edit/`) +
        "/" +
        hrefToFile;
      editFileButton.replaceWith(
        $(
          `<a href="${editFileHref}" class="pl-5 dropdown-item btn-link" target="_blank">Edit file</a>`
        )
      );
    });
  }

  function resetRangeEditor() {
    $(`.range-cross-repo-pair details`).each(function (index) {
      const parent = $(this).parents(`.range-cross-repo-pair`);
      parent.append($(this)); // make $(this) a direct child
    });

    $(`.details-container`).remove();
  }

  modifyWhenReady(modifyRangeEditor, () => {
    return (
      location.pathname.indexOf(`/compare/`) !== -1 &&
      $(`#browse-link-0`).length === 0 &&
      $(`.range-cross-repo-pair details`).length > 0
    );
  });

  modifyWhenReady(modifyFiles, () => {
    return (
      location.pathname.indexOf(`/compare/`) !== -1 &&
      $(`#files`).length > 0 &&
      hrefs.length > 0
    );
  });

  modifyWhenReady(
    () => {
      $(`.js-toggle-range-editor-cross-repo`).onFirst(
        "click",
        resetRangeEditor
      );
    },
    () => {
      return (
        location.pathname.indexOf(`/compare/`) !== -1 &&
        getEvents($(`.js-toggle-range-editor-cross-repo`)) === undefined
      );
    }
  );

  $(`.js-toggle-range-editor-cross-repo`).onFirst("click", resetRangeEditor);
});