Greasy Fork is available in English.

GitHub Toggle Diff Comments

A userscript that toggles diff/PR and commit comments

От 22.12.2017. Виж последната версия.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

  1. // ==UserScript==
  2. // @name GitHub Toggle Diff Comments
  3. // @version 0.1.1
  4. // @description A userscript that toggles diff/PR and commit comments
  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. // @grant GM_addStyle
  12. // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
  13. // @require https://greatest.deepsurf.us/scripts/28721-mutations/code/mutations.js?version=234970
  14. // @icon https://assets-cdn.github.com/pinned-octocat.svg
  15. // ==/UserScript==
  16. (() => {
  17. "use strict";
  18.  
  19. let timer,
  20. ignoreEvents = false;
  21. const targets = {
  22. // PR has notes (added to <div id="diff-00" class="file ...">)
  23. headerHasNotes: ".has-inline-notes:not(.hide-file-notes-toggle)",
  24. // show comments wrapper for each file
  25. headerComment: "show-file-notes",
  26. // show comments checkbox
  27. headerCheckbox: "js-toggle-file-notes",
  28. // comment block row - class added to TR containing the comment
  29. rowComment: "inline-comments"
  30. },
  31. icons = {
  32. "show": `<svg xmlns="http://www.w3.org/2000/svg" class="octicon ghtc-primary ghtc-comment-hide" width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><g fill="#777"><path d="M6 9h1L6 8 5 7v1c0 .6.5 1 1 1z"/><path d="M9 11H4.5L3 12.5V11H1V5h2L2 4H1a1 1 0 0 0-1 1v6c0 .6.5 1 1 1h1v3l3-3h4c.3 0 .6-.1.7-.3l-.7-.8v.1zM15 1H6a1 1 0 0 0-1 1v.3l1 1V2h9v6h-2v1.5L11.5 8h-.8l3.3 3.2V9h1c.6 0 1-.5 1-1V2c0-.6-.4-1-1-1z"/></g><path d="M.4.9L13.7 14h1.7L2 .9z"/></svg>
  33. <svg xmlns="http://www.w3.org/2000/svg" class="octicon ghtc-secondary ghtc-comment-show" width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path fill-rule="evenodd" d="M15 1H6c-.55 0-1 .45-1 1v2H1c-.55 0-1 .45-1 1v6c0 .55.45 1 1 1h1v3l3-3h4c.55 0 1-.45 1-1V9h1l3 3V9h1c.55 0 1-.45 1-1V2c0-.55-.45-1-1-1zM9 11H4.5L3 12.5V11H1V5h4v3c0 .55.45 1 1 1h3v2zm6-3h-2v1.5L11.5 8H6V2h9v6z"></path></svg>`,
  34. "collapse": `<svg xmlns="http://www.w3.org/2000/svg" class="octicon ghtc-primary ghtc-collapse" width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><g fill="#777"><path d="M4.2 12.8L5.6 11H4.5L3 12.5V11H1V5h4v3c0 .6.5 1 1 1h1.2L8 8H6V5.4L5 4H1a1 1 0 0 0-1 1v6c0 .6.5 1 1 1h1v3l2.2-2.2zM6 2.2V2h.6V1H6a1 1 0 0 0-1 1v.2h1zM15 1h-4.6v1H15v6h-2v1.5L11.5 8H9.1l.9 1.2V9h1l3 3V9h1c.6 0 1-.5 1-1V2c0-.6-.4-1-1-1z"/></g><path d="M11.5 3h-2V1h-2v2h-2l3 4zM5.5 13h2v2h2v-2h2l-3-4z"/></svg><svg xmlns="http://www.w3.org/2000/svg" class="octicon ghtc-secondary ghtc-expand" width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><g fill="#777"><path d="M6 5.8H5V8c0 .6.5 1 1 1h.8V8H6V5.8z"/><path d="M4.6 11h-.1L3 12.5V11H1V5h3.3L6 2.9V2h.7l.8-1H6a1 1 0 0 0-1 1v2H1a1 1 0 0 0-1 1v6c0 .6.5 1 1 1h1v3l3-3h.3l-.7-1zM15 1h-5l.7 1H15v6h-2v1.5L11.5 8h-1v1h.5l.8.8h1.8l-.8 1L14 12V9h1c.6 0 1-.5 1-1V2c0-.6-.4-1-1-1z"/></g><path d="M11.7 11h-2V9h-2v2h-2l3 4zM11.7 5h-2v2h-2V5h-2l3-4z"/></svg>`
  35. },
  36. activeClass = "ghtc-active",
  37. button = document.createElement("div");
  38. button.className = "btn btn-sm BtnGroup-item ghtc-toggle tooltipped tooltipped-s";
  39.  
  40. // Using small black triangles because Windows doesn't
  41. // replace them with ugly emoji images
  42. GM.addStyle(`
  43. td.js-quote-selection-container {
  44. position: relative;
  45. }
  46. .review-thread:before {
  47. content: "\\25be";
  48. font-size: 2rem;
  49. position: absolute;
  50. right: 10px;
  51. top: -1rem;
  52. pointer-events: none;
  53. }
  54. .ghtc-collapsed .review-thread:before {
  55. content: "\\25c2";
  56. }
  57. .ghtc-collapsed .review-thread {
  58. padding: 0 0 5px;
  59. border: 0;
  60. }
  61. .ghtc-collapsed .review-thread:last-child {
  62. margin-bottom: 16px;
  63. }
  64. .ghtc-toggle .ghtc-secondary,
  65. .ghtc-toggle.${activeClass} .ghtc-primary,
  66. .ghtc-toggle input ~ .ghtc-secondary,
  67. .ghtc-toggle input:checked ~ .ghtc-primary,
  68. .ghtc-collapsed .review-thread > *,
  69. .ghtc-collapsed .last-review-thread,
  70. .ghtc-collapsed .inline-comment-form-container {
  71. display: none;
  72. }
  73. .ghtc-collapsed td.line-comments {
  74. padding: 0 5px;
  75. cursor: pointer;
  76. }
  77. .pr-toolbar .pr-review-tools.float-right .diffbar-item + .diffbar-item {
  78. margin-left: 10px;
  79. }
  80. .ghtc-toggle {
  81. height: 28px;
  82. }
  83. .ghtc-toggle svg {
  84. display: inline-block;
  85. max-height: 16px;
  86. pointer-events: none;
  87. vertical-align: baseline !important;
  88. }
  89. .ghtc-toggle.${activeClass} .ghtc-secondary,
  90. .ghtc-toggle input:checked ~ .ghtc-secondary {
  91. display: block;
  92. }`
  93. );
  94.  
  95. function toggleSingleComment(el) {
  96. // Toggle individual inline comment
  97. el.parentNode.classList.toggle("ghtc-collapsed");
  98. }
  99.  
  100. function toggleMultipleComments(wrapper, state) {
  101. $(".ghtc-collapse-toggle-file", wrapper).classList.toggle(activeClass, state);
  102. $$(`tr.${targets.rowComment}`, wrapper).forEach(el => {
  103. el.classList.toggle("ghtc-collapsed", state);
  104. });
  105. }
  106.  
  107. function getState(el) {
  108. el.classList.toggle(activeClass);
  109. return el.classList.contains(activeClass);
  110. }
  111.  
  112. function toggleFile(el) {
  113. // Toggle all inline comments for one file
  114. const state = getState(el);
  115. toggleMultipleComments(el.closest(".file"), state);
  116. }
  117.  
  118. function toggleAll(el) {
  119. // Toggle all comments on page
  120. const state = getState(el);
  121. $("#ghtc-collapse-toggle-all").classList.toggle(activeClass, state);
  122. toggleMultipleComments(el.closest("#files_bucket"), state);
  123. $$(".ghtc-collapse-toggle-file").forEach(el => {
  124. el.classList.toggle(activeClass, state);
  125. });
  126. }
  127.  
  128. function showAll(el) {
  129. // Show/hide all comments on page
  130. const state = getState(el);
  131. $("#ghtc-show-toggle-all").classList.toggle(activeClass, state);
  132. $$("#files .js-toggle-file-notes").forEach(el => {
  133. el.checked = state;
  134. el.dispatchEvent(new Event("change", {bubbles: true}));
  135. });
  136. }
  137.  
  138. function createButton({id, className, icon, title}) {
  139. const btn = button.cloneNode(true);
  140. if (id) {
  141. btn.id = id;
  142. }
  143. btn.className += ` ${className || ""}`;
  144. btn.setAttribute("aria-label", title);
  145. btn.innerHTML = icons[icon];
  146. return btn;
  147. }
  148.  
  149. function execFunction(event, callback) {
  150. clearTimeout(timer);
  151. ignoreEvents = true;
  152. event.stopPropagation();
  153. event.preventDefault();
  154. callback(event.target);
  155. timer = setTimeout(() => {
  156. ignoreEvents = false;
  157. }, 250);
  158. }
  159.  
  160. function addListeners() {
  161. $(".repository-content").addEventListener("change", event => {
  162. const el = event.target;
  163. if (el && el.classList.contains(targets.headerCheckbox)) {
  164. el.parentNode.classList.toggle(activeClass, el.checked);
  165. }
  166. });
  167. $(".repository-content").addEventListener("click", event => {
  168. const el = event.target;
  169. if (!ignoreEvents && el) {
  170. const shift = event.shiftKey,
  171. toggle = el.classList.contains("ghtc-collapse-toggle-file"),
  172. show = el.nodeName === "LABEL",
  173. comment = el.classList.contains("js-quote-selection-container");
  174. if (el.id === "ghtc-collapse-toggle-all" || toggle && shift) {
  175. execFunction(event, toggleAll);
  176. } else if (el.id === "ghtc-show-toggle-all" || show && shift) {
  177. execFunction(event, showAll);
  178. } else if (toggle || comment && shift) {
  179. execFunction(event, toggleFile);
  180. } else if (comment) {
  181. execFunction(event, toggleSingleComment);
  182. }
  183. }
  184. });
  185. }
  186.  
  187. function addButtons() {
  188. $$(`.${targets.headerComment}`).forEach(wrapper => {
  189. if (!wrapper.classList.contains("ghtc-hidden")) {
  190. const label = $("label", wrapper),
  191. checkbox = $("input", wrapper);
  192. let btn;
  193. // Make span wrapper a button group
  194. wrapper.classList.add("ghtc-hidden", "BtnGroup");
  195. // Remove top margin
  196. wrapper.classList.remove("pt-1");
  197.  
  198. // Convert "Show Comments" label wrapping checkbox into a button
  199. label.className = "btn btn-sm BtnGroup-item ghtc-toggle tooltipped tooltipped-s";
  200. label.setAttribute("aria-label", "Show or hide all comments in this file");
  201. label.innerHTML = `
  202. <input type="checkbox" checked="checked" class="js-toggle-file-notes" hidden="true">
  203. ${icons.show}`;
  204.  
  205. // Add collapse all file comments button before label
  206. btn = createButton({
  207. className: "ghtc-collapse-toggle-file",
  208. icon: "collapse",
  209. title: "Expand or collapse all comments in this file"
  210. });
  211. label.parentNode.insertBefore(btn, label);
  212. // Hide checkbox
  213. checkbox.setAttribute("hidden", true);
  214. }
  215. });
  216. // Add collapse all comments on the page - test adding global toggle on
  217. // https://github.com/openstyles/stylus/pull/150/files (edit.js)
  218. if (!$("#ghtc-collapse-toggle-all")) {
  219. const wrapper = document.createElement("div"),
  220. // insert before Unified/Split button group
  221. diffmode = $(".pr-review-tools .diffbar-item, #toc .toc-diff-stats");
  222. let btn;
  223. wrapper.className = "BtnGroup " +
  224. // PR: diffbar-item; commit: toc-diff-stats
  225. (diffmode.classList.contains("diffbar-item") ? "diffbar-item" : "float-right pr-2");
  226. diffmode.parentNode.insertBefore(wrapper, diffmode);
  227. // collapse/expand all comments
  228. btn = createButton({
  229. id: "ghtc-collapse-toggle-all",
  230. icon: "collapse",
  231. title: "Expand or collapse all comments"
  232. });
  233. wrapper.appendChild(btn);
  234. // show/hide all comments
  235. btn = createButton({
  236. id: "ghtc-show-toggle-all",
  237. icon: "show",
  238. className: activeClass,
  239. title: "Show or hide all comments"
  240. });
  241. wrapper.appendChild(btn);
  242. }
  243. }
  244.  
  245. function $(str, el) {
  246. return (el || document).querySelector(str);
  247. }
  248.  
  249. function $$(str, el) {
  250. return [...(el || document).querySelectorAll(str)];
  251. }
  252.  
  253. function init() {
  254. if ($("#files") && $(targets.headerHasNotes)) {
  255. addButtons();
  256. addListeners();
  257. }
  258. }
  259.  
  260. document.addEventListener("ghmo:container", init);
  261. document.addEventListener("ghmo:diff", init);
  262. init();
  263.  
  264. })();