summaryrefslogtreecommitdiff
path: root/source/features/highlight-deleted-and-added-files-in-diffs.tsx
blob: 1acf46b9d90f995241c7332324afa76208b46f19 (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
import React from 'dom-chef';
import select from 'select-dom';
import oneMutation from 'one-mutation';
import elementReady from 'element-ready';
import * as pageDetect from 'github-url-detection';

import features from '../feature-manager.js';
import observe from '../helpers/selector-observer.js';

async function loadDeferred(jumpList: Element): Promise<void> {
	// This event will trigger the loading, but if run too early, GitHub might not have attached the listener yet, so we try multiple times.
	const retrier = setInterval(() => {
		jumpList.parentElement!.dispatchEvent(new MouseEvent('mouseover'));
	}, 100);
	await oneMutation(jumpList, {childList: true, subtree: true});
	clearInterval(retrier);
}

function highlightFilename(filename: HTMLAnchorElement, sourceIcon: SVGSVGElement): void {
	const icon = sourceIcon.cloneNode(true);
	const action = icon.getAttribute('title')!;
	if (action === 'added') {
		icon.classList.add('color-fg-success');
	} else if (action === 'removed') {
		icon.classList.add('color-fg-danger');
	} else {
		return;
	}

	icon.classList.remove('select-menu-item-icon');
	filename.parentElement!.append(
		<span className="tooltipped tooltipped-s ml-1" aria-label={'File ' + action}>
			{icon}
		</span>,
	);
}

async function init(signal: AbortSignal): Promise<void> {
	const fileList = await elementReady([
		'.toc-select details-menu[src*="/show_toc?"]', // `isPR`
		'.toc-diff-stats + .content', // `isSingleCommit` and `isCompare`
	].join(','));

	if (pageDetect.isPR()) {
		await loadDeferred(fileList!);
	}

	// Link--primary excludes CODEOWNERS icon #5565
	observe('.file-info a.Link--primary', filename => {
		const sourceIcon = pageDetect.isPR()
			? select(`[href="${filename.hash}"] svg`, fileList)!
			: select(`svg + [href="${filename.hash}"]`, fileList)?.previousElementSibling as SVGSVGElement;

		highlightFilename(filename, sourceIcon);
	}, {signal});
}

void features.add(import.meta.url, {
	include: [
		pageDetect.isPRFiles,
		pageDetect.isCommit,
	],
	exclude: [
		pageDetect.isPRFile404,
		pageDetect.isPRCommit404,
	],
	init,
});

/*

## Test URLs

PR: https://github.com/refined-github/refined-github/pull/5631/files
PR with CODEOWNERS: https://github.com/dotnet/winforms/pull/6028/files

*/