|
|
@ -5,11 +5,8 @@ |
|
|
|
'use strict'; |
|
|
|
|
|
|
|
|
|
|
|
var Autolinker = require('autolinker'); |
|
|
|
var arrayReplaceAt = require('../common/utils').arrayReplaceAt; |
|
|
|
|
|
|
|
|
|
|
|
var LINK_SCAN_RE = /www|@|\:\/\//; |
|
|
|
var normalizeLink = require('../common/utils').normalizeLink; |
|
|
|
|
|
|
|
|
|
|
|
function isLinkOpen(str) { |
|
|
@ -19,54 +16,17 @@ function isLinkClose(str) { |
|
|
|
return /^<\/a\s*>/i.test(str); |
|
|
|
} |
|
|
|
|
|
|
|
// Stupid fabric to avoid singletons, for thread safety.
|
|
|
|
// Required for engines like Nashorn.
|
|
|
|
//
|
|
|
|
function createLinkifier() { |
|
|
|
var links = []; |
|
|
|
var autolinker = new Autolinker({ |
|
|
|
stripPrefix: false, |
|
|
|
url: true, |
|
|
|
email: true, |
|
|
|
twitter: false, |
|
|
|
replaceFn: function (__, match) { |
|
|
|
// Only collect matched strings but don't change anything.
|
|
|
|
switch (match.getType()) { |
|
|
|
/*eslint default-case:0*/ |
|
|
|
case 'url': |
|
|
|
links.push({ |
|
|
|
text: match.matchedText, |
|
|
|
url: match.getUrl() |
|
|
|
}); |
|
|
|
break; |
|
|
|
case 'email': |
|
|
|
links.push({ |
|
|
|
text: match.matchedText, |
|
|
|
// normalize email protocol
|
|
|
|
url: 'mailto:' + match.getEmail().replace(/^mailto:/i, '') |
|
|
|
}); |
|
|
|
break; |
|
|
|
} |
|
|
|
return false; |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
return { |
|
|
|
links: links, |
|
|
|
autolinker: autolinker |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
module.exports = function linkify(state) { |
|
|
|
var i, j, l, tokens, token, text, nodes, ln, pos, level, htmlLinkLevel, |
|
|
|
var i, j, l, tokens, token, nodes, ln, text, pos, lastPos, level, htmlLinkLevel, |
|
|
|
blockTokens = state.tokens, |
|
|
|
linkifier = null, links, autolinker; |
|
|
|
links; |
|
|
|
|
|
|
|
if (!state.md.options.linkify) { return; } |
|
|
|
|
|
|
|
for (j = 0, l = blockTokens.length; j < l; j++) { |
|
|
|
if (blockTokens[j].type !== 'inline') { continue; } |
|
|
|
|
|
|
|
tokens = blockTokens[j].children; |
|
|
|
|
|
|
|
htmlLinkLevel = 0; |
|
|
@ -96,42 +56,33 @@ module.exports = function linkify(state) { |
|
|
|
} |
|
|
|
if (htmlLinkLevel > 0) { continue; } |
|
|
|
|
|
|
|
if (token.type === 'text' && LINK_SCAN_RE.test(token.content)) { |
|
|
|
|
|
|
|
// Init linkifier in lazy manner, only if required.
|
|
|
|
if (!linkifier) { |
|
|
|
linkifier = createLinkifier(); |
|
|
|
links = linkifier.links; |
|
|
|
autolinker = linkifier.autolinker; |
|
|
|
} |
|
|
|
if (token.type === 'text' && state.md.linkify.test(token.content)) { |
|
|
|
|
|
|
|
text = token.content; |
|
|
|
links.length = 0; |
|
|
|
autolinker.link(text); |
|
|
|
|
|
|
|
if (!links.length) { continue; } |
|
|
|
links = state.md.linkify.match(text); |
|
|
|
|
|
|
|
// Now split string to nodes
|
|
|
|
nodes = []; |
|
|
|
level = token.level; |
|
|
|
lastPos = 0; |
|
|
|
|
|
|
|
for (ln = 0; ln < links.length; ln++) { |
|
|
|
|
|
|
|
if (!state.md.inline.validateLink(links[ln].url)) { continue; } |
|
|
|
|
|
|
|
pos = text.indexOf(links[ln].text); |
|
|
|
pos = links[ln].index; |
|
|
|
|
|
|
|
if (pos) { |
|
|
|
if (pos > lastPos) { |
|
|
|
level = level; |
|
|
|
nodes.push({ |
|
|
|
type: 'text', |
|
|
|
content: text.slice(0, pos), |
|
|
|
content: text.slice(lastPos, pos), |
|
|
|
level: level |
|
|
|
}); |
|
|
|
} |
|
|
|
nodes.push({ |
|
|
|
type: 'link_open', |
|
|
|
href: links[ln].url, |
|
|
|
href: normalizeLink(links[ln].url), |
|
|
|
target: '', |
|
|
|
title: '', |
|
|
|
level: level++ |
|
|
@ -145,12 +96,12 @@ module.exports = function linkify(state) { |
|
|
|
type: 'link_close', |
|
|
|
level: --level |
|
|
|
}); |
|
|
|
text = text.slice(pos + links[ln].text.length); |
|
|
|
lastPos = links[ln].lastIndex; |
|
|
|
} |
|
|
|
if (text.length) { |
|
|
|
if (lastPos < text.length) { |
|
|
|
nodes.push({ |
|
|
|
type: 'text', |
|
|
|
content: text, |
|
|
|
content: text.slice(lastPos), |
|
|
|
level: level |
|
|
|
}); |
|
|
|
} |
|
|
|