Greasy Fork is available in English.

GitHub Issue Comments

A userscript that toggles issues/pull request comments & messages

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