@peaceroad/markdown-it-strong-ja 0.5.1 → 0.5.3

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