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

// 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
	}
};

export const editTextNodes = (fn, el) => {
	for (const textNode of getTextNodes(el)) {
		if (fn === linkifyUrls && textNode.textContent.length < 11) { // Shortest url: http://j.mp
			continue;
		}

		const linkified = fn(textNode.textContent, options);
		if (linkified.children.length > 0) { // Children are <a>
			if (fn === linkifyIssues) {
				// Enable native issue title fetch
				for (const link of linkified.children) {
					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}`);
				}
			}

			textNode.replaceWith(linkified);
		}
	}
};

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

	// Don't linkify any already linkified code
	if (wrappers.length === 0) {
		return false;
	}

	// Linkify full URLs
	// `.blob-code-inner` in diffs
	// `pre` in GitHub comments
	for (const el of select.all('.blob-code-inner, pre', wrappers)) {
		editTextNodes(linkifyUrls, el);
	}

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

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

features.add({
	id: 'linkify-urls-in-code',
	load: features.onAjaxedPages,
	init
});