// ==UserScript==
// @name GitHub Show Repo Issues
// @version 3.0.0
// @description A userscript that adds a repo issues count to the repository tab & organization page (https://github.com/:user)
// @license https://creativecommons.org/licenses/by-sa/4.0/
// @namespace http://github.com/Mottie
// @include https://github.com/*
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @connect api.github.com
// @run-at document-idle
// @author Rob Garrison
// @downloadURL none
// ==/UserScript==
/* global GM_addStyle, GM_xmlhttpRequest */
/* jshint esnext:true, unused:true */
(() => {
"use strict";
let busy = false;
// 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, #org-repositories, ol.pinned-repos-list";
// 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 a", repo);
setClass = "pinned-repo-meta muted-link ghic2-issue-link";
} else {
// user/org list = third div in repo list contains links
wrapper = $$("div", repo)[3];
el = wrapper && $$("a", wrapper);
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) {
el.insertAdjacentHTML("afterend", html);
}
}
}
});
busy = false;
}
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
) {
busy = true;
// no issue count for non-public & forks
repos = $$("li", repos[0]).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 : function(response) {
const data = JSON.parse(response.responseText || "null");
if (data) {
addLinks(data, repos);
}
}
});
}
} else {
busy = false;
}
} else {
busy = false;
}
}
function $(str, el) {
return (el || document).querySelector(str);
}
function $$(str, el) {
return Array.from((el || document).querySelectorAll(str));
}
Array.from(
document.querySelectorAll(
"#js-repo-pjax-container, #js-pjax-container, .js-contribution-activity"
)
).forEach(
target => {
new MutationObserver(mutations => {
mutations.forEach(mutation => {
// preform checks before adding code wrap to minimize function calls
if (!busy && mutation.target === target) {
addIssues();
}
});
}).observe(target, {
childList: true,
subtree: true
});
});
addIssues();
})();