GitHub Issue Comments

A userscript that toggles issues/pull request comments & messages

As of 2021-02-21. See the latest version.

You will need to install an extension such as Tampermonkey, Greasemonkey 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 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.

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

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

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 Issue Comments
  3. // @version 1.4.5
  4. // @description A userscript that toggles issues/pull request comments & messages
  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_getValue
  12. // @grant GM_setValue
  13. // @require https://greatest.deepsurf.us/scripts/28721-mutations/code/mutations.js?version=882023
  14. // @icon https://github.githubassets.com/pinned-octocat.svg
  15. // @supportURL https://github.com/Mottie/GitHub-userscripts/issues
  16. // ==/UserScript==
  17. (() => {
  18. "use strict";
  19.  
  20. GM_addStyle(`
  21. .ghic-button { float:right; }
  22. .ghic-button .btn:hover div.select-menu-modal-holder { display:block; top:auto; bottom:25px; right:0; }
  23. .ghic-right { position:absolute; right:10px; top:9px; }
  24. .ghic-button .select-menu-header, .ghic-participants { cursor:default; display:block; }
  25. .ghic-participants { border-top:1px solid #484848; padding:15px; }
  26. .ghic-avatar { display:inline-block; float:left; margin: 0 2px 2px 0; cursor:pointer; position:relative; }
  27. .ghic-avatar:last-child { margin-bottom:5px; }
  28. .ghic-avatar.comments-hidden svg { display:block; position:absolute; top:-2px; left:-2px; z-index:1; }
  29. .ghic-avatar.comments-hidden img { opacity:0.5; }
  30. .ghic-button .dropdown-item { font-weight:normal; position:relative; }
  31. .ghic-button .dropdown-item span { font-weight:normal; opacity:.5; }
  32. .ghic-button .dropdown-item.ghic-has-content span { opacity:1; }
  33. .ghic-button .dropdown-item.ghic-checked span { font-weight:bold; }
  34. .ghic-button .dropdown-item.ghic-checked svg,
  35. .ghic-button .dropdown-item:not(.ghic-checked) .ghic-count { display:inline-block; }
  36. .ghic-button .dropdown-item:not(.ghic-checked) { text-decoration:line-through; }
  37. .ghic-button .ghic-count { margin-left:5px; }
  38. .ghic-button .select-menu-modal { margin:0; }
  39. .ghic-button .ghic-participants { margin-bottom:20px; }
  40. /* for testing: ".ghic-hidden { opacity: 0.3; } */
  41. body .ghic-hidden { display:none !important; }
  42. .ghic-hidden-participant, body .ghic-avatar svg, .dropdown-item.ghic-checked .ghic-count,
  43. .ghic-hide-reactions .TimelineItem .comment-reactions,
  44. .select-menu-header.ghic-active + .select-menu-list .dropdown-item:not(.ghic-has-content) { display:none; }
  45. .ghic-menu-wrapper input[type=checkbox] { height:0; width:0; visibility:hidden; position:absolute; }
  46. .ghic-menu-wrapper .ghic-toggle { cursor:pointer; text-indent:-9999px; width:20px; height:10px;
  47. background:grey; display:block; border-radius:10px; position:relative; }
  48. .ghic-menu-wrapper .ghic-toggle:after { content:''; position:absolute; top:0; left:1px; width:9px;
  49. height:9px; background:#fff; border-radius:9px; transition:.3s; }
  50. .ghic-menu-wrapper input:checked + .ghic-toggle { background:#070; }
  51. .ghic-menu-wrapper input:checked + .ghic-toggle:after { top:0; left:calc(100% - 1px);
  52. transform:translateX(-100%); }
  53. .ghic-menu-wrapper .ghic-toggle:active:after { width:13px; }
  54. .TimelineItem.ghic-highlight .comment { border-color:#800 !important; }
  55. `);
  56.  
  57. const regex = /(svg|path)/i;
  58. // ZenHub addon active (include ZenHub Enterprise)
  59. const hasZenHub = $(".zhio, .zhe") ? true : false;
  60.  
  61. const exceptions = [
  62. "ghsr-sort-block" // sort reactions block (github-sort-reactions.user.js)
  63. ];
  64.  
  65. const settings = {
  66. // example: https://github.com/Mottie/Keyboard/issues/448
  67. title: {
  68. isHidden: false,
  69. name: "ghic-title",
  70. selector: ".TimelineItem-badge .octicon-pencil",
  71. containsText: "changed the title",
  72. label: "Title Changes"
  73. },
  74. labels: {
  75. isHidden: false,
  76. name: "ghic-labels",
  77. selector: ".TimelineItem-badge .octicon-tag",
  78. containsText: "label",
  79. label: "Label Changes"
  80. },
  81. state: {
  82. isHidden: false,
  83. name: "ghic-state",
  84. selector: `.TimelineItem-badge .octicon-primitive-dot,
  85. .TimelineItem-badge .octicon-circle-slash`,
  86. label: "State Changes (close/reopen)"
  87. },
  88.  
  89. // example: https://github.com/jquery/jquery/issues/2986
  90. milestone: {
  91. isHidden: false,
  92. name: "ghic-milestone",
  93. selector: ".TimelineItem-badge .octicon-milestone",
  94. label: "Milestone Changes"
  95. },
  96. refs: {
  97. isHidden: false,
  98. name: "ghic-refs",
  99. selector: ".TimelineItem-badge .octicon-bookmark",
  100. containsText: "referenced",
  101. label: "References"
  102. },
  103. mentioned: {
  104. isHidden: false,
  105. name: "ghic-mentions",
  106. selector: ".TimelineItem-badge .octicon-bookmark",
  107. containsText: "mentioned",
  108. label: "Mentioned"
  109. },
  110. assigned: {
  111. isHidden: false,
  112. name: "ghic-assigned",
  113. selector: ".TimelineItem-badge .octicon-person",
  114. label: "Assignment Changes"
  115. },
  116.  
  117. // Pull Requests
  118. commits: {
  119. isHidden: false,
  120. name: "ghic-commits",
  121. selector: `.TimelineItem-badge .octicon-repo-push,
  122. .TimelineItem-badge .octicon-git-commit`,
  123. wrapper: ".js-timeline-item",
  124. label: "Commits"
  125. },
  126. forcePush: {
  127. isHidden: false,
  128. name: "ghic-force-push",
  129. selector: ".TimelineItem-badge .octicon-repo-force-push",
  130. label: "Force Push"
  131. },
  132. // example: https://github.com/jquery/jquery/pull/3014
  133. reviews: {
  134. isHidden: false,
  135. name: "ghic-reviews",
  136. selector: `.TimelineItem-badge .octicon-eye, .TimelineItem-badge .octicon-x,
  137. .TimelineItem-badge .octicon-check, .js-resolvable-timeline-thread-container`,
  138. wrapper: ".js-timeline-item",
  139. label: "Reviews (All)"
  140. },
  141. outdated: {
  142. isHidden: false,
  143. name: "ghic-outdated",
  144. selector: ".js-resolvable-timeline-thread-container .Label--outline[title*='Outdated']",
  145. wrapper: ".js-resolvable-timeline-thread-container",
  146. label: "- Reviews (Outdated)"
  147. },
  148. resolved: {
  149. isHidden: false,
  150. name: "ghic-resolved",
  151. selector: ".js-resolvable-timeline-thread-container[data-resolved='true']",
  152. label: "- Reviews (Resolved)"
  153. },
  154. diffNew: {
  155. isHidden: false,
  156. name: "ghic-diffNew",
  157. selector: ".js-resolvable-timeline-thread-container",
  158. notSelector: ".Label--outline[title*='Outdated']",
  159. wrapper: ".js-resolvable-timeline-thread-container",
  160. label: "- Reviews (Current)"
  161. },
  162. // example: https://github.com/jquery/jquery/pull/2949
  163. merged: {
  164. isHidden: false,
  165. name: "ghic-merged",
  166. selector: ".TimelineItem-badge .octicon-git-merge",
  167. label: "Merged"
  168. },
  169. integrate: {
  170. isHidden: false,
  171. name: "ghic-integrate",
  172. selector: ".TimelineItem-badge .octicon-rocket",
  173. label: "Integrations"
  174. },
  175. // bot: {
  176. // isHidden: false,
  177. // name: "ghic-bot",
  178. // selector: ".Label--outline",
  179. // containsText: "bot",
  180. // label: "Bot"
  181. // },
  182. // similar comments
  183. similar: {
  184. isHidden: false,
  185. name: "ghic-similar",
  186. selector: `.js-discussion > .Details-element.details-reset:not([open]),
  187. #js-progressive-timeline-item-container > .Details-element.details-reset:not([open])`,
  188. label: "Similar comments"
  189. },
  190.  
  191. // extras (special treatment - no selector)
  192. plus1: {
  193. isHidden: false,
  194. name: "ghic-plus1",
  195. label: "+1 Comments",
  196. callback: hidePlus1,
  197. },
  198. reactions: {
  199. isHidden: false,
  200. name: "ghic-reactions",
  201. label: "Reactions",
  202. callback: hideReactions,
  203. },
  204. projects: {
  205. isHidden: false,
  206. name: "ghic-projects",
  207. selector: `.discussion-item-added_to_project,
  208. .discussion-item-moved_columns_in_project,
  209. .discussion-item-removed_from_project`,
  210. label: "Project Changes"
  211. },
  212. // Jenkins auto-merged
  213. autoMerged: {
  214. isHidden: false,
  215. name: "ghic-automerged",
  216. selector: ".Details a[title*='auto-merged' i]",
  217. label: "Auto merged"
  218. },
  219. // Jenkins temp deployments that have become inactive
  220. inactive: {
  221. isHidden: false,
  222. name: "ghic-inactive",
  223. selector: ".deployment-status-label.is-inactive, .Label[title*='inactive' i]",
  224. label: "Inactive deployments"
  225. },
  226. // page with lots of users to hide:
  227. // https://github.com/isaacs/github/issues/215
  228. // ZenHub pipeline change
  229. pipeline: {
  230. isHidden: false,
  231. name: "ghic-pipeline",
  232. selector: ".TimelineItem-badge .zh-icon-board-small",
  233. label: "ZenHub Pipeline Changes"
  234. }
  235. };
  236.  
  237. const 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>`;
  238. const plus1Icon = `<img src="https://github.githubassets.com/images/icons/emoji/unicode/1f44d.png" class="emoji" title=":+1:" alt=":+1:" height="20" width="20" align="absmiddle">`;
  239.  
  240. function addMenu() {
  241. if ($("#discussion_bucket") && !$(".ghic-button")) {
  242. // update "isHidden" values
  243. getSettings();
  244. let name, isHidden, isChecked,
  245. list = "",
  246. keys = Object.keys(settings),
  247. onlyActive = GM_getValue("onlyActive", false),
  248. header = $(".discussion-sidebar-item:last-child"),
  249. menu = document.createElement("div");
  250.  
  251. for (name of keys) {
  252. if (!(name === "pipeline" && !hasZenHub)) {
  253. isHidden = settings[name].isHidden;
  254. isChecked = isHidden ? "" : "ghic-checked";
  255. list += `<label class="dropdown-item ${isChecked} ${settings[name].name}" data-ghic="${name}">
  256. <span>${settings[name].label} <span class="ghic-count"> </span></span>
  257. <span class="ghic-right">
  258. <input type="checkbox"${isHidden ? "" : " checked"}>
  259. <span class="ghic-toggle"></span>
  260. </span>
  261. </label>`;
  262. }
  263. }
  264.  
  265. menu.className = "ghic-button";
  266. menu.innerHTML = `
  267. <span class="btn btn-sm" role="button" tabindex="0" aria-haspopup="true">
  268. <span class="tooltipped tooltipped-w" aria-label="Toggle issue comments">
  269. <svg class="octicon octicon-comment-discussion" height="16" width="16" role="img" viewBox="0 0 16 16">
  270. <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>
  271. </svg>
  272. </span>
  273. <div class="select-menu-modal-holder ghic-menu-wrapper">
  274. <div class="select-menu-modal" aria-hidden="true">
  275. <div class="select-menu-header ${onlyActive ? "ghic-active" : ""}" tabindex="-1">
  276. <span class="select-menu-title">Toggle items</span>
  277. <label class="ghic-right tooltipped tooltipped-w" aria-label="Only show active items">
  278. <input id="ghic-only-active" type="checkbox" ${onlyActive ? "checked" : ""}>
  279. <span class="ghic-toggle"></span>
  280. </label>
  281. </div>
  282. <div class="select-menu-list ghic-menu" role="menu">
  283. ${list}
  284. <div class="ghic-participants">
  285. <p><strong>Hide Comments from</strong></p>
  286. <div class="ghic-list"></div>
  287. </div>
  288. </div>
  289. </div>
  290. </div>
  291. </span>
  292. `;
  293. if (hasZenHub) {
  294. header.insertBefore(menu, header.childNodes[0]);
  295. } else {
  296. header.appendChild(menu);
  297. }
  298. addAvatars();
  299. }
  300. update();
  301. }
  302.  
  303. function addAvatars() {
  304. let indx = 0;
  305. let str = "";
  306. const list = $(".ghic-list");
  307. const unique = $$("span.ghic-avatar", list).map(el => el.getAttribute("aria-label"));
  308. // get all avatars
  309. const avatars = $$(".TimelineItem-avatar img");
  310. const len = avatars.length - 1; // last avatar is the new comment with the current user
  311. const updateAvatars = () => {
  312. list.innerHTML += str;
  313. str = "";
  314. };
  315.  
  316. const loop = () => {
  317. let el, name;
  318. let max = 0;
  319. while (max < 50 && indx <= len) {
  320. if (indx > len) {
  321. return updateAvatars();
  322. }
  323. el = avatars[indx];
  324. name = (el.getAttribute("alt") || "").replace("@", "");
  325. if (!unique.includes(name)) {
  326. str += `<span class="ghic-avatar tooltipped tooltipped-n" aria-label="${name}">
  327. ${iconHidden}
  328. <img class="ghic-avatar avatar" width="24" height="24" src="${el.src}"/>
  329. </span>`;
  330. unique[unique.length] = name;
  331. max++;
  332. }
  333. indx++;
  334. }
  335. updateAvatars();
  336. if (indx < len) {
  337. setTimeout(() => {
  338. window.requestAnimationFrame(loop);
  339. }, 200);
  340. }
  341. };
  342. loop();
  343. }
  344.  
  345. function getSettings() {
  346. const keys = Object.keys(settings);
  347. for (let name of keys) {
  348. settings[name].isHidden = GM_getValue(settings[name].name, false);
  349. }
  350. }
  351.  
  352. function saveSettings() {
  353. const keys = Object.keys(settings);
  354. for (let name of keys) {
  355. GM_setValue(settings[name].name, settings[name].isHidden);
  356. }
  357. }
  358.  
  359. function getInputValues() {
  360. const keys = Object.keys(settings);
  361. const menu = $(".ghic-menu");
  362. for (let name of keys) {
  363. if (!(name === "pipeline" && !hasZenHub)) {
  364. const item = $(`.${settings[name].name}`, menu).closest(".dropdown-item");
  365. if (item) {
  366. settings[name].isHidden = !$("input", item).checked;
  367. toggleClass(item, "ghic-checked", !settings[name].isHidden);
  368. }
  369. }
  370. }
  371. }
  372.  
  373. function hideStuff(name, init) {
  374. const obj = settings[name];
  375. const item = $(".ghic-menu .dropdown-item." + obj.name);
  376. if (item) {
  377. const isHidden = obj.isHidden;
  378. if (typeof obj.callback === "function") {
  379. obj.callback({ obj, item, init });
  380. } else if (obj.selector) {
  381. let results = $$(obj.selector).map(el =>
  382. el.closest(obj.wrapper || ".TimelineItem, .Details-element")
  383. );
  384. if (obj.containsText) {
  385. results = results.filter(
  386. el => el && el.textContent.includes(obj.containsText)
  387. );
  388. }
  389. if (obj.notSelector) {
  390. results = results.filter(el => el && !$(obj.notSelector, el));
  391. }
  392. toggleClass(item, "ghic-checked", !isHidden);
  393. if (isHidden) {
  394. const count = addClass(results, "ghic-hidden");
  395. $(".ghic-count", item).textContent = count ? `(${count} hidden)` : " ";
  396. } else if (!init) {
  397. // no need to remove classes on initialization
  398. removeClass(results, "ghic-hidden");
  399. }
  400. toggleClass(item, "ghic-has-content", results.length);
  401. }
  402. }
  403. }
  404.  
  405. function hideReactions({ obj, item }) {
  406. toggleClass($("body"), "ghic-hide-reactions", obj.isHidden);
  407. toggleClass(item, "ghic-has-content", $$(".has-reactions").length > 0);
  408. // make first comment reactions visible
  409. const origPost = $(".TimelineItem .comment-reactions");
  410. if (origPost && origPost.classList.contains("has-reactions")) {
  411. origPost.style.display = "block";
  412. }
  413. }
  414.  
  415. function hidePlus1({ item, init }) {
  416. let max,
  417. indx = 0,
  418. count = 0,
  419. total = 0;
  420. // keep a list of post authors to prevent duplicate +1 counts
  421. const authors = [];
  422. // used https://github.com/isaacs/github/issues/215 for matches here...
  423. // matches "+1!!!!", "++1", "+!", "+99!!!", "-1", "+ 100", "thumbs up"; ":+1:^21425235"
  424. // ignoring -1's... add unicode for thumbs up; it gets replaced with an image in Windows
  425. const regexPlus = /([?!*,.:^[\]()\'\"+-\d]|bump|thumbs|up|\ud83d\udc4d)/gi;
  426. // other comments to hide - they are still counted towards the +1 counter (for now?)
  427. // seen "^^^" to bump posts; "bump plleeaaassee"; "eta?"; "pretty please"
  428. // "need this"; "right now"; "still nothing?"; "super helpful"; "for gods sake"
  429. const regexHide = new RegExp("(" + [
  430. "@\\w+",
  431. "\\b(it|is|a|so|the|and|no|on|oh|do|this|any|very|much|here|just|my|me|too|want|yet|image)\\b",
  432. "pretty",
  433. "pl+e+a+s+e+",
  434. "plz",
  435. "totally",
  436. "y+e+s+",
  437. "eta",
  438. "fix",
  439. "right",
  440. "now",
  441. "hope(ful)?",
  442. "still",
  443. "wait(ed|ing)?",
  444. "nothing",
  445. "really",
  446. "add(ed|ing)?",
  447. "need(ed|ing)?",
  448. "updat(es|ed|ing)?",
  449. "(months|years)\\slater",
  450. "back",
  451. "features?",
  452. "infinity", // +Infinity
  453. "useful",
  454. "super",
  455. "helpful",
  456. "thanks",
  457. "for\\sgod'?s\\ssake",
  458. "c['emon]+" // c'mon, com'on, comeon
  459. ].join("|") + ")", "gi");
  460. // image title ":{anything}:", etc.
  461. const regexEmoji = /(:.*:)|[\u{1f300}-\u{1f64f}\u{1f680}-\u{1f6ff}\u{1f900}-\u{1f9ff}]/gu;
  462. const regexWhitespace = /\s+/g;
  463.  
  464. const comments = $$(".js-discussion .TimelineItem").filter(comment => {
  465. const classes = comment.className.split(" ");
  466. return !exceptions.some(ex => classes.includes(ex));
  467. });
  468. const len = comments.length;
  469.  
  470. const loop = () => {
  471. let wrapper, el, tmp, txt, img, hasLink, dupe;
  472. max = 0;
  473. while (max < 20 && indx < len) {
  474. if (indx >= len) {
  475. if (init) {
  476. item.classList.toggle("ghic-has-content", count > 0);
  477. }
  478. return;
  479. }
  480. wrapper = comments[indx];
  481. // save author list to prevent repeat +1s
  482. el = $(".timeline-comment-header .author", wrapper);
  483. txt = (el ? el.textContent || "" : "").toLowerCase();
  484. dupe = true;
  485. if (txt && authors.indexOf(txt) < 0) {
  486. authors[authors.length] = txt;
  487. dupe = false;
  488. }
  489. // .js-comments-holder wraps review comments
  490. el = $(".comment-body, .js-comments-holder", wrapper);
  491. if (el) {
  492. // ignore quoted messages, but get all fragments
  493. tmp = $$(".email-fragment", el);
  494. // some posts only contain a link to related issues; these should not be counted as a +1
  495. // see https://github.com/isaacs/github/issues/618#issuecomment-200869630
  496. hasLink = $$(tmp.length ? ".email-fragment .issue-link" : ".issue-link", el).length;
  497. if (tmp.length) {
  498. // ignore quoted messages
  499. txt = getAllText(tmp);
  500. } else {
  501. txt = (el ? el.textContent || "" : "").trim();
  502. }
  503. if (!txt) {
  504. img = $("img", el);
  505. if (img) {
  506. txt = img.getAttribute("title") || img.getAttribute("alt");
  507. }
  508. }
  509. // remove fluff
  510. txt = (txt || "")
  511. .replace(regexEmoji, "")
  512. .replace(regexHide, "")
  513. .replace(regexPlus, "")
  514. .replace(regexWhitespace, " ")
  515. .trim();
  516. if (txt === "" || (txt.length <= 4 && !hasLink)) {
  517. if (init && !settings.plus1.isHidden) {
  518. // +1 Comments has-content
  519. item.classList.toggle("ghic-has-content", true);
  520. return;
  521. }
  522. if (settings.plus1.isHidden) {
  523. wrapper.classList.add("ghic-hidden", "ghic-highlight");
  524. total++;
  525. // one +1 per author
  526. if (!dupe) {
  527. count++;
  528. }
  529. } else if (!init) {
  530. wrapper.classList.remove("ghic-hidden");
  531. }
  532. max++;
  533. }
  534. }
  535. indx++;
  536. }
  537. if (indx < len) {
  538. setTimeout(() => {
  539. window.requestAnimationFrame(loop);
  540. }, 200);
  541. } else {
  542. if (init) {
  543. item.classList.toggle("ghic-has-content", count > 0);
  544. }
  545. $(".ghic-menu .ghic-plus1 .ghic-count").textContent = total
  546. ? "(" + total + " hidden)"
  547. : " ";
  548. addCountToReaction(count);
  549. }
  550. };
  551. loop();
  552. }
  553.  
  554. function getAllText(els) {
  555. let txt = "";
  556. let indx = els.length;
  557. // text order doesn't matter
  558. while (indx--) {
  559. txt += els[indx].textContent.trim();
  560. }
  561. return txt;
  562. }
  563.  
  564. function addCountToReaction(count) {
  565. if (!count) {
  566. count = ($(".ghic-menu .ghic-plus1 .ghic-count").textContent || "")
  567. .replace(/[()]/g, "")
  568. .trim();
  569. }
  570. const origPost = $(".timeline-comment");
  571. const hasPositiveReaction = $(
  572. ".has-reactions button[value='THUMBS_UP react'], .has-reactions button[value='THUMBS_UP unreact']",
  573. origPost
  574. );
  575. let el = $(".ghic-count", origPost);
  576. if (el) {
  577. // the count may have been appended to the comment & now
  578. // there is a reaction, so remove any "ghic-count" elements
  579. el.parentNode.removeChild(el);
  580. }
  581. if (count) {
  582. if (hasPositiveReaction) {
  583. el = document.createElement("span");
  584. el.className = "ghic-count";
  585. el.textContent = count ? " + " + count + " (from hidden comments)" : "";
  586. hasPositiveReaction.appendChild(el);
  587. } else {
  588. el = document.createElement("p");
  589. el.className = "ghic-count";
  590. el.innerHTML = "<hr>" + plus1Icon + " " + count + " (from hidden comments)";
  591. $(".comment-body", origPost).appendChild(el);
  592. }
  593. }
  594. }
  595.  
  596. function hideParticipant(el) {
  597. if (el) {
  598. el.classList.toggle("comments-hidden");
  599. let name = el.getAttribute("aria-label");
  600. const results = $$(".TimelineItem, .commit-comment, .discussion-item")
  601. .filter(el => {
  602. const author = $(".js-discussion .author", el);
  603. return author ? name === author.textContent.trim() : false;
  604. });
  605. // use a different participant class name to hide timeline events
  606. // or unselecting all users will show everything
  607. if (el.classList.contains("comments-hidden")) {
  608. addClass(results, "ghic-hidden-participant");
  609. } else {
  610. removeClass(results, "ghic-hidden-participant");
  611. }
  612. results = [];
  613. }
  614. }
  615.  
  616. function update() {
  617. if ($("#discussion_bucket") && $(".ghic-button")) {
  618. const keys = Object.keys(settings);
  619. let indx = keys.length;
  620. while (indx--) {
  621. // true flag for init - no need to remove classes
  622. hideStuff(keys[indx], true);
  623. }
  624. addAvatars();
  625. }
  626. }
  627.  
  628. function checkItem(event) {
  629. if (document.getElementById("discussion_bucket")) {
  630. const menuItem = event.target;
  631. const wrap = menuItem && menuItem.closest(".dropdown-item, .ghic-participants");
  632. if (menuItem && wrap) {
  633. if (menuItem.nodeName === "INPUT") {
  634. getInputValues();
  635. saveSettings();
  636. const name = wrap.dataset.ghic;
  637. if (name) {
  638. hideStuff(name);
  639. }
  640. } else if (menuItem.classList.contains("ghic-avatar")) {
  641. // make sure we're targeting the span wrapping the image
  642. hideParticipant(menuItem.nodeName === "IMG"
  643. ? menuItem.parentNode
  644. : menuItem
  645. );
  646. } else if (regex.test(menuItem.nodeName)) {
  647. // clicking on the SVG may target the svg or path inside
  648. hideParticipant(menuItem.closest(".ghic-avatar"));
  649. }
  650. } else if (menuItem.id === "ghic-only-active") {
  651. menuItem
  652. .closest(".select-menu-header")
  653. .classList
  654. .toggle("ghic-active", menuItem.checked);
  655. GM_setValue("onlyActive", menuItem.checked);
  656. }
  657. // Make button show if it is active
  658. const button = $(".ghic-button .btn");
  659. if (button) {
  660. const active = $$(".ghic-hidden, .ghic-hidden-participant").length > 0;
  661. button.classList.toggle("btn-outline", active);
  662. }
  663. }
  664. }
  665.  
  666. function $(selector, el) {
  667. return (el || document).querySelector(selector);
  668. }
  669.  
  670. function $$(selector, el) {
  671. return [...(el || document).querySelectorAll(selector)];
  672. }
  673.  
  674. function addClass(els, name) {
  675. let indx;
  676. const len = els.length;
  677. for (indx = 0; indx < len; indx++) {
  678. els[indx] && els[indx].classList.add(name);
  679. }
  680. return len;
  681. }
  682.  
  683. function removeClass(els, name) {
  684. let indx;
  685. const len = els.length;
  686. for (indx = 0; indx < len; indx++) {
  687. els[indx] && els[indx].classList.remove(name);
  688. }
  689. }
  690.  
  691. function toggleClass(els, name, flag) {
  692. els = Array.isArray(els) ? els : [els];
  693. const undef = typeof flag === "undefined";
  694. let indx = els.length;
  695. while (indx--) {
  696. const el = els[indx];
  697. if (el) {
  698. if (undef) {
  699. flag = !el.classList.contains(name);
  700. }
  701. if (flag) {
  702. el.classList.add(name);
  703. } else {
  704. el.classList.remove(name);
  705. }
  706. }
  707. }
  708. }
  709.  
  710. function init() {
  711. getSettings();
  712. addMenu();
  713. $("body").addEventListener("click", checkItem);
  714. update();
  715. }
  716.  
  717. // update list when content changes
  718. document.addEventListener("ghmo:container", addMenu);
  719. document.addEventListener("ghmo:comments", update);
  720. init();
  721.  
  722. })();