Greasy Fork 还支持 简体中文。

GitHub Issue Comments

A userscript that toggles issues/pull request comments & messages

  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://greasyfork.org/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. })();