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;
|
---|