2016-03-15 03:30:54 +08:00
|
|
|
import {RegExpUtils, DOMUtils} from 'nylas-exports';
|
|
|
|
|
2016-04-27 10:03:43 +08:00
|
|
|
function _matchesAnyRegexp(text, regexps) {
|
|
|
|
for (const excludeRegexp of regexps) {
|
|
|
|
if (excludeRegexp.test(text)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-03-15 03:30:54 +08:00
|
|
|
function _runOnTextNode(node, matchers) {
|
|
|
|
if (node.parentElement) {
|
|
|
|
const withinScript = node.parentElement.tagName === "SCRIPT";
|
|
|
|
const withinStyle = node.parentElement.tagName === "STYLE";
|
|
|
|
const withinA = (node.parentElement.closest('a') !== null);
|
|
|
|
if (withinScript || withinA || withinStyle) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (node.textContent.trim().length < 4) {
|
|
|
|
return;
|
|
|
|
}
|
2016-04-27 10:03:43 +08:00
|
|
|
|
|
|
|
let longest = null;
|
|
|
|
let longestLength = null;
|
|
|
|
for (const [prefix, regex, options = {}] of matchers) {
|
2016-03-15 03:30:54 +08:00
|
|
|
regex.lastIndex = 0;
|
|
|
|
const match = regex.exec(node.textContent);
|
|
|
|
if (match !== null) {
|
2016-04-27 10:03:43 +08:00
|
|
|
if (options.exclude && _matchesAnyRegexp(match[0], options.exclude)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (match[0].length > longestLength) {
|
|
|
|
longest = [prefix, match];
|
|
|
|
longestLength = match[0].length;
|
|
|
|
}
|
2016-03-15 03:30:54 +08:00
|
|
|
}
|
|
|
|
}
|
2016-04-27 10:03:43 +08:00
|
|
|
|
|
|
|
if (longest) {
|
|
|
|
const [prefix, match] = longest;
|
|
|
|
const href = `${prefix}${match[0]}`;
|
|
|
|
const range = document.createRange();
|
|
|
|
range.setStart(node, match.index);
|
|
|
|
range.setEnd(node, match.index + match[0].length);
|
|
|
|
const aTag = DOMUtils.wrap(range, 'A');
|
|
|
|
aTag.href = href;
|
|
|
|
aTag.title = href;
|
|
|
|
return;
|
|
|
|
}
|
2016-03-15 03:30:54 +08:00
|
|
|
}
|
|
|
|
|
2016-03-24 10:02:51 +08:00
|
|
|
export function autolink(doc, {async} = {}) {
|
2016-03-15 03:30:54 +08:00
|
|
|
// Traverse the new DOM tree and make things that look like links clickable,
|
|
|
|
// and ensure anything with an href has a title attribute.
|
|
|
|
const textWalker = document.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT);
|
|
|
|
const matchers = [
|
2016-04-27 10:03:43 +08:00
|
|
|
['mailto:', RegExpUtils.emailRegex(), {
|
|
|
|
// Technically, gmail.com/bengotow@gmail.com is an email address. After
|
|
|
|
// matching, manully exclude any email that follows the .*[/?].*@ pattern.
|
2016-11-16 02:20:39 +08:00
|
|
|
exclude: [/\..*[/|?].*@/],
|
2016-04-27 10:03:43 +08:00
|
|
|
}],
|
2016-03-15 03:30:54 +08:00
|
|
|
['tel:', RegExpUtils.phoneRegex()],
|
2016-11-17 05:35:12 +08:00
|
|
|
['', RegExpUtils.nylasCommandRegex()],
|
2016-03-15 03:30:54 +08:00
|
|
|
['', RegExpUtils.urlRegex({matchEntireString: false})],
|
|
|
|
];
|
|
|
|
|
2016-03-24 10:02:51 +08:00
|
|
|
if (async) {
|
|
|
|
const fn = (deadline) => {
|
|
|
|
while (textWalker.nextNode()) {
|
|
|
|
_runOnTextNode(textWalker.currentNode, matchers);
|
|
|
|
if (deadline.timeRemaining() <= 0) {
|
|
|
|
window.requestIdleCallback(fn, {timeout: 500});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
window.requestIdleCallback(fn, {timeout: 500});
|
|
|
|
} else {
|
|
|
|
while (textWalker.nextNode()) {
|
|
|
|
_runOnTextNode(textWalker.currentNode, matchers);
|
|
|
|
}
|
2016-03-15 03:30:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Traverse the new DOM tree and make sure everything with an href has a title.
|
|
|
|
const aTagWalker = document.createTreeWalker(doc.body, NodeFilter.SHOW_ELEMENT, {
|
|
|
|
acceptNode: (node) =>
|
2016-05-07 07:23:48 +08:00
|
|
|
(node.href ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP)
|
2016-03-15 03:30:54 +08:00
|
|
|
,
|
|
|
|
});
|
|
|
|
while (aTagWalker.nextNode()) {
|
2016-04-27 08:01:15 +08:00
|
|
|
aTagWalker.currentNode.title = aTagWalker.currentNode.getAttribute('href');
|
2016-03-15 03:30:54 +08:00
|
|
|
}
|
|
|
|
}
|