Skip to content

Commit 39bdf6e

Browse files
Copilotdevm33
andcommitted
Add Chrome extension structure with PR link copy and Copilot review features
Co-authored-by: devm33 <1682753+devm33@users.noreply.github.com>
1 parent ff1467b commit 39bdf6e

7 files changed

Lines changed: 312 additions & 2 deletions

File tree

README.md

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,75 @@
1-
# github-ui-mods
2-
set of small mods for the github ui
1+
# GitHub UI Mods
2+
3+
A Chrome extension that adds useful modifications to the GitHub pull request interface.
4+
5+
## Features
6+
7+
### 1. Copy PR Link Button
8+
Adds a button next to the PR title that copies the PR title and link to your clipboard in the format: `PR Title: https://link-to-pr`
9+
10+
### 2. Request Copilot Review Button (Draft PRs)
11+
When viewing a draft PR, adds a "Request Review" button next to the Copilot reviewer link (`/apps/copilot-pull-request-reviewer`) to easily request a review while the PR is still in draft status.
12+
13+
## Installation
14+
15+
### Load as Unpacked Extension (For Development/Testing)
16+
17+
1. Clone or download this repository:
18+
```bash
19+
git clone https://github.com/devm33/github-ui-mods.git
20+
cd github-ui-mods
21+
```
22+
23+
2. Open Chrome and navigate to `chrome://extensions/`
24+
25+
3. Enable "Developer mode" by toggling the switch in the top-right corner
26+
27+
4. Click "Load unpacked" button
28+
29+
5. Select the `github-ui-mods` directory (the one containing `manifest.json`)
30+
31+
6. The extension should now be installed and active!
32+
33+
### Verify Installation
34+
35+
Navigate to any GitHub pull request page. You should see:
36+
- A "Copy PR Link" button next to the PR title
37+
- (If it's a draft PR) A "Request Review" button next to the Copilot reviewer
38+
39+
## Usage
40+
41+
### Copy PR Link
42+
1. Navigate to any GitHub pull request
43+
2. Click the "Copy PR Link" button next to the PR title
44+
3. The PR title and URL will be copied to your clipboard
45+
4. The button will show "Copied!" briefly to confirm
46+
47+
### Request Copilot Review (Draft PRs)
48+
1. Navigate to a GitHub pull request that is in draft status
49+
2. Find the "Request Review" button next to the Copilot reviewer link
50+
3. Click the button to request a review from Copilot
51+
4. The button will show "Requested!" briefly to confirm
52+
53+
## Development
54+
55+
The extension consists of:
56+
- `manifest.json` - Extension configuration
57+
- `content.js` - Content script that modifies GitHub PR pages
58+
- `icon*.png` - Extension icons in various sizes
59+
60+
To modify the extension:
61+
1. Edit the files as needed
62+
2. Go to `chrome://extensions/`
63+
3. Click the refresh icon on the extension card
64+
4. Refresh any GitHub PR pages to see your changes
65+
66+
## Browser Compatibility
67+
68+
This extension is built for Chrome (Manifest V3) but should also work in:
69+
- Microsoft Edge
70+
- Brave
71+
- Other Chromium-based browsers
72+
73+
## License
74+
75+
See LICENSE file for details.

content.js

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
// GitHub UI Mods - Content Script
2+
// Adds custom buttons to GitHub PR pages
3+
4+
(function() {
5+
'use strict';
6+
7+
// Feature 1: Add "Copy PR Link" button next to PR title
8+
function addCopyPRLinkButton() {
9+
// Find the PR title element
10+
const titleElement = document.querySelector('.js-issue-title');
11+
if (!titleElement) {
12+
return false;
13+
}
14+
15+
// Check if button already exists
16+
if (document.getElementById('copy-pr-link-btn')) {
17+
return true;
18+
}
19+
20+
// Create the button
21+
const button = document.createElement('button');
22+
button.id = 'copy-pr-link-btn';
23+
button.className = 'btn btn-sm';
24+
button.style.marginLeft = '8px';
25+
button.style.verticalAlign = 'middle';
26+
button.innerHTML = `
27+
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" style="display:inline-block;vertical-align:text-bottom;">
28+
<path fill="currentColor" 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.25Zm5.22-1.72a.75.75 0 0 1 1.06 0l3.97 3.97V4.25a.75.75 0 0 1 1.5 0v6.5a.75.75 0 0 1-.75.75h-6.5a.75.75 0 0 1 0-1.5H9.44L5.47 5.53a.75.75 0 0 1 0-1.06Z"></path>
29+
</svg>
30+
Copy PR Link
31+
`;
32+
button.title = 'Copy PR title and link to clipboard';
33+
34+
// Add click handler
35+
button.addEventListener('click', async function(e) {
36+
e.preventDefault();
37+
38+
// Get PR title and URL
39+
const prTitle = titleElement.textContent.trim();
40+
const prUrl = window.location.href;
41+
const textToCopy = `${prTitle}: ${prUrl}`;
42+
43+
// Copy to clipboard
44+
try {
45+
await navigator.clipboard.writeText(textToCopy);
46+
47+
// Visual feedback
48+
const originalText = button.innerHTML;
49+
button.innerHTML = `
50+
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" style="display:inline-block;vertical-align:text-bottom;">
51+
<path fill="currentColor" d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path>
52+
</svg>
53+
Copied!
54+
`;
55+
button.style.color = '#1a7f37';
56+
57+
setTimeout(() => {
58+
button.innerHTML = originalText;
59+
button.style.color = '';
60+
}, 2000);
61+
} catch (err) {
62+
console.error('Failed to copy:', err);
63+
button.textContent = 'Failed to copy';
64+
setTimeout(() => {
65+
button.innerHTML = originalText;
66+
}, 2000);
67+
}
68+
});
69+
70+
// Insert button next to title
71+
const titleContainer = titleElement.parentElement;
72+
if (titleContainer) {
73+
titleContainer.style.display = 'flex';
74+
titleContainer.style.alignItems = 'center';
75+
titleElement.after(button);
76+
return true;
77+
}
78+
79+
return false;
80+
}
81+
82+
// Feature 2: Add "Request Review" button next to Copilot reviewer for draft PRs
83+
function addCopilotReviewButton() {
84+
// Check if this is a draft PR
85+
const draftBadge = document.querySelector('.State[title="Status: Draft"]');
86+
if (!draftBadge) {
87+
// Not a draft PR, no need to add the button
88+
return false;
89+
}
90+
91+
// Find the Copilot reviewer link
92+
const copilotLink = document.querySelector('a[href="/apps/copilot-pull-request-reviewer"]');
93+
if (!copilotLink) {
94+
return false;
95+
}
96+
97+
// Check if button already exists
98+
if (document.getElementById('copilot-request-review-btn')) {
99+
return true;
100+
}
101+
102+
// Create the button
103+
const button = document.createElement('button');
104+
button.id = 'copilot-request-review-btn';
105+
button.className = 'btn btn-sm';
106+
button.style.marginLeft = '8px';
107+
button.innerHTML = `
108+
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" style="display:inline-block;vertical-align:text-bottom;">
109+
<path fill="currentColor" d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7.25-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7.25 8V4.75a.75.75 0 0 1 1.5 0Z"></path>
110+
</svg>
111+
Request Review
112+
`;
113+
button.title = 'Request Copilot review for draft PR';
114+
115+
// Add click handler
116+
button.addEventListener('click', async function(e) {
117+
e.preventDefault();
118+
119+
// Find the review request form/button
120+
// GitHub's UI typically has a button or action to request reviews
121+
// We'll try to find and click it programmatically
122+
123+
// Try to find the "Reviewers" section and expand it if needed
124+
const reviewersSection = document.querySelector('.discussion-sidebar-item.js-discussion-sidebar-item');
125+
if (reviewersSection) {
126+
const requestReviewBtn = reviewersSection.querySelector('button[aria-label*="request"]');
127+
if (requestReviewBtn) {
128+
requestReviewBtn.click();
129+
130+
// Wait a bit for the modal/dropdown to appear
131+
setTimeout(() => {
132+
// Try to find and click on Copilot in the reviewer list
133+
const copilotReviewerOption = document.querySelector('[data-filterable-for*="copilot"]');
134+
if (copilotReviewerOption) {
135+
copilotReviewerOption.click();
136+
}
137+
}, 100);
138+
139+
// Visual feedback
140+
const originalText = button.innerHTML;
141+
button.innerHTML = `
142+
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" style="display:inline-block;vertical-align:text-bottom;">
143+
<path fill="currentColor" d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path>
144+
</svg>
145+
Requested!
146+
`;
147+
button.style.color = '#1a7f37';
148+
149+
setTimeout(() => {
150+
button.innerHTML = originalText;
151+
button.style.color = '';
152+
}, 2000);
153+
} else {
154+
// Fallback: provide feedback that manual action is needed
155+
alert('Please request the review manually from the Reviewers section');
156+
}
157+
} else {
158+
alert('Could not find Reviewers section. Please request the review manually.');
159+
}
160+
});
161+
162+
// Insert button next to Copilot link
163+
const copilotContainer = copilotLink.parentElement;
164+
if (copilotContainer) {
165+
copilotLink.after(button);
166+
return true;
167+
}
168+
169+
return false;
170+
}
171+
172+
// Initialize the modifications
173+
function init() {
174+
// Try to add features immediately
175+
addCopyPRLinkButton();
176+
addCopilotReviewButton();
177+
178+
// Also observe DOM changes in case elements load dynamically
179+
const observer = new MutationObserver(function(mutations) {
180+
addCopyPRLinkButton();
181+
addCopilotReviewButton();
182+
});
183+
184+
// Observe the main container for changes
185+
const container = document.querySelector('#partial-discussion-header') || document.body;
186+
observer.observe(container, {
187+
childList: true,
188+
subtree: true
189+
});
190+
191+
// Also check periodically in case of dynamic loading
192+
const checkInterval = setInterval(() => {
193+
const btn1Added = addCopyPRLinkButton();
194+
const btn2Added = addCopilotReviewButton();
195+
196+
// If both buttons are added or not needed, we can stop checking so frequently
197+
if (btn1Added) {
198+
clearInterval(checkInterval);
199+
}
200+
}, 1000);
201+
202+
// Clear interval after 10 seconds regardless
203+
setTimeout(() => clearInterval(checkInterval), 10000);
204+
}
205+
206+
// Wait for DOM to be ready
207+
if (document.readyState === 'loading') {
208+
document.addEventListener('DOMContentLoaded', init);
209+
} else {
210+
init();
211+
}
212+
})();

icon.svg

Lines changed: 4 additions & 0 deletions
Loading

icon128.png

413 Bytes
Loading

icon16.png

109 Bytes
Loading

icon48.png

184 Bytes
Loading

manifest.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"manifest_version": 3,
3+
"name": "GitHub UI Mods",
4+
"version": "1.0.0",
5+
"description": "Small modifications for the GitHub UI",
6+
"permissions": [
7+
"clipboardWrite"
8+
],
9+
"content_scripts": [
10+
{
11+
"matches": ["https://github.com/*/*/pull/*"],
12+
"js": ["content.js"],
13+
"run_at": "document_idle"
14+
}
15+
],
16+
"icons": {
17+
"16": "icon16.png",
18+
"48": "icon48.png",
19+
"128": "icon128.png"
20+
}
21+
}

0 commit comments

Comments
 (0)