Skip to content

Instantly share code, notes, and snippets.

@eai04191
Last active May 13, 2025 12:47
Show Gist options
  • Save eai04191/d06abacfe198f13db4b143d192817743 to your computer and use it in GitHub Desktop.
Save eai04191/d06abacfe198f13db4b143d192817743 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name GitHub | Rich Link Copy
// @namespace net.mizle
// @version 1.0.0
// @description GitHubのIssue/PRページで番号とタイトルをHTMLリンクとしてコピーする
// @match https://github.com/*
// @grant GM_registerMenuCommand
// @grant GM_addStyle
// @grant GM_getResourceText
// @require https://cdn.jsdelivr.net/npm/@violentmonkey/shortcut@1
// @require https://cdn.jsdelivr.net/npm/notyf@3/notyf.min.js
// @resource Notyf_CSS https://cdn.jsdelivr.net/npm/notyf@3/notyf.min.css
// ==/UserScript==
// @ts-check
// @ts-expect-error Userscript API
GM_addStyle(GM_getResourceText("Notyf_CSS"));
// @ts-expect-error Userscript API
GM_addStyle(`
.notyf {
color: #1f2328;
}
`);
// @ts-expect-error Notyf API
const notyf = new Notyf({
// duration: 3000000,
position: {
x: "right",
y: "top",
},
types: [
{
type: "info",
background: "#f6f8fa",
icon: "",
},
{
type: "error",
background: "#d73a4a",
icon: "",
},
{
type: "success",
background: "#dafbe1",
icon: "",
},
],
});
/**
* @param {number} ms
*/
const delay = (ms) => new Promise((r) => setTimeout(r, ms));
/**
* @param {ClipboardItem} item
* @param {number} ms
*/
async function writeToClipboardAfter(item, ms = 500) {
await delay(ms);
await navigator.clipboard.write([item]);
}
function isPRPage() {
const path = window.location.pathname;
return path.includes("/pull/");
}
function isIssuePage() {
const path = window.location.pathname;
return path.includes("/issues/");
}
async function copyPageInfo() {
if (!isPRPage() && !isIssuePage()) {
return;
}
const loadingNotyf = notyf.open({
type: "info",
message: "コピー中...",
});
const type = isPRPage() ? "PR" : "Issue";
const headingElement = document.querySelector("h1:has(bdi):has(span)");
if (!headingElement) {
alert("Headingが見つかりませんでした。");
return;
}
// ex: #1234
const prNumber = headingElement.querySelector("span")?.innerText.trim();
const prTitle = headingElement.querySelector("bdi")?.innerText.trim();
const currentUrl = window.location.href;
// 表示テキスト
const displayText = `${type} ${prNumber} ${prTitle}`;
// HTML形式
const htmlText = `<a href="${currentUrl}">${displayText}</a>`;
try {
const item = new ClipboardItem({
"text/plain": new Blob([displayText], { type: "text/plain" }),
"text/html": new Blob([htmlText], { type: "text/html" }),
});
await writeToClipboardAfter(item);
notyf.dismiss(loadingNotyf);
notyf.success("リンクをコピーしました");
} catch (error) {
console.error("クリップボードへのコピーに失敗しました:", error);
notyf.error("クリップボードへのコピーに失敗しました。", "danger");
}
}
// コマンドの登録
// @ts-expect-error Userscript API
GM_registerMenuCommand("PRリンクをコピー", copyPageInfo);
// @ts-expect-error VM.shortcut API
VM.shortcut.register(
// Windows: ctrl+shift+c
// Mac: cmd+shift+c
"ctrlcmd-shift-c",
() => {
copyPageInfo();
}
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment