summaryrefslogtreecommitdiff
path: root/source/features/highlight-deleted-and-added-files-in-diffs.tsx
blob: 8e18e501a58b265226353ff15b9b51b376e7114a (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
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 '.';
import {onDiffFileLoad} from '../github-events/on-fragment-load';
import observe from '../helpers/selector-observer';

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,
	],
	awaitDomReady: false,
	deduplicate: 'has-rgh-inner',
	init,
}, {
	include: [
		pageDetect.isCompare,
	],
	exclude: [
		() => select.exists('.blankslate:not(.blankslate-large)'),
	],
	additionalListeners: [
		onDiffFileLoad,
	],
	onlyAdditionalListeners: true,
	deduplicate: false,
	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

*/