GitHub Toggle Issue Comments

A userscript that toggles issues/pull request comments & messages

当前为 2018-11-16 提交的版本,查看 最新版本

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