summaryrefslogtreecommitdiff
path: root/source/features/linkify-urls-in-code.tsx
blob: 066bfef334e511835cfcdc2f055ea2bb9b10f6bb (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
import select from 'select-dom';
import linkifyUrls from 'linkify-urls';
import zipTextNodes from 'zip-text-nodes';
import linkifyIssues from 'linkify-issues';
import features from '../libs/features';
import {getOwnerAndRepo} from '../libs/utils';

export function linkifyIssuesInDom(element: Element): void {
	const linkified = linkifyIssues(element.textContent!, options);
	if (linkified.children.length === 0) { // Children are <a>
		return;
	}

	// Enable native issue title fetch
	for (const link of (linkified.children as HTMLCollectionOf<HTMLAnchorElement>)) {
		const issue = link.href.split('/').pop();
		link.setAttribute('class', 'issue-link js-issue-link tooltipped tooltipped-ne');
		link.setAttribute('data-error-text', 'Failed to load issue title');
		link.setAttribute('data-permission-text', 'Issue title is private');
		link.setAttribute('data-url', link.href);
		link.setAttribute('data-id', `rgh-issue-${issue}`);
	}

	zipTextNodes(element, linkified);
}

// Shared class necessary to avoid also shortening the links
export const linkifiedURLClass = 'rgh-linkified-code';

// If we are not in a repo, relative issue references won't make sense
// but `user`/`repo` need to be set to avoid breaking errors in `linkify-issues`
// https://github.com/sindresorhus/refined-github/issues/1305
const currentRepo = getOwnerAndRepo();
const options = {
	user: currentRepo.ownerName || '/',
	repo: currentRepo.repoName || '/',
	type: 'dom',
	baseUrl: '',
	attributes: {
		rel: 'noreferrer noopener',
		class: linkifiedURLClass // Necessary to avoid also shortening the links
	}
};

function init(): false | void {
	const wrappers = select.all(`
		.js-blob-wrapper:not(.${linkifiedURLClass}),
		.blob-wrapper:not(.${linkifiedURLClass}),
		.comment-body:not(.${linkifiedURLClass})
	`);

	if (wrappers.length === 0) {
		return false;
	}

	// Linkify full URLs
	// `.blob-code-inner` in diffs
	// `pre` in GitHub comments
	for (const element of select.all('.blob-code-inner, pre', wrappers)) {
		if (element.textContent!.length < 15) { // Must be long enough for a URL
			continue;
		}

		const linkified = linkifyUrls(element.textContent!, options);
		if (linkified.children.length === 0) { // Children are <a>
			continue;
		}

		zipTextNodes(element, linkified);
	}

	// Linkify issue refs in comments
	for (const element of select.all('span.pl-c', wrappers)) {
		linkifyIssuesInDom(element);
	}

	// Mark code block as touched
	for (const el of wrappers) {
		el.classList.add(linkifiedURLClass);
	}
}

features.add({
	id: __featureName__,
	description: 'Linkifies URLs in code.',
	screenshot: 'https://cloud.githubusercontent.com/assets/170270/25370217/61718820-29b3-11e7-89c5-2959eaf8cac8.png',
	include: [
		features.hasCode
	],
	load: features.onAjaxedPages,
	init
});