GitHub Issue Comments

A userscript that toggles issues/pull request comments & messages

当前为 2020-03-01 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name GitHub Issue Comments
  3. // @version 1.4.3
  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=666427
  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. // page with lots of users to hide:
  212. // https://github.com/isaacs/github/issues/215
  213. // ZenHub pipeline change
  214. pipeline: {
  215. isHidden: false,
  216. name: "ghic-pipeline",
  217. selector: ".TimelineItem-badge .zh-icon-board-small",
  218. label: "ZenHub Pipeline Changes"
  219. }
  220. };
  221.  
  222. 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>`;
  223. 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">`;
  224.  
  225. function addMenu() {
  226. if ($("#discussion_bucket") && !$(".ghic-button")) {
  227. // update "isHidden" values
  228. getSettings();
  229. let name, isHidden, isChecked,
  230. list = "",
  231. keys = Object.keys(settings),
  232. onlyActive = GM_getValue("onlyActive", false),
  233. header = $(".discussion-sidebar-item:last-child"),
  234. menu = document.createElement("div");
  235.  
  236. for (name of keys) {
  237. if (!(name === "pipeline" && !hasZenHub)) {
  238. isHidden = settings[name].isHidden;
  239. isChecked = isHidden ? "" : "ghic-checked";
  240. list += `<label class="dropdown-item ${isChecked} ${settings[name].name}" data-ghic="${name}">
  241. <span>${settings[name].label} <span class="ghic-count"> </span></span>
  242. <span class="ghic-right">
  243. <input type="checkbox"${isHidden ? "" : " checked"}>
  244. <span class="ghic-toggle"></span>
  245. </span>
  246. </label>`;
  247. }
  248. }
  249.  
  250. menu.className = "ghic-button";
  251. menu.innerHTML = `
  252. <span class="btn btn-sm" role="button" tabindex="0" aria-haspopup="true">
  253. <span class="tooltipped tooltipped-w" aria-label="Toggle issue comments">
  254. <svg class="octicon octicon-comment-discussion" height="16" width="16" role="img" viewBox="0 0 16 16">
  255. <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>
  256. </svg>
  257. </span>
  258. <div class="select-menu-modal-holder ghic-menu-wrapper">
  259. <div class="select-menu-modal" aria-hidden="true">
  260. <div class="select-menu-header ${onlyActive ? "ghic-active" : ""}" tabindex="-1">
  261. <span class="select-menu-title">Toggle items</span>
  262. <label class="ghic-right tooltipped tooltipped-w" aria-label="Only show active items">
  263. <input id="ghic-only-active" type="checkbox" ${onlyActive ? "checked" : ""}>
  264. <span class="ghic-toggle"></span>
  265. </label>
  266. </div>
  267. <div class="select-menu-list ghic-menu" role="menu">
  268. ${list}
  269. <div class="ghic-participants">
  270. <p><strong>Hide Comments from</strong></p>
  271. <div class="ghic-list"></div>
  272. </div>
  273. </div>
  274. </div>
  275. </div>
  276. </span>
  277. `;
  278. if (hasZenHub) {
  279. header.insertBefore(menu, header.childNodes[0]);
  280. } else {
  281. header.appendChild(menu);
  282. }
  283. addAvatars();
  284. }
  285. update();
  286. }
  287.  
  288. function addAvatars() {
  289. let indx = 0;
  290. let str = "";
  291. const list = $(".ghic-list");
  292. const unique = $$("span.ghic-avatar", list).map(el => el.getAttribute("aria-label"));
  293. // get all avatars
  294. const avatars = $$(".TimelineItem-avatar img");
  295. const len = avatars.length - 1; // last avatar is the new comment with the current user
  296. const updateAvatars = () => {
  297. list.innerHTML += str;
  298. str = "";
  299. };
  300.  
  301. const loop = () => {
  302. let el, name;
  303. let max = 0;
  304. while (max < 50 && indx <= len) {
  305. if (indx > len) {
  306. return updateAvatars();
  307. }
  308. el = avatars[indx];
  309. name = (el.getAttribute("alt") || "").replace("@", "");
  310. if (!unique.includes(name)) {
  311. str += `<span class="ghic-avatar tooltipped tooltipped-n" aria-label="${name}">
  312. ${iconHidden}
  313. <img class="ghic-avatar avatar" width="24" height="24" src="${el.src}"/>
  314. </span>`;
  315. unique[unique.length] = name;
  316. max++;
  317. }
  318. indx++;
  319. }
  320. updateAvatars();
  321. if (indx < len) {
  322. setTimeout(() => {
  323. window.requestAnimationFrame(loop);
  324. }, 200);
  325. }
  326. };
  327. loop();
  328. }
  329.  
  330. function getSettings() {
  331. const keys = Object.keys(settings);
  332. for (let name of keys) {
  333. settings[name].isHidden = GM_getValue(settings[name].name, false);
  334. }
  335. }
  336.  
  337. function saveSettings() {
  338. const keys = Object.keys(settings);
  339. for (let name of keys) {
  340. GM_setValue(settings[name].name, settings[name].isHidden);
  341. }
  342. }
  343.  
  344. function getInputValues() {
  345. const keys = Object.keys(settings);
  346. const menu = $(".ghic-menu");
  347. for (let name of keys) {
  348. if (!(name === "pipeline" && !hasZenHub)) {
  349. const item = $(`.${settings[name].name}`, menu).closest(".dropdown-item");
  350. if (item) {
  351. settings[name].isHidden = !$("input", item).checked;
  352. toggleClass(item, "ghic-checked", !settings[name].isHidden);
  353. }
  354. }
  355. }
  356. }
  357.  
  358. function hideStuff(name, init) {
  359. const obj = settings[name];
  360. const item = $(".ghic-menu .dropdown-item." + obj.name);
  361. if (item) {
  362. const isHidden = obj.isHidden;
  363. if (typeof obj.callback === "function") {
  364. obj.callback({ obj, item, init });
  365. } else if (obj.selector) {
  366. let results = $$(obj.selector).map(el =>
  367. el.closest(obj.wrapper || ".TimelineItem, .Details-element")
  368. );
  369. if (obj.containsText) {
  370. results = results.filter(
  371. el => el && el.textContent.includes(obj.containsText)
  372. );
  373. }
  374. if (obj.notSelector) {
  375. results = results.filter(el => el && !$(obj.notSelector, el));
  376. }
  377. toggleClass(item, "ghic-checked", !isHidden);
  378. if (isHidden) {
  379. const count = addClass(results, "ghic-hidden");
  380. $(".ghic-count", item).textContent = count ? `(${count} hidden)` : " ";
  381. } else if (!init) {
  382. // no need to remove classes on initialization
  383. removeClass(results, "ghic-hidden");
  384. }
  385. toggleClass(item, "ghic-has-content", results.length);
  386. }
  387. }
  388. }
  389.  
  390. function hideReactions({ obj, item }) {
  391. toggleClass($("body"), "ghic-hide-reactions", obj.isHidden);
  392. toggleClass(item, "ghic-has-content", $$(".has-reactions").length > 0);
  393. // make first comment reactions visible
  394. const origPost = $(".TimelineItem .comment-reactions");
  395. if (origPost && origPost.classList.contains("has-reactions")) {
  396. origPost.style.display = "block";
  397. }
  398. }
  399.  
  400. function hidePlus1({ item, init }) {
  401. let max,
  402. indx = 0,
  403. count = 0,
  404. total = 0;
  405. // keep a list of post authors to prevent duplicate +1 counts
  406. const authors = [];
  407. // used https://github.com/isaacs/github/issues/215 for matches here...
  408. // matches "+1!!!!", "++1", "+!", "+99!!!", "-1", "+ 100", "thumbs up"; ":+1:^21425235"
  409. // ignoring -1's... add unicode for thumbs up; it gets replaced with an image in Windows
  410. const regexPlus = /([?!*,.:^[\]()\'\"+-\d]|bump|thumbs|up|\ud83d\udc4d)/gi;
  411. // other comments to hide - they are still counted towards the +1 counter (for now?)
  412. // seen "^^^" to bump posts; "bump plleeaaassee"; "eta?"; "pretty please"
  413. // "need this"; "right now"; "still nothing?"; "super helpful"; "for gods sake"
  414. const regexHide = new RegExp("(" + [
  415. "@\\w+",
  416. "\\b(it|is|a|so|the|and|no|on|oh|do|this|any|very|much|here|just|my|me|too|want|yet|image)\\b",
  417. "pretty",
  418. "pl+e+a+s+e+",
  419. "plz",
  420. "totally",
  421. "y+e+s+",
  422. "eta",
  423. "fix",
  424. "right",
  425. "now",
  426. "hope(ful)?",
  427. "still",
  428. "wait(ed|ing)?",
  429. "nothing",
  430. "really",
  431. "add(ed|ing)?",
  432. "need(ed|ing)?",
  433. "updat(es|ed|ing)?",
  434. "(months|years)\\slater",
  435. "back",
  436. "features?",
  437. "infinity", // +Infinity
  438. "useful",
  439. "super",
  440. "helpful",
  441. "thanks",
  442. "for\\sgod'?s\\ssake",
  443. "c['emon]+" // c'mon, com'on, comeon
  444. ].join("|") + ")", "gi");
  445. // image title ":{anything}:", etc.
  446. const regexEmoji = /(:.*:)|[\u{1f300}-\u{1f64f}\u{1f680}-\u{1f6ff}\u{1f900}-\u{1f9ff}]/gu;
  447. const regexWhitespace = /\s+/g;
  448.  
  449. const comments = $$(".js-discussion .TimelineItem").filter(comment => {
  450. const classes = comment.className.split(" ");
  451. return !exceptions.some(ex => classes.includes(ex));
  452. });
  453. const len = comments.length;
  454.  
  455. const loop = () => {
  456. let wrapper, el, tmp, txt, img, hasLink, dupe;
  457. max = 0;
  458. while (max < 20 && indx < len) {
  459. if (indx >= len) {
  460. if (init) {
  461. item.classList.toggle("ghic-has-content", count > 0);
  462. }
  463. return;
  464. }
  465. wrapper = comments[indx];
  466. // save author list to prevent repeat +1s
  467. el = $(".timeline-comment-header .author", wrapper);
  468. txt = (el ? el.textContent || "" : "").toLowerCase();
  469. dupe = true;
  470. if (txt && authors.indexOf(txt) < 0) {
  471. authors[authors.length] = txt;
  472. dupe = false;
  473. }
  474. // .js-comments-holder wraps review comments
  475. el = $(".comment-body, .js-comments-holder", wrapper);
  476. if (el) {
  477. // ignore quoted messages, but get all fragments
  478. tmp = $$(".email-fragment", el);
  479. // some posts only contain a link to related issues; these should not be counted as a +1
  480. // see https://github.com/isaacs/github/issues/618#issuecomment-200869630
  481. hasLink = $$(tmp.length ? ".email-fragment .issue-link" : ".issue-link", el).length;
  482. if (tmp.length) {
  483. // ignore quoted messages
  484. txt = getAllText(tmp);
  485. } else {
  486. txt = (el ? el.textContent || "" : "").trim();
  487. }
  488. if (!txt) {
  489. img = $("img", el);
  490. if (img) {
  491. txt = img.getAttribute("title") || img.getAttribute("alt");
  492. }
  493. }
  494. // remove fluff
  495. txt = (txt || "")
  496. .replace(regexEmoji, "")
  497. .replace(regexHide, "")
  498. .replace(regexPlus, "")
  499. .replace(regexWhitespace, " ")
  500. .trim();
  501. if (txt === "" || (txt.length <= 4 && !hasLink)) {
  502. if (init && !settings.plus1.isHidden) {
  503. // +1 Comments has-content
  504. item.classList.toggle("ghic-has-content", true);
  505. return;
  506. }
  507. if (settings.plus1.isHidden) {
  508. wrapper.classList.add("ghic-hidden", "ghic-highlight");
  509. total++;
  510. // one +1 per author
  511. if (!dupe) {
  512. count++;
  513. }
  514. } else if (!init) {
  515. wrapper.classList.remove("ghic-hidden");
  516. }
  517. max++;
  518. }
  519. }
  520. indx++;
  521. }
  522. if (indx < len) {
  523. setTimeout(() => {
  524. window.requestAnimationFrame(loop);
  525. }, 200);
  526. } else {
  527. if (init) {
  528. item.classList.toggle("ghic-has-content", count > 0);
  529. }
  530. $(".ghic-menu .ghic-plus1 .ghic-count").textContent = total
  531. ? "(" + total + " hidden)"
  532. : " ";
  533. addCountToReaction(count);
  534. }
  535. };
  536. loop();
  537. }
  538.  
  539. function getAllText(els) {
  540. let txt = "";
  541. let indx = els.length;
  542. // text order doesn't matter
  543. while (indx--) {
  544. txt += els[indx].textContent.trim();
  545. }
  546. return txt;
  547. }
  548.  
  549. function addCountToReaction(count) {
  550. if (!count) {
  551. count = ($(".ghic-menu .ghic-plus1 .ghic-count").textContent || "")
  552. .replace(/[()]/g, "")
  553. .trim();
  554. }
  555. const origPost = $(".timeline-comment");
  556. const hasPositiveReaction = $(
  557. ".has-reactions button[value='THUMBS_UP react'], .has-reactions button[value='THUMBS_UP unreact']",
  558. origPost
  559. );
  560. let el = $(".ghic-count", origPost);
  561. if (el) {
  562. // the count may have been appended to the comment & now
  563. // there is a reaction, so remove any "ghic-count" elements
  564. el.parentNode.removeChild(el);
  565. }
  566. if (count) {
  567. if (hasPositiveReaction) {
  568. el = document.createElement("span");
  569. el.className = "ghic-count";
  570. el.textContent = count ? " + " + count + " (from hidden comments)" : "";
  571. hasPositiveReaction.appendChild(el);
  572. } else {
  573. el = document.createElement("p");
  574. el.className = "ghic-count";
  575. el.innerHTML = "<hr>" + plus1Icon + " " + count + " (from hidden comments)";
  576. $(".comment-body", origPost).appendChild(el);
  577. }
  578. }
  579. }
  580.  
  581. function hideParticipant(el) {
  582. if (el) {
  583. el.classList.toggle("comments-hidden");
  584. let name = el.getAttribute("aria-label");
  585. const results = $$(".TimelineItem, .commit-comment, .discussion-item")
  586. .filter(el => {
  587. const author = $(".js-discussion .author", el);
  588. return author ? name === author.textContent.trim() : false;
  589. });
  590. // use a different participant class name to hide timeline events
  591. // or unselecting all users will show everything
  592. if (el.classList.contains("comments-hidden")) {
  593. addClass(results, "ghic-hidden-participant");
  594. } else {
  595. removeClass(results, "ghic-hidden-participant");
  596. }
  597. results = [];
  598. }
  599. }
  600.  
  601. function update() {
  602. if ($("#discussion_bucket") && $(".ghic-button")) {
  603. const keys = Object.keys(settings);
  604. let indx = keys.length;
  605. while (indx--) {
  606. // true flag for init - no need to remove classes
  607. hideStuff(keys[indx], true);
  608. }
  609. addAvatars();
  610. }
  611. }
  612.  
  613. function checkItem(event) {
  614. if (document.getElementById("discussion_bucket")) {
  615. const menuItem = event.target;
  616. const wrap = menuItem && menuItem.closest(".dropdown-item, .ghic-participants");
  617. if (menuItem && wrap) {
  618. if (menuItem.nodeName === "INPUT") {
  619. getInputValues();
  620. saveSettings();
  621. const name = wrap.dataset.ghic;
  622. if (name) {
  623. hideStuff(name);
  624. }
  625. } else if (menuItem.classList.contains("ghic-avatar")) {
  626. // make sure we're targeting the span wrapping the image
  627. hideParticipant(menuItem.nodeName === "IMG"
  628. ? menuItem.parentNode
  629. : menuItem
  630. );
  631. } else if (regex.test(menuItem.nodeName)) {
  632. // clicking on the SVG may target the svg or path inside
  633. hideParticipant(menuItem.closest(".ghic-avatar"));
  634. }
  635. } else if (menuItem.id === "ghic-only-active") {
  636. menuItem
  637. .closest(".select-menu-header")
  638. .classList
  639. .toggle("ghic-active", menuItem.checked);
  640. GM_setValue("onlyActive", menuItem.checked);
  641. }
  642. // Make button show if it is active
  643. const button = $(".ghic-button .btn");
  644. if (button) {
  645. const active = $$(".ghic-hidden, .ghic-hidden-participant").length > 0;
  646. button.classList.toggle("btn-outline", active);
  647. }
  648. }
  649. }
  650.  
  651. function $(selector, el) {
  652. return (el || document).querySelector(selector);
  653. }
  654.  
  655. function $$(selector, el) {
  656. return [...(el || document).querySelectorAll(selector)];
  657. }
  658.  
  659. function addClass(els, name) {
  660. let indx;
  661. const len = els.length;
  662. for (indx = 0; indx < len; indx++) {
  663. els[indx] && els[indx].classList.add(name);
  664. }
  665. return len;
  666. }
  667.  
  668. function removeClass(els, name) {
  669. let indx;
  670. const len = els.length;
  671. for (indx = 0; indx < len; indx++) {
  672. els[indx] && els[indx].classList.remove(name);
  673. }
  674. }
  675.  
  676. function toggleClass(els, name, flag) {
  677. els = Array.isArray(els) ? els : [els];
  678. const undef = typeof flag === "undefined";
  679. let indx = els.length;
  680. while (indx--) {
  681. const el = els[indx];
  682. if (el) {
  683. if (undef) {
  684. flag = !el.classList.contains(name);
  685. }
  686. if (flag) {
  687. el.classList.add(name);
  688. } else {
  689. el.classList.remove(name);
  690. }
  691. }
  692. }
  693. }
  694.  
  695. function init() {
  696. getSettings();
  697. addMenu();
  698. $("body").addEventListener("click", checkItem);
  699. update();
  700. }
  701.  
  702. // update list when content changes
  703. document.addEventListener("ghmo:container", addMenu);
  704. document.addEventListener("ghmo:comments", update);
  705. init();
  706.  
  707. })();