GitHub Collapse In Comment

A userscript that adds a header that can toggle long code and quote blocks in comments

目前为 2017-04-14 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name GitHub Collapse In Comment
  3. // @version 1.0.7
  4. // @description A userscript that adds a header that can toggle long code and quote blocks in comments
  5. // @license https://creativecommons.org/licenses/by-sa/4.0/
  6. // @author Rob Garrison
  7. // @namespace https://github.com/Mottie
  8. // @include https://github.com/*
  9. // @include https://gist.github.com/*
  10. // @run-at document-idle
  11. // @grant GM_addStyle
  12. // @grant GM_getValue
  13. // @grant GM_setValue
  14. // @grant GM_registerMenuCommand
  15. // @require https://greasyfork.org/scripts/28721-mutations/code/mutations.js?version=188043
  16. // @icon https://github.com/fluidicon.png
  17. // ==/UserScript==
  18. (() => {
  19. "use strict";
  20. /*
  21. Idea from: https://github.com/dear-github/dear-github/issues/166 &
  22. https://github.com/isaacs/github/issues/208
  23. examples:
  24. https://github.com/Mottie/tablesorter/issues/569
  25. https://github.com/jquery/jquery/issues/3195
  26. */
  27. // hide code/quotes longer than this number of lines
  28. let minLines = GM_getValue("gcic-max-lines", 10),
  29. startCollapsed = GM_getValue("gcic-start-collapsed", true);
  30.  
  31. // syntax highlight class name lookup table
  32. const syntaxClass = {
  33. basic: "HTML",
  34. cs: "C#",
  35. fsharp: "F#",
  36. gfm: "Markdown",
  37. jq: "JSONiq",
  38. shell: "Bash (shell)",
  39. tcl: "Glyph",
  40. tex: "LaTex"
  41. };
  42.  
  43. GM_addStyle(`
  44. .gcic-block {
  45. border:#eee 1px solid;
  46. padding:2px 8px 2px 10px;
  47. border-radius:5px 5px 0 0;
  48. position:relative;
  49. top:1px;
  50. cursor:pointer;
  51. font-weight:bold;
  52. display:block;
  53. }
  54. .gcic-block + .highlight {
  55. border-top:none;
  56. }
  57. .gcic-block + .email-signature-reply {
  58. margin-top:0;
  59. }
  60. .gcic-block:after {
  61. content:"\u25bc ";
  62. float:right;
  63. }
  64. .gcic-block-closed {
  65. border-radius:5px;
  66. margin-bottom:10px;
  67. }
  68. .gcic-block-closed:after {
  69. transform: rotate(90deg);
  70. }
  71. .gcic-block-closed + .highlight, .gcic-block-closed + .email-signature-reply,
  72. .gcic-block-closed + pre {
  73. display:none;
  74. }
  75. `);
  76.  
  77. function makeToggle(name, lines) {
  78. /* full list of class names from (look at "tm_scope" value)
  79. https://github.com/github/linguist/blob/master/lib/linguist/languages.yml
  80. here are some example syntax highlighted class names:
  81. highlight-text-html-markdown-source-gfm-apib
  82. highlight-text-html-basic
  83. highlight-source-fortran-modern
  84. highlight-text-tex
  85. */
  86. let n = (name || "").replace(
  87. /(highlight[-\s]|(source-)|(text-)|(html-)|(markdown-)|(-modern))/g, ""
  88. );
  89. n = (syntaxClass[n] || n).toUpperCase().trim();
  90. return `${n || "Block"} (${lines} lines)`;
  91. }
  92.  
  93. function addToggles() {
  94. // issue comments
  95. if ($("#discussion_bucket")) {
  96. let indx = 0;
  97. const block = document.createElement("a"),
  98. els = $$(".markdown-body pre, .email-signature-reply"),
  99. len = els.length;
  100.  
  101. // "flash" = blue box styling
  102. block.className = `gcic-block border flash${
  103. startCollapsed ? " gcic-block-closed" : ""
  104. }`;
  105. block.href = "#";
  106.  
  107. // loop with delay to allow user interaction
  108. const loop = () => {
  109. let el, wrap, node, syntaxClass, numberOfLines,
  110. // max number of DOM insertions per loop
  111. max = 0;
  112. while (max < 20 && indx < len) {
  113. if (indx >= len) {
  114. return;
  115. }
  116. el = els[indx];
  117. if (el && !el.classList.contains("gcic-has-toggle")) {
  118. numberOfLines = el.innerHTML.split("\n").length;
  119. if (numberOfLines > minLines) {
  120. syntaxClass = "";
  121. wrap = closest(".highlight", el);
  122. if (wrap && wrap.classList.contains("highlight")) {
  123. syntaxClass = wrap.className;
  124. } else {
  125. // no syntax highlighter defined (not wrapped)
  126. wrap = el;
  127. }
  128. node = block.cloneNode();
  129. node.innerHTML = makeToggle(syntaxClass, numberOfLines);
  130. wrap.parentNode.insertBefore(node, wrap);
  131. el.classList.add("gcic-has-toggle");
  132. if (startCollapsed) {
  133. el.display = "none";
  134. }
  135. max++;
  136. }
  137. }
  138. indx++;
  139. }
  140. if (indx < len) {
  141. setTimeout(() => {
  142. loop();
  143. }, 200);
  144. }
  145. };
  146. loop();
  147. }
  148. }
  149.  
  150. function addBindings() {
  151. document.addEventListener("click", event => {
  152. let els, indx, flag;
  153. const el = event.target;
  154. if (el && el.classList.contains("gcic-block")) {
  155. event.preventDefault();
  156. // shift + click = toggle all blocks in a single comment
  157. // shift + ctrl + click = toggle all blocks on page
  158. if (event.shiftKey) {
  159. els = $$(
  160. ".gcic-block",
  161. event.ctrlKey ? "" : closest(".markdown-body", el)
  162. );
  163. indx = els.length;
  164. flag = el.classList.contains("gcic-block-closed");
  165. while (indx--) {
  166. els[indx].classList.toggle("gcic-block-closed", !flag);
  167. }
  168. } else {
  169. el.classList.toggle("gcic-block-closed");
  170. }
  171. removeSelection();
  172. }
  173. });
  174. }
  175.  
  176. function update() {
  177. let toggles = $$(".gcic-block"),
  178. indx = toggles.length;
  179. while (indx--) {
  180. toggles[indx].parentNode.removeChild(toggles[indx]);
  181. }
  182. toggles = $$(".gcic-has-toggle");
  183. indx = toggles.length;
  184. while (indx--) {
  185. toggles[indx].classList.remove("gcic-has-toggle");
  186. }
  187. addToggles();
  188. }
  189.  
  190. function $(selector, el) {
  191. return (el || document).querySelector(selector);
  192. }
  193.  
  194. function $$(selector, el) {
  195. return Array.from((el || document).querySelectorAll(selector));
  196. }
  197.  
  198. function closest(selector, el) {
  199. while (el && el.nodeType === 1) {
  200. if (el.matches(selector)) {
  201. return el;
  202. }
  203. el = el.parentNode;
  204. }
  205. return null;
  206. }
  207.  
  208. function removeSelection() {
  209. // remove text selection - https://stackoverflow.com/a/3171348/145346
  210. const sel = window.getSelection ? window.getSelection() : document.selection;
  211. if (sel) {
  212. if (sel.removeAllRanges) {
  213. sel.removeAllRanges();
  214. } else if (sel.empty) {
  215. sel.empty();
  216. }
  217. }
  218. }
  219.  
  220. GM_registerMenuCommand("Set GitHub Collapse In Comment Max Lines", () => {
  221. let val = prompt("Minimum number of lines before adding a toggle:",
  222. minLines);
  223. val = parseInt(val, 10);
  224. if (val) {
  225. minLines = val;
  226. GM_setValue("gcic-max-lines", val);
  227. update();
  228. }
  229. });
  230.  
  231. GM_registerMenuCommand("Set GitHub Collapse In Comment Initial State", () => {
  232. let val = prompt(
  233. "Start with blocks (c)ollapsed or (e)xpanded (first letter necessary):",
  234. startCollapsed ? "collapsed" : "expanded"
  235. );
  236. if (val) {
  237. val = /^c/.test(val || "");
  238. startCollapsed = val;
  239. GM_setValue("gcic-start-collapsed", val);
  240. update();
  241. }
  242. });
  243.  
  244. document.addEventListener("ghmo:container", addToggles);
  245. document.addEventListener("ghmo:preview", addToggles);
  246. addBindings();
  247. addToggles();
  248.  
  249. })();