GitHub Diff File Toggle

A userscript that adds a toggle to show or hide diff files

As of 2016-06-09. See the latest version.

  1. // ==UserScript==
  2. // @name GitHub Diff File Toggle
  3. // @version 1.1.0
  4. // @description A userscript that adds a toggle to show or hide diff files
  5. // @license https://creativecommons.org/licenses/by-sa/4.0/
  6. // @namespace https://github.com/StylishThemes
  7. // @include https://github.com/*
  8. // @grant GM_addStyle
  9. // @grant GM_getValue
  10. // @grant GM_setValue
  11. // @grant GM_registerMenuCommand
  12. // @run-at document-end
  13. // @author StylishThemes
  14. // ==/UserScript==
  15. /* global GM_addStyle, GM_getValue, GM_setValue, GM_registerMenuCommand */
  16. (function() {
  17. "use strict";
  18. /*
  19. This code is also part of the GitHub-Dark Script (https://github.com/StylishThemes/GitHub-Dark-Script)
  20. Extracted out into a separate userscript in case users only want to add this functionality
  21. */
  22. var busy = false,
  23.  
  24. icon = [
  25. "<svg class='octicon' xmlns='http://www.w3.org/2000/svg' width='10' height='6.5' viewBox='0 0 10 6.5'>",
  26. "<path d='M0 1.497L1.504 0l3.49 3.76L8.505.016 10 1.52 4.988 6.51 0 1.496z'/>",
  27. "</svg>",
  28. ].join(""),
  29.  
  30. // Add file diffs toggle
  31. addFileToggle = function() {
  32. busy = true;
  33. var el, button,
  34. files = document.querySelectorAll("#files .file-actions"),
  35. indx = files.length;
  36. while (indx--) {
  37. el = files[indx];
  38. if (!el.querySelector(".ghd-file-toggle")) {
  39. button = document.querySelector("button");
  40. button.type = "button";
  41. button.className = "ghd-file-toggle btn btn-sm tooltipped tooltipped-n";
  42. button.setAttribute("aria-label", "Click to Expand or Collapse file");
  43. button.setAttribute("tabindex", "-1");
  44. button.innerHTML = icon;
  45. el.appendChild(button);
  46. }
  47. }
  48. // start with all but first entry collapsed
  49. if (files.length) {
  50. if (/^t/.test(GM_getValue("accordion"))) {
  51. toggleFile({
  52. target: document.querySelector(".ghd-file-toggle")
  53. }, true);
  54. }
  55. }
  56. busy = false;
  57. },
  58.  
  59. matches = function(el, selector) {
  60. // https://developer.mozilla.org/en-US/docs/Web/API/Element/matches
  61. var matches = document.querySelectorAll(selector),
  62. i = matches.length;
  63. while (--i >= 0 && matches.item(i) !== el) {}
  64. return i > -1;
  65. },
  66.  
  67. closest = function(el, selector) {
  68. while (el && !matches(el, selector)) {
  69. el = el.parentNode;
  70. }
  71. return matches(el, selector) ? el : null;
  72. },
  73.  
  74. toggleSibs = function(target, state) {
  75. var el,
  76. isCollapsed = state || target.classList.contains("ghd-file-collapsed"),
  77. toggles = document.querySelectorAll(".file"),
  78. indx = toggles.length;
  79. while (indx--) {
  80. el = toggles[indx];
  81. if (el !== target) {
  82. el.classList[isCollapsed ? "add" : "remove"]("ghd-file-collapsed");
  83. }
  84. }
  85. },
  86.  
  87. toggleFile = function(event, init) {
  88. busy = true;
  89. var accordion = GM_getValue("accordion"),
  90. el = closest(event.target, ".file");
  91. if (accordion) {
  92. if (!init) {
  93. el.classList.toggle("ghd-file-collapsed");
  94. }
  95. toggleSibs(el, true);
  96. } else {
  97. el.classList.toggle("ghd-file-collapsed");
  98. // shift+click toggle all files!
  99. if (event.shiftKey) {
  100. toggleSibs(el);
  101. }
  102. }
  103. document.activeElement.blur();
  104. busy = false;
  105. },
  106.  
  107. addBindings = function() {
  108. document.querySelector("body").addEventListener("click", function(event) {
  109. var target = event.target;
  110. if (target && target.classList.contains("ghd-file-toggle")) {
  111. toggleFile(event);
  112. return false;
  113. }
  114. });
  115. },
  116.  
  117. targets = document.querySelectorAll([
  118. "#js-repo-pjax-container",
  119. "#js-pjax-container",
  120. ".js-preview-body"
  121. ].join(","));
  122.  
  123. // Don't initialize if GitHub Dark Script is active
  124. if (!document.querySelector("#ghd-menu")) {
  125. GM_addStyle([
  126. ".ghd-file-collapsed > :not(.file-header) { display: none !important; }",
  127. // file collapsed icon
  128. ".ghd-file-collapsed .ghd-file-toggle svg { -webkit-transform:rotate(90deg); transform:rotate(90deg); }",
  129. ".ghd-file-toggle svg.octicon { pointer-events: none; vertical-align: middle; }"
  130. ].join(""));
  131.  
  132. Array.prototype.forEach.call(targets, function(target) {
  133. new MutationObserver(function(mutations) {
  134. mutations.forEach(function(mutation) {
  135. // preform checks before adding code wrap to minimize function calls
  136. if (!busy && mutation.target === target) {
  137. addFileToggle();
  138. }
  139. });
  140. }).observe(target, {
  141. childList: true,
  142. subtree: true
  143. });
  144. });
  145.  
  146. addBindings();
  147. addFileToggle();
  148. }
  149.  
  150. // Add GM options
  151. GM_registerMenuCommand("GitHub Diff File Toggle", function() {
  152. var result = "" + !GM_getValue("accordion"),
  153. val = prompt("Accordion Mode? (true/false):", result);
  154. if (val) {
  155. result = /^t/.test(val);
  156. GM_setValue("accordion", result);
  157. }
  158. });
  159.  
  160. })();