GitHub Issue Comments

A userscript that toggles issues/pull request comments & messages

当前为 2021-07-21 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name GitHub Issue Comments
  3. // @version 1.4.6
  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://greasyfork.org/scripts/28721-mutations/code/mutations.js?version=952601
  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. })();