GitHub Diff Expander

A userscript that adds more diff code expanding buttons

目前为 2018-10-05 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name GitHub Diff Expander
  3. // @version 0.1.1
  4. // @description A userscript that adds more diff code expanding buttons
  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 none
  11. // @require https://greasyfork.org/scripts/28721-mutations/code/mutations.js?version=634242
  12. // @icon https://assets-cdn.github.com/pinned-octocat.svg
  13. // ==/UserScript==
  14. (() => {
  15. "use strict";
  16.  
  17. // A max of 20 (0-19) rows are loaded; set by GitHub server
  18. const expandMax = 20;
  19. // The expand server call expects these parameters; but they may have no
  20. // setting (first expander), or be completely missing (last expander)
  21. const order = [
  22. "prev_line_num_left",
  23. "next_line_num_left",
  24. "prev_line_num_right",
  25. "next_line_num_right"
  26. ];
  27. const regexp = order.reduce((acc, line) => {
  28. acc[line] = new RegExp(`${line}=\\d*`);
  29. return acc;
  30. }, {});
  31.  
  32. const span = document.createElement("span");
  33. let busy = false;
  34.  
  35. function parse(num) {
  36. return Number(num) || 0;
  37. }
  38.  
  39. function getRange(tr, isExpander) {
  40. let range = [];
  41. if (isExpander) {
  42. const link = $("a.diff-expander", tr);
  43. // Using data-(left/right)-range because the data-url may not include all
  44. // the values
  45. [range[0], range[1]] = link.dataset.leftRange.split("-").map(parse);
  46. [range[2], range[3]] = link.dataset.rightRange.split("-").map(parse);
  47. // but dataset range[0] is equal to prev_line_num_left after expanding.
  48. // To get the correct number, the start needs to be increased by two
  49. const prev = tr.previousElementSibling;
  50. if (prev && prev.classList.contains("blob-expanded")) {
  51. range[0] = range[0] + 2;
  52. range[2] = range[2] + 2;
  53. }
  54. } else {
  55. range[0] = range[1] = parse($("td:first-child", tr).dataset.lineNumber);
  56. range[2] = range[3] = parse($("td:nth-child(2)", tr).dataset.lineNumber);
  57. }
  58. return range;
  59. }
  60.  
  61. function removeExpanders(file) {
  62. $$("tr.ghdex-expander", file).forEach(tr => {
  63. tr.parentNode.removeChild(tr);
  64. });
  65. }
  66.  
  67. function findGaps(file) {
  68. let left = 0;
  69. let right = 0;
  70. removeExpanders(file);
  71. // It's not efficient, but cycling through every row was found to be the
  72. // most reliable method of getting an accurate expander range
  73. $$("tr", file).forEach(row => {
  74. const isExpander = row.classList.contains("js-expandable-line");
  75. const range = getRange(row, isExpander);
  76. if (isExpander) {
  77. let max = left + expandMax;
  78. if (typeof row.dataset.position === "undefined") {
  79. // Last expander row data-url parameter doesn't include either
  80. // "next_line_num_(left/right)"
  81. return updateExpander(row, [], [
  82. range[0],
  83. Math.min(range[1], range[0] + expandMax - 1)
  84. ]);
  85. } else if (max > range[1] && range[1] - left < expandMax) {
  86. range[0] = Math.max(1, left, range[0]);
  87. range[2] = Math.max(1, right, range[2]);
  88. } else if (range[1] > max && left < range[1] - expandMax) {
  89. addExpander(row, [left, max, right, right + expandMax]);
  90. } else if (range[1] - expandMax > left) {
  91. range[0] = left;
  92. range[2] = right;
  93. }
  94. const text = [
  95. Math.max(1, range[0], range[1] - expandMax + 1),
  96. range[1],
  97. Math.max(1, range[2], range[3] - expandMax + 1),
  98. range[3]
  99. ];
  100. updateExpander(row, range, text);
  101. } else {
  102. left = range[0];
  103. right = range[2];
  104. }
  105. });
  106. }
  107.  
  108. function updateExpander(tr, range, text = range) {
  109. const expander = $("a.diff-expander", tr);
  110. if (range.length) {
  111. expander.dataset.leftRange = range.slice(0, 2).join("-");
  112. expander.dataset.rightRange = range.slice(2).join("-");
  113. let result = expander.dataset.url;
  114. range.forEach((value, index) => {
  115. // prev_line_num_(left/right) requires an empty value to get line 1 (0)
  116. // next_line_num_(left/right) requires the *next* line number + 1, so a
  117. // range of 1-20 would have [0, 21, 0, 21] and a range of 20-40 would be
  118. // [20, 41, 20, 41]
  119. result = replacer(result, order[index], value + index % 2);
  120. });
  121. expander.dataset.url = result;
  122. }
  123. // The last expander won't include text[2] & text[3] values
  124. const [start, end] = text;
  125. const txt = start > end ? `${end}-${end}` : `${start}-${end}`;
  126. let el = $("span", expander);
  127. if (el) {
  128. el.textContent = txt;
  129. } else {
  130. span.textContent = txt;
  131. expander.insertAdjacentElement("beforeend", span.cloneNode(true));
  132. }
  133. }
  134.  
  135. function replacer(url, order, value) {
  136. // The first expander will require a key with no value to load the first
  137. // line, i.e. "...&prev_line_num_left=&prev_line_num_right=&..."
  138. const result = `${order}=${value <= 1 ? "" : value}`;
  139. const regex = regexp[order];
  140. // The last expander row doesn't include next_line_num_(left/right);
  141. // it'll need to be added
  142. return regex.test(url) ? url.replace(regex, result) : url + result;
  143. }
  144.  
  145. function addExpander(tr, range) {
  146. const expander = tr.cloneNode(true);
  147. expander.classList.add("ghdex-expander");
  148. $("td.blob-code", expander).textContent = "\xA0";
  149. expander.removeAttribute("data-position");
  150. tr.parentNode.insertBefore(expander, tr);
  151. const text = [range[0] + 1, range[1], range[2] + 1, range[3]];
  152. updateExpander(expander, range, text);
  153. }
  154.  
  155. function processFiles() {
  156. busy = true;
  157. $$(".diff-table").forEach(findGaps);
  158. setTimeout(() => {
  159. busy = false;
  160. }, 500);
  161. }
  162.  
  163. function init(e) {
  164. if (!busy && $("#diff-0")) {
  165. if (e) {
  166. setTimeout(processFiles, 100);
  167. } else {
  168. processFiles();
  169. }
  170. }
  171. }
  172.  
  173. function $(selector, el) {
  174. return (el || document).querySelector(selector);
  175. }
  176.  
  177. function $$(selectors, el) {
  178. return [...(el || document).querySelectorAll(selectors)];
  179. }
  180.  
  181. document.addEventListener("ghmo:container", init);
  182. document.addEventListener("ghmo:diff", init);
  183. init();
  184.  
  185. })();