A userscript that toggles issues/pull request comments & messages
当前为 
- // ==UserScript==
 - // @name GitHub Toggle Issue Comments
 - // @version 1.0.15
 - // @description A userscript that toggles issues/pull request comments & messages
 - // @license https://creativecommons.org/licenses/by-sa/4.0/
 - // @namespace http://github.com/Mottie
 - // @include https://github.com/*
 - // @run-at document-idle
 - // @grant GM_addStyle
 - // @grant GM_getValue
 - // @grant GM_setValue
 - // @author Rob Garrison
 - // ==/UserScript==
 - /* global GM_addStyle, GM_getValue, GM_setValue */
 - /*jshint unused:true, esnext:true */
 - (function() {
 - "use strict";
 - GM_addStyle(`
 - .ghic-button { float:right; }
 - .ghic-button .btn:hover div.select-menu-modal-holder { display:block; top:auto; bottom:25px; right:0; }
 - .ghic-right { float:right; }
 - /* pre-wrap set for Firefox; see https://greasyfork.org/en/forum/discussion/9166/x */
 - .ghic-menu label { display:block; padding:5px 15px; white-space:pre-wrap; }
 - .ghic-button .select-menu-header, .ghic-participants { cursor:default; }
 - .ghic-participants { border-top:1px solid #484848; padding:15px; }
 - .ghic-avatar { display:inline-block; float:left; margin: 0 2px 2px 0; cursor:pointer; position:relative; }
 - .ghic-avatar:last-child { margin-bottom:5px; }
 - .ghic-avatar.comments-hidden svg { display:block; position:absolute; top:-2px; left:-2px; z-index:1; }
 - .ghic-avatar.comments-hidden img { opacity:0.5; }
 - .ghic-button .dropdown-item span { font-weight:normal; opacity:.5; }
 - .ghic-button .dropdown-item.ghic-has-content span { opacity:1; }
 - .ghic-button .dropdown-item.ghic-checked span { font-weight:bold; }
 - .ghic-button .dropdown-item.ghic-checked svg,
 - .ghic-button .dropdown-item.ghic-checked .ghic-count { display:inline-block; }
 - .ghic-button .ghic-count { float:left; margin-right:5px; }
 - .ghic-button .select-menu-modal { margin:0; }
 - .ghic-button .ghic-participants { margin-bottom:20px; }
 - /* for testing: ".ghic-hidden { opacity: 0.3; } */
 - .ghic-hidden, .ghic-hidden-participant, .ghic-avatar svg, .ghic-button .ghic-right > *,
 - .ghic-hideReactions .comment-reactions { display:none; }
 - `);
 - let targets,
 - busy = false,
 - // ZenHub addon active (include ZenHub Enterprise)
 - hasZenHub = $(".zhio, .zhe") ? true : false;
 - const regex = /(svg|path)/i,
 - settings = {
 - // example: https://github.com/Mottie/Keyboard/issues/448
 - title: {
 - isHidden: false,
 - name: "ghic-title",
 - selector: ".discussion-item-renamed",
 - label: "Title Changes"
 - },
 - labels: {
 - isHidden: false,
 - name: "ghic-labels",
 - selector: ".discussion-item-labeled, .discussion-item-unlabeled",
 - label: "Label Changes"
 - },
 - state: {
 - isHidden: false,
 - name: "ghic-state",
 - selector: ".discussion-item-reopened, .discussion-item-closed",
 - label: "State Changes (close/reopen)"
 - },
 - // example: https://github.com/jquery/jquery/issues/2986
 - milestone: {
 - isHidden: false,
 - name: "ghic-milestone",
 - selector: ".discussion-item-milestoned",
 - label: "Milestone Changes"
 - },
 - refs: {
 - isHidden: false,
 - name: "ghic-refs",
 - selector: ".discussion-item-ref, .discussion-item-head_ref_deleted",
 - label: "References"
 - },
 - assigned: {
 - isHidden: false,
 - name: "ghic-assigned",
 - selector: ".discussion-item-assigned",
 - label: "Assignment Changes"
 - },
 - // Pull Requests
 - commits: {
 - isHidden: false,
 - name: "ghic-commits",
 - selector: ".discussion-commits",
 - label: "Commits"
 - },
 - // example: https://github.com/jquery/jquery/pull/3014
 - diffOld: {
 - isHidden: false,
 - name: "ghic-diffOld",
 - selector: ".outdated-diff-comment-container",
 - label: "Diff (outdated) Comments"
 - },
 - diffNew: {
 - isHidden: false,
 - name: "ghic-diffNew",
 - selector: "[id^=diff-for-comment-]:not(.outdated-diff-comment-container)",
 - label: "Diff (current) Comments"
 - },
 - // example: https://github.com/jquery/jquery/pull/2949
 - merged: {
 - isHidden: false,
 - name: "ghic-merged",
 - selector: ".discussion-item-merged",
 - label: "Merged"
 - },
 - integrate: {
 - isHidden: false,
 - name: "ghic-integrate",
 - selector: ".discussion-item-integrations-callout",
 - label: "Integrations"
 - },
 - // extras (special treatment - no selector)
 - plus1: {
 - isHidden: false,
 - name: "ghic-plus1",
 - label: "Hide +1s"
 - },
 - reactions: {
 - isHidden: false,
 - name: "ghic-reactions",
 - label: "Reactions"
 - },
 - // page with lots of users to hide:
 - // https://github.com/isaacs/github/issues/215
 - // ZenHub pipeline change
 - pipeline: {
 - isHidden: false,
 - name: "ghic-pipeline",
 - selector: ".discussion-item.zh-discussion-item",
 - label: "ZenHub Pipeline Changes"
 - }
 - };
 - 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>`,
 - iconCheck = `<svg class="octicon octicon-check" height="16" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg>`,
 - 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">`;
 - function $(selector, el) {
 - return (el || document).querySelector(selector);
 - }
 - function $$(selector, el) {
 - return Array.from((el || document).querySelectorAll(selector));
 - }
 - function closest(el, selector) {
 - while (el && el.nodeName !== "BODY" && !el.matches(selector)) {
 - el = el.parentNode;
 - }
 - return el && el.matches(selector) ? el : null;
 - }
 - function addClass(els, name) {
 - let indx,
 - len = els.length;
 - for (indx = 0; indx < len; indx++) {
 - els[indx].classList.add(name);
 - }
 - return len;
 - }
 - function removeClass(els, name) {
 - let indx,
 - len = els.length;
 - for (indx = 0; indx < len; indx++) {
 - els[indx].classList.remove(name);
 - }
 - }
 - function toggleClass(els, name, flag) {
 - els = Array.isArray(els) ? els : [els];
 - let el,
 - indx = els.length;
 - while (indx--) {
 - el = els[indx];
 - if (el) {
 - if (typeof flag === "undefined") {
 - flag = !el.classList.contains(name);
 - }
 - if (flag) {
 - el.classList.add(name);
 - } else {
 - el.classList.remove(name);
 - }
 - }
 - }
 - }
 - function addMenu() {
 - busy = true;
 - if ($("#discussion_bucket") && !$(".ghic-button")) {
 - // update "isHidden" values
 - getSettings();
 - let name, bright, isHidden, isChecked,
 - list = "",
 - keys = Object.keys(settings),
 - header = $(".discussion-sidebar-item:last-child"),
 - menu = document.createElement("div");
 - for (name of keys) {
 - if (!(name === "pipeline" && !hasZenHub)) {
 - // make plus1 and reactions list items always bright
 - bright = name === "plus1" ? " ghic-has-content" : "";
 - isHidden = settings[name].isHidden;
 - isChecked = isHidden ? " ghic-checked": "";
 - // not using multi-line backticks because it adds lots of white-space to the label
 - list += `<label class="dropdown-item${bright}${isChecked}">` +
 - `<span>${settings[name].label}</span>` +
 - `<span class="ghic-right ${settings[name].name}">` +
 - `<input type="checkbox"${isHidden ? " checked" : ""}>` +
 - `${iconCheck}<span class="ghic-count"> </span>` +
 - `</span></label>`;
 - }
 - }
 - menu.className = "ghic-button";
 - menu.innerHTML = `
 - <span class="btn btn-sm" role="button" tabindex="0" aria-haspopup="true">
 - <span class="tooltipped tooltipped-w" aria-label="Toggle issue comments">
 - <svg class="octicon octicon-comment-discussion" height="16" width="16" role="img" viewBox="0 0 16 16">
 - <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>
 - </svg>
 - </span>
 - <div class="select-menu-modal-holder">
 - <div class="select-menu-modal" aria-hidden="true">
 - <div class="select-menu-header" tabindex="-1">
 - <span class="select-menu-title">Toggle items</span>
 - </div>
 - <div class="select-menu-list ghic-menu" role="menu">
 - ${list}
 - <div class="ghic-participants"></div>
 - </div>
 - </div>
 - </div>
 - </span>
 - `;
 - if (hasZenHub) {
 - header.insertBefore(menu, header.childNodes[0]);
 - } else {
 - header.appendChild(menu);
 - }
 - addAvatars();
 - }
 - update();
 - busy = false;
 - }
 - function addAvatars() {
 - let indx = 0,
 - str = "<h3>Hide Comments from</h3>",
 - unique = [],
 - // get all avatars
 - avatars = $$(".timeline-comment-avatar"),
 - len = avatars.length - 1, // last avatar is the new comment with the current user
 - loop = function(callback) {
 - let el, name,
 - max = 0;
 - while (max < 50 && indx < len) {
 - if (indx >= len) {
 - return callback();
 - }
 - el = avatars[indx];
 - name = (el.getAttribute("alt") || "").replace("@", "");
 - if (unique.indexOf(name) < 0) {
 - str += `<span class="ghic-avatar tooltipped tooltipped-n" aria-label="${name}">
 - ${iconHidden}
 - <img class="ghic-avatar avatar" width="24" height="24" src="${el.src}"/>
 - </span>`;
 - unique[unique.length] = name;
 - max++;
 - }
 - indx++;
 - }
 - if (indx < len) {
 - setTimeout(function() {
 - loop(callback);
 - }, 200);
 - } else {
 - callback();
 - }
 - };
 - loop(function() {
 - $(".ghic-participants").innerHTML = str;
 - });
 - }
 - function getSettings() {
 - let name,
 - keys = Object.keys(settings);
 - for (name of keys) {
 - settings[name].isHidden = GM_getValue(settings[name].name, false);
 - }
 - }
 - function saveSettings() {
 - let name,
 - keys = Object.keys(settings);
 - for (name of keys) {
 - GM_setValue(settings[name].name, settings[name].isHidden);
 - }
 - }
 - function getInputValues() {
 - let name, item,
 - keys = Object.keys(settings),
 - menu = $(".ghic-menu");
 - for (name of keys) {
 - if (!(name === "pipeline" && !hasZenHub)) {
 - item = closest($("." + settings[name].name, menu), ".dropdown-item");
 - settings[name].isHidden = $("input", item).checked;
 - toggleClass(item, "ghic-checked", settings[name].isHidden);
 - }
 - }
 - }
 - function hideStuff(name, init) {
 - let count, results,
 - obj = settings[name],
 - isHidden = obj.isHidden,
 - item = closest($(".ghic-menu ." + obj.name), ".dropdown-item");
 - if (obj.selector) {
 - results = $$(obj.selector);
 - toggleClass(item, "ghic-checked", isHidden);
 - if (isHidden) {
 - count = addClass(results, "ghic-hidden");
 - $(".ghic-count", item).textContent = count ? "(" + count + ")" : " ";
 - } else if (!init) {
 - // no need to remove classes on initialization
 - removeClass(results, "ghic-hidden");
 - }
 - toggleClass(item, "ghic-has-content", results.length);
 - } else if (name === "plus1") {
 - hidePlus1(init);
 - } else if (name === "reactions") {
 - toggleClass($("body"), "ghic-hideReactions", isHidden);
 - toggleClass(item, "ghic-has-content", $$(".has-reactions").length);
 - }
 - }
 - function hidePlus1(init) {
 - if (init && !settings.plus1.isHidden) { return; }
 - let max,
 - indx = 0,
 - count = 0,
 - total = 0,
 - // keep a list of post authors to prevent duplicate +1 counts
 - authors = [],
 - // used https://github.com/isaacs/github/issues/215 for matches here...
 - // matches "+1!!!!", "++1", "+!", "+99!!!", "-1", "+ 100", "thumbs up"; ":+1:^21425235"
 - // ignoring -1's...
 - regexPlus = /([?!,.:^[\]()\'\"+-\d]|bump|thumbs|up)/gi,
 - // other comments to hide - they are still counted towards the +1 counter (for now?)
 - // seen "^^^" to bump posts; "bump plleeaaassee"; "eta?"; "pretty please"
 - // "need this"; "right now"; "still nothing?"; "super helpful"; "for gods sake"
 - regexHide = new RegExp("(" + [
 - "@\\w+",
 - "pretty",
 - "pl+e+a+s+e+",
 - "y+e+s+",
 - "eta",
 - "much",
 - "need(ed)?",
 - "fix",
 - "this",
 - "right",
 - "now",
 - "still",
 - "nothing",
 - "super",
 - "helpful",
 - "for\\sgods\\ssake"
 - ].join("|") + ")", "gi"),
 - // image title ":{anything}:", etc.
 - regexEmoji = /:(.*):/,
 - comments = $$(".js-discussion .timeline-comment-wrapper"),
 - len = comments.length,
 - loop = function() {
 - let wrapper, el, tmp, txt, img, hasLink, dupe;
 - max = 0;
 - while (max < 20 && indx < len) {
 - if (indx >= len) {
 - return;
 - }
 - wrapper = comments[indx];
 - // save author list to prevent repeat +1s
 - el = $(".timeline-comment-header .author", wrapper);
 - txt = (el ? el.textContent || "" : "").toLowerCase();
 - dupe = true;
 - if (txt && authors.indexOf(txt) < 0) {
 - authors[authors.length] = txt;
 - dupe = false;
 - }
 - el = $(".comment-body", wrapper);
 - // ignore quoted messages, but get all fragments
 - tmp = $$(".email-fragment", el);
 - // some posts only contain a link to related issues; these should not be counted as a +1
 - // see https://github.com/isaacs/github/issues/618#issuecomment-200869630
 - hasLink = $$(tmp.length ? ".email-fragment .issue-link" : ".issue-link", el).length;
 - if (tmp.length) {
 - // ignore quoted messages
 - txt = getAllText(tmp);
 - } else {
 - txt = el.textContent.trim();
 - }
 - if (!txt) {
 - img = $("img", el);
 - if (img) {
 - txt = img.getAttribute("title") || img.getAttribute("alt");
 - }
 - }
 - // remove fluff
 - txt = txt.replace(regexEmoji, "").replace(regexPlus, "").replace(regexHide, "").trim();
 - if (txt === "" || (txt.length < 4 && !hasLink)) {
 - if (settings.plus1.isHidden) {
 - wrapper.classList.add("ghic-hidden");
 - total++;
 - // one +1 per author
 - if (!dupe) {
 - count++;
 - }
 - } else if (!init) {
 - wrapper.classList.remove("ghic-hidden");
 - }
 - max++;
 - }
 - indx++;
 - }
 - if (indx < len) {
 - setTimeout(function() {
 - loop();
 - }, 200);
 - } else {
 - $(".ghic-menu .ghic-plus1 .ghic-count").textContent = total ? "(" + total + ")" : " ";
 - toggleClass($(".ghic-menu ." + settings.plus1.name), "ghic-has-content", total);
 - addCountToReaction(count);
 - }
 - };
 - loop();
 - }
 - function getAllText(el) {
 - let txt = "",
 - indx = el.length;
 - // text order doesn't matter
 - while (indx--) {
 - txt += el[indx].textContent.trim();
 - }
 - return txt;
 - }
 - function addCountToReaction(count) {
 - if (!count) {
 - count = ($(".ghic-menu .ghic-plus1 .ghic-count").textContent || "")
 - .replace(/[()]/g, "")
 - .trim();
 - }
 - let comment = $(".timeline-comment"),
 - tmp = $(".has-reactions button[value='+1 react'], .has-reactions button[value='+1 unreact']", comment),
 - el = $(".ghic-count", comment);
 - if (el) {
 - // the count may have been appended to the comment & now
 - // there is a reaction, so remove any "ghic-count" elements
 - el.parentNode.removeChild(el);
 - }
 - if (count) {
 - if (tmp) {
 - el = document.createElement("span");
 - el.className = "ghic-count";
 - el.textContent = count ? " + " + count + " (from hidden comments)" : "";
 - tmp.appendChild(el);
 - } else {
 - el = document.createElement("p");
 - el.className = "ghic-count";
 - el.innerHTML = "<hr>" + plus1Icon + " " + count + " (from hidden comments)";
 - $(".comment-body", comment).appendChild(el);
 - }
 - }
 - }
 - function hideParticipant(el) {
 - let els, indx, len, hide, name,
 - results = [];
 - if (el) {
 - el.classList.toggle("comments-hidden");
 - hide = el.classList.contains("comments-hidden");
 - name = el.getAttribute("aria-label");
 - els = $$(".js-discussion .author");
 - len = els.length;
 - for (indx = 0; indx < len; indx++) {
 - if (els[indx].textContent.trim() === name) {
 - results[results.length] = closest(els[indx], ".timeline-comment-wrapper, .commit-comment, .discussion-item");
 - }
 - }
 - // use a different participant class name to hide timeline events
 - // or unselecting all users will show everything
 - if (el.classList.contains("comments-hidden")) {
 - addClass(results, "ghic-hidden-participant");
 - } else {
 - removeClass(results, "ghic-hidden-participant");
 - }
 - results = [];
 - }
 - }
 - function update() {
 - busy = true;
 - if ($("#discussion_bucket") && $(".ghic-button")) {
 - let keys = Object.keys(settings),
 - indx = keys.length;
 - while (indx--) {
 - // true flag for init - no need to remove classes
 - hideStuff(keys[indx], true);
 - }
 - }
 - busy = false;
 - }
 - function checkItem(event) {
 - busy = true;
 - if (document.getElementById("discussion_bucket")) {
 - let name,
 - target = event.target,
 - wrap = target && target.parentNode;
 - if (target && wrap) {
 - if (target.nodeName === "INPUT" && wrap.classList.contains("ghic-right")) {
 - getInputValues();
 - saveSettings();
 - // extract ghic-{name}, because it matches the name in settings
 - name = wrap.className.replace("ghic-right", "").replace("ghic-has-content", "").trim();
 - if (wrap.classList.contains(name)) {
 - hideStuff(name.replace("ghic-", ""));
 - }
 - } else if (target.classList.contains("ghic-avatar")) {
 - // make sure we're targeting the span wrapping the image
 - hideParticipant(target.nodeName === "IMG" ? target.parentNode : target);
 - } else if (regex.test(target.nodeName)) {
 - // clicking on the SVG may target the svg or path inside
 - hideParticipant(closest(target, ".ghic-avatar"));
 - }
 - }
 - }
 - busy = false;
 - }
 - function init() {
 - busy = true;
 - getSettings();
 - addMenu();
 - $("body").addEventListener("input", checkItem);
 - $("body").addEventListener("click", checkItem);
 - update();
 - busy = false;
 - }
 - // DOM targets - to detect GitHub dynamic ajax page loading
 - targets = $$("#js-repo-pjax-container, #js-pjax-container, .js-discussion");
 - // update TOC when content changes
 - Array.prototype.forEach.call(targets, function(target) {
 - new MutationObserver(function(mutations) {
 - mutations.forEach(function(mutation) {
 - // preform checks before adding code wrap to minimize function calls
 - if (!busy && mutation.target === target) {
 - addMenu();
 - }
 - });
 - }).observe(target, {
 - childList: true,
 - subtree: true
 - });
 - });
 - init();
 - })();