GitHub Issue Comments

A userscript that toggles issues/pull request comments & messages

18.02.2020 itibariyledir. En son verisyonu görün.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

You will need to install an extension such as Tampermonkey to install this script.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Zateb bir user-style yöneticim var, yükleyeyim!)

  1. // ==UserScript==
  2. // @name GitHub Issue Comments
  3. // @version 1.4.2
  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://greatest.deepsurf.us/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. function: hidePlus1,
  196. },
  197. reactions: {
  198. isHidden: false,
  199. name: "ghic-reactions",
  200. label: "Reactions",
  201. function: 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. const 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. })();