|
1 |
| -/** |
2 |
| - * @typedef {import('mdast').Link} Link |
3 |
| - * @typedef {import('mdast-util-from-markdown').CompileContext} CompileContext |
4 |
| - * @typedef {import('mdast-util-from-markdown').Extension} FromMarkdownExtension |
5 |
| - * @typedef {import('mdast-util-from-markdown').Transform} FromMarkdownTransform |
6 |
| - * @typedef {import('mdast-util-from-markdown').Handle} FromMarkdownHandle |
7 |
| - * @typedef {import('mdast-util-to-markdown/lib/types.js').Options} ToMarkdownExtension |
8 |
| - * @typedef {import('mdast-util-find-and-replace').ReplaceFunction} ReplaceFunction |
9 |
| - * @typedef {import('mdast-util-find-and-replace').RegExpMatchObject} RegExpMatchObject |
10 |
| - * @typedef {import('mdast').PhrasingContent} PhrasingContent |
11 |
| - */ |
12 |
| - |
13 |
| -import {ccount} from 'ccount' |
14 |
| -import {findAndReplace} from 'mdast-util-find-and-replace' |
15 |
| -import {unicodePunctuation, unicodeWhitespace} from 'micromark-util-character' |
16 |
| - |
17 |
| -const inConstruct = 'phrasing' |
18 |
| -const notInConstruct = ['autolink', 'link', 'image', 'label'] |
19 |
| - |
20 |
| -/** @type {FromMarkdownExtension} */ |
21 |
| -export const gfmAutolinkLiteralFromMarkdown = { |
22 |
| - transforms: [transformGfmAutolinkLiterals], |
23 |
| - enter: { |
24 |
| - literalAutolink: enterLiteralAutolink, |
25 |
| - literalAutolinkEmail: enterLiteralAutolinkValue, |
26 |
| - literalAutolinkHttp: enterLiteralAutolinkValue, |
27 |
| - literalAutolinkWww: enterLiteralAutolinkValue |
28 |
| - }, |
29 |
| - exit: { |
30 |
| - literalAutolink: exitLiteralAutolink, |
31 |
| - literalAutolinkEmail: exitLiteralAutolinkEmail, |
32 |
| - literalAutolinkHttp: exitLiteralAutolinkHttp, |
33 |
| - literalAutolinkWww: exitLiteralAutolinkWww |
34 |
| - } |
35 |
| -} |
36 |
| - |
37 |
| -/** @type {ToMarkdownExtension} */ |
38 |
| -export const gfmAutolinkLiteralToMarkdown = { |
39 |
| - unsafe: [ |
40 |
| - { |
41 |
| - character: '@', |
42 |
| - before: '[+\\-.\\w]', |
43 |
| - after: '[\\-.\\w]', |
44 |
| - inConstruct, |
45 |
| - // @ts-expect-error: to do: use map. |
46 |
| - notInConstruct |
47 |
| - }, |
48 |
| - { |
49 |
| - character: '.', |
50 |
| - before: '[Ww]', |
51 |
| - after: '[\\-.\\w]', |
52 |
| - inConstruct, |
53 |
| - // @ts-expect-error: to do: use map. |
54 |
| - notInConstruct |
55 |
| - }, |
56 |
| - { |
57 |
| - character: ':', |
58 |
| - before: '[ps]', |
59 |
| - after: '\\/', |
60 |
| - inConstruct, |
61 |
| - // @ts-expect-error: to do: use map. |
62 |
| - notInConstruct |
63 |
| - } |
64 |
| - ] |
65 |
| -} |
66 |
| - |
67 |
| -/** |
68 |
| - * @this {CompileContext} |
69 |
| - * @type {FromMarkdownHandle} |
70 |
| - */ |
71 |
| -function enterLiteralAutolink(token) { |
72 |
| - this.enter({type: 'link', title: null, url: '', children: []}, token) |
73 |
| -} |
74 |
| - |
75 |
| -/** |
76 |
| - * @this {CompileContext} |
77 |
| - * @type {FromMarkdownHandle} |
78 |
| - */ |
79 |
| -function enterLiteralAutolinkValue(token) { |
80 |
| - this.config.enter.autolinkProtocol.call(this, token) |
81 |
| -} |
82 |
| - |
83 |
| -/** |
84 |
| - * @this {CompileContext} |
85 |
| - * @type {FromMarkdownHandle} |
86 |
| - */ |
87 |
| -function exitLiteralAutolinkHttp(token) { |
88 |
| - this.config.exit.autolinkProtocol.call(this, token) |
89 |
| -} |
90 |
| - |
91 |
| -/** |
92 |
| - * @this {CompileContext} |
93 |
| - * @type {FromMarkdownHandle} |
94 |
| - */ |
95 |
| -function exitLiteralAutolinkWww(token) { |
96 |
| - this.config.exit.data.call(this, token) |
97 |
| - const node = /** @type {Link} */ (this.stack[this.stack.length - 1]) |
98 |
| - node.url = 'http://' + this.sliceSerialize(token) |
99 |
| -} |
100 |
| - |
101 |
| -/** |
102 |
| - * @this {CompileContext} |
103 |
| - * @type {FromMarkdownHandle} |
104 |
| - */ |
105 |
| -function exitLiteralAutolinkEmail(token) { |
106 |
| - this.config.exit.autolinkEmail.call(this, token) |
107 |
| -} |
108 |
| - |
109 |
| -/** |
110 |
| - * @this {CompileContext} |
111 |
| - * @type {FromMarkdownHandle} |
112 |
| - */ |
113 |
| -function exitLiteralAutolink(token) { |
114 |
| - this.exit(token) |
115 |
| -} |
116 |
| - |
117 |
| -/** @type {FromMarkdownTransform} */ |
118 |
| -function transformGfmAutolinkLiterals(tree) { |
119 |
| - findAndReplace( |
120 |
| - tree, |
121 |
| - [ |
122 |
| - [/(https?:\/\/|www(?=\.))([-.\w]+)([^ \t\r\n]*)/gi, findUrl], |
123 |
| - [/([-.\w+]+)@([-\w]+(?:\.[-\w]+)+)/g, findEmail] |
124 |
| - ], |
125 |
| - {ignore: ['link', 'linkReference']} |
126 |
| - ) |
127 |
| -} |
128 |
| - |
129 |
| -/** |
130 |
| - * @type {ReplaceFunction} |
131 |
| - * @param {string} _ |
132 |
| - * @param {string} protocol |
133 |
| - * @param {string} domain |
134 |
| - * @param {string} path |
135 |
| - * @param {RegExpMatchObject} match |
136 |
| - */ |
137 |
| -// eslint-disable-next-line max-params |
138 |
| -function findUrl(_, protocol, domain, path, match) { |
139 |
| - let prefix = '' |
140 |
| - |
141 |
| - // Not an expected previous character. |
142 |
| - if (!previous(match)) { |
143 |
| - return false |
144 |
| - } |
145 |
| - |
146 |
| - // Treat `www` as part of the domain. |
147 |
| - if (/^w/i.test(protocol)) { |
148 |
| - domain = protocol + domain |
149 |
| - protocol = '' |
150 |
| - prefix = 'http://' |
151 |
| - } |
152 |
| - |
153 |
| - if (!isCorrectDomain(domain)) { |
154 |
| - return false |
155 |
| - } |
156 |
| - |
157 |
| - const parts = splitUrl(domain + path) |
158 |
| - |
159 |
| - if (!parts[0]) return false |
160 |
| - |
161 |
| - /** @type {PhrasingContent} */ |
162 |
| - const result = { |
163 |
| - type: 'link', |
164 |
| - title: null, |
165 |
| - url: prefix + protocol + parts[0], |
166 |
| - children: [{type: 'text', value: protocol + parts[0]}] |
167 |
| - } |
168 |
| - |
169 |
| - if (parts[1]) { |
170 |
| - return [result, {type: 'text', value: parts[1]}] |
171 |
| - } |
172 |
| - |
173 |
| - return result |
174 |
| -} |
175 |
| - |
176 |
| -/** |
177 |
| - * @type {ReplaceFunction} |
178 |
| - * @param {string} _ |
179 |
| - * @param {string} atext |
180 |
| - * @param {string} label |
181 |
| - * @param {RegExpMatchObject} match |
182 |
| - */ |
183 |
| -function findEmail(_, atext, label, match) { |
184 |
| - if ( |
185 |
| - // Not an expected previous character. |
186 |
| - !previous(match, true) || |
187 |
| - // Label ends in not allowed character. |
188 |
| - /[-\d_]$/.test(label) |
189 |
| - ) { |
190 |
| - return false |
191 |
| - } |
192 |
| - |
193 |
| - return { |
194 |
| - type: 'link', |
195 |
| - title: null, |
196 |
| - url: 'mailto:' + atext + '@' + label, |
197 |
| - children: [{type: 'text', value: atext + '@' + label}] |
198 |
| - } |
199 |
| -} |
200 |
| - |
201 |
| -/** |
202 |
| - * @param {string} domain |
203 |
| - * @returns {boolean} |
204 |
| - */ |
205 |
| -function isCorrectDomain(domain) { |
206 |
| - const parts = domain.split('.') |
207 |
| - |
208 |
| - if ( |
209 |
| - parts.length < 2 || |
210 |
| - (parts[parts.length - 1] && |
211 |
| - (/_/.test(parts[parts.length - 1]) || |
212 |
| - !/[a-zA-Z\d]/.test(parts[parts.length - 1]))) || |
213 |
| - (parts[parts.length - 2] && |
214 |
| - (/_/.test(parts[parts.length - 2]) || |
215 |
| - !/[a-zA-Z\d]/.test(parts[parts.length - 2]))) |
216 |
| - ) { |
217 |
| - return false |
218 |
| - } |
219 |
| - |
220 |
| - return true |
221 |
| -} |
222 |
| - |
223 |
| -/** |
224 |
| - * @param {string} url |
225 |
| - * @returns {[string, string|undefined]} |
226 |
| - */ |
227 |
| -function splitUrl(url) { |
228 |
| - const trailExec = /[!"&'),.:;<>?\]}]+$/.exec(url) |
229 |
| - /** @type {number} */ |
230 |
| - let closingParenIndex |
231 |
| - /** @type {number} */ |
232 |
| - let openingParens |
233 |
| - /** @type {number} */ |
234 |
| - let closingParens |
235 |
| - /** @type {string|undefined} */ |
236 |
| - let trail |
237 |
| - |
238 |
| - if (trailExec) { |
239 |
| - url = url.slice(0, trailExec.index) |
240 |
| - trail = trailExec[0] |
241 |
| - closingParenIndex = trail.indexOf(')') |
242 |
| - openingParens = ccount(url, '(') |
243 |
| - closingParens = ccount(url, ')') |
244 |
| - |
245 |
| - while (closingParenIndex !== -1 && openingParens > closingParens) { |
246 |
| - url += trail.slice(0, closingParenIndex + 1) |
247 |
| - trail = trail.slice(closingParenIndex + 1) |
248 |
| - closingParenIndex = trail.indexOf(')') |
249 |
| - closingParens++ |
250 |
| - } |
251 |
| - } |
252 |
| - |
253 |
| - return [url, trail] |
254 |
| -} |
255 |
| - |
256 |
| -/** |
257 |
| - * @param {RegExpMatchObject} match |
258 |
| - * @param {boolean} [email=false] |
259 |
| - * @returns {boolean} |
260 |
| - */ |
261 |
| -function previous(match, email) { |
262 |
| - const code = match.input.charCodeAt(match.index - 1) |
263 |
| - |
264 |
| - return ( |
265 |
| - (match.index === 0 || |
266 |
| - unicodeWhitespace(code) || |
267 |
| - unicodePunctuation(code)) && |
268 |
| - (!email || code !== 47) |
269 |
| - ) |
270 |
| -} |
| 1 | +export { |
| 2 | + gfmAutolinkLiteralFromMarkdown, |
| 3 | + gfmAutolinkLiteralToMarkdown |
| 4 | +} from './lib/index.js' |
0 commit comments