@peaceroad/markdown-it-strong-ja 0.5.0 → 0.5.2

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.
Files changed (2) hide show
  1. package/index.js +1466 -1024
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1,61 +1,148 @@
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
-
1
+ import Token from 'markdown-it/lib/token.mjs'
2
+ import { parseLinkDestination, parseLinkTitle } from 'markdown-it/lib/helpers/index.mjs'
3
+ import { isSpace } from 'markdown-it/lib/common/utils.mjs'
4
+
5
+ const CHAR_ASTERISK = 0x2A // *
6
+ //const CHAR_UNDERSCORE = 0x5F // _
7
+ const CHAR_BACKSLASH = 0x5C // \
8
+ const CHAR_BACKTICK = 0x60 // `
9
+ const CHAR_DOLLAR = 0x24 // $
10
+ const CHAR_LT = 0x3C // <
11
+ const CHAR_GT = 0x3E // >
12
+ const CHAR_SLASH = 0x2F // /
13
+ const CHAR_SPACE = 0x20 // ' ' (space)
14
+ const CHAR_OPEN_BRACKET = 0x5B // [
15
+ const CHAR_CLOSE_BRACKET = 0x5D // ]
16
+ const CHAR_OPEN_PAREN = 0x28 // (
17
+ const CHAR_CLOSE_PAREN = 0x29 // )
18
+
19
+ const REG_ASTERISKS = /^\*+$/
20
+ const REG_ATTRS = /{[^{}\n!@#%^&*()]+?}$/
21
+ const REG_PUNCTUATION = /[!-/:-@[-`{-~ ]/
22
+ 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 // ひらがな|カタカナ|漢字|句読点|記号|フォーマット文字|絵文字
23
+
24
+ const REG_MARKDOWN_HTML = /^\[[^\[\]]+\]\([^)]+\)$|^<([a-zA-Z][a-zA-Z0-9]*)[^>]*>([^<]+<\/\1>)$|^`[^`]+`$|^\$[^$]+\$$/ // for mixed-language context detection
25
+
26
+ const hasBackslash = (state, start) => {
27
+ if (start <= 0) return false
28
+ const cache = state.__strongJaBackslashCache
29
+ if (cache && cache.has(start)) {
30
+ return cache.get(start)
31
+ }
32
+ let slashNum = 0
33
+ let i = start - 1
34
+ const src = state.src
35
+ if (i < 0 || src.charCodeAt(i) !== CHAR_BACKSLASH) {
36
+ return false
37
+ }
38
+ while (i >= 0 && src.charCodeAt(i) === CHAR_BACKSLASH) {
39
+ slashNum++
40
+ i--
41
+ }
42
+ const isEscaped = slashNum % 2 === 1
43
+ if (cache) {
44
+ cache.set(start, isEscaped)
45
+ } else {
46
+ state.__strongJaBackslashCache = new Map([[start, isEscaped]])
47
+ }
48
+ return isEscaped
49
+ }
50
+
51
+ const findMatchingBracket = (state, start, max, openChar, closeChar) => {
52
+ let depth = 1
53
+ let pos = start + 1
54
+ const src = state.src
55
+ while (pos < max) {
56
+ const ch = src.charCodeAt(pos)
57
+ if (ch === openChar && !hasBackslash(state, pos)) {
58
+ depth++
59
+ } else if (ch === closeChar && !hasBackslash(state, pos)) {
60
+ depth--
61
+ if (depth === 0) return pos
62
+ }
63
+ pos++
64
+ }
65
+ return -1
66
+ }
67
+
68
+ const getInlineLabelRanges = (inlineLinkRanges) => {
69
+ if (!inlineLinkRanges || inlineLinkRanges.length === 0) return null
70
+ return inlineLinkRanges.__labelRanges
71
+ }
72
+
73
+ const hasInlineLinkLabelCrossing = (inlineLinkRanges, from, to) => {
74
+ if (from >= to) return false
75
+ const labelRanges = getInlineLabelRanges(inlineLinkRanges)
76
+ if (!labelRanges || labelRanges.length === 0) return false
77
+ if (labelRanges.length <= 8) {
78
+ for (let idx = 0; idx < labelRanges.length; idx++) {
79
+ const range = labelRanges[idx]
80
+ if (range.start >= to) break
81
+ if (range.start >= from && range.end >= to) return true
82
+ }
83
+ return false
84
+ }
85
+ let left = 0
86
+ let right = labelRanges.length - 1
87
+ let firstIdx = labelRanges.length
88
+ while (left <= right) {
89
+ const mid = left + Math.floor((right - left) / 2)
90
+ if (labelRanges[mid].start < from) {
91
+ left = mid + 1
92
+ } else {
93
+ firstIdx = mid
94
+ right = mid - 1
95
+ }
96
+ }
97
+ for (let idx = firstIdx; idx < labelRanges.length; idx++) {
98
+ const range = labelRanges[idx]
99
+ if (range.start >= to) break
100
+ if (range.end >= to) return true
101
+ }
102
+ return false
103
+ }
104
+
55
105
  const findRefRangeIndex = (pos, refRanges) => {
56
106
  if (!refRanges || refRanges.length === 0) return -1
57
- const cache = refRanges.__cache
58
- if (cache && cache.has(pos)) return cache.get(pos)
107
+
108
+ const tryIndex = (idx) => {
109
+ if (idx < 0 || idx >= refRanges.length) return -1
110
+ const range = refRanges[idx]
111
+ if (pos >= range.start && pos <= range.end) {
112
+ return range.hasReference ? idx : -1
113
+ }
114
+ return null
115
+ }
116
+
117
+ const tracker = refRanges.__lastIndexState || (refRanges.__lastIndexState = { idx: 0 })
118
+ let idx = tracker.idx
119
+ if (idx >= refRanges.length) idx = refRanges.length - 1
120
+ let result = tryIndex(idx)
121
+ if (result !== null) {
122
+ tracker.idx = idx
123
+ return result
124
+ }
125
+
126
+ if (pos < refRanges[idx].start) {
127
+ while (idx > 0 && pos < refRanges[idx].start) {
128
+ idx--
129
+ result = tryIndex(idx)
130
+ if (result !== null) {
131
+ tracker.idx = idx
132
+ return result
133
+ }
134
+ }
135
+ } else {
136
+ while (idx < refRanges.length - 1 && pos > refRanges[idx].end) {
137
+ idx++
138
+ result = tryIndex(idx)
139
+ if (result !== null) {
140
+ tracker.idx = idx
141
+ return result
142
+ }
143
+ }
144
+ }
145
+
59
146
  let left = 0
60
147
  let right = refRanges.length - 1
61
148
  while (left <= right) {
@@ -66,43 +153,43 @@ const findRefRangeIndex = (pos, refRanges) => {
66
153
  } else if (pos > range.end) {
67
154
  left = mid + 1
68
155
  } else {
69
- const result = range.hasReference ? mid : -1
70
- if (cache) cache.set(pos, result)
71
- return result
156
+ tracker.idx = mid
157
+ return range.hasReference ? mid : -1
72
158
  }
73
159
  }
74
- if (cache) cache.set(pos, -1)
75
160
  return -1
76
161
  }
77
-
78
- // Detect reference-link label ranges within the current inline slice
162
+
163
+ // Detect reference-link label ranges within the current inline slice
79
164
  const computeReferenceRanges = (state, start, max) => {
80
165
  const src = state.src
81
166
  const references = state.env && state.env.references
82
- const hasReferences = references && Object.keys(references).length > 0
167
+ const referenceCount = state.__strongJaReferenceCount
168
+ const hasReferences = references && (referenceCount !== undefined
169
+ ? referenceCount > 0
170
+ : Object.keys(references).length > 0)
171
+ if (!hasReferences) return []
83
172
  const firstBracket = src.indexOf('[', start)
84
173
  if (firstBracket === -1 || firstBracket >= max) return []
85
174
  const ranges = []
86
175
  let pos = start
87
176
  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)
177
+ if (src.charCodeAt(pos) === CHAR_OPEN_BRACKET && !hasBackslash(state, pos)) {
178
+ const labelClose = findMatchingBracket(state, pos, max, CHAR_OPEN_BRACKET, CHAR_CLOSE_BRACKET)
90
179
  if (labelClose !== -1) {
91
180
  const nextPos = labelClose + 1
92
181
  if (nextPos < max && src.charCodeAt(nextPos) === CHAR_OPEN_BRACKET && !hasBackslash(state, nextPos)) {
93
182
  const refClose = findMatchingBracket(state, nextPos, max, CHAR_OPEN_BRACKET, CHAR_CLOSE_BRACKET)
94
183
  if (refClose !== -1) {
95
184
  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]
105
- }
185
+ if (refClose === nextPos + 1) {
186
+ const labelRaw = src.slice(pos + 1, labelClose)
187
+ const normalizedLabel = normalizeReferenceCandidate(state, labelRaw, { useClean: true })
188
+ hasReference = !!references[normalizedLabel]
189
+ } else {
190
+ const refRaw = src.slice(nextPos + 1, refClose)
191
+ const normalizedRef = normalizeReferenceCandidate(state, refRaw)
192
+ hasReference = !!references[normalizedRef]
106
193
  }
107
194
  if (hasReference) {
108
195
  ranges.push({ start: pos, end: labelClose, hasReference: true })
@@ -121,7 +208,82 @@ const computeReferenceRanges = (state, start, max) => {
121
208
  }
122
209
  return ranges
123
210
  }
124
-
211
+
212
+ const computeInlineLinkRanges = (state, start, max) => {
213
+ const src = state.src
214
+ const ranges = []
215
+ const labelRanges = []
216
+ let pos = start
217
+ let rangeId = 0
218
+ while (pos < max) {
219
+ if (src.charCodeAt(pos) === CHAR_OPEN_BRACKET && !hasBackslash(state, pos)) {
220
+ const labelClose = findMatchingBracket(state, pos, max, CHAR_OPEN_BRACKET, CHAR_CLOSE_BRACKET)
221
+ if (labelClose === -1) break
222
+ let destStart = labelClose + 1
223
+ while (destStart < max) {
224
+ const ch = src.charCodeAt(destStart)
225
+ if (ch !== CHAR_SPACE && ch !== 0x0A && ch !== 0x09) break
226
+ destStart++
227
+ }
228
+ if (destStart < max && src.charCodeAt(destStart) === CHAR_OPEN_PAREN && !hasBackslash(state, destStart)) {
229
+ const destClose = findMatchingBracket(state, destStart, max, CHAR_OPEN_PAREN, CHAR_CLOSE_PAREN)
230
+ if (destClose !== -1) {
231
+ const labelRange = { start: pos, end: labelClose, kind: 'label', id: rangeId }
232
+ ranges.push(labelRange)
233
+ labelRanges.push(labelRange)
234
+ ranges.push({ start: destStart, end: destClose, kind: 'dest', id: rangeId })
235
+ rangeId++
236
+ pos = destClose + 1
237
+ continue
238
+ }
239
+ }
240
+ pos = labelClose + 1
241
+ continue
242
+ }
243
+ pos++
244
+ }
245
+ if (ranges.length && labelRanges.length) {
246
+ ranges.__labelRanges = labelRanges
247
+ }
248
+ return ranges
249
+ }
250
+
251
+ const getInlineRangeCacheMap = (ranges, kind, create) => {
252
+ const prop = kind ? `__cache_${kind}` : '__cache_any'
253
+ let cache = ranges[prop]
254
+ if (!cache && create) {
255
+ cache = new Map()
256
+ ranges[prop] = cache
257
+ }
258
+ return cache
259
+ }
260
+
261
+ const findInlineLinkRange = (pos, ranges, kind) => {
262
+ if (!ranges || ranges.length === 0) return null
263
+ const cache = getInlineRangeCacheMap(ranges, kind, false)
264
+ if (cache && cache.has(pos)) return cache.get(pos)
265
+ let left = 0
266
+ let right = ranges.length - 1
267
+ let found = null
268
+ while (left <= right) {
269
+ const mid = left + Math.floor((right - left) / 2)
270
+ const range = ranges[mid]
271
+ if (pos < range.start) {
272
+ right = mid - 1
273
+ } else if (pos > range.end) {
274
+ left = mid + 1
275
+ } else {
276
+ if (!kind || range.kind === kind) {
277
+ found = range
278
+ }
279
+ break
280
+ }
281
+ }
282
+ const storeCache = getInlineRangeCacheMap(ranges, kind, true)
283
+ storeCache.set(pos, found)
284
+ return found
285
+ }
286
+
125
287
  const copyInlineTokenFields = (dest, src) => {
126
288
  if (src.attrs) dest.attrs = src.attrs
127
289
  if (src.map) dest.map = src.map
@@ -135,21 +297,14 @@ const copyInlineTokenFields = (dest, src) => {
135
297
  dest.hidden = src.hidden
136
298
  }
137
299
 
138
- const inlineHasCollapsedRef = (state) => {
139
- if (state.__strongJaHasCollapsedRefs === undefined) {
140
- state.__strongJaHasCollapsedRefs = /\[[^\]]*\]\s*\[[^\]]*\]/.test(state.src)
141
- }
142
- return state.__strongJaHasCollapsedRefs
143
- }
144
-
145
- const registerCollapsedRefTarget = (state) => {
300
+ const registerPostProcessTarget = (state) => {
146
301
  const env = state.env
147
- if (!env.__strongJaCollapsedTargets) {
148
- env.__strongJaCollapsedTargets = []
149
- env.__strongJaCollapsedTargetSet = typeof WeakSet !== 'undefined' ? new WeakSet() : null
302
+ if (!env.__strongJaPostProcessTargets) {
303
+ env.__strongJaPostProcessTargets = []
304
+ env.__strongJaPostProcessTargetSet = typeof WeakSet !== 'undefined' ? new WeakSet() : null
150
305
  }
151
- const targets = env.__strongJaCollapsedTargets
152
- const targetSet = env.__strongJaCollapsedTargetSet
306
+ const targets = env.__strongJaPostProcessTargets
307
+ const targetSet = env.__strongJaPostProcessTargetSet
153
308
  if (targetSet) {
154
309
  if (targetSet.has(state.tokens)) return
155
310
  targetSet.add(state.tokens)
@@ -160,112 +315,112 @@ const registerCollapsedRefTarget = (state) => {
160
315
  }
161
316
 
162
317
  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
- }
318
+ const src = state.src
319
+ let i = 0
320
+ let attrsIsText = {
321
+ val: false,
322
+ tag: '',
323
+ }
324
+ while (i < inlines.length) {
325
+ let type = inlines[i].type
326
+ const tag = type.replace(/(?:_open|_close)$/, '')
327
+
328
+ if (/_open$/.test(type)) {
329
+ const startToken = state.push(type, tag, 1)
330
+ startToken.markup = tag === 'strong' ? '**' : '*'
331
+ attrsIsText = {
332
+ val: true,
333
+ tag: tag,
334
+ }
335
+ }
336
+
337
+ if (type === 'html_inline') {
338
+ type = 'text'
339
+ }
340
+ if (type === 'text') {
341
+ let content = src.slice(inlines[i].s, inlines[i].e + 1)
342
+ if (REG_ASTERISKS.test(content)) {
343
+ const asteriskToken = state.push(type, '', 0)
344
+ asteriskToken.content = content
345
+ i++
346
+ continue
347
+ }
348
+ if (opt.mditAttrs && attrsIsText.val && i + 1 < inlines.length) {
349
+ const hasImmediatelyAfterAsteriskClose = inlines[i+1].type === attrsIsText.tag + '_close'
350
+ if (hasImmediatelyAfterAsteriskClose && REG_ATTRS.test(content)) {
351
+ const attrsToken = state.push(type, '', 0)
352
+
353
+ const hasBackslashBeforeCurlyAttribute = content.match(/(\\+){/)
354
+ if (hasBackslashBeforeCurlyAttribute) {
355
+ if (hasBackslashBeforeCurlyAttribute[1].length === 1) {
356
+ attrsToken.content = content.replace(/\\{/, '{')
357
+ } else {
358
+ let backSlashNum = Math.floor(hasBackslashBeforeCurlyAttribute[1].length / 2)
359
+ let k = 0
360
+ let backSlash = ''
361
+ while (k < backSlashNum) {
362
+ backSlash += '\\'
363
+ k++
364
+ }
365
+ attrsToken.content = content.replace(/\\+{/, backSlash + '{')
366
+ }
367
+ } else {
368
+ attrsToken.content = content
369
+ }
370
+ attrsIsText.val = false
371
+ i++
372
+ continue
373
+ }
374
+ }
375
+
376
+ const childTokens = state.md.parseInline(content, state.env)
377
+ if (childTokens[0] && childTokens[0].children) {
378
+ let j = 0
379
+ while (j < childTokens[0].children.length) {
380
+ const t = childTokens[0].children[j]
381
+ if (t.type === 'softbreak' && !opt.mdBreaks) {
382
+ t.type = 'text'
383
+ t.tag = ''
384
+ t.content = '\n'
385
+ }
386
+ if (!opt.mditAttrs && t.tag === 'br') {
387
+ t.tag = ''
388
+ t.content = '\n'
389
+ }
235
390
  const token = state.push(t.type, t.tag, t.nesting)
236
391
  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
-
392
+ j++
393
+ }
394
+ }
395
+ }
396
+
397
+ if (/_close$/.test(type)) {
398
+ const closeToken = state.push(type, tag, -1)
399
+ closeToken.markup = tag === 'strong' ? '**' : '*'
400
+ attrsIsText = {
401
+ val: false,
402
+ tag: '',
403
+ }
404
+ }
405
+
406
+ i++
407
+ }
408
+ }
409
+
410
+ const pushInlines = (inlines, s, e, len, type, tag, tagType) => {
411
+ const inline = {
412
+ s: s,
413
+ sp: s,
414
+ e: e,
415
+ ep: e,
416
+ len: len,
417
+ type: type,
418
+ check: type === 'text',
419
+ }
420
+ if (tag) inline.tag = [tag, tagType]
421
+ inlines.push(inline)
422
+ }
423
+
269
424
  const findNextSymbolPos = (state, n, max, symbol) => {
270
425
  const src = state.src
271
426
  if (src.charCodeAt(n) !== symbol || hasBackslash(state, n)) return -1
@@ -291,374 +446,389 @@ const processSymbolPair = (state, n, srcLen, symbol, noMark, textStart, pushInli
291
446
  return { shouldBreak: true, newN: nextSymbolPos + 1, newNoMark }
292
447
  }
293
448
  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
-
449
+ }
450
+
451
+ const processTextSegment = (inlines, textStart, n, noMark) => {
452
+ if (n !== 0 && noMark.length !== 0) {
453
+ pushInlines(inlines, textStart, n - 1, n - textStart, 'text')
454
+ return ''
455
+ }
456
+ return noMark
457
+ }
458
+
459
+ const createInlines = (state, start, max, opt) => {
460
+ const src = state.src
461
+ const srcLen = max
462
+ const htmlEnabled = state.md.options.html
463
+ let n = start
464
+ let inlines = []
465
+ let noMark = ''
466
+ let textStart = n
467
+
468
+ // Infinite loop prevention
469
+ const maxIterations = srcLen * 2 // Safe upper bound
470
+ let iterations = 0
471
+
472
+ while (n < srcLen) {
473
+ // Prevent infinite loops
474
+ iterations++
475
+ if (iterations > maxIterations) {
476
+ // Add remaining text as-is and exit safely
477
+ if (textStart < srcLen) {
478
+ pushInlines(inlines, textStart, srcLen - 1, srcLen - textStart, 'text')
479
+ }
480
+ break
481
+ }
482
+
483
+ const currentChar = src.charCodeAt(n)
484
+
485
+ // Unified escape check
486
+ let isEscaped = false
487
+ if (currentChar === CHAR_ASTERISK || currentChar === CHAR_BACKTICK ||
488
+ (opt.dollarMath && currentChar === CHAR_DOLLAR) ||
489
+ (htmlEnabled && currentChar === CHAR_LT)) {
490
+ isEscaped = hasBackslash(state, n)
491
+ }
492
+
493
+ // Asterisk handling
494
+ if (currentChar === CHAR_ASTERISK) {
495
+ if (!isEscaped) {
496
+ noMark = processTextSegment(inlines, textStart, n, noMark)
497
+ if (n === srcLen - 1) {
498
+ pushInlines(inlines, n, n, 1, '')
499
+ break
500
+ }
501
+ let i = n + 1
502
+ while (i < srcLen && src.charCodeAt(i) === CHAR_ASTERISK) {
503
+ i++
504
+ }
505
+ if (i === srcLen) {
506
+ pushInlines(inlines, n, i - 1, i - n, '')
507
+ } else {
508
+ pushInlines(inlines, n, i - 1, i - n, '')
509
+ textStart = i
510
+ }
511
+ n = i
512
+ continue
513
+ }
514
+ }
515
+
516
+ // Inline code (backticks)
517
+ if (currentChar === CHAR_BACKTICK) {
518
+ if (!isEscaped) {
519
+ const result = processSymbolPair(state, n, srcLen, CHAR_BACKTICK, noMark, textStart,
520
+ (start, end, len, type) => pushInlines(inlines, start, end, len, type))
521
+ if (result.shouldBreak) break
522
+ if (result.shouldContinue) {
523
+ n = result.newN
524
+ noMark = result.newNoMark
525
+ continue
526
+ }
527
+ noMark = result.newNoMark
528
+ }
529
+ }
530
+
531
+ // Inline math ($...$)
532
+ if (opt.dollarMath && currentChar === CHAR_DOLLAR) {
533
+ if (!isEscaped) {
534
+ const result = processSymbolPair(state, n, srcLen, CHAR_DOLLAR, noMark, textStart,
535
+ (start, end, len, type) => pushInlines(inlines, start, end, len, type))
536
+ if (result.shouldBreak) break
537
+ if (result.shouldContinue) {
538
+ n = result.newN
539
+ noMark = result.newNoMark
540
+ continue
541
+ }
542
+ noMark = result.newNoMark
543
+ }
544
+ }
545
+
546
+ // HTML tags
547
+ if (htmlEnabled && currentChar === CHAR_LT) {
548
+ if (!isEscaped) {
549
+ let foundClosingTag = false
550
+ for (let i = n + 1; i < srcLen; i++) {
551
+ if (src.charCodeAt(i) === CHAR_GT && !hasBackslash(state, i)) {
552
+ noMark = processTextSegment(inlines, textStart, n, noMark)
553
+ let tag = src.slice(n + 1, i)
554
+ let tagType
555
+ if (tag.charCodeAt(0) === CHAR_SLASH) {
556
+ tag = tag.slice(1)
557
+ tagType = 'close'
558
+ } else {
559
+ tagType = 'open'
560
+ }
561
+ pushInlines(inlines, n, i, i - n + 1, 'html_inline', tag, tagType)
562
+ textStart = i + 1
563
+ n = i + 1
564
+ foundClosingTag = true
565
+ break
566
+ }
567
+ }
568
+ if (foundClosingTag) {
569
+ continue
570
+ }
571
+ // If no closing tag found, treat as regular character to prevent infinite loops
572
+ }
573
+ }
574
+
575
+ // Regular character
576
+ noMark += src[n]
577
+ if (n === srcLen - 1) {
578
+ pushInlines(inlines, textStart, n, n - textStart + 1, 'text')
579
+ break
580
+ }
581
+ n++
582
+ }
583
+ return inlines
584
+ }
585
+
586
+ const pushMark = (marks, opts) => {
587
+ // Maintain sorted order during insertion
588
+ const newMark = {
589
+ nest: opts.nest,
590
+ s: opts.s,
591
+ e: opts.e,
592
+ len: opts.len,
593
+ oLen: opts.oLen,
594
+ type: opts.type
595
+ }
596
+ // Binary search for insertion point to maintain sorted order
597
+ let left = 0
598
+ let right = marks.length
599
+ while (left < right) {
600
+ const mid = Math.floor((left + right) / 2)
601
+ if (marks[mid].s <= newMark.s) {
602
+ left = mid + 1
603
+ } else {
604
+ right = mid
605
+ }
606
+ }
607
+
608
+ marks.splice(left, 0, newMark)
609
+ }
610
+
611
+ const setStrong = (state, inlines, marks, n, memo, opt, nestTracker, refRanges, inlineLinkRanges) => {
612
+ if (opt.disallowMixed === true) {
613
+ let i = n + 1
614
+ const inlinesLength = inlines.length
615
+ while (i < inlinesLength) {
616
+ if (inlines[i].len === 0 || inlines[i].check) { i++; continue }
617
+ if (inlines[i].type !== '') { i++; continue }
618
+
619
+ if (inlines[i].len > 1) {
620
+ const mixedCheck = checkMixedLanguagePattern(state, inlines, n, i, opt)
621
+ if (mixedCheck.shouldBlock) {
622
+ return [n, 0]
623
+ }
624
+ break
625
+ }
626
+ i++
627
+ }
628
+ }
629
+
475
630
  const strongOpenRange = findRefRangeIndex(inlines[n].s, refRanges)
631
+ const openLinkRange = findInlineLinkRange(inlines[n].s, inlineLinkRanges)
476
632
  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
-
633
+ let j = 0
634
+ let nest = 0
635
+ let insideTagsIsClose = 1
636
+ const inlinesLength = inlines.length
637
+ while (i < inlinesLength) {
638
+ if (inlines[i].type !== '') { i++; continue }
639
+ if (inlines[i].len === 0 || inlines[i].check) { i++; continue }
640
+ if (inlines[i].type === 'html_inline') {
641
+ inlines[i].check = true
642
+ insideTagsIsClose = checkInsideTags(inlines, i, memo)
643
+ if (insideTagsIsClose === -1) return [n, nest]
644
+ if (insideTagsIsClose === 0) { i++; continue }
645
+ }
646
+
647
+ if (inlineLinkRanges && inlineLinkRanges.length > 0 &&
648
+ hasInlineLinkLabelCrossing(inlineLinkRanges, inlines[n].ep + 1, inlines[i].sp)) {
649
+ i++
650
+ continue
651
+ }
652
+
491
653
  const closeRange = findRefRangeIndex(inlines[i].s, refRanges)
492
654
  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) {
655
+
656
+ const closeLinkRange = findInlineLinkRange(inlines[i].s, inlineLinkRanges)
657
+ if (openLinkRange || closeLinkRange) {
658
+ if (!openLinkRange || !closeLinkRange || openLinkRange.id !== closeLinkRange.id || openLinkRange.kind !== closeLinkRange.kind) {
659
+ i++
660
+ continue
661
+ }
662
+ }
663
+
664
+ nest = checkNest(inlines, marks, n, i, nestTracker)
665
+ if (nest === -1) return [n, nest]
666
+
667
+ if (inlines[i].len === 1 && inlines[n].len > 2) {
668
+ pushMark(marks, {
669
+ nest: nest,
670
+ s: inlines[n].ep,
671
+ e: inlines[n].ep,
672
+ len: 1,
673
+ oLen: inlines[n].len - 1,
674
+ type: 'em_open'
675
+ })
676
+ pushMark(marks, {
677
+ nest: nest,
678
+ s: inlines[i].sp,
679
+ e: inlines[i].ep,
680
+ len: 1,
681
+ oLen: inlines[i].len - 1,
682
+ type: 'em_close'
683
+ })
684
+ inlines[n].len -= 1
685
+ inlines[n].ep -= 1
686
+ inlines[i].len -= 1
687
+ if (inlines[i].len > 0) inlines[i].sp += 1
688
+ if (insideTagsIsClose === 1) {
689
+ const [newN, newNest] = setEm(state, inlines, marks, n, memo, opt, null, nestTracker, refRanges, inlineLinkRanges)
690
+ n = newN
691
+ nest = newNest
692
+ }
693
+ }
694
+ let strongNum = Math.trunc(Math.min(inlines[n].len, inlines[i].len) / 2)
695
+
696
+ if (inlines[i].len > 1) {
527
697
  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
-
698
+ if (memo.inlineMarkEnd) {
699
+ marks.push(...createMarks(state, inlines, i, inlinesLength - 1, memo, opt, refRanges, inlineLinkRanges))
700
+ if (inlines[i].len === 0) { i++; continue }
701
+ } else {
702
+ return [n, nest]
703
+ }
704
+ }
705
+
706
+ j = 0
707
+ while (j < strongNum) {
708
+ pushMark(marks, {
709
+ nest: nest + strongNum - 1 - j,
710
+ s: inlines[n].ep - 1,
711
+ e: inlines[n].ep,
712
+ len: 2,
713
+ oLen: inlines[n].len - 2,
714
+ type: 'strong_open'
715
+ })
716
+ inlines[n].ep -= 2
717
+ inlines[n].len -= 2
718
+ pushMark(marks, {
719
+ nest: nest + strongNum - 1 - j,
720
+ s: inlines[i].sp,
721
+ e: inlines[i].sp + 1,
722
+ len: 2,
723
+ oLen: inlines[i].len - 2,
724
+ type: 'strong_close'
725
+ })
726
+ inlines[i].sp += 2
727
+ inlines[i].len -= 2
728
+ j++
729
+ }
730
+ if (inlines[n].len === 0) return [n, nest]
731
+ }
732
+
733
+ if (inlines[n].len === 1 && inlines[i].len > 0) {
734
+ nest++
735
+ const [newN, newNest] = setEm(state, inlines, marks, n, memo, opt, nest, nestTracker, refRanges, inlineLinkRanges)
736
+ n = newN
737
+ nest = newNest
738
+ }
739
+
740
+ i++
741
+ }
742
+
743
+ if (n == 0 && memo.inlineMarkEnd) {
744
+ marks.push(...createMarks(state, inlines, n + 1, inlinesLength - 1, memo, opt, refRanges, inlineLinkRanges))
745
+ }
746
+ return [n, nest]
747
+ }
748
+
749
+ const checkInsideTags = (inlines, i, memo) => {
750
+ if (inlines[i].tag === undefined) return 0
751
+ const tagName = inlines[i].tag[0].toLowerCase()
752
+ if (memo.htmlTags[tagName] === undefined) {
753
+ memo.htmlTags[tagName] = 0
754
+ }
755
+ if (inlines[i].tag[1] === 'open') {
756
+ memo.htmlTags[tagName] += 1
757
+ }
758
+ if (inlines[i].tag[1] === 'close') {
759
+ memo.htmlTags[tagName] -= 1
760
+ }
761
+ if (memo.htmlTags[tagName] < 0) {
762
+ return -1
763
+ }
764
+
765
+ // Direct check instead of Object.values().every()
766
+ for (const count of Object.values(memo.htmlTags)) {
767
+ if (count !== 0) return 0
768
+ }
769
+ return 1
770
+ }
771
+
772
+ // Check if character is ASCII punctuation or space
773
+ // Covers: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ and space
774
+ const isPunctuation = (ch) => {
775
+ if (!ch) return false
776
+ const code = ch.charCodeAt(0)
777
+ // ASCII punctuation: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
778
+ return (code >= 33 && code <= 47) || (code >= 58 && code <= 64) ||
779
+ (code >= 91 && code <= 96) || (code >= 123 && code <= 126) || code === 32
780
+ }
781
+
782
+ // Check if character is Japanese (hiragana, katakana, kanji, punctuation, symbols, format chars, emoji)
783
+ // Uses fast Unicode range checks for common cases, falls back to REG_JAPANESE for complex Unicode
784
+ const isJapanese = (ch) => {
785
+ if (!ch) return false
786
+ const code = ch.charCodeAt(0)
787
+ // Fast ASCII check first
788
+ if (code < 128) return false
789
+ // Hiragana: U+3040-U+309F, Katakana: U+30A0-U+30FF, Kanji: U+4E00-U+9FAF
790
+ return (code >= 0x3040 && code <= 0x309F) ||
791
+ (code >= 0x30A0 && code <= 0x30FF) ||
792
+ (code >= 0x4E00 && code <= 0x9FAF) ||
793
+ // Fallback to regex for complex Unicode cases
794
+ REG_JAPANESE.test(ch)
795
+ }
796
+
797
+ // Check if character is English (letters, numbers) or other non-Japanese characters
798
+ // Uses REG_JAPANESE and REG_PUNCTUATION to exclude Japanese and punctuation characters
799
+ const isEnglish = (ch) => {
800
+ if (!ch) return false
801
+ const code = ch.charCodeAt(0)
802
+ if ((code >= 65 && code <= 90) || (code >= 97 && code <= 122) || (code >= 48 && code <= 57)) {
803
+ return true
804
+ }
805
+ if (code < 128) {
806
+ return code === CHAR_SPACE || (code > 126)
807
+ }
808
+ return !REG_JAPANESE.test(ch) && !REG_PUNCTUATION.test(ch)
809
+ }
810
+
811
+ const checkMixedLanguagePattern = (state, inlines, n, i, opt) => {
812
+ const src = state.src
813
+ const openPrevChar = src[inlines[n].s - 1] || ''
814
+ const closeNextChar = src[inlines[i].e + 1] || ''
815
+
816
+ const isEnglishPrefix = isEnglish(openPrevChar)
817
+ const isEnglishSuffix = isEnglish(closeNextChar)
818
+ if (!isEnglishPrefix && !isEnglishSuffix) {
819
+ return { hasEnglishContext: false, hasMarkdownOrHtml: false, shouldBlock: false }
820
+ }
821
+
822
+ const contentBetween = src.slice(inlines[n].e + 1, inlines[i].s)
823
+ const hasMarkdownOrHtml = REG_MARKDOWN_HTML.test(contentBetween)
824
+
825
+ return {
826
+ hasEnglishContext: true,
827
+ hasMarkdownOrHtml,
828
+ shouldBlock: hasMarkdownOrHtml
829
+ }
830
+ }
831
+
662
832
  const hasPunctuationOrNonJapanese = (state, inlines, n, i, opt, refRanges) => {
663
833
  const src = state.src
664
834
  const openPrevChar = src[inlines[n].s - 1] || ''
@@ -680,293 +850,361 @@ const hasPunctuationOrNonJapanese = (state, inlines, n, i, opt, refRanges) => {
680
850
  }
681
851
  const closeNextChar = src[inlines[i].e + 1] || ''
682
852
  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) => {
853
+
854
+ if (opt.disallowMixed === false) {
855
+ if (isEnglish(openPrevChar) || isEnglish(closeNextChar)) {
856
+ const contentBetween = src.slice(inlines[n].e + 1, inlines[i].s)
857
+ if (REG_MARKDOWN_HTML.test(contentBetween)) {
858
+ return false
859
+ }
860
+ }
861
+ }
862
+
863
+ const result = (checkOpenNextChar || checkClosePrevChar) && !checkCloseNextChar && !(isJapanese(openPrevChar) || isJapanese(closeNextChar))
864
+ return result
865
+ }
866
+
867
+ const setEm = (state, inlines, marks, n, memo, opt, sNest, nestTracker, refRanges, inlineLinkRanges) => {
698
868
  const emOpenRange = findRefRangeIndex(inlines[n].s, refRanges)
869
+ const openLinkRange = findInlineLinkRange(inlines[n].s, inlineLinkRanges)
699
870
  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
-
871
+ let i = n + 1
872
+ const inlinesLength = inlines.length
873
+ while (i < inlinesLength) {
874
+ if (inlines[i].len === 0 || inlines[i].check) { i++; continue }
875
+ if (inlines[i].type !== '') { i++; continue }
876
+
877
+ if (inlines[i].len > 0) {
878
+ const mixedCheck = checkMixedLanguagePattern(state, inlines, n, i, opt)
879
+ if (mixedCheck.shouldBlock) {
880
+ return [n, 0]
881
+ }
882
+ break
883
+ }
884
+ i++
885
+ }
886
+ }
887
+
888
+ let i = n + 1
889
+ let nest = 0
890
+ let strongPNum = 0
891
+ let insideTagsIsClose = 1
892
+ const inlinesLength = inlines.length
893
+ while (i < inlinesLength) {
894
+ if (inlines[i].len === 0 || inlines[i].check) { i++; continue }
895
+ if (!sNest && inlines[i].type === 'html_inline') {
896
+ inlines[i].check = true
897
+ insideTagsIsClose = checkInsideTags(inlines, i, memo)
898
+ if (insideTagsIsClose === -1) return [n, nest]
899
+ if (insideTagsIsClose === 0) { i++; continue }
900
+ }
901
+ if (inlines[i].type !== '') { i++; continue }
902
+
903
+ if (inlineLinkRanges && inlineLinkRanges.length > 0 &&
904
+ hasInlineLinkLabelCrossing(inlineLinkRanges, inlines[n].ep + 1, inlines[i].sp)) {
905
+ i++
906
+ continue
907
+ }
908
+
732
909
  const closeRange = findRefRangeIndex(inlines[i].s, refRanges)
733
910
  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) {
911
+ i++
912
+ continue
913
+ }
914
+
915
+ const closeLinkRange = findInlineLinkRange(inlines[i].s, inlineLinkRanges)
916
+ if (openLinkRange || closeLinkRange) {
917
+ if (!openLinkRange || !closeLinkRange || openLinkRange.id !== closeLinkRange.id || openLinkRange.kind !== closeLinkRange.kind) {
918
+ i++
919
+ continue
920
+ }
921
+ }
922
+
923
+ const emNum = Math.min(inlines[n].len, inlines[i].len)
924
+
925
+ if (!sNest && emNum !== 1) return [n, sNest, memo]
926
+
927
+ const hasMarkersAtStartAndEnd = (i) => {
928
+ let flag = memo.inlineMarkStart
929
+ if (!flag) return false
930
+ inlinesLength - 1 === i ? flag = true : flag = false
931
+ if (!flag) return false
932
+ inlines[i].len > 1 ? flag = true : flag = false
933
+ return flag
934
+ }
935
+ if (!sNest && inlines[i].len === 2 && !hasMarkersAtStartAndEnd(i)) {
936
+ strongPNum++
937
+ i++
938
+ continue
939
+ }
940
+
941
+ if (sNest) {
942
+ nest = sNest - 1
943
+ } else {
944
+ nest = checkNest(inlines, marks, n, i, nestTracker)
945
+ }
946
+ if (nest === -1) return [n, nest]
947
+
948
+ if (emNum === 1) {
764
949
  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)
950
+ if (memo.inlineMarkEnd) {
951
+ marks.push(...createMarks(state, inlines, i, inlinesLength - 1, memo, opt, refRanges, inlineLinkRanges))
952
+
953
+ if (inlines[i].len === 0) { i++; continue }
954
+ } else {
955
+ return [n, nest]
956
+ }
957
+ }
958
+ if (inlines[i].len < 1) {
959
+ i++; continue;
960
+ }
961
+
962
+ pushMark(marks, {
963
+ nest: nest,
964
+ s: inlines[n].ep,
965
+ e: inlines[n].ep,
966
+ len: 1,
967
+ oLen: inlines[n].len - 1,
968
+ type: 'em_open'
969
+ })
970
+ inlines[n].ep -= 1
971
+ inlines[n].len -= 1
972
+
973
+ if (strongPNum % 2 === 0 || inlines[i].len < 2) {
974
+ pushMark(marks, {
975
+ nest: nest,
976
+ s: inlines[i].sp,
977
+ e: inlines[i].sp,
978
+ len: 1,
979
+ oLen: inlines[i].len - 1,
980
+ type: 'em_close'
981
+ })
982
+ inlines[i].sp += 1
983
+ } else {
984
+ pushMark(marks, {
985
+ nest: nest,
986
+ s: inlines[i].ep,
987
+ e: inlines[i].ep,
988
+ len: 1,
989
+ oLen: inlines[i].len - 1,
990
+ type: 'em_close'
991
+ })
992
+ inlines[i].sp = inlines[i].ep - 1
993
+ inlines[i].ep -= 1
994
+ }
995
+ inlines[i].len -= 1
996
+ if (inlines[n].len === 0) return [n, nest]
997
+ }
998
+
999
+ i++
1000
+ }
1001
+ return [n, nest]
1002
+ }
1003
+
1004
+ const setText = (inlines, marks, n, nest) => {
1005
+ pushMark(marks, {
1006
+ nest: nest,
1007
+ s: inlines[n].sp,
1008
+ e: inlines[n].ep,
1009
+ len: inlines[n].len,
1010
+ oLen: -1,
1011
+ type: 'text'
1012
+ })
1013
+ inlines[n].len = 0
1014
+ }
1015
+
1016
+ // Nest state management
1017
+ const createNestTracker = () => {
1018
+ return {
1019
+ strongNest: 0,
1020
+ emNest: 0,
1021
+ markIndex: 0
1022
+ }
1023
+ }
1024
+
1025
+ const updateNestTracker = (tracker, marks, targetPos) => {
1026
+ while (tracker.markIndex < marks.length && marks[tracker.markIndex].s <= targetPos) {
1027
+ const mark = marks[tracker.markIndex]
1028
+ if (mark.type === 'strong_open') tracker.strongNest++
1029
+ else if (mark.type === 'strong_close') tracker.strongNest--
1030
+ else if (mark.type === 'em_open') tracker.emNest++
1031
+ else if (mark.type === 'em_close') tracker.emNest--
1032
+ tracker.markIndex++
1033
+ }
1034
+ }
1035
+
1036
+ const checkNest = (inlines, marks, n, i, nestTracker) => {
1037
+ if (marks.length === 0) return 1
1038
+ // Update nest state up to current position
1039
+ updateNestTracker(nestTracker, marks, inlines[n].s)
1040
+
1041
+ const parentNest = nestTracker.strongNest + nestTracker.emNest
1042
+ // Check if there's a conflicting close mark before the end position
1043
+ let parentCloseN = nestTracker.markIndex
1044
+ while (parentCloseN < marks.length) {
1045
+ if (marks[parentCloseN].nest === parentNest) break
1046
+ parentCloseN++
1047
+ }
1048
+ if (parentCloseN < marks.length && marks[parentCloseN].s < inlines[i].s) {
1049
+ return -1
1050
+ }
1051
+ return parentNest + 1
1052
+ }
1053
+
1054
+ const createMarks = (state, inlines, start, end, memo, opt, refRanges, inlineLinkRanges) => {
1055
+ let marks = []
1056
+ let n = start
1057
+ const nestTracker = createNestTracker()
1058
+
1059
+ while (n < end) {
1060
+ if (inlines[n].type !== '') { n++; continue }
1061
+ let nest = 0
1062
+
1063
+ if (inlines[n].len > 1) {
1064
+ const [newN, newNest] = setStrong(state, inlines, marks, n, memo, opt, nestTracker, refRanges, inlineLinkRanges)
1065
+ n = newN
1066
+ nest = newNest
1067
+ }
1068
+ if (inlines[n].len !== 0) {
1069
+ const [newN2, newNest2] = setEm(state, inlines, marks, n, memo, opt, null, nestTracker, refRanges, inlineLinkRanges)
1070
+ n = newN2
1071
+ nest = newNest2
1072
+ }
1073
+ if (inlines[n].len !== 0) {
1074
+ setText(inlines, marks, n, nest)
1075
+ }
1076
+ n++
1077
+ }
1078
+ return marks
1079
+ }
1080
+
1081
+ const mergeInlinesAndMarks = (inlines, marks) => {
1082
+ // marks array is already sorted, skip sorting
1083
+ const merged = []
1084
+ let markIndex = 0
1085
+ for (const token of inlines) {
1086
+ if (token.type === '') {
1087
+ while (markIndex < marks.length && marks[markIndex].s >= token.s && marks[markIndex].e <= token.e) {
1088
+ merged.push(marks[markIndex])
1089
+ markIndex++
1090
+ }
1091
+ } else {
1092
+ merged.push(token)
1093
+ }
1094
+ }
1095
+ while (markIndex < marks.length) {
1096
+ merged.push(marks[markIndex++])
1097
+ }
1098
+ return merged
1099
+ }
1100
+
1101
+ const isWhitespaceToken = (token) => token && token.type === 'text' && token.content.trim() === ''
1102
+
1103
+ const strongJa = (state, silent, opt) => {
1104
+ if (silent) return false
1105
+ const start = state.pos
1106
+ let max = state.posMax
1107
+ const src = state.src
1108
+ let attributesSrc
1109
+ if (start > max) return false
1110
+ if (src.charCodeAt(start) !== CHAR_ASTERISK) return false
1111
+ if (hasBackslash(state, start)) return false
1112
+
1113
+ if (start === 0) {
1114
+ state.__strongJaRefRangeCache = null
1115
+ state.__strongJaInlineLinkRangeCache = null
1116
+ state.__strongJaBackslashCache = undefined
1117
+ }
1118
+
1119
+ if (opt.mditAttrs) {
1120
+ attributesSrc = src.match(/((\n)? *){([^{}\n!@#%^&*()]+?)} *$/)
1121
+ if (attributesSrc && attributesSrc[3] !== '.') {
1122
+ max = src.slice(0, attributesSrc.index).length
1123
+ if (attributesSrc[2] === '\n') {
1124
+ max = src.slice(0, attributesSrc.index - 1).length
1125
+ }
1126
+ if(hasBackslash(state, attributesSrc.index) && attributesSrc[2] === '' && attributesSrc[1].length === 0) {
1127
+ max = state.posMax
1128
+ }
1129
+ } else {
1130
+ let endCurlyKet = src.match(/(\n *){([^{}\n!@#%^&*()]*?)}.*(} *?)$/)
1131
+ if (endCurlyKet) {
1132
+ max -= endCurlyKet[3].length
1133
+ }
1134
+ }
1135
+ }
1136
+
1137
+ if (state.__strongJaHasCollapsedRefs === undefined) {
1138
+ state.__strongJaHasCollapsedRefs = /\[[^\]]*\]\s*\[[^\]]*\]/.test(state.src)
1139
+ }
1140
+
1141
+ if (state.__strongJaReferenceCount === undefined) {
1142
+ const references = state.env && state.env.references
1143
+ state.__strongJaReferenceCount = references ? Object.keys(references).length : 0
1144
+ }
1145
+
1146
+ let refRanges
1147
+ const refCache = state.__strongJaRefRangeCache
1148
+ if (refCache && refCache.max === max && refCache.start <= start) {
1149
+ refRanges = refCache.ranges
1150
+ } else {
1151
+ refRanges = computeReferenceRanges(state, start, max)
1152
+ state.__strongJaRefRangeCache = { start, max, ranges: refRanges }
1153
+ }
947
1154
  if (refRanges.length > 0) {
948
1155
  state.__strongJaHasCollapsedRefs = true
949
1156
  }
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
-
1157
+
1158
+ let inlineLinkRanges = null
1159
+ const inlineLinkCandidatePos = state.src.indexOf('](', start)
1160
+ const hasInlineLinkCandidate = inlineLinkCandidatePos !== -1 && inlineLinkCandidatePos < max
1161
+ if (hasInlineLinkCandidate) {
1162
+ const inlineCache = state.__strongJaInlineLinkRangeCache
1163
+ if (inlineCache && inlineCache.max === max && inlineCache.start <= start) {
1164
+ inlineLinkRanges = inlineCache.ranges
1165
+ } else {
1166
+ inlineLinkRanges = computeInlineLinkRanges(state, start, max)
1167
+ state.__strongJaInlineLinkRangeCache = { start, max, ranges: inlineLinkRanges }
1168
+ }
1169
+ if (inlineLinkRanges.length > 0) {
1170
+ state.__strongJaHasInlineLinks = true
1171
+ }
1172
+ }
1173
+ let inlines = createInlines(state, start, max, opt)
1174
+
1175
+ const memo = {
1176
+ html: state.md.options.html,
1177
+ htmlTags: {},
1178
+ inlineMarkStart: src.charCodeAt(0) === CHAR_ASTERISK,
1179
+ inlineMarkEnd: src.charCodeAt(max - 1) === CHAR_ASTERISK,
1180
+ }
1181
+
1182
+ let marks = createMarks(state, inlines, 0, inlines.length, memo, opt, refRanges, inlineLinkRanges)
1183
+
1184
+ inlines = mergeInlinesAndMarks(inlines, marks)
1185
+
963
1186
  setToken(state, inlines, opt)
964
1187
 
965
- if (inlineHasCollapsedRef(state) && !state.__strongJaCollapsedRefRegistered) {
966
- registerCollapsedRefTarget(state)
967
- state.__strongJaCollapsedRefRegistered = true
1188
+ if (inlineLinkRanges && inlineLinkRanges.length > 0) {
1189
+ const labelSources = []
1190
+ for (let idx = 0; idx < inlineLinkRanges.length; idx++) {
1191
+ const range = inlineLinkRanges[idx]
1192
+ if (range.kind !== 'label') continue
1193
+ labelSources.push(src.slice(range.start + 1, range.end))
1194
+ }
1195
+ if (labelSources.length > 0) {
1196
+ state.tokens.__strongJaInlineLabelSources = labelSources
1197
+ state.tokens.__strongJaInlineLabelIndex = 0
1198
+ }
1199
+ }
1200
+
1201
+ const needsInlineLinkFix = state.__strongJaHasInlineLinks === true
1202
+ const needsCollapsedRefFix = state.__strongJaHasCollapsedRefs === true
1203
+ if ((needsCollapsedRefFix || needsInlineLinkFix) && !state.__strongJaPostProcessRegistered) {
1204
+ registerPostProcessTarget(state)
1205
+ state.__strongJaPostProcessRegistered = true
968
1206
  }
969
-
1207
+
970
1208
  if (opt.mditAttrs && max !== state.posMax) {
971
1209
  if (!attributesSrc) {
972
1210
  state.pos = max
@@ -976,26 +1214,26 @@ const strongJa = (state, silent, opt) => {
976
1214
  return true
977
1215
  }
978
1216
  state.pos = max
979
- return true
980
- }
981
-
1217
+ return true
1218
+ }
1219
+
982
1220
  // Collapsed reference helpers
983
1221
  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
-
1222
+ let label = ''
1223
+ for (const token of tokens) {
1224
+ if (token.type === 'text' || token.type === 'code_inline') {
1225
+ label += token.content
1226
+ } else if (token.type === 'softbreak' || token.type === 'hardbreak') {
1227
+ label += ' '
1228
+ } else if (token.type && token.type.endsWith('_open') && token.markup) {
1229
+ label += token.markup
1230
+ } else if (token.type && token.type.endsWith('_close') && token.markup) {
1231
+ label += token.markup
1232
+ }
1233
+ }
1234
+ return label
1235
+ }
1236
+
999
1237
  const cleanLabelText = (label) => {
1000
1238
  return label.replace(/^[*_]+/, '').replace(/[*_]+$/, '')
1001
1239
  }
@@ -1010,39 +1248,40 @@ const normalizeRefKey = (state, label) => {
1010
1248
  ? state.md.utils.normalizeReference
1011
1249
  : (str) => str.trim().replace(/\s+/g, ' ').toUpperCase()
1012
1250
  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) => {
1251
+ }
1252
+
1253
+ const adjustTokenLevels = (tokens, startIdx, endIdx, delta) => {
1254
+ for (let i = startIdx; i < endIdx; i++) {
1255
+ if (tokens[i]) tokens[i].level += delta
1256
+ }
1257
+ }
1258
+
1259
+ const cloneTextToken = (source, content) => {
1260
+ const newToken = new Token('text', '', 0)
1261
+ newToken.content = content
1262
+ newToken.level = source.level
1263
+ newToken.markup = source.markup
1264
+ newToken.info = source.info
1265
+ newToken.meta = source.meta ? {...source.meta} : null
1266
+ newToken.block = source.block
1267
+ newToken.hidden = source.hidden
1268
+ return newToken
1269
+ }
1270
+
1271
+ // Split only text tokens that actually contain bracket characters
1272
+ const splitBracketToken = (tokens, index, options) => {
1035
1273
  const token = tokens[index]
1036
1274
  if (!token || token.type !== 'text') return false
1037
1275
  const content = token.content
1038
1276
  if (!content || (content.indexOf('[') === -1 && content.indexOf(']') === -1)) {
1039
1277
  return false
1040
1278
  }
1279
+ const splitEmptyPair = options && options.splitEmptyPair
1041
1280
  const segments = []
1042
1281
  let buffer = ''
1043
1282
  let pos = 0
1044
1283
  while (pos < content.length) {
1045
- if (content.startsWith('[]', pos)) {
1284
+ if (!splitEmptyPair && content.startsWith('[]', pos)) {
1046
1285
  if (buffer) {
1047
1286
  segments.push(buffer)
1048
1287
  buffer = ''
@@ -1075,7 +1314,7 @@ const splitBracketToken = (tokens, index) => {
1075
1314
  }
1076
1315
  return true
1077
1316
  }
1078
-
1317
+
1079
1318
  const isBracketToken = (token, bracket) => {
1080
1319
  return token && token.type === 'text' && token.content === bracket
1081
1320
  }
@@ -1093,9 +1332,285 @@ const findLinkCloseIndex = (tokens, startIdx) => {
1093
1332
  return -1
1094
1333
  }
1095
1334
 
1335
+ const consumeCharactersFromTokens = (tokens, startIdx, count) => {
1336
+ let remaining = count
1337
+ let idx = startIdx
1338
+ while (idx < tokens.length && remaining > 0) {
1339
+ const token = tokens[idx]
1340
+ if (!token || token.type !== 'text') {
1341
+ return false
1342
+ }
1343
+ const len = token.content.length
1344
+ if (remaining >= len) {
1345
+ remaining -= len
1346
+ tokens.splice(idx, 1)
1347
+ continue
1348
+ }
1349
+ token.content = token.content.slice(remaining)
1350
+ remaining = 0
1351
+ }
1352
+ return remaining === 0
1353
+ }
1354
+
1355
+ const wrapLabelTokensWithLink = (tokens, labelStartIdx, labelEndIdx, linkOpenToken, linkCloseToken, labelSource) => {
1356
+ const wrapperPairs = []
1357
+ let startIdx = labelStartIdx
1358
+ let endIdx = labelEndIdx
1359
+ while (startIdx > 0) {
1360
+ const prevToken = tokens[startIdx - 1]
1361
+ const nextToken = tokens[endIdx + 1]
1362
+ if (!prevToken || !nextToken) break
1363
+ if (!/_close$/.test(prevToken.type)) break
1364
+ const expectedOpen = prevToken.type.replace('_close', '_open')
1365
+ if (nextToken.type !== expectedOpen) break
1366
+ wrapperPairs.push({
1367
+ base: prevToken.type.replace('_close', ''),
1368
+ tag: prevToken.tag,
1369
+ markup: prevToken.markup
1370
+ })
1371
+ tokens.splice(endIdx + 1, 1)
1372
+ tokens.splice(startIdx - 1, 1)
1373
+ startIdx -= 1
1374
+ endIdx -= 1
1375
+ }
1376
+
1377
+ if (startIdx > endIdx) {
1378
+ if (labelSource !== undefined && labelSource !== null) {
1379
+ const placeholder = new Token('text', '', 0)
1380
+ placeholder.content = labelSource
1381
+ placeholder.level = linkOpenToken.level + 1
1382
+ tokens.splice(startIdx, 0, placeholder)
1383
+ endIdx = startIdx
1384
+ } else {
1385
+ return startIdx
1386
+ }
1387
+ }
1388
+
1389
+ let labelLength = endIdx - startIdx + 1
1390
+ const firstLabelToken = tokens[startIdx]
1391
+ const linkLevel = firstLabelToken ? Math.max(firstLabelToken.level - 1, 0) : 0
1392
+ linkOpenToken.level = linkLevel
1393
+ linkCloseToken.level = linkLevel
1394
+ tokens.splice(startIdx, 0, linkOpenToken)
1395
+ tokens.splice(startIdx + labelLength + 1, 0, linkCloseToken)
1396
+
1397
+ adjustTokenLevels(tokens, startIdx + 1, startIdx + labelLength + 1, 1)
1398
+
1399
+ if (wrapperPairs.length > 0) {
1400
+ let insertIdx = startIdx + 1
1401
+ for (let wp = 0; wp < wrapperPairs.length; wp++) {
1402
+ const pair = wrapperPairs[wp]
1403
+ const innerOpen = new Token(pair.base + '_open', pair.tag, 1)
1404
+ innerOpen.markup = pair.markup
1405
+ innerOpen.level = linkLevel + 1 + wp
1406
+ tokens.splice(insertIdx, 0, innerOpen)
1407
+ insertIdx++
1408
+ labelLength++
1409
+ }
1410
+ let linkClosePos = startIdx + labelLength + 1
1411
+ for (let wp = wrapperPairs.length - 1; wp >= 0; wp--) {
1412
+ const pair = wrapperPairs[wp]
1413
+ const innerClose = new Token(pair.base + '_close', pair.tag, -1)
1414
+ innerClose.markup = pair.markup
1415
+ innerClose.level = linkLevel + 1 + wp
1416
+ tokens.splice(linkClosePos, 0, innerClose)
1417
+ labelLength++
1418
+ }
1419
+ }
1420
+
1421
+ return startIdx + labelLength + 2
1422
+ }
1423
+
1424
+ const parseInlineLinkTail = (content, md) => {
1425
+ if (!content || content.charCodeAt(0) !== CHAR_OPEN_PAREN) return null
1426
+ const max = content.length
1427
+ let pos = 1
1428
+ while (pos < max) {
1429
+ const code = content.charCodeAt(pos)
1430
+ if (!isSpace(code) && code !== 0x0A) break
1431
+ pos++
1432
+ }
1433
+ if (pos >= max) return null
1434
+
1435
+ let href = ''
1436
+ let destPos = pos
1437
+ if (pos < max && content.charCodeAt(pos) === CHAR_CLOSE_PAREN) {
1438
+ href = ''
1439
+ } else {
1440
+ const dest = parseLinkDestination(content, pos, max)
1441
+ if (!dest.ok) return null
1442
+ href = md.normalizeLink(dest.str)
1443
+ if (!md.validateLink(href)) {
1444
+ return null
1445
+ }
1446
+ pos = dest.pos
1447
+ destPos = dest.pos
1448
+ }
1449
+
1450
+ while (pos < max) {
1451
+ const code = content.charCodeAt(pos)
1452
+ if (!isSpace(code) && code !== 0x0A) break
1453
+ pos++
1454
+ }
1455
+
1456
+ let title = ''
1457
+ const titleRes = parseLinkTitle(content, pos, max)
1458
+ if (pos < max && pos !== destPos && titleRes.ok) {
1459
+ title = titleRes.str
1460
+ pos = titleRes.pos
1461
+ while (pos < max) {
1462
+ const code = content.charCodeAt(pos)
1463
+ if (!isSpace(code) && code !== 0x0A) break
1464
+ pos++
1465
+ }
1466
+ }
1467
+
1468
+ if (pos >= max || content.charCodeAt(pos) !== CHAR_CLOSE_PAREN) {
1469
+ return null
1470
+ }
1471
+ pos++
1472
+ return { href, title, consumed: pos }
1473
+ }
1474
+
1475
+ const INLINE_LINK_BRACKET_SPLIT_OPTIONS = { splitEmptyPair: true }
1476
+
1477
+ const removeGhostLabelText = (tokens, linkCloseToken, labelText) => {
1478
+ if (!labelText) return
1479
+ const closeIdx = tokens.indexOf(linkCloseToken)
1480
+ if (closeIdx === -1) return
1481
+ let idx = closeIdx + 1
1482
+ while (idx < tokens.length) {
1483
+ const token = tokens[idx]
1484
+ if (!token) {
1485
+ idx++
1486
+ continue
1487
+ }
1488
+ if (token.type === 'text') {
1489
+ if (token.content.startsWith(labelText)) {
1490
+ if (token.content.length === labelText.length) {
1491
+ tokens.splice(idx, 1)
1492
+ } else {
1493
+ token.content = token.content.slice(labelText.length)
1494
+ }
1495
+ }
1496
+ break
1497
+ }
1498
+ if (!/_close$/.test(token.type)) {
1499
+ break
1500
+ }
1501
+ idx++
1502
+ }
1503
+ }
1504
+
1505
+ const convertInlineLinks = (tokens, state) => {
1506
+ if (!tokens || tokens.length === 0) return
1507
+ const labelSources = tokens.__strongJaInlineLabelSources
1508
+ let labelSourceIndex = tokens.__strongJaInlineLabelIndex || 0
1509
+ let i = 0
1510
+ while (i < tokens.length) {
1511
+ if (splitBracketToken(tokens, i, INLINE_LINK_BRACKET_SPLIT_OPTIONS)) {
1512
+ continue
1513
+ }
1514
+ if (!isBracketToken(tokens[i], '[')) {
1515
+ i++
1516
+ continue
1517
+ }
1518
+ let closeIdx = i + 1
1519
+ let invalid = false
1520
+ while (closeIdx < tokens.length && !isBracketToken(tokens[closeIdx], ']')) {
1521
+ if (splitBracketToken(tokens, closeIdx, INLINE_LINK_BRACKET_SPLIT_OPTIONS)) {
1522
+ continue
1523
+ }
1524
+ if (tokens[closeIdx].type === 'link_open') {
1525
+ invalid = true
1526
+ break
1527
+ }
1528
+ closeIdx++
1529
+ }
1530
+ if (invalid || closeIdx >= tokens.length) {
1531
+ i++
1532
+ continue
1533
+ }
1534
+ const currentLabelSource = labelSources && labelSourceIndex < labelSources.length
1535
+ ? labelSources[labelSourceIndex]
1536
+ : undefined
1537
+
1538
+ const labelLength = closeIdx - i - 1
1539
+ const needsPlaceholder = labelLength <= 0
1540
+ if (needsPlaceholder && !currentLabelSource) {
1541
+ i++
1542
+ continue
1543
+ }
1544
+
1545
+ let tailIdx = closeIdx + 1
1546
+ let tailContent = ''
1547
+ let parsedTail = null
1548
+ while (tailIdx < tokens.length) {
1549
+ if (splitBracketToken(tokens, tailIdx, INLINE_LINK_BRACKET_SPLIT_OPTIONS)) {
1550
+ continue
1551
+ }
1552
+ const tailToken = tokens[tailIdx]
1553
+ if (tailToken.type !== 'text' || !tailToken.content) {
1554
+ break
1555
+ }
1556
+ tailContent += tailToken.content
1557
+ parsedTail = parseInlineLinkTail(tailContent, state.md)
1558
+ if (parsedTail) break
1559
+ tailIdx++
1560
+ }
1561
+
1562
+ if (!parsedTail) {
1563
+ i++
1564
+ continue
1565
+ }
1566
+
1567
+ if (!consumeCharactersFromTokens(tokens, closeIdx + 1, parsedTail.consumed)) {
1568
+ i++
1569
+ continue
1570
+ }
1571
+
1572
+ tokens.splice(closeIdx, 1)
1573
+ tokens.splice(i, 1)
1574
+
1575
+ const linkOpenToken = new Token('link_open', 'a', 1)
1576
+ linkOpenToken.attrs = [['href', parsedTail.href]]
1577
+ if (parsedTail.title) linkOpenToken.attrPush(['title', parsedTail.title])
1578
+ linkOpenToken.markup = '[]()'
1579
+ linkOpenToken.info = 'auto'
1580
+ const linkCloseToken = new Token('link_close', 'a', -1)
1581
+ linkCloseToken.markup = '[]()'
1582
+ linkCloseToken.info = 'auto'
1583
+
1584
+ const nextIndex = wrapLabelTokensWithLink(tokens, i, i + labelLength - 1, linkOpenToken, linkCloseToken, currentLabelSource)
1585
+ if (nextIndex === i) {
1586
+ i++
1587
+ continue
1588
+ }
1589
+ if (needsPlaceholder && currentLabelSource) {
1590
+ removeGhostLabelText(tokens, linkCloseToken, currentLabelSource)
1591
+ }
1592
+
1593
+ if (labelSources && labelSources.length > 0) {
1594
+ if (labelSourceIndex < labelSources.length) {
1595
+ labelSourceIndex++
1596
+ }
1597
+ }
1598
+ i = nextIndex
1599
+ }
1600
+ if (labelSources) {
1601
+ tokens.__strongJaInlineLabelIndex = labelSourceIndex
1602
+ }
1603
+ }
1604
+
1096
1605
  const convertCollapsedReferenceLinks = (tokens, state) => {
1097
1606
  const references = state.env && state.env.references
1098
- if (!references || Object.keys(references).length === 0) return
1607
+ if (!references) return
1608
+ const referenceCount = state.__strongJaReferenceCount
1609
+ if (referenceCount !== undefined) {
1610
+ if (referenceCount === 0) return
1611
+ } else if (Object.keys(references).length === 0) {
1612
+ return
1613
+ }
1099
1614
 
1100
1615
  let i = 0
1101
1616
  while (i < tokens.length) {
@@ -1116,12 +1631,12 @@ const convertCollapsedReferenceLinks = (tokens, state) => {
1116
1631
  break
1117
1632
  }
1118
1633
  closeIdx++
1119
- }
1120
- if (closeIdx === -1 || closeIdx >= tokens.length) {
1121
- i++
1122
- continue
1123
- }
1124
-
1634
+ }
1635
+ if (closeIdx === -1 || closeIdx >= tokens.length) {
1636
+ i++
1637
+ continue
1638
+ }
1639
+
1125
1640
  if (closeIdx === i + 1) {
1126
1641
  i++
1127
1642
  continue
@@ -1144,12 +1659,6 @@ const convertCollapsedReferenceLinks = (tokens, state) => {
1144
1659
  let existingLinkOpen = null
1145
1660
  let existingLinkClose = null
1146
1661
  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))
1151
- }
1152
-
1153
1662
  if (isBracketToken(nextToken, '[]')) {
1154
1663
  refKey = normalizeReferenceCandidate(state, cleanedLabel)
1155
1664
  refRemoveCount = 1
@@ -1183,11 +1692,6 @@ const convertCollapsedReferenceLinks = (tokens, state) => {
1183
1692
  i++
1184
1693
  continue
1185
1694
  }
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
1695
  let linkOpenToken = null
1192
1696
  let linkCloseToken = null
1193
1697
  if (existingLinkOpen && existingLinkClose) {
@@ -1228,144 +1732,82 @@ const convertCollapsedReferenceLinks = (tokens, state) => {
1228
1732
  }
1229
1733
  tokens.splice(closeIdx, 1)
1230
1734
  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
-
1735
+
1736
+ const nextIndex = wrapLabelTokensWithLink(tokens, i, i + labelTokens.length - 1, linkOpenToken, linkCloseToken)
1737
+ i = nextIndex
1738
+ }
1739
+ }
1740
+
1302
1741
  // Link cleanup helpers
1303
1742
  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
1743
+ let i = 0
1744
+ while (i < tokens.length) {
1745
+ const closeToken = tokens[i]
1746
+ if (!closeToken || !/_close$/.test(closeToken.type)) {
1747
+ i++
1748
+ continue
1749
+ }
1750
+ const openType = closeToken.type.replace('_close', '_open')
1751
+ let j = i + 1
1752
+ while (j < tokens.length && isWhitespaceToken(tokens[j])) j++
1753
+ if (j >= tokens.length || tokens[j].type !== 'link_open') {
1754
+ i++
1755
+ continue
1756
+ }
1757
+ let linkDepth = 1
1758
+ j++
1759
+ while (j < tokens.length && linkDepth > 0) {
1760
+ if (tokens[j].type === 'link_open') linkDepth++
1761
+ if (tokens[j].type === 'link_close') linkDepth--
1762
+ j++
1763
+ }
1764
+ if (linkDepth !== 0) {
1765
+ i++
1766
+ continue
1767
+ }
1768
+ while (j < tokens.length && isWhitespaceToken(tokens[j])) j++
1769
+ if (j >= tokens.length) {
1770
+ i++
1771
+ continue
1772
+ }
1773
+ const reopenToken = tokens[j]
1774
+ if (reopenToken.type !== openType || reopenToken.level !== closeToken.level) {
1775
+ i++
1776
+ continue
1777
+ }
1778
+ tokens.splice(j, 1)
1779
+ tokens.splice(i, 1)
1780
+ }
1781
+ }
1782
+
1783
+
1784
+ const mditStrongJa = (md, option) => {
1785
+ const opt = {
1786
+ dollarMath: true, //inline math $...$
1787
+ mditAttrs: true, //markdown-it-attrs
1788
+ mdBreaks: md.options.breaks,
1789
+ disallowMixed: false, //Non-Japanese text handling
1790
+ }
1791
+ if (option) Object.assign(opt, option)
1792
+
1793
+ md.inline.ruler.before('emphasis', 'strong_ja', (state, silent) => {
1794
+ return strongJa(state, silent, opt)
1795
+ })
1796
+
1797
+ md.core.ruler.after('inline', 'strong_ja_postprocess', (state) => {
1798
+ const targets = state.env.__strongJaPostProcessTargets
1360
1799
  if (!targets || targets.length === 0) return
1361
1800
  for (const tokens of targets) {
1362
1801
  if (!tokens || !tokens.length) continue
1802
+ convertInlineLinks(tokens, state)
1363
1803
  convertCollapsedReferenceLinks(tokens, state)
1364
1804
  mergeBrokenMarksAroundLinks(tokens)
1805
+ delete tokens.__strongJaInlineLabelSources
1806
+ delete tokens.__strongJaInlineLabelIndex
1365
1807
  }
1366
- delete state.env.__strongJaCollapsedTargets
1367
- delete state.env.__strongJaCollapsedTargetSet
1808
+ delete state.env.__strongJaPostProcessTargets
1809
+ delete state.env.__strongJaPostProcessTargetSet
1368
1810
  })
1369
- }
1370
-
1371
- export default mditStrongJa
1811
+ }
1812
+
1813
+ export default mditStrongJa