@depup/markdown-it 14.1.1-depup.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +31 -0
- package/bin/markdown-it.mjs +107 -0
- package/changes.json +10 -0
- package/dist/index.cjs.js +5547 -0
- package/dist/markdown-it.js +6969 -0
- package/dist/markdown-it.min.js +2 -0
- package/index.mjs +1 -0
- package/lib/common/html_blocks.mjs +67 -0
- package/lib/common/html_re.mjs +25 -0
- package/lib/common/utils.mjs +304 -0
- package/lib/helpers/index.mjs +11 -0
- package/lib/helpers/parse_link_destination.mjs +77 -0
- package/lib/helpers/parse_link_label.mjs +49 -0
- package/lib/helpers/parse_link_title.mjs +66 -0
- package/lib/index.mjs +565 -0
- package/lib/parser_block.mjs +134 -0
- package/lib/parser_core.mjs +62 -0
- package/lib/parser_inline.mjs +197 -0
- package/lib/presets/commonmark.mjs +88 -0
- package/lib/presets/default.mjs +47 -0
- package/lib/presets/zero.mjs +70 -0
- package/lib/renderer.mjs +322 -0
- package/lib/ruler.mjs +340 -0
- package/lib/rules_block/blockquote.mjs +209 -0
- package/lib/rules_block/code.mjs +30 -0
- package/lib/rules_block/fence.mjs +94 -0
- package/lib/rules_block/heading.mjs +51 -0
- package/lib/rules_block/hr.mjs +40 -0
- package/lib/rules_block/html_block.mjs +69 -0
- package/lib/rules_block/lheading.mjs +82 -0
- package/lib/rules_block/list.mjs +331 -0
- package/lib/rules_block/paragraph.mjs +46 -0
- package/lib/rules_block/reference.mjs +212 -0
- package/lib/rules_block/state_block.mjs +220 -0
- package/lib/rules_block/table.mjs +228 -0
- package/lib/rules_core/block.mjs +13 -0
- package/lib/rules_core/inline.mjs +11 -0
- package/lib/rules_core/linkify.mjs +134 -0
- package/lib/rules_core/normalize.mjs +17 -0
- package/lib/rules_core/replacements.mjs +101 -0
- package/lib/rules_core/smartquotes.mjs +193 -0
- package/lib/rules_core/state_core.mjs +17 -0
- package/lib/rules_core/text_join.mjs +43 -0
- package/lib/rules_inline/autolink.mjs +72 -0
- package/lib/rules_inline/backticks.mjs +60 -0
- package/lib/rules_inline/balance_pairs.mjs +124 -0
- package/lib/rules_inline/emphasis.mjs +123 -0
- package/lib/rules_inline/entity.mjs +51 -0
- package/lib/rules_inline/escape.mjs +69 -0
- package/lib/rules_inline/fragments_join.mjs +38 -0
- package/lib/rules_inline/html_inline.mjs +50 -0
- package/lib/rules_inline/image.mjs +138 -0
- package/lib/rules_inline/link.mjs +139 -0
- package/lib/rules_inline/linkify.mjs +63 -0
- package/lib/rules_inline/newline.mjs +42 -0
- package/lib/rules_inline/state_inline.mjs +123 -0
- package/lib/rules_inline/strikethrough.mjs +127 -0
- package/lib/rules_inline/text.mjs +86 -0
- package/lib/token.mjs +191 -0
- package/package.json +109 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// Process *this* and _that_
|
|
2
|
+
//
|
|
3
|
+
|
|
4
|
+
// Insert each marker as a separate text token, and add it to delimiter list
|
|
5
|
+
//
|
|
6
|
+
function emphasis_tokenize (state, silent) {
|
|
7
|
+
const start = state.pos
|
|
8
|
+
const marker = state.src.charCodeAt(start)
|
|
9
|
+
|
|
10
|
+
if (silent) { return false }
|
|
11
|
+
|
|
12
|
+
if (marker !== 0x5F /* _ */ && marker !== 0x2A /* * */) { return false }
|
|
13
|
+
|
|
14
|
+
const scanned = state.scanDelims(state.pos, marker === 0x2A)
|
|
15
|
+
|
|
16
|
+
for (let i = 0; i < scanned.length; i++) {
|
|
17
|
+
const token = state.push('text', '', 0)
|
|
18
|
+
token.content = String.fromCharCode(marker)
|
|
19
|
+
|
|
20
|
+
state.delimiters.push({
|
|
21
|
+
// Char code of the starting marker (number).
|
|
22
|
+
//
|
|
23
|
+
marker,
|
|
24
|
+
|
|
25
|
+
// Total length of these series of delimiters.
|
|
26
|
+
//
|
|
27
|
+
length: scanned.length,
|
|
28
|
+
|
|
29
|
+
// A position of the token this delimiter corresponds to.
|
|
30
|
+
//
|
|
31
|
+
token: state.tokens.length - 1,
|
|
32
|
+
|
|
33
|
+
// If this delimiter is matched as a valid opener, `end` will be
|
|
34
|
+
// equal to its position, otherwise it's `-1`.
|
|
35
|
+
//
|
|
36
|
+
end: -1,
|
|
37
|
+
|
|
38
|
+
// Boolean flags that determine if this delimiter could open or close
|
|
39
|
+
// an emphasis.
|
|
40
|
+
//
|
|
41
|
+
open: scanned.can_open,
|
|
42
|
+
close: scanned.can_close
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
state.pos += scanned.length
|
|
47
|
+
|
|
48
|
+
return true
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function postProcess (state, delimiters) {
|
|
52
|
+
const max = delimiters.length
|
|
53
|
+
|
|
54
|
+
for (let i = max - 1; i >= 0; i--) {
|
|
55
|
+
const startDelim = delimiters[i]
|
|
56
|
+
|
|
57
|
+
if (startDelim.marker !== 0x5F/* _ */ && startDelim.marker !== 0x2A/* * */) {
|
|
58
|
+
continue
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Process only opening markers
|
|
62
|
+
if (startDelim.end === -1) {
|
|
63
|
+
continue
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const endDelim = delimiters[startDelim.end]
|
|
67
|
+
|
|
68
|
+
// If the previous delimiter has the same marker and is adjacent to this one,
|
|
69
|
+
// merge those into one strong delimiter.
|
|
70
|
+
//
|
|
71
|
+
// `<em><em>whatever</em></em>` -> `<strong>whatever</strong>`
|
|
72
|
+
//
|
|
73
|
+
const isStrong = i > 0 &&
|
|
74
|
+
delimiters[i - 1].end === startDelim.end + 1 &&
|
|
75
|
+
// check that first two markers match and adjacent
|
|
76
|
+
delimiters[i - 1].marker === startDelim.marker &&
|
|
77
|
+
delimiters[i - 1].token === startDelim.token - 1 &&
|
|
78
|
+
// check that last two markers are adjacent (we can safely assume they match)
|
|
79
|
+
delimiters[startDelim.end + 1].token === endDelim.token + 1
|
|
80
|
+
|
|
81
|
+
const ch = String.fromCharCode(startDelim.marker)
|
|
82
|
+
|
|
83
|
+
const token_o = state.tokens[startDelim.token]
|
|
84
|
+
token_o.type = isStrong ? 'strong_open' : 'em_open'
|
|
85
|
+
token_o.tag = isStrong ? 'strong' : 'em'
|
|
86
|
+
token_o.nesting = 1
|
|
87
|
+
token_o.markup = isStrong ? ch + ch : ch
|
|
88
|
+
token_o.content = ''
|
|
89
|
+
|
|
90
|
+
const token_c = state.tokens[endDelim.token]
|
|
91
|
+
token_c.type = isStrong ? 'strong_close' : 'em_close'
|
|
92
|
+
token_c.tag = isStrong ? 'strong' : 'em'
|
|
93
|
+
token_c.nesting = -1
|
|
94
|
+
token_c.markup = isStrong ? ch + ch : ch
|
|
95
|
+
token_c.content = ''
|
|
96
|
+
|
|
97
|
+
if (isStrong) {
|
|
98
|
+
state.tokens[delimiters[i - 1].token].content = ''
|
|
99
|
+
state.tokens[delimiters[startDelim.end + 1].token].content = ''
|
|
100
|
+
i--
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Walk through delimiter list and replace text tokens with tags
|
|
106
|
+
//
|
|
107
|
+
function emphasis_post_process (state) {
|
|
108
|
+
const tokens_meta = state.tokens_meta
|
|
109
|
+
const max = state.tokens_meta.length
|
|
110
|
+
|
|
111
|
+
postProcess(state, state.delimiters)
|
|
112
|
+
|
|
113
|
+
for (let curr = 0; curr < max; curr++) {
|
|
114
|
+
if (tokens_meta[curr] && tokens_meta[curr].delimiters) {
|
|
115
|
+
postProcess(state, tokens_meta[curr].delimiters)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export default {
|
|
121
|
+
tokenize: emphasis_tokenize,
|
|
122
|
+
postProcess: emphasis_post_process
|
|
123
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Process html entity - {, ¯, ", ...
|
|
2
|
+
|
|
3
|
+
import { decodeHTML } from 'entities'
|
|
4
|
+
import { isValidEntityCode, fromCodePoint } from '../common/utils.mjs'
|
|
5
|
+
|
|
6
|
+
const DIGITAL_RE = /^&#((?:x[a-f0-9]{1,6}|[0-9]{1,7}));/i
|
|
7
|
+
const NAMED_RE = /^&([a-z][a-z0-9]{1,31});/i
|
|
8
|
+
|
|
9
|
+
export default function entity (state, silent) {
|
|
10
|
+
const pos = state.pos
|
|
11
|
+
const max = state.posMax
|
|
12
|
+
|
|
13
|
+
if (state.src.charCodeAt(pos) !== 0x26/* & */) return false
|
|
14
|
+
|
|
15
|
+
if (pos + 1 >= max) return false
|
|
16
|
+
|
|
17
|
+
const ch = state.src.charCodeAt(pos + 1)
|
|
18
|
+
|
|
19
|
+
if (ch === 0x23 /* # */) {
|
|
20
|
+
const match = state.src.slice(pos).match(DIGITAL_RE)
|
|
21
|
+
if (match) {
|
|
22
|
+
if (!silent) {
|
|
23
|
+
const code = match[1][0].toLowerCase() === 'x' ? parseInt(match[1].slice(1), 16) : parseInt(match[1], 10)
|
|
24
|
+
|
|
25
|
+
const token = state.push('text_special', '', 0)
|
|
26
|
+
token.content = isValidEntityCode(code) ? fromCodePoint(code) : fromCodePoint(0xFFFD)
|
|
27
|
+
token.markup = match[0]
|
|
28
|
+
token.info = 'entity'
|
|
29
|
+
}
|
|
30
|
+
state.pos += match[0].length
|
|
31
|
+
return true
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
const match = state.src.slice(pos).match(NAMED_RE)
|
|
35
|
+
if (match) {
|
|
36
|
+
const decoded = decodeHTML(match[0])
|
|
37
|
+
if (decoded !== match[0]) {
|
|
38
|
+
if (!silent) {
|
|
39
|
+
const token = state.push('text_special', '', 0)
|
|
40
|
+
token.content = decoded
|
|
41
|
+
token.markup = match[0]
|
|
42
|
+
token.info = 'entity'
|
|
43
|
+
}
|
|
44
|
+
state.pos += match[0].length
|
|
45
|
+
return true
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Process escaped chars and hardbreaks
|
|
2
|
+
|
|
3
|
+
import { isSpace } from '../common/utils.mjs'
|
|
4
|
+
|
|
5
|
+
const ESCAPED = []
|
|
6
|
+
|
|
7
|
+
for (let i = 0; i < 256; i++) { ESCAPED.push(0) }
|
|
8
|
+
|
|
9
|
+
'\\!"#$%&\'()*+,./:;<=>?@[]^_`{|}~-'
|
|
10
|
+
.split('').forEach(function (ch) { ESCAPED[ch.charCodeAt(0)] = 1 })
|
|
11
|
+
|
|
12
|
+
export default function escape (state, silent) {
|
|
13
|
+
let pos = state.pos
|
|
14
|
+
const max = state.posMax
|
|
15
|
+
|
|
16
|
+
if (state.src.charCodeAt(pos) !== 0x5C/* \ */) return false
|
|
17
|
+
pos++
|
|
18
|
+
|
|
19
|
+
// '\' at the end of the inline block
|
|
20
|
+
if (pos >= max) return false
|
|
21
|
+
|
|
22
|
+
let ch1 = state.src.charCodeAt(pos)
|
|
23
|
+
|
|
24
|
+
if (ch1 === 0x0A) {
|
|
25
|
+
if (!silent) {
|
|
26
|
+
state.push('hardbreak', 'br', 0)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
pos++
|
|
30
|
+
// skip leading whitespaces from next line
|
|
31
|
+
while (pos < max) {
|
|
32
|
+
ch1 = state.src.charCodeAt(pos)
|
|
33
|
+
if (!isSpace(ch1)) break
|
|
34
|
+
pos++
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
state.pos = pos
|
|
38
|
+
return true
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let escapedStr = state.src[pos]
|
|
42
|
+
|
|
43
|
+
if (ch1 >= 0xD800 && ch1 <= 0xDBFF && pos + 1 < max) {
|
|
44
|
+
const ch2 = state.src.charCodeAt(pos + 1)
|
|
45
|
+
|
|
46
|
+
if (ch2 >= 0xDC00 && ch2 <= 0xDFFF) {
|
|
47
|
+
escapedStr += state.src[pos + 1]
|
|
48
|
+
pos++
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const origStr = '\\' + escapedStr
|
|
53
|
+
|
|
54
|
+
if (!silent) {
|
|
55
|
+
const token = state.push('text_special', '', 0)
|
|
56
|
+
|
|
57
|
+
if (ch1 < 256 && ESCAPED[ch1] !== 0) {
|
|
58
|
+
token.content = escapedStr
|
|
59
|
+
} else {
|
|
60
|
+
token.content = origStr
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
token.markup = origStr
|
|
64
|
+
token.info = 'escape'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
state.pos = pos + 1
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Clean up tokens after emphasis and strikethrough postprocessing:
|
|
2
|
+
// merge adjacent text nodes into one and re-calculate all token levels
|
|
3
|
+
//
|
|
4
|
+
// This is necessary because initially emphasis delimiter markers (*, _, ~)
|
|
5
|
+
// are treated as their own separate text tokens. Then emphasis rule either
|
|
6
|
+
// leaves them as text (needed to merge with adjacent text) or turns them
|
|
7
|
+
// into opening/closing tags (which messes up levels inside).
|
|
8
|
+
//
|
|
9
|
+
|
|
10
|
+
export default function fragments_join (state) {
|
|
11
|
+
let curr, last
|
|
12
|
+
let level = 0
|
|
13
|
+
const tokens = state.tokens
|
|
14
|
+
const max = state.tokens.length
|
|
15
|
+
|
|
16
|
+
for (curr = last = 0; curr < max; curr++) {
|
|
17
|
+
// re-calculate levels after emphasis/strikethrough turns some text nodes
|
|
18
|
+
// into opening/closing tags
|
|
19
|
+
if (tokens[curr].nesting < 0) level-- // closing tag
|
|
20
|
+
tokens[curr].level = level
|
|
21
|
+
if (tokens[curr].nesting > 0) level++ // opening tag
|
|
22
|
+
|
|
23
|
+
if (tokens[curr].type === 'text' &&
|
|
24
|
+
curr + 1 < max &&
|
|
25
|
+
tokens[curr + 1].type === 'text') {
|
|
26
|
+
// collapse two adjacent text nodes
|
|
27
|
+
tokens[curr + 1].content = tokens[curr].content + tokens[curr + 1].content
|
|
28
|
+
} else {
|
|
29
|
+
if (curr !== last) { tokens[last] = tokens[curr] }
|
|
30
|
+
|
|
31
|
+
last++
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (curr !== last) {
|
|
36
|
+
tokens.length = last
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Process html tags
|
|
2
|
+
|
|
3
|
+
import { HTML_TAG_RE } from '../common/html_re.mjs'
|
|
4
|
+
|
|
5
|
+
function isLinkOpen (str) {
|
|
6
|
+
return /^<a[>\s]/i.test(str)
|
|
7
|
+
}
|
|
8
|
+
function isLinkClose (str) {
|
|
9
|
+
return /^<\/a\s*>/i.test(str)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function isLetter (ch) {
|
|
13
|
+
/* eslint no-bitwise:0 */
|
|
14
|
+
const lc = ch | 0x20 // to lower case
|
|
15
|
+
return (lc >= 0x61/* a */) && (lc <= 0x7a/* z */)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default function html_inline (state, silent) {
|
|
19
|
+
if (!state.md.options.html) { return false }
|
|
20
|
+
|
|
21
|
+
// Check start
|
|
22
|
+
const max = state.posMax
|
|
23
|
+
const pos = state.pos
|
|
24
|
+
if (state.src.charCodeAt(pos) !== 0x3C/* < */ ||
|
|
25
|
+
pos + 2 >= max) {
|
|
26
|
+
return false
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Quick fail on second char
|
|
30
|
+
const ch = state.src.charCodeAt(pos + 1)
|
|
31
|
+
if (ch !== 0x21/* ! */ &&
|
|
32
|
+
ch !== 0x3F/* ? */ &&
|
|
33
|
+
ch !== 0x2F/* / */ &&
|
|
34
|
+
!isLetter(ch)) {
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const match = state.src.slice(pos).match(HTML_TAG_RE)
|
|
39
|
+
if (!match) { return false }
|
|
40
|
+
|
|
41
|
+
if (!silent) {
|
|
42
|
+
const token = state.push('html_inline', '', 0)
|
|
43
|
+
token.content = match[0]
|
|
44
|
+
|
|
45
|
+
if (isLinkOpen(token.content)) state.linkLevel++
|
|
46
|
+
if (isLinkClose(token.content)) state.linkLevel--
|
|
47
|
+
}
|
|
48
|
+
state.pos += match[0].length
|
|
49
|
+
return true
|
|
50
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// Process 
|
|
2
|
+
|
|
3
|
+
import { normalizeReference, isSpace } from '../common/utils.mjs'
|
|
4
|
+
|
|
5
|
+
export default function image (state, silent) {
|
|
6
|
+
let code, content, label, pos, ref, res, title, start
|
|
7
|
+
let href = ''
|
|
8
|
+
const oldPos = state.pos
|
|
9
|
+
const max = state.posMax
|
|
10
|
+
|
|
11
|
+
if (state.src.charCodeAt(state.pos) !== 0x21/* ! */) { return false }
|
|
12
|
+
if (state.src.charCodeAt(state.pos + 1) !== 0x5B/* [ */) { return false }
|
|
13
|
+
|
|
14
|
+
const labelStart = state.pos + 2
|
|
15
|
+
const labelEnd = state.md.helpers.parseLinkLabel(state, state.pos + 1, false)
|
|
16
|
+
|
|
17
|
+
// parser failed to find ']', so it's not a valid link
|
|
18
|
+
if (labelEnd < 0) { return false }
|
|
19
|
+
|
|
20
|
+
pos = labelEnd + 1
|
|
21
|
+
if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) {
|
|
22
|
+
//
|
|
23
|
+
// Inline link
|
|
24
|
+
//
|
|
25
|
+
|
|
26
|
+
// [link]( <href> "title" )
|
|
27
|
+
// ^^ skipping these spaces
|
|
28
|
+
pos++
|
|
29
|
+
for (; pos < max; pos++) {
|
|
30
|
+
code = state.src.charCodeAt(pos)
|
|
31
|
+
if (!isSpace(code) && code !== 0x0A) { break }
|
|
32
|
+
}
|
|
33
|
+
if (pos >= max) { return false }
|
|
34
|
+
|
|
35
|
+
// [link]( <href> "title" )
|
|
36
|
+
// ^^^^^^ parsing link destination
|
|
37
|
+
start = pos
|
|
38
|
+
res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax)
|
|
39
|
+
if (res.ok) {
|
|
40
|
+
href = state.md.normalizeLink(res.str)
|
|
41
|
+
if (state.md.validateLink(href)) {
|
|
42
|
+
pos = res.pos
|
|
43
|
+
} else {
|
|
44
|
+
href = ''
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// [link]( <href> "title" )
|
|
49
|
+
// ^^ skipping these spaces
|
|
50
|
+
start = pos
|
|
51
|
+
for (; pos < max; pos++) {
|
|
52
|
+
code = state.src.charCodeAt(pos)
|
|
53
|
+
if (!isSpace(code) && code !== 0x0A) { break }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// [link]( <href> "title" )
|
|
57
|
+
// ^^^^^^^ parsing link title
|
|
58
|
+
res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax)
|
|
59
|
+
if (pos < max && start !== pos && res.ok) {
|
|
60
|
+
title = res.str
|
|
61
|
+
pos = res.pos
|
|
62
|
+
|
|
63
|
+
// [link]( <href> "title" )
|
|
64
|
+
// ^^ skipping these spaces
|
|
65
|
+
for (; pos < max; pos++) {
|
|
66
|
+
code = state.src.charCodeAt(pos)
|
|
67
|
+
if (!isSpace(code) && code !== 0x0A) { break }
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
title = ''
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) {
|
|
74
|
+
state.pos = oldPos
|
|
75
|
+
return false
|
|
76
|
+
}
|
|
77
|
+
pos++
|
|
78
|
+
} else {
|
|
79
|
+
//
|
|
80
|
+
// Link reference
|
|
81
|
+
//
|
|
82
|
+
if (typeof state.env.references === 'undefined') { return false }
|
|
83
|
+
|
|
84
|
+
if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) {
|
|
85
|
+
start = pos + 1
|
|
86
|
+
pos = state.md.helpers.parseLinkLabel(state, pos)
|
|
87
|
+
if (pos >= 0) {
|
|
88
|
+
label = state.src.slice(start, pos++)
|
|
89
|
+
} else {
|
|
90
|
+
pos = labelEnd + 1
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
pos = labelEnd + 1
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// covers label === '' and label === undefined
|
|
97
|
+
// (collapsed reference link and shortcut reference link respectively)
|
|
98
|
+
if (!label) { label = state.src.slice(labelStart, labelEnd) }
|
|
99
|
+
|
|
100
|
+
ref = state.env.references[normalizeReference(label)]
|
|
101
|
+
if (!ref) {
|
|
102
|
+
state.pos = oldPos
|
|
103
|
+
return false
|
|
104
|
+
}
|
|
105
|
+
href = ref.href
|
|
106
|
+
title = ref.title
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
//
|
|
110
|
+
// We found the end of the link, and know for a fact it's a valid link;
|
|
111
|
+
// so all that's left to do is to call tokenizer.
|
|
112
|
+
//
|
|
113
|
+
if (!silent) {
|
|
114
|
+
content = state.src.slice(labelStart, labelEnd)
|
|
115
|
+
|
|
116
|
+
const tokens = []
|
|
117
|
+
state.md.inline.parse(
|
|
118
|
+
content,
|
|
119
|
+
state.md,
|
|
120
|
+
state.env,
|
|
121
|
+
tokens
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
const token = state.push('image', 'img', 0)
|
|
125
|
+
const attrs = [['src', href], ['alt', '']]
|
|
126
|
+
token.attrs = attrs
|
|
127
|
+
token.children = tokens
|
|
128
|
+
token.content = content
|
|
129
|
+
|
|
130
|
+
if (title) {
|
|
131
|
+
attrs.push(['title', title])
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
state.pos = pos
|
|
136
|
+
state.posMax = max
|
|
137
|
+
return true
|
|
138
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Process [link](<to> "stuff")
|
|
2
|
+
|
|
3
|
+
import { normalizeReference, isSpace } from '../common/utils.mjs'
|
|
4
|
+
|
|
5
|
+
export default function link (state, silent) {
|
|
6
|
+
let code, label, res, ref
|
|
7
|
+
let href = ''
|
|
8
|
+
let title = ''
|
|
9
|
+
let start = state.pos
|
|
10
|
+
let parseReference = true
|
|
11
|
+
|
|
12
|
+
if (state.src.charCodeAt(state.pos) !== 0x5B/* [ */) { return false }
|
|
13
|
+
|
|
14
|
+
const oldPos = state.pos
|
|
15
|
+
const max = state.posMax
|
|
16
|
+
const labelStart = state.pos + 1
|
|
17
|
+
const labelEnd = state.md.helpers.parseLinkLabel(state, state.pos, true)
|
|
18
|
+
|
|
19
|
+
// parser failed to find ']', so it's not a valid link
|
|
20
|
+
if (labelEnd < 0) { return false }
|
|
21
|
+
|
|
22
|
+
let pos = labelEnd + 1
|
|
23
|
+
if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) {
|
|
24
|
+
//
|
|
25
|
+
// Inline link
|
|
26
|
+
//
|
|
27
|
+
|
|
28
|
+
// might have found a valid shortcut link, disable reference parsing
|
|
29
|
+
parseReference = false
|
|
30
|
+
|
|
31
|
+
// [link]( <href> "title" )
|
|
32
|
+
// ^^ skipping these spaces
|
|
33
|
+
pos++
|
|
34
|
+
for (; pos < max; pos++) {
|
|
35
|
+
code = state.src.charCodeAt(pos)
|
|
36
|
+
if (!isSpace(code) && code !== 0x0A) { break }
|
|
37
|
+
}
|
|
38
|
+
if (pos >= max) { return false }
|
|
39
|
+
|
|
40
|
+
// [link]( <href> "title" )
|
|
41
|
+
// ^^^^^^ parsing link destination
|
|
42
|
+
start = pos
|
|
43
|
+
res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax)
|
|
44
|
+
if (res.ok) {
|
|
45
|
+
href = state.md.normalizeLink(res.str)
|
|
46
|
+
if (state.md.validateLink(href)) {
|
|
47
|
+
pos = res.pos
|
|
48
|
+
} else {
|
|
49
|
+
href = ''
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// [link]( <href> "title" )
|
|
53
|
+
// ^^ skipping these spaces
|
|
54
|
+
start = pos
|
|
55
|
+
for (; pos < max; pos++) {
|
|
56
|
+
code = state.src.charCodeAt(pos)
|
|
57
|
+
if (!isSpace(code) && code !== 0x0A) { break }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// [link]( <href> "title" )
|
|
61
|
+
// ^^^^^^^ parsing link title
|
|
62
|
+
res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax)
|
|
63
|
+
if (pos < max && start !== pos && res.ok) {
|
|
64
|
+
title = res.str
|
|
65
|
+
pos = res.pos
|
|
66
|
+
|
|
67
|
+
// [link]( <href> "title" )
|
|
68
|
+
// ^^ skipping these spaces
|
|
69
|
+
for (; pos < max; pos++) {
|
|
70
|
+
code = state.src.charCodeAt(pos)
|
|
71
|
+
if (!isSpace(code) && code !== 0x0A) { break }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) {
|
|
77
|
+
// parsing a valid shortcut link failed, fallback to reference
|
|
78
|
+
parseReference = true
|
|
79
|
+
}
|
|
80
|
+
pos++
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (parseReference) {
|
|
84
|
+
//
|
|
85
|
+
// Link reference
|
|
86
|
+
//
|
|
87
|
+
if (typeof state.env.references === 'undefined') { return false }
|
|
88
|
+
|
|
89
|
+
if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) {
|
|
90
|
+
start = pos + 1
|
|
91
|
+
pos = state.md.helpers.parseLinkLabel(state, pos)
|
|
92
|
+
if (pos >= 0) {
|
|
93
|
+
label = state.src.slice(start, pos++)
|
|
94
|
+
} else {
|
|
95
|
+
pos = labelEnd + 1
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
pos = labelEnd + 1
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// covers label === '' and label === undefined
|
|
102
|
+
// (collapsed reference link and shortcut reference link respectively)
|
|
103
|
+
if (!label) { label = state.src.slice(labelStart, labelEnd) }
|
|
104
|
+
|
|
105
|
+
ref = state.env.references[normalizeReference(label)]
|
|
106
|
+
if (!ref) {
|
|
107
|
+
state.pos = oldPos
|
|
108
|
+
return false
|
|
109
|
+
}
|
|
110
|
+
href = ref.href
|
|
111
|
+
title = ref.title
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
//
|
|
115
|
+
// We found the end of the link, and know for a fact it's a valid link;
|
|
116
|
+
// so all that's left to do is to call tokenizer.
|
|
117
|
+
//
|
|
118
|
+
if (!silent) {
|
|
119
|
+
state.pos = labelStart
|
|
120
|
+
state.posMax = labelEnd
|
|
121
|
+
|
|
122
|
+
const token_o = state.push('link_open', 'a', 1)
|
|
123
|
+
const attrs = [['href', href]]
|
|
124
|
+
token_o.attrs = attrs
|
|
125
|
+
if (title) {
|
|
126
|
+
attrs.push(['title', title])
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
state.linkLevel++
|
|
130
|
+
state.md.inline.tokenize(state)
|
|
131
|
+
state.linkLevel--
|
|
132
|
+
|
|
133
|
+
state.push('link_close', 'a', -1)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
state.pos = pos
|
|
137
|
+
state.posMax = max
|
|
138
|
+
return true
|
|
139
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Process links like https://example.org/
|
|
2
|
+
|
|
3
|
+
// RFC3986: scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
|
|
4
|
+
const SCHEME_RE = /(?:^|[^a-z0-9.+-])([a-z][a-z0-9.+-]*)$/i
|
|
5
|
+
|
|
6
|
+
export default function linkify (state, silent) {
|
|
7
|
+
if (!state.md.options.linkify) return false
|
|
8
|
+
if (state.linkLevel > 0) return false
|
|
9
|
+
|
|
10
|
+
const pos = state.pos
|
|
11
|
+
const max = state.posMax
|
|
12
|
+
|
|
13
|
+
if (pos + 3 > max) return false
|
|
14
|
+
if (state.src.charCodeAt(pos) !== 0x3A/* : */) return false
|
|
15
|
+
if (state.src.charCodeAt(pos + 1) !== 0x2F/* / */) return false
|
|
16
|
+
if (state.src.charCodeAt(pos + 2) !== 0x2F/* / */) return false
|
|
17
|
+
|
|
18
|
+
const match = state.pending.match(SCHEME_RE)
|
|
19
|
+
if (!match) return false
|
|
20
|
+
|
|
21
|
+
const proto = match[1]
|
|
22
|
+
|
|
23
|
+
const link = state.md.linkify.matchAtStart(state.src.slice(pos - proto.length))
|
|
24
|
+
if (!link) return false
|
|
25
|
+
|
|
26
|
+
let url = link.url
|
|
27
|
+
|
|
28
|
+
// invalid link, but still detected by linkify somehow;
|
|
29
|
+
// need to check to prevent infinite loop below
|
|
30
|
+
if (url.length <= proto.length) return false
|
|
31
|
+
|
|
32
|
+
// disallow '*' at the end of the link (conflicts with emphasis)
|
|
33
|
+
// do manual backsearch to avoid perf issues with regex /\*+$/ on "****...****a".
|
|
34
|
+
let urlEnd = url.length
|
|
35
|
+
while (urlEnd > 0 && url.charCodeAt(urlEnd - 1) === 0x2A/* * */) {
|
|
36
|
+
urlEnd--
|
|
37
|
+
}
|
|
38
|
+
if (urlEnd !== url.length) {
|
|
39
|
+
url = url.slice(0, urlEnd)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const fullUrl = state.md.normalizeLink(url)
|
|
43
|
+
if (!state.md.validateLink(fullUrl)) return false
|
|
44
|
+
|
|
45
|
+
if (!silent) {
|
|
46
|
+
state.pending = state.pending.slice(0, -proto.length)
|
|
47
|
+
|
|
48
|
+
const token_o = state.push('link_open', 'a', 1)
|
|
49
|
+
token_o.attrs = [['href', fullUrl]]
|
|
50
|
+
token_o.markup = 'linkify'
|
|
51
|
+
token_o.info = 'auto'
|
|
52
|
+
|
|
53
|
+
const token_t = state.push('text', '', 0)
|
|
54
|
+
token_t.content = state.md.normalizeLinkText(url)
|
|
55
|
+
|
|
56
|
+
const token_c = state.push('link_close', 'a', -1)
|
|
57
|
+
token_c.markup = 'linkify'
|
|
58
|
+
token_c.info = 'auto'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
state.pos += url.length - proto.length
|
|
62
|
+
return true
|
|
63
|
+
}
|