summaryrefslogtreecommitdiff
path: root/source/features/releases-tab.tsx
blob: b1e7812ba7843ff5b0d192488170edf67de5e33d (plain) (blame)
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
153
154
import React from 'dom-chef';
import cache from 'webext-storage-cache';
import select from 'select-dom';
import TagIcon from 'octicon/tag.svg';
import elementReady from 'element-ready';
import * as pageDetect from 'github-url-detection';

import features from '.';
import * as api from '../github-helpers/api';
import looseParseInt from '../helpers/loose-parse-int';
import {appendBefore} from '../helpers/dom-utils';
import {createDropdownItem} from './more-dropdown';
import {buildRepoURL, getRepo} from '../github-helpers';

const getCacheKey = (): string => `releases-count:${getRepo()!.nameWithOwner}`;

function parseCountFromDom(): number {
	const releasesCountElement = select('.numbers-summary a[href$="/releases"] .num');
	if (releasesCountElement) {
		return looseParseInt(releasesCountElement);
	}

	// In "Repository refresh" layout, look for the releases link in the sidebar
	const moreReleasesCountElement = select('[href$="/tags"] strong');
	if (moreReleasesCountElement) {
		return looseParseInt(moreReleasesCountElement);
	}

	return 0;
}

async function fetchFromApi(): Promise<number> {
	const {repository} = await api.v4(`
		repository() {
			refs(refPrefix: "refs/tags/") {
				totalCount
			}
		}
	`);

	return repository.refs.totalCount;
}

const getReleaseCount = cache.function(async () => pageDetect.isRepoRoot() ? parseCountFromDom() : fetchFromApi(), {
	maxAge: {hours: 1},
	staleWhileRevalidate: {days: 3},
	cacheKey: getCacheKey
});

async function init(): Promise<false | void> {
	// Always prefer the information in the DOM
	if (pageDetect.isRepoRoot()) {
		await cache.delete(getCacheKey());
	}

	const count = await getReleaseCount();
	if (count === 0) {
		return false;
	}

	// Wait for the tab bar to be loaded
	await elementReady([
		'.pagehead + *', // Pre "Repository refresh" layout
		'.UnderlineNav-body + *'
	].join());

	const repoNavigationBar = select('.js-responsive-underlinenav');
	if (repoNavigationBar) {
		// "Repository refresh" layout
		const releasesTab = (
			<a
				href={buildRepoURL('releases')}
				className="js-selected-navigation-item UnderlineNav-item hx_underlinenav-item no-wrap js-responsive-underlinenav-item"
				data-hotkey="g r"
				data-selected-links="repo_releases"
				data-tab-item="rgh-releases-item"
			>
				<TagIcon className="UnderlineNav-octicon"/>
				<span data-content="Releases">Releases</span>
				{count && <span className="Counter">{count}</span>}
			</a>
		);

		select(':scope > ul', repoNavigationBar)!.append(
			<li className="d-flex">
				{releasesTab}
			</li>
		);

		// This re-triggers the overflow listener forcing it to also hide this tab if necessary #3347
		repoNavigationBar.replaceWith(repoNavigationBar);

		// Update "selected" tab mark
		if (pageDetect.isReleasesOrTags()) {
			const selected = select('.UnderlineNav-item.selected');
			if (selected) {
				selected.classList.remove('selected');
				selected.removeAttribute('aria-current');
			}

			releasesTab.classList.add('selected');
			releasesTab.setAttribute('aria-current', 'page');
		}

		appendBefore(
			select('.js-responsive-underlinenav .dropdown-menu ul')!,
			'.dropdown-divider', // Won't exist if `more-dropdown` is disabled
			createDropdownItem('Releases', buildRepoURL('releases'), {
				'data-menu-item': 'rgh-releases-item'
			})
		);

		// Hide redundant 'Releases' section from repo sidebar
		if (pageDetect.isRepoRoot()) {
			const sidebarReleases = await elementReady('.BorderGrid-cell a[href$="/releases"]');
			sidebarReleases!.closest('.BorderGrid-row')!.setAttribute('hidden', '');
		}

		return;
	}

	const releasesTab = (
		<a href={buildRepoURL('releases')} className="reponav-item" data-hotkey="g r">
			<TagIcon/>
			<span> Releases </span>
			{count && <span className="Counter">{count}</span>}
		</a>
	);

	appendBefore(
		// GHE doesn't have `.reponav > ul`
		select('.reponav > ul') ?? select('.reponav')!,
		'.reponav-dropdown, [data-selected-links^="repo_settings"]',
		releasesTab
	);

	// Update "selected" tab mark
	if (pageDetect.isReleasesOrTags()) {
		select('.reponav-item.selected')?.classList.remove('js-selected-navigation-item', 'selected');
		releasesTab.classList.add('js-selected-navigation-item', 'selected');
		releasesTab.dataset.selectedLinks = 'repo_releases'; // Required for ajaxLoad
	}
}

void features.add(__filebasename, {
	shortcuts: {
		'g r': 'Go to Releases'
	},
	include: [
		pageDetect.isRepo
	],
	awaitDomReady: false,
	init
});