// ==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(); })();