// Replace link-like texts with link nodes. // // Currently restricted by `md.validateLink()` to http/https/ftp // 'use strict'; var arrayReplaceAt = require('../common/utils').arrayReplaceAt; var normalizeLink = require('../common/utils').normalizeLink; var Token = require('../token'); function isLinkOpen(str) { return /^\s]/i.test(str); } function isLinkClose(str) { return /^<\/a\s*>/i.test(str); } module.exports = function linkify(state) { var i, j, l, tokens, token, currentToken, nodes, ln, text, pos, lastPos, level, htmlLinkLevel, blockTokens = state.tokens, 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; // We scan from the end, to keep position when new tags added. // Use reversed logic in links start/end match for (i = tokens.length - 1; i >= 0; i--) { currentToken = tokens[i]; // Skip content of markdown links if (currentToken.type === 'link_close') { i--; while (tokens[i].level !== currentToken.level && tokens[i].type !== 'link_open') { i--; } continue; } // Skip content of html tag links if (currentToken.type === 'html_inline') { if (isLinkOpen(currentToken.content) && htmlLinkLevel > 0) { htmlLinkLevel--; } if (isLinkClose(currentToken.content)) { htmlLinkLevel++; } } if (htmlLinkLevel > 0) { continue; } if (currentToken.type === 'text' && state.md.linkify.test(currentToken.content)) { text = currentToken.content; links = state.md.linkify.match(text); // Now split string to nodes nodes = []; level = currentToken.level; lastPos = 0; for (ln = 0; ln < links.length; ln++) { if (!state.md.validateLink(links[ln].url)) { continue; } pos = links[ln].index; if (pos > lastPos) { token = new Token('text', '', 0); token.content = text.slice(lastPos, pos); token.level = level; nodes.push(token); } token = new Token('link_open', 'a', 1); token.attrs = [ [ 'href', normalizeLink(links[ln].url) ] ]; token.level = level++; nodes.push(token); token = new Token('text', '', 0); token.content = links[ln].text; token.level = level; nodes.push(token); token = new Token('link_close', 'a', -1); token.level = --level; nodes.push(token); lastPos = links[ln].lastIndex; } if (lastPos < text.length) { token = new Token('text', '', 0); token.content = text.slice(lastPos); token.level = level; nodes.push(token); } // replace current node blockTokens[j].children = tokens = arrayReplaceAt(tokens, i, nodes); } } } };