Github anchor enhance

Enhance all github link with badges

目前为 2019-10-18 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @namespace https://github.com/NateScarlet/Scripts/tree/master/user-script
  3. // @name Github anchor enhance
  4. // @description Enhance all github link with badges
  5. // @version 16
  6. // @grant GM.xmlHttpRequest
  7. // @run-at document-end
  8. // @include *
  9. // ==/UserScript==
  10. const reservedUsername = [
  11. "topics",
  12. "search",
  13. "ghost",
  14. "pulls",
  15. "issues",
  16. "marketplace",
  17. "explore",
  18. "discover",
  19. "notifications",
  20. "new",
  21. "organizations",
  22. "settings",
  23. "site",
  24. "about",
  25. "contact",
  26. "pricing",
  27. "apps",
  28. "features",
  29. "password_reset",
  30. ];
  31. const allBadgeClasses = [
  32. "added-stars-badge",
  33. "added-last-commit-badge",
  34. "added-followers-badge",
  35. ];
  36. class URLParseResult {
  37. constructor({ user, repo }) {
  38. this.user = user;
  39. this.repo = repo;
  40. }
  41. equals(other) {
  42. return other.repo === this.repo && other.user === this.user;
  43. }
  44. }
  45. URLParseResult.EMPTY = new URLParseResult({});
  46. function parseURL(v) {
  47. const match = v.match(/^https?:\/\/github.com\/([^/]*?)(?:\/([^/]*?))?(?:\.git)?(?:[#?].*)?$/);
  48. if (!match) {
  49. return URLParseResult.EMPTY;
  50. }
  51. if (reservedUsername.includes(match[1])) {
  52. return URLParseResult.EMPTY;
  53. }
  54. return new URLParseResult({
  55. user: match[1],
  56. repo: match[2],
  57. });
  58. }
  59. async function appendBadge(el, className, url) {
  60. if (el.classList.contains(className)) {
  61. return;
  62. }
  63. return new Promise((resolve, reject) => {
  64. GM.xmlHttpRequest({
  65. method: "GET",
  66. url: url,
  67. onload: resp => {
  68. if (resp.status === 200) {
  69. if (!el.classList.contains(className)) {
  70. const img = document.createElement("img");
  71. img.src = `data:image/svg+xml;base64,${btoa(resp.response)}`;
  72. const containerClassNames = [
  73. "natescarlet-gmail-com",
  74. "badge-container",
  75. ];
  76. const selector = containerClassNames.map(i => "." + i).join("");
  77. /** @type {HTMLElement} */
  78. const container = el.querySelector(selector) || document.createElement("span");
  79. el.appendChild(container);
  80. container.classList.add(...containerClassNames);
  81. container.append(img);
  82. img.style.order = allBadgeClasses.indexOf(className).toString();
  83. container.style.display = "inline-flex";
  84. el.classList.add(className);
  85. }
  86. resolve();
  87. }
  88. reject(`${resp.status}: ${url}`);
  89. },
  90. onerror: reject,
  91. });
  92. });
  93. }
  94. async function appendStarsBadge(el) {
  95. const { repo, user } = parseURL(el.href);
  96. if (!(user && repo)) {
  97. return;
  98. }
  99. await appendBadge(el, "added-stars-badge", `https://img.shields.io/github/stars/${user}/${repo}.svg?style=social`);
  100. }
  101. async function appendLastCommitBadge(el) {
  102. const { repo, user } = parseURL(el.href);
  103. if (!(user && repo)) {
  104. return;
  105. }
  106. await appendBadge(el, "added-last-commit-badge", `https://img.shields.io/github/last-commit/${user}/${repo}.svg`);
  107. }
  108. async function appendFollowersBadge(el) {
  109. const { user } = parseURL(el.href);
  110. if (!user) {
  111. return;
  112. }
  113. await appendBadge(el, "added-followers-badge", `https://img.shields.io/github/followers/${user}.svg?style=social`);
  114. }
  115. (async function () {
  116. document.addEventListener("mouseover", async (e) => {
  117. if (e.target instanceof HTMLAnchorElement) {
  118. const el = e.target;
  119. if (parseURL(location.href).equals(parseURL(el.href))) {
  120. // Skip self link
  121. return;
  122. }
  123. try {
  124. await Promise.all([
  125. appendStarsBadge(el),
  126. appendLastCommitBadge(el),
  127. appendFollowersBadge(el),
  128. ]);
  129. }
  130. catch (err) {
  131. console.error(err);
  132. }
  133. }
  134. }, {});
  135. })();