GitHub Show Repo Issues

A userscript that adds a repo issues count to the repository tab & organization page (https://github.com/:user)

当前为 2016-12-19 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name GitHub Show Repo Issues
  3. // @version 3.0.0
  4. // @description A userscript that adds a repo issues count to the repository tab & organization page (https://github.com/:user)
  5. // @license https://creativecommons.org/licenses/by-sa/4.0/
  6. // @namespace http://github.com/Mottie
  7. // @include https://github.com/*
  8. // @grant GM_addStyle
  9. // @grant GM_xmlhttpRequest
  10. // @connect api.github.com
  11. // @run-at document-idle
  12. // @author Rob Garrison
  13. // ==/UserScript==
  14. /* global GM_addStyle, GM_xmlhttpRequest */
  15. /* jshint esnext:true, unused:true */
  16. (() => {
  17. "use strict";
  18. let busy = false;
  19.  
  20. // issue count = get all repos from user => api v3
  21. // https://api.github.com/users/:user/repos
  22. // then look for "open_issues_count" in the named repos
  23. const api = "https://api.github.com/users",
  24. // bug icon
  25. icon = `<svg class="octicon octicon-repo-issues" xmlns="http://www.w3.org/2000/svg" width="12" height="16" viewBox="0 0 53 53">
  26. <path d="m39.2 0c2 0 3.7 1.5 3.7 3.4 0 1.9-1.7 3.4-3.7 3.4l-0.9-0.1-3.4 4c2.3 1.6 4.4 3.8 6 6.4-4 1.2-8.9 1.9-14.2 1.9-5.3 0-10.1-0.7-14.2-1.9 1.5-2.6 3.5-4.7 5.7-6.3l-3.5-4.1c-0.2 0-0.4 0.1-0.6 0.1-2 0-3.7-1.5-3.7-3.4 0-1.9 1.7-3.4 3.7-3.4 2 0 3.7 1.5 3.7 3.4 0 0.7-0.2 1.3-0.6 1.8l3.5 4.2c1.8-0.8 3.8-1.3 5.9-1.3 2 0 3.9 0.4 5.6 1.2l3.7-4.3c-0.3-0.5-0.4-1-0.4-1.6 0-1.9 1.6-3.4 3.7-3.4zm11.8 28.5c1.2 0 2.2 0.9 2.2 2 0 1.1-1 2-2.2 2l-6.7 0c-0.1 1.5-0.3 2.9-0.6 4.3l7.8 3.4c1.1 0.5 1.6 1.7 1.1 2.7-0.5 1-1.8 1.5-2.9 1l-7.2-3.1c-2.7 6.7-8 11.4-14.3 12.1l0-31.2c5.2-0.1 10-1 13.9-2.3l0.3 0.7 7.5-2.7c1.1-0.4 2.4 0.1 2.9 1.2 0.4 1.1-0.1 2.2-1.3 2.6l-7.9 2.8c0.3 1.4 0.6 2.9 0.7 4.5l6.7 0 0 0zm-48.7 0 6.7 0c0.1-1.5 0.3-3.1 0.7-4.5l-7.9-2.8c-1.1-0.4-1.7-1.6-1.3-2.6 0.4-1 1.7-1.6 2.9-1.2l7.5 2.7 0.3-0.7c3.9 1.3 8.7 2.1 13.9 2.3l0 31.2c-6.2-0.7-11.5-5.4-14.3-12.1l-7.2 3.1c-1.1 0.5-2.4 0-2.9-1-0.5-1 0-2.2 1.1-2.7l7.8-3.4c-0.3-1.4-0.5-2.8-0.6-4.3l-6.7 0c-1.2 0-2.2-0.9-2.2-2 0-1.1 1-2 2.2-2z" />
  27. </svg>`,
  28.  
  29. repoSelectors = "#user-repositories-list, #org-repositories, ol.pinned-repos-list";
  30.  
  31. // add bug image styling
  32. GM_addStyle(`
  33. .repo-list-stats a.issues svg {
  34. position: relative;
  35. top: 2px;
  36. fill: #888;
  37. }
  38. .repo-list-stats a.issues:hover svg {
  39. fill: #4078C0;
  40. }
  41. `);
  42.  
  43. /*
  44. * Org repos
  45. * container = div#org-repositories > div > div.org-repos.repo-list > li
  46. * User repos
  47. * container = div#user-repositories-list > div.js-repo-list > li
  48. * Common org/user
  49. * repo url = container h3 a (first a)
  50. * issue link location = container div[3] a[last]:after
  51. * fork link HTML - both user/org (Dec 2016)
  52. * <a class="muted-link tooltipped tooltipped-s mr-3" href="/:user/:repo/network" aria-label="Forks">
  53. * <svg class="octicon octicon-repo-forked">...</svg> :fork-count
  54. * </a>
  55. *
  56. * Pinned repos
  57. * container = ol.pinned-repos-list li.pinned-repo-item
  58. * repo url = container span span a (first a)
  59. * issue link location = container > span.pinned-repo-item-content p[last] a[last]:after
  60. * fork link HTML
  61. * <a href="/:user/:repo/network" class="pinned-repo-meta muted-link">
  62. * <svg aria-label="forks" class="octicon octicon-repo-forked">...</svg> :fork-count
  63. * </a>
  64. */
  65. function addLinks(data, repos) {
  66. repos.forEach(repo => {
  67. let wrapper, el, html, setClass;
  68. const url = ($("a", repo).getAttribute("href") || "").slice(1),
  69. result = url && data.find(item => {
  70. return item.full_name === url;
  71. });
  72. // pinned
  73. if (repo.classList.contains("pinned-repo-item")) {
  74. el = $$(".pinned-repo-item-content a", repo);
  75. setClass = "pinned-repo-meta muted-link ghic2-issue-link";
  76. } else {
  77. // user/org list = third div in repo list contains links
  78. wrapper = $$("div", repo)[3];
  79. el = wrapper && $$("a", wrapper);
  80. setClass = "muted-link tooltipped tooltipped-s mr-3 ghic2-issue-link";
  81. }
  82. if (el) {
  83. if (result && typeof result.open_issues_count === "number") {
  84. html = `<a class="${setClass}" href="${url}/issues" aria-label="Issues">
  85. ${icon} ${result.open_issues_count}
  86. </a>`;
  87. // target the last "a"
  88. el = el[el.length - 1];
  89. // add after last link, sometimes there is no fork
  90. if (el) {
  91. el.insertAdjacentHTML("afterend", html);
  92. }
  93. }
  94. }
  95. });
  96. busy = false;
  97. }
  98.  
  99. function addIssues() {
  100. let user, url,
  101. repos = $$(repoSelectors);
  102. if (
  103. // look for user overview, user repositories & organization repo page
  104. repos.length &&
  105. // and not already applied
  106. !$$(".ghic2-issue-link").length
  107. ) {
  108. busy = true;
  109. // no issue count for non-public & forks
  110. repos = $$("li", repos[0]).filter(repo => {
  111. let list = repo.classList;
  112. return list.contains("public") && !list.contains("fork");
  113. });
  114. if (repos.length) {
  115. url = $("a", repos[ 0 ]).getAttribute("href");
  116. user = (url || "").match(/^\/[^/]+/);
  117.  
  118. if (user && user.length) {
  119. GM_xmlhttpRequest({
  120. method : "GET",
  121. url : api + user[0] + "/repos",
  122. onload : function(response) {
  123. const data = JSON.parse(response.responseText || "null");
  124. if (data) {
  125. addLinks(data, repos);
  126. }
  127. }
  128. });
  129. }
  130. } else {
  131. busy = false;
  132. }
  133. } else {
  134. busy = false;
  135. }
  136. }
  137.  
  138. function $(str, el) {
  139. return (el || document).querySelector(str);
  140. }
  141.  
  142. function $$(str, el) {
  143. return Array.from((el || document).querySelectorAll(str));
  144. }
  145.  
  146. Array.from(
  147. document.querySelectorAll(
  148. "#js-repo-pjax-container, #js-pjax-container, .js-contribution-activity"
  149. )
  150. ).forEach(
  151. target => {
  152. new MutationObserver(mutations => {
  153. mutations.forEach(mutation => {
  154. // preform checks before adding code wrap to minimize function calls
  155. if (!busy && mutation.target === target) {
  156. addIssues();
  157. }
  158. });
  159. }).observe(target, {
  160. childList: true,
  161. subtree: true
  162. });
  163. });
  164.  
  165. addIssues();
  166.  
  167. })();