summaryrefslogtreecommitdiff
path: root/source/features/repo-age.tsx
blob: aafe6712376ef8f42160ea160fbdcd49f7403f84 (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
import twas from 'twas';
import cache from 'webext-storage-cache';
import React from 'dom-chef';
import select from 'select-dom';
import RepoIcon from 'octicon/repo.svg';
import elementReady from 'element-ready';
import * as pageDetect from 'github-url-detection';

import features from '.';
import * as api from '../github-helpers/api';
import {getRepoGQL, getRepo} from '../github-helpers';

const dateFormatter = new Intl.DateTimeFormat('en-US', {
	year: 'numeric',
	month: 'long',
	day: 'numeric'
});

const getRepoAge = async (commitSha: string, commitsCount: number): Promise<[committedDate: string, resourcePath: string]> => {
	const {repository} = await api.v4(`
		repository(${getRepoGQL()}) {
			defaultBranchRef {
				target {
					... on Commit {
						history(first: 5, after: "${commitSha} ${commitsCount - Math.min(6, commitsCount)}") {
							nodes {
								committedDate
								resourcePath
							}
						}
					}
				}
			}
		}
	`);

	const {committedDate, resourcePath} = repository.defaultBranchRef.target.history.nodes
		.reverse()
		// Filter out any invalid commit dates #3185
		.find((commit: AnyObject) => new Date(commit.committedDate).getFullYear() > 1970);

	return [committedDate, resourcePath];
};

const getFirstCommit = cache.function(async (): Promise<[committedDate: string, resourcePath: string]> => {
	const {repository} = await api.v4(`
		repository(${getRepoGQL()}) {
			defaultBranchRef {
				target {
					... on Commit {
						oid
						committedDate
						resourcePath
						history {
							totalCount
						}
					}
				}
			}
		}
	`);

	const {oid: commitSha, history, committedDate, resourcePath} = repository.defaultBranchRef.target;
	const commitsCount = history.totalCount;
	if (commitsCount === 1) {
		return [committedDate, resourcePath];
	}

	return getRepoAge(commitSha, commitsCount);
}, {
	cacheKey: () => __filebasename + ':' + getRepo()!.nameWithOwner
});

async function init(): Promise<void> {
	const [firstCommitDate, firstCommitHref] = await getFirstCommit()!;
	const date = new Date(firstCommitDate);

	// `twas` could also return `an hour ago` or `just now`
	const [value, unit] = twas(date.getTime())
		.replace('just now', '1 second')
		.replace(/^an?/, '1')
		.split(' ');

	// TODO: simplify selector after https://github.com/sindresorhus/element-ready/issues/29
	const secondSidebarSection = await elementReady('.repository-content .BorderGrid-row + .BorderGrid-row');
	if (secondSidebarSection) {
		const sidebarAboutSection = secondSidebarSection.previousElementSibling!;
		select('.BorderGrid-cell', sidebarAboutSection)!.append(
			<h3 className="sr-only">Repository age</h3>,
			<div className="mt-3">
				<a href={firstCommitHref} className="muted-link" title={`First commit dated ${dateFormatter.format(date)}`}>
					<RepoIcon className="mr-2"/> {value} {unit} old
				</a>
			</div>
		);

		return;
	}

	// Pre "Repository refresh" layout
	const element = (
		<li className="text-gray" title={`First commit dated ${dateFormatter.format(date)}`}>
			<a href={firstCommitHref}>
				<RepoIcon/> <span className="num text-emphasized">{value}</span> {unit} old
			</a>
		</li>
	);

	const license = select('.numbers-summary .octicon-law');
	if (license) {
		license.closest('li')!.before(element);
	} else {
		select('.numbers-summary')!.append(element);
	}
}

void features.add(__filebasename, {
	include: [
		pageDetect.isRepoRoot
	],
	exclude: [
		pageDetect.isEmptyRepoRoot
	],
	awaitDomReady: false,
	init
});