// ==UserScript==
// @name GitHub Issue Comments
// @version 1.4.7
// @description A userscript that toggles issues/pull request comments & messages
// @license MIT
// @author Rob Garrison
// @namespace https://github.com/Mottie
// @match https://github.com/*
// @run-at document-idle
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @require https://greasyfork.org/scripts/28721-mutations/code/mutations.js?version=1108163
// @icon https://github.githubassets.com/pinned-octocat.svg
// @updateURL https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-issue-comments.user.js
// @downloadURL https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-issue-comments.user.js
// @supportURL https://github.com/Mottie/GitHub-userscripts/issues
// ==/UserScript==
(() => {
"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 { position:absolute; right:10px; top:9px; }
.ghic-button .select-menu-header, .ghic-participants { cursor:default; display:block; }
.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 { font-weight:normal; position:relative; }
.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:not(.ghic-checked) .ghic-count { display:inline-block; }
.ghic-button .dropdown-item:not(.ghic-checked) { text-decoration:line-through; }
.ghic-button .ghic-count { margin-left:5px; }
.ghic-button .select-menu-modal { margin:0; }
.ghic-button .ghic-participants { margin-bottom:20px; }
/* for testing: ".ghic-hidden { opacity: 0.3; } */
body .ghic-hidden { display:none !important; }
.ghic-hidden-participant, body .ghic-avatar svg, .dropdown-item.ghic-checked .ghic-count,
.ghic-hide-reactions .TimelineItem .comment-reactions,
.select-menu-header.ghic-active + .select-menu-list .dropdown-item:not(.ghic-has-content) { display:none; }
.ghic-menu-wrapper input[type=checkbox] { height:0; width:0; visibility:hidden; position:absolute; }
.ghic-menu-wrapper .ghic-toggle { cursor:pointer; text-indent:-9999px; width:20px; height:10px;
background:grey; display:block; border-radius:10px; position:relative; }
.ghic-menu-wrapper .ghic-toggle:after { content:''; position:absolute; top:0; left:1px; width:9px;
height:9px; background:#fff; border-radius:9px; transition:.3s; }
.ghic-menu-wrapper input:checked + .ghic-toggle { background:#070; }
.ghic-menu-wrapper input:checked + .ghic-toggle:after { top:0; left:calc(100% - 1px);
transform:translateX(-100%); }
.ghic-menu-wrapper .ghic-toggle:active:after { width:13px; }
.TimelineItem.ghic-highlight .comment { border-color:#800 !important; }
`);
const regex = /(svg|path)/i;
// ZenHub addon active (include ZenHub Enterprise)
const hasZenHub = $(".zhio, .zhe") ? true : false;
const exceptions = [
"ghsr-sort-block" // sort reactions block (github-sort-reactions.user.js)
];
const settings = {
// example: https://github.com/Mottie/Keyboard/issues/448
title: {
isHidden: false,
name: "ghic-title",
selector: ".TimelineItem-badge .octicon-pencil",
containsText: "changed the title",
label: "Title Changes"
},
labels: {
isHidden: false,
name: "ghic-labels",
selector: ".TimelineItem-badge .octicon-tag",
containsText: "label",
label: "Label Changes"
},
state: {
isHidden: false,
name: "ghic-state",
selector: `.TimelineItem-badge .octicon-primitive-dot,
.TimelineItem-badge .octicon-circle-slash`,
label: "State Changes (close/reopen)"
},
// example: https://github.com/jquery/jquery/issues/2986
milestone: {
isHidden: false,
name: "ghic-milestone",
selector: ".TimelineItem-badge .octicon-milestone",
label: "Milestone Changes"
},
refs: {
isHidden: false,
name: "ghic-refs",
selector: ".TimelineItem-badge .octicon-bookmark",
containsText: "referenced",
label: "References"
},
mentioned: {
isHidden: false,
name: "ghic-mentions",
selector: ".TimelineItem-badge .octicon-bookmark",
containsText: "mentioned",
label: "Mentioned"
},
assigned: {
isHidden: false,
name: "ghic-assigned",
selector: ".TimelineItem-badge .octicon-person",
label: "Assignment Changes"
},
// Pull Requests
commits: {
isHidden: false,
name: "ghic-commits",
selector: `.TimelineItem-badge .octicon-repo-push,
.TimelineItem-badge .octicon-git-commit`,
wrapper: ".js-timeline-item",
label: "Commits"
},
forcePush: {
isHidden: false,
name: "ghic-force-push",
selector: ".TimelineItem-badge .octicon-repo-force-push",
label: "Force Push"
},
// example: https://github.com/jquery/jquery/pull/3014
reviews: {
isHidden: false,
name: "ghic-reviews",
selector: `.TimelineItem-badge .octicon-eye, .TimelineItem-badge .octicon-x,
.TimelineItem-badge .octicon-check, .js-resolvable-timeline-thread-container`,
wrapper: ".js-timeline-item",
label: "Reviews (All)"
},
outdated: {
isHidden: false,
name: "ghic-outdated",
selector: ".js-resolvable-timeline-thread-container .Label--outline[title*='Outdated']",
wrapper: ".js-resolvable-timeline-thread-container",
label: "- Reviews (Outdated)"
},
resolved: {
isHidden: false,
name: "ghic-resolved",
selector: ".js-resolvable-timeline-thread-container[data-resolved='true']",
label: "- Reviews (Resolved)"
},
diffNew: {
isHidden: false,
name: "ghic-diffNew",
selector: ".js-resolvable-timeline-thread-container",
notSelector: ".Label--outline[title*='Outdated']",
wrapper: ".js-resolvable-timeline-thread-container",
label: "- Reviews (Current)"
},
// example: https://github.com/jquery/jquery/pull/2949
merged: {
isHidden: false,
name: "ghic-merged",
selector: ".TimelineItem-badge .octicon-git-merge",
label: "Merged"
},
integrate: {
isHidden: false,
name: "ghic-integrate",
selector: ".TimelineItem-badge .octicon-rocket",
label: "Integrations"
},
// bot: {
// isHidden: false,
// name: "ghic-bot",
// selector: ".Label--outline",
// containsText: "bot",
// label: "Bot"
// },
// similar comments
similar: {
isHidden: false,
name: "ghic-similar",
selector: `.js-discussion > .Details-element.details-reset:not([open]),
#js-progressive-timeline-item-container > .Details-element.details-reset:not([open])`,
label: "Similar comments"
},
// extras (special treatment - no selector)
plus1: {
isHidden: false,
name: "ghic-plus1",
label: "+1 Comments",
callback: hidePlus1,
},
reactions: {
isHidden: false,
name: "ghic-reactions",
label: "Reactions",
callback: hideReactions,
},
projects: {
isHidden: false,
name: "ghic-projects",
selector: `.discussion-item-added_to_project,
.discussion-item-moved_columns_in_project,
.discussion-item-removed_from_project`,
label: "Project Changes"
},
// Jenkins auto-merged
autoMerged: {
isHidden: false,
name: "ghic-automerged",
selector: ".Details a[title*='auto-merged' i]",
label: "Auto merged"
},
// Jenkins temp deployments that have become inactive
inactive: {
isHidden: false,
name: "ghic-inactive",
selector: ".deployment-status-label.is-inactive, .Label[title*='inactive' i]",
label: "Inactive deployments"
},
// page with lots of users to hide:
// https://github.com/isaacs/github/issues/215
// ZenHub pipeline change
pipeline: {
isHidden: false,
name: "ghic-pipeline",
selector: ".TimelineItem-badge .zh-icon-board-small",
label: "ZenHub Pipeline Changes"
}
};
const iconHidden = ``;
const plus1Icon = ``;
function addMenu() {
if ($("#discussion_bucket") && !$(".ghic-button")) {
// update "isHidden" values
getSettings();
let name, isHidden, isChecked,
list = "",
keys = Object.keys(settings),
onlyActive = GM_getValue("onlyActive", false),
header = $(".discussion-sidebar-item:last-child"),
menu = document.createElement("div");
for (name of keys) {
if (!(name === "pipeline" && !hasZenHub)) {
isHidden = settings[name].isHidden;
isChecked = isHidden ? "" : "ghic-checked";
list += ``;
}
}
menu.className = "ghic-button";
menu.innerHTML = `
`;
if (hasZenHub) {
header.insertBefore(menu, header.childNodes[0]);
} else {
header.appendChild(menu);
}
addAvatars();
}
update();
}
function addAvatars() {
let indx = 0;
let str = "";
const list = $(".ghic-list");
const unique = $$("span.ghic-avatar", list).map(el => el.getAttribute("aria-label"));
// get all avatars
const avatars = $$(".TimelineItem-avatar img");
const len = avatars.length - 1; // last avatar is the new comment with the current user
const updateAvatars = () => {
list.innerHTML += str;
str = "";
};
const loop = () => {
let el, name;
let max = 0;
while (max < 50 && indx <= len) {
if (indx > len) {
return updateAvatars();
}
el = avatars[indx];
name = (el.getAttribute("alt") || "").replace("@", "");
if (!unique.includes(name)) {
str += `
${iconHidden}
`;
unique[unique.length] = name;
max++;
}
indx++;
}
updateAvatars();
if (indx < len) {
setTimeout(() => {
window.requestAnimationFrame(loop);
}, 200);
}
};
loop();
}
function getSettings() {
const keys = Object.keys(settings);
for (let name of keys) {
settings[name].isHidden = GM_getValue(settings[name].name, false);
}
}
function saveSettings() {
const keys = Object.keys(settings);
for (let name of keys) {
GM_setValue(settings[name].name, settings[name].isHidden);
}
}
function getInputValues() {
const keys = Object.keys(settings);
const menu = $(".ghic-menu");
for (let name of keys) {
if (!(name === "pipeline" && !hasZenHub)) {
const item = $(`.${settings[name].name}`, menu).closest(".dropdown-item");
if (item) {
settings[name].isHidden = !$("input", item).checked;
toggleClass(item, "ghic-checked", !settings[name].isHidden);
}
}
}
}
function hideStuff(name, init) {
const obj = settings[name];
const item = $(".ghic-menu .dropdown-item." + obj.name);
if (item) {
const isHidden = obj.isHidden;
if (typeof obj.callback === "function") {
obj.callback({ obj, item, init });
} else if (obj.selector) {
let results = $$(obj.selector).map(el =>
el.closest(obj.wrapper || ".TimelineItem, .Details-element")
);
if (obj.containsText) {
results = results.filter(
el => el && el.textContent.includes(obj.containsText)
);
}
if (obj.notSelector) {
results = results.filter(el => el && !$(obj.notSelector, el));
}
toggleClass(item, "ghic-checked", !isHidden);
if (isHidden) {
const count = addClass(results, "ghic-hidden");
$(".ghic-count", item).textContent = count ? `(${count} hidden)` : " ";
} else if (!init) {
// no need to remove classes on initialization
removeClass(results, "ghic-hidden");
}
toggleClass(item, "ghic-has-content", results.length);
}
}
}
function hideReactions({ obj, item }) {
toggleClass($("body"), "ghic-hide-reactions", obj.isHidden);
toggleClass(item, "ghic-has-content", $$(".has-reactions").length > 0);
// make first comment reactions visible
const origPost = $(".TimelineItem .comment-reactions");
if (origPost && origPost.classList.contains("has-reactions")) {
origPost.style.display = "block";
}
}
function hidePlus1({ item, init }) {
let max,
indx = 0,
count = 0,
total = 0;
// keep a list of post authors to prevent duplicate +1 counts
const 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... add unicode for thumbs up; it gets replaced with an image in Windows
const regexPlus = /([?!*,.:^[\]()\'\"+-\d]|bump|thumbs|up|\ud83d\udc4d)/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"
const regexHide = new RegExp("(" + [
"@\\w+",
"\\b(it|is|a|so|the|and|no|on|oh|do|this|any|very|much|here|just|my|me|too|want|yet|image)\\b",
"pretty",
"pl+e+a+s+e+",
"plz",
"totally",
"y+e+s+",
"eta",
"fix",
"right",
"now",
"hope(ful)?",
"still",
"wait(ed|ing)?",
"nothing",
"really",
"add(ed|ing)?",
"need(ed|ing)?",
"updat(es|ed|ing)?",
"(months|years)\\slater",
"back",
"features?",
"infinity", // +Infinity
"useful",
"super",
"helpful",
"thanks",
"for\\sgod'?s\\ssake",
"c['emon]+" // c'mon, com'on, comeon
].join("|") + ")", "gi");
// image title ":{anything}:", etc.
const regexEmoji = /(:.*:)|[\u{1f300}-\u{1f64f}\u{1f680}-\u{1f6ff}\u{1f900}-\u{1f9ff}]/gu;
const regexWhitespace = /\s+/g;
const comments = $$(".js-discussion .TimelineItem").filter(comment => {
const classes = comment.className.split(" ");
return !exceptions.some(ex => classes.includes(ex));
});
const len = comments.length;
const loop = () => {
let wrapper, el, tmp, txt, img, hasLink, dupe;
max = 0;
while (max < 20 && indx < len) {
if (indx >= len) {
if (init) {
item.classList.toggle("ghic-has-content", count > 0);
}
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;
}
// .js-comments-holder wraps review comments
el = $(".comment-body, .js-comments-holder", wrapper);
if (el) {
// 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 ? el.textContent || "" : "").trim();
}
if (!txt) {
img = $("img", el);
if (img) {
txt = img.getAttribute("title") || img.getAttribute("alt");
}
}
// remove fluff
txt = (txt || "")
.replace(regexEmoji, "")
.replace(regexHide, "")
.replace(regexPlus, "")
.replace(regexWhitespace, " ")
.trim();
if (txt === "" || (txt.length <= 4 && !hasLink)) {
if (init && !settings.plus1.isHidden) {
// +1 Comments has-content
item.classList.toggle("ghic-has-content", true);
return;
}
if (settings.plus1.isHidden) {
wrapper.classList.add("ghic-hidden", "ghic-highlight");
total++;
// one +1 per author
if (!dupe) {
count++;
}
} else if (!init) {
wrapper.classList.remove("ghic-hidden");
}
max++;
}
}
indx++;
}
if (indx < len) {
setTimeout(() => {
window.requestAnimationFrame(loop);
}, 200);
} else {
if (init) {
item.classList.toggle("ghic-has-content", count > 0);
}
$(".ghic-menu .ghic-plus1 .ghic-count").textContent = total
? "(" + total + " hidden)"
: " ";
addCountToReaction(count);
}
};
loop();
}
function getAllText(els) {
let txt = "";
let indx = els.length;
// text order doesn't matter
while (indx--) {
txt += els[indx].textContent.trim();
}
return txt;
}
function addCountToReaction(count) {
if (!count) {
count = ($(".ghic-menu .ghic-plus1 .ghic-count").textContent || "")
.replace(/[()]/g, "")
.trim();
}
const origPost = $(".timeline-comment");
const hasPositiveReaction = $(
".has-reactions button[value='THUMBS_UP react'], .has-reactions button[value='THUMBS_UP unreact']",
origPost
);
let el = $(".ghic-count", origPost);
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 (hasPositiveReaction) {
el = document.createElement("span");
el.className = "ghic-count";
el.textContent = count ? " + " + count + " (from hidden comments)" : "";
hasPositiveReaction.appendChild(el);
} else {
el = document.createElement("p");
el.className = "ghic-count";
el.innerHTML = "
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: