summaryrefslogtreecommitdiff
path: root/source/features/linkify-urls-in-code.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'source/features/linkify-urls-in-code.tsx')
-rw-r--r--source/features/linkify-urls-in-code.tsx82
1 files changed, 82 insertions, 0 deletions
diff --git a/source/features/linkify-urls-in-code.tsx b/source/features/linkify-urls-in-code.tsx
new file mode 100644
index 00000000..d41178ae
--- /dev/null
+++ b/source/features/linkify-urls-in-code.tsx
@@ -0,0 +1,82 @@
+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/page-detect';
+
+// 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
+});