|
11 | 11 | return true; |
12 | 12 | } |
13 | 13 |
|
14 | | - // Find the copy branch button (clipboard icon next to branch name) |
15 | | - const copyBranchBtn = document.querySelector('.gh-header-meta clipboard-copy'); |
| 14 | + // Find the copy branch button (IconButton with octicon-copy next to branch name) |
| 15 | + // Look for the button that has a tooltip about copying the head branch name |
| 16 | + // The button is inside .prc-PageHeader-Description-w-ejP (non-sticky header) |
| 17 | + const headerDescription = document.querySelector('.prc-PageHeader-Description-w-ejP'); |
| 18 | + let copyBranchBtn = null; |
| 19 | + if (headerDescription) { |
| 20 | + copyBranchBtn = headerDescription.querySelector('button.prc-Button-IconButton-fyge7 .octicon-copy')?.closest('button'); |
| 21 | + } |
| 22 | + // Fallback to legacy selector |
| 23 | + if (!copyBranchBtn) { |
| 24 | + copyBranchBtn = document.querySelector('.gh-header-meta clipboard-copy'); |
| 25 | + } |
16 | 26 | if (!copyBranchBtn) { |
17 | 27 | return false; |
18 | 28 | } |
19 | 29 |
|
20 | | - // Get PR title for copying |
21 | | - const titleElement = document.querySelector('.js-issue-title'); |
| 30 | + // Get PR title for copying - try new React header first, then legacy |
| 31 | + const titleElement = document.querySelector('.prc-PageHeader-Title-p0Mgh .markdown-title') || |
| 32 | + document.querySelector('h1 .markdown-title') || |
| 33 | + document.querySelector('.js-issue-title'); |
22 | 34 | if (!titleElement) { |
23 | 35 | return false; |
24 | 36 | } |
25 | 37 |
|
26 | | - // Create the button - match the style of the existing clipboard-copy button |
| 38 | + // Create the button - match the style of the existing IconButton |
27 | 39 | const button = document.createElement('button'); |
28 | 40 | button.id = 'copy-pr-link-btn'; |
29 | | - button.className = 'Button Button--iconOnly Button--secondary Button--small'; |
30 | 41 | button.type = 'button'; |
| 42 | + // Use the same class as the copy branch button for consistent styling |
| 43 | + button.className = 'prc-Button-ButtonBase-9n-Xk prc-Button-IconButton-fyge7'; |
| 44 | + button.setAttribute('data-loading', 'false'); |
| 45 | + button.setAttribute('data-no-visuals', 'true'); |
| 46 | + button.setAttribute('data-size', 'small'); |
| 47 | + button.setAttribute('data-variant', 'invisible'); |
31 | 48 | button.title = 'Copy PR link'; |
32 | | - button.style.cssText = 'vertical-align: middle; margin-left: 4px;'; |
| 49 | + button.style.cssText = 'margin-left: 4px;'; |
33 | 50 | button.innerHTML = ` |
34 | | - <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" class="octicon octicon-link"> |
35 | | - <path fill="currentColor" d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path> |
| 51 | + <svg aria-hidden="true" focusable="false" height="16" viewBox="0 0 16 16" width="16" fill="currentColor" class="octicon octicon-link" style="vertical-align: text-bottom;"> |
| 52 | + <path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path> |
36 | 53 | </svg> |
37 | 54 | `; |
38 | 55 |
|
|
48 | 65 | await navigator.clipboard.writeText(textToCopy); |
49 | 66 | // Visual feedback - change to checkmark |
50 | 67 | button.innerHTML = ` |
51 | | - <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" class="octicon octicon-check" style="color: #1a7f37;"> |
52 | | - <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> |
| 68 | + <svg aria-hidden="true" focusable="false" height="16" viewBox="0 0 16 16" width="16" fill="#1a7f37" class="octicon octicon-check" style="vertical-align: text-bottom;"> |
| 69 | + <path 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> |
53 | 70 | </svg> |
54 | 71 | `; |
55 | 72 | setTimeout(() => { |
56 | 73 | button.innerHTML = ` |
57 | | - <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" class="octicon octicon-link"> |
58 | | - <path fill="currentColor" d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path> |
| 74 | + <svg aria-hidden="true" focusable="false" height="16" viewBox="0 0 16 16" width="16" fill="currentColor" class="octicon octicon-link" style="vertical-align: text-bottom;"> |
| 75 | + <path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path> |
59 | 76 | </svg> |
60 | 77 | `; |
61 | 78 | }, 2000); |
|
71 | 88 |
|
72 | 89 | // Feature 2: Add "Request Review" button next to Copilot reviewer for draft PRs |
73 | 90 | function addCopilotReviewButton() { |
74 | | - // Check if this is a draft PR |
75 | | - const draftBadge = document.querySelector('.State[title="Status: Draft"]'); |
| 91 | + // Check if this is a draft PR - try new React state label first, then legacy |
| 92 | + const draftBadge = document.querySelector('[data-status="pullDraft"]') || |
| 93 | + document.querySelector('.State[title="Status: Draft"]'); |
76 | 94 | if (!draftBadge) { |
77 | 95 | // Not a draft PR, no need to add the button |
78 | 96 | return false; |
79 | 97 | } |
80 | 98 |
|
81 | | - // Find the Copilot reviewer link in the sidebar only (not in timeline) |
82 | | - const sidebar = document.querySelector('.Layout-sidebar'); |
83 | | - if (!sidebar) { |
| 99 | + // Find the Copilot reviewer link in the sidebar/reviewers section only (not in timeline) |
| 100 | + // Look for the reviewers container first |
| 101 | + const reviewersSection = document.querySelector('form[action*="review-requests"]')?.closest('.discussion-sidebar-item') || |
| 102 | + document.querySelector('.sidebar-assignee'); |
| 103 | + if (!reviewersSection) { |
84 | 104 | return false; |
85 | 105 | } |
86 | 106 |
|
87 | | - const copilotLink = sidebar.querySelector('a[href="/apps/copilot-pull-request-reviewer"]'); |
| 107 | + const copilotLink = reviewersSection.querySelector('a[href="/apps/copilot-pull-request-reviewer"]'); |
88 | 108 | if (!copilotLink) { |
89 | 109 | return false; |
90 | 110 | } |
|
218 | 238 | addCopilotReviewButton(); |
219 | 239 | }); |
220 | 240 |
|
221 | | - // Observe the main container for changes |
222 | | - const container = document.querySelector('#partial-discussion-header') || document.body; |
| 241 | + // Observe the main container for changes - try React app container first |
| 242 | + const container = document.querySelector('react-app[app-name="pull-requests"]') || |
| 243 | + document.querySelector('#partial-discussion-header') || |
| 244 | + document.body; |
223 | 245 | observer.observe(container, { |
224 | 246 | childList: true, |
225 | 247 | subtree: true |
|
0 commit comments