@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,101 @@
|
|
|
1
|
+
// Simple typographic replacements
|
|
2
|
+
//
|
|
3
|
+
// (c) (C) → ©
|
|
4
|
+
// (tm) (TM) → ™
|
|
5
|
+
// (r) (R) → ®
|
|
6
|
+
// +- → ±
|
|
7
|
+
// ... → … (also ?.... → ?.., !.... → !..)
|
|
8
|
+
// ???????? → ???, !!!!! → !!!, `,,` → `,`
|
|
9
|
+
// -- → –, --- → —
|
|
10
|
+
//
|
|
11
|
+
|
|
12
|
+
// TODO:
|
|
13
|
+
// - fractionals 1/2, 1/4, 3/4 -> ½, ¼, ¾
|
|
14
|
+
// - multiplications 2 x 4 -> 2 × 4
|
|
15
|
+
|
|
16
|
+
const RARE_RE = /\+-|\.\.|\?\?\?\?|!!!!|,,|--/
|
|
17
|
+
|
|
18
|
+
// Workaround for phantomjs - need regex without /g flag,
|
|
19
|
+
// or root check will fail every second time
|
|
20
|
+
const SCOPED_ABBR_TEST_RE = /\((c|tm|r)\)/i
|
|
21
|
+
|
|
22
|
+
const SCOPED_ABBR_RE = /\((c|tm|r)\)/ig
|
|
23
|
+
const SCOPED_ABBR = {
|
|
24
|
+
c: '©',
|
|
25
|
+
r: '®',
|
|
26
|
+
tm: '™'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function replaceFn (match, name) {
|
|
30
|
+
return SCOPED_ABBR[name.toLowerCase()]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function replace_scoped (inlineTokens) {
|
|
34
|
+
let inside_autolink = 0
|
|
35
|
+
|
|
36
|
+
for (let i = inlineTokens.length - 1; i >= 0; i--) {
|
|
37
|
+
const token = inlineTokens[i]
|
|
38
|
+
|
|
39
|
+
if (token.type === 'text' && !inside_autolink) {
|
|
40
|
+
token.content = token.content.replace(SCOPED_ABBR_RE, replaceFn)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (token.type === 'link_open' && token.info === 'auto') {
|
|
44
|
+
inside_autolink--
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (token.type === 'link_close' && token.info === 'auto') {
|
|
48
|
+
inside_autolink++
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function replace_rare (inlineTokens) {
|
|
54
|
+
let inside_autolink = 0
|
|
55
|
+
|
|
56
|
+
for (let i = inlineTokens.length - 1; i >= 0; i--) {
|
|
57
|
+
const token = inlineTokens[i]
|
|
58
|
+
|
|
59
|
+
if (token.type === 'text' && !inside_autolink) {
|
|
60
|
+
if (RARE_RE.test(token.content)) {
|
|
61
|
+
token.content = token.content
|
|
62
|
+
.replace(/\+-/g, '±')
|
|
63
|
+
// .., ..., ....... -> …
|
|
64
|
+
// but ?..... & !..... -> ?.. & !..
|
|
65
|
+
.replace(/\.{2,}/g, '…').replace(/([?!])…/g, '$1..')
|
|
66
|
+
.replace(/([?!]){4,}/g, '$1$1$1').replace(/,{2,}/g, ',')
|
|
67
|
+
// em-dash
|
|
68
|
+
.replace(/(^|[^-])---(?=[^-]|$)/mg, '$1\u2014')
|
|
69
|
+
// en-dash
|
|
70
|
+
.replace(/(^|\s)--(?=\s|$)/mg, '$1\u2013')
|
|
71
|
+
.replace(/(^|[^-\s])--(?=[^-\s]|$)/mg, '$1\u2013')
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (token.type === 'link_open' && token.info === 'auto') {
|
|
76
|
+
inside_autolink--
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (token.type === 'link_close' && token.info === 'auto') {
|
|
80
|
+
inside_autolink++
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default function replace (state) {
|
|
86
|
+
let blkIdx
|
|
87
|
+
|
|
88
|
+
if (!state.md.options.typographer) { return }
|
|
89
|
+
|
|
90
|
+
for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) {
|
|
91
|
+
if (state.tokens[blkIdx].type !== 'inline') { continue }
|
|
92
|
+
|
|
93
|
+
if (SCOPED_ABBR_TEST_RE.test(state.tokens[blkIdx].content)) {
|
|
94
|
+
replace_scoped(state.tokens[blkIdx].children)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (RARE_RE.test(state.tokens[blkIdx].content)) {
|
|
98
|
+
replace_rare(state.tokens[blkIdx].children)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// Convert straight quotation marks to typographic ones
|
|
2
|
+
//
|
|
3
|
+
|
|
4
|
+
import { isWhiteSpace, isPunctChar, isMdAsciiPunct } from '../common/utils.mjs'
|
|
5
|
+
|
|
6
|
+
const QUOTE_TEST_RE = /['"]/
|
|
7
|
+
const QUOTE_RE = /['"]/g
|
|
8
|
+
const APOSTROPHE = '\u2019' /* ’ */
|
|
9
|
+
|
|
10
|
+
function replaceAt (str, index, ch) {
|
|
11
|
+
return str.slice(0, index) + ch + str.slice(index + 1)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function process_inlines (tokens, state) {
|
|
15
|
+
let j
|
|
16
|
+
|
|
17
|
+
const stack = []
|
|
18
|
+
|
|
19
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
20
|
+
const token = tokens[i]
|
|
21
|
+
|
|
22
|
+
const thisLevel = tokens[i].level
|
|
23
|
+
|
|
24
|
+
for (j = stack.length - 1; j >= 0; j--) {
|
|
25
|
+
if (stack[j].level <= thisLevel) { break }
|
|
26
|
+
}
|
|
27
|
+
stack.length = j + 1
|
|
28
|
+
|
|
29
|
+
if (token.type !== 'text') { continue }
|
|
30
|
+
|
|
31
|
+
let text = token.content
|
|
32
|
+
let pos = 0
|
|
33
|
+
let max = text.length
|
|
34
|
+
|
|
35
|
+
/* eslint no-labels:0,block-scoped-var:0 */
|
|
36
|
+
OUTER:
|
|
37
|
+
while (pos < max) {
|
|
38
|
+
QUOTE_RE.lastIndex = pos
|
|
39
|
+
const t = QUOTE_RE.exec(text)
|
|
40
|
+
if (!t) { break }
|
|
41
|
+
|
|
42
|
+
let canOpen = true
|
|
43
|
+
let canClose = true
|
|
44
|
+
pos = t.index + 1
|
|
45
|
+
const isSingle = (t[0] === "'")
|
|
46
|
+
|
|
47
|
+
// Find previous character,
|
|
48
|
+
// default to space if it's the beginning of the line
|
|
49
|
+
//
|
|
50
|
+
let lastChar = 0x20
|
|
51
|
+
|
|
52
|
+
if (t.index - 1 >= 0) {
|
|
53
|
+
lastChar = text.charCodeAt(t.index - 1)
|
|
54
|
+
} else {
|
|
55
|
+
for (j = i - 1; j >= 0; j--) {
|
|
56
|
+
if (tokens[j].type === 'softbreak' || tokens[j].type === 'hardbreak') break // lastChar defaults to 0x20
|
|
57
|
+
if (!tokens[j].content) continue // should skip all tokens except 'text', 'html_inline' or 'code_inline'
|
|
58
|
+
|
|
59
|
+
lastChar = tokens[j].content.charCodeAt(tokens[j].content.length - 1)
|
|
60
|
+
break
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Find next character,
|
|
65
|
+
// default to space if it's the end of the line
|
|
66
|
+
//
|
|
67
|
+
let nextChar = 0x20
|
|
68
|
+
|
|
69
|
+
if (pos < max) {
|
|
70
|
+
nextChar = text.charCodeAt(pos)
|
|
71
|
+
} else {
|
|
72
|
+
for (j = i + 1; j < tokens.length; j++) {
|
|
73
|
+
if (tokens[j].type === 'softbreak' || tokens[j].type === 'hardbreak') break // nextChar defaults to 0x20
|
|
74
|
+
if (!tokens[j].content) continue // should skip all tokens except 'text', 'html_inline' or 'code_inline'
|
|
75
|
+
|
|
76
|
+
nextChar = tokens[j].content.charCodeAt(0)
|
|
77
|
+
break
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar))
|
|
82
|
+
const isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar))
|
|
83
|
+
|
|
84
|
+
const isLastWhiteSpace = isWhiteSpace(lastChar)
|
|
85
|
+
const isNextWhiteSpace = isWhiteSpace(nextChar)
|
|
86
|
+
|
|
87
|
+
if (isNextWhiteSpace) {
|
|
88
|
+
canOpen = false
|
|
89
|
+
} else if (isNextPunctChar) {
|
|
90
|
+
if (!(isLastWhiteSpace || isLastPunctChar)) {
|
|
91
|
+
canOpen = false
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (isLastWhiteSpace) {
|
|
96
|
+
canClose = false
|
|
97
|
+
} else if (isLastPunctChar) {
|
|
98
|
+
if (!(isNextWhiteSpace || isNextPunctChar)) {
|
|
99
|
+
canClose = false
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (nextChar === 0x22 /* " */ && t[0] === '"') {
|
|
104
|
+
if (lastChar >= 0x30 /* 0 */ && lastChar <= 0x39 /* 9 */) {
|
|
105
|
+
// special case: 1"" - count first quote as an inch
|
|
106
|
+
canClose = canOpen = false
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (canOpen && canClose) {
|
|
111
|
+
// Replace quotes in the middle of punctuation sequence, but not
|
|
112
|
+
// in the middle of the words, i.e.:
|
|
113
|
+
//
|
|
114
|
+
// 1. foo " bar " baz - not replaced
|
|
115
|
+
// 2. foo-"-bar-"-baz - replaced
|
|
116
|
+
// 3. foo"bar"baz - not replaced
|
|
117
|
+
//
|
|
118
|
+
canOpen = isLastPunctChar
|
|
119
|
+
canClose = isNextPunctChar
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!canOpen && !canClose) {
|
|
123
|
+
// middle of word
|
|
124
|
+
if (isSingle) {
|
|
125
|
+
token.content = replaceAt(token.content, t.index, APOSTROPHE)
|
|
126
|
+
}
|
|
127
|
+
continue
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (canClose) {
|
|
131
|
+
// this could be a closing quote, rewind the stack to get a match
|
|
132
|
+
for (j = stack.length - 1; j >= 0; j--) {
|
|
133
|
+
let item = stack[j]
|
|
134
|
+
if (stack[j].level < thisLevel) { break }
|
|
135
|
+
if (item.single === isSingle && stack[j].level === thisLevel) {
|
|
136
|
+
item = stack[j]
|
|
137
|
+
|
|
138
|
+
let openQuote
|
|
139
|
+
let closeQuote
|
|
140
|
+
if (isSingle) {
|
|
141
|
+
openQuote = state.md.options.quotes[2]
|
|
142
|
+
closeQuote = state.md.options.quotes[3]
|
|
143
|
+
} else {
|
|
144
|
+
openQuote = state.md.options.quotes[0]
|
|
145
|
+
closeQuote = state.md.options.quotes[1]
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// replace token.content *before* tokens[item.token].content,
|
|
149
|
+
// because, if they are pointing at the same token, replaceAt
|
|
150
|
+
// could mess up indices when quote length != 1
|
|
151
|
+
token.content = replaceAt(token.content, t.index, closeQuote)
|
|
152
|
+
tokens[item.token].content = replaceAt(
|
|
153
|
+
tokens[item.token].content, item.pos, openQuote)
|
|
154
|
+
|
|
155
|
+
pos += closeQuote.length - 1
|
|
156
|
+
if (item.token === i) { pos += openQuote.length - 1 }
|
|
157
|
+
|
|
158
|
+
text = token.content
|
|
159
|
+
max = text.length
|
|
160
|
+
|
|
161
|
+
stack.length = j
|
|
162
|
+
continue OUTER
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (canOpen) {
|
|
168
|
+
stack.push({
|
|
169
|
+
token: i,
|
|
170
|
+
pos: t.index,
|
|
171
|
+
single: isSingle,
|
|
172
|
+
level: thisLevel
|
|
173
|
+
})
|
|
174
|
+
} else if (canClose && isSingle) {
|
|
175
|
+
token.content = replaceAt(token.content, t.index, APOSTROPHE)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export default function smartquotes (state) {
|
|
182
|
+
/* eslint max-depth:0 */
|
|
183
|
+
if (!state.md.options.typographer) { return }
|
|
184
|
+
|
|
185
|
+
for (let blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) {
|
|
186
|
+
if (state.tokens[blkIdx].type !== 'inline' ||
|
|
187
|
+
!QUOTE_TEST_RE.test(state.tokens[blkIdx].content)) {
|
|
188
|
+
continue
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
process_inlines(state.tokens[blkIdx].children, state)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Core state object
|
|
2
|
+
//
|
|
3
|
+
|
|
4
|
+
import Token from '../token.mjs'
|
|
5
|
+
|
|
6
|
+
function StateCore (src, md, env) {
|
|
7
|
+
this.src = src
|
|
8
|
+
this.env = env
|
|
9
|
+
this.tokens = []
|
|
10
|
+
this.inlineMode = false
|
|
11
|
+
this.md = md // link to parser instance
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// re-export Token class to use in core rules
|
|
15
|
+
StateCore.prototype.Token = Token
|
|
16
|
+
|
|
17
|
+
export default StateCore
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Join raw text tokens with the rest of the text
|
|
2
|
+
//
|
|
3
|
+
// This is set as a separate rule to provide an opportunity for plugins
|
|
4
|
+
// to run text replacements after text join, but before escape join.
|
|
5
|
+
//
|
|
6
|
+
// For example, `\:)` shouldn't be replaced with an emoji.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
export default function text_join (state) {
|
|
10
|
+
let curr, last
|
|
11
|
+
const blockTokens = state.tokens
|
|
12
|
+
const l = blockTokens.length
|
|
13
|
+
|
|
14
|
+
for (let j = 0; j < l; j++) {
|
|
15
|
+
if (blockTokens[j].type !== 'inline') continue
|
|
16
|
+
|
|
17
|
+
const tokens = blockTokens[j].children
|
|
18
|
+
const max = tokens.length
|
|
19
|
+
|
|
20
|
+
for (curr = 0; curr < max; curr++) {
|
|
21
|
+
if (tokens[curr].type === 'text_special') {
|
|
22
|
+
tokens[curr].type = 'text'
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
for (curr = last = 0; curr < max; curr++) {
|
|
27
|
+
if (tokens[curr].type === 'text' &&
|
|
28
|
+
curr + 1 < max &&
|
|
29
|
+
tokens[curr + 1].type === 'text') {
|
|
30
|
+
// collapse two adjacent text nodes
|
|
31
|
+
tokens[curr + 1].content = tokens[curr].content + tokens[curr + 1].content
|
|
32
|
+
} else {
|
|
33
|
+
if (curr !== last) { tokens[last] = tokens[curr] }
|
|
34
|
+
|
|
35
|
+
last++
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (curr !== last) {
|
|
40
|
+
tokens.length = last
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Process autolinks '<protocol:...>'
|
|
2
|
+
|
|
3
|
+
/* eslint max-len:0 */
|
|
4
|
+
const EMAIL_RE = /^([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)$/
|
|
5
|
+
/* eslint-disable-next-line no-control-regex */
|
|
6
|
+
const AUTOLINK_RE = /^([a-zA-Z][a-zA-Z0-9+.-]{1,31}):([^<>\x00-\x20]*)$/
|
|
7
|
+
|
|
8
|
+
export default function autolink (state, silent) {
|
|
9
|
+
let pos = state.pos
|
|
10
|
+
|
|
11
|
+
if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false }
|
|
12
|
+
|
|
13
|
+
const start = state.pos
|
|
14
|
+
const max = state.posMax
|
|
15
|
+
|
|
16
|
+
for (;;) {
|
|
17
|
+
if (++pos >= max) return false
|
|
18
|
+
|
|
19
|
+
const ch = state.src.charCodeAt(pos)
|
|
20
|
+
|
|
21
|
+
if (ch === 0x3C /* < */) return false
|
|
22
|
+
if (ch === 0x3E /* > */) break
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const url = state.src.slice(start + 1, pos)
|
|
26
|
+
|
|
27
|
+
if (AUTOLINK_RE.test(url)) {
|
|
28
|
+
const fullUrl = state.md.normalizeLink(url)
|
|
29
|
+
if (!state.md.validateLink(fullUrl)) { return false }
|
|
30
|
+
|
|
31
|
+
if (!silent) {
|
|
32
|
+
const token_o = state.push('link_open', 'a', 1)
|
|
33
|
+
token_o.attrs = [['href', fullUrl]]
|
|
34
|
+
token_o.markup = 'autolink'
|
|
35
|
+
token_o.info = 'auto'
|
|
36
|
+
|
|
37
|
+
const token_t = state.push('text', '', 0)
|
|
38
|
+
token_t.content = state.md.normalizeLinkText(url)
|
|
39
|
+
|
|
40
|
+
const token_c = state.push('link_close', 'a', -1)
|
|
41
|
+
token_c.markup = 'autolink'
|
|
42
|
+
token_c.info = 'auto'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
state.pos += url.length + 2
|
|
46
|
+
return true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (EMAIL_RE.test(url)) {
|
|
50
|
+
const fullUrl = state.md.normalizeLink('mailto:' + url)
|
|
51
|
+
if (!state.md.validateLink(fullUrl)) { return false }
|
|
52
|
+
|
|
53
|
+
if (!silent) {
|
|
54
|
+
const token_o = state.push('link_open', 'a', 1)
|
|
55
|
+
token_o.attrs = [['href', fullUrl]]
|
|
56
|
+
token_o.markup = 'autolink'
|
|
57
|
+
token_o.info = 'auto'
|
|
58
|
+
|
|
59
|
+
const token_t = state.push('text', '', 0)
|
|
60
|
+
token_t.content = state.md.normalizeLinkText(url)
|
|
61
|
+
|
|
62
|
+
const token_c = state.push('link_close', 'a', -1)
|
|
63
|
+
token_c.markup = 'autolink'
|
|
64
|
+
token_c.info = 'auto'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
state.pos += url.length + 2
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Parse backticks
|
|
2
|
+
|
|
3
|
+
export default function backtick (state, silent) {
|
|
4
|
+
let pos = state.pos
|
|
5
|
+
const ch = state.src.charCodeAt(pos)
|
|
6
|
+
|
|
7
|
+
if (ch !== 0x60/* ` */) { return false }
|
|
8
|
+
|
|
9
|
+
const start = pos
|
|
10
|
+
pos++
|
|
11
|
+
const max = state.posMax
|
|
12
|
+
|
|
13
|
+
// scan marker length
|
|
14
|
+
while (pos < max && state.src.charCodeAt(pos) === 0x60/* ` */) { pos++ }
|
|
15
|
+
|
|
16
|
+
const marker = state.src.slice(start, pos)
|
|
17
|
+
const openerLength = marker.length
|
|
18
|
+
|
|
19
|
+
if (state.backticksScanned && (state.backticks[openerLength] || 0) <= start) {
|
|
20
|
+
if (!silent) state.pending += marker
|
|
21
|
+
state.pos += openerLength
|
|
22
|
+
return true
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let matchEnd = pos
|
|
26
|
+
let matchStart
|
|
27
|
+
|
|
28
|
+
// Nothing found in the cache, scan until the end of the line (or until marker is found)
|
|
29
|
+
while ((matchStart = state.src.indexOf('`', matchEnd)) !== -1) {
|
|
30
|
+
matchEnd = matchStart + 1
|
|
31
|
+
|
|
32
|
+
// scan marker length
|
|
33
|
+
while (matchEnd < max && state.src.charCodeAt(matchEnd) === 0x60/* ` */) { matchEnd++ }
|
|
34
|
+
|
|
35
|
+
const closerLength = matchEnd - matchStart
|
|
36
|
+
|
|
37
|
+
if (closerLength === openerLength) {
|
|
38
|
+
// Found matching closer length.
|
|
39
|
+
if (!silent) {
|
|
40
|
+
const token = state.push('code_inline', 'code', 0)
|
|
41
|
+
token.markup = marker
|
|
42
|
+
token.content = state.src.slice(pos, matchStart)
|
|
43
|
+
.replace(/\n/g, ' ')
|
|
44
|
+
.replace(/^ (.+) $/, '$1')
|
|
45
|
+
}
|
|
46
|
+
state.pos = matchEnd
|
|
47
|
+
return true
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Some different length found, put it in cache as upper limit of where closer can be found
|
|
51
|
+
state.backticks[closerLength] = matchStart
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Scanned through the end, didn't find anything
|
|
55
|
+
state.backticksScanned = true
|
|
56
|
+
|
|
57
|
+
if (!silent) state.pending += marker
|
|
58
|
+
state.pos += openerLength
|
|
59
|
+
return true
|
|
60
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// For each opening emphasis-like marker find a matching closing one
|
|
2
|
+
//
|
|
3
|
+
|
|
4
|
+
function processDelimiters (delimiters) {
|
|
5
|
+
const openersBottom = {}
|
|
6
|
+
const max = delimiters.length
|
|
7
|
+
|
|
8
|
+
if (!max) return
|
|
9
|
+
|
|
10
|
+
// headerIdx is the first delimiter of the current (where closer is) delimiter run
|
|
11
|
+
let headerIdx = 0
|
|
12
|
+
let lastTokenIdx = -2 // needs any value lower than -1
|
|
13
|
+
const jumps = []
|
|
14
|
+
|
|
15
|
+
for (let closerIdx = 0; closerIdx < max; closerIdx++) {
|
|
16
|
+
const closer = delimiters[closerIdx]
|
|
17
|
+
|
|
18
|
+
jumps.push(0)
|
|
19
|
+
|
|
20
|
+
// markers belong to same delimiter run if:
|
|
21
|
+
// - they have adjacent tokens
|
|
22
|
+
// - AND markers are the same
|
|
23
|
+
//
|
|
24
|
+
if (delimiters[headerIdx].marker !== closer.marker || lastTokenIdx !== closer.token - 1) {
|
|
25
|
+
headerIdx = closerIdx
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
lastTokenIdx = closer.token
|
|
29
|
+
|
|
30
|
+
// Length is only used for emphasis-specific "rule of 3",
|
|
31
|
+
// if it's not defined (in strikethrough or 3rd party plugins),
|
|
32
|
+
// we can default it to 0 to disable those checks.
|
|
33
|
+
//
|
|
34
|
+
closer.length = closer.length || 0
|
|
35
|
+
|
|
36
|
+
if (!closer.close) continue
|
|
37
|
+
|
|
38
|
+
// Previously calculated lower bounds (previous fails)
|
|
39
|
+
// for each marker, each delimiter length modulo 3,
|
|
40
|
+
// and for whether this closer can be an opener;
|
|
41
|
+
// https://github.com/commonmark/cmark/commit/34250e12ccebdc6372b8b49c44fab57c72443460
|
|
42
|
+
/* eslint-disable-next-line no-prototype-builtins */
|
|
43
|
+
if (!openersBottom.hasOwnProperty(closer.marker)) {
|
|
44
|
+
openersBottom[closer.marker] = [-1, -1, -1, -1, -1, -1]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const minOpenerIdx = openersBottom[closer.marker][(closer.open ? 3 : 0) + (closer.length % 3)]
|
|
48
|
+
|
|
49
|
+
let openerIdx = headerIdx - jumps[headerIdx] - 1
|
|
50
|
+
|
|
51
|
+
let newMinOpenerIdx = openerIdx
|
|
52
|
+
|
|
53
|
+
for (; openerIdx > minOpenerIdx; openerIdx -= jumps[openerIdx] + 1) {
|
|
54
|
+
const opener = delimiters[openerIdx]
|
|
55
|
+
|
|
56
|
+
if (opener.marker !== closer.marker) continue
|
|
57
|
+
|
|
58
|
+
if (opener.open && opener.end < 0) {
|
|
59
|
+
let isOddMatch = false
|
|
60
|
+
|
|
61
|
+
// from spec:
|
|
62
|
+
//
|
|
63
|
+
// If one of the delimiters can both open and close emphasis, then the
|
|
64
|
+
// sum of the lengths of the delimiter runs containing the opening and
|
|
65
|
+
// closing delimiters must not be a multiple of 3 unless both lengths
|
|
66
|
+
// are multiples of 3.
|
|
67
|
+
//
|
|
68
|
+
if (opener.close || closer.open) {
|
|
69
|
+
if ((opener.length + closer.length) % 3 === 0) {
|
|
70
|
+
if (opener.length % 3 !== 0 || closer.length % 3 !== 0) {
|
|
71
|
+
isOddMatch = true
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!isOddMatch) {
|
|
77
|
+
// If previous delimiter cannot be an opener, we can safely skip
|
|
78
|
+
// the entire sequence in future checks. This is required to make
|
|
79
|
+
// sure algorithm has linear complexity (see *_*_*_*_*_... case).
|
|
80
|
+
//
|
|
81
|
+
const lastJump = openerIdx > 0 && !delimiters[openerIdx - 1].open
|
|
82
|
+
? jumps[openerIdx - 1] + 1
|
|
83
|
+
: 0
|
|
84
|
+
|
|
85
|
+
jumps[closerIdx] = closerIdx - openerIdx + lastJump
|
|
86
|
+
jumps[openerIdx] = lastJump
|
|
87
|
+
|
|
88
|
+
closer.open = false
|
|
89
|
+
opener.end = closerIdx
|
|
90
|
+
opener.close = false
|
|
91
|
+
newMinOpenerIdx = -1
|
|
92
|
+
// treat next token as start of run,
|
|
93
|
+
// it optimizes skips in **<...>**a**<...>** pathological case
|
|
94
|
+
lastTokenIdx = -2
|
|
95
|
+
break
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (newMinOpenerIdx !== -1) {
|
|
101
|
+
// If match for this delimiter run failed, we want to set lower bound for
|
|
102
|
+
// future lookups. This is required to make sure algorithm has linear
|
|
103
|
+
// complexity.
|
|
104
|
+
//
|
|
105
|
+
// See details here:
|
|
106
|
+
// https://github.com/commonmark/cmark/issues/178#issuecomment-270417442
|
|
107
|
+
//
|
|
108
|
+
openersBottom[closer.marker][(closer.open ? 3 : 0) + ((closer.length || 0) % 3)] = newMinOpenerIdx
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export default function link_pairs (state) {
|
|
114
|
+
const tokens_meta = state.tokens_meta
|
|
115
|
+
const max = state.tokens_meta.length
|
|
116
|
+
|
|
117
|
+
processDelimiters(state.delimiters)
|
|
118
|
+
|
|
119
|
+
for (let curr = 0; curr < max; curr++) {
|
|
120
|
+
if (tokens_meta[curr] && tokens_meta[curr].delimiters) {
|
|
121
|
+
processDelimiters(tokens_meta[curr].delimiters)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|