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
|
import React from 'dom-chef';
import cache from 'webext-storage-cache';
import select from 'select-dom';
import {BookIcon} from '@primer/octicons-react';
import elementReady from 'element-ready';
import * as pageDetect from 'github-url-detection';
import features from '../feature-manager.js';
import * as api from '../github-helpers/api.js';
import {wrapAll} from '../helpers/dom-utils.js';
import {buildRepoURL, cacheByRepo} from '../github-helpers/index.js';
type FileType = {
name: string;
type: string;
};
const cacheName = 'changelog';
const changelogFiles = /^(changelog|news|changes|history|release|whatsnew)(\.(mdx?|mkdn?|mdwn|mdown|markdown|litcoffee|txt|rst))?$/i;
function findChangelogName(files: string[]): string | false {
return files.find(name => changelogFiles.test(name)) ?? false;
}
function parseFromDom(): false {
const files = select.all('[aria-labelledby="files"] .js-navigation-open[href*="/blob/"').map(file => file.title);
void cache.set(cacheName + ':' + cacheByRepo(), findChangelogName(files));
return false;
}
const getChangelogName = cache.function(cacheName, async (): Promise<string | false> => {
const {repository} = await api.v4(`
repository() {
object(expression: "HEAD:") {
...on Tree {
entries {
name
type
}
}
}
}
`);
const files: string[] = [];
for (const entry of repository.object.entries as FileType[]) {
if (entry.type === 'blob') {
files.push(entry.name);
}
}
return findChangelogName(files);
}, {
cacheKey: cacheByRepo,
});
async function init(): Promise<void | false> {
const changelog = await getChangelogName();
if (!changelog) {
return false;
}
const changelogButton = (
<a
className={'tooltipped tooltipped-n btn ml-3' + (pageDetect.isEnterprise() ? '' : ' flex-self-start')}
aria-label={`View the ${changelog} file`}
href={buildRepoURL('blob', 'HEAD', changelog)}
style={pageDetect.isEnterprise() ? {padding: '6px 16px'} : {}}
role="button"
>
<BookIcon className="color-fg-accent mr-2"/>
<span>Changelog</span>
</a>
);
const releasesOrTagsNavbarSelector = [
'nav[aria-label^="Releases and Tags"]', // Release list
'.subnav-links', // Tag list
].join(',');
const navbar = (await elementReady(releasesOrTagsNavbarSelector, {waitForChildren: false}))!;
navbar.classList.remove('flex-1');
wrapAll([navbar, changelogButton], <div className="d-flex flex-justify-start flex-1"/>);
}
void features.add(import.meta.url, {
include: [
pageDetect.isReleasesOrTags,
],
exclude: [
pageDetect.isSingleTag,
],
deduplicate: 'has-rgh-inner',
init,
}, {
include: [
pageDetect.isRepoHome,
],
deduplicate: 'has-rgh',
awaitDomReady: true, // Does not affect current visit
init: parseFromDom,
});
|