GitHub First Commit

Add a link to a GitHub repo's first commit

目前为 2023-12-21 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name GitHub First Commit
  3. // @description Add a link to a GitHub repo's first commit
  4. // @author chocolateboy
  5. // @copyright chocolateboy
  6. // @version 3.1.0
  7. // @namespace https://github.com/chocolateboy/userscripts
  8. // @license GPL
  9. // @include https://github.com/
  10. // @include https://github.com/*
  11. // @require https://cdn.jsdelivr.net/npm/cash-dom@8.1.5/dist/cash.min.js
  12. // @grant GM_log
  13. // @noframes
  14. // @run-at document-start
  15. // ==/UserScript==
  16.  
  17. // NOTE This file is generated from src/github-first-commit.user.ts and should not be edited directly.
  18.  
  19. "use strict";
  20. (() => {
  21. // src/lib/util.ts
  22. var pipe = (value, fn) => fn(value);
  23.  
  24. // src/github-first-commit/util.ts
  25. function openFirstCommit(user, repo) {
  26. return fetch(`https://api.github.com/repos/${user}/${repo}/commits`).then((res) => Promise.all([res.headers.get("link"), res.json()])).then(([link, commits]) => {
  27. if (!link) {
  28. return commits;
  29. }
  30. const lastPage = link.match(/^.+?<([^>]+)>;/)[1];
  31. return fetch(lastPage).then((res) => res.json());
  32. }).then((commits) => {
  33. if (Array.isArray(commits)) {
  34. location.href = commits[commits.length - 1].html_url;
  35. } else {
  36. console.error(commits);
  37. }
  38. });
  39. }
  40.  
  41. // src/github-first-commit/first-commit.ts
  42. var DEFAULT_TIMEOUT = 1e3;
  43. var DUMMY_MUTATIONS = [];
  44. var getCommitHistoryButton = (root) => {
  45. return root.querySelector("svg.octicon.octicon-history")?.closest("a") || null;
  46. };
  47. var FirstCommit = class {
  48. constructor(state, options = {}) {
  49. this.state = state;
  50. this.timeout = options.timeout || DEFAULT_TIMEOUT;
  51. }
  52. timeout;
  53. append($target, $firstCommit) {
  54. const $targetLi = $target.parent("li");
  55. const $firstCommitLi = $($targetLi[0].cloneNode(false)).empty().append($firstCommit);
  56. $targetLi.after($firstCommitLi);
  57. }
  58. /*
  59. * add the "1st Commit" button after the commit-history ("123 Commits")
  60. * button
  61. */
  62. attach(target) {
  63. console.log("inside attach:", target);
  64. const $target = $(target);
  65. const $firstCommit = $target.clone().removeAttr("href data-pjax data-turbo-frame").removeClass("react-last-commit-history-group").attr({
  66. "aria-label": "First commit",
  67. "id": "first-commit"
  68. }).css("cursor", "pointer");
  69. const $label = this.findLabel($firstCommit);
  70. $label.text("1st Commit");
  71. const [user, repo] = $('meta[name="octolytics-dimension-repository_network_root_nwo"][content]').attr("content").split("/");
  72. $firstCommit.one("click", () => {
  73. $label.text("Loading...");
  74. openFirstCommit(user, repo);
  75. return false;
  76. });
  77. console.log("attaching first-commit button:", $firstCommit[0]);
  78. this.append($target, $firstCommit);
  79. }
  80. findLabel($firstCommit) {
  81. const $label = $firstCommit.find(":scope span > strong").first();
  82. $label.nextAll().remove();
  83. return $label;
  84. }
  85. getRoot() {
  86. return document.getElementById("js-repo-pjax-container");
  87. }
  88. handleFirstCommitButton(firstCommit) {
  89. console.debug("removing obsolete first-commit button");
  90. firstCommit.remove();
  91. return true;
  92. }
  93. // in most cases, the "turbo:load" event signals that the (SPA) page has
  94. // finished loading and is ready to be queried and updated (i.e. the SPA
  95. // equivalent of DOMContentLoaded), but that's not the case for the
  96. // commit-history button, which can either be:
  97. //
  98. // a) already loaded (full page load)
  99. // b) not there yet (still loading)
  100. // c) already loaded or still loading, but invalid
  101. //
  102. // b) and c) can occur when navigating to a repo page via the back button or
  103. // via on-site links, including self-links (i.e. from a repo page to
  104. // itself).
  105. //
  106. // in the c) case, the "old" [1] button is displayed (with the old
  107. // first-commit button still attached) before being replaced by the
  108. // refreshed version, unless the user is not logged in, in which case the
  109. // old first-commit button is not replaced.
  110. //
  111. // this method handles all 3 cases
  112. //
  113. // [1] actually restored (i.e. new) versions of these cached elements, but
  114. // the behavior is the same
  115. onLoad(_event) {
  116. const state = this.state;
  117. const root = this.getRoot();
  118. if (!root) {
  119. console.warn("can't find root element!");
  120. return;
  121. }
  122. let timerHandle = 0;
  123. let disconnected = false;
  124. const disconnect = () => {
  125. if (disconnected) {
  126. return;
  127. }
  128. disconnected = true;
  129. observer.disconnect();
  130. if (timerHandle) {
  131. pipe(timerHandle, ($timerHandle) => {
  132. timerHandle = 0;
  133. clearTimeout($timerHandle);
  134. });
  135. }
  136. };
  137. const timeout = () => {
  138. console.warn(`timed out after ${this.timeout}ms`);
  139. disconnect();
  140. };
  141. const callback = (mutations) => {
  142. if (mutations !== DUMMY_MUTATIONS) {
  143. console.debug("inside mutation callback:", mutations);
  144. }
  145. if (!root.isConnected) {
  146. console.warn("root is not connected:", root);
  147. disconnect();
  148. return;
  149. }
  150. if (generation !== state.generation) {
  151. console.warn("obsolete page:", { generation, state });
  152. disconnect();
  153. return;
  154. }
  155. const firstCommit = document.getElementById("first-commit");
  156. if (firstCommit) {
  157. console.debug("obsolete button:", firstCommit);
  158. const handled = this.handleFirstCommitButton(firstCommit);
  159. if (!handled) {
  160. return;
  161. }
  162. }
  163. const commitHistoryButton = getCommitHistoryButton(root);
  164. if (commitHistoryButton) {
  165. console.debug("found commit-history button");
  166. disconnect();
  167. queueMicrotask(() => this.attach(commitHistoryButton));
  168. }
  169. };
  170. const generation = state.generation;
  171. const observer = new MutationObserver(callback);
  172. callback(DUMMY_MUTATIONS, observer);
  173. if (!disconnected) {
  174. console.debug("starting mutation observer");
  175. timerHandle = setTimeout(timeout, this.timeout);
  176. observer.observe(root, { childList: true, subtree: true });
  177. }
  178. }
  179. };
  180.  
  181. // src/github-first-commit/first-commit-logged-in.ts
  182. var FirstCommitLoggedIn = class extends FirstCommit {
  183. append($target, $firstCommit) {
  184. $target.after($firstCommit);
  185. }
  186. findLabel($firstCommit) {
  187. return $firstCommit.find(':scope [data-component="text"] > span').first();
  188. }
  189. getRoot() {
  190. return document.querySelector('[partial-name="repos-overview"]') || super.getRoot();
  191. }
  192. handleFirstCommitButton(_firstCommit) {
  193. return false;
  194. }
  195. };
  196.  
  197. // src/github-first-commit.user.ts
  198. // @license GPL
  199. var LOCATION = 'meta[name="analytics-location"][content]';
  200. var TIMEOUT = 1e3;
  201. var USER_LOGIN = 'meta[name="user-login"][content]:not([content=""])';
  202. var main = () => {
  203. const state = { generation: 0 };
  204. const anonHandler = new FirstCommit(state, { timeout: TIMEOUT });
  205. const loggedInHandler = new FirstCommitLoggedIn(state, { timeout: TIMEOUT });
  206. $(window).on("turbo:load", (event) => {
  207. ++state.generation;
  208. const path = document.querySelector(LOCATION)?.content;
  209. const isRepoPage = path === "/<user-name>/<repo-name>";
  210. console.log("inside turbo:load", {
  211. path,
  212. repo: isRepoPage,
  213. ...state,
  214. event
  215. });
  216. if (!isRepoPage) {
  217. console.log("skipping: non-repo page");
  218. return;
  219. }
  220. const isLoggedIn = document.querySelector(USER_LOGIN);
  221. const handler = isLoggedIn ? loggedInHandler : anonHandler;
  222. handler.onLoad(event);
  223. });
  224. };
  225. console.debug("inside:", GM_info.script.name);
  226. main();
  227. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址