您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add a link to a GitHub repo's first commit
当前为
// ==UserScript== // @name GitHub First Commit // @description Add a link to a GitHub repo's first commit // @author chocolateboy // @copyright chocolateboy // @version 3.0.0 // @namespace https://github.com/chocolateboy/userscripts // @license GPL // @include https://github.com/ // @include https://github.com/* // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/cash.min.js // @grant GM_log // @noframes // @run-at document-start // ==/UserScript== // NOTE This file is generated from src/github-first-commit.user.ts and should not be edited directly. "use strict"; (() => { // src/lib/util.ts var pipe = (value, fn) => fn(value); // src/github-first-commit/util.ts function openFirstCommit(user, repo) { return fetch(`https://api.github.com/repos/${user}/${repo}/commits`).then((res) => Promise.all([res.headers.get("link"), res.json()])).then(([link, commits]) => { if (!link) { return commits; } const lastPage = link.match(/^.+?<([^>]+)>;/)[1]; return fetch(lastPage).then((res) => res.json()); }).then((commits) => { if (Array.isArray(commits)) { location.href = commits[commits.length - 1].html_url; } else { console.error(commits); } }); } // src/github-first-commit/first-commit.ts var DEFAULT_TIMEOUT = 1e3; var getCommitHistoryButton = (root) => { return root.querySelector("svg.octicon.octicon-history")?.closest("a:not(.react-last-commit-history-icon)") || null; }; var FirstCommit = class { constructor(state, options = {}) { this.state = state; this.timeout = options.timeout || DEFAULT_TIMEOUT; } isLoggedIn = false; timeout; append($target, $firstCommit) { const $targetLi = $target.parent("li"); const $firstCommitLi = $($targetLi[0].cloneNode(false)).empty().append($firstCommit); $targetLi.after($firstCommitLi); } /* * add the "1st Commit" button after the commit-history ("123 Commits") button */ attach(target) { console.log("inside attach:", target); const $target = $(target); const $firstCommit = $target.clone().removeAttr("href data-pjax data-turbo-frame").removeClass("react-last-commit-history-group").attr({ "aria-label": "First commit", "id": "first-commit" }).css("cursor", "pointer"); const $label = this.findLabel($firstCommit); $label.text("1st Commit"); const [user, repo] = $('meta[name="octolytics-dimension-repository_network_root_nwo"][content]').attr("content").split("/"); $firstCommit.one("click", () => { $label.text("Loading..."); openFirstCommit(user, repo); return false; }); console.log("attaching first-commit button:", $firstCommit[0]); this.append($target, $firstCommit); } findLabel($firstCommit) { const $label = $firstCommit.find(":scope span > strong").first(); $label.nextAll().remove(); return $label; } getRoot() { return document.getElementById("js-repo-pjax-container"); } handleFirstCommitButton(firstCommit) { console.debug("removing obsolete first-commit button"); firstCommit.remove(); return true; } // in most cases, the "turbo:load" event signals that the (SPA) page has // finished loading and is ready to be queried and updated (i.e. the SPA // equivalent of DOMContentLoaded), but that's not the case for the // commit-history button, which can either be: // // a) already loaded (full page load) // b) not there yet (still loading) // c) already loaded or still loading, but invalid // // b) and c) can occur when navigating to a repo page via the back button or via // on-site links, including self-links (i.e. from a repo page to itself). // // in the c) case, the old button is displayed (with the old first-commit button // still attached) before being replaced by the final, updated version, unless // the user is not logged in, in which case the old first-commit button is not // replaced. // // this method handles all 3 cases onLoad(_event) { const state = this.state; const root = this.getRoot(); if (!root) { console.warn("can't find root element!"); return; } let timerHandle = 0; let disconnected = false; const disconnect = () => { if (disconnected) { return; } disconnected = true; observer.disconnect(); if (timerHandle) { pipe(timerHandle, ($timerHandle) => { timerHandle = 0; clearTimeout($timerHandle); }); } }; const timeout = () => { console.warn(`timed out after ${this.timeout}ms`); disconnect(); }; const callback = (mutations) => { console.debug("inside mutation callback:", mutations); if (!root.isConnected) { console.warn("root is not connected:", root); disconnect(); return; } if (generation !== state.generation) { console.warn("obsolete page:", { generation, state }); disconnect(); return; } const firstCommit = document.getElementById("first-commit"); if (firstCommit) { console.debug("obsolete button:", firstCommit); const handled = this.handleFirstCommitButton(firstCommit); if (!handled) { return; } } const commitHistoryButton = getCommitHistoryButton(root); if (commitHistoryButton) { console.debug("found commit-history button"); disconnect(); queueMicrotask(() => this.attach(commitHistoryButton)); } }; const generation = state.generation; const observer = new MutationObserver(callback); callback([], observer); if (!disconnected) { timerHandle = setTimeout(timeout, this.timeout); observer.observe(root, { childList: true, subtree: true }); } } }; // src/github-first-commit/first-commit-logged-in.ts var FirstCommitLoggedIn = class extends FirstCommit { isLoggedIn = true; append($target, $firstCommit) { $target.after($firstCommit); } findLabel($firstCommit) { return $firstCommit.find(':scope [data-component="text"] > span').first(); } getRoot() { return document.querySelector('[partial-name="repos-overview"]') || super.getRoot(); } handleFirstCommitButton(_firstCommit) { return false; } }; // src/github-first-commit.user.ts // @license GPL var REPO_PATH = /^\/[^\/]+\/[^\/]+$/; var TIMEOUT = 1e3; var USER_LOGIN = 'meta[name="user-login"][content]:not([content=""])'; var main = () => { const state = { generation: 0 }; $(document).on("turbo:load", (event) => { ++state.generation; console.log("inside turbo:load", { ...state, event }); if (!REPO_PATH.test(location.pathname)) { console.log("skipping invalid path"); return; } const isLoggedIn = document.querySelector(USER_LOGIN); const handler = isLoggedIn ? new FirstCommitLoggedIn(state, { timeout: TIMEOUT }) : new FirstCommit(state, { timeout: TIMEOUT }); handler.onLoad(event); }); }; console.debug("inside:", GM_info.script.name); main(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址