[d24f17c] | 1 | 'use strict';
|
---|
| 2 |
|
---|
| 3 | Object.defineProperty(exports, '__esModule', { value: true });
|
---|
| 4 |
|
---|
| 5 | function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
|
---|
| 6 |
|
---|
| 7 | var Autolinker = _interopDefault(require('autolinker'));
|
---|
| 8 |
|
---|
| 9 | // Autoconvert URL-like texts to links
|
---|
| 10 |
|
---|
| 11 |
|
---|
| 12 | var LINK_SCAN_RE = /www|@|\:\/\//;
|
---|
| 13 |
|
---|
| 14 |
|
---|
| 15 | function isLinkOpen(str) {
|
---|
| 16 | return /^<a[>\s]/i.test(str);
|
---|
| 17 | }
|
---|
| 18 | function isLinkClose(str) {
|
---|
| 19 | return /^<\/a\s*>/i.test(str);
|
---|
| 20 | }
|
---|
| 21 |
|
---|
| 22 | // Stupid fabric to avoid singletons, for thread safety.
|
---|
| 23 | // Required for engines like Nashorn.
|
---|
| 24 | //
|
---|
| 25 | function createLinkifier() {
|
---|
| 26 | var links = [];
|
---|
| 27 | var autolinker = new Autolinker({
|
---|
| 28 | stripPrefix: false,
|
---|
| 29 | url: true,
|
---|
| 30 | email: true,
|
---|
| 31 | replaceFn: function (match) {
|
---|
| 32 | // Only collect matched strings but don't change anything.
|
---|
| 33 | switch (match.getType()) {
|
---|
| 34 | /*eslint default-case:0*/
|
---|
| 35 | case 'url':
|
---|
| 36 | links.push({
|
---|
| 37 | text: match.matchedText,
|
---|
| 38 | url: match.getUrl()
|
---|
| 39 | });
|
---|
| 40 | break;
|
---|
| 41 | case 'email':
|
---|
| 42 | links.push({
|
---|
| 43 | text: match.matchedText,
|
---|
| 44 | // normalize email protocol
|
---|
| 45 | url: 'mailto:' + match.getEmail().replace(/^mailto:/i, '')
|
---|
| 46 | });
|
---|
| 47 | break;
|
---|
| 48 | }
|
---|
| 49 | return false;
|
---|
| 50 | }
|
---|
| 51 | });
|
---|
| 52 |
|
---|
| 53 | return {
|
---|
| 54 | links: links,
|
---|
| 55 | autolinker: autolinker
|
---|
| 56 | };
|
---|
| 57 | }
|
---|
| 58 |
|
---|
| 59 |
|
---|
| 60 | function parseTokens(state) {
|
---|
| 61 | var i, j, l, tokens, token, text, nodes, ln, pos, level, htmlLinkLevel,
|
---|
| 62 | blockTokens = state.tokens,
|
---|
| 63 | linkifier = null, links, autolinker;
|
---|
| 64 |
|
---|
| 65 | for (j = 0, l = blockTokens.length; j < l; j++) {
|
---|
| 66 | if (blockTokens[j].type !== 'inline') { continue; }
|
---|
| 67 | tokens = blockTokens[j].children;
|
---|
| 68 |
|
---|
| 69 | htmlLinkLevel = 0;
|
---|
| 70 |
|
---|
| 71 | // We scan from the end, to keep position when new tags added.
|
---|
| 72 | // Use reversed logic in links start/end match
|
---|
| 73 | for (i = tokens.length - 1; i >= 0; i--) {
|
---|
| 74 | token = tokens[i];
|
---|
| 75 |
|
---|
| 76 | // Skip content of markdown links
|
---|
| 77 | if (token.type === 'link_close') {
|
---|
| 78 | i--;
|
---|
| 79 | while (tokens[i].level !== token.level && tokens[i].type !== 'link_open') {
|
---|
| 80 | i--;
|
---|
| 81 | }
|
---|
| 82 | continue;
|
---|
| 83 | }
|
---|
| 84 |
|
---|
| 85 | // Skip content of html tag links
|
---|
| 86 | if (token.type === 'htmltag') {
|
---|
| 87 | if (isLinkOpen(token.content) && htmlLinkLevel > 0) {
|
---|
| 88 | htmlLinkLevel--;
|
---|
| 89 | }
|
---|
| 90 | if (isLinkClose(token.content)) {
|
---|
| 91 | htmlLinkLevel++;
|
---|
| 92 | }
|
---|
| 93 | }
|
---|
| 94 | if (htmlLinkLevel > 0) { continue; }
|
---|
| 95 |
|
---|
| 96 | if (token.type === 'text' && LINK_SCAN_RE.test(token.content)) {
|
---|
| 97 |
|
---|
| 98 | // Init linkifier in lazy manner, only if required.
|
---|
| 99 | if (!linkifier) {
|
---|
| 100 | linkifier = createLinkifier();
|
---|
| 101 | links = linkifier.links;
|
---|
| 102 | autolinker = linkifier.autolinker;
|
---|
| 103 | }
|
---|
| 104 |
|
---|
| 105 | text = token.content;
|
---|
| 106 | links.length = 0;
|
---|
| 107 | autolinker.link(text);
|
---|
| 108 |
|
---|
| 109 | if (!links.length) { continue; }
|
---|
| 110 |
|
---|
| 111 | // Now split string to nodes
|
---|
| 112 | nodes = [];
|
---|
| 113 | level = token.level;
|
---|
| 114 |
|
---|
| 115 | for (ln = 0; ln < links.length; ln++) {
|
---|
| 116 |
|
---|
| 117 | if (!state.inline.validateLink(links[ln].url)) { continue; }
|
---|
| 118 |
|
---|
| 119 | pos = text.indexOf(links[ln].text);
|
---|
| 120 |
|
---|
| 121 | if (pos) {
|
---|
| 122 | nodes.push({
|
---|
| 123 | type: 'text',
|
---|
| 124 | content: text.slice(0, pos),
|
---|
| 125 | level: level
|
---|
| 126 | });
|
---|
| 127 | }
|
---|
| 128 | nodes.push({
|
---|
| 129 | type: 'link_open',
|
---|
| 130 | href: links[ln].url,
|
---|
| 131 | title: '',
|
---|
| 132 | level: level++
|
---|
| 133 | });
|
---|
| 134 | nodes.push({
|
---|
| 135 | type: 'text',
|
---|
| 136 | content: links[ln].text,
|
---|
| 137 | level: level
|
---|
| 138 | });
|
---|
| 139 | nodes.push({
|
---|
| 140 | type: 'link_close',
|
---|
| 141 | level: --level
|
---|
| 142 | });
|
---|
| 143 | text = text.slice(pos + links[ln].text.length);
|
---|
| 144 | }
|
---|
| 145 | if (text.length) {
|
---|
| 146 | nodes.push({
|
---|
| 147 | type: 'text',
|
---|
| 148 | content: text,
|
---|
| 149 | level: level
|
---|
| 150 | });
|
---|
| 151 | }
|
---|
| 152 |
|
---|
| 153 | // replace current node
|
---|
| 154 | blockTokens[j].children = tokens = [].concat(tokens.slice(0, i), nodes, tokens.slice(i + 1));
|
---|
| 155 | }
|
---|
| 156 | }
|
---|
| 157 | }
|
---|
| 158 | }
|
---|
| 159 | function linkify(md) {
|
---|
| 160 | md.core.ruler.push('linkify', parseTokens);
|
---|
| 161 | }
|
---|
| 162 |
|
---|
| 163 | exports.linkify = linkify;
|
---|