GitHub Collapse In Comment

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

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址