@peaceroad/markdown-it-strong-ja 0.4.5 → 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 +1298 -775
- package/package.json +5 -2
package/index.js
CHANGED
|
@@ -1,848 +1,1371 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
+
}
|
|
30
73
|
}
|
|
31
|
-
|
|
74
|
+
if (cache) cache.set(pos, -1)
|
|
75
|
+
return -1
|
|
32
76
|
}
|
|
33
|
-
|
|
34
|
-
|
|
77
|
+
|
|
78
|
+
// Detect reference-link label ranges within the current inline slice
|
|
79
|
+
const computeReferenceRanges = (state, start, max) => {
|
|
35
80
|
const src = state.src
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const asteriskToken = state.push(type, '', 0)
|
|
61
|
-
asteriskToken.content = content
|
|
62
|
-
i++
|
|
63
|
-
continue
|
|
64
|
-
}
|
|
65
|
-
if (opt.mditAttrs && attrsIsText.val && i + 1 < inlines.length) {
|
|
66
|
-
const hasImmediatelyAfterAsteriskClose = inlines[i+1].type === attrsIsText.tag + '_close'
|
|
67
|
-
if (hasImmediatelyAfterAsteriskClose && REG_ATTRS.test(content)) {
|
|
68
|
-
const attrsToken = state.push(type, '', 0)
|
|
69
|
-
|
|
70
|
-
const hasBackslashBeforeCurlyAttribute = content.match(/(\\+){/)
|
|
71
|
-
if (hasBackslashBeforeCurlyAttribute) {
|
|
72
|
-
if (hasBackslashBeforeCurlyAttribute[1].length === 1) {
|
|
73
|
-
attrsToken.content = content.replace(/\\{/, '{')
|
|
74
|
-
} else {
|
|
75
|
-
let backSlashNum = Math.floor(hasBackslashBeforeCurlyAttribute[1].length / 2)
|
|
76
|
-
let k = 0
|
|
77
|
-
let backSlash = ''
|
|
78
|
-
while (k < backSlashNum) {
|
|
79
|
-
backSlash += '\\'
|
|
80
|
-
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]
|
|
81
105
|
}
|
|
82
|
-
attrsToken.content = content.replace(/\\+{/, backSlash + '{')
|
|
83
106
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const childTokens = state.md.parseInline(content, state.env)
|
|
94
|
-
if (childTokens[0] && childTokens[0].children) {
|
|
95
|
-
let j = 0
|
|
96
|
-
while (j < childTokens[0].children.length) {
|
|
97
|
-
const t = childTokens[0].children[j]
|
|
98
|
-
if (t.type === 'softbreak' && !opt.mdBreaks) {
|
|
99
|
-
t.type = 'text'
|
|
100
|
-
t.tag = ''
|
|
101
|
-
t.content = '\n'
|
|
102
|
-
}
|
|
103
|
-
if (!opt.mditAttrs && t.tag === 'br') {
|
|
104
|
-
t.tag = ''
|
|
105
|
-
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
|
|
106
113
|
}
|
|
107
|
-
const token = state.push(t.type, t.tag, t.nesting)
|
|
108
|
-
token.attrs = t.attrs
|
|
109
|
-
token.map = t.map
|
|
110
|
-
token.level = t.level
|
|
111
|
-
token.children = t.children
|
|
112
|
-
token.content = t.content
|
|
113
|
-
token.markup = t.markup
|
|
114
|
-
token.info = t.info
|
|
115
|
-
token.meta = t.meta
|
|
116
|
-
token.block = t.block
|
|
117
|
-
token.hidden = t.hidden
|
|
118
|
-
j++
|
|
119
114
|
}
|
|
120
115
|
}
|
|
121
116
|
}
|
|
117
|
+
pos++
|
|
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
|
|
136
|
+
}
|
|
122
137
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
attrsIsText = {
|
|
127
|
-
val: false,
|
|
128
|
-
tag: '',
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
i++
|
|
138
|
+
const inlineHasCollapsedRef = (state) => {
|
|
139
|
+
if (state.__strongJaHasCollapsedRefs === undefined) {
|
|
140
|
+
state.__strongJaHasCollapsedRefs = /\[[^\]]*\]\s*\[[^\]]*\]/.test(state.src)
|
|
133
141
|
}
|
|
142
|
+
return state.__strongJaHasCollapsedRefs
|
|
134
143
|
}
|
|
135
144
|
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
|
145
158
|
}
|
|
146
|
-
|
|
147
|
-
inlines.push(inline)
|
|
159
|
+
targets.push(state.tokens)
|
|
148
160
|
}
|
|
149
161
|
|
|
150
|
-
const
|
|
151
|
-
|
|
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) => {
|
|
152
270
|
const src = state.src
|
|
153
|
-
if (src.charCodeAt(n)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
noMark += src.substring(n, i + 1)
|
|
158
|
-
nextSymbolPos = i
|
|
159
|
-
break
|
|
160
|
-
}
|
|
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
|
|
161
275
|
}
|
|
162
276
|
}
|
|
163
|
-
return
|
|
277
|
+
return -1
|
|
164
278
|
}
|
|
165
279
|
|
|
166
280
|
const processSymbolPair = (state, n, srcLen, symbol, noMark, textStart, pushInlines) => {
|
|
167
|
-
const
|
|
168
|
-
if (nextSymbolPos
|
|
169
|
-
|
|
170
|
-
pushInlines(textStart, nextSymbolPos, nextSymbolPos - textStart + 1, 'text')
|
|
171
|
-
return { shouldBreak: true, newN: nextSymbolPos + 1, newNoMark }
|
|
172
|
-
}
|
|
173
|
-
return { shouldBreak: false, shouldContinue: true, newN: nextSymbolPos + 1, newNoMark }
|
|
281
|
+
const nextSymbolPos = findNextSymbolPos(state, n, srcLen, symbol)
|
|
282
|
+
if (nextSymbolPos === -1) {
|
|
283
|
+
return { shouldBreak: false, shouldContinue: false, newN: n, newNoMark: noMark }
|
|
174
284
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
if (
|
|
180
|
-
pushInlines(
|
|
181
|
-
return
|
|
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 }
|
|
182
292
|
}
|
|
183
|
-
return
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const
|
|
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)
|
|
476
|
+
let i = n + 1
|
|
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) => {
|
|
187
663
|
const src = state.src
|
|
188
|
-
const
|
|
189
|
-
const
|
|
190
|
-
let
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
// Infinite loop prevention
|
|
196
|
-
const maxIterations = srcLen * 2 // Safe upper bound
|
|
197
|
-
let iterations = 0
|
|
198
|
-
|
|
199
|
-
while (n < srcLen) {
|
|
200
|
-
// Prevent infinite loops
|
|
201
|
-
iterations++
|
|
202
|
-
if (iterations > maxIterations) {
|
|
203
|
-
// Add remaining text as-is and exit safely
|
|
204
|
-
if (textStart < srcLen) {
|
|
205
|
-
pushInlines(inlines, textStart, srcLen - 1, srcLen - textStart, 'text')
|
|
206
|
-
}
|
|
207
|
-
break
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const currentChar = src.charCodeAt(n)
|
|
211
|
-
|
|
212
|
-
// Unified escape check
|
|
213
|
-
let isEscaped = false
|
|
214
|
-
if (currentChar === CHAR_ASTERISK || currentChar === CHAR_BACKTICK ||
|
|
215
|
-
(opt.dollarMath && currentChar === CHAR_DOLLAR) ||
|
|
216
|
-
(htmlEnabled && currentChar === CHAR_LT)) {
|
|
217
|
-
isEscaped = hasBackslash(state, n)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Asterisk handling
|
|
221
|
-
if (currentChar === CHAR_ASTERISK) {
|
|
222
|
-
if (!isEscaped) {
|
|
223
|
-
noMark = processTextSegment(inlines, textStart, n, noMark)
|
|
224
|
-
if (n === srcLen - 1) {
|
|
225
|
-
pushInlines(inlines, n, n, 1, '')
|
|
226
|
-
break
|
|
227
|
-
}
|
|
228
|
-
let i = n + 1
|
|
229
|
-
while (i < srcLen && src.charCodeAt(i) === CHAR_ASTERISK) {
|
|
230
|
-
i++
|
|
231
|
-
}
|
|
232
|
-
if (i === srcLen) {
|
|
233
|
-
pushInlines(inlines, n, i - 1, i - n, '')
|
|
234
|
-
} else {
|
|
235
|
-
pushInlines(inlines, n, i - 1, i - n, '')
|
|
236
|
-
textStart = i
|
|
237
|
-
}
|
|
238
|
-
n = i
|
|
239
|
-
continue
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Inline code (backticks)
|
|
244
|
-
if (currentChar === CHAR_BACKTICK) {
|
|
245
|
-
if (!isEscaped) {
|
|
246
|
-
const result = processSymbolPair(state, n, srcLen, CHAR_BACKTICK, noMark, textStart,
|
|
247
|
-
(start, end, len, type) => pushInlines(inlines, start, end, len, type))
|
|
248
|
-
if (result.shouldBreak) break
|
|
249
|
-
if (result.shouldContinue) {
|
|
250
|
-
n = result.newN
|
|
251
|
-
noMark = result.newNoMark
|
|
252
|
-
continue
|
|
253
|
-
}
|
|
254
|
-
noMark = result.newNoMark
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// Inline math ($...$)
|
|
259
|
-
if (opt.dollarMath && currentChar === CHAR_DOLLAR) {
|
|
260
|
-
if (!isEscaped) {
|
|
261
|
-
const result = processSymbolPair(state, n, srcLen, CHAR_DOLLAR, noMark, textStart,
|
|
262
|
-
(start, end, len, type) => pushInlines(inlines, start, end, len, type))
|
|
263
|
-
if (result.shouldBreak) break
|
|
264
|
-
if (result.shouldContinue) {
|
|
265
|
-
n = result.newN
|
|
266
|
-
noMark = result.newNoMark
|
|
267
|
-
continue
|
|
268
|
-
}
|
|
269
|
-
noMark = result.newNoMark
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// HTML tags
|
|
274
|
-
if (htmlEnabled && currentChar === CHAR_LT) {
|
|
275
|
-
if (!isEscaped) {
|
|
276
|
-
let foundClosingTag = false
|
|
277
|
-
for (let i = n + 1; i < srcLen; i++) {
|
|
278
|
-
if (src.charCodeAt(i) === CHAR_GT && !hasBackslash(state, i)) {
|
|
279
|
-
noMark = processTextSegment(inlines, textStart, n, noMark)
|
|
280
|
-
let tag = src.slice(n + 1, i)
|
|
281
|
-
let tagType
|
|
282
|
-
if (tag.charCodeAt(0) === CHAR_SLASH) {
|
|
283
|
-
tag = tag.slice(1)
|
|
284
|
-
tagType = 'close'
|
|
285
|
-
} else {
|
|
286
|
-
tagType = 'open'
|
|
287
|
-
}
|
|
288
|
-
pushInlines(inlines, n, i, i - n + 1, 'html_inline', tag, tagType)
|
|
289
|
-
textStart = i + 1
|
|
290
|
-
n = i + 1
|
|
291
|
-
foundClosingTag = true
|
|
292
|
-
break
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
if (foundClosingTag) {
|
|
296
|
-
continue
|
|
297
|
-
}
|
|
298
|
-
// If no closing tag found, treat as regular character to prevent infinite loops
|
|
299
|
-
}
|
|
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
|
|
300
671
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
|
307
679
|
}
|
|
308
|
-
n++
|
|
309
680
|
}
|
|
310
|
-
|
|
311
|
-
|
|
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
|
|
949
|
+
}
|
|
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)
|
|
312
964
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
nest: opts.nest,
|
|
317
|
-
s: opts.s,
|
|
318
|
-
e: opts.e,
|
|
319
|
-
len: opts.len,
|
|
320
|
-
oLen: opts.oLen,
|
|
321
|
-
type: opts.type
|
|
965
|
+
if (inlineHasCollapsedRef(state) && !state.__strongJaCollapsedRefRegistered) {
|
|
966
|
+
registerCollapsedRefTarget(state)
|
|
967
|
+
state.__strongJaCollapsedRefRegistered = true
|
|
322
968
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (marks[mid].s <= newMark.s) {
|
|
329
|
-
left = mid + 1
|
|
330
|
-
} else {
|
|
331
|
-
right = mid
|
|
969
|
+
|
|
970
|
+
if (opt.mditAttrs && max !== state.posMax) {
|
|
971
|
+
if (!attributesSrc) {
|
|
972
|
+
state.pos = max
|
|
973
|
+
return true
|
|
332
974
|
}
|
|
975
|
+
state.pos = attributesSrc[1].length > 1 ? max + attributesSrc[1].length : max
|
|
976
|
+
return true
|
|
333
977
|
}
|
|
334
|
-
|
|
335
|
-
|
|
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(/[*_]+$/, '')
|
|
336
1001
|
}
|
|
337
1002
|
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
while (i < inlinesLength) {
|
|
343
|
-
if (inlines[i].len === 0 || inlines[i].check) { i++; continue }
|
|
344
|
-
if (inlines[i].type !== '') { i++; continue }
|
|
345
|
-
|
|
346
|
-
if (inlines[i].len > 1) {
|
|
347
|
-
const mixedCheck = checkMixedLanguagePattern(state, inlines, n, i, opt)
|
|
348
|
-
if (mixedCheck.shouldBlock) {
|
|
349
|
-
return [n, 0]
|
|
350
|
-
}
|
|
351
|
-
break
|
|
352
|
-
}
|
|
353
|
-
i++
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
let i = n + 1
|
|
358
|
-
let j = 0
|
|
359
|
-
let nest = 0
|
|
360
|
-
let insideTagsIsClose = 1
|
|
361
|
-
const inlinesLength = inlines.length
|
|
362
|
-
while (i < inlinesLength) {
|
|
363
|
-
if (inlines[i].type !== '') { i++; continue }
|
|
364
|
-
if (inlines[i].len === 0 || inlines[i].check) { i++; continue }
|
|
365
|
-
if (inlines[i].type === 'html_inline') {
|
|
366
|
-
inlines[i].check = true
|
|
367
|
-
insideTagsIsClose = checkInsideTags(inlines, i, memo)
|
|
368
|
-
if (insideTagsIsClose === -1) return [n, nest]
|
|
369
|
-
if (insideTagsIsClose === 0) { i++; continue }
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
nest = checkNest(inlines, marks, n, i, nestTracker)
|
|
373
|
-
if (nest === -1) return [n, nest]
|
|
1003
|
+
const normalizeReferenceCandidate = (state, text, { useClean = false } = {}) => {
|
|
1004
|
+
const source = useClean ? cleanLabelText(text) : text.replace(/\s+/g, ' ').trim()
|
|
1005
|
+
return normalizeRefKey(state, source)
|
|
1006
|
+
}
|
|
374
1007
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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
|
|
1040
|
+
}
|
|
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 = ''
|
|
400
1049
|
}
|
|
1050
|
+
segments.push('[]')
|
|
1051
|
+
pos += 2
|
|
1052
|
+
continue
|
|
401
1053
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
marks.push(...createMarks(state, inlines, i, inlinesLength - 1, memo, opt))
|
|
408
|
-
if (inlines[i].len === 0) { i++; continue }
|
|
409
|
-
} else {
|
|
410
|
-
return [n, nest]
|
|
411
|
-
}
|
|
1054
|
+
const ch = content[pos]
|
|
1055
|
+
if (ch === '[' || ch === ']') {
|
|
1056
|
+
if (buffer) {
|
|
1057
|
+
segments.push(buffer)
|
|
1058
|
+
buffer = ''
|
|
412
1059
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
pushMark(marks, {
|
|
417
|
-
nest: nest + strongNum - 1 - j,
|
|
418
|
-
s: inlines[n].ep - 1,
|
|
419
|
-
e: inlines[n].ep,
|
|
420
|
-
len: 2,
|
|
421
|
-
oLen: inlines[n].len - 2,
|
|
422
|
-
type: 'strong_open'
|
|
423
|
-
})
|
|
424
|
-
inlines[n].ep -= 2
|
|
425
|
-
inlines[n].len -= 2
|
|
426
|
-
pushMark(marks, {
|
|
427
|
-
nest: nest + strongNum - 1 - j,
|
|
428
|
-
s: inlines[i].sp,
|
|
429
|
-
e: inlines[i].sp + 1,
|
|
430
|
-
len: 2,
|
|
431
|
-
oLen: inlines[i].len - 2,
|
|
432
|
-
type: 'strong_close'
|
|
433
|
-
})
|
|
434
|
-
inlines[i].sp += 2
|
|
435
|
-
inlines[i].len -= 2
|
|
436
|
-
j++
|
|
437
|
-
}
|
|
438
|
-
if (inlines[n].len === 0) return [n, nest]
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
if (inlines[n].len === 1 && inlines[i].len > 0) {
|
|
442
|
-
nest++
|
|
443
|
-
const [newN, newNest] = setEm(state, inlines, marks, n, memo, opt, nest, nestTracker)
|
|
444
|
-
n = newN
|
|
445
|
-
nest = newNest
|
|
1060
|
+
segments.push(ch)
|
|
1061
|
+
pos++
|
|
1062
|
+
continue
|
|
446
1063
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
if (n == 0 && memo.inlineMarkEnd) {
|
|
452
|
-
marks.push(...createMarks(state, inlines, n + 1, inlinesLength - 1, memo, opt))
|
|
453
|
-
}
|
|
454
|
-
return [n, nest]
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
const checkInsideTags = (inlines, i, memo) => {
|
|
458
|
-
if (inlines[i].tag === undefined) return 0
|
|
459
|
-
const tagName = inlines[i].tag[0].toLowerCase()
|
|
460
|
-
if (memo.htmlTags[tagName] === undefined) {
|
|
461
|
-
memo.htmlTags[tagName] = 0
|
|
1064
|
+
buffer += ch
|
|
1065
|
+
pos++
|
|
462
1066
|
}
|
|
463
|
-
if (
|
|
464
|
-
|
|
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++
|
|
465
1075
|
}
|
|
466
|
-
|
|
467
|
-
memo.htmlTags[tagName] -= 1
|
|
468
|
-
}
|
|
469
|
-
if (memo.htmlTags[tagName] < 0) {
|
|
470
|
-
return -1
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// Direct check instead of Object.values().every()
|
|
474
|
-
for (const count of Object.values(memo.htmlTags)) {
|
|
475
|
-
if (count !== 0) return 0
|
|
476
|
-
}
|
|
477
|
-
return 1
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// Check if character is ASCII punctuation or space
|
|
481
|
-
// Covers: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ and space
|
|
482
|
-
const isPunctuation = (ch) => {
|
|
483
|
-
if (!ch) return false
|
|
484
|
-
const code = ch.charCodeAt(0)
|
|
485
|
-
// ASCII punctuation: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
|
|
486
|
-
return (code >= 33 && code <= 47) || (code >= 58 && code <= 64) ||
|
|
487
|
-
(code >= 91 && code <= 96) || (code >= 123 && code <= 126) || code === 32
|
|
1076
|
+
return true
|
|
488
1077
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
const isJapanese = (ch) => {
|
|
493
|
-
if (!ch) return false
|
|
494
|
-
const code = ch.charCodeAt(0)
|
|
495
|
-
// Fast ASCII check first
|
|
496
|
-
if (code < 128) return false
|
|
497
|
-
// Hiragana: U+3040-U+309F, Katakana: U+30A0-U+30FF, Kanji: U+4E00-U+9FAF
|
|
498
|
-
return (code >= 0x3040 && code <= 0x309F) ||
|
|
499
|
-
(code >= 0x30A0 && code <= 0x30FF) ||
|
|
500
|
-
(code >= 0x4E00 && code <= 0x9FAF) ||
|
|
501
|
-
// Fallback to regex for complex Unicode cases
|
|
502
|
-
REG_JAPANESE.test(ch)
|
|
1078
|
+
|
|
1079
|
+
const isBracketToken = (token, bracket) => {
|
|
1080
|
+
return token && token.type === 'text' && token.content === bracket
|
|
503
1081
|
}
|
|
504
1082
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
return code === CHAR_SPACE || (code > 126)
|
|
515
|
-
}
|
|
516
|
-
return !REG_JAPANESE.test(ch) && !REG_PUNCTUATION.test(ch)
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
const checkMixedLanguagePattern = (state, inlines, n, i, opt) => {
|
|
520
|
-
const src = state.src
|
|
521
|
-
const openPrevChar = src[inlines[n].s - 1] || ''
|
|
522
|
-
const closeNextChar = src[inlines[i].e + 1] || ''
|
|
523
|
-
|
|
524
|
-
const isEnglishPrefix = isEnglish(openPrevChar)
|
|
525
|
-
const isEnglishSuffix = isEnglish(closeNextChar)
|
|
526
|
-
if (!isEnglishPrefix && !isEnglishSuffix) {
|
|
527
|
-
return { hasEnglishContext: false, hasMarkdownOrHtml: false, shouldBlock: false }
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
const contentBetween = src.slice(inlines[n].e + 1, inlines[i].s)
|
|
531
|
-
const hasMarkdownOrHtml = REG_MARKDOWN_HTML.test(contentBetween)
|
|
532
|
-
|
|
533
|
-
return {
|
|
534
|
-
hasEnglishContext: true,
|
|
535
|
-
hasMarkdownOrHtml,
|
|
536
|
-
shouldBlock: hasMarkdownOrHtml
|
|
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
|
|
1091
|
+
}
|
|
537
1092
|
}
|
|
1093
|
+
return -1
|
|
538
1094
|
}
|
|
539
1095
|
|
|
540
|
-
const
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
const openNextChar = src[inlines[n].e + 1] || ''
|
|
544
|
-
const checkOpenNextChar = isPunctuation(openNextChar)
|
|
545
|
-
const closePrevChar = src[inlines[i].s - 1] || ''
|
|
546
|
-
const checkClosePrevChar = isPunctuation(closePrevChar)
|
|
547
|
-
const closeNextChar = src[inlines[i].e + 1] || ''
|
|
548
|
-
const checkCloseNextChar = (isPunctuation(closeNextChar) || i === inlines.length - 1)
|
|
1096
|
+
const convertCollapsedReferenceLinks = (tokens, state) => {
|
|
1097
|
+
const references = state.env && state.env.references
|
|
1098
|
+
if (!references || Object.keys(references).length === 0) return
|
|
549
1099
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
return false
|
|
555
|
-
}
|
|
1100
|
+
let i = 0
|
|
1101
|
+
while (i < tokens.length) {
|
|
1102
|
+
if (splitBracketToken(tokens, i)) {
|
|
1103
|
+
continue
|
|
556
1104
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
if (inlines[i].len === 0 || inlines[i].check) { i++; continue }
|
|
569
|
-
if (inlines[i].type !== '') { i++; continue }
|
|
570
|
-
|
|
571
|
-
if (inlines[i].len > 0) {
|
|
572
|
-
const mixedCheck = checkMixedLanguagePattern(state, inlines, n, i, opt)
|
|
573
|
-
if (mixedCheck.shouldBlock) {
|
|
574
|
-
return [n, 0]
|
|
575
|
-
}
|
|
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
|
|
576
1116
|
break
|
|
577
1117
|
}
|
|
1118
|
+
closeIdx++
|
|
1119
|
+
}
|
|
1120
|
+
if (closeIdx === -1 || closeIdx >= tokens.length) {
|
|
1121
|
+
i++
|
|
1122
|
+
continue
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
if (closeIdx === i + 1) {
|
|
578
1126
|
i++
|
|
1127
|
+
continue
|
|
579
1128
|
}
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
let i = n + 1
|
|
583
|
-
let nest = 0
|
|
584
|
-
let strongPNum = 0
|
|
585
|
-
let insideTagsIsClose = 1
|
|
586
|
-
const inlinesLength = inlines.length
|
|
587
|
-
while (i < inlinesLength) {
|
|
588
|
-
if (inlines[i].len === 0 || inlines[i].check) { i++; continue }
|
|
589
|
-
if (!sNest && inlines[i].type === 'html_inline') {
|
|
590
|
-
inlines.check = true
|
|
591
|
-
insideTagsIsClose = checkInsideTags(inlines, i, memo)
|
|
592
|
-
if (insideTagsIsClose === -1) return [n, nest]
|
|
593
|
-
if (insideTagsIsClose === 0) { i++; continue }
|
|
594
|
-
}
|
|
595
|
-
if (inlines[i].type !== '') { i++; continue }
|
|
596
|
-
|
|
597
|
-
const emNum = Math.min(inlines[n].len, inlines[i].len)
|
|
598
1129
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
inlines[i].len > 1 ? flag = true : flag = false
|
|
607
|
-
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++
|
|
608
1137
|
}
|
|
609
|
-
if (
|
|
610
|
-
strongPNum++
|
|
611
|
-
i++
|
|
1138
|
+
if (splitBracketToken(tokens, refRemoveStart)) {
|
|
612
1139
|
continue
|
|
613
1140
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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))
|
|
619
1151
|
}
|
|
620
|
-
if (nest === -1) return [n, nest]
|
|
621
|
-
|
|
622
|
-
if (emNum === 1) {
|
|
623
|
-
if (hasPunctuationOrNonJapanese(state, inlines, n, i, opt)) {
|
|
624
|
-
if (memo.inlineMarkEnd) {
|
|
625
|
-
marks.push(...createMarks(state, inlines, i, inlinesLength - 1, memo, opt))
|
|
626
1152
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
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++
|
|
631
1160
|
}
|
|
632
|
-
if (
|
|
633
|
-
i
|
|
1161
|
+
if (refCloseIdx >= tokens.length) {
|
|
1162
|
+
i++
|
|
1163
|
+
continue
|
|
634
1164
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
s: inlines[n].ep,
|
|
639
|
-
e: inlines[n].ep,
|
|
640
|
-
len: 1,
|
|
641
|
-
oLen: inlines[n].len - 1,
|
|
642
|
-
type: 'em_open'
|
|
643
|
-
})
|
|
644
|
-
inlines[n].ep -= 1
|
|
645
|
-
inlines[n].len -= 1
|
|
646
|
-
|
|
647
|
-
if (strongPNum % 2 === 0 || inlines[i].len < 2) {
|
|
648
|
-
pushMark(marks, {
|
|
649
|
-
nest: nest,
|
|
650
|
-
s: inlines[i].sp,
|
|
651
|
-
e: inlines[i].sp,
|
|
652
|
-
len: 1,
|
|
653
|
-
oLen: inlines[i].len - 1,
|
|
654
|
-
type: 'em_close'
|
|
655
|
-
})
|
|
656
|
-
inlines[i].sp += 1
|
|
1165
|
+
const refTokens = tokens.slice(refRemoveStart + 1, refCloseIdx)
|
|
1166
|
+
if (refTokens.length === 0) {
|
|
1167
|
+
refKey = normalizeReferenceCandidate(state, cleanedLabel)
|
|
657
1168
|
} else {
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
s: inlines[i].ep,
|
|
661
|
-
e: inlines[i].ep,
|
|
662
|
-
len: 1,
|
|
663
|
-
oLen: inlines[i].len - 1,
|
|
664
|
-
type: 'em_close'
|
|
665
|
-
})
|
|
666
|
-
inlines[i].sp = inlines[i].ep - 1
|
|
667
|
-
inlines[i].ep -= 1
|
|
1169
|
+
const refLabelText = buildReferenceLabel(refTokens)
|
|
1170
|
+
refKey = normalizeReferenceCandidate(state, refLabelText)
|
|
668
1171
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
return [n, nest]
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
const setText = (inlines, marks, n, nest) => {
|
|
679
|
-
pushMark(marks, {
|
|
680
|
-
nest: nest,
|
|
681
|
-
s: inlines[n].sp,
|
|
682
|
-
e: inlines[n].ep,
|
|
683
|
-
len: inlines[n].len,
|
|
684
|
-
oLen: -1,
|
|
685
|
-
type: 'text'
|
|
686
|
-
})
|
|
687
|
-
inlines[n].len = 0
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
// Nest state management
|
|
691
|
-
const createNestTracker = () => {
|
|
692
|
-
return {
|
|
693
|
-
strongNest: 0,
|
|
694
|
-
emNest: 0,
|
|
695
|
-
markIndex: 0
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
const updateNestTracker = (tracker, marks, targetPos) => {
|
|
700
|
-
while (tracker.markIndex < marks.length && marks[tracker.markIndex].s <= targetPos) {
|
|
701
|
-
const mark = marks[tracker.markIndex]
|
|
702
|
-
if (mark.type === 'strong_open') tracker.strongNest++
|
|
703
|
-
else if (mark.type === 'strong_close') tracker.strongNest--
|
|
704
|
-
else if (mark.type === 'em_open') tracker.emNest++
|
|
705
|
-
else if (mark.type === 'em_close') tracker.emNest--
|
|
706
|
-
tracker.markIndex++
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
const checkNest = (inlines, marks, n, i, nestTracker) => {
|
|
711
|
-
if (marks.length === 0) return 1
|
|
712
|
-
// Update nest state up to current position
|
|
713
|
-
updateNestTracker(nestTracker, marks, inlines[n].s)
|
|
714
|
-
|
|
715
|
-
const parentNest = nestTracker.strongNest + nestTracker.emNest
|
|
716
|
-
// Check if there's a conflicting close mark before the end position
|
|
717
|
-
let parentCloseN = nestTracker.markIndex
|
|
718
|
-
while (parentCloseN < marks.length) {
|
|
719
|
-
if (marks[parentCloseN].nest === parentNest) break
|
|
720
|
-
parentCloseN++
|
|
721
|
-
}
|
|
722
|
-
if (parentCloseN < marks.length && marks[parentCloseN].s < inlines[i].s) {
|
|
723
|
-
return -1
|
|
724
|
-
}
|
|
725
|
-
return parentNest + 1
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
const createMarks = (state, inlines, start, end, memo, opt) => {
|
|
729
|
-
let marks = []
|
|
730
|
-
let n = start
|
|
731
|
-
const nestTracker = createNestTracker()
|
|
732
|
-
|
|
733
|
-
while (n < end) {
|
|
734
|
-
if (inlines[n].type !== '') { n++; continue }
|
|
735
|
-
let nest = 0
|
|
736
|
-
|
|
737
|
-
if (inlines[n].len > 1) {
|
|
738
|
-
const [newN, newNest] = setStrong(state, inlines, marks, n, memo, opt, nestTracker)
|
|
739
|
-
n = newN
|
|
740
|
-
nest = newNest
|
|
741
|
-
}
|
|
742
|
-
if (inlines[n].len !== 0) {
|
|
743
|
-
const [newN2, newNest2] = setEm(state, inlines, marks, n, memo, opt, null, nestTracker)
|
|
744
|
-
n = newN2
|
|
745
|
-
nest = newNest2
|
|
746
|
-
}
|
|
747
|
-
if (inlines[n].len !== 0) {
|
|
748
|
-
setText(inlines, marks, n, nest)
|
|
749
|
-
}
|
|
750
|
-
n++
|
|
751
|
-
}
|
|
752
|
-
return marks
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
const mergeInlinesAndMarks = (inlines, marks) => {
|
|
756
|
-
// marks array is already sorted, skip sorting
|
|
757
|
-
const merged = []
|
|
758
|
-
let markIndex = 0
|
|
759
|
-
for (const token of inlines) {
|
|
760
|
-
if (token.type === '') {
|
|
761
|
-
while (markIndex < marks.length && marks[markIndex].s >= token.s && marks[markIndex].e <= token.e) {
|
|
762
|
-
merged.push(marks[markIndex])
|
|
763
|
-
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
|
|
764
1178
|
}
|
|
1179
|
+
existingLinkOpen = tokens[refRemoveStart]
|
|
1180
|
+
existingLinkClose = tokens[linkCloseIdx]
|
|
1181
|
+
refRemoveCount = linkCloseIdx - refRemoveStart + 1
|
|
765
1182
|
} else {
|
|
766
|
-
|
|
1183
|
+
i++
|
|
1184
|
+
continue
|
|
767
1185
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
const src = state.src
|
|
780
|
-
let attributesSrc
|
|
781
|
-
if (start > max) return false
|
|
782
|
-
if (src.charCodeAt(start) !== CHAR_ASTERISK) return false
|
|
783
|
-
if (hasBackslash(state, start)) return false
|
|
784
|
-
|
|
785
|
-
if (opt.mditAttrs) {
|
|
786
|
-
attributesSrc = src.match(/((\n)? *){([^{}\n!@#%^&*()]+?)} *$/)
|
|
787
|
-
if (attributesSrc && attributesSrc[3] !== '.') {
|
|
788
|
-
max = src.slice(0, attributesSrc.index).length
|
|
789
|
-
if (attributesSrc[2] === '\n') {
|
|
790
|
-
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
|
|
791
1197
|
}
|
|
792
|
-
if(
|
|
793
|
-
|
|
1198
|
+
if (refRemoveCount > 0) {
|
|
1199
|
+
tokens.splice(refRemoveStart, refRemoveCount)
|
|
794
1200
|
}
|
|
1201
|
+
linkOpenToken = existingLinkOpen
|
|
1202
|
+
linkCloseToken = existingLinkClose
|
|
795
1203
|
} else {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
1204
|
+
if (!refKey) {
|
|
1205
|
+
i++
|
|
1206
|
+
continue
|
|
799
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'
|
|
800
1228
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
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)
|
|
827
1365
|
}
|
|
828
|
-
|
|
829
|
-
state.
|
|
830
|
-
}
|
|
831
|
-
return true
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
const mditStrongJa = (md, option) => {
|
|
835
|
-
const opt = {
|
|
836
|
-
dollarMath: true, //inline math $...$
|
|
837
|
-
mditAttrs: true, //markdown-it-attrs
|
|
838
|
-
mdBreaks: md.options.breaks,
|
|
839
|
-
disallowMixed: false, //Non-Japanese text handling
|
|
840
|
-
}
|
|
841
|
-
if (option) Object.assign(opt, option)
|
|
842
|
-
|
|
843
|
-
md.inline.ruler.before('emphasis', 'strong_ja', (state, silent) => {
|
|
844
|
-
return strongJa(state, silent, opt)
|
|
1366
|
+
delete state.env.__strongJaCollapsedTargets
|
|
1367
|
+
delete state.env.__strongJaCollapsedTargetSet
|
|
845
1368
|
})
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
export default mditStrongJa
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
export default mditStrongJa
|