|
| 1 | +// ==UserScript== |
| 2 | +// @name Github copy clone command button(fork) |
| 3 | +// @name:zh-CN GitHub 复制克隆命令按钮(fork) |
| 4 | +// @description Add a 1-click copy button with the "git clone --recurse-submodules ..." command, after the Code button on the GitHub repository page |
| 5 | +// @description:zh-CN 在 GitHub 仓库页面的“代码”按钮之后,添加一个使用“git clone --recurse-submodules ...”命令的一键复制按钮。 |
| 6 | +// @author 人民的勤务员 <china.qinwuyuan@gmail.com> |
| 7 | +// @namespace https://github.com/ChinaGodMan/UserScripts |
| 8 | +// @supportURL https://github.com/ChinaGodMan/UserScripts/issues |
| 9 | +// @homepageURL https://github.com/ChinaGodMan/UserScripts |
| 10 | +// @homepage https://github.com/ChinaGodMan/UserScripts |
| 11 | +// @license MIT |
| 12 | +// @match https://github.com/* |
| 13 | +// @icon https://raw.githubusercontent.com/ChinaGodMan/UserScriptsHistory/main/scriptsIcon/github-commit-viewer.png |
| 14 | +// @grant GM_setClipboard |
| 15 | +// @compatible chrome |
| 16 | +// @compatible firefox |
| 17 | +// @compatible edge |
| 18 | +// @compatible opera |
| 19 | +// @compatible safari |
| 20 | +// @compatible kiwi |
| 21 | +// @compatible qq |
| 22 | +// @compatible via |
| 23 | +// @compatible brave |
| 24 | +// @version 2026.3.16.1 |
| 25 | +// @created 2026-03-16 15:12:45 |
| 26 | +// @downloadURL https://raw.githubusercontent.com/ChinaGodMan/UserScripts/main/github-clone/github-clone.user.js |
| 27 | +// @updateURL https://raw.githubusercontent.com/ChinaGodMan/UserScripts/main/github-clone/github-clone.user.js |
| 28 | +// ==/UserScript== |
| 29 | + |
| 30 | +(function () { |
| 31 | + 'use strict' |
| 32 | + |
| 33 | + function getCloneCmd() { |
| 34 | + const match = location.pathname.match(/^\/([^/]+)\/([^/]+)(\/|$)/) |
| 35 | + if (!match) return null |
| 36 | + return `git clone --recurse-submodules https://github.com/${match[1]}/${match[2]}.git` |
| 37 | + } |
| 38 | + |
| 39 | + function copyCloneCmd(cmd) { |
| 40 | + if (!cmd) return |
| 41 | + if (typeof GM_setClipboard === 'function') { |
| 42 | + GM_setClipboard(cmd) |
| 43 | + } else if (navigator.clipboard) { |
| 44 | + navigator.clipboard.writeText(cmd) |
| 45 | + } |
| 46 | + showTip(`Copied: ${cmd}`) |
| 47 | + } |
| 48 | + |
| 49 | + function showTip(msg) { |
| 50 | + const tip = document.createElement('div') |
| 51 | + tip.textContent = msg |
| 52 | + tip.style.position = 'fixed' |
| 53 | + tip.style.top = '20px' |
| 54 | + tip.style.right = '20px' |
| 55 | + tip.style.background = '#28a745' |
| 56 | + tip.style.color = '#fff' |
| 57 | + tip.style.padding = '8px 16px' |
| 58 | + tip.style.borderRadius = '6px' |
| 59 | + tip.style.zIndex = 9999 |
| 60 | + tip.style.fontSize = '16px' |
| 61 | + tip.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15)' |
| 62 | + document.body.appendChild(tip) |
| 63 | + setTimeout(() => tip.remove(), 1800) |
| 64 | + } |
| 65 | + |
| 66 | + function addCopyButton() { |
| 67 | + const cmd = getCloneCmd() |
| 68 | + if (!cmd) return |
| 69 | + const codeBtn = Array.from(document.querySelectorAll('button[class^="prc-Button-ButtonBase-"]')).find( |
| 70 | + btn => ['代码', 'Code'].includes(btn.textContent.trim()) |
| 71 | + ) |
| 72 | + if (!codeBtn) return |
| 73 | + if (document.getElementById('copy-clone-submodules-btn')) return |
| 74 | + |
| 75 | + // SVG字符串多行可读 |
| 76 | + const svg = ` |
| 77 | +<svg aria-hidden="true" focusable="false" class="octicon octicon-copy" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align: text-bottom;"> |
| 78 | + <path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path> |
| 79 | + <path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path> |
| 80 | +</svg> |
| 81 | + `.trim() |
| 82 | + |
| 83 | + // 克隆Code按钮 |
| 84 | + const copyBtn = codeBtn.cloneNode(true) |
| 85 | + copyBtn.id = 'copy-clone-submodules-btn' |
| 86 | + |
| 87 | + // 替换内容为SVG |
| 88 | + const contentSpan = copyBtn.querySelector('[data-component="buttonContent"]') |
| 89 | + if (contentSpan) { |
| 90 | + contentSpan.innerHTML = svg |
| 91 | + } else { |
| 92 | + copyBtn.innerHTML = svg |
| 93 | + } |
| 94 | + |
| 95 | + // 设置title为命令内容 |
| 96 | + copyBtn.title = cmd |
| 97 | + |
| 98 | + // 移除aria-haspopup等下拉相关属性 |
| 99 | + copyBtn.removeAttribute('aria-haspopup') |
| 100 | + copyBtn.removeAttribute('aria-expanded') |
| 101 | + copyBtn.removeAttribute('aria-describedby') |
| 102 | + |
| 103 | + // 绑定复制事件 |
| 104 | + copyBtn.addEventListener('click', function (e) { |
| 105 | + e.preventDefault() |
| 106 | + e.stopPropagation() |
| 107 | + copyCloneCmd(cmd) |
| 108 | + }) |
| 109 | + |
| 110 | + // 插入到Code按钮后 |
| 111 | + codeBtn.parentNode.insertBefore(copyBtn, codeBtn.nextSibling) |
| 112 | + } |
| 113 | + |
| 114 | + // 监听页面变化(支持pjax和动态加载) |
| 115 | + const observer = new MutationObserver(addCopyButton) |
| 116 | + observer.observe(document.body, { childList: true, subtree: true }) |
| 117 | + addCopyButton() |
| 118 | +})() |
0 commit comments