GitHub Issue Comments

A userscript that toggles issues/pull request comments & messages

当前为 2021-01-31 提交的版本,查看 最新版本

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