// ==UserScript==
// @name GitHub Issue Counts
// @version 3.0.20
// @description A userscript that adds a repo issues count to the repository tab & organization page (https://github.com/:user)
// @license MIT
// @author Rob Garrison
// @namespace https://github.com/Mottie
// @match https://github.com/*
// @run-at document-idle
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @connect api.github.com
// @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-counts.user.js
// @downloadURL https://raw.githubusercontent.com/Mottie/Github-userscripts/master/github-issue-counts.user.js
// @supportURL https://github.com/Mottie/GitHub-userscripts/issues
// ==/UserScript==
(() => {
"use strict";
// issue count = get all repos from user => api v3
// https://api.github.com/users/:user/repos
// then look for "open_issues_count" in the named repos
const api = "https://api.github.com/users",
// bug icon
icon = ``,
repoSelectors = `
#user-repositories-list li,
#org-repositories li,
ol.pinned-repos-list li
`;
// add bug image styling
GM_addStyle(`
.repo-list-stats a.issues svg {
position: relative;
top: 2px;
fill: #888;
}
.repo-list-stats a.issues:hover svg {
fill: #4078C0;
}
`);
/*
* Org repos
* container = div#org-repositories > div > div.org-repos.repo-list > li
* User repos
* container = div#user-repositories-list > div.js-repo-list > li
* Common org/user
* repo url = container h3 a (first a)
* issue link location = container div[3] a[last]:after
* fork link HTML - both user/org (Dec 2016)
*
* :fork-count
*
*
* Pinned repos
* container = ol.pinned-repos-list li.pinned-repo-item
* repo url = container span span a (first a)
* issue link location = container > span.pinned-repo-item-content p[last] a[last]:after
* fork link HTML
*
* :fork-count
*
*/
function addLinks(data, repos) {
repos.forEach(repo => {
let wrapper, el, html, setClass;
const url = ($("a", repo).getAttribute("href") || "").slice(1),
result = url && data.find(item => {
return item.full_name === url;
});
// pinned
if (repo.classList.contains("pinned-repo-item")) {
el = $$(".pinned-repo-item-content p:last-child", repo);
setClass = "muted-link ghic2-issue-link";
} else {
// user/org list = last div in repo list contains links
wrapper = $$("div", repo);
el = wrapper && $$("a", wrapper[wrapper.length - 1]);
setClass = "muted-link tooltipped tooltipped-s mr-3 ghic2-issue-link";
}
if (el) {
if (result && typeof result.open_issues_count === "number") {
html = `
${icon} ${result.open_issues_count}
`;
// target the last "a"
el = el[el.length - 1];
// add after last link, sometimes there is no fork
if (el) {
if (el.tagName === "P") {
wrapper = document.createElement("span");
wrapper.className = "ghic-link pinned-repo-meta";
el.appendChild(wrapper);
el.querySelector(".ghic-link").innerHTML = html;
} else {
el.insertAdjacentHTML("afterend", html);
}
}
}
}
});
}
function addIssues() {
let user, url,
repos = $$(repoSelectors);
if (
// look for user overview, user repositories & organization repo page
repos.length &&
// and not already applied
!$$(".ghic2-issue-link").length
) {
// no issue count for non-public & forks
repos = repos.filter(repo => {
let list = repo.classList;
return list.contains("public") && !list.contains("fork");
});
if (repos.length) {
url = $("a", repos[ 0 ]).getAttribute("href");
user = (url || "").match(/^\/[^/]+/);
if (user && user.length) {
GM_xmlhttpRequest({
method : "GET",
url : api + user[0] + "/repos",
onload : response => {
const data = JSON.parse(response.responseText || "null");
if (data) {
addLinks(data, repos);
}
}
});
}
}
}
}
function $(str, el) {
return (el || document).querySelector(str);
}
function $$(str, el) {
return Array.from((el || document).querySelectorAll(str));
}
document.addEventListener("ghmo:container", addIssues);
addIssues();
})();