// ==UserScript== // @name GitHub Show Repo Issues // @version 2.3.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 // @run-at document-idle // @author Rob Garrison // @downloadURL none // ==/UserScript== /* global GM_addStyle, GM_xmlhttpRequest */ (function() { "use strict"; var busy = false, addIssues = function() { // look for repo tab or user/organization page if (document.querySelectorAll(".tabnav-tab.selected, .repo-list").length && // and not already applied !document.querySelectorAll(".repo-list-stat-item.issues").length) { // set busy flag to ignore mutation observer firing while adding new content busy = true; // Does not include forks & only includes the first 10 repos, or first 20 on the // organization page - these are the repos showing the participation graphs var user, len, url, items = document.querySelectorAll(".repo-list-item"), // bug icon icon = "", // 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 // previsouly used https://api.github.com/repos/:user/:repo/issues?state=open (first 30 issues only) api = "https://api.github.com/users"; items = Array.prototype.filter.call(items, function(item) { var cl = item.classList; return cl.contains("public") && !cl.contains("fork"); }); len = items.length; // expecting fork link to look like this: // // 1 // url = len ? items[ 0 ].querySelector("a.repo-list-stat-item[aria-label='Forks']").getAttribute("href") : ""; user = (url || "").match(/^\/[^/]+/); if (user && user.length) { // add bug image background GM_addStyle([ ".repo-list-stats a.issues svg { position: relative; top: 2px; fill: #888; }", ".repo-list-stats a.issues:hover svg { fill: #4078C0; }" ].join("")); GM_xmlhttpRequest({ method : "GET", url : api + user[ 0 ] + "/repos", onload : function(response) { var itemIndex, repoIndex, repoLen, repo, link, data = JSON.parse(response.responseText || "null"); if (data) { repoLen = data.length; for (itemIndex = 0; itemIndex < len; itemIndex++) { link = items[ itemIndex ].querySelector("a.repo-list-stat-item[aria-label='Forks']"); repo = (link.getAttribute("href") || "").replace("/network", "").slice(1); for (repoIndex = 0; repoIndex < repoLen; repoIndex++) { if (repo === data[ repoIndex ].full_name) { link.insertAdjacentHTML("afterend", "" + icon + " " + data[ repoIndex ].open_issues_count + "" ); } } } } busy = false; } }); } else { busy = false; } } else { busy = false; } }, containers = "#js-repo-pjax-container, #js-pjax-container, .js-contribution-activity", targets = document.querySelectorAll(containers); Array.prototype.forEach.call(targets, function(target) { new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { // preform checks before addIssues to minimize function calls if (!(busy || document.querySelectorAll(".repo-list-stat-item.issues").length) && document.querySelectorAll(".tabnav-tab.selected, .repo-list").length && mutation.target === target) { addIssues(); } }); }).observe(target, { childList: true, subtree: true }); }); addIssues(); })();