1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
import React from 'dom-chef';
import {CachedFunction} from 'webext-storage-cache';
import {$} from 'select-dom';
import {TagIcon} from '@primer/octicons-react';
import * as pageDetect from 'github-url-detection';
import features from '../feature-manager.js';
import fetchDom from '../helpers/fetch-dom.js';
import onPrMerge from '../github-events/on-pr-merge.js';
import createBanner from '../github-helpers/banner.js';
import TimelineItem from '../github-helpers/timeline-item.js';
import attachElement from '../helpers/attach-element.js';
import {canEditEveryComment} from './quick-comment-edit.js';
import {buildRepoURL, getRepo, isRefinedGitHubRepo} from '../github-helpers/index.js';
import {getReleases} from './releases-tab.js';
import observe from '../helpers/selector-observer.js';
// TODO: Not an exact match; Moderators can edit comments but not create releases
const canCreateRelease = canEditEveryComment;
const firstTag = new CachedFunction('first-tag', {
async updater(commit: string): Promise<string | false> {
const firstTag = await fetchDom(
buildRepoURL('branch_commits', commit),
'ul.branches-tag-list li:last-child a',
);
return firstTag?.textContent ?? false;
},
cacheKey: ([commit]) => [getRepo()!.nameWithOwner, commit].join(':'),
});
function createReleaseUrl(): string | undefined {
if (!canCreateRelease()) {
return;
}
if (isRefinedGitHubRepo()) {
return 'https://github.com/refined-github/refined-github/actions/workflows/release.yml';
}
return buildRepoURL('releases/new');
}
async function init(signal: AbortSignal): Promise<void> {
const mergeCommit = $(`.TimelineItem.js-details-container.Details a[href^="/${getRepo()!.nameWithOwner}/commit/" i] > code`)!.textContent;
const tagName = await firstTag.get(mergeCommit);
if (tagName) {
const tagUrl = buildRepoURL('releases/tag', tagName);
// Add static box at the bottom
addExistingTagLinkFooter(tagName, tagUrl);
// PRs have a regular and a sticky header
observe('#partial-discussion-header relative-time', addExistingTagLinkToHeader.bind(null, tagName, tagUrl), {signal});
} else {
void addReleaseBanner('This PR’s merge commit doesn’t appear in any tags');
}
}
function addExistingTagLinkToHeader(tagName: string, tagUrl: string, discussionHeader: HTMLElement): void {
// TODO: Use :has selector instead
discussionHeader.parentElement!.append(
<span>
<TagIcon className="ml-2 mr-1 color-fg-muted"/>
<a
href={tagUrl}
className="commit-ref"
title={`${tagName} was the first Git tag to include this pull request`}
>
{tagName}
</a>
</span>,
);
}
function addExistingTagLinkFooter(tagName: string, tagUrl: string): void {
const linkedTag = <a href={tagUrl} className="Link--primary text-bold">{tagName}</a>;
attachElement('#issue-comment-box', {
before: () => (
<TimelineItem>
{createBanner({
icon: <TagIcon className="m-0"/>,
text: <>This pull request first appeared in {linkedTag}</>,
classes: ['flash-success', 'rgh-bg-none'],
})}
</TimelineItem>
),
});
}
async function addReleaseBanner(text = 'Now you can release this change'): Promise<void> {
const [releases] = await getReleases();
if (releases === 0) {
return;
}
const url = createReleaseUrl();
const bannerContent = {
icon: <TagIcon className="m-0"/>,
classes: ['rgh-bg-none'],
text,
};
attachElement('#issue-comment-box', {
before: () => (
<TimelineItem>
{createBanner(url ? {
...bannerContent,
action: url,
buttonLabel: 'Draft a new release',
} : bannerContent)}
</TimelineItem>
),
});
}
void features.add(import.meta.url, {
// When arriving on an already-merged PR
asLongAs: [
pageDetect.isPRConversation,
pageDetect.isMergedPR,
],
awaitDomReady: true, // It must look for the merge commit
init,
}, {
// This catches a PR while it's being merged
asLongAs: [
pageDetect.isPRConversation,
pageDetect.isOpenPR,
canCreateRelease,
],
additionalListeners: [
onPrMerge,
],
onlyAdditionalListeners: true,
awaitDomReady: true, // DOM-based filters
init() {
void addReleaseBanner();
},
});
/*
Test URLs
- PR: https://github.com/refined-github/refined-github/pull/5600
- Locked PR: https://github.com/eslint/eslint/pull/17
- Archived repo: https://github.com/fregante/iphone-inline-video/pull/130
- RGH tagged PR: https://github.com/refined-github/sandbox/pull/1
*/
|