GitHub Diff Links

A userscript that adds links to diff and pull request headers to jump back & forth between files

As of 2018-05-10. See the latest version.

  1. // ==UserScript==
  2. // @name GitHub Diff Links
  3. // @version 1.2.10
  4. // @description A userscript that adds links to diff and pull request headers to jump back & forth between files
  5. // @license MIT
  6. // @author Rob Garrison
  7. // @namespace https://github.com/Mottie
  8. // @include https://github.com/*
  9. // @run-at document-idle
  10. // @grant GM_addStyle
  11. // @require https://greatest.deepsurf.us/scripts/28721-mutations/code/mutations.js?version=596445
  12. // @icon https://assets-cdn.github.com/pinned-octocat.svg
  13. // ==/UserScript==
  14. (() => {
  15. "use strict";
  16.  
  17. // sometimes tooltips are too narrow...
  18. // and move diff anchors below sticky header
  19. GM_addStyle(`
  20. .gh-diff-links:after { min-width: 120px; }
  21. a[name*="diff-"]:before {
  22. padding-top: 60px !important;
  23. margin-top: -60px !important;
  24. content: "";
  25. position: absolute;
  26. }`
  27. );
  28.  
  29. const button = document.createElement("a"),
  30. // button [ InnerHTML, tooltip ]
  31. nextBtn = ["Next", "Jump to next file\n("],
  32. prevBtn = ["Prev", "Jump to previous file\n(" ];
  33.  
  34. button.className = "btn btn-sm tooltipped tooltipped-s tooltipped-multiline gh-diff-links";
  35. button.setAttribute("rel", "nofollow");
  36.  
  37. function addButton(el, content, link) {
  38. let btn = button.cloneNode(),
  39. txt = el.classList.contains("select-menu-item") ?
  40. $(".description", el).textContent :
  41. link.textContent || "";
  42. // clean up whitespace
  43. txt = txt.replace(/\s+/g, " ").trim();
  44. // only add file name to tooltip
  45. txt = txt.substring(txt.lastIndexOf("/") + 1, txt.length);
  46. btn.innerHTML = content[0];
  47. btn.setAttribute("aria-label", content[1] + txt + ")" );
  48. btn.href = link.hash;
  49. // prepend button
  50. el.insertBefore(btn, el.childNodes[0]);
  51. }
  52.  
  53. function addSpace(el, content) {
  54. let btn = button.cloneNode();
  55. btn.disabled = true;
  56. btn.className = "btn btn-sm gh-diff-links disabled";
  57. btn.innerHTML = content[0];
  58. el.insertBefore(btn, el.childNodes[0]);
  59. }
  60.  
  61. function addLinks() {
  62. let last, temp,
  63. links = $$(".file-header .file-info a");
  64. if (links.length) {
  65. // links & file-actions "should" be the same length
  66. last = links.length - 1;
  67. $$(".file-actions").forEach((el, indx) => {
  68. // remove disabled buttons added before progressive
  69. // content has completed loading
  70. temp = $(".gh-diff-links.disabled", el);
  71. if (temp) {
  72. temp.parentNode.removeChild(temp);
  73. // remove both buttons to allow updating
  74. temp = $(".gh-diff-links", el);
  75. temp.parentNode.removeChild(temp);
  76. }
  77. if (!$(".gh-diff-links", el)) {
  78. if (indx === 0) {
  79. addButton(el, nextBtn, links[indx + 1]);
  80. addSpace(el, prevBtn);
  81. } else if (indx === last) {
  82. // add dummy "next" button to keep spacing
  83. addSpace(el, nextBtn);
  84. addButton(el, prevBtn, links[indx - 1]);
  85. } else {
  86. addButton(el, nextBtn, links[indx + 1]);
  87. addButton(el, prevBtn, links[indx - 1]);
  88. }
  89. }
  90. });
  91. }
  92. }
  93.  
  94. function init() {
  95. if ($("#files.diff-view") || $(".pr-toolbar")) {
  96. addLinks();
  97. }
  98. }
  99.  
  100. function $(selector, el) {
  101. return (el || document).querySelector(selector);
  102. }
  103.  
  104. function $$(selector, el) {
  105. return Array.from((el || document).querySelectorAll(selector));
  106. }
  107.  
  108. // DOM targets - to detect GitHub dynamic ajax page loading
  109. document.addEventListener("ghmo:container", init);
  110. document.addEventListener("ghmo:diff", addLinks);
  111. init();
  112.  
  113. })();