GitHub Network Shortcut

Adds a shortcut to the network graph on the repository's main page

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         GitHub Network Shortcut
// @namespace    https://mkps.app/
// @version      0.1.2
// @description  Adds a shortcut to the network graph on the repository's main page
// @tag          productivity
// @author       MK
// @license      MIT
// @match        *://github.com/*
// @icon         https://github.com/favicon.ico
// ==/UserScript==

(() => {
	"use strict";

	const ELEMENT_CLASS = "MK_userscript_ghNetworkShortcut_element";
	const CONTAINER_SELECTOR_LG = `#repo-content-turbo-frame .Layout-sidebar .about-margin > div.BorderGrid-row > div.BorderGrid-cell > div.hide-sm.hide-md`;
	const CONTAINER_SELECTOR_SM = `#responsive-meta-container ul[aria-label="Repository details"]`;

	function main() {
		forMatchingElementsForever(CONTAINER_SELECTOR_LG, (element) => {
			if (element.querySelector(`.${ELEMENT_CLASS}`)) {return;}
			for (const child of element.children) {
				if (child.children[0]?.getAttribute("href")?.endsWith("/forks")) {
					child.insertAdjacentElement("afterend", createShortcutLg());
					break;
				}
			}
		});
		forMatchingElementsForever(CONTAINER_SELECTOR_SM, (element) => {
			if (element.querySelector(`.${ELEMENT_CLASS}`)) {return;}
			for (const child of element.children) {
				if (child.getAttribute("href")?.endsWith("/activity")) {
					child.insertAdjacentElement("afterend", createShortcutSm());
					break;
				}
			}
		});
	}

	function getNetworkUrl() {
		const terms = [...location.pathname.split("/").slice(1, 3), "network"];
		return `/${terms.join("/")}`;
	}

	function createShortcutLg() {
		const div = document.createElement("div");
		div.classList.add(ELEMENT_CLASS);
		div.classList.add("mt-2");
		const a = document.createElement("a");
		a.setAttribute("href", getNetworkUrl());
		a.setAttribute("data-view-component", "true");
		a.classList.add("Link", "Link--muted");
		a.appendChild(createCommitIcon("mr-2"));
		a.appendChild(document.createTextNode(" "));
		const span = document.createElement("span");
		span.classList.add("color-fg-muted");
		span.textContent = "Network";
		a.appendChild(span);
		div.appendChild(a);
		return div;
	}

	function createShortcutSm() {
		const a = document.createElement("a");
		a.classList.add(ELEMENT_CLASS, "Link--secondary", "no-underline", "d-block", "mr-2");
		a.setAttribute("role", "listitem");
		a.setAttribute("href", getNetworkUrl());
		a.appendChild(createCommitIcon("mr-1"));
		a.appendChild(document.createTextNode(" "));
		const span = document.createElement("span");
		span.textContent = "Network";
		a.appendChild(span);
		return a;
	}

	function createCommitIcon(marginClass) {
		const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
		svg.setAttribute("version", "1.1");
		svg.setAttribute("width", "16");
		svg.setAttribute("height", "16");
		svg.setAttribute("viewBox", "0 0 16 16");
		svg.setAttribute("aria-hidden", "true");
		svg.setAttribute("data-view-component", "true");
		svg.classList.add("octicon", "octicon-commit", marginClass);
		svg.innerHTML = `<path d="M11.93 8.5a4.002 4.002 0 0 1-7.86 0H.75a.75.75 0 0 1 0-1.5h3.32a4.002 4.002 0 0 1 7.86 0h3.32a.75.75 0 0 1 0 1.5Zm-1.43-.75a2.5 2.5 0 1 0-5 0 2.5 2.5 0 0 0 5 0Z"></path>`;
		return svg;
	}

	function forMatchingElementsForever(selector, fn) {
		const observer = new MutationObserver((list, observer) => {
			for (const mutation of list) {
				for (const addedNode of mutation.addedNodes) {
					if (addedNode.nodeType !== Node.ELEMENT_NODE) {continue;}
					for (const newNode of addedNode.querySelectorAll(selector)) {
						fn(newNode);
					}
				}
			}
		});
		observer.observe(document.body, {childList: true, subtree: true});

		for (const element of document.querySelectorAll(selector)) {
			fn(element);
		}
	}

	main();
})();