@peaceroad/markdown-it-strong-ja 0.4.4 → 0.5.0
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/index.js +1301 -713
- package/package.json +5 -2
package/index.js
CHANGED
|
@@ -1,783 +1,1371 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
1
|
+
import Token from 'markdown-it/lib/token.mjs'
|
|
2
|
+
|
|
3
|
+
const CHAR_ASTERISK = 0x2A // *
|
|
4
|
+
//const CHAR_UNDERSCORE = 0x5F // _
|
|
5
|
+
const CHAR_BACKSLASH = 0x5C // \
|
|
6
|
+
const CHAR_BACKTICK = 0x60 // `
|
|
7
|
+
const CHAR_DOLLAR = 0x24 // $
|
|
8
|
+
const CHAR_LT = 0x3C // <
|
|
9
|
+
const CHAR_GT = 0x3E // >
|
|
10
|
+
const CHAR_SLASH = 0x2F // /
|
|
11
|
+
const CHAR_SPACE = 0x20 // ' ' (space)
|
|
12
|
+
const CHAR_OPEN_BRACKET = 0x5B // [
|
|
13
|
+
const CHAR_CLOSE_BRACKET = 0x5D // ]
|
|
14
|
+
|
|
15
|
+
const REG_ASTERISKS = /^\*+$/
|
|
16
|
+
const REG_ATTRS = /{[^{}\n!@#%^&*()]+?}$/
|
|
17
|
+
const REG_PUNCTUATION = /[!-/:-@[-`{-~ ]/
|
|
18
|
+
const REG_JAPANESE = /\p{Script=Hiragana}|\p{Script=Katakana}|\p{Script=Han}|\p{General_Category=Punctuation}|\p{General_Category=Symbol}|\p{General_Category=Format}|\p{Emoji}/u // ひらがな|カタカナ|漢字|句読点|記号|フォーマット文字|絵文字
|
|
19
|
+
|
|
20
|
+
const REG_MARKDOWN_HTML = /^\[[^\[\]]+\]\([^)]+\)$|^<([a-zA-Z][a-zA-Z0-9]*)[^>]*>([^<]+<\/\1>)$|^`[^`]+`$|^\$[^$]+\$$/ // for mixed-language context detection
|
|
21
|
+
|
|
22
|
+
const hasBackslash = (state, start) => {
|
|
23
|
+
let slashNum = 0
|
|
24
|
+
let i = start - 1
|
|
25
|
+
const src = state.src
|
|
26
|
+
// Early exit if no backslash at all
|
|
27
|
+
if (i < 0 || src.charCodeAt(i) !== CHAR_BACKSLASH) {
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
// Count consecutive backslashes efficiently
|
|
31
|
+
while (i >= 0 && src.charCodeAt(i) === CHAR_BACKSLASH) {
|
|
32
|
+
slashNum++
|
|
33
|
+
i--
|
|
34
|
+
}
|
|
35
|
+
return slashNum % 2 === 1
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const findMatchingBracket = (state, start, max, openChar, closeChar) => {
|
|
39
|
+
let depth = 1
|
|
40
|
+
let pos = start + 1
|
|
41
|
+
const src = state.src
|
|
42
|
+
while (pos < max) {
|
|
43
|
+
const ch = src.charCodeAt(pos)
|
|
44
|
+
if (ch === openChar && !hasBackslash(state, pos)) {
|
|
45
|
+
depth++
|
|
46
|
+
} else if (ch === closeChar && !hasBackslash(state, pos)) {
|
|
47
|
+
depth--
|
|
48
|
+
if (depth === 0) return pos
|
|
49
|
+
}
|
|
50
|
+
pos++
|
|
51
|
+
}
|
|
52
|
+
return -1
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const findRefRangeIndex = (pos, refRanges) => {
|
|
56
|
+
if (!refRanges || refRanges.length === 0) return -1
|
|
57
|
+
const cache = refRanges.__cache
|
|
58
|
+
if (cache && cache.has(pos)) return cache.get(pos)
|
|
59
|
+
let left = 0
|
|
60
|
+
let right = refRanges.length - 1
|
|
61
|
+
while (left <= right) {
|
|
62
|
+
const mid = left + Math.floor((right - left) / 2)
|
|
63
|
+
const range = refRanges[mid]
|
|
64
|
+
if (pos < range.start) {
|
|
65
|
+
right = mid - 1
|
|
66
|
+
} else if (pos > range.end) {
|
|
67
|
+
left = mid + 1
|
|
68
|
+
} else {
|
|
69
|
+
const result = range.hasReference ? mid : -1
|
|
70
|
+
if (cache) cache.set(pos, result)
|
|
71
|
+
return result
|
|
72
|
+
}
|
|
26
73
|
}
|
|
27
|
-
|
|
74
|
+
if (cache) cache.set(pos, -1)
|
|
75
|
+
return -1
|
|
28
76
|
}
|
|
29
|
-
|
|
30
|
-
|
|
77
|
+
|
|
78
|
+
// Detect reference-link label ranges within the current inline slice
|
|
79
|
+
const computeReferenceRanges = (state, start, max) => {
|
|
31
80
|
const src = state.src
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const asteriskToken = state.push(type, '', 0)
|
|
57
|
-
asteriskToken.content = content
|
|
58
|
-
i++
|
|
59
|
-
continue
|
|
60
|
-
}
|
|
61
|
-
if (opt.mditAttrs && attrsIsText.val && i + 1 < inlines.length) {
|
|
62
|
-
const hasImmediatelyAfterAsteriskClose = inlines[i+1].type === attrsIsText.tag + '_close'
|
|
63
|
-
if (hasImmediatelyAfterAsteriskClose && REG_ATTRS.test(content)) {
|
|
64
|
-
const attrsToken = state.push(type, '', 0)
|
|
65
|
-
|
|
66
|
-
const hasBackslashBeforeCurlyAttribute = content.match(/(\\+){/)
|
|
67
|
-
if (hasBackslashBeforeCurlyAttribute) {
|
|
68
|
-
if (hasBackslashBeforeCurlyAttribute[1].length === 1) {
|
|
69
|
-
attrsToken.content = content.replace(/\\{/, '{')
|
|
70
|
-
} else {
|
|
71
|
-
let backSlashNum = Math.floor(hasBackslashBeforeCurlyAttribute[1].length / 2)
|
|
72
|
-
let k = 0
|
|
73
|
-
let backSlash = ''
|
|
74
|
-
while (k < backSlashNum) {
|
|
75
|
-
backSlash += '\\'
|
|
76
|
-
k++
|
|
81
|
+
const references = state.env && state.env.references
|
|
82
|
+
const hasReferences = references && Object.keys(references).length > 0
|
|
83
|
+
const firstBracket = src.indexOf('[', start)
|
|
84
|
+
if (firstBracket === -1 || firstBracket >= max) return []
|
|
85
|
+
const ranges = []
|
|
86
|
+
let pos = start
|
|
87
|
+
while (pos < max) {
|
|
88
|
+
if (src.charCodeAt(pos) === CHAR_OPEN_BRACKET && !hasBackslash(state, pos)) {
|
|
89
|
+
const labelClose = findMatchingBracket(state, pos, max, CHAR_OPEN_BRACKET, CHAR_CLOSE_BRACKET)
|
|
90
|
+
if (labelClose !== -1) {
|
|
91
|
+
const nextPos = labelClose + 1
|
|
92
|
+
if (nextPos < max && src.charCodeAt(nextPos) === CHAR_OPEN_BRACKET && !hasBackslash(state, nextPos)) {
|
|
93
|
+
const refClose = findMatchingBracket(state, nextPos, max, CHAR_OPEN_BRACKET, CHAR_CLOSE_BRACKET)
|
|
94
|
+
if (refClose !== -1) {
|
|
95
|
+
let hasReference = false
|
|
96
|
+
if (hasReferences) {
|
|
97
|
+
if (refClose === nextPos + 1) {
|
|
98
|
+
const labelRaw = src.slice(pos + 1, labelClose)
|
|
99
|
+
const normalizedLabel = normalizeReferenceCandidate(state, labelRaw, { useClean: true })
|
|
100
|
+
hasReference = !!references[normalizedLabel]
|
|
101
|
+
} else {
|
|
102
|
+
const refRaw = src.slice(nextPos + 1, refClose)
|
|
103
|
+
const normalizedRef = normalizeReferenceCandidate(state, refRaw)
|
|
104
|
+
hasReference = !!references[normalizedRef]
|
|
77
105
|
}
|
|
78
|
-
attrsToken.content = content.replace(/\\+{/, backSlash + '{')
|
|
79
106
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const childTokens = state.md.parseInline(content, state.env)
|
|
90
|
-
if (childTokens[0] && childTokens[0].children) {
|
|
91
|
-
let j = 0
|
|
92
|
-
while (j < childTokens[0].children.length) {
|
|
93
|
-
const t = childTokens[0].children[j]
|
|
94
|
-
if (t.type === 'softbreak' && !opt.mdBreaks) {
|
|
95
|
-
t.type = 'text'
|
|
96
|
-
t.tag = ''
|
|
97
|
-
t.content = '\n'
|
|
98
|
-
}
|
|
99
|
-
if (!opt.mditAttrs && t.tag === 'br') {
|
|
100
|
-
t.tag = ''
|
|
101
|
-
t.content = '\n'
|
|
107
|
+
if (hasReference) {
|
|
108
|
+
ranges.push({ start: pos, end: labelClose, hasReference: true })
|
|
109
|
+
ranges.push({ start: nextPos, end: refClose, hasReference: true })
|
|
110
|
+
}
|
|
111
|
+
pos = refClose
|
|
112
|
+
continue
|
|
102
113
|
}
|
|
103
|
-
const token = state.push(t.type, t.tag, t.nesting)
|
|
104
|
-
token.attrs = t.attrs
|
|
105
|
-
token.map = t.map
|
|
106
|
-
token.level = t.level
|
|
107
|
-
token.children = t.children
|
|
108
|
-
token.content = t.content
|
|
109
|
-
token.markup = t.markup
|
|
110
|
-
token.info = t.info
|
|
111
|
-
token.meta = t.meta
|
|
112
|
-
token.block = t.block
|
|
113
|
-
token.hidden = t.hidden
|
|
114
|
-
j++
|
|
115
114
|
}
|
|
116
115
|
}
|
|
117
116
|
}
|
|
118
|
-
|
|
119
|
-
if (/_close$/.test(type)) {
|
|
120
|
-
const closeToken = state.push(type, tag, -1)
|
|
121
|
-
closeToken.markup = tag === 'strong' ? '**' : '*'
|
|
122
|
-
attrsIsText = {
|
|
123
|
-
val: false,
|
|
124
|
-
tag: '',
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
i++
|
|
117
|
+
pos++
|
|
129
118
|
}
|
|
119
|
+
if (ranges.length) {
|
|
120
|
+
ranges.__cache = new Map()
|
|
121
|
+
}
|
|
122
|
+
return ranges
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const copyInlineTokenFields = (dest, src) => {
|
|
126
|
+
if (src.attrs) dest.attrs = src.attrs
|
|
127
|
+
if (src.map) dest.map = src.map
|
|
128
|
+
dest.level = src.level
|
|
129
|
+
if (src.children) dest.children = src.children
|
|
130
|
+
dest.content = src.content
|
|
131
|
+
dest.markup = src.markup
|
|
132
|
+
if (src.info) dest.info = src.info
|
|
133
|
+
if (src.meta) dest.meta = src.meta
|
|
134
|
+
dest.block = src.block
|
|
135
|
+
dest.hidden = src.hidden
|
|
130
136
|
}
|
|
131
137
|
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
sp: s,
|
|
136
|
-
e: e,
|
|
137
|
-
ep: e,
|
|
138
|
-
len: len,
|
|
139
|
-
type: type,
|
|
140
|
-
check: type === 'text',
|
|
138
|
+
const inlineHasCollapsedRef = (state) => {
|
|
139
|
+
if (state.__strongJaHasCollapsedRefs === undefined) {
|
|
140
|
+
state.__strongJaHasCollapsedRefs = /\[[^\]]*\]\s*\[[^\]]*\]/.test(state.src)
|
|
141
141
|
}
|
|
142
|
-
|
|
143
|
-
inlines.push(inline)
|
|
142
|
+
return state.__strongJaHasCollapsedRefs
|
|
144
143
|
}
|
|
145
144
|
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
145
|
+
const registerCollapsedRefTarget = (state) => {
|
|
146
|
+
const env = state.env
|
|
147
|
+
if (!env.__strongJaCollapsedTargets) {
|
|
148
|
+
env.__strongJaCollapsedTargets = []
|
|
149
|
+
env.__strongJaCollapsedTargetSet = typeof WeakSet !== 'undefined' ? new WeakSet() : null
|
|
150
|
+
}
|
|
151
|
+
const targets = env.__strongJaCollapsedTargets
|
|
152
|
+
const targetSet = env.__strongJaCollapsedTargetSet
|
|
153
|
+
if (targetSet) {
|
|
154
|
+
if (targetSet.has(state.tokens)) return
|
|
155
|
+
targetSet.add(state.tokens)
|
|
156
|
+
} else if (targets.includes(state.tokens)) {
|
|
157
|
+
return
|
|
158
158
|
}
|
|
159
|
-
|
|
159
|
+
targets.push(state.tokens)
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
const
|
|
162
|
+
const setToken = (state, inlines, opt) => {
|
|
163
|
+
const src = state.src
|
|
164
|
+
let i = 0
|
|
165
|
+
let attrsIsText = {
|
|
166
|
+
val: false,
|
|
167
|
+
tag: '',
|
|
168
|
+
}
|
|
169
|
+
while (i < inlines.length) {
|
|
170
|
+
let type = inlines[i].type
|
|
171
|
+
const tag = type.replace(/(?:_open|_close)$/, '')
|
|
172
|
+
|
|
173
|
+
if (/_open$/.test(type)) {
|
|
174
|
+
const startToken = state.push(type, tag, 1)
|
|
175
|
+
startToken.markup = tag === 'strong' ? '**' : '*'
|
|
176
|
+
attrsIsText = {
|
|
177
|
+
val: true,
|
|
178
|
+
tag: tag,
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (type === 'html_inline') {
|
|
183
|
+
type = 'text'
|
|
184
|
+
}
|
|
185
|
+
if (type === 'text') {
|
|
186
|
+
let content = src.slice(inlines[i].s, inlines[i].e + 1)
|
|
187
|
+
if (REG_ASTERISKS.test(content)) {
|
|
188
|
+
const asteriskToken = state.push(type, '', 0)
|
|
189
|
+
asteriskToken.content = content
|
|
190
|
+
i++
|
|
191
|
+
continue
|
|
192
|
+
}
|
|
193
|
+
if (opt.mditAttrs && attrsIsText.val && i + 1 < inlines.length) {
|
|
194
|
+
const hasImmediatelyAfterAsteriskClose = inlines[i+1].type === attrsIsText.tag + '_close'
|
|
195
|
+
if (hasImmediatelyAfterAsteriskClose && REG_ATTRS.test(content)) {
|
|
196
|
+
const attrsToken = state.push(type, '', 0)
|
|
197
|
+
|
|
198
|
+
const hasBackslashBeforeCurlyAttribute = content.match(/(\\+){/)
|
|
199
|
+
if (hasBackslashBeforeCurlyAttribute) {
|
|
200
|
+
if (hasBackslashBeforeCurlyAttribute[1].length === 1) {
|
|
201
|
+
attrsToken.content = content.replace(/\\{/, '{')
|
|
202
|
+
} else {
|
|
203
|
+
let backSlashNum = Math.floor(hasBackslashBeforeCurlyAttribute[1].length / 2)
|
|
204
|
+
let k = 0
|
|
205
|
+
let backSlash = ''
|
|
206
|
+
while (k < backSlashNum) {
|
|
207
|
+
backSlash += '\\'
|
|
208
|
+
k++
|
|
209
|
+
}
|
|
210
|
+
attrsToken.content = content.replace(/\\+{/, backSlash + '{')
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
attrsToken.content = content
|
|
214
|
+
}
|
|
215
|
+
attrsIsText.val = false
|
|
216
|
+
i++
|
|
217
|
+
continue
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const childTokens = state.md.parseInline(content, state.env)
|
|
222
|
+
if (childTokens[0] && childTokens[0].children) {
|
|
223
|
+
let j = 0
|
|
224
|
+
while (j < childTokens[0].children.length) {
|
|
225
|
+
const t = childTokens[0].children[j]
|
|
226
|
+
if (t.type === 'softbreak' && !opt.mdBreaks) {
|
|
227
|
+
t.type = 'text'
|
|
228
|
+
t.tag = ''
|
|
229
|
+
t.content = '\n'
|
|
230
|
+
}
|
|
231
|
+
if (!opt.mditAttrs && t.tag === 'br') {
|
|
232
|
+
t.tag = ''
|
|
233
|
+
t.content = '\n'
|
|
234
|
+
}
|
|
235
|
+
const token = state.push(t.type, t.tag, t.nesting)
|
|
236
|
+
copyInlineTokenFields(token, t)
|
|
237
|
+
j++
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (/_close$/.test(type)) {
|
|
243
|
+
const closeToken = state.push(type, tag, -1)
|
|
244
|
+
closeToken.markup = tag === 'strong' ? '**' : '*'
|
|
245
|
+
attrsIsText = {
|
|
246
|
+
val: false,
|
|
247
|
+
tag: '',
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
i++
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const pushInlines = (inlines, s, e, len, type, tag, tagType) => {
|
|
256
|
+
const inline = {
|
|
257
|
+
s: s,
|
|
258
|
+
sp: s,
|
|
259
|
+
e: e,
|
|
260
|
+
ep: e,
|
|
261
|
+
len: len,
|
|
262
|
+
type: type,
|
|
263
|
+
check: type === 'text',
|
|
264
|
+
}
|
|
265
|
+
if (tag) inline.tag = [tag, tagType]
|
|
266
|
+
inlines.push(inline)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const findNextSymbolPos = (state, n, max, symbol) => {
|
|
163
270
|
const src = state.src
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
let noMark = ''
|
|
169
|
-
let textStart = n
|
|
170
|
-
|
|
171
|
-
// Infinite loop prevention
|
|
172
|
-
const maxIterations = srcLen * 2 // Safe upper bound
|
|
173
|
-
let iterations = 0
|
|
174
|
-
|
|
175
|
-
while (n < srcLen) {
|
|
176
|
-
// Prevent infinite loops
|
|
177
|
-
iterations++
|
|
178
|
-
if (iterations > maxIterations) {
|
|
179
|
-
// Add remaining text as-is and exit safely
|
|
180
|
-
if (textStart < srcLen) {
|
|
181
|
-
pushInlines(inlines, textStart, srcLen - 1, srcLen - textStart, 'text')
|
|
182
|
-
}
|
|
183
|
-
break
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const currentChar = src.charCodeAt(n)
|
|
187
|
-
let nextSymbolPos = -1
|
|
188
|
-
|
|
189
|
-
// Inline code (backticks)
|
|
190
|
-
if (currentChar === CHAR_BACKTICK && !hasBackslash(state, n)) {
|
|
191
|
-
[nextSymbolPos, noMark] = hasNextSymbol(state, n, srcLen, CHAR_BACKTICK, noMark)
|
|
192
|
-
if (nextSymbolPos !== -1) {
|
|
193
|
-
if (nextSymbolPos === srcLen - 1) {
|
|
194
|
-
pushInlines(inlines, textStart, nextSymbolPos, nextSymbolPos - textStart + 1, 'text')
|
|
195
|
-
break
|
|
196
|
-
}
|
|
197
|
-
n = nextSymbolPos + 1
|
|
198
|
-
continue
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Inline math ($...$)
|
|
203
|
-
if (opt.dollarMath && currentChar === CHAR_DOLLAR && !hasBackslash(state, n)) {
|
|
204
|
-
[nextSymbolPos, noMark] = hasNextSymbol(state, n, srcLen, CHAR_DOLLAR, noMark)
|
|
205
|
-
if (nextSymbolPos !== -1) {
|
|
206
|
-
if (nextSymbolPos === srcLen - 1) {
|
|
207
|
-
pushInlines(inlines, textStart, nextSymbolPos, nextSymbolPos - textStart + 1, 'text')
|
|
208
|
-
break
|
|
209
|
-
}
|
|
210
|
-
n = nextSymbolPos + 1
|
|
211
|
-
continue
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// HTML tags
|
|
216
|
-
if (htmlEnabled && currentChar === CHAR_LT && !hasBackslash(state, n)) {
|
|
217
|
-
let foundClosingTag = false
|
|
218
|
-
for (let i = n + 1; i < srcLen; i++) {
|
|
219
|
-
if (src.charCodeAt(i) === CHAR_GT && !hasBackslash(state, i)) {
|
|
220
|
-
if (noMark.length !== 0) {
|
|
221
|
-
pushInlines(inlines, textStart, n - 1, n - textStart, 'text')
|
|
222
|
-
noMark = ''
|
|
223
|
-
}
|
|
224
|
-
let tag = src.slice(n + 1, i)
|
|
225
|
-
let tagType
|
|
226
|
-
if (tag.charCodeAt(0) === CHAR_SLASH) {
|
|
227
|
-
tag = tag.slice(1)
|
|
228
|
-
tagType = 'close'
|
|
229
|
-
} else {
|
|
230
|
-
tagType = 'open'
|
|
231
|
-
}
|
|
232
|
-
pushInlines(inlines, n, i, i - n + 1, 'html_inline', tag, tagType)
|
|
233
|
-
textStart = i + 1
|
|
234
|
-
n = i + 1
|
|
235
|
-
foundClosingTag = true
|
|
236
|
-
break
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
if (foundClosingTag) {
|
|
240
|
-
continue
|
|
241
|
-
}
|
|
242
|
-
// If no closing tag found, treat as regular character to prevent infinite loops
|
|
271
|
+
if (src.charCodeAt(n) !== symbol || hasBackslash(state, n)) return -1
|
|
272
|
+
for (let i = n + 1; i < max; i++) {
|
|
273
|
+
if (src.charCodeAt(i) === symbol && !hasBackslash(state, i)) {
|
|
274
|
+
return i
|
|
243
275
|
}
|
|
244
|
-
|
|
245
|
-
// Asterisk handling
|
|
246
|
-
if (currentChar === CHAR_ASTERISK && !hasBackslash(state, n)) {
|
|
247
|
-
if (n !== 0 && noMark.length !== 0) {
|
|
248
|
-
pushInlines(inlines, textStart, n - 1, n - textStart, 'text')
|
|
249
|
-
noMark = ''
|
|
250
|
-
}
|
|
251
|
-
if (n === srcLen - 1) {
|
|
252
|
-
pushInlines(inlines, n, n, 1, '')
|
|
253
|
-
break
|
|
254
|
-
}
|
|
255
|
-
let i = n + 1
|
|
256
|
-
while (i < srcLen && src.charCodeAt(i) === CHAR_ASTERISK) {
|
|
257
|
-
i++
|
|
258
|
-
}
|
|
259
|
-
if (i === srcLen) {
|
|
260
|
-
pushInlines(inlines, n, i - 1, i - n, '')
|
|
261
|
-
} else {
|
|
262
|
-
pushInlines(inlines, n, i - 1, i - n, '')
|
|
263
|
-
textStart = i
|
|
264
|
-
}
|
|
265
|
-
n = i
|
|
266
|
-
continue
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Regular character
|
|
270
|
-
noMark += src[n]
|
|
271
|
-
if (n === srcLen - 1) {
|
|
272
|
-
pushInlines(inlines, textStart, n, n - textStart + 1, 'text')
|
|
273
|
-
break
|
|
274
|
-
}
|
|
275
|
-
n++
|
|
276
276
|
}
|
|
277
|
-
return
|
|
277
|
+
return -1
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
if (marks[mid].s > opts.s) {
|
|
285
|
-
right = mid
|
|
286
|
-
} else {
|
|
287
|
-
left = mid + 1
|
|
288
|
-
}
|
|
280
|
+
const processSymbolPair = (state, n, srcLen, symbol, noMark, textStart, pushInlines) => {
|
|
281
|
+
const nextSymbolPos = findNextSymbolPos(state, n, srcLen, symbol)
|
|
282
|
+
if (nextSymbolPos === -1) {
|
|
283
|
+
return { shouldBreak: false, shouldContinue: false, newN: n, newNoMark: noMark }
|
|
289
284
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const
|
|
294
|
-
if (
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
while (i < inlinesLength) {
|
|
298
|
-
if (inlines[i].len === 0 || inlines[i].check) { i++; continue }
|
|
299
|
-
if (inlines[i].type !== '') { i++; continue }
|
|
300
|
-
|
|
301
|
-
if (inlines[i].len > 1) {
|
|
302
|
-
const mixedCheck = checkMixedLanguagePattern(state, inlines, n, i, opt)
|
|
303
|
-
if (mixedCheck.shouldBlock) {
|
|
304
|
-
return [n, 0]
|
|
305
|
-
}
|
|
306
|
-
break
|
|
307
|
-
}
|
|
308
|
-
i++
|
|
309
|
-
}
|
|
285
|
+
const src = state.src
|
|
286
|
+
const innerText = src.slice(n + 1, nextSymbolPos)
|
|
287
|
+
const markup = src.slice(n, nextSymbolPos + 1)
|
|
288
|
+
const newNoMark = noMark + innerText + markup
|
|
289
|
+
if (nextSymbolPos === srcLen - 1) {
|
|
290
|
+
pushInlines(textStart, nextSymbolPos, nextSymbolPos - textStart + 1, 'text')
|
|
291
|
+
return { shouldBreak: true, newN: nextSymbolPos + 1, newNoMark }
|
|
310
292
|
}
|
|
311
|
-
|
|
293
|
+
return { shouldBreak: false, shouldContinue: true, newN: nextSymbolPos + 1, newNoMark }
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const processTextSegment = (inlines, textStart, n, noMark) => {
|
|
297
|
+
if (n !== 0 && noMark.length !== 0) {
|
|
298
|
+
pushInlines(inlines, textStart, n - 1, n - textStart, 'text')
|
|
299
|
+
return ''
|
|
300
|
+
}
|
|
301
|
+
return noMark
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const createInlines = (state, start, max, opt) => {
|
|
305
|
+
const src = state.src
|
|
306
|
+
const srcLen = max
|
|
307
|
+
const htmlEnabled = state.md.options.html
|
|
308
|
+
let n = start
|
|
309
|
+
let inlines = []
|
|
310
|
+
let noMark = ''
|
|
311
|
+
let textStart = n
|
|
312
|
+
|
|
313
|
+
// Infinite loop prevention
|
|
314
|
+
const maxIterations = srcLen * 2 // Safe upper bound
|
|
315
|
+
let iterations = 0
|
|
316
|
+
|
|
317
|
+
while (n < srcLen) {
|
|
318
|
+
// Prevent infinite loops
|
|
319
|
+
iterations++
|
|
320
|
+
if (iterations > maxIterations) {
|
|
321
|
+
// Add remaining text as-is and exit safely
|
|
322
|
+
if (textStart < srcLen) {
|
|
323
|
+
pushInlines(inlines, textStart, srcLen - 1, srcLen - textStart, 'text')
|
|
324
|
+
}
|
|
325
|
+
break
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const currentChar = src.charCodeAt(n)
|
|
329
|
+
|
|
330
|
+
// Unified escape check
|
|
331
|
+
let isEscaped = false
|
|
332
|
+
if (currentChar === CHAR_ASTERISK || currentChar === CHAR_BACKTICK ||
|
|
333
|
+
(opt.dollarMath && currentChar === CHAR_DOLLAR) ||
|
|
334
|
+
(htmlEnabled && currentChar === CHAR_LT)) {
|
|
335
|
+
isEscaped = hasBackslash(state, n)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Asterisk handling
|
|
339
|
+
if (currentChar === CHAR_ASTERISK) {
|
|
340
|
+
if (!isEscaped) {
|
|
341
|
+
noMark = processTextSegment(inlines, textStart, n, noMark)
|
|
342
|
+
if (n === srcLen - 1) {
|
|
343
|
+
pushInlines(inlines, n, n, 1, '')
|
|
344
|
+
break
|
|
345
|
+
}
|
|
346
|
+
let i = n + 1
|
|
347
|
+
while (i < srcLen && src.charCodeAt(i) === CHAR_ASTERISK) {
|
|
348
|
+
i++
|
|
349
|
+
}
|
|
350
|
+
if (i === srcLen) {
|
|
351
|
+
pushInlines(inlines, n, i - 1, i - n, '')
|
|
352
|
+
} else {
|
|
353
|
+
pushInlines(inlines, n, i - 1, i - n, '')
|
|
354
|
+
textStart = i
|
|
355
|
+
}
|
|
356
|
+
n = i
|
|
357
|
+
continue
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Inline code (backticks)
|
|
362
|
+
if (currentChar === CHAR_BACKTICK) {
|
|
363
|
+
if (!isEscaped) {
|
|
364
|
+
const result = processSymbolPair(state, n, srcLen, CHAR_BACKTICK, noMark, textStart,
|
|
365
|
+
(start, end, len, type) => pushInlines(inlines, start, end, len, type))
|
|
366
|
+
if (result.shouldBreak) break
|
|
367
|
+
if (result.shouldContinue) {
|
|
368
|
+
n = result.newN
|
|
369
|
+
noMark = result.newNoMark
|
|
370
|
+
continue
|
|
371
|
+
}
|
|
372
|
+
noMark = result.newNoMark
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Inline math ($...$)
|
|
377
|
+
if (opt.dollarMath && currentChar === CHAR_DOLLAR) {
|
|
378
|
+
if (!isEscaped) {
|
|
379
|
+
const result = processSymbolPair(state, n, srcLen, CHAR_DOLLAR, noMark, textStart,
|
|
380
|
+
(start, end, len, type) => pushInlines(inlines, start, end, len, type))
|
|
381
|
+
if (result.shouldBreak) break
|
|
382
|
+
if (result.shouldContinue) {
|
|
383
|
+
n = result.newN
|
|
384
|
+
noMark = result.newNoMark
|
|
385
|
+
continue
|
|
386
|
+
}
|
|
387
|
+
noMark = result.newNoMark
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// HTML tags
|
|
392
|
+
if (htmlEnabled && currentChar === CHAR_LT) {
|
|
393
|
+
if (!isEscaped) {
|
|
394
|
+
let foundClosingTag = false
|
|
395
|
+
for (let i = n + 1; i < srcLen; i++) {
|
|
396
|
+
if (src.charCodeAt(i) === CHAR_GT && !hasBackslash(state, i)) {
|
|
397
|
+
noMark = processTextSegment(inlines, textStart, n, noMark)
|
|
398
|
+
let tag = src.slice(n + 1, i)
|
|
399
|
+
let tagType
|
|
400
|
+
if (tag.charCodeAt(0) === CHAR_SLASH) {
|
|
401
|
+
tag = tag.slice(1)
|
|
402
|
+
tagType = 'close'
|
|
403
|
+
} else {
|
|
404
|
+
tagType = 'open'
|
|
405
|
+
}
|
|
406
|
+
pushInlines(inlines, n, i, i - n + 1, 'html_inline', tag, tagType)
|
|
407
|
+
textStart = i + 1
|
|
408
|
+
n = i + 1
|
|
409
|
+
foundClosingTag = true
|
|
410
|
+
break
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if (foundClosingTag) {
|
|
414
|
+
continue
|
|
415
|
+
}
|
|
416
|
+
// If no closing tag found, treat as regular character to prevent infinite loops
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Regular character
|
|
421
|
+
noMark += src[n]
|
|
422
|
+
if (n === srcLen - 1) {
|
|
423
|
+
pushInlines(inlines, textStart, n, n - textStart + 1, 'text')
|
|
424
|
+
break
|
|
425
|
+
}
|
|
426
|
+
n++
|
|
427
|
+
}
|
|
428
|
+
return inlines
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const pushMark = (marks, opts) => {
|
|
432
|
+
// Maintain sorted order during insertion
|
|
433
|
+
const newMark = {
|
|
434
|
+
nest: opts.nest,
|
|
435
|
+
s: opts.s,
|
|
436
|
+
e: opts.e,
|
|
437
|
+
len: opts.len,
|
|
438
|
+
oLen: opts.oLen,
|
|
439
|
+
type: opts.type
|
|
440
|
+
}
|
|
441
|
+
// Binary search for insertion point to maintain sorted order
|
|
442
|
+
let left = 0
|
|
443
|
+
let right = marks.length
|
|
444
|
+
while (left < right) {
|
|
445
|
+
const mid = Math.floor((left + right) / 2)
|
|
446
|
+
if (marks[mid].s <= newMark.s) {
|
|
447
|
+
left = mid + 1
|
|
448
|
+
} else {
|
|
449
|
+
right = mid
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
marks.splice(left, 0, newMark)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const setStrong = (state, inlines, marks, n, memo, opt, nestTracker, refRanges) => {
|
|
457
|
+
if (opt.disallowMixed === true) {
|
|
458
|
+
let i = n + 1
|
|
459
|
+
const inlinesLength = inlines.length
|
|
460
|
+
while (i < inlinesLength) {
|
|
461
|
+
if (inlines[i].len === 0 || inlines[i].check) { i++; continue }
|
|
462
|
+
if (inlines[i].type !== '') { i++; continue }
|
|
463
|
+
|
|
464
|
+
if (inlines[i].len > 1) {
|
|
465
|
+
const mixedCheck = checkMixedLanguagePattern(state, inlines, n, i, opt)
|
|
466
|
+
if (mixedCheck.shouldBlock) {
|
|
467
|
+
return [n, 0]
|
|
468
|
+
}
|
|
469
|
+
break
|
|
470
|
+
}
|
|
471
|
+
i++
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const strongOpenRange = findRefRangeIndex(inlines[n].s, refRanges)
|
|
312
476
|
let i = n + 1
|
|
313
|
-
let j = 0
|
|
314
|
-
let nest = 0
|
|
315
|
-
let insideTagsIsClose = 1
|
|
316
|
-
const inlinesLength = inlines.length
|
|
317
|
-
while (i < inlinesLength) {
|
|
318
|
-
if (inlines[i].type !== '') { i++; continue }
|
|
319
|
-
if (inlines[i].len === 0 || inlines[i].check) { i++; continue }
|
|
320
|
-
if (inlines[i].type === 'html_inline') {
|
|
321
|
-
inlines[i].check = true
|
|
322
|
-
insideTagsIsClose = checkInsideTags(inlines, i, memo)
|
|
323
|
-
if (insideTagsIsClose === -1) return [n, nest]
|
|
324
|
-
if (insideTagsIsClose === 0) { i++; continue }
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
477
|
+
let j = 0
|
|
478
|
+
let nest = 0
|
|
479
|
+
let insideTagsIsClose = 1
|
|
480
|
+
const inlinesLength = inlines.length
|
|
481
|
+
while (i < inlinesLength) {
|
|
482
|
+
if (inlines[i].type !== '') { i++; continue }
|
|
483
|
+
if (inlines[i].len === 0 || inlines[i].check) { i++; continue }
|
|
484
|
+
if (inlines[i].type === 'html_inline') {
|
|
485
|
+
inlines[i].check = true
|
|
486
|
+
insideTagsIsClose = checkInsideTags(inlines, i, memo)
|
|
487
|
+
if (insideTagsIsClose === -1) return [n, nest]
|
|
488
|
+
if (insideTagsIsClose === 0) { i++; continue }
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const closeRange = findRefRangeIndex(inlines[i].s, refRanges)
|
|
492
|
+
if (strongOpenRange !== closeRange) { i++; continue }
|
|
493
|
+
|
|
494
|
+
nest = checkNest(inlines, marks, n, i, nestTracker)
|
|
495
|
+
if (nest === -1) return [n, nest]
|
|
496
|
+
|
|
497
|
+
if (inlines[i].len === 1 && inlines[n].len > 2) {
|
|
498
|
+
pushMark(marks, {
|
|
499
|
+
nest: nest,
|
|
500
|
+
s: inlines[n].ep,
|
|
501
|
+
e: inlines[n].ep,
|
|
502
|
+
len: 1,
|
|
503
|
+
oLen: inlines[n].len - 1,
|
|
504
|
+
type: 'em_open'
|
|
505
|
+
})
|
|
506
|
+
pushMark(marks, {
|
|
507
|
+
nest: nest,
|
|
508
|
+
s: inlines[i].sp,
|
|
509
|
+
e: inlines[i].ep,
|
|
510
|
+
len: 1,
|
|
511
|
+
oLen: inlines[i].len - 1,
|
|
512
|
+
type: 'em_close'
|
|
513
|
+
})
|
|
514
|
+
inlines[n].len -= 1
|
|
515
|
+
inlines[n].ep -= 1
|
|
516
|
+
inlines[i].len -= 1
|
|
517
|
+
if (inlines[i].len > 0) inlines[i].sp += 1
|
|
518
|
+
if (insideTagsIsClose === 1) {
|
|
519
|
+
const [newN, newNest] = setEm(state, inlines, marks, n, memo, opt, null, nestTracker, refRanges)
|
|
520
|
+
n = newN
|
|
521
|
+
nest = newNest
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
let strongNum = Math.trunc(Math.min(inlines[n].len, inlines[i].len) / 2)
|
|
525
|
+
|
|
526
|
+
if (inlines[i].len > 1) {
|
|
527
|
+
if (hasPunctuationOrNonJapanese(state, inlines, n, i, opt, refRanges)) {
|
|
528
|
+
if (memo.inlineMarkEnd) {
|
|
529
|
+
marks.push(...createMarks(state, inlines, i, inlinesLength - 1, memo, opt, refRanges))
|
|
530
|
+
if (inlines[i].len === 0) { i++; continue }
|
|
531
|
+
} else {
|
|
532
|
+
return [n, nest]
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
j = 0
|
|
537
|
+
while (j < strongNum) {
|
|
538
|
+
pushMark(marks, {
|
|
539
|
+
nest: nest + strongNum - 1 - j,
|
|
540
|
+
s: inlines[n].ep - 1,
|
|
541
|
+
e: inlines[n].ep,
|
|
542
|
+
len: 2,
|
|
543
|
+
oLen: inlines[n].len - 2,
|
|
544
|
+
type: 'strong_open'
|
|
545
|
+
})
|
|
546
|
+
inlines[n].ep -= 2
|
|
547
|
+
inlines[n].len -= 2
|
|
548
|
+
pushMark(marks, {
|
|
549
|
+
nest: nest + strongNum - 1 - j,
|
|
550
|
+
s: inlines[i].sp,
|
|
551
|
+
e: inlines[i].sp + 1,
|
|
552
|
+
len: 2,
|
|
553
|
+
oLen: inlines[i].len - 2,
|
|
554
|
+
type: 'strong_close'
|
|
555
|
+
})
|
|
556
|
+
inlines[i].sp += 2
|
|
557
|
+
inlines[i].len -= 2
|
|
558
|
+
j++
|
|
559
|
+
}
|
|
560
|
+
if (inlines[n].len === 0) return [n, nest]
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (inlines[n].len === 1 && inlines[i].len > 0) {
|
|
564
|
+
nest++
|
|
565
|
+
const [newN, newNest] = setEm(state, inlines, marks, n, memo, opt, nest, nestTracker, refRanges)
|
|
566
|
+
n = newN
|
|
567
|
+
nest = newNest
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
i++
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (n == 0 && memo.inlineMarkEnd) {
|
|
574
|
+
marks.push(...createMarks(state, inlines, n + 1, inlinesLength - 1, memo, opt, refRanges))
|
|
575
|
+
}
|
|
576
|
+
return [n, nest]
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const checkInsideTags = (inlines, i, memo) => {
|
|
580
|
+
if (inlines[i].tag === undefined) return 0
|
|
581
|
+
const tagName = inlines[i].tag[0].toLowerCase()
|
|
582
|
+
if (memo.htmlTags[tagName] === undefined) {
|
|
583
|
+
memo.htmlTags[tagName] = 0
|
|
584
|
+
}
|
|
585
|
+
if (inlines[i].tag[1] === 'open') {
|
|
586
|
+
memo.htmlTags[tagName] += 1
|
|
587
|
+
}
|
|
588
|
+
if (inlines[i].tag[1] === 'close') {
|
|
589
|
+
memo.htmlTags[tagName] -= 1
|
|
590
|
+
}
|
|
591
|
+
if (memo.htmlTags[tagName] < 0) {
|
|
592
|
+
return -1
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Direct check instead of Object.values().every()
|
|
596
|
+
for (const count of Object.values(memo.htmlTags)) {
|
|
597
|
+
if (count !== 0) return 0
|
|
598
|
+
}
|
|
599
|
+
return 1
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Check if character is ASCII punctuation or space
|
|
603
|
+
// Covers: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ and space
|
|
604
|
+
const isPunctuation = (ch) => {
|
|
605
|
+
if (!ch) return false
|
|
606
|
+
const code = ch.charCodeAt(0)
|
|
607
|
+
// ASCII punctuation: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
|
|
608
|
+
return (code >= 33 && code <= 47) || (code >= 58 && code <= 64) ||
|
|
609
|
+
(code >= 91 && code <= 96) || (code >= 123 && code <= 126) || code === 32
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Check if character is Japanese (hiragana, katakana, kanji, punctuation, symbols, format chars, emoji)
|
|
613
|
+
// Uses fast Unicode range checks for common cases, falls back to REG_JAPANESE for complex Unicode
|
|
614
|
+
const isJapanese = (ch) => {
|
|
615
|
+
if (!ch) return false
|
|
616
|
+
const code = ch.charCodeAt(0)
|
|
617
|
+
// Fast ASCII check first
|
|
618
|
+
if (code < 128) return false
|
|
619
|
+
// Hiragana: U+3040-U+309F, Katakana: U+30A0-U+30FF, Kanji: U+4E00-U+9FAF
|
|
620
|
+
return (code >= 0x3040 && code <= 0x309F) ||
|
|
621
|
+
(code >= 0x30A0 && code <= 0x30FF) ||
|
|
622
|
+
(code >= 0x4E00 && code <= 0x9FAF) ||
|
|
623
|
+
// Fallback to regex for complex Unicode cases
|
|
624
|
+
REG_JAPANESE.test(ch)
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Check if character is English (letters, numbers) or other non-Japanese characters
|
|
628
|
+
// Uses REG_JAPANESE and REG_PUNCTUATION to exclude Japanese and punctuation characters
|
|
629
|
+
const isEnglish = (ch) => {
|
|
630
|
+
if (!ch) return false
|
|
631
|
+
const code = ch.charCodeAt(0)
|
|
632
|
+
if ((code >= 65 && code <= 90) || (code >= 97 && code <= 122) || (code >= 48 && code <= 57)) {
|
|
633
|
+
return true
|
|
634
|
+
}
|
|
635
|
+
if (code < 128) {
|
|
636
|
+
return code === CHAR_SPACE || (code > 126)
|
|
637
|
+
}
|
|
638
|
+
return !REG_JAPANESE.test(ch) && !REG_PUNCTUATION.test(ch)
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const checkMixedLanguagePattern = (state, inlines, n, i, opt) => {
|
|
642
|
+
const src = state.src
|
|
643
|
+
const openPrevChar = src[inlines[n].s - 1] || ''
|
|
644
|
+
const closeNextChar = src[inlines[i].e + 1] || ''
|
|
645
|
+
|
|
646
|
+
const isEnglishPrefix = isEnglish(openPrevChar)
|
|
647
|
+
const isEnglishSuffix = isEnglish(closeNextChar)
|
|
648
|
+
if (!isEnglishPrefix && !isEnglishSuffix) {
|
|
649
|
+
return { hasEnglishContext: false, hasMarkdownOrHtml: false, shouldBlock: false }
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const contentBetween = src.slice(inlines[n].e + 1, inlines[i].s)
|
|
653
|
+
const hasMarkdownOrHtml = REG_MARKDOWN_HTML.test(contentBetween)
|
|
654
|
+
|
|
655
|
+
return {
|
|
656
|
+
hasEnglishContext: true,
|
|
657
|
+
hasMarkdownOrHtml,
|
|
658
|
+
shouldBlock: hasMarkdownOrHtml
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const hasPunctuationOrNonJapanese = (state, inlines, n, i, opt, refRanges) => {
|
|
663
|
+
const src = state.src
|
|
664
|
+
const openPrevChar = src[inlines[n].s - 1] || ''
|
|
665
|
+
const openNextChar = src[inlines[n].e + 1] || ''
|
|
666
|
+
let checkOpenNextChar = isPunctuation(openNextChar)
|
|
667
|
+
if (checkOpenNextChar && (openNextChar === '[' || openNextChar === ']')) {
|
|
668
|
+
const openNextRange = findRefRangeIndex(inlines[n].e + 1, refRanges)
|
|
669
|
+
if (openNextRange !== -1) {
|
|
670
|
+
checkOpenNextChar = false
|
|
394
671
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
672
|
+
}
|
|
673
|
+
const closePrevChar = src[inlines[i].s - 1] || ''
|
|
674
|
+
let checkClosePrevChar = isPunctuation(closePrevChar)
|
|
675
|
+
if (checkClosePrevChar && (closePrevChar === '[' || closePrevChar === ']')) {
|
|
676
|
+
const closePrevRange = findRefRangeIndex(inlines[i].s - 1, refRanges)
|
|
677
|
+
if (closePrevRange !== -1) {
|
|
678
|
+
checkClosePrevChar = false
|
|
401
679
|
}
|
|
402
|
-
|
|
403
|
-
i++
|
|
404
680
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
681
|
+
const closeNextChar = src[inlines[i].e + 1] || ''
|
|
682
|
+
const checkCloseNextChar = (isPunctuation(closeNextChar) || i === inlines.length - 1)
|
|
683
|
+
|
|
684
|
+
if (opt.disallowMixed === false) {
|
|
685
|
+
if (isEnglish(openPrevChar) || isEnglish(closeNextChar)) {
|
|
686
|
+
const contentBetween = src.slice(inlines[n].e + 1, inlines[i].s)
|
|
687
|
+
if (REG_MARKDOWN_HTML.test(contentBetween)) {
|
|
688
|
+
return false
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const result = (checkOpenNextChar || checkClosePrevChar) && !checkCloseNextChar && !(isJapanese(openPrevChar) || isJapanese(closeNextChar))
|
|
694
|
+
return result
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const setEm = (state, inlines, marks, n, memo, opt, sNest, nestTracker, refRanges) => {
|
|
698
|
+
const emOpenRange = findRefRangeIndex(inlines[n].s, refRanges)
|
|
699
|
+
if (opt.disallowMixed === true && !sNest) {
|
|
700
|
+
let i = n + 1
|
|
701
|
+
const inlinesLength = inlines.length
|
|
702
|
+
while (i < inlinesLength) {
|
|
703
|
+
if (inlines[i].len === 0 || inlines[i].check) { i++; continue }
|
|
704
|
+
if (inlines[i].type !== '') { i++; continue }
|
|
705
|
+
|
|
706
|
+
if (inlines[i].len > 0) {
|
|
707
|
+
const mixedCheck = checkMixedLanguagePattern(state, inlines, n, i, opt)
|
|
708
|
+
if (mixedCheck.shouldBlock) {
|
|
709
|
+
return [n, 0]
|
|
710
|
+
}
|
|
711
|
+
break
|
|
712
|
+
}
|
|
713
|
+
i++
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
let i = n + 1
|
|
718
|
+
let nest = 0
|
|
719
|
+
let strongPNum = 0
|
|
720
|
+
let insideTagsIsClose = 1
|
|
721
|
+
const inlinesLength = inlines.length
|
|
722
|
+
while (i < inlinesLength) {
|
|
723
|
+
if (inlines[i].len === 0 || inlines[i].check) { i++; continue }
|
|
724
|
+
if (!sNest && inlines[i].type === 'html_inline') {
|
|
725
|
+
inlines.check = true
|
|
726
|
+
insideTagsIsClose = checkInsideTags(inlines, i, memo)
|
|
727
|
+
if (insideTagsIsClose === -1) return [n, nest]
|
|
728
|
+
if (insideTagsIsClose === 0) { i++; continue }
|
|
729
|
+
}
|
|
730
|
+
if (inlines[i].type !== '') { i++; continue }
|
|
731
|
+
|
|
732
|
+
const closeRange = findRefRangeIndex(inlines[i].s, refRanges)
|
|
733
|
+
if (emOpenRange !== closeRange) {
|
|
734
|
+
i++
|
|
735
|
+
continue
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const emNum = Math.min(inlines[n].len, inlines[i].len)
|
|
739
|
+
|
|
740
|
+
if (!sNest && emNum !== 1) return [n, sNest, memo]
|
|
741
|
+
|
|
742
|
+
const hasMarkersAtStartAndEnd = (i) => {
|
|
743
|
+
let flag = memo.inlineMarkStart
|
|
744
|
+
if (!flag) return false
|
|
745
|
+
inlinesLength - 1 === i ? flag = true : flag = false
|
|
746
|
+
if (!flag) return false
|
|
747
|
+
inlines[i].len > 1 ? flag = true : flag = false
|
|
748
|
+
return flag
|
|
749
|
+
}
|
|
750
|
+
if (!sNest && inlines[i].len === 2 && !hasMarkersAtStartAndEnd(i)) {
|
|
751
|
+
strongPNum++
|
|
752
|
+
i++
|
|
753
|
+
continue
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
if (sNest) {
|
|
757
|
+
nest = sNest - 1
|
|
758
|
+
} else {
|
|
759
|
+
nest = checkNest(inlines, marks, n, i, nestTracker)
|
|
760
|
+
}
|
|
761
|
+
if (nest === -1) return [n, nest]
|
|
762
|
+
|
|
763
|
+
if (emNum === 1) {
|
|
764
|
+
if (hasPunctuationOrNonJapanese(state, inlines, n, i, opt, refRanges)) {
|
|
765
|
+
if (memo.inlineMarkEnd) {
|
|
766
|
+
marks.push(...createMarks(state, inlines, i, inlinesLength - 1, memo, opt, refRanges))
|
|
767
|
+
|
|
768
|
+
if (inlines[i].len === 0) { i++; continue }
|
|
769
|
+
} else {
|
|
770
|
+
return [n, nest]
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
if (inlines[i].len < 1) {
|
|
774
|
+
i++; continue;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
pushMark(marks, {
|
|
778
|
+
nest: nest,
|
|
779
|
+
s: inlines[n].ep,
|
|
780
|
+
e: inlines[n].ep,
|
|
781
|
+
len: 1,
|
|
782
|
+
oLen: inlines[n].len - 1,
|
|
783
|
+
type: 'em_open'
|
|
784
|
+
})
|
|
785
|
+
inlines[n].ep -= 1
|
|
786
|
+
inlines[n].len -= 1
|
|
787
|
+
|
|
788
|
+
if (strongPNum % 2 === 0 || inlines[i].len < 2) {
|
|
789
|
+
pushMark(marks, {
|
|
790
|
+
nest: nest,
|
|
791
|
+
s: inlines[i].sp,
|
|
792
|
+
e: inlines[i].sp,
|
|
793
|
+
len: 1,
|
|
794
|
+
oLen: inlines[i].len - 1,
|
|
795
|
+
type: 'em_close'
|
|
796
|
+
})
|
|
797
|
+
inlines[i].sp += 1
|
|
798
|
+
} else {
|
|
799
|
+
pushMark(marks, {
|
|
800
|
+
nest: nest,
|
|
801
|
+
s: inlines[i].ep,
|
|
802
|
+
e: inlines[i].ep,
|
|
803
|
+
len: 1,
|
|
804
|
+
oLen: inlines[i].len - 1,
|
|
805
|
+
type: 'em_close'
|
|
806
|
+
})
|
|
807
|
+
inlines[i].sp = inlines[i].ep - 1
|
|
808
|
+
inlines[i].ep -= 1
|
|
809
|
+
}
|
|
810
|
+
inlines[i].len -= 1
|
|
811
|
+
if (inlines[n].len === 0) return [n, nest]
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
i++
|
|
815
|
+
}
|
|
816
|
+
return [n, nest]
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
const setText = (inlines, marks, n, nest) => {
|
|
820
|
+
pushMark(marks, {
|
|
821
|
+
nest: nest,
|
|
822
|
+
s: inlines[n].sp,
|
|
823
|
+
e: inlines[n].ep,
|
|
824
|
+
len: inlines[n].len,
|
|
825
|
+
oLen: -1,
|
|
826
|
+
type: 'text'
|
|
827
|
+
})
|
|
828
|
+
inlines[n].len = 0
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// Nest state management
|
|
832
|
+
const createNestTracker = () => {
|
|
833
|
+
return {
|
|
834
|
+
strongNest: 0,
|
|
835
|
+
emNest: 0,
|
|
836
|
+
markIndex: 0
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
const updateNestTracker = (tracker, marks, targetPos) => {
|
|
841
|
+
while (tracker.markIndex < marks.length && marks[tracker.markIndex].s <= targetPos) {
|
|
842
|
+
const mark = marks[tracker.markIndex]
|
|
843
|
+
if (mark.type === 'strong_open') tracker.strongNest++
|
|
844
|
+
else if (mark.type === 'strong_close') tracker.strongNest--
|
|
845
|
+
else if (mark.type === 'em_open') tracker.emNest++
|
|
846
|
+
else if (mark.type === 'em_close') tracker.emNest--
|
|
847
|
+
tracker.markIndex++
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
const checkNest = (inlines, marks, n, i, nestTracker) => {
|
|
852
|
+
if (marks.length === 0) return 1
|
|
853
|
+
// Update nest state up to current position
|
|
854
|
+
updateNestTracker(nestTracker, marks, inlines[n].s)
|
|
855
|
+
|
|
856
|
+
const parentNest = nestTracker.strongNest + nestTracker.emNest
|
|
857
|
+
// Check if there's a conflicting close mark before the end position
|
|
858
|
+
let parentCloseN = nestTracker.markIndex
|
|
859
|
+
while (parentCloseN < marks.length) {
|
|
860
|
+
if (marks[parentCloseN].nest === parentNest) break
|
|
861
|
+
parentCloseN++
|
|
862
|
+
}
|
|
863
|
+
if (parentCloseN < marks.length && marks[parentCloseN].s < inlines[i].s) {
|
|
864
|
+
return -1
|
|
865
|
+
}
|
|
866
|
+
return parentNest + 1
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
const createMarks = (state, inlines, start, end, memo, opt, refRanges) => {
|
|
870
|
+
let marks = []
|
|
871
|
+
let n = start
|
|
872
|
+
const nestTracker = createNestTracker()
|
|
873
|
+
|
|
874
|
+
while (n < end) {
|
|
875
|
+
if (inlines[n].type !== '') { n++; continue }
|
|
876
|
+
let nest = 0
|
|
877
|
+
|
|
878
|
+
if (inlines[n].len > 1) {
|
|
879
|
+
const [newN, newNest] = setStrong(state, inlines, marks, n, memo, opt, nestTracker, refRanges)
|
|
880
|
+
n = newN
|
|
881
|
+
nest = newNest
|
|
882
|
+
}
|
|
883
|
+
if (inlines[n].len !== 0) {
|
|
884
|
+
const [newN2, newNest2] = setEm(state, inlines, marks, n, memo, opt, null, nestTracker, refRanges)
|
|
885
|
+
n = newN2
|
|
886
|
+
nest = newNest2
|
|
887
|
+
}
|
|
888
|
+
if (inlines[n].len !== 0) {
|
|
889
|
+
setText(inlines, marks, n, nest)
|
|
890
|
+
}
|
|
891
|
+
n++
|
|
892
|
+
}
|
|
893
|
+
return marks
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const mergeInlinesAndMarks = (inlines, marks) => {
|
|
897
|
+
// marks array is already sorted, skip sorting
|
|
898
|
+
const merged = []
|
|
899
|
+
let markIndex = 0
|
|
900
|
+
for (const token of inlines) {
|
|
901
|
+
if (token.type === '') {
|
|
902
|
+
while (markIndex < marks.length && marks[markIndex].s >= token.s && marks[markIndex].e <= token.e) {
|
|
903
|
+
merged.push(marks[markIndex])
|
|
904
|
+
markIndex++
|
|
905
|
+
}
|
|
906
|
+
} else {
|
|
907
|
+
merged.push(token)
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
while (markIndex < marks.length) {
|
|
911
|
+
merged.push(marks[markIndex++])
|
|
912
|
+
}
|
|
913
|
+
return merged
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
const isWhitespaceToken = (token) => token && token.type === 'text' && token.content.trim() === ''
|
|
917
|
+
|
|
918
|
+
const strongJa = (state, silent, opt) => {
|
|
919
|
+
if (silent) return false
|
|
920
|
+
const start = state.pos
|
|
921
|
+
let max = state.posMax
|
|
922
|
+
const src = state.src
|
|
923
|
+
let attributesSrc
|
|
924
|
+
if (start > max) return false
|
|
925
|
+
if (src.charCodeAt(start) !== CHAR_ASTERISK) return false
|
|
926
|
+
if (hasBackslash(state, start)) return false
|
|
927
|
+
|
|
928
|
+
if (opt.mditAttrs) {
|
|
929
|
+
attributesSrc = src.match(/((\n)? *){([^{}\n!@#%^&*()]+?)} *$/)
|
|
930
|
+
if (attributesSrc && attributesSrc[3] !== '.') {
|
|
931
|
+
max = src.slice(0, attributesSrc.index).length
|
|
932
|
+
if (attributesSrc[2] === '\n') {
|
|
933
|
+
max = src.slice(0, attributesSrc.index - 1).length
|
|
934
|
+
}
|
|
935
|
+
if(hasBackslash(state, attributesSrc.index) && attributesSrc[2] === '' && attributesSrc[1].length === 0) {
|
|
936
|
+
max = state.posMax
|
|
937
|
+
}
|
|
938
|
+
} else {
|
|
939
|
+
let endCurlyKet = src.match(/(\n *){([^{}\n!@#%^&*()]*?)}.*(} *?)$/)
|
|
940
|
+
if (endCurlyKet) {
|
|
941
|
+
max -= endCurlyKet[3].length
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
const refRanges = computeReferenceRanges(state, start, max)
|
|
947
|
+
if (refRanges.length > 0) {
|
|
948
|
+
state.__strongJaHasCollapsedRefs = true
|
|
408
949
|
}
|
|
409
|
-
|
|
410
|
-
|
|
950
|
+
let inlines = createInlines(state, start, max, opt)
|
|
951
|
+
|
|
952
|
+
const memo = {
|
|
953
|
+
html: state.md.options.html,
|
|
954
|
+
htmlTags: {},
|
|
955
|
+
inlineMarkStart: src.charCodeAt(0) === CHAR_ASTERISK,
|
|
956
|
+
inlineMarkEnd: src.charCodeAt(max - 1) === CHAR_ASTERISK,
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
let marks = createMarks(state, inlines, 0, inlines.length, memo, opt, refRanges)
|
|
960
|
+
|
|
961
|
+
inlines = mergeInlinesAndMarks(inlines, marks)
|
|
962
|
+
|
|
963
|
+
setToken(state, inlines, opt)
|
|
411
964
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
if (memo.htmlTags[tagName] === undefined) {
|
|
416
|
-
memo.htmlTags[tagName] = 0
|
|
417
|
-
}
|
|
418
|
-
if (inlines[i].tag[1] === 'open') {
|
|
419
|
-
memo.htmlTags[tagName] += 1
|
|
420
|
-
}
|
|
421
|
-
if (inlines[i].tag[1] === 'close') {
|
|
422
|
-
memo.htmlTags[tagName] -= 1
|
|
965
|
+
if (inlineHasCollapsedRef(state) && !state.__strongJaCollapsedRefRegistered) {
|
|
966
|
+
registerCollapsedRefTarget(state)
|
|
967
|
+
state.__strongJaCollapsedRefRegistered = true
|
|
423
968
|
}
|
|
424
|
-
|
|
425
|
-
|
|
969
|
+
|
|
970
|
+
if (opt.mditAttrs && max !== state.posMax) {
|
|
971
|
+
if (!attributesSrc) {
|
|
972
|
+
state.pos = max
|
|
973
|
+
return true
|
|
974
|
+
}
|
|
975
|
+
state.pos = attributesSrc[1].length > 1 ? max + attributesSrc[1].length : max
|
|
976
|
+
return true
|
|
426
977
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
978
|
+
state.pos = max
|
|
979
|
+
return true
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// Collapsed reference helpers
|
|
983
|
+
const buildReferenceLabel = (tokens) => {
|
|
984
|
+
let label = ''
|
|
985
|
+
for (const token of tokens) {
|
|
986
|
+
if (token.type === 'text' || token.type === 'code_inline') {
|
|
987
|
+
label += token.content
|
|
988
|
+
} else if (token.type === 'softbreak' || token.type === 'hardbreak') {
|
|
989
|
+
label += ' '
|
|
990
|
+
} else if (token.type && token.type.endsWith('_open') && token.markup) {
|
|
991
|
+
label += token.markup
|
|
992
|
+
} else if (token.type && token.type.endsWith('_close') && token.markup) {
|
|
993
|
+
label += token.markup
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
return label
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
const cleanLabelText = (label) => {
|
|
1000
|
+
return label.replace(/^[*_]+/, '').replace(/[*_]+$/, '')
|
|
430
1001
|
}
|
|
431
1002
|
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
const isJapanese = (ch) => {
|
|
436
|
-
return REG_JAPANESE.test(ch)
|
|
1003
|
+
const normalizeReferenceCandidate = (state, text, { useClean = false } = {}) => {
|
|
1004
|
+
const source = useClean ? cleanLabelText(text) : text.replace(/\s+/g, ' ').trim()
|
|
1005
|
+
return normalizeRefKey(state, source)
|
|
437
1006
|
}
|
|
438
1007
|
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
1008
|
+
const normalizeRefKey = (state, label) => {
|
|
1009
|
+
const normalize = state.md && state.md.utils && state.md.utils.normalizeReference
|
|
1010
|
+
? state.md.utils.normalizeReference
|
|
1011
|
+
: (str) => str.trim().replace(/\s+/g, ' ').toUpperCase()
|
|
1012
|
+
return normalize(label)
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
const adjustTokenLevels = (tokens, startIdx, endIdx, delta) => {
|
|
1016
|
+
for (let i = startIdx; i < endIdx; i++) {
|
|
1017
|
+
if (tokens[i]) tokens[i].level += delta
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
const cloneTextToken = (source, content) => {
|
|
1022
|
+
const newToken = new Token('text', '', 0)
|
|
1023
|
+
newToken.content = content
|
|
1024
|
+
newToken.level = source.level
|
|
1025
|
+
newToken.markup = source.markup
|
|
1026
|
+
newToken.info = source.info
|
|
1027
|
+
newToken.meta = source.meta ? {...source.meta} : null
|
|
1028
|
+
newToken.block = source.block
|
|
1029
|
+
newToken.hidden = source.hidden
|
|
1030
|
+
return newToken
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// Split only text tokens that actually contain bracket characters
|
|
1034
|
+
const splitBracketToken = (tokens, index) => {
|
|
1035
|
+
const token = tokens[index]
|
|
1036
|
+
if (!token || token.type !== 'text') return false
|
|
1037
|
+
const content = token.content
|
|
1038
|
+
if (!content || (content.indexOf('[') === -1 && content.indexOf(']') === -1)) {
|
|
1039
|
+
return false
|
|
447
1040
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
1041
|
+
const segments = []
|
|
1042
|
+
let buffer = ''
|
|
1043
|
+
let pos = 0
|
|
1044
|
+
while (pos < content.length) {
|
|
1045
|
+
if (content.startsWith('[]', pos)) {
|
|
1046
|
+
if (buffer) {
|
|
1047
|
+
segments.push(buffer)
|
|
1048
|
+
buffer = ''
|
|
1049
|
+
}
|
|
1050
|
+
segments.push('[]')
|
|
1051
|
+
pos += 2
|
|
1052
|
+
continue
|
|
1053
|
+
}
|
|
1054
|
+
const ch = content[pos]
|
|
1055
|
+
if (ch === '[' || ch === ']') {
|
|
1056
|
+
if (buffer) {
|
|
1057
|
+
segments.push(buffer)
|
|
1058
|
+
buffer = ''
|
|
1059
|
+
}
|
|
1060
|
+
segments.push(ch)
|
|
1061
|
+
pos++
|
|
1062
|
+
continue
|
|
1063
|
+
}
|
|
1064
|
+
buffer += ch
|
|
1065
|
+
pos++
|
|
460
1066
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
1067
|
+
if (buffer) segments.push(buffer)
|
|
1068
|
+
if (segments.length <= 1) return false
|
|
1069
|
+
token.content = segments[0]
|
|
1070
|
+
let insertIdx = index + 1
|
|
1071
|
+
for (let s = 1; s < segments.length; s++) {
|
|
1072
|
+
const newToken = cloneTextToken(token, segments[s])
|
|
1073
|
+
tokens.splice(insertIdx, 0, newToken)
|
|
1074
|
+
insertIdx++
|
|
469
1075
|
}
|
|
1076
|
+
return true
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
const isBracketToken = (token, bracket) => {
|
|
1080
|
+
return token && token.type === 'text' && token.content === bracket
|
|
470
1081
|
}
|
|
471
1082
|
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const checkCloseNextChar = (isPunctuation(closeNextChar) || i === inlines.length - 1)
|
|
481
|
-
|
|
482
|
-
if (opt.disallowMixed === false) {
|
|
483
|
-
const openPrevChar = src[inlines[n].s - 1] || ''
|
|
484
|
-
const closeNextChar = src[inlines[i].e + 1] || ''
|
|
485
|
-
|
|
486
|
-
if (isEnglish(openPrevChar) || isEnglish(closeNextChar)) {
|
|
487
|
-
const contentBetween = src.slice(inlines[n].e + 1, inlines[i].s)
|
|
488
|
-
if (REG_MARKDOWN_HTML.test(contentBetween)) {
|
|
489
|
-
return false
|
|
490
|
-
}
|
|
1083
|
+
const findLinkCloseIndex = (tokens, startIdx) => {
|
|
1084
|
+
let depth = 0
|
|
1085
|
+
for (let idx = startIdx; idx < tokens.length; idx++) {
|
|
1086
|
+
const token = tokens[idx]
|
|
1087
|
+
if (token.type === 'link_open') depth++
|
|
1088
|
+
if (token.type === 'link_close') {
|
|
1089
|
+
depth--
|
|
1090
|
+
if (depth === 0) return idx
|
|
491
1091
|
}
|
|
492
1092
|
}
|
|
493
|
-
|
|
494
|
-
const result = (checkOpenNextChar || checkClosePrevChar) && !checkCloseNextChar && !(isJapanese(openPrevChar) || isJapanese(closeNextChar))
|
|
495
|
-
return result
|
|
1093
|
+
return -1
|
|
496
1094
|
}
|
|
497
1095
|
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
1096
|
+
const convertCollapsedReferenceLinks = (tokens, state) => {
|
|
1097
|
+
const references = state.env && state.env.references
|
|
1098
|
+
if (!references || Object.keys(references).length === 0) return
|
|
1099
|
+
|
|
1100
|
+
let i = 0
|
|
1101
|
+
while (i < tokens.length) {
|
|
1102
|
+
if (splitBracketToken(tokens, i)) {
|
|
1103
|
+
continue
|
|
1104
|
+
}
|
|
1105
|
+
if (!isBracketToken(tokens[i], '[')) {
|
|
1106
|
+
i++
|
|
1107
|
+
continue
|
|
1108
|
+
}
|
|
1109
|
+
let closeIdx = i + 1
|
|
1110
|
+
while (closeIdx < tokens.length && !isBracketToken(tokens[closeIdx], ']')) {
|
|
1111
|
+
if (splitBracketToken(tokens, closeIdx)) {
|
|
1112
|
+
continue
|
|
1113
|
+
}
|
|
1114
|
+
if (tokens[closeIdx].type === 'link_open') {
|
|
1115
|
+
closeIdx = -1
|
|
511
1116
|
break
|
|
512
1117
|
}
|
|
1118
|
+
closeIdx++
|
|
1119
|
+
}
|
|
1120
|
+
if (closeIdx === -1 || closeIdx >= tokens.length) {
|
|
1121
|
+
i++
|
|
1122
|
+
continue
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
if (closeIdx === i + 1) {
|
|
513
1126
|
i++
|
|
1127
|
+
continue
|
|
514
1128
|
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
let i = n + 1
|
|
518
|
-
let nest = 0
|
|
519
|
-
let strongPNum = 0
|
|
520
|
-
let insideTagsIsClose = 1
|
|
521
|
-
const inlinesLength = inlines.length
|
|
522
|
-
while (i < inlinesLength) {
|
|
523
|
-
if (inlines[i].len === 0 || inlines[i].check) { i++; continue }
|
|
524
|
-
if (!sNest && inlines[i].type === 'html_inline') {
|
|
525
|
-
inlines.check = true
|
|
526
|
-
insideTagsIsClose = checkInsideTags(inlines, i, memo)
|
|
527
|
-
if (insideTagsIsClose === -1) return [n, nest]
|
|
528
|
-
if (insideTagsIsClose === 0) { i++; continue }
|
|
529
|
-
}
|
|
530
|
-
if (inlines[i].type !== '') { i++; continue }
|
|
531
|
-
|
|
532
|
-
const emNum = Math.min(inlines[n].len, inlines[i].len)
|
|
533
1129
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
inlines[i].len > 1 ? flag = true : flag = false
|
|
542
|
-
return flag
|
|
1130
|
+
const labelTokens = tokens.slice(i + 1, closeIdx)
|
|
1131
|
+
const labelText = buildReferenceLabel(labelTokens)
|
|
1132
|
+
const cleanedLabel = cleanLabelText(labelText)
|
|
1133
|
+
const whitespaceStart = closeIdx + 1
|
|
1134
|
+
let refRemoveStart = whitespaceStart
|
|
1135
|
+
while (refRemoveStart < tokens.length && isWhitespaceToken(tokens[refRemoveStart])) {
|
|
1136
|
+
refRemoveStart++
|
|
543
1137
|
}
|
|
544
|
-
if (
|
|
545
|
-
strongPNum++
|
|
546
|
-
i++
|
|
1138
|
+
if (splitBracketToken(tokens, refRemoveStart)) {
|
|
547
1139
|
continue
|
|
548
1140
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
1141
|
+
const whitespaceCount = refRemoveStart - whitespaceStart
|
|
1142
|
+
let refKey = null
|
|
1143
|
+
let refRemoveCount = 0
|
|
1144
|
+
let existingLinkOpen = null
|
|
1145
|
+
let existingLinkClose = null
|
|
1146
|
+
const nextToken = tokens[refRemoveStart]
|
|
1147
|
+
if (process.env.DEBUG_COLLAPSED === 'wide') {
|
|
1148
|
+
const debugSlice = tokens.slice(i, Math.min(tokens.length, refRemoveStart + 3)).map((t) => `${t.type}:${t.content || ''}`)
|
|
1149
|
+
console.log('debug collapsed ctx:', debugSlice)
|
|
1150
|
+
console.log('next token info:', nextToken && nextToken.type, nextToken && JSON.stringify(nextToken.content))
|
|
554
1151
|
}
|
|
555
|
-
if (nest === -1) return [n, nest]
|
|
556
|
-
|
|
557
|
-
if (emNum === 1) {
|
|
558
|
-
if (hasPunctuationOrNonJapanese(state, inlines, n, i, opt)) {
|
|
559
|
-
if (memo.inlineMarkEnd) {
|
|
560
|
-
marks.push(...createMarks(state, inlines, i, inlinesLength - 1, memo, opt))
|
|
561
1152
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
1153
|
+
if (isBracketToken(nextToken, '[]')) {
|
|
1154
|
+
refKey = normalizeReferenceCandidate(state, cleanedLabel)
|
|
1155
|
+
refRemoveCount = 1
|
|
1156
|
+
} else if (isBracketToken(nextToken, '[')) {
|
|
1157
|
+
let refCloseIdx = refRemoveStart + 1
|
|
1158
|
+
while (refCloseIdx < tokens.length && !isBracketToken(tokens[refCloseIdx], ']')) {
|
|
1159
|
+
refCloseIdx++
|
|
566
1160
|
}
|
|
567
|
-
if (
|
|
568
|
-
i
|
|
1161
|
+
if (refCloseIdx >= tokens.length) {
|
|
1162
|
+
i++
|
|
1163
|
+
continue
|
|
569
1164
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
s: inlines[n].ep,
|
|
574
|
-
e: inlines[n].ep,
|
|
575
|
-
len: 1,
|
|
576
|
-
oLen: inlines[n].len - 1,
|
|
577
|
-
type: 'em_open'
|
|
578
|
-
})
|
|
579
|
-
inlines[n].ep -= 1
|
|
580
|
-
inlines[n].len -= 1
|
|
581
|
-
|
|
582
|
-
if (strongPNum % 2 === 0 || inlines[i].len < 2) {
|
|
583
|
-
pushMark(marks, {
|
|
584
|
-
nest: nest,
|
|
585
|
-
s: inlines[i].sp,
|
|
586
|
-
e: inlines[i].sp,
|
|
587
|
-
len: 1,
|
|
588
|
-
oLen: inlines[i].len - 1,
|
|
589
|
-
type: 'em_close'
|
|
590
|
-
})
|
|
591
|
-
inlines[i].sp += 1
|
|
1165
|
+
const refTokens = tokens.slice(refRemoveStart + 1, refCloseIdx)
|
|
1166
|
+
if (refTokens.length === 0) {
|
|
1167
|
+
refKey = normalizeReferenceCandidate(state, cleanedLabel)
|
|
592
1168
|
} else {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
s: inlines[i].ep,
|
|
596
|
-
e: inlines[i].ep,
|
|
597
|
-
len: 1,
|
|
598
|
-
oLen: inlines[i].len - 1,
|
|
599
|
-
type: 'em_close'
|
|
600
|
-
})
|
|
601
|
-
inlines[i].sp = inlines[i].ep - 1
|
|
602
|
-
inlines[i].ep -= 1
|
|
1169
|
+
const refLabelText = buildReferenceLabel(refTokens)
|
|
1170
|
+
refKey = normalizeReferenceCandidate(state, refLabelText)
|
|
603
1171
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
return [n, nest]
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
const setText = (inlines, marks, n, nest) => {
|
|
614
|
-
pushMark(marks, {
|
|
615
|
-
nest: nest,
|
|
616
|
-
s: inlines[n].sp,
|
|
617
|
-
e: inlines[n].ep,
|
|
618
|
-
len: inlines[n].len,
|
|
619
|
-
oLen: -1,
|
|
620
|
-
type: 'text'
|
|
621
|
-
})
|
|
622
|
-
inlines[n].len = 0
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
const checkNest = (inlines, marks, n, i) => {
|
|
626
|
-
let nest = 1
|
|
627
|
-
let isRange = true
|
|
628
|
-
if (marks.length === 0) return nest
|
|
629
|
-
let strongNest = 0
|
|
630
|
-
let emNest = 0
|
|
631
|
-
let j = 0
|
|
632
|
-
const marksLength = marks.length
|
|
633
|
-
while (j < marksLength) {
|
|
634
|
-
if (marks[j].s <= inlines[n].s) {
|
|
635
|
-
if (marks[j].type === 'strong_open') strongNest++
|
|
636
|
-
if (marks[j].type === 'strong_close') strongNest--
|
|
637
|
-
if (marks[j].type === 'em_open') emNest++
|
|
638
|
-
if (marks[j].type === 'em_close') emNest--
|
|
639
|
-
} else { break }
|
|
640
|
-
j++
|
|
641
|
-
}
|
|
642
|
-
let parentNest = strongNest + emNest
|
|
643
|
-
let parentCloseN = j
|
|
644
|
-
if (parentCloseN < marksLength) {
|
|
645
|
-
while (parentCloseN < marksLength) {
|
|
646
|
-
if (marks[parentCloseN].nest === parentNest) break
|
|
647
|
-
parentCloseN++
|
|
648
|
-
}
|
|
649
|
-
if (parentCloseN > marksLength - 1) {
|
|
650
|
-
isRange = true
|
|
651
|
-
} else {
|
|
652
|
-
if (marks[parentCloseN].s < inlines[i].s) isRange = false
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
if (isRange) {
|
|
657
|
-
nest = parentNest + 1
|
|
658
|
-
} else {
|
|
659
|
-
nest = -1
|
|
660
|
-
}
|
|
661
|
-
return nest
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
const createMarks = (state, inlines, start, end, memo, opt) => {
|
|
665
|
-
let marks = []
|
|
666
|
-
let n = start
|
|
667
|
-
|
|
668
|
-
while (n < end) {
|
|
669
|
-
if (inlines[n].type !== '') { n++; continue }
|
|
670
|
-
let nest = 0
|
|
671
|
-
|
|
672
|
-
if (inlines[n].len > 1) {
|
|
673
|
-
const [newN, newNest] = setStrong(state, inlines, marks, n, memo, opt)
|
|
674
|
-
n = newN
|
|
675
|
-
nest = newNest
|
|
676
|
-
}
|
|
677
|
-
if (inlines[n].len !== 0) {
|
|
678
|
-
const [newN2, newNest2] = setEm(state, inlines, marks, n, memo, opt)
|
|
679
|
-
n = newN2
|
|
680
|
-
nest = newNest2
|
|
681
|
-
}
|
|
682
|
-
if (inlines[n].len !== 0) {
|
|
683
|
-
setText(inlines, marks, n, nest)
|
|
684
|
-
}
|
|
685
|
-
n++
|
|
686
|
-
}
|
|
687
|
-
return marks
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
const mergeInlinesAndMarks = (inlines, marks) => {
|
|
692
|
-
marks.sort((a, b) => a.s - b.s)
|
|
693
|
-
const merged = []
|
|
694
|
-
let markIndex = 0
|
|
695
|
-
for (const token of inlines) {
|
|
696
|
-
if (token.type === '') {
|
|
697
|
-
while (markIndex < marks.length && marks[markIndex].s >= token.s && marks[markIndex].e <= token.e) {
|
|
698
|
-
merged.push(marks[markIndex])
|
|
699
|
-
markIndex++
|
|
1172
|
+
refRemoveCount = refCloseIdx - refRemoveStart + 1
|
|
1173
|
+
} else if (nextToken && nextToken.type === 'link_open') {
|
|
1174
|
+
const linkCloseIdx = findLinkCloseIndex(tokens, refRemoveStart)
|
|
1175
|
+
if (linkCloseIdx === -1) {
|
|
1176
|
+
i++
|
|
1177
|
+
continue
|
|
700
1178
|
}
|
|
1179
|
+
existingLinkOpen = tokens[refRemoveStart]
|
|
1180
|
+
existingLinkClose = tokens[linkCloseIdx]
|
|
1181
|
+
refRemoveCount = linkCloseIdx - refRemoveStart + 1
|
|
701
1182
|
} else {
|
|
702
|
-
|
|
1183
|
+
i++
|
|
1184
|
+
continue
|
|
703
1185
|
}
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
const src = state.src
|
|
716
|
-
let attributesSrc
|
|
717
|
-
if (start > max) return false
|
|
718
|
-
if (src.charCodeAt(start) !== CHAR_ASTERISK) return false
|
|
719
|
-
if (hasBackslash(state, start)) return false
|
|
720
|
-
|
|
721
|
-
if (opt.mditAttrs) {
|
|
722
|
-
attributesSrc = src.match(/((\n)? *){([^{}\n!@#%^&*()]+?)} *$/)
|
|
723
|
-
if (attributesSrc && attributesSrc[3] !== '.') {
|
|
724
|
-
max = src.slice(0, attributesSrc.index).length
|
|
725
|
-
if (attributesSrc[2] === '\n') {
|
|
726
|
-
max = src.slice(0, attributesSrc.index - 1).length
|
|
1186
|
+
if (process.env.DEBUG_COLLAPSED === '1') {
|
|
1187
|
+
const context = tokens.slice(Math.max(0, i - 2), Math.min(tokens.length, closeIdx + 3))
|
|
1188
|
+
console.log('[collapsed-ref] context:',
|
|
1189
|
+
context.map((t) => t.type + ':' + (t.content || '')))
|
|
1190
|
+
}
|
|
1191
|
+
let linkOpenToken = null
|
|
1192
|
+
let linkCloseToken = null
|
|
1193
|
+
if (existingLinkOpen && existingLinkClose) {
|
|
1194
|
+
if (whitespaceCount > 0) {
|
|
1195
|
+
tokens.splice(whitespaceStart, whitespaceCount)
|
|
1196
|
+
refRemoveStart -= whitespaceCount
|
|
727
1197
|
}
|
|
728
|
-
if(
|
|
729
|
-
|
|
1198
|
+
if (refRemoveCount > 0) {
|
|
1199
|
+
tokens.splice(refRemoveStart, refRemoveCount)
|
|
730
1200
|
}
|
|
1201
|
+
linkOpenToken = existingLinkOpen
|
|
1202
|
+
linkCloseToken = existingLinkClose
|
|
731
1203
|
} else {
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
1204
|
+
if (!refKey) {
|
|
1205
|
+
i++
|
|
1206
|
+
continue
|
|
735
1207
|
}
|
|
1208
|
+
const ref = references[refKey]
|
|
1209
|
+
if (!ref) {
|
|
1210
|
+
i++
|
|
1211
|
+
continue
|
|
1212
|
+
}
|
|
1213
|
+
if (whitespaceCount > 0) {
|
|
1214
|
+
tokens.splice(whitespaceStart, whitespaceCount)
|
|
1215
|
+
refRemoveStart -= whitespaceCount
|
|
1216
|
+
}
|
|
1217
|
+
if (refRemoveCount > 0) {
|
|
1218
|
+
tokens.splice(refRemoveStart, refRemoveCount)
|
|
1219
|
+
}
|
|
1220
|
+
linkOpenToken = new Token('link_open', 'a', 1)
|
|
1221
|
+
linkOpenToken.attrs = [['href', ref.href]]
|
|
1222
|
+
if (ref.title) linkOpenToken.attrPush(['title', ref.title])
|
|
1223
|
+
linkOpenToken.markup = '[]'
|
|
1224
|
+
linkOpenToken.info = 'auto'
|
|
1225
|
+
linkCloseToken = new Token('link_close', 'a', -1)
|
|
1226
|
+
linkCloseToken.markup = '[]'
|
|
1227
|
+
linkCloseToken.info = 'auto'
|
|
736
1228
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
1229
|
+
tokens.splice(closeIdx, 1)
|
|
1230
|
+
tokens.splice(i, 1)
|
|
1231
|
+
|
|
1232
|
+
let labelStartIdx = i
|
|
1233
|
+
let labelEndIdx = i + labelTokens.length - 1
|
|
1234
|
+
if (labelStartIdx > labelEndIdx) {
|
|
1235
|
+
i++
|
|
1236
|
+
continue
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
const wrapperPairs = []
|
|
1240
|
+
while (labelStartIdx > 0) {
|
|
1241
|
+
const prevToken = tokens[labelStartIdx - 1]
|
|
1242
|
+
const nextToken = tokens[labelEndIdx + 1]
|
|
1243
|
+
if (!prevToken || !nextToken) break
|
|
1244
|
+
if (!/_close$/.test(prevToken.type)) break
|
|
1245
|
+
const expectedOpen = prevToken.type.replace('_close', '_open')
|
|
1246
|
+
if (nextToken.type !== expectedOpen) break
|
|
1247
|
+
if (process.env.DEBUG_COLLAPSED === '1') {
|
|
1248
|
+
console.log('[collapsed-ref] wrapper pair:', prevToken.type, nextToken.type)
|
|
1249
|
+
}
|
|
1250
|
+
wrapperPairs.push({
|
|
1251
|
+
base: prevToken.type.replace('_close', ''),
|
|
1252
|
+
tag: prevToken.tag,
|
|
1253
|
+
markup: prevToken.markup
|
|
1254
|
+
})
|
|
1255
|
+
tokens.splice(labelEndIdx + 1, 1)
|
|
1256
|
+
tokens.splice(labelStartIdx - 1, 1)
|
|
1257
|
+
labelStartIdx -= 1
|
|
1258
|
+
labelEndIdx -= 1
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
if (labelStartIdx > labelEndIdx) {
|
|
1262
|
+
i++
|
|
1263
|
+
continue
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
let labelLength = labelEndIdx - labelStartIdx + 1
|
|
1267
|
+
const firstLabelToken = tokens[labelStartIdx]
|
|
1268
|
+
const linkLevel = firstLabelToken ? Math.max(firstLabelToken.level - 1, 0) : 0
|
|
1269
|
+
linkOpenToken.level = linkLevel
|
|
1270
|
+
linkCloseToken.level = linkLevel
|
|
1271
|
+
tokens.splice(labelStartIdx, 0, linkOpenToken)
|
|
1272
|
+
tokens.splice(labelStartIdx + labelLength + 1, 0, linkCloseToken)
|
|
1273
|
+
|
|
1274
|
+
adjustTokenLevels(tokens, labelStartIdx + 1, labelStartIdx + labelLength + 1, 1)
|
|
1275
|
+
|
|
1276
|
+
if (wrapperPairs.length > 0) {
|
|
1277
|
+
let insertIdx = labelStartIdx + 1
|
|
1278
|
+
for (let wp = 0; wp < wrapperPairs.length; wp++) {
|
|
1279
|
+
const pair = wrapperPairs[wp]
|
|
1280
|
+
const innerOpen = new Token(pair.base + '_open', pair.tag, 1)
|
|
1281
|
+
innerOpen.markup = pair.markup
|
|
1282
|
+
innerOpen.level = linkLevel + 1 + wp
|
|
1283
|
+
tokens.splice(insertIdx, 0, innerOpen)
|
|
1284
|
+
insertIdx++
|
|
1285
|
+
labelLength++
|
|
1286
|
+
}
|
|
1287
|
+
let linkClosePos = labelStartIdx + labelLength + 1
|
|
1288
|
+
for (let wp = wrapperPairs.length - 1; wp >= 0; wp--) {
|
|
1289
|
+
const pair = wrapperPairs[wp]
|
|
1290
|
+
const innerClose = new Token(pair.base + '_close', pair.tag, -1)
|
|
1291
|
+
innerClose.markup = pair.markup
|
|
1292
|
+
innerClose.level = linkLevel + 1 + wp
|
|
1293
|
+
tokens.splice(linkClosePos, 0, innerClose)
|
|
1294
|
+
labelLength++
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
i = labelStartIdx + labelLength + 2
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
// Link cleanup helpers
|
|
1303
|
+
const mergeBrokenMarksAroundLinks = (tokens) => {
|
|
1304
|
+
let i = 0
|
|
1305
|
+
while (i < tokens.length) {
|
|
1306
|
+
const closeToken = tokens[i]
|
|
1307
|
+
if (!closeToken || !/_close$/.test(closeToken.type)) {
|
|
1308
|
+
i++
|
|
1309
|
+
continue
|
|
1310
|
+
}
|
|
1311
|
+
const openType = closeToken.type.replace('_close', '_open')
|
|
1312
|
+
let j = i + 1
|
|
1313
|
+
while (j < tokens.length && isWhitespaceToken(tokens[j])) j++
|
|
1314
|
+
if (j >= tokens.length || tokens[j].type !== 'link_open') {
|
|
1315
|
+
i++
|
|
1316
|
+
continue
|
|
1317
|
+
}
|
|
1318
|
+
let linkDepth = 1
|
|
1319
|
+
j++
|
|
1320
|
+
while (j < tokens.length && linkDepth > 0) {
|
|
1321
|
+
if (tokens[j].type === 'link_open') linkDepth++
|
|
1322
|
+
if (tokens[j].type === 'link_close') linkDepth--
|
|
1323
|
+
j++
|
|
1324
|
+
}
|
|
1325
|
+
if (linkDepth !== 0) {
|
|
1326
|
+
i++
|
|
1327
|
+
continue
|
|
1328
|
+
}
|
|
1329
|
+
while (j < tokens.length && isWhitespaceToken(tokens[j])) j++
|
|
1330
|
+
if (j >= tokens.length) {
|
|
1331
|
+
i++
|
|
1332
|
+
continue
|
|
1333
|
+
}
|
|
1334
|
+
const reopenToken = tokens[j]
|
|
1335
|
+
if (reopenToken.type !== openType || reopenToken.level !== closeToken.level) {
|
|
1336
|
+
i++
|
|
1337
|
+
continue
|
|
1338
|
+
}
|
|
1339
|
+
tokens.splice(j, 1)
|
|
1340
|
+
tokens.splice(i, 1)
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
|
|
1345
|
+
const mditStrongJa = (md, option) => {
|
|
1346
|
+
const opt = {
|
|
1347
|
+
dollarMath: true, //inline math $...$
|
|
1348
|
+
mditAttrs: true, //markdown-it-attrs
|
|
1349
|
+
mdBreaks: md.options.breaks,
|
|
1350
|
+
disallowMixed: false, //Non-Japanese text handling
|
|
1351
|
+
}
|
|
1352
|
+
if (option) Object.assign(opt, option)
|
|
1353
|
+
|
|
1354
|
+
md.inline.ruler.before('emphasis', 'strong_ja', (state, silent) => {
|
|
1355
|
+
return strongJa(state, silent, opt)
|
|
1356
|
+
})
|
|
1357
|
+
|
|
1358
|
+
md.core.ruler.after('inline', 'strong_ja_collapsed_refs', (state) => {
|
|
1359
|
+
const targets = state.env.__strongJaCollapsedTargets
|
|
1360
|
+
if (!targets || targets.length === 0) return
|
|
1361
|
+
for (const tokens of targets) {
|
|
1362
|
+
if (!tokens || !tokens.length) continue
|
|
1363
|
+
convertCollapsedReferenceLinks(tokens, state)
|
|
1364
|
+
mergeBrokenMarksAroundLinks(tokens)
|
|
763
1365
|
}
|
|
764
|
-
|
|
765
|
-
state.
|
|
766
|
-
}
|
|
767
|
-
return true
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
const mditStrongJa = (md, option) => {
|
|
771
|
-
const opt = {
|
|
772
|
-
dollarMath: true, //inline math $...$
|
|
773
|
-
mditAttrs: true, //markdown-it-attrs
|
|
774
|
-
mdBreaks: md.options.breaks,
|
|
775
|
-
disallowMixed: false, //Non-Japanese text handling
|
|
776
|
-
}
|
|
777
|
-
if (option) Object.assign(opt, option)
|
|
778
|
-
|
|
779
|
-
md.inline.ruler.before('emphasis', 'strong_ja', (state, silent) => {
|
|
780
|
-
return strongJa(state, silent, opt)
|
|
1366
|
+
delete state.env.__strongJaCollapsedTargets
|
|
1367
|
+
delete state.env.__strongJaCollapsedTargetSet
|
|
781
1368
|
})
|
|
782
|
-
}
|
|
783
|
-
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
export default mditStrongJa
|