GitHub Toggle Issue Comments

A userscript that toggles issues/pull request comments & messages

Version vom 17.05.2016. Aktuellste Version

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

  1. // ==UserScript==
  2. // @name GitHub Toggle Issue Comments
  3. // @version 1.0.3
  4. // @description A userscript that toggles issues/pull request comments & messages
  5. // @license https://creativecommons.org/licenses/by-sa/4.0/
  6. // @namespace http://github.com/Mottie
  7. // @include https://github.com/*
  8. // @run-at document-idle
  9. // @grant GM_addStyle
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @author Rob Garrison
  13. // ==/UserScript==
  14. /* global GM_addStyle, GM_getValue, GM_setValue */
  15. /*jshint unused:true */
  16. (function() {
  17. "use strict";
  18.  
  19. GM_addStyle([
  20. ".ghic-button { float:right; }",
  21. ".ghic-button .btn:hover div.select-menu-modal-holder { display:block; top:auto; bottom:25px; right:0; }",
  22. ".ghic-right { float:right; }",
  23. // pre-wrap set for Firefox; see https://greatest.deepsurf.us/en/forum/discussion/9166/x
  24. ".ghic-menu label { display:block; padding:5px 15px; white-space:pre-wrap; }",
  25. ".ghic-button .select-menu-header, .ghic-participants { cursor:default; }",
  26. ".ghic-participants { border-top:1px solid #484848; padding:15px; }",
  27. ".ghic-avatar { display:inline-block; float:left; margin: 0 2px 2px 0; cursor:pointer; position:relative; }",
  28. ".ghic-avatar:last-child { margin-bottom:5px; }",
  29. ".ghic-avatar.comments-hidden svg { display:block; position:absolute; top:-2px; left:-2px; z-index:1; }",
  30. ".ghic-avatar.comments-hidden img { opacity:0.5; }",
  31. ".ghic-button .dropdown-item input:checked + svg { display: block; }",
  32. ".ghic-button .select-menu-modal { margin: 0; }",
  33. ".ghic-button .ghic-participants { margin-bottom: 20px; }",
  34. ".ghic-hidden, .ghic-hidden-participant, .ghic-avatar svg, .ghic-button .ghic-right > *,",
  35. ".ghic-hideReactions .comment-reactions { display:none; }",
  36. ].join(""));
  37.  
  38. var busy = false,
  39.  
  40. // ZenHub addon active
  41. hasZenHub = document.querySelector("body").classList.contains("zhio"),
  42.  
  43. iconHidden = "<svg class='octicon' xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 9 9'><path fill='#777' d='M7.07 4.5c0-.47-.12-.9-.35-1.3L3.2 6.7c.4.25.84.37 1.3.37.35 0 .68-.07 1-.2.32-.14.6-.32.82-.55.23-.23.4-.5.55-.82.13-.32.2-.65.2-1zM2.3 5.8l3.5-3.52c-.4-.23-.83-.35-1.3-.35-.35 0-.68.07-1 .2-.3.14-.6.32-.82.55-.23.23-.4.5-.55.82-.13.32-.2.65-.2 1 0 .47.12.9.36 1.3zm6.06-1.3c0 .7-.17 1.34-.52 1.94-.34.6-.8 1.05-1.4 1.4-.6.34-1.24.52-1.94.52s-1.34-.18-1.94-.52c-.6-.35-1.05-.8-1.4-1.4C.82 5.84.64 5.2.64 4.5s.18-1.35.52-1.94.8-1.06 1.4-1.4S3.8.64 4.5.64s1.35.17 1.94.52 1.06.8 1.4 1.4c.35.6.52 1.24.52 1.94z'/></svg>",
  44. iconCheck = "<svg class='octicon octicon-check' height='16' viewBox='0 0 12 16' width='12'><path d='M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z'></path></svg>",
  45.  
  46. settings = {
  47. // https://github.com/Mottie/Keyboard/issues/448
  48. title: {
  49. isHidden: false,
  50. name: "ghic-title",
  51. selector: ".discussion-item-renamed",
  52. label: "Title Changes"
  53. },
  54. labels: {
  55. isHidden: false,
  56. name: "ghic-labels",
  57. selector: ".discussion-item-labeled, .discussion-item-unlabeled",
  58. label: "Label Changes"
  59. },
  60. state: {
  61. isHidden: false,
  62. name: "ghic-state",
  63. selector: ".discussion-item-reopened, .discussion-item-closed",
  64. label: "State Changes (close/reopen)"
  65. },
  66.  
  67. // https://github.com/jquery/jquery/issues/2986
  68. milestone: {
  69. isHidden: false,
  70. name: "ghic-milestone",
  71. selector: ".discussion-item-milestoned",
  72. label: "Milestone Changes"
  73. },
  74. refs: {
  75. isHidden: false,
  76. name: "ghic-refs",
  77. selector: ".discussion-item-ref, .discussion-item-head_ref_deleted",
  78. label: "References"
  79. },
  80. assigned: {
  81. isHidden: false,
  82. name: "ghic-assigned",
  83. selector: ".discussion-item-assigned",
  84. label: "Assignment Changes"
  85. },
  86.  
  87. // Pull Requests
  88. commits: {
  89. isHidden: false,
  90. name: "ghic-commits",
  91. selector: ".discussion-commits",
  92. label: "Commits"
  93. },
  94. // https://github.com/jquery/jquery/pull/3014
  95. diffOld: {
  96. isHidden: false,
  97. name: "ghic-diffOld",
  98. selector: ".outdated-diff-comment-container",
  99. label: "Diff (outdated) Comments"
  100. },
  101. diffNew: {
  102. isHidden: false,
  103. name: "ghic-diffNew",
  104. selector: "[id^=diff-for-comment-]:not(.outdated-diff-comment-container)",
  105. label: "Diff (current) Comments"
  106. },
  107. // https://github.com/jquery/jquery/pull/2949
  108. merged: {
  109. isHidden: false,
  110. name: "ghic-merged",
  111. selector: ".discussion-item-merged",
  112. label: "Merged"
  113. },
  114. integrate: {
  115. isHidden: false,
  116. name: "ghic-integrate",
  117. selector: ".discussion-item-integrations-callout",
  118. label: "Integrations"
  119. },
  120.  
  121. // extras (special treatment - no selector)
  122. plus1: {
  123. isHidden: false,
  124. name: "ghic-plus1",
  125. label: "Hide +1s"
  126. },
  127. reactions: {
  128. isHidden: false,
  129. name: "ghic-reactions",
  130. label: "Reactions"
  131. },
  132. // page with lots of users to hide:
  133. // https://github.com/isaacs/github/issues/215
  134.  
  135. // ZenHub pipeline change
  136. pipeline: {
  137. isHidden: false,
  138. name: "ghic-pipeline",
  139. selector: ".discussion-item.zh-discussion-item",
  140. label: "ZenHub Pipeline Changes"
  141. }
  142. },
  143.  
  144. matches = function(el, selector) {
  145. // https://developer.mozilla.org/en-US/docs/Web/API/Element/matches
  146. var matches = document.querySelectorAll(selector),
  147. i = matches.length;
  148. while (--i >= 0 && matches.item(i) !== el) {}
  149. return i > -1;
  150. },
  151.  
  152. closest = function(el, selector) {
  153. while (el && !matches(el, selector)) {
  154. el = el.parentNode;
  155. }
  156. return matches(el, selector) ? el : null;
  157. },
  158.  
  159. addClass = function(els, name) {
  160. var indx,
  161. len = els.length;
  162. for (indx = 0; indx < len; indx++) {
  163. els[indx].classList.add(name);
  164. }
  165. },
  166.  
  167. removeClass = function(els, name) {
  168. var indx,
  169. len = els.length;
  170. for (indx = 0; indx < len; indx++) {
  171. els[indx].classList.remove(name);
  172. }
  173. },
  174.  
  175. addMenu = function() {
  176. busy = true;
  177. if (document.getElementById("discussion_bucket") && !document.querySelector(".ghic-button")) {
  178. // update "isHidden" values
  179. getSettings();
  180. var name,
  181. list = "",
  182. header = document.querySelector(".discussion-sidebar-item:last-child"),
  183. menu = document.createElement("div");
  184.  
  185. for (name in settings) {
  186. if (settings.hasOwnProperty(name) && !(name === "pipeline" && !hasZenHub)) {
  187. list += "<label class='dropdown-item'>" + settings[name].label +
  188. "<span class='ghic-right " + settings[name].name + "'>" +
  189. "<input type='checkbox'" + (settings[name].isHidden ? " checked" : "") + ">" + iconCheck + "</span></label>";
  190. }
  191. }
  192.  
  193. menu.className = "ghic-button";
  194. menu.innerHTML = [
  195. "<span class='btn btn-sm' role='button' tabindex='0' aria-haspopup='true'>",
  196. "<span class='tooltipped tooltipped-w' aria-label='Toggle issue comments'>",
  197. "<svg class='octicon octicon-comment-discussion' height='16' width='16' role='img' viewBox='0 0 16 16'><path d='M15 2H6c-0.55 0-1 0.45-1 1v2H1c-0.55 0-1 0.45-1 1v6c0 0.55 0.45 1 1 1h1v3l3-3h4c0.55 0 1-0.45 1-1V10h1l3 3V10h1c0.55 0 1-0.45 1-1V3c0-0.55-0.45-1-1-1zM9 12H4.5l-1.5 1.5v-1.5H1V6h4v3c0 0.55 0.45 1 1 1h3v2z m6-3H13v1.5l-1.5-1.5H6V3h9v6z'></path></svg>",
  198. "</span>",
  199. "<div class='select-menu-modal-holder'>",
  200. "<div class='select-menu-modal' aria-hidden='true'>",
  201. "<div class='select-menu-header' tabindex='-1'>",
  202. "<span class='select-menu-title'>Toggle items</span>",
  203. "</div>",
  204. "<div class='select-menu-list ghic-menu' role='menu'>",
  205. list,
  206. "<div class='ghic-participants'></div>",
  207. "</div>",
  208. "</div>",
  209. "</div>",
  210. "</span>"
  211. ].join("");
  212. if (hasZenHub) {
  213. header.insertBefore(menu, header.childNodes[0]);
  214. } else {
  215. header.appendChild(menu);
  216. }
  217. addAvatars();
  218. update();
  219. }
  220. busy = false;
  221. },
  222.  
  223. addAvatars = function() {
  224. var indx = 0,
  225.  
  226. str = "<h3>Hide Comments from</h3>",
  227. unique = [],
  228. // get all avatars
  229. avatars = document.querySelectorAll(".timeline-comment-avatar"),
  230. len = avatars.length - 1, // last avatar is the new comment with the current user
  231.  
  232. loop = function(callback) {
  233. var el, name,
  234. max = 0;
  235. while (max < 50 && indx < len) {
  236. if (indx >= len) {
  237. return callback();
  238. }
  239. el = avatars[indx];
  240. name = (el.getAttribute("alt") || "").replace("@", "");
  241. if (unique.indexOf(name) < 0) {
  242. str += "<span class='ghic-avatar tooltipped tooltipped-n' aria-label='" + name + "'>" +
  243. iconHidden +
  244. "<img class='ghic-avatar avatar' width='24' height='24' src='" + el.src + "'/>" +
  245. "</span>";
  246. unique[unique.length] = name;
  247. max++;
  248. }
  249. indx++;
  250. }
  251. if (indx < len) {
  252. setTimeout(function() {
  253. loop(callback);
  254. }, 200);
  255. } else {
  256. callback();
  257. }
  258. };
  259. loop(function() {
  260. document.querySelector(".ghic-participants").innerHTML = str;
  261. });
  262. },
  263.  
  264. getSettings = function() {
  265. var name;
  266. for (name in settings) {
  267. if (settings.hasOwnProperty(name)) {
  268. settings[name].isHidden = GM_getValue(settings[name].name, false);
  269. }
  270. }
  271. },
  272.  
  273. saveSettings = function() {
  274. var name;
  275. for (name in settings) {
  276. if (settings.hasOwnProperty(name)) {
  277. GM_setValue(settings[name].name, settings[name].isHidden);
  278. }
  279. }
  280. },
  281.  
  282. getInputValues = function() {
  283. var name,
  284. menu = document.querySelector(".ghic-menu");
  285. for (name in settings) {
  286. if (settings.hasOwnProperty(name) && !(name === "pipeline" && !hasZenHub)) {
  287. settings[name].isHidden = menu.querySelector("." + settings[name].name + " input").checked;
  288. }
  289. }
  290. },
  291.  
  292. hideStuff = function(name, init) {
  293. if (settings[name].selector) {
  294. var results = document.querySelectorAll(settings[name].selector);
  295. if (settings[name].isHidden) {
  296. addClass(results, "ghic-hidden");
  297. } else if (!init) {
  298. // no need to remove classes on initialization
  299. removeClass(results, "ghic-hidden");
  300. }
  301. } else if (name === "plus1") {
  302. hidePlus1(init);
  303. } else if (name === "reactions") {
  304. document.querySelector("body").classList[settings[name].isHidden ? "add" : "remove"]("ghic-hideReactions");
  305. }
  306. },
  307.  
  308. hidePlus1 = function(init) {
  309. var max,
  310. indx = 0,
  311. // used https://github.com/isaacs/github/issues/215 for matches here...
  312. // matches "+1!!!!", "++1", "+!", "+99!!!", "-1", "+ 100", etc
  313. // image title ":{anything}:", etc.
  314. regex = /([+-]+\s*[\d!]+|^:(.+):)$/,
  315. comments = document.querySelectorAll(".timeline-comment-wrapper .comment-body"),
  316. len = comments.length,
  317.  
  318. loop = function() {
  319. var el, txt, img;
  320. max = 0;
  321. while (max < 20 && indx < len) {
  322. if (indx >= len) {
  323. return;
  324. }
  325. el = comments[indx];
  326. if (el.querySelector(".email-quoted-reply")) {
  327. // ignore quoted messages
  328. txt = el.querySelector(".email-fragment").textContent;
  329. } else {
  330. txt = el.textContent;
  331. }
  332. // including ":" because someone posted "::+1::"; seen "+1."
  333. // seen "^^^" to bump posts; "bump plleeaaassee"
  334. txt = txt.replace(/([!,.:^[\]]|bump|pl+e+a+s+e+)/gi, "").trim();
  335. if (!txt) {
  336. img = el.querySelector("img");
  337. if (img) {
  338. txt = img.getAttribute("title") || img.getAttribute("alt");
  339. }
  340. }
  341. if (regex.test(txt) || txt === "" || txt.length < 5) {
  342. if (settings.plus1.isHidden) {
  343. closest(el, ".timeline-comment-wrapper").classList.add("ghic-hidden");
  344. } else if (!init) {
  345. closest(el, ".timeline-comment-wrapper").classList.remove("ghic-hidden");
  346. }
  347. max++;
  348. }
  349. indx++;
  350. }
  351. if (indx < len) {
  352. setTimeout(function() {
  353. loop();
  354. }, 200);
  355. }
  356. };
  357. loop();
  358. },
  359.  
  360. hideParticipant = function(el) {
  361. var els, indx, len, hide, name,
  362. results = [];
  363. if (el) {
  364. el.classList.toggle("comments-hidden");
  365. hide = el.classList.contains("comments-hidden");
  366. name = el.getAttribute("aria-label");
  367. els = document.querySelectorAll(".js-discussion .author");
  368. len = els.length;
  369. for (indx = 0; indx < len; indx++) {
  370. if (els[indx].textContent.trim() === name) {
  371. results[results.length] = closest(els[indx], ".timeline-comment-wrapper, .commit-comment, .discussion-item");
  372. }
  373. }
  374. // use a different participant class name to hide timeline events
  375. // or unselecting all users will show everything
  376. if (el.classList.contains("comments-hidden")) {
  377. addClass(results, "ghic-hidden-participant");
  378. } else {
  379. removeClass(results, "ghic-hidden-participant");
  380. }
  381. results = [];
  382. }
  383. },
  384.  
  385. regex = /(svg|path)/i,
  386.  
  387. update = function() {
  388. var keys = Object.keys(settings),
  389. indx = keys.length;
  390. while (indx--) {
  391. // true flag for init - no need to remove classes
  392. hideStuff(keys[indx], true);
  393. }
  394. },
  395.  
  396. checkItem = function(event) {
  397. busy = true;
  398. if (document.getElementById("discussion_bucket")) {
  399. var name,
  400. target = event.target,
  401. wrap = target && target.parentNode;
  402. if (target && wrap) {
  403. if (target.nodeName === "INPUT" && wrap.classList.contains("ghic-right")) {
  404. getInputValues();
  405. saveSettings();
  406. // extract ghic-{name}, because it matches the name in settings
  407. name = wrap.className.replace("ghic-right", "").trim();
  408. if (wrap.classList.contains(name)) {
  409. hideStuff(name.replace("ghic-", ""));
  410. }
  411. } else if (target.classList.contains("ghic-avatar")) {
  412. // make sure we're targeting the span wrapping the image
  413. hideParticipant(target.nodeName === "IMG" ? target.parentNode : target);
  414. } else if (regex.test(target.nodeName)) {
  415. // clicking on the SVG may target the svg or path inside
  416. hideParticipant(closest(target, ".ghic-avatar"));
  417. }
  418. }
  419. }
  420. busy = false;
  421. },
  422.  
  423. init = function() {
  424. busy = true;
  425. getSettings();
  426. addMenu();
  427. document.querySelector("body").addEventListener("input", checkItem);
  428. document.querySelector("body").addEventListener("click", checkItem);
  429. update();
  430. busy = false;
  431. },
  432.  
  433. // DOM targets - to detect GitHub dynamic ajax page loading
  434. targets = document.querySelectorAll([
  435. "#js-repo-pjax-container",
  436. "#js-pjax-container"
  437. ].join(","));
  438.  
  439. // update TOC when content changes
  440. Array.prototype.forEach.call(targets, function(target) {
  441. new MutationObserver(function(mutations) {
  442. mutations.forEach(function(mutation) {
  443. // preform checks before adding code wrap to minimize function calls
  444. if (!busy && mutation.target === target) {
  445. addMenu();
  446. }
  447. });
  448. }).observe(target, {
  449. childList: true,
  450. subtree: true
  451. });
  452. });
  453.  
  454. init();
  455.  
  456. })();