Created
June 5, 2025 04:11
-
-
Save fauxparse/6d60305328587a17b535ccc092b61dc6 to your computer and use it in GitHub Desktop.
Add copy button to Github PRs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"manifest_version": 2, | |
"name": "Copy PR details", | |
"version": "1.0", | |
"description": "Copies Github PR details to clipboard for pasting into Slack", | |
"permissions": [ | |
"activeTab" | |
], | |
"content_scripts": [ | |
{ | |
"matches": ["*://*.github.com/*"], | |
"js": ["script.js"] | |
} | |
] | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function addCopyButton() { | |
const titleElement = document.querySelector('.js-issue-title'); | |
// Check if titleElement exists and if our button hasn't been added yet | |
if (titleElement && !document.getElementById('copy-pr-details-button')) { | |
const button = document.createElement('button'); | |
const clipboardIconSVG = '<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-copy"> <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><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></svg>'; | |
button.id = 'copy-pr-details-button'; | |
button.innerHTML = clipboardIconSVG; | |
button.type = 'button'; // Good practice for buttons | |
// Use GitHub's button classes for styling and add a small margin | |
button.className = 'btn btn-sm'; | |
button.style.marginLeft = '8px'; | |
button.style.marginTop = '-6px'; | |
button.style.cursor = 'pointer'; | |
// Ensure it's vertically aligned reasonably if possible | |
button.style.verticalAlign = 'middle'; | |
button.addEventListener('click', (event) => { | |
event.stopPropagation(); // Prevent any other click listeners on parent elements | |
// Re-fetch the title element at click time to ensure it's still valid, | |
// especially relevant if the DOM could have changed. | |
const currentTitleElement = document.querySelector('.js-issue-title'); | |
if (!currentTitleElement) { | |
console.error('Copy PR Details: Could not find PR title element at click time.'); | |
alert('Copy PR Details: Could not find PR title element.'); | |
return; | |
} | |
const issueTitle = currentTitleElement.textContent.trim(); | |
const pageUrl = window.location.href; | |
const textToCopy = `🔍 [${issueTitle}](${pageUrl})`; | |
navigator.clipboard.writeText(textToCopy) | |
.then(() => { | |
console.log('PR details copied to clipboard:', textToCopy); | |
button.textContent = 'Copied!'; | |
button.disabled = true; | |
setTimeout(() => { | |
button.innerHTML = clipboardIconSVG; | |
button.disabled = false; | |
}, 2000); | |
}) | |
.catch(err => { | |
console.error('Failed to copy PR details: ', err); | |
alert('Failed to copy PR details: ' + err.message); | |
}); | |
}); | |
// Insert the button. Appending to the parent of .js-issue-title is usually a good spot. | |
if (titleElement.parentElement) { | |
// Check if the parent is displayed, sometimes titleElement might be in a hidden structure initially | |
if (window.getComputedStyle(titleElement.parentElement).display !== 'none') { | |
titleElement.parentElement.appendChild(button); | |
} else { | |
// If parent is not visible, try to insert after titleElement itself as a fallback, | |
// though this might indicate a more complex scenario. | |
titleElement.insertAdjacentElement('afterend', button); | |
} | |
} else { | |
// Fallback if parentElement is null for some reason | |
titleElement.insertAdjacentElement('afterend', button); | |
} | |
} | |
} | |
// --- MutationObserver to handle dynamic content loading (e.g., PJAX navigation in GitHub) --- | |
const observerCallback = (mutationsList, observer) => { | |
// Check if the title element is present and the button is not. | |
// This is a common pattern for SPAs where content is replaced. | |
if (document.querySelector('.js-issue-title') && !document.getElementById('copy-pr-details-button')) { | |
addCopyButton(); | |
} | |
// If the title element is gone, and our button might still be somewhere else (orphaned), | |
// this doesn't explicitly clean it up, but addCopyButton checks for existence. | |
}; | |
const observer = new MutationObserver(observerCallback); | |
function initializeExtension() { | |
// Call addCopyButton once at the start in case the page is already loaded with the title | |
addCopyButton(); | |
// Then, observe for changes in the DOM | |
observer.observe(document.body, { childList: true, subtree: true }); | |
} | |
// Start the process depending on the document loading state. | |
if (document.readyState === 'loading') { | |
document.addEventListener('DOMContentLoaded', initializeExtension); | |
} else { | |
// DOM is already loaded | |
initializeExtension(); | |
} | |
// Optional: Disconnect observer on page unload (though browser usually handles this for content scripts) | |
// window.addEventListener('unload', () => { | |
// if (observer) { | |
// observer.disconnect(); | |
// console.log('Copy PR Details: Observer disconnected.'); | |
// } | |
// }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment