Skip to content
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
"element-ready": "^2.0.0",
"escape-goat": "^1.1.0",
"github-injection": "^0.3.0",
"linkify-issues": "^1.1.0",
"linkify-urls": "^1.0.2",
"select-dom": "^4.1.0",
"to-semver": "^1.1.0",
"webext-dynamic-content-scripts": "^2.0.1"
Expand Down
18 changes: 5 additions & 13 deletions src/content.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import elementReady from 'element-ready';
import gitHubInjection from 'github-injection';
import toSemver from 'to-semver';
import {escape as escapeHtml} from 'escape-goat';
import linkifyIssues from 'linkify-issues';
import select from 'select-dom';
import domLoaded from 'dom-loaded';
import $ from './libs/vendor/jquery.slim.min';
Expand All @@ -15,12 +15,12 @@ import addReactionParticipants from './libs/reactions-avatars';
import showRealNames from './libs/show-names';
import filePathCopyBtnListner from './libs/copy-file-path';
import addFileCopyButton from './libs/copy-file';
import {linkifyCode} from './libs/linkify-urls-in-code';
import {issueRegex, linkifyIssueRef} from './libs/util';
import linkifyCode, {editTextNodes} from './libs/linkify-urls-in-code';
import * as icons from './libs/icons';
import * as pageDetect from './libs/page-detect';

const repoUrl = pageDetect.getRepoURL();

const getUsername = () => select('meta[name="user-login"]').getAttribute('content');

function getCanonicalBranchFromRef($element) {
Expand Down Expand Up @@ -245,15 +245,7 @@ function addDeleteForkLink() {
}

function linkifyIssuesInTitles() {
const title = select('.js-issue-title');
const titleText = escapeHtml(title.textContent);

if (issueRegex.test(titleText)) {
title.innerHTML = titleText.replace(
new RegExp(issueRegex.source, 'g'),
match => linkifyIssueRef(repoUrl, match, '')
);
}
editTextNodes(linkifyIssues, select('.js-issue-title'));
}

function addPatchDiffLinks() {
Expand Down Expand Up @@ -635,7 +627,7 @@ function init() {
}

if (pageDetect.hasCode()) {
linkifyCode(repoUrl);
linkifyCode();
}

if (pageDetect.isRepoSettings()) {
Expand Down
2 changes: 2 additions & 0 deletions src/libs/domify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Get DOM node from HTML
export default html => document.createRange().createContextualFragment(html);
12 changes: 12 additions & 0 deletions src/libs/get-text-nodes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default el => {
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
const next = () => {
const value = walker.nextNode();
return {
value,
done: !value
};
};
walker[Symbol.iterator] = () => ({next});
return walker;
};
Copy link
Member

@sindresorhus sindresorhus Jun 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be a useful npm package. Hint hint ;) Shouldn't block merging this PR though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about t but I'm not good at testing browser packages 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's annoying, but easier if JSDom supports the tree-walker API. See: https://github.com/sindresorhus/is-blob/blob/927899acea40ce7bd31e2b3c4352248eba3a0609/test.js

79 changes: 50 additions & 29 deletions src/libs/linkify-urls-in-code.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,59 @@
import select from 'select-dom';
import {issueRegex, linkifyIssueRef} from './util';
import linkifyUrls from 'linkify-urls';
import linkifyIssues from 'linkify-issues';
import {getOwnerAndRepo} from './page-detect';
import getTextNodes from './get-text-nodes';
import html from './domify';

const URLRegex = /(http(s)?(:\/\/))(www\.)?[a-zA-Z0-9-_.]+(\.[a-zA-Z0-9]{2,})([-a-zA-Z0-9:%_+.~#?&//=]*)/g;
const linkifiedURLClass = 'rg-linkified-code';
const commonURLAttrs = `target="_blank" class="${linkifiedURLClass}"`;
const linkifiedURLClass = 'refined-github-linkified-code';
const {
ownerName,
repoName
} = getOwnerAndRepo();

const linkifyURL = url => `<a href="${url}" ${commonURLAttrs}>${url}</a>`;

export const hasIssue = text => issueRegex.test(text);
export const findURLs = text => text.match(URLRegex) || [];
const options = {
user: ownerName,
repo: repoName,
attrs: {
target: '_blank'
}
};

export const linkifyCode = repoPath => {
// Don't linkify any already linkified code
if (select.exists(`.${linkifiedURLClass}`)) {
export const editTextNodes = (fn, el) => {
if (!el) {
return;
}
const codeBlobs = document.querySelectorAll('.blob-code-inner');
const commentCodeBlobs = document.querySelectorAll('.blob-code-inner span.pl-c');

codeBlobs
.forEach(blob => {
for (let match of findURLs(blob.innerHTML)) {
// Remove < or > from beginning or end of an URL
match = match.replace(/(^&lt)|(&gt$)/, '');
blob.innerHTML = blob.innerHTML.replace(match, linkifyURL(match));
for (const textNode of getTextNodes(el)) {
if (textNode.textContent.length < 11) { // Shortest url: http://j.mp
continue;
}
});

commentCodeBlobs
.forEach(blob => {
const blobHTML = blob.innerHTML;
if (hasIssue(blobHTML)) {
const issueMatch = blobHTML.match(issueRegex)[0];
blob.innerHTML = blobHTML.replace(issueMatch, linkifyIssueRef(repoPath, issueMatch, commonURLAttrs));
const linkified = fn(textNode.textContent, options);
if (linkified !== textNode.textContent) {
textNode.replaceWith(html(linkified));
}
});
}
};

export default () => {
const untouchedCode = select.all(`.blob-wrapper:not(.${linkifiedURLClass})`);

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

// Linkify full URLs
for (const el of select.all('.blob-code-inner', untouchedCode)) {
editTextNodes(linkifyUrls, el);
}

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

// Mark code block as touched
for (const el of untouchedCode) {
el.classList.add(linkifiedURLClass);
}
};
9 changes: 0 additions & 9 deletions src/libs/util.js
Original file line number Diff line number Diff line change
@@ -1,9 +0,0 @@
export const issueRegex = /([a-zA-Z0-9-_.]+\/[a-zA-Z0-9-_.]+)?#[0-9]+/;
export const linkifyIssueRef = (repoPath, issue, attrs) => {
if (/\//.test(issue)) {
const issueParts = issue.split('#');
return `<a href="/${issueParts[0]}/issues/${issueParts[1]}" ${attrs}>${issue}</a>`;
}
return `<a href="/${repoPath}/issues/${issue.replace('#', '')}" ${attrs}>${issue}</a>`;
};

48 changes: 0 additions & 48 deletions test/linkify-urls-in-code.js

This file was deleted.