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