summaryrefslogtreecommitdiff
path: root/source/features/useful-not-found-page.tsx
blob: b65b7939b5440b337f1ba40bc5b54c0ed18b7911 (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
import React from 'dom-chef';
import select from 'select-dom';
import features from '../libs/features';
import {getCleanPathname} from '../libs/utils';
import getDefaultBranch from '../libs/get-default-branch';

async function is404(url: string): Promise<boolean> {
	const {status} = await fetch(url, {method: 'head'});
	return status === 404;
}

function getStrikeThrough(text: string): HTMLElement {
	return <del style={{color: '#6a737d'}}>{text}</del>;
}

async function checkAnchor(anchor: HTMLAnchorElement): Promise<void> {
	if (await is404(anchor.href)) {
		anchor.replaceWith(getStrikeThrough(anchor.textContent!));
	}
}

function parseCurrentURL(): string[] {
	const parts = getCleanPathname().split('/');
	if (parts[2] === 'blob') { // Blob URLs are never useful
		parts[2] = 'tree';
	}

	return parts;
}

// If the resource was deleted, link to the commit history
async function addCommitHistoryLink(bar: Element): Promise<void> {
	const parts = parseCurrentURL();
	parts[2] = 'commits';
	const url = '/' + parts.join('/');
	if (await is404(location.origin + url)) {
		return;
	}

	bar.after(
		<p className="container mt-4 text-center">
			See also the file’s {<a href={url}>commit history</a>}
		</p>
	);
}

// If the resource exists in the default branch, link to it
async function addDefaultBranchLink(bar: Element): Promise<void> {
	const parts = getCleanPathname().split('/');
	const branch = parts[3];
	if (!branch) {
		return;
	}

	const defaultBranch = await getDefaultBranch();
	if (!defaultBranch || branch === defaultBranch) {
		return;
	}

	parts[3] = defaultBranch; // Change branch
	const url = '/' + parts.join('/');
	if (await is404(location.origin + url)) {
		return;
	}

	bar.after(
		<p className="container mt-4 text-center">
			See also the file on the {<a href={url}>default branch</a>}
		</p>
	);
}

function init(): false | void {
	const parts = parseCurrentURL();
	if (parts.length <= 1 || !select.exists('[alt*="This is not the web page you are looking for"]')) {
		return false;
	}

	const bar = <h2 className="container mt-4 text-center"/>;

	for (const [i, part] of parts.entries()) {
		if (i === 2 && part === 'tree') {
			// `/tree/` is not a real part of the URL
			continue;
		}

		if (i === parts.length - 1) {
			// The last part of the URL is a known 404
			bar.append(' / ', getStrikeThrough(part));
		} else {
			const pathname = '/' + parts.slice(0, i + 1).join('/');
			bar.append(i ? ' / ' : '', <a href={pathname}>{part}</a>);
		}
	}

	select('main > :first-child, #parallax_illustration')!.after(bar);

	// Check parts from right to left; skip the last part
	for (let i = bar.children.length - 2; i >= 0; i--) {
		checkAnchor(bar.children[i] as HTMLAnchorElement);
	}

	if (parts[2] === 'tree') {
		addCommitHistoryLink(bar);
		addDefaultBranchLink(bar);
	}
}

features.add({
	id: __featureName__,
	description: 'Adds possible related pages and alternatives on 404 pages.',
	screenshot: 'https://user-images.githubusercontent.com/1402241/46402857-7bdada80-c733-11e8-91a1-856573078ff5.png',
	include: [
		features.is404
	],
	load: features.onDomReady,
	init
});