@peaceroad/markdown-it-strong-ja 0.6.2 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -1,2506 +1,62 @@
1
- import Token from 'markdown-it/lib/token.mjs'
2
- import { parseLinkDestination, parseLinkTitle } from 'markdown-it/lib/helpers/index.mjs'
3
- import { isSpace, isWhiteSpace } from 'markdown-it/lib/common/utils.mjs'
1
+ import { hasCjkBreaksRule, normalizeCoreRulesBeforePostprocess, ensureCoreRuleOrder, resolveMode } from './src/token-utils.js'
2
+ import { patchScanDelims } from './src/token-core.js'
3
+ import { registerTokenCompat } from './src/token-compat.js'
4
+ import { registerTokenPostprocess } from './src/token-postprocess.js'
4
5
 
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_ATTRS = /{[^{}\n!@#%^&*()]+?}$/
24
- const REG_ASCII_PUNCT = /[!-/:-@[-`{-~]/g
25
- const REG_JAPANESE = /[\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Han}\u3000-\u303F\uFF00-\uFFEF]/u // ひらがな|カタカナ|漢字|CJK句読点・全角形状(絵文字は除外)
26
-
27
- const REG_MARKDOWN_HTML = /^\[[^\[\]]+\]\([^)]+\)$|^<([a-zA-Z][a-zA-Z0-9]*)[^>]*>([^<]+<\/\1>)$|^`[^`]+`$|^\$[^$]+\$$/ // for mixed-language context detection
28
-
29
- const hasCjkBreaksRule = (md) => {
30
- if (!md || !md.core || !md.core.ruler || !Array.isArray(md.core.ruler.__rules__)) return false
31
- if (md.__strongJaHasCjkBreaks === true) return true
32
- const found = md.core.ruler.__rules__.some((rule) => rule && typeof rule.name === 'string' && rule.name.indexOf('cjk_breaks') !== -1)
33
- if (found) md.__strongJaHasCjkBreaks = true
34
- return found
35
- }
36
-
37
- const hasBackslash = (state, start) => {
38
- if (start <= 0) return false
39
- if (state.__strongJaHasBackslash === false) return false
40
- if (state.__strongJaHasBackslash === undefined) {
41
- state.__strongJaHasBackslash = state.src.indexOf('\\') !== -1
42
- if (!state.__strongJaHasBackslash) return false
43
- }
44
- const cache = state.__strongJaBackslashCache
45
- if (cache && cache.has(start)) {
46
- return cache.get(start)
47
- }
48
- let slashNum = 0
49
- let i = start - 1
50
- const src = state.src
51
- if (i < 0 || src.charCodeAt(i) !== CHAR_BACKSLASH) {
52
- return false
53
- }
54
- while (i >= 0 && src.charCodeAt(i) === CHAR_BACKSLASH) {
55
- slashNum++
56
- i--
57
- }
58
- const isEscaped = slashNum % 2 === 1
59
- if (cache) {
60
- cache.set(start, isEscaped)
61
- } else {
62
- state.__strongJaBackslashCache = new Map([[start, isEscaped]])
63
- }
64
- return isEscaped
65
- }
66
-
67
- const findMatchingBracket = (state, start, max, openChar, closeChar) => {
68
- let depth = 1
69
- let pos = start + 1
70
- const src = state.src
71
- while (pos < max) {
72
- const ch = src.charCodeAt(pos)
73
- if (ch === openChar && !hasBackslash(state, pos)) {
74
- depth++
75
- } else if (ch === closeChar && !hasBackslash(state, pos)) {
76
- depth--
77
- if (depth === 0) return pos
78
- }
79
- pos++
80
- }
81
- return -1
82
- }
83
-
84
- const getInlineLabelRanges = (inlineLinkRanges) => {
85
- if (!inlineLinkRanges || inlineLinkRanges.length === 0) return null
86
- return inlineLinkRanges.__labelRanges
87
- }
88
-
89
- const hasInlineLinkLabelCrossing = (inlineLinkRanges, from, to) => {
90
- if (from >= to) return false
91
- const labelRanges = getInlineLabelRanges(inlineLinkRanges)
92
- if (!labelRanges || labelRanges.length === 0) return false
93
- if (labelRanges.length <= 8) {
94
- for (let idx = 0; idx < labelRanges.length; idx++) {
95
- const range = labelRanges[idx]
96
- if (range.start >= to) break
97
- if (range.start >= from && range.end >= to) return true
98
- }
99
- return false
100
- }
101
- let left = 0
102
- let right = labelRanges.length - 1
103
- let firstIdx = labelRanges.length
104
- while (left <= right) {
105
- const mid = left + Math.floor((right - left) / 2)
106
- if (labelRanges[mid].start < from) {
107
- left = mid + 1
108
- } else {
109
- firstIdx = mid
110
- right = mid - 1
111
- }
112
- }
113
- for (let idx = firstIdx; idx < labelRanges.length; idx++) {
114
- const range = labelRanges[idx]
115
- if (range.start >= to) break
116
- if (range.end >= to) return true
117
- }
118
- return false
119
- }
120
-
121
- const findRefRangeIndex = (pos, refRanges) => {
122
- if (!refRanges || refRanges.length === 0) return -1
123
-
124
- const tryIndex = (idx) => {
125
- if (idx < 0 || idx >= refRanges.length) return -1
126
- const range = refRanges[idx]
127
- if (pos >= range.start && pos <= range.end) {
128
- return range.hasReference ? idx : -1
129
- }
130
- return null
131
- }
132
-
133
- const tracker = refRanges.__lastIndexState || (refRanges.__lastIndexState = { idx: 0 })
134
- let idx = tracker.idx
135
- if (idx >= refRanges.length) idx = refRanges.length - 1
136
- let result = tryIndex(idx)
137
- if (result !== null) {
138
- tracker.idx = idx
139
- return result
140
- }
141
-
142
- if (pos < refRanges[idx].start) {
143
- while (idx > 0 && pos < refRanges[idx].start) {
144
- idx--
145
- result = tryIndex(idx)
146
- if (result !== null) {
147
- tracker.idx = idx
148
- return result
149
- }
150
- }
151
- } else {
152
- while (idx < refRanges.length - 1 && pos > refRanges[idx].end) {
153
- idx++
154
- result = tryIndex(idx)
155
- if (result !== null) {
156
- tracker.idx = idx
157
- return result
158
- }
159
- }
160
- }
161
-
162
- let left = 0
163
- let right = refRanges.length - 1
164
- while (left <= right) {
165
- const mid = left + Math.floor((right - left) / 2)
166
- const range = refRanges[mid]
167
- if (pos < range.start) {
168
- right = mid - 1
169
- } else if (pos > range.end) {
170
- left = mid + 1
171
- } else {
172
- tracker.idx = mid
173
- return range.hasReference ? mid : -1
174
- }
175
- }
176
- return -1
177
- }
178
-
179
- // Detect reference-link label ranges within the current inline slice
180
- const computeReferenceRanges = (state, start, max) => {
181
- const src = state.src
182
- const references = state.env && state.env.references
183
- const referenceCount = state.__strongJaReferenceCount
184
- const hasReferences = references && (referenceCount !== undefined
185
- ? referenceCount > 0
186
- : Object.keys(references).length > 0)
187
- if (!hasReferences) return []
188
- let pos = src.indexOf('[', start)
189
- if (pos === -1 || pos >= max) return []
190
- const ranges = []
191
- while (pos !== -1 && pos < max) {
192
- if (!hasBackslash(state, pos)) {
193
- const labelClose = findMatchingBracket(state, pos, max, CHAR_OPEN_BRACKET, CHAR_CLOSE_BRACKET)
194
- if (labelClose !== -1) {
195
- const nextPos = labelClose + 1
196
- if (nextPos < max && src.charCodeAt(nextPos) === CHAR_OPEN_BRACKET && !hasBackslash(state, nextPos)) {
197
- const refClose = findMatchingBracket(state, nextPos, max, CHAR_OPEN_BRACKET, CHAR_CLOSE_BRACKET)
198
- if (refClose !== -1) {
199
- let hasReference = false
200
- if (refClose === nextPos + 1) {
201
- const labelRaw = src.slice(pos + 1, labelClose)
202
- const normalizedLabel = normalizeReferenceCandidate(state, labelRaw, { useClean: true })
203
- hasReference = !!references[normalizedLabel]
204
- } else {
205
- const refRaw = src.slice(nextPos + 1, refClose)
206
- const normalizedRef = normalizeReferenceCandidate(state, refRaw)
207
- hasReference = !!references[normalizedRef]
208
- }
209
- if (hasReference) {
210
- ranges.push({ start: pos, end: labelClose, hasReference: true })
211
- ranges.push({ start: nextPos, end: refClose, hasReference: true })
212
- }
213
- pos = src.indexOf('[', refClose + 1)
214
- continue
215
- }
216
- }
217
- }
218
- }
219
- pos = src.indexOf('[', pos + 1)
220
- }
221
- return ranges
222
- }
223
-
224
- const computeInlineLinkRanges = (state, start, max) => {
225
- const src = state.src
226
- const ranges = []
227
- const labelRanges = []
228
- let pos = src.indexOf('[', start)
229
- if (pos === -1 || pos >= max) return []
230
- let rangeId = 0
231
- while (pos !== -1 && pos < max) {
232
- if (!hasBackslash(state, pos)) {
233
- const labelClose = findMatchingBracket(state, pos, max, CHAR_OPEN_BRACKET, CHAR_CLOSE_BRACKET)
234
- if (labelClose === -1) break
235
- let destStart = labelClose + 1
236
- while (destStart < max) {
237
- const ch = src.charCodeAt(destStart)
238
- if (ch !== CHAR_SPACE && ch !== 0x0A && ch !== 0x09) break
239
- destStart++
240
- }
241
- if (destStart < max && src.charCodeAt(destStart) === CHAR_OPEN_PAREN && !hasBackslash(state, destStart)) {
242
- const destClose = findMatchingBracket(state, destStart, max, CHAR_OPEN_PAREN, CHAR_CLOSE_PAREN)
243
- if (destClose !== -1) {
244
- const labelRange = { start: pos, end: labelClose, kind: 'label', id: rangeId }
245
- ranges.push(labelRange)
246
- labelRanges.push(labelRange)
247
- ranges.push({ start: destStart, end: destClose, kind: 'dest', id: rangeId })
248
- rangeId++
249
- pos = src.indexOf('[', destClose + 1)
250
- continue
251
- }
252
- }
253
- pos = src.indexOf('[', labelClose + 1)
254
- continue
255
- }
256
- pos = src.indexOf('[', pos + 1)
257
- }
258
- if (ranges.length && labelRanges.length) {
259
- ranges.__labelRanges = labelRanges
260
- }
261
- return ranges
262
- }
263
-
264
- const getInlineRangeCacheMap = (ranges, kind, create) => {
265
- const prop = kind ? `__cache_${kind}` : '__cache_any'
266
- let cache = ranges[prop]
267
- if (!cache && create) {
268
- cache = new Map()
269
- ranges[prop] = cache
270
- }
271
- return cache
272
- }
273
-
274
- const findInlineLinkRange = (pos, ranges, kind) => {
275
- if (!ranges || ranges.length === 0) return null
276
- const useCache = ranges.length > 32
277
- const cache = useCache ? getInlineRangeCacheMap(ranges, kind, false) : null
278
- if (cache && cache.has(pos)) return cache.get(pos)
279
- const first = ranges[0]
280
- const last = ranges[ranges.length - 1]
281
- if (pos < first.start || pos > last.end) {
282
- if (useCache) {
283
- const storeCache = getInlineRangeCacheMap(ranges, kind, true)
284
- storeCache.set(pos, null)
285
- }
286
- return null
287
- }
288
- let left = 0
289
- let right = ranges.length - 1
290
- let found = null
291
- while (left <= right) {
292
- const mid = left + Math.floor((right - left) / 2)
293
- const range = ranges[mid]
294
- if (pos < range.start) {
295
- right = mid - 1
296
- } else if (pos > range.end) {
297
- left = mid + 1
298
- } else {
299
- if (!kind || range.kind === kind) {
300
- found = range
301
- }
302
- break
303
- }
304
- }
305
- if (useCache) {
306
- const storeCache = getInlineRangeCacheMap(ranges, kind, true)
307
- storeCache.set(pos, found)
308
- }
309
- return found
310
- }
311
-
312
- const copyInlineTokenFields = (dest, src) => {
313
- Object.assign(dest, src)
314
- }
315
-
316
- const registerPostProcessTarget = (state) => {
317
- const env = state.env
318
- if (!env.__strongJaPostProcessTargets) {
319
- env.__strongJaPostProcessTargets = []
320
- env.__strongJaPostProcessTargetSet = typeof WeakSet !== 'undefined' ? new WeakSet() : null
321
- }
322
- const targets = env.__strongJaPostProcessTargets
323
- const targetSet = env.__strongJaPostProcessTargetSet
324
- if (targetSet) {
325
- if (targetSet.has(state.tokens)) return
326
- targetSet.add(state.tokens)
327
- } else if (targets.includes(state.tokens)) {
328
- return
329
- }
330
- targets.push(state.tokens)
331
- }
332
-
333
- const hasMditAttrs = (state) => {
334
- if (state.__strongJaHasAttrs !== undefined) return state.__strongJaHasAttrs
335
- const rules = state.md && state.md.core && state.md.core.ruler && state.md.core.ruler.__rules__
336
- if (!rules || !Array.isArray(rules)) {
337
- state.__strongJaHasAttrs = false
338
- return false
339
- }
340
- for (let i = 0; i < rules.length; i++) {
341
- if (rules[i].name === 'curly_attributes') {
342
- state.__strongJaHasAttrs = true
343
- return true
344
- }
345
- }
346
- state.__strongJaHasAttrs = false
347
- return false
348
- }
349
-
350
- const isAllAsterisks = (content) => {
351
- for (let i = 0; i < content.length; i++) {
352
- if (content.charCodeAt(i) !== CHAR_ASTERISK) return false
353
- }
354
- return true
355
- }
356
-
357
- function isPlainTextContent(content) {
358
- for (let idx = 0; idx < content.length; idx++) {
359
- const code = content.charCodeAt(idx)
360
- if (code === CHAR_BACKSLASH || code === CHAR_NEWLINE || code === CHAR_TAB) {
361
- return false
362
- }
363
- if (code === CHAR_BACKTICK || code === CHAR_DOLLAR || code === CHAR_LT || code === CHAR_GT) {
364
- return false
365
- }
366
- if (code === CHAR_OPEN_BRACKET || code === CHAR_CLOSE_BRACKET || code === CHAR_OPEN_PAREN || code === CHAR_CLOSE_PAREN) {
367
- return false
368
- }
369
- if (code === 0x5E || code === 0x7E) {
370
- return false
371
- }
372
- }
373
- return true
374
- }
375
-
376
- // Cache newline positions for lightweight map generation
377
- const getLineOffsets = (state) => {
378
- if (state.__strongJaLineOffsets) return state.__strongJaLineOffsets
379
- const offsets = []
380
- const src = state.src || ''
381
- for (let i = 0; i < src.length; i++) {
382
- if (src.charCodeAt(i) === CHAR_NEWLINE) offsets.push(i)
383
- }
384
- state.__strongJaLineOffsets = offsets
385
- return offsets
386
- }
387
-
388
- const createLineMapper = (state) => {
389
- const offsets = getLineOffsets(state)
390
- let idx = 0
391
- const maxIdx = offsets.length
392
- return (startPos, endPos) => {
393
- const start = startPos === undefined || startPos === null ? 0 : startPos
394
- const end = endPos === undefined || endPos === null ? start : endPos
395
- while (idx < maxIdx && offsets[idx] < start) idx++
396
- const startLine = idx
397
- let endIdx = idx
398
- while (endIdx < maxIdx && offsets[endIdx] < end) endIdx++
399
- return [startLine, endIdx]
400
- }
401
- }
402
-
403
- const setToken = (state, inlines, opt, attrsEnabled) => {
404
- const src = state.src
405
- const mapFromPos = createLineMapper(state)
406
- let i = 0
407
- let lastTextToken = null
408
- while (i < inlines.length) {
409
- let type = inlines[i].type
410
- let tag = ''
411
- let isOpen = false
412
- let isClose = false
413
- if (type.length > 5 && type.endsWith('_open')) {
414
- isOpen = true
415
- tag = type.slice(0, -5)
416
- } else if (type.length > 6 && type.endsWith('_close')) {
417
- isClose = true
418
- tag = type.slice(0, -6)
419
- }
420
-
421
- if (isOpen) {
422
- const startToken = state.push(type, tag, 1)
423
- startToken.markup = tag === 'strong' ? '**' : '*'
424
- startToken.map = mapFromPos(inlines[i].s, inlines[i].e)
425
- }
426
-
427
- if (type === 'html_inline') {
428
- const content = src.slice(inlines[i].s, inlines[i].e + 1)
429
- if (lastTextToken && inlines[i].s > 0) {
430
- const prevChar = src.charAt(inlines[i].s - 1)
431
- if (prevChar === ' ' || prevChar === '\t') {
432
- if (!lastTextToken.content.endsWith(prevChar)) {
433
- lastTextToken.content += prevChar
434
- }
435
- }
436
- }
437
- const htmlToken = state.push('html_inline', '', 0)
438
- htmlToken.content = content
439
- htmlToken.map = mapFromPos(inlines[i].s, inlines[i].e)
440
- i++
441
- continue
442
- }
443
- if (type === 'text') {
444
- let content = src.slice(inlines[i].s, inlines[i].e + 1)
445
- if (content.length > 0 && content.charCodeAt(0) === CHAR_ASTERISK) {
446
- if (isAllAsterisks(content)) {
447
- const asteriskToken = state.push(type, '', 0)
448
- asteriskToken.content = content
449
- asteriskToken.map = mapFromPos(inlines[i].s, inlines[i].e)
450
- i++
451
- continue
452
- }
453
- }
454
- const attrMatch = attrsEnabled && content.length > 0 && content.charCodeAt(content.length - 1) === CHAR_CLOSE_CURLY && REG_ATTRS.test(content)
455
- ? content.match(/^(.*?)(\s+{[^{}\n!@#%^&*()]+?})$/)
456
- : null
457
- if (attrMatch) {
458
- const textPart = attrMatch[1] ? attrMatch[1].replace(/[ \t]+$/, '') : ''
459
- const attrPart = attrMatch[2]
460
- if (textPart && textPart.length > 0) {
461
- const textToken = state.push(type, '', 0)
462
- textToken.content = textPart
463
- textToken.map = mapFromPos(inlines[i].s, inlines[i].s + textPart.length)
464
- lastTextToken = textToken
465
- }
466
- const attrsToken = state.push(type, '', 0)
467
- let attrsContent = attrPart.replace(/^\s+/, '')
468
- if (attrsContent.indexOf('\\') !== -1) {
469
- const hasBackslashBeforeCurlyAttribute = attrsContent.match(/(\\+){/)
470
- if (hasBackslashBeforeCurlyAttribute) {
471
- if (hasBackslashBeforeCurlyAttribute[1].length === 1) {
472
- attrsContent = attrsContent.replace(/\\{/, '{')
473
- } else {
474
- let backSlashNum = Math.floor(hasBackslashBeforeCurlyAttribute[1].length / 2)
475
- let k = 0
476
- let backSlash = ''
477
- while (k < backSlashNum) {
478
- backSlash += '\\'
479
- k++
480
- }
481
- attrsContent = attrsContent.replace(/\\+{/, backSlash + '{')
482
- }
483
- }
484
- }
485
- attrsToken.content = attrsContent
486
- attrsToken.map = mapFromPos(inlines[i].s + content.length - attrPart.length, inlines[i].e)
487
- i++
488
- continue
489
- }
490
- if (isPlainTextContent(content)) {
491
- const textToken = state.push(type, '', 0)
492
- textToken.content = content
493
- textToken.map = mapFromPos(inlines[i].s, inlines[i].e)
494
- lastTextToken = textToken
495
- i++
496
- continue
497
- }
498
-
499
- const hasOnlySimpleNewline = attrsEnabled && (content.indexOf('{') !== -1 || content.indexOf('}') !== -1) &&
500
- content.indexOf('\n') !== -1 &&
501
- content.indexOf('`') === -1 &&
502
- content.indexOf('$') === -1 &&
503
- content.indexOf('<') === -1 &&
504
- content.indexOf('>') === -1 &&
505
- content.indexOf('[') === -1 &&
506
- content.indexOf(']') === -1 &&
507
- content.indexOf('(') === -1 &&
508
- content.indexOf(')') === -1 &&
509
- content.indexOf('^') === -1 &&
510
- content.indexOf('~') === -1 &&
511
- content.indexOf('\\') === -1
512
-
513
- if (hasOnlySimpleNewline) {
514
- const textToken = state.push(type, '', 0)
515
- textToken.content = content
516
- textToken.map = mapFromPos(inlines[i].s, inlines[i].e)
517
- lastTextToken = textToken
518
- i++
519
- continue
520
- }
521
-
522
- const childTokens = []
523
- state.md.inline.parse(content, state.md, state.env, childTokens)
524
- let j = 0
525
- while (j < childTokens.length) {
526
- const t = childTokens[j]
527
- if (t.type === 'softbreak' && !opt.mdBreaks) {
528
- const hasCjk = opt.hasCjkBreaks === true
529
- if (hasCjk) {
530
- const prevToken = childTokens[j - 1]
531
- const nextToken = childTokens[j + 1]
532
- const prevChar = prevToken && prevToken.content ? prevToken.content.slice(-1) : ''
533
- const nextChar = nextToken && nextToken.content ? nextToken.content.charAt(0) : ''
534
- const isAsciiWord = nextChar >= '0' && nextChar <= 'z' && /[A-Za-z0-9]/.test(nextChar)
535
- if (isAsciiWord && isJapanese(prevChar) && !isJapanese(nextChar)) {
536
- t.type = 'text'
537
- t.tag = ''
538
- t.content = ' '
539
- }
540
- }
541
- }
542
- if (!attrsEnabled && t.tag === 'br') {
543
- t.tag = ''
544
- t.content = '\n'
545
- }
546
- const token = state.push(t.type, t.tag, t.nesting)
547
- copyInlineTokenFields(token, t)
548
- if (t.type === 'text') {
549
- lastTextToken = token
550
- }
551
- j++
552
- }
553
- }
554
-
555
- if (isClose) {
556
- const closeToken = state.push(type, tag, -1)
557
- closeToken.markup = tag === 'strong' ? '**' : '*'
558
- closeToken.map = mapFromPos(inlines[i].s, inlines[i].e)
559
- }
560
-
561
- i++
562
- }
563
- }
564
-
565
- const pushInlines = (inlines, s, e, len, type, tag, tagType) => {
566
- const inline = {
567
- s: s,
568
- sp: s,
569
- e: e,
570
- ep: e,
571
- len: len,
572
- type: type,
573
- check: false
574
- }
575
- if (tag) inline.tag = [tag, tagType]
576
- inlines.push(inline)
577
- }
578
-
579
- const findNextAsciiPunctuation = (src, start, max) => {
580
- REG_ASCII_PUNCT.lastIndex = start
581
- const match = REG_ASCII_PUNCT.exec(src)
582
- if (!match || match.index >= max) return -1
583
- return match.index
584
- }
585
-
586
- const findNextSymbolPos = (state, n, max, symbol, symbolChar) => {
587
- const src = state.src
588
- if (src.charCodeAt(n) !== symbol || hasBackslash(state, n)) return -1
589
- let i = src.indexOf(symbolChar, n + 1)
590
- while (i !== -1 && i < max) {
591
- if (!hasBackslash(state, i)) return i
592
- i = src.indexOf(symbolChar, i + 1)
593
- }
594
- return -1
595
- }
596
-
597
- const processSymbolPair = (state, n, srcLen, symbol, symbolChar, hasText, textStart, pushInlines) => {
598
- const nextSymbolPos = findNextSymbolPos(state, n, srcLen, symbol, symbolChar)
599
- if (nextSymbolPos === -1) {
600
- return { shouldBreak: false, shouldContinue: false, newN: n, hasText: hasText }
601
- }
602
- if (nextSymbolPos === srcLen - 1) {
603
- pushInlines(textStart, nextSymbolPos, nextSymbolPos - textStart + 1, 'text')
604
- return { shouldBreak: true, newN: nextSymbolPos + 1, hasText: true }
605
- }
606
- return { shouldBreak: false, shouldContinue: true, newN: nextSymbolPos + 1, hasText: true }
607
- }
608
-
609
- const processTextSegment = (inlines, textStart, n, hasText) => {
610
- if (n !== 0 && hasText) {
611
- pushInlines(inlines, textStart, n - 1, n - textStart, 'text')
612
- return false
613
- }
614
- return hasText
615
- }
616
-
617
- const createInlines = (state, start, max, opt) => {
618
- const src = state.src
619
- const srcLen = max
620
- const htmlEnabled = state.md.options.html
621
- const dollarMath = opt.dollarMath
622
- let n = start
623
- let inlines = []
624
- let hasText = false
625
- let textStart = n
626
-
627
- while (n < srcLen) {
628
- let currentChar = src.charCodeAt(n)
629
-
630
- if (!isAsciiPunctuationCode(currentChar)) {
631
- const nextPunc = findNextAsciiPunctuation(src, n, srcLen)
632
- if (nextPunc === -1) {
633
- if (textStart < srcLen) {
634
- pushInlines(inlines, textStart, srcLen - 1, srcLen - textStart, 'text')
635
- }
636
- break
637
- }
638
- if (nextPunc > n) {
639
- hasText = true
640
- n = nextPunc
641
- currentChar = src.charCodeAt(n)
642
- }
643
- }
644
-
645
- // Unified escape check
646
- let isEscaped = false
647
- if (currentChar === CHAR_ASTERISK || currentChar === CHAR_BACKTICK ||
648
- (dollarMath && currentChar === CHAR_DOLLAR) ||
649
- (htmlEnabled && currentChar === CHAR_LT)) {
650
- isEscaped = hasBackslash(state, n)
651
- }
652
-
653
- // Asterisk handling
654
- if (currentChar === CHAR_ASTERISK) {
655
- if (!isEscaped) {
656
- hasText = processTextSegment(inlines, textStart, n, hasText)
657
- if (n === srcLen - 1) {
658
- pushInlines(inlines, n, n, 1, '')
659
- break
660
- }
661
- let i = n + 1
662
- while (i < srcLen && src.charCodeAt(i) === CHAR_ASTERISK) {
663
- i++
664
- }
665
- if (i === srcLen) {
666
- pushInlines(inlines, n, i - 1, i - n, '')
667
- } else {
668
- pushInlines(inlines, n, i - 1, i - n, '')
669
- textStart = i
670
- hasText = false
671
- }
672
- n = i
673
- continue
674
- }
675
- }
676
-
677
- // Inline code (backticks)
678
- if (currentChar === CHAR_BACKTICK) {
679
- if (!isEscaped) {
680
- const result = processSymbolPair(state, n, srcLen, CHAR_BACKTICK, '`', hasText, textStart,
681
- (start, end, len, type) => pushInlines(inlines, start, end, len, type))
682
- if (result.shouldBreak) break
683
- if (result.shouldContinue) {
684
- n = result.newN
685
- hasText = result.hasText
686
- continue
687
- }
688
- hasText = result.hasText
689
- }
690
- }
691
-
692
- // Inline math ($...$)
693
- if (dollarMath && currentChar === CHAR_DOLLAR) {
694
- if (!isEscaped) {
695
- const result = processSymbolPair(state, n, srcLen, CHAR_DOLLAR, '$', hasText, textStart,
696
- (start, end, len, type) => pushInlines(inlines, start, end, len, type))
697
- if (result.shouldBreak) break
698
- if (result.shouldContinue) {
699
- n = result.newN
700
- hasText = result.hasText
701
- continue
702
- }
703
- hasText = result.hasText
704
- }
705
- }
706
-
707
- // HTML tags
708
- if (htmlEnabled && currentChar === CHAR_LT) {
709
- if (!isEscaped) {
710
- const guardHtml = srcLen - n > 8192
711
- const maxScanEnd = guardHtml ? Math.min(srcLen, n + 8192) : srcLen
712
- let foundClosingTag = false
713
- let i = n + 1
714
- while (i < srcLen) {
715
- i = src.indexOf('>', i)
716
- if (i === -1 || i >= maxScanEnd) break
717
- if (!hasBackslash(state, i)) {
718
- hasText = processTextSegment(inlines, textStart, n, hasText)
719
- let tag = src.slice(n + 1, i)
720
- let tagType
721
- if (tag.charCodeAt(0) === CHAR_SLASH) {
722
- tag = tag.slice(1)
723
- tagType = 'close'
724
- } else {
725
- tagType = 'open'
726
- }
727
- pushInlines(inlines, n, i, i - n + 1, 'html_inline', tag, tagType)
728
- textStart = i + 1
729
- hasText = false
730
- n = i + 1
731
- foundClosingTag = true
732
- break
733
- }
734
- i += 1
735
- }
736
- if (foundClosingTag) {
737
- continue
738
- }
739
- // If no closing tag found, treat as regular character to prevent infinite loops
740
- }
741
- }
742
-
743
- // Regular character
744
- hasText = true
745
- if (n === srcLen - 1) {
746
- pushInlines(inlines, textStart, n, n - textStart + 1, 'text')
747
- break
748
- }
749
- n++
750
- }
751
- return inlines
752
- }
753
-
754
- const pushMark = (marks, opts) => {
755
- // Maintain sorted order during insertion
756
- const newMark = {
757
- nest: opts.nest,
758
- s: opts.s,
759
- e: opts.e,
760
- len: opts.len,
761
- oLen: opts.oLen,
762
- type: opts.type
763
- }
764
- if (marks.length === 0 || marks[marks.length - 1].s <= newMark.s) {
765
- marks.push(newMark)
766
- return
767
- }
768
- // Binary search for insertion point to maintain sorted order
769
- let left = 0
770
- let right = marks.length
771
- while (left < right) {
772
- const mid = Math.floor((left + right) / 2)
773
- if (marks[mid].s <= newMark.s) {
774
- left = mid + 1
775
- } else {
776
- right = mid
777
- }
778
- }
779
-
780
- marks.splice(left, 0, newMark)
781
- }
782
-
783
- const setStrong = (state, inlines, marks, n, memo, opt, nestTracker, refRanges, inlineLinkRanges) => {
784
- const hasInlineLinkRanges = inlineLinkRanges && inlineLinkRanges.length > 0
785
- const hasRefRanges = refRanges && refRanges.length > 0
786
- const inlinesLength = inlines.length
787
- const leadingCompat = opt.leadingAsterisk === false
788
- const conservativePunctuation = opt.disallowMixed === true
789
- if (opt.disallowMixed === true) {
790
- let i = n + 1
791
- while (i < inlinesLength) {
792
- if (inlines[i].len === 0 || inlines[i].check) { i++; continue }
793
- if (inlines[i].type !== '') { i++; continue }
794
-
795
- if (inlines[i].len > 1) {
796
- if (shouldBlockMixedLanguage(state, inlines, n, i)) {
797
- return [n, 0]
798
- }
799
- break
800
- }
801
- i++
802
- }
803
- }
804
-
805
- const strongOpenRange = hasRefRanges ? findRefRangeIndex(inlines[n].s, refRanges) : -1
806
- const openLinkRange = hasInlineLinkRanges ? findInlineLinkRange(inlines[n].s, inlineLinkRanges) : null
807
- let i = n + 1
808
- let j = 0
809
- let nest = 0
810
- while (i < inlinesLength) {
811
- if (inlines[i].type !== '') { i++; continue }
812
- if (inlines[i].len === 0 || inlines[i].check) { i++; continue }
813
-
814
- if (hasInlineLinkRanges &&
815
- hasInlineLinkLabelCrossing(inlineLinkRanges, inlines[n].ep + 1, inlines[i].sp)) {
816
- i++
817
- continue
818
- }
819
-
820
- const closeRange = hasRefRanges ? findRefRangeIndex(inlines[i].s, refRanges) : -1
821
- if (strongOpenRange !== closeRange) { i++; continue }
822
-
823
- const closeLinkRange = hasInlineLinkRanges ? findInlineLinkRange(inlines[i].s, inlineLinkRanges) : null
824
- if (openLinkRange || closeLinkRange) {
825
- if (!openLinkRange || !closeLinkRange || openLinkRange.id !== closeLinkRange.id || openLinkRange.kind !== closeLinkRange.kind) {
826
- i++
827
- continue
828
- }
829
- }
830
-
831
- if (state.md && state.md.options && state.md.options.html && hasCodeTagInside(state, inlines, n, i)) {
832
- return [n, nest]
833
- }
834
-
835
- nest = checkNest(inlines, marks, n, i, nestTracker)
836
- if (nest === -1) return [n, nest]
837
-
838
- if (inlines[i].len === 1 && inlines[n].len > 2) {
839
- pushMark(marks, {
840
- nest: nest,
841
- s: inlines[n].ep,
842
- e: inlines[n].ep,
843
- len: 1,
844
- oLen: inlines[n].len - 1,
845
- type: 'em_open'
846
- })
847
- pushMark(marks, {
848
- nest: nest,
849
- s: inlines[i].sp,
850
- e: inlines[i].ep,
851
- len: 1,
852
- oLen: inlines[i].len - 1,
853
- type: 'em_close'
854
- })
855
- inlines[n].len -= 1
856
- inlines[n].ep -= 1
857
- inlines[i].len -= 1
858
- if (inlines[i].len > 0) inlines[i].sp += 1
859
- const [newN, newNest] = setEm(state, inlines, marks, n, memo, opt, null, nestTracker, refRanges, inlineLinkRanges)
860
- n = newN
861
- nest = newNest
862
- }
863
- let strongNum = Math.trunc(Math.min(inlines[n].len, inlines[i].len) / 2)
864
-
865
- if (inlines[i].len > 1) {
866
- const hasJapaneseContext = isJapanese(state.src[inlines[n].s - 1] || '') || isJapanese(state.src[inlines[i].e + 1] || '')
867
- const needsPunctuationCheck = (conservativePunctuation && !hasJapaneseContext) || hasHtmlLikePunctuation(state, inlines, n, i) || hasAngleBracketInside(state, inlines, n, i)
868
- if (needsPunctuationCheck && hasPunctuationOrNonJapanese(state, inlines, n, i, opt, refRanges, hasRefRanges)) {
869
- if (leadingCompat) {
870
- return [n, nest]
871
- }
872
- if (memo.inlineMarkEnd) {
873
- marks.push(...createMarks(state, inlines, i, inlinesLength - 1, memo, opt, refRanges, inlineLinkRanges))
874
- if (inlines[i].len === 0) { i++; continue }
875
- } else {
876
- return [n, nest]
877
- }
878
- }
879
-
880
- j = 0
881
- while (j < strongNum) {
882
- pushMark(marks, {
883
- nest: nest + strongNum - 1 - j,
884
- s: inlines[n].ep - 1,
885
- e: inlines[n].ep,
886
- len: 2,
887
- oLen: inlines[n].len - 2,
888
- type: 'strong_open'
889
- })
890
- inlines[n].ep -= 2
891
- inlines[n].len -= 2
892
- pushMark(marks, {
893
- nest: nest + strongNum - 1 - j,
894
- s: inlines[i].sp,
895
- e: inlines[i].sp + 1,
896
- len: 2,
897
- oLen: inlines[i].len - 2,
898
- type: 'strong_close'
899
- })
900
- inlines[i].sp += 2
901
- inlines[i].len -= 2
902
- j++
903
- }
904
- if (inlines[n].len === 0) return [n, nest]
905
- }
906
-
907
- if (inlines[n].len === 1 && inlines[i].len > 0) {
908
- nest++
909
- const [newN, newNest] = setEm(state, inlines, marks, n, memo, opt, nest, nestTracker, refRanges, inlineLinkRanges)
910
- n = newN
911
- nest = newNest
912
- }
913
-
914
- i++
915
- }
916
-
917
- if (n == 0 && memo.inlineMarkEnd) {
918
- marks.push(...createMarks(state, inlines, n + 1, inlinesLength - 1, memo, opt, refRanges, inlineLinkRanges))
919
- }
920
- return [n, nest]
921
- }
922
-
923
- const checkInsideTags = (inlines, i, memo) => {
924
- if (inlines[i].tag === undefined) return 0
925
- const tagName = inlines[i].tag[0].toLowerCase()
926
- if (memo.htmlTags[tagName] === undefined) {
927
- memo.htmlTags[tagName] = 0
928
- }
929
- const tagType = inlines[i].tag[1]
930
- if (tagType === 'open') {
931
- memo.htmlTags[tagName] += 1
932
- memo.htmlTagDepth += 1
933
- }
934
- if (tagType === 'close') {
935
- memo.htmlTags[tagName] -= 1
936
- memo.htmlTagDepth -= 1
937
- }
938
- if (memo.htmlTags[tagName] < 0 || memo.htmlTagDepth < 0) {
939
- return -1
940
- }
941
- return memo.htmlTagDepth === 0 ? 1 : 0
942
- }
943
-
944
- // Check if character is ASCII punctuation or space
945
- // Covers: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ and space
946
- const isPunctuation = (ch) => {
947
- if (!ch) return false
948
- const code = ch.charCodeAt(0)
949
- // ASCII punctuation: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
950
- return (code >= 33 && code <= 47) || (code >= 58 && code <= 64) ||
951
- (code >= 91 && code <= 96) || (code >= 123 && code <= 126) || code === 32
952
- }
953
-
954
- const isAsciiPunctuationCode = (code) => {
955
- if (code < 33 || code > 126) return false
956
- return (code <= 47) || (code >= 58 && code <= 64) || (code >= 91 && code <= 96) || (code >= 123)
957
- }
958
-
959
- const isUnicodePunctuation = (ch) => {
960
- if (!ch) return false
961
- return /\p{P}/u.test(ch)
962
- }
963
-
964
- // Check if character is Japanese (hiragana, katakana, kanji, CJK punctuation/fullwidth)
965
- // Uses fast Unicode range checks for common cases, falls back to REG_JAPANESE for complex Unicode
966
- const isJapanese = (ch) => {
967
- if (!ch) return false
968
- const code = ch.charCodeAt(0)
969
- // Fast ASCII check first
970
- if (code < 128) return false
971
- // Hiragana: U+3040-U+309F, Katakana: U+30A0-U+30FF, Kanji: U+4E00-U+9FAF
972
- return (code >= 0x3040 && code <= 0x309F) ||
973
- (code >= 0x30A0 && code <= 0x30FF) ||
974
- (code >= 0x4E00 && code <= 0x9FAF) ||
975
- // Fallback to regex for complex Unicode cases
976
- REG_JAPANESE.test(ch)
977
- }
978
-
979
- const hasJapaneseText = (str) => {
980
- if (!str) return false
981
- return REG_JAPANESE.test(str)
982
- }
983
-
984
- const resolveLeadingAsterisk = (state, opt, start, max) => {
985
- const modeRaw = opt.mode || 'japanese-only'
986
- const mode = typeof modeRaw === 'string' ? modeRaw.toLowerCase() : 'japanese-only'
987
- if (mode === 'aggressive') return true
988
- if (mode === 'compatible') return false
989
- let hasJapanese = state.__strongJaHasJapanese
990
- if (hasJapanese === undefined) {
991
- hasJapanese = hasJapaneseText(state.src.slice(0, max))
992
- state.__strongJaHasJapanese = hasJapanese
993
- }
994
- if (opt.disallowMixed === true) return hasJapanese
995
-
996
- return hasJapanese
997
- }
998
-
999
- // Check if character is English (letters, numbers) or other non-Japanese characters
1000
- // Uses REG_JAPANESE to exclude Japanese characters
1001
- const isEnglish = (ch) => {
1002
- if (!ch) return false
1003
- const code = ch.charCodeAt(0)
1004
- if ((code >= 65 && code <= 90) || (code >= 97 && code <= 122) || (code >= 48 && code <= 57)) {
1005
- return true
1006
- }
1007
- if (code < 128) {
1008
- return code === CHAR_SPACE || (code > 126)
1009
- }
1010
- return !REG_JAPANESE.test(ch)
1011
- }
1012
-
1013
- const shouldBlockMixedLanguage = (state, inlines, n, i) => {
1014
- const src = state.src
1015
- const openPrevChar = src[inlines[n].s - 1] || ''
1016
- const closeNextChar = src[inlines[i].e + 1] || ''
1017
-
1018
- const isEnglishPrefix = isEnglish(openPrevChar)
1019
- const isEnglishSuffix = isEnglish(closeNextChar)
1020
- if (!isEnglishPrefix && !isEnglishSuffix) {
1021
- return false
1022
- }
1023
- return hasMarkdownHtmlPattern(src, inlines[n].e + 1, inlines[i].s)
1024
- }
1025
-
1026
- const hasPunctuationOrNonJapanese = (state, inlines, n, i, opt, refRanges, hasRefRanges) => {
1027
- const src = state.src
1028
- const openPrevChar = src[inlines[n].s - 1] || ''
1029
- const openNextChar = src[inlines[n].e + 1] || ''
1030
- let checkOpenNextChar = isPunctuation(openNextChar)
1031
- if (!checkOpenNextChar && opt.leadingAsterisk === false && isUnicodePunctuation(openNextChar)) {
1032
- checkOpenNextChar = true
1033
- }
1034
- if (hasRefRanges && checkOpenNextChar && (openNextChar === '[' || openNextChar === ']')) {
1035
- const openNextRange = findRefRangeIndex(inlines[n].e + 1, refRanges)
1036
- if (openNextRange !== -1) {
1037
- checkOpenNextChar = false
1038
- }
1039
- }
1040
- const closePrevChar = src[inlines[i].s - 1] || ''
1041
- let checkClosePrevChar = isPunctuation(closePrevChar)
1042
- if (!checkClosePrevChar && opt.leadingAsterisk === false && isUnicodePunctuation(closePrevChar)) {
1043
- checkClosePrevChar = true
1044
- }
1045
- if (hasRefRanges && checkClosePrevChar && (closePrevChar === '[' || closePrevChar === ']')) {
1046
- const closePrevRange = findRefRangeIndex(inlines[i].s - 1, refRanges)
1047
- if (closePrevRange !== -1) {
1048
- checkClosePrevChar = false
1049
- }
1050
- }
1051
- const closeNextChar = src[inlines[i].e + 1] || ''
1052
- const isLastInline = i === inlines.length - 1
1053
- let checkCloseNextChar = isLastInline || isPunctuation(closeNextChar) || closeNextChar === '\n'
1054
- if (!checkCloseNextChar && opt.leadingAsterisk === false && isUnicodePunctuation(closeNextChar)) {
1055
- checkCloseNextChar = true
1056
- }
1057
-
1058
- if (opt.disallowMixed === false) {
1059
- if (isEnglish(openPrevChar) || isEnglish(closeNextChar)) {
1060
- if (hasMarkdownHtmlPattern(src, inlines[n].e + 1, inlines[i].s)) {
1061
- return false
1062
- }
1063
- }
1064
- }
1065
-
1066
- const result = (checkOpenNextChar || checkClosePrevChar) && !checkCloseNextChar && !(isJapanese(openPrevChar) || isJapanese(closeNextChar))
1067
- return result
1068
- }
1069
-
1070
- const hasHtmlLikePunctuation = (state, inlines, n, i) => {
1071
- const src = state.src
1072
- const chars = [
1073
- src[inlines[n].e + 1] || '',
1074
- src[inlines[i].s - 1] || '',
1075
- src[inlines[i].e + 1] || ''
1076
- ]
1077
- for (let idx = 0; idx < chars.length; idx++) {
1078
- const ch = chars[idx]
1079
- if (ch === '<' || ch === '>') return true
1080
- }
1081
- return false
1082
- }
1083
-
1084
- const hasAngleBracketInside = (state, inlines, n, i) => {
1085
- const src = state.src
1086
- const start = inlines[n].s
1087
- const end = inlines[i].e
1088
- const ltPos = src.indexOf('<', start)
1089
- if (ltPos !== -1 && ltPos <= end) return true
1090
- const gtPos = src.indexOf('>', start)
1091
- return gtPos !== -1 && gtPos <= end
1092
- }
1093
-
1094
- const hasCodeTagInside = (state, inlines, n, i) => {
1095
- const src = state.src
1096
- const start = inlines[n].s
1097
- const end = inlines[i].e
1098
- const codeOpen = src.indexOf('<code', start)
1099
- if (codeOpen !== -1 && codeOpen <= end) return true
1100
- const codeClose = src.indexOf('</code', start)
1101
- if (codeClose !== -1 && codeClose <= end) return true
1102
- const preOpen = src.indexOf('<pre', start)
1103
- if (preOpen !== -1 && preOpen <= end) return true
1104
- const preClose = src.indexOf('</pre', start)
1105
- return preClose !== -1 && preClose <= end
1106
- }
1107
-
1108
- const setEm = (state, inlines, marks, n, memo, opt, sNest, nestTracker, refRanges, inlineLinkRanges) => {
1109
- const hasInlineLinkRanges = inlineLinkRanges && inlineLinkRanges.length > 0
1110
- const hasRefRanges = refRanges && refRanges.length > 0
1111
- const inlinesLength = inlines.length
1112
- const emOpenRange = hasRefRanges ? findRefRangeIndex(inlines[n].s, refRanges) : -1
1113
- const openLinkRange = hasInlineLinkRanges ? findInlineLinkRange(inlines[n].s, inlineLinkRanges) : null
1114
- const leadingCompat = opt.leadingAsterisk === false
1115
- const conservativePunctuation = leadingCompat || opt.disallowMixed === true
1116
- if (opt.disallowMixed === true && !sNest) {
1117
- let i = n + 1
1118
- while (i < inlinesLength) {
1119
- if (inlines[i].len === 0 || inlines[i].check) { i++; continue }
1120
- if (inlines[i].type !== '') { i++; continue }
1121
-
1122
- if (inlines[i].len > 0) {
1123
- if (shouldBlockMixedLanguage(state, inlines, n, i)) {
1124
- return [n, 0]
1125
- }
1126
- break
1127
- }
1128
- i++
1129
- }
1130
- }
1131
-
1132
- let i = n + 1
1133
- let nest = 0
1134
- let strongPNum = 0
1135
- let insideTagsIsClose = 1
1136
- while (i < inlinesLength) {
1137
- if (inlines[i].len === 0 || inlines[i].check) { i++; continue }
1138
- if (!sNest && inlines[i].type === 'html_inline') {
1139
- inlines[i].check = true
1140
- insideTagsIsClose = checkInsideTags(inlines, i, memo)
1141
- if (insideTagsIsClose === -1) return [n, nest]
1142
- if (insideTagsIsClose === 0) { i++; continue }
1143
- }
1144
- if (inlines[i].type !== '') { i++; continue }
1145
-
1146
- if (hasInlineLinkRanges &&
1147
- hasInlineLinkLabelCrossing(inlineLinkRanges, inlines[n].ep + 1, inlines[i].sp)) {
1148
- i++
1149
- continue
1150
- }
1151
-
1152
- const closeRange = hasRefRanges ? findRefRangeIndex(inlines[i].s, refRanges) : -1
1153
- if (emOpenRange !== closeRange) {
1154
- i++
1155
- continue
1156
- }
1157
-
1158
- const closeLinkRange = hasInlineLinkRanges ? findInlineLinkRange(inlines[i].s, inlineLinkRanges) : null
1159
- if (openLinkRange || closeLinkRange) {
1160
- if (!openLinkRange || !closeLinkRange || openLinkRange.id !== closeLinkRange.id || openLinkRange.kind !== closeLinkRange.kind) {
1161
- i++
1162
- continue
1163
- }
1164
- }
1165
-
1166
- if (state.md && state.md.options && state.md.options.html && hasCodeTagInside(state, inlines, n, i)) {
1167
- return [n, nest]
1168
- }
1169
-
1170
- const emNum = Math.min(inlines[n].len, inlines[i].len)
1171
-
1172
- if (!sNest && emNum !== 1) return [n, sNest, memo]
1173
-
1174
- const isMarkerAtStartAndEnd = memo.inlineMarkStart &&
1175
- i === inlinesLength - 1 &&
1176
- inlines[i].len > 1
1177
- if (!sNest && inlines[i].len === 2 && !isMarkerAtStartAndEnd) {
1178
- strongPNum++
1179
- i++
1180
- continue
1181
- }
1182
-
1183
- if (sNest) {
1184
- nest = sNest - 1
1185
- } else {
1186
- nest = checkNest(inlines, marks, n, i, nestTracker)
1187
- }
1188
- if (nest === -1) return [n, nest]
1189
-
1190
- if (emNum === 1) {
1191
- const needsPunctuationCheckClose = conservativePunctuation || hasHtmlLikePunctuation(state, inlines, n, i) || hasAngleBracketInside(state, inlines, n, i)
1192
- if (needsPunctuationCheckClose && hasPunctuationOrNonJapanese(state, inlines, n, i, opt, refRanges, hasRefRanges)) {
1193
- if (leadingCompat) {
1194
- return [n, nest]
1195
- }
1196
- if (memo.inlineMarkEnd) {
1197
- marks.push(...createMarks(state, inlines, i, inlinesLength - 1, memo, opt, refRanges, inlineLinkRanges))
1198
-
1199
- if (inlines[i].len === 0) { i++; continue }
1200
- } else {
1201
- return [n, nest]
1202
- }
1203
- }
1204
- if (inlines[i].len < 1) {
1205
- i++; continue;
1206
- }
1207
-
1208
- pushMark(marks, {
1209
- nest: nest,
1210
- s: inlines[n].ep,
1211
- e: inlines[n].ep,
1212
- len: 1,
1213
- oLen: inlines[n].len - 1,
1214
- type: 'em_open'
1215
- })
1216
- inlines[n].ep -= 1
1217
- inlines[n].len -= 1
1218
-
1219
- if (strongPNum % 2 === 0 || inlines[i].len < 2) {
1220
- pushMark(marks, {
1221
- nest: nest,
1222
- s: inlines[i].sp,
1223
- e: inlines[i].sp,
1224
- len: 1,
1225
- oLen: inlines[i].len - 1,
1226
- type: 'em_close'
1227
- })
1228
- inlines[i].sp += 1
1229
- } else {
1230
- pushMark(marks, {
1231
- nest: nest,
1232
- s: inlines[i].ep,
1233
- e: inlines[i].ep,
1234
- len: 1,
1235
- oLen: inlines[i].len - 1,
1236
- type: 'em_close'
1237
- })
1238
- inlines[i].sp = inlines[i].ep - 1
1239
- inlines[i].ep -= 1
1240
- }
1241
- inlines[i].len -= 1
1242
- if (inlines[n].len === 0) return [n, nest]
1243
- }
1244
-
1245
- i++
1246
- }
1247
- return [n, nest]
1248
- }
1249
-
1250
- const setText = (inlines, marks, n, nest) => {
1251
- pushMark(marks, {
1252
- nest: nest,
1253
- s: inlines[n].sp,
1254
- e: inlines[n].ep,
1255
- len: inlines[n].len,
1256
- oLen: -1,
1257
- type: 'text'
1258
- })
1259
- inlines[n].len = 0
1260
- }
1261
-
1262
- // Nest state management
1263
- const createNestTracker = () => {
1264
- return {
1265
- strongNest: 0,
1266
- emNest: 0,
1267
- markIndex: 0
1268
- }
1269
- }
1270
-
1271
- const updateNestTracker = (tracker, marks, targetPos) => {
1272
- while (tracker.markIndex < marks.length && marks[tracker.markIndex].s <= targetPos) {
1273
- const mark = marks[tracker.markIndex]
1274
- if (mark.type === 'strong_open') tracker.strongNest++
1275
- else if (mark.type === 'strong_close') tracker.strongNest--
1276
- else if (mark.type === 'em_open') tracker.emNest++
1277
- else if (mark.type === 'em_close') tracker.emNest--
1278
- tracker.markIndex++
1279
- }
1280
- }
1281
-
1282
- const checkNest = (inlines, marks, n, i, nestTracker) => {
1283
- if (marks.length === 0) return 1
1284
- // Update nest state up to current position
1285
- updateNestTracker(nestTracker, marks, inlines[n].s)
1286
-
1287
- const parentNest = nestTracker.strongNest + nestTracker.emNest
1288
- // Check if there's a conflicting close mark before the end position
1289
- let parentCloseN = nestTracker.markIndex
1290
- while (parentCloseN < marks.length) {
1291
- if (marks[parentCloseN].nest === parentNest) break
1292
- parentCloseN++
1293
- }
1294
- if (parentCloseN < marks.length && marks[parentCloseN].s < inlines[i].s) {
1295
- return -1
1296
- }
1297
- return parentNest + 1
1298
- }
1299
-
1300
- const createMarks = (state, inlines, start, end, memo, opt, refRanges, inlineLinkRanges) => {
1301
- let marks = []
1302
- let n = start
1303
- const nestTracker = createNestTracker()
1304
-
1305
- while (n < end) {
1306
- if (inlines[n].type !== '') { n++; continue }
1307
- let nest = 0
1308
-
1309
- if (inlines[n].len > 1) {
1310
- const [newN, newNest] = setStrong(state, inlines, marks, n, memo, opt, nestTracker, refRanges, inlineLinkRanges)
1311
- n = newN
1312
- nest = newNest
1313
- }
1314
- if (inlines[n].len !== 0) {
1315
- const [newN2, newNest2] = setEm(state, inlines, marks, n, memo, opt, null, nestTracker, refRanges, inlineLinkRanges)
1316
- n = newN2
1317
- nest = newNest2
1318
- }
1319
- if (inlines[n].len !== 0) {
1320
- setText(inlines, marks, n, nest)
1321
- }
1322
- n++
1323
- }
1324
- return marks
1325
- }
1326
-
1327
- const mergeInlinesAndMarks = (inlines, marks) => {
1328
- // marks array is already sorted, skip sorting
1329
- const merged = []
1330
- let markIndex = 0
1331
- for (const token of inlines) {
1332
- if (token.type === '') {
1333
- while (markIndex < marks.length && marks[markIndex].s >= token.s && marks[markIndex].e <= token.e) {
1334
- merged.push(marks[markIndex])
1335
- markIndex++
1336
- }
1337
- } else {
1338
- merged.push(token)
1339
- }
1340
- }
1341
- while (markIndex < marks.length) {
1342
- merged.push(marks[markIndex++])
1343
- }
1344
- return merged
1345
- }
1346
-
1347
- const isWhitespaceToken = (token) => {
1348
- if (!token || token.type !== 'text') return false
1349
- const content = token.content
1350
- if (!content) return true
1351
- for (let i = 0; i < content.length; i++) {
1352
- if (!isWhiteSpace(content.charCodeAt(i))) return false
1353
- }
1354
- return true
1355
- }
1356
-
1357
- const hasMarkdownHtmlPattern = (src, start, end) => {
1358
- if (start >= end) return false
1359
- const first = src.charCodeAt(start)
1360
- const last = src.charCodeAt(end - 1)
1361
- if (first === CHAR_OPEN_BRACKET) {
1362
- if (last !== CHAR_CLOSE_PAREN) return false
1363
- } else if (first === CHAR_LT) {
1364
- if (last !== CHAR_GT) return false
1365
- } else if (first === CHAR_BACKTICK) {
1366
- if (last !== CHAR_BACKTICK) return false
1367
- } else if (first === CHAR_DOLLAR) {
1368
- if (last !== CHAR_DOLLAR) return false
1369
- } else {
1370
- return false
1371
- }
1372
- return REG_MARKDOWN_HTML.test(src.slice(start, end))
1373
- }
1374
-
1375
- const strongJa = (state, silent, opt) => {
1376
- if (silent) return false
1377
- const start = state.pos
1378
- let max = state.posMax
1379
- const originalMax = max
1380
- const src = state.src
1381
- let attributesSrc
1382
- if (start > max) return false
1383
- if (src.charCodeAt(start) !== CHAR_ASTERISK) return false
1384
- if (hasBackslash(state, start)) return false
1385
-
1386
- const attrsEnabled = opt.mditAttrs && hasMditAttrs(state)
1387
-
1388
- const leadingAsterisk = resolveLeadingAsterisk(state, opt, start, originalMax)
1389
-
1390
- if (leadingAsterisk === false) {
1391
- return false
1392
- }
1393
-
1394
- const runtimeOpt = leadingAsterisk === opt.leadingAsterisk
1395
- ? opt
1396
- : { ...opt, leadingAsterisk }
1397
-
1398
- if (start === 0) {
1399
- state.__strongJaRefRangeCache = null
1400
- state.__strongJaInlineLinkRangeCache = null
1401
- state.__strongJaBackslashCache = undefined
1402
- state.__strongJaHasBackslash = undefined
1403
- }
1404
-
1405
- if (attrsEnabled) {
1406
- let attrCandidate = false
1407
- let probe = originalMax - 1
1408
- while (probe >= start) {
1409
- const code = src.charCodeAt(probe)
1410
- if (code === CHAR_CLOSE_CURLY) {
1411
- attrCandidate = true
1412
- break
1413
- }
1414
- if (code === CHAR_SPACE || code === CHAR_TAB || code === CHAR_NEWLINE) {
1415
- probe--
1416
- continue
1417
- }
1418
- break
1419
- }
1420
-
1421
- if (attrCandidate) {
1422
- const attrScanTarget = originalMax === src.length ? src : src.slice(0, originalMax)
1423
- attributesSrc = attrScanTarget.match(/((\n)? *){([^{}\n!@#%^&*()]+?)} *$/)
1424
- if (attributesSrc && attributesSrc[3] !== '.') {
1425
- max = attrScanTarget.slice(0, attributesSrc.index).length
1426
- if (attributesSrc[2] === '\n') {
1427
- max = attrScanTarget.slice(0, attributesSrc.index - 1).length
1428
- }
1429
- if (hasBackslash(state, attributesSrc.index) && attributesSrc[2] === '' && attributesSrc[1].length === 0) {
1430
- max = state.posMax
1431
- }
1432
- } else {
1433
- const endCurlyKet = attrScanTarget.match(/(\n *){([^{}\n!@#%^&*()]*?)}.*(} *?)$/)
1434
- if (endCurlyKet) {
1435
- max -= endCurlyKet[3].length
1436
- }
1437
- }
1438
- }
1439
- }
1440
-
1441
- if (state.__strongJaHasCollapsedRefs === undefined) {
1442
- state.__strongJaHasCollapsedRefs = src.indexOf('[') !== -1 &&
1443
- /\[[^\]]*\]\s*\[[^\]]*\]/.test(src)
1444
- }
1445
-
1446
- if (state.__strongJaReferenceCount === undefined) {
1447
- const references = state.env && state.env.references
1448
- state.__strongJaReferenceCount = references ? Object.keys(references).length : 0
1449
- }
1450
-
1451
- let refRanges = []
1452
- const hasReferenceDefinitions = state.__strongJaReferenceCount > 0
1453
- const refScanStart = 0
1454
- if (hasReferenceDefinitions) {
1455
- const firstRefBracket = state.src.indexOf('[', refScanStart)
1456
- if (firstRefBracket !== -1 && firstRefBracket < max) {
1457
- const refCache = state.__strongJaRefRangeCache
1458
- if (refCache && refCache.max === max && refCache.start === refScanStart) {
1459
- refRanges = refCache.ranges
1460
- } else {
1461
- refRanges = computeReferenceRanges(state, refScanStart, max)
1462
- state.__strongJaRefRangeCache = { start: refScanStart, max, ranges: refRanges }
1463
- }
1464
- if (refRanges.length > 0) {
1465
- state.__strongJaHasCollapsedRefs = true
1466
- }
1467
- }
1468
- }
1469
-
1470
- let inlineLinkRanges = null
1471
- const inlineLinkScanStart = 0
1472
- const inlineLinkCandidatePos = state.src.indexOf('](', inlineLinkScanStart)
1473
- const hasInlineLinkCandidate = inlineLinkCandidatePos !== -1 && inlineLinkCandidatePos < max
1474
- if (hasInlineLinkCandidate) {
1475
- const inlineCache = state.__strongJaInlineLinkRangeCache
1476
- if (inlineCache && inlineCache.max === max && inlineCache.start === inlineLinkScanStart) {
1477
- inlineLinkRanges = inlineCache.ranges
1478
- } else {
1479
- inlineLinkRanges = computeInlineLinkRanges(state, inlineLinkScanStart, max)
1480
- state.__strongJaInlineLinkRangeCache = { start: inlineLinkScanStart, max, ranges: inlineLinkRanges }
1481
- }
1482
- if (inlineLinkRanges.length > 0) {
1483
- state.__strongJaHasInlineLinks = true
1484
- }
1485
- }
1486
- let inlines = createInlines(state, start, max, runtimeOpt)
1487
-
1488
- const memo = {
1489
- html: state.md.options.html,
1490
- htmlTags: {},
1491
- htmlTagDepth: 0,
1492
- inlineMarkStart: src.charCodeAt(0) === CHAR_ASTERISK,
1493
- inlineMarkEnd: src.charCodeAt(max - 1) === CHAR_ASTERISK,
1494
- }
1495
-
1496
- let marks = createMarks(state, inlines, 0, inlines.length, memo, runtimeOpt, refRanges, inlineLinkRanges)
1497
-
1498
- inlines = mergeInlinesAndMarks(inlines, marks)
1499
-
1500
- setToken(state, inlines, runtimeOpt, attrsEnabled)
1501
-
1502
- if (inlineLinkRanges && inlineLinkRanges.length > 0) {
1503
- const labelSources = []
1504
- for (let idx = 0; idx < inlineLinkRanges.length; idx++) {
1505
- const range = inlineLinkRanges[idx]
1506
- if (range.kind !== 'label') continue
1507
- labelSources.push(src.slice(range.start + 1, range.end))
1508
- }
1509
- if (labelSources.length > 0) {
1510
- restoreLabelWhitespace(state.tokens, labelSources)
1511
- state.tokens.__strongJaInlineLabelSources = labelSources
1512
- state.tokens.__strongJaInlineLabelIndex = 0
1513
- if (state.env) {
1514
- if (!state.env.__strongJaInlineLabelSourceList) {
1515
- state.env.__strongJaInlineLabelSourceList = []
1516
- }
1517
- state.env.__strongJaInlineLabelSourceList.push(labelSources)
1518
- }
1519
- }
1520
- }
1521
-
1522
- const needsInlineLinkFix = state.__strongJaHasInlineLinks === true
1523
- const needsCollapsedRefFix = state.__strongJaHasCollapsedRefs === true
1524
- if ((needsCollapsedRefFix || needsInlineLinkFix) && !state.__strongJaPostProcessRegistered) {
1525
- registerPostProcessTarget(state)
1526
- state.__strongJaPostProcessRegistered = true
1527
- }
1528
-
1529
- if (attrsEnabled && max !== state.posMax) {
1530
- if (!attributesSrc) {
1531
- state.pos = max
1532
- return true
1533
- }
1534
- state.pos = attributesSrc[1].length > 1 ? max + attributesSrc[1].length : max
1535
- return true
1536
- }
1537
- state.pos = max
1538
- return true
1539
- }
1540
-
1541
- // Collapsed reference helpers
1542
- const buildReferenceLabelRange = (tokens, startIdx, endIdx) => {
1543
- if (startIdx > endIdx) return ''
1544
- let label = ''
1545
- for (let idx = startIdx; idx <= endIdx; idx++) {
1546
- const token = tokens[idx]
1547
- if (!token) continue
1548
- if (token.type === 'text' || token.type === 'code_inline') {
1549
- label += token.content
1550
- } else if (token.type === 'softbreak' || token.type === 'hardbreak') {
1551
- label += ' '
1552
- } else if (token.type && token.type.endsWith('_open') && token.markup) {
1553
- label += token.markup
1554
- } else if (token.type && token.type.endsWith('_close') && token.markup) {
1555
- label += token.markup
1556
- }
1557
- }
1558
- return label
1559
- }
1560
-
1561
- const cleanLabelText = (label) => {
1562
- if (label.indexOf('*') === -1 && label.indexOf('_') === -1) return label
1563
- return label.replace(/^[*_]+/, '').replace(/[*_]+$/, '')
1564
- }
1565
-
1566
- const normalizeReferenceCandidate = (state, text, { useClean = false } = {}) => {
1567
- const source = useClean ? cleanLabelText(text) : text
1568
- return normalizeRefKey(state, source)
1569
- }
1570
-
1571
- const getNormalizeRef = (state) => {
1572
- if (state.__strongJaNormalizeRef) return state.__strongJaNormalizeRef
1573
- const normalize = state.md && state.md.utils && state.md.utils.normalizeReference
1574
- ? state.md.utils.normalizeReference
1575
- : (str) => str.trim().replace(/\s+/g, ' ').toUpperCase()
1576
- state.__strongJaNormalizeRef = normalize
1577
- return normalize
1578
- }
1579
-
1580
- const normalizeRefKey = (state, label) => {
1581
- return getNormalizeRef(state)(label)
1582
- }
1583
-
1584
- const adjustTokenLevels = (tokens, startIdx, endIdx, delta) => {
1585
- for (let i = startIdx; i < endIdx; i++) {
1586
- if (tokens[i]) tokens[i].level += delta
1587
- }
1588
- }
1589
-
1590
- const cloneTextToken = (source, content) => {
1591
- const newToken = new Token('text', '', 0)
1592
- Object.assign(newToken, source)
1593
- newToken.content = content
1594
- if (source.meta) newToken.meta = { ...source.meta }
1595
- if (source.map) newToken.map = source.map
1596
- return newToken
6
+ const buildNoLinkCacheKey = (opt) => {
7
+ const mode = resolveMode(opt)
8
+ const mditAttrs = opt && opt.mditAttrs === false ? '0' : '1'
9
+ const mdBreaks = opt && opt.mdBreaks === true ? '1' : '0'
10
+ return `${mode}|${mditAttrs}|${mdBreaks}`
1597
11
  }
1598
12
 
1599
- // Split only text tokens that actually contain bracket characters
1600
- const splitBracketToken = (tokens, index, options) => {
1601
- const token = tokens[index]
1602
- if (!token || token.type !== 'text') return false
1603
- if (token.__strongJaBracketAtomic) return false
1604
- if (token.__strongJaHasBracket === false) return false
1605
- const content = token.content
1606
- if (!content) {
1607
- token.__strongJaHasBracket = false
1608
- token.__strongJaBracketAtomic = false
1609
- return false
1610
- }
1611
- if (token.__strongJaHasBracket !== true) {
1612
- if (content.indexOf('[') === -1 && content.indexOf(']') === -1) {
1613
- token.__strongJaHasBracket = false
1614
- token.__strongJaBracketAtomic = false
1615
- return false
1616
- }
1617
- token.__strongJaHasBracket = true
1618
- }
1619
- const splitEmptyPair = options && options.splitEmptyPair
1620
- const segments = []
1621
- let buffer = ''
1622
- let pos = 0
1623
- while (pos < content.length) {
1624
- if (!splitEmptyPair &&
1625
- content.charCodeAt(pos) === CHAR_OPEN_BRACKET &&
1626
- content.charCodeAt(pos + 1) === CHAR_CLOSE_BRACKET) {
1627
- if (buffer) {
1628
- segments.push(buffer)
1629
- buffer = ''
1630
- }
1631
- segments.push('[]')
1632
- pos += 2
1633
- continue
1634
- }
1635
- const ch = content[pos]
1636
- if (ch === '[' || ch === ']') {
1637
- if (buffer) {
1638
- segments.push(buffer)
1639
- buffer = ''
1640
- }
1641
- segments.push(ch)
1642
- pos++
1643
- continue
1644
- }
1645
- buffer += ch
1646
- pos++
13
+ const getNoLinkMdInstance = (md, opt) => {
14
+ const baseOpt = opt || md.__strongJaTokenOpt || { mode: 'japanese' }
15
+ const key = buildNoLinkCacheKey(baseOpt)
16
+ if (!md.__strongJaTokenNoLinkCache) {
17
+ md.__strongJaTokenNoLinkCache = new Map()
1647
18
  }
1648
- if (buffer) segments.push(buffer)
1649
- if (segments.length <= 1) {
1650
- if (segments.length === 0) {
1651
- token.__strongJaHasBracket = false
1652
- token.__strongJaBracketAtomic = false
1653
- } else {
1654
- const seg = segments[0]
1655
- if (seg === '[' || seg === ']') {
1656
- token.__strongJaHasBracket = true
1657
- token.__strongJaBracketAtomic = true
1658
- } else if (seg === '[]') {
1659
- token.__strongJaHasBracket = true
1660
- token.__strongJaBracketAtomic = false
1661
- } else {
1662
- token.__strongJaHasBracket = false
1663
- token.__strongJaBracketAtomic = false
1664
- }
1665
- }
1666
- return false
1667
- }
1668
- token.content = segments[0]
1669
- if (token.content === '[' || token.content === ']') {
1670
- token.__strongJaHasBracket = true
1671
- token.__strongJaBracketAtomic = true
1672
- } else if (token.content === '[]') {
1673
- token.__strongJaHasBracket = true
1674
- token.__strongJaBracketAtomic = false
1675
- } else {
1676
- token.__strongJaHasBracket = false
1677
- token.__strongJaBracketAtomic = false
1678
- }
1679
- let insertIdx = index + 1
1680
- for (let s = 1; s < segments.length; s++) {
1681
- const newToken = cloneTextToken(token, segments[s])
1682
- if (segments[s] === '[' || segments[s] === ']') {
1683
- newToken.__strongJaHasBracket = true
1684
- newToken.__strongJaBracketAtomic = true
1685
- } else if (segments[s] === '[]') {
1686
- newToken.__strongJaHasBracket = true
1687
- newToken.__strongJaBracketAtomic = false
1688
- } else {
1689
- newToken.__strongJaHasBracket = false
1690
- newToken.__strongJaBracketAtomic = false
1691
- }
1692
- tokens.splice(insertIdx, 0, newToken)
1693
- insertIdx++
1694
- }
1695
- return true
1696
- }
1697
-
1698
- const isBracketToken = (token, bracket) => {
1699
- return token && token.type === 'text' && token.content === bracket
19
+ const cache = md.__strongJaTokenNoLinkCache
20
+ if (cache.has(key)) return cache.get(key)
21
+ const noLink = new md.constructor(md.options)
22
+ mditStrongJa(noLink, { ...baseOpt, _skipPostprocess: true })
23
+ noLink.inline.ruler.disable(['link'])
24
+ cache.set(key, noLink)
25
+ return noLink
1700
26
  }
1701
27
 
1702
- const findLinkCloseIndex = (tokens, startIdx) => {
1703
- let depth = 0
1704
- for (let idx = startIdx; idx < tokens.length; idx++) {
1705
- const token = tokens[idx]
1706
- if (token.type === 'link_open') depth++
1707
- if (token.type === 'link_close') {
1708
- depth--
1709
- if (depth === 0) return idx
1710
- }
1711
- }
1712
- return -1
1713
- }
1714
-
1715
- const consumeCharactersFromTokens = (tokens, startIdx, count) => {
1716
- let remaining = count
1717
- let idx = startIdx
1718
- while (idx < tokens.length && remaining > 0) {
1719
- const token = tokens[idx]
1720
- if (!token || token.type !== 'text') {
1721
- return false
1722
- }
1723
- const len = token.content.length
1724
- if (remaining >= len) {
1725
- remaining -= len
1726
- tokens.splice(idx, 1)
1727
- continue
1728
- }
1729
- token.content = token.content.slice(remaining)
1730
- remaining = 0
1731
- }
1732
- return remaining === 0
1733
- }
1734
-
1735
- const wrapLabelTokensWithLink = (tokens, labelStartIdx, labelEndIdx, linkOpenToken, linkCloseToken, labelSource) => {
1736
- const wrapperPairs = []
1737
- let startIdx = labelStartIdx
1738
- let endIdx = labelEndIdx
1739
- while (startIdx > 0) {
1740
- const prevToken = tokens[startIdx - 1]
1741
- const nextToken = tokens[endIdx + 1]
1742
- if (!prevToken || !nextToken) break
1743
- if (!/_close$/.test(prevToken.type)) break
1744
- const expectedOpen = prevToken.type.replace('_close', '_open')
1745
- if (nextToken.type !== expectedOpen) break
1746
- wrapperPairs.push({
1747
- base: prevToken.type.replace('_close', ''),
1748
- tag: prevToken.tag,
1749
- markup: prevToken.markup
1750
- })
1751
- tokens.splice(endIdx + 1, 1)
1752
- tokens.splice(startIdx - 1, 1)
1753
- startIdx -= 1
1754
- endIdx -= 1
1755
- }
1756
-
1757
- if (startIdx > endIdx) {
1758
- if (labelSource !== undefined && labelSource !== null) {
1759
- const placeholder = new Token('text', '', 0)
1760
- placeholder.content = labelSource
1761
- placeholder.level = linkOpenToken.level + 1
1762
- tokens.splice(startIdx, 0, placeholder)
1763
- endIdx = startIdx
1764
- } else {
1765
- return startIdx
1766
- }
1767
- }
1768
-
1769
- let labelLength = endIdx - startIdx + 1
1770
- const firstLabelToken = tokens[startIdx]
1771
- const linkLevel = firstLabelToken ? Math.max(firstLabelToken.level - 1, 0) : 0
1772
- linkOpenToken.level = linkLevel
1773
- linkCloseToken.level = linkLevel
1774
- tokens.splice(startIdx, 0, linkOpenToken)
1775
- tokens.splice(startIdx + labelLength + 1, 0, linkCloseToken)
1776
-
1777
- adjustTokenLevels(tokens, startIdx + 1, startIdx + labelLength + 1, 1)
1778
-
1779
- if (wrapperPairs.length > 0) {
1780
- let insertIdx = startIdx + 1
1781
- for (let wp = 0; wp < wrapperPairs.length; wp++) {
1782
- const pair = wrapperPairs[wp]
1783
- const innerOpen = new Token(pair.base + '_open', pair.tag, 1)
1784
- innerOpen.markup = pair.markup
1785
- innerOpen.level = linkLevel + 1 + wp
1786
- tokens.splice(insertIdx, 0, innerOpen)
1787
- insertIdx++
1788
- labelLength++
1789
- }
1790
- let linkClosePos = startIdx + labelLength + 1
1791
- for (let wp = wrapperPairs.length - 1; wp >= 0; wp--) {
1792
- const pair = wrapperPairs[wp]
1793
- const innerClose = new Token(pair.base + '_close', pair.tag, -1)
1794
- innerClose.markup = pair.markup
1795
- innerClose.level = linkLevel + 1 + wp
1796
- tokens.splice(linkClosePos, 0, innerClose)
1797
- labelLength++
1798
- }
1799
- }
1800
-
1801
- return startIdx + labelLength + 2
1802
- }
1803
-
1804
- const parseInlineLinkTail = (content, md) => {
1805
- if (!content || content.charCodeAt(0) !== CHAR_OPEN_PAREN) return null
1806
- const max = content.length
1807
- let pos = 1
1808
- while (pos < max) {
1809
- const code = content.charCodeAt(pos)
1810
- if (!isSpace(code) && code !== 0x0A) break
1811
- pos++
1812
- }
1813
- if (pos >= max) return null
1814
-
1815
- let href = ''
1816
- let destPos = pos
1817
- if (pos < max && content.charCodeAt(pos) === CHAR_CLOSE_PAREN) {
1818
- href = ''
1819
- } else {
1820
- const dest = parseLinkDestination(content, pos, max)
1821
- if (!dest.ok) return null
1822
- href = md.normalizeLink(dest.str)
1823
- if (!md.validateLink(href)) {
1824
- return null
1825
- }
1826
- pos = dest.pos
1827
- destPos = dest.pos
1828
- }
1829
-
1830
- while (pos < max) {
1831
- const code = content.charCodeAt(pos)
1832
- if (!isSpace(code) && code !== 0x0A) break
1833
- pos++
1834
- }
1835
-
1836
- let title = ''
1837
- const titleRes = parseLinkTitle(content, pos, max)
1838
- if (pos < max && pos !== destPos && titleRes.ok) {
1839
- title = titleRes.str
1840
- pos = titleRes.pos
1841
- while (pos < max) {
1842
- const code = content.charCodeAt(pos)
1843
- if (!isSpace(code) && code !== 0x0A) break
1844
- pos++
1845
- }
1846
- }
1847
-
1848
- if (pos >= max || content.charCodeAt(pos) !== CHAR_CLOSE_PAREN) {
1849
- return null
1850
- }
1851
- pos++
1852
- return { href, title, consumed: pos }
1853
- }
1854
-
1855
- const INLINE_LINK_BRACKET_SPLIT_OPTIONS = { splitEmptyPair: true }
1856
-
1857
- const removeGhostLabelText = (tokens, linkCloseIndex, labelText) => {
1858
- if (!labelText) return
1859
- if (linkCloseIndex === null || linkCloseIndex === undefined) return
1860
- if (linkCloseIndex < 0 || linkCloseIndex >= tokens.length) return
1861
- const closeToken = tokens[linkCloseIndex]
1862
- if (!closeToken || closeToken.type !== 'link_close') return
1863
- let idx = linkCloseIndex + 1
1864
- while (idx < tokens.length) {
1865
- const token = tokens[idx]
1866
- if (!token) {
1867
- idx++
1868
- continue
1869
- }
1870
- if (token.type === 'text') {
1871
- if (token.content.startsWith(labelText)) {
1872
- if (token.content.length === labelText.length) {
1873
- tokens.splice(idx, 1)
1874
- } else {
1875
- token.content = token.content.slice(labelText.length)
1876
- }
1877
- }
1878
- break
1879
- }
1880
- if (!/_close$/.test(token.type)) {
1881
- break
1882
- }
1883
- idx++
1884
- }
1885
- }
1886
-
1887
- const restoreLabelWhitespace = (tokens, labelSources) => {
1888
- if (!tokens || !labelSources || labelSources.length === 0) return
1889
- let labelIdx = 0
1890
- for (let i = 0; i < tokens.length && labelIdx < labelSources.length; i++) {
1891
- if (tokens[i].type !== 'link_open') continue
1892
- const closeIdx = findLinkCloseIndex(tokens, i)
1893
- if (closeIdx === -1) continue
1894
- const labelSource = labelSources[labelIdx] || ''
1895
- if (!labelSource) {
1896
- labelIdx++
1897
- continue
1898
- }
1899
- let cursor = 0
1900
- for (let pos = i + 1; pos < closeIdx; pos++) {
1901
- const t = tokens[pos]
1902
- const markup = t.markup || ''
1903
- const text = t.content || ''
1904
- const startPos = cursor
1905
- if (t.type === 'text') {
1906
- cursor += text.length
1907
- } else if (t.type === 'code_inline') {
1908
- cursor += markup.length + text.length + markup.length
1909
- } else if (markup) {
1910
- cursor += markup.length
1911
- }
1912
- if ((t.type === 'strong_open' || t.type === 'em_open') && startPos > 0) {
1913
- const prevToken = tokens[pos - 1]
1914
- if (prevToken && prevToken.type === 'text' && prevToken.content && !prevToken.content.endsWith(' ')) {
1915
- const hasSpaceBefore = startPos - 1 >= 0 && startPos - 1 < labelSource.length && labelSource[startPos - 1] === ' '
1916
- const hasSpaceAt = startPos >= 0 && startPos < labelSource.length && labelSource[startPos] === ' '
1917
- if (hasSpaceBefore || hasSpaceAt) {
1918
- prevToken.content += ' '
1919
- }
1920
- }
1921
- }
1922
- }
1923
- labelIdx++
1924
- }
1925
- }
1926
-
1927
- const convertInlineLinks = (tokens, state) => {
1928
- if (!tokens || tokens.length === 0) return
1929
- let labelSources = tokens.__strongJaInlineLabelSources
1930
- if ((!labelSources || labelSources.length === 0) && state && state.env && Array.isArray(state.env.__strongJaInlineLabelSourceList) && state.env.__strongJaInlineLabelSourceList.length > 0) {
1931
- labelSources = state.env.__strongJaInlineLabelSourceList.shift()
1932
- }
1933
- let labelSourceIndex = tokens.__strongJaInlineLabelIndex || 0
1934
- let i = 0
1935
- while (i < tokens.length) {
1936
- if (splitBracketToken(tokens, i, INLINE_LINK_BRACKET_SPLIT_OPTIONS)) {
1937
- continue
1938
- }
1939
- if (!isBracketToken(tokens[i], '[')) {
1940
- i++
1941
- continue
1942
- }
1943
- let closeIdx = i + 1
1944
- let invalid = false
1945
- while (closeIdx < tokens.length && !isBracketToken(tokens[closeIdx], ']')) {
1946
- if (splitBracketToken(tokens, closeIdx, INLINE_LINK_BRACKET_SPLIT_OPTIONS)) {
1947
- continue
1948
- }
1949
- if (tokens[closeIdx].type === 'link_open') {
1950
- invalid = true
1951
- break
1952
- }
1953
- closeIdx++
1954
- }
1955
- if (invalid || closeIdx >= tokens.length) {
1956
- i++
1957
- continue
1958
- }
1959
- const currentLabelSource = labelSources && labelSourceIndex < labelSources.length
1960
- ? labelSources[labelSourceIndex]
1961
- : undefined
1962
-
1963
- const labelLength = closeIdx - i - 1
1964
- const needsPlaceholder = labelLength <= 0
1965
- if (needsPlaceholder && !currentLabelSource) {
1966
- i++
1967
- continue
1968
- }
1969
-
1970
- let tailIdx = closeIdx + 1
1971
- let tailContent = ''
1972
- let parsedTail = null
1973
- let tailHasCloseParen = false
1974
- while (tailIdx < tokens.length) {
1975
- if (splitBracketToken(tokens, tailIdx, INLINE_LINK_BRACKET_SPLIT_OPTIONS)) {
1976
- continue
1977
- }
1978
- const tailToken = tokens[tailIdx]
1979
- if (tailToken.type !== 'text' || !tailToken.content) {
1980
- break
1981
- }
1982
- tailContent += tailToken.content
1983
- if (!tailHasCloseParen) {
1984
- if (tailToken.content.indexOf(')') === -1) {
1985
- tailIdx++
1986
- continue
1987
- }
1988
- tailHasCloseParen = true
1989
- }
1990
- parsedTail = parseInlineLinkTail(tailContent, state.md)
1991
- if (parsedTail) break
1992
- tailIdx++
1993
- }
1994
-
1995
- if (!parsedTail) {
1996
- i++
1997
- continue
1998
- }
1999
-
2000
- if (!consumeCharactersFromTokens(tokens, closeIdx + 1, parsedTail.consumed)) {
2001
- i++
2002
- continue
2003
- }
2004
-
2005
- tokens.splice(closeIdx, 1)
2006
- tokens.splice(i, 1)
2007
-
2008
- const linkOpenToken = new Token('link_open', 'a', 1)
2009
- linkOpenToken.attrs = [['href', parsedTail.href]]
2010
- if (parsedTail.title) linkOpenToken.attrPush(['title', parsedTail.title])
2011
- linkOpenToken.markup = '[]()'
2012
- linkOpenToken.info = 'auto'
2013
- const linkCloseToken = new Token('link_close', 'a', -1)
2014
- linkCloseToken.markup = '[]()'
2015
- linkCloseToken.info = 'auto'
2016
-
2017
- const nextIndex = wrapLabelTokensWithLink(tokens, i, i + labelLength - 1, linkOpenToken, linkCloseToken, currentLabelSource)
2018
- if (nextIndex === i) {
2019
- i++
2020
- continue
2021
- }
2022
- if (currentLabelSource) {
2023
- const linkCloseIdx = findLinkCloseIndex(tokens, i)
2024
- if (linkCloseIdx !== -1) {
2025
- let cursor = 0
2026
- for (let pos = i + 1; pos < linkCloseIdx; pos++) {
2027
- const t = tokens[pos]
2028
- const markup = t.markup || ''
2029
- const text = t.content || ''
2030
- const startPos = cursor
2031
- if (t.type === 'text') {
2032
- cursor += text.length
2033
- } else if (t.type === 'code_inline') {
2034
- cursor += markup.length + text.length + markup.length
2035
- } else if (markup) {
2036
- cursor += markup.length
2037
- }
2038
- if ((t.type === 'strong_open' || t.type === 'em_open') && startPos > 0) {
2039
- const prevToken = tokens[pos - 1]
2040
- if (prevToken && prevToken.type === 'text' && prevToken.content && !prevToken.content.endsWith(' ')) {
2041
- const labelHasSpaceBefore = startPos - 1 >= 0 && startPos - 1 < currentLabelSource.length && currentLabelSource[startPos - 1] === ' '
2042
- const labelHasSpaceAt = startPos >= 0 && startPos < currentLabelSource.length && currentLabelSource[startPos] === ' '
2043
- if (labelHasSpaceBefore || labelHasSpaceAt) {
2044
- prevToken.content += ' '
2045
- }
2046
- }
2047
- }
2048
- }
2049
- }
2050
- }
2051
- if (needsPlaceholder && currentLabelSource) {
2052
- removeGhostLabelText(tokens, nextIndex - 1, currentLabelSource)
2053
- }
2054
-
2055
- if (labelSources && labelSources.length > 0) {
2056
- if (labelSourceIndex < labelSources.length) {
2057
- labelSourceIndex++
2058
- }
2059
- }
2060
- i = nextIndex
2061
- }
2062
- if (labelSources) {
2063
- tokens.__strongJaInlineLabelIndex = labelSourceIndex
2064
- }
2065
- }
2066
-
2067
- const convertCollapsedReferenceLinks = (tokens, state) => {
2068
- const references = state.env && state.env.references
2069
- if (!references) return
2070
- const referenceCount = state.__strongJaReferenceCount
2071
- if (referenceCount !== undefined) {
2072
- if (referenceCount === 0) return
2073
- } else if (Object.keys(references).length === 0) {
2074
- return
2075
- }
2076
-
2077
- let i = 0
2078
- while (i < tokens.length) {
2079
- if (splitBracketToken(tokens, i)) {
2080
- continue
2081
- }
2082
- if (!isBracketToken(tokens[i], '[')) {
2083
- i++
2084
- continue
2085
- }
2086
- let closeIdx = i + 1
2087
- while (closeIdx < tokens.length && !isBracketToken(tokens[closeIdx], ']')) {
2088
- if (splitBracketToken(tokens, closeIdx)) {
2089
- continue
2090
- }
2091
- if (tokens[closeIdx].type === 'link_open') {
2092
- closeIdx = -1
2093
- break
2094
- }
2095
- closeIdx++
2096
- }
2097
- if (closeIdx === -1 || closeIdx >= tokens.length) {
2098
- i++
2099
- continue
2100
- }
2101
-
2102
- if (closeIdx === i + 1) {
2103
- i++
2104
- continue
2105
- }
2106
-
2107
- const labelStart = i + 1
2108
- const labelEnd = closeIdx - 1
2109
- const labelLength = closeIdx - i - 1
2110
- const labelText = buildReferenceLabelRange(tokens, labelStart, labelEnd)
2111
- const cleanedLabel = cleanLabelText(labelText)
2112
- const whitespaceStart = closeIdx + 1
2113
- let refRemoveStart = whitespaceStart
2114
- while (refRemoveStart < tokens.length && isWhitespaceToken(tokens[refRemoveStart])) {
2115
- refRemoveStart++
2116
- }
2117
- if (splitBracketToken(tokens, refRemoveStart)) {
2118
- continue
2119
- }
2120
- const whitespaceCount = refRemoveStart - whitespaceStart
2121
- let refKey = null
2122
- let refRemoveCount = 0
2123
- let existingLinkOpen = null
2124
- let existingLinkClose = null
2125
- const nextToken = tokens[refRemoveStart]
2126
- if (isBracketToken(nextToken, '[]')) {
2127
- refKey = normalizeReferenceCandidate(state, cleanedLabel)
2128
- refRemoveCount = 1
2129
- } else if (isBracketToken(nextToken, '[')) {
2130
- let refCloseIdx = refRemoveStart + 1
2131
- while (refCloseIdx < tokens.length && !isBracketToken(tokens[refCloseIdx], ']')) {
2132
- refCloseIdx++
2133
- }
2134
- if (refCloseIdx >= tokens.length) {
2135
- i++
2136
- continue
2137
- }
2138
- const refStart = refRemoveStart + 1
2139
- const refEnd = refCloseIdx - 1
2140
- if (refStart > refEnd) {
2141
- refKey = normalizeReferenceCandidate(state, cleanedLabel)
2142
- } else {
2143
- const refLabelText = buildReferenceLabelRange(tokens, refStart, refEnd)
2144
- refKey = normalizeReferenceCandidate(state, refLabelText)
2145
- }
2146
- refRemoveCount = refCloseIdx - refRemoveStart + 1
2147
- } else if (nextToken && nextToken.type === 'link_open') {
2148
- const linkCloseIdx = findLinkCloseIndex(tokens, refRemoveStart)
2149
- if (linkCloseIdx === -1) {
2150
- i++
2151
- continue
2152
- }
2153
- existingLinkOpen = tokens[refRemoveStart]
2154
- existingLinkClose = tokens[linkCloseIdx]
2155
- refRemoveCount = linkCloseIdx - refRemoveStart + 1
2156
- } else {
2157
- i++
2158
- continue
2159
- }
2160
- let linkOpenToken = null
2161
- let linkCloseToken = null
2162
- if (existingLinkOpen && existingLinkClose) {
2163
- if (whitespaceCount > 0) {
2164
- tokens.splice(whitespaceStart, whitespaceCount)
2165
- refRemoveStart -= whitespaceCount
2166
- }
2167
- if (refRemoveCount > 0) {
2168
- tokens.splice(refRemoveStart, refRemoveCount)
2169
- }
2170
- linkOpenToken = existingLinkOpen
2171
- linkCloseToken = existingLinkClose
2172
- } else {
2173
- if (!refKey) {
2174
- i++
2175
- continue
2176
- }
2177
- const ref = references[refKey]
2178
- if (!ref) {
2179
- i++
2180
- continue
2181
- }
2182
- if (whitespaceCount > 0) {
2183
- tokens.splice(whitespaceStart, whitespaceCount)
2184
- refRemoveStart -= whitespaceCount
2185
- }
2186
- if (refRemoveCount > 0) {
2187
- tokens.splice(refRemoveStart, refRemoveCount)
2188
- }
2189
- linkOpenToken = new Token('link_open', 'a', 1)
2190
- linkOpenToken.attrs = [['href', ref.href]]
2191
- if (ref.title) linkOpenToken.attrPush(['title', ref.title])
2192
- linkOpenToken.markup = '[]'
2193
- linkOpenToken.info = 'auto'
2194
- linkCloseToken = new Token('link_close', 'a', -1)
2195
- linkCloseToken.markup = '[]'
2196
- linkCloseToken.info = 'auto'
2197
- }
2198
- tokens.splice(closeIdx, 1)
2199
- tokens.splice(i, 1)
2200
-
2201
- const nextIndex = wrapLabelTokensWithLink(tokens, i, i + labelLength - 1, linkOpenToken, linkCloseToken)
2202
- i = nextIndex
2203
- }
2204
- }
2205
-
2206
- // Link cleanup helpers
2207
- const mergeBrokenMarksAroundLinks = (tokens) => {
2208
- let i = 0
2209
- while (i < tokens.length) {
2210
- const closeToken = tokens[i]
2211
- if (!closeToken || !/_close$/.test(closeToken.type)) {
2212
- i++
2213
- continue
2214
- }
2215
- const openType = closeToken.type.replace('_close', '_open')
2216
- let j = i + 1
2217
- while (j < tokens.length && isWhitespaceToken(tokens[j])) j++
2218
- if (j >= tokens.length || tokens[j].type !== 'link_open') {
2219
- i++
2220
- continue
2221
- }
2222
- let linkDepth = 1
2223
- j++
2224
- while (j < tokens.length && linkDepth > 0) {
2225
- if (tokens[j].type === 'link_open') linkDepth++
2226
- if (tokens[j].type === 'link_close') linkDepth--
2227
- j++
2228
- }
2229
- if (linkDepth !== 0) {
2230
- i++
2231
- continue
2232
- }
2233
- while (j < tokens.length && isWhitespaceToken(tokens[j])) j++
2234
- if (j >= tokens.length) {
2235
- i++
2236
- continue
2237
- }
2238
- const reopenToken = tokens[j]
2239
- if (reopenToken.type !== openType || reopenToken.level !== closeToken.level) {
2240
- i++
2241
- continue
2242
- }
2243
- tokens.splice(j, 1)
2244
- tokens.splice(i, 1)
2245
- }
2246
- }
2247
-
2248
-
2249
28
  const mditStrongJa = (md, option) => {
29
+ if (option && typeof option.engine === 'string' && option.engine !== 'token') {
30
+ throw new Error('mditStrongJa: legacy engine was removed; use token (default)')
31
+ }
2250
32
  const opt = {
2251
- dollarMath: true, //inline math $...$
2252
- mditAttrs: true, //markdown-it-attrs
2253
- mdBreaks: md.options.breaks,
2254
- disallowMixed: false, //Non-Japanese text handling
2255
- mode: 'japanese-only', // 'japanese-only' | 'aggressive' | 'compatible'
2256
- coreRulesBeforePostprocess: [] // e.g. ['cjk_breaks'] when CJK line-break plugins are active
33
+ mditAttrs: true, // assume markdown-it-attrs integration by default
34
+ mdBreaks: md.options.breaks, // inherit md.options.breaks for compat handling
35
+ mode: 'japanese', // 'japanese' | 'aggressive' | 'compatible' (pairing behavior)
36
+ coreRulesBeforePostprocess: [], // e.g. ['cjk_breaks'] to keep rules ahead of postprocess
37
+ postprocess: true, // enable link/ref reconstruction pass
38
+ patchCorePush: true // keep restore-softbreaks after late cjk_breaks
2257
39
  }
2258
40
  if (option) Object.assign(opt, option)
2259
41
  opt.hasCjkBreaks = hasCjkBreaksRule(md)
2260
- const rawCoreRules = opt.coreRulesBeforePostprocess
2261
- const hasCoreRuleConfig = Array.isArray(rawCoreRules)
2262
- ? rawCoreRules.length > 0
2263
- : !!rawCoreRules
2264
- const coreRulesBeforePostprocess = hasCoreRuleConfig
2265
- ? normalizeCoreRulesBeforePostprocess(rawCoreRules)
2266
- : []
2267
-
2268
- md.inline.ruler.before('emphasis', 'strong_ja', (state, silent) => {
2269
- return strongJa(state, silent, opt)
2270
- })
2271
42
 
2272
- // Trim trailing spaces that remain after markdown-it-attrs strips `{...}`
2273
- // Trim trailing spaces only at the very end of inline content (after attrs/core rules have run).
2274
- const trimInlineTrailingSpaces = (state) => {
2275
- if (!state || !state.tokens) return
2276
- for (let i = 0; i < state.tokens.length; i++) {
2277
- const token = state.tokens[i]
2278
- if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) continue
2279
- let idx = token.children.length - 1
2280
- while (idx >= 0 && (!token.children[idx] || (token.children[idx].type === 'text' && token.children[idx].content === ''))) {
2281
- idx--
2282
- }
2283
- if (idx < 0) continue
2284
- const tail = token.children[idx]
2285
- if (!tail || tail.type !== 'text' || !tail.content) continue
2286
- const trimmed = tail.content.replace(/[ \t]+$/, '')
2287
- if (trimmed !== tail.content) {
2288
- tail.content = trimmed
2289
- }
2290
- }
2291
- }
2292
- const hasTextJoinRule = Array.isArray(md.core?.ruler?.__rules__)
2293
- ? md.core.ruler.__rules__.some((rule) => rule && rule.name === 'text_join')
2294
- : false
2295
- if (hasTextJoinRule) {
2296
- md.core.ruler.after('text_join', 'strong_ja_trim_trailing_spaces', trimInlineTrailingSpaces)
2297
- } else {
2298
- md.core.ruler.after('inline', 'strong_ja_trim_trailing_spaces', trimInlineTrailingSpaces)
2299
- }
2300
-
2301
- const normalizeSoftbreakSpacing = (state) => {
2302
- if (!state || opt.hasCjkBreaks !== true) return
2303
- if (!state.tokens || state.tokens.length === 0) return
2304
- for (let i = 0; i < state.tokens.length; i++) {
2305
- const token = state.tokens[i]
2306
- if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) continue
2307
- for (let j = 0; j < token.children.length; j++) {
2308
- const child = token.children[j]
2309
- if (!child || child.type !== 'text' || !child.content) continue
2310
- if (child.content.indexOf('\n') === -1) continue
2311
- let normalized = ''
2312
- for (let idx = 0; idx < child.content.length; idx++) {
2313
- const ch = child.content[idx]
2314
- if (ch === '\n') {
2315
- const prevChar = idx > 0 ? child.content[idx - 1] : ''
2316
- const nextChar = idx + 1 < child.content.length ? child.content[idx + 1] : ''
2317
- const isAsciiWord = nextChar && nextChar >= '0' && nextChar <= 'z' && /[A-Za-z0-9]/.test(nextChar)
2318
- const shouldReplace = isAsciiWord && nextChar !== '{' && nextChar !== '\\' && isJapanese(prevChar) && !isJapanese(nextChar)
2319
- if (shouldReplace) {
2320
- normalized += ' '
2321
- continue
2322
- }
2323
- }
2324
- normalized += ch
2325
- }
2326
- if (normalized !== child.content) {
2327
- child.content = normalized
2328
- }
2329
- }
2330
- }
2331
- }
2332
- if (hasTextJoinRule) {
2333
- md.core.ruler.after('text_join', 'strong_ja_softbreak_spacing', normalizeSoftbreakSpacing)
2334
- } else {
2335
- md.core.ruler.after('inline', 'strong_ja_softbreak_spacing', normalizeSoftbreakSpacing)
2336
- }
43
+ md.__strongJaTokenOpt = opt
44
+ patchScanDelims(md)
45
+ registerTokenCompat(md, opt)
2337
46
 
2338
- const restoreSoftbreaksAfterCjk = (state) => {
2339
- if (!state) return
2340
- if (!state.md || state.md.__strongJaRestoreSoftbreaksForAttrs !== true) return
2341
- if (opt.hasCjkBreaks !== true) return
2342
- if (!state.tokens || state.tokens.length === 0) return
2343
- for (let i = 0; i < state.tokens.length; i++) {
2344
- const token = state.tokens[i]
2345
- if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) continue
2346
- const children = token.children
2347
- for (let j = 0; j < children.length; j++) {
2348
- const child = children[j]
2349
- if (!child || child.type !== 'text' || child.content !== '') continue
2350
- // Find previous non-empty text content to inspect the trailing character.
2351
- let prevChar = ''
2352
- for (let k = j - 1; k >= 0; k--) {
2353
- const prev = children[k]
2354
- if (prev && prev.type === 'text' && prev.content) {
2355
- prevChar = prev.content.charAt(prev.content.length - 1)
2356
- break
2357
- }
2358
- }
2359
- if (!prevChar || !isJapanese(prevChar)) continue
2360
- const next = children[j + 1]
2361
- if (!next || next.type !== 'text' || !next.content) continue
2362
- const nextChar = next.content.charAt(0)
2363
- if (nextChar !== '{') continue
2364
- child.type = 'softbreak'
2365
- child.tag = ''
2366
- child.content = '\n'
2367
- child.markup = ''
2368
- child.info = ''
2369
- }
2370
- }
47
+ if (!opt._skipPostprocess) {
48
+ registerTokenPostprocess(md, opt, getNoLinkMdInstance)
49
+ const rawCoreRules = opt.coreRulesBeforePostprocess
50
+ const hasCoreRuleConfig = Array.isArray(rawCoreRules)
51
+ ? rawCoreRules.length > 0
52
+ : !!rawCoreRules
53
+ const coreRulesBeforePostprocess = hasCoreRuleConfig
54
+ ? normalizeCoreRulesBeforePostprocess(rawCoreRules)
55
+ : []
56
+ ensureCoreRuleOrder(md, coreRulesBeforePostprocess, 'strong_ja_token_postprocess')
2371
57
  }
2372
58
 
2373
- const registerRestoreSoftbreaks = () => {
2374
- if (md.__strongJaRestoreRegistered) return
2375
- const anchorRule = hasTextJoinRule ? 'text_join' : 'inline'
2376
- const added = md.core.ruler.after(anchorRule, 'strong_ja_restore_softbreaks', restoreSoftbreaksAfterCjk)
2377
- if (added !== false) {
2378
- md.__strongJaRestoreRegistered = true
2379
- md.__strongJaRestoreSoftbreaksForAttrs = opt.mditAttrs === false
2380
- if (opt.hasCjkBreaks) {
2381
- moveRuleAfter(md.core.ruler, 'strong_ja_restore_softbreaks', 'cjk_breaks')
2382
- md.__strongJaRestoreReordered = true
2383
- }
2384
- if (!md.__strongJaPatchCorePush) {
2385
- md.__strongJaPatchCorePush = true
2386
- const originalPush = md.core.ruler.push.bind(md.core.ruler)
2387
- md.core.ruler.push = (name, fn, options) => {
2388
- const res = originalPush(name, fn, options)
2389
- if (name && name.indexOf && name.indexOf('cjk_breaks') !== -1) {
2390
- opt.hasCjkBreaks = true
2391
- moveRuleAfter(md.core.ruler, 'strong_ja_restore_softbreaks', name)
2392
- md.__strongJaRestoreReordered = true
2393
- }
2394
- return res
2395
- }
2396
- }
2397
- if (opt.hasCjkBreaks) {
2398
- moveRuleAfter(md.core.ruler, 'strong_ja_restore_softbreaks', 'cjk_breaks')
2399
- md.__strongJaRestoreReordered = true
2400
- }
2401
- }
2402
- }
2403
- registerRestoreSoftbreaks()
2404
-
2405
- md.core.ruler.after('inline', 'strong_ja_postprocess', (state) => {
2406
- const targets = state.env.__strongJaPostProcessTargets
2407
- if (!targets || targets.length === 0) return
2408
- for (const tokens of targets) {
2409
- if (!tokens || !tokens.length) continue
2410
- let hasBracketText = false
2411
- for (let i = 0; i < tokens.length; i++) {
2412
- const token = tokens[i]
2413
- if (!token || token.type !== 'text') continue
2414
- const content = token.content
2415
- if (!content) continue
2416
- if (content.indexOf('[') !== -1 || content.indexOf(']') !== -1) {
2417
- hasBracketText = true
2418
- break
2419
- }
2420
- }
2421
- if (!hasBracketText) {
2422
- delete tokens.__strongJaInlineLabelSources
2423
- delete tokens.__strongJaInlineLabelIndex
2424
- continue
2425
- }
2426
- convertInlineLinks(tokens, state)
2427
- convertCollapsedReferenceLinks(tokens, state)
2428
- mergeBrokenMarksAroundLinks(tokens)
2429
- delete tokens.__strongJaInlineLabelSources
2430
- delete tokens.__strongJaInlineLabelIndex
2431
- }
2432
- if (state.env && state.env.__strongJaInlineLabelSourceList) {
2433
- delete state.env.__strongJaInlineLabelSourceList
2434
- }
2435
- delete state.env.__strongJaPostProcessTargets
2436
- delete state.env.__strongJaPostProcessTargetSet
2437
- })
2438
-
2439
- if (coreRulesBeforePostprocess.length > 0) {
2440
- ensureCoreRuleOrder(md, coreRulesBeforePostprocess)
2441
- }
59
+ return md
2442
60
  }
2443
61
 
2444
62
  export default mditStrongJa
2445
-
2446
-
2447
- function normalizeCoreRulesBeforePostprocess(value) {
2448
- if (!value) return []
2449
- const list = Array.isArray(value) ? value : [value]
2450
- const normalized = []
2451
- const seen = new Set()
2452
- for (let idx = 0; idx < list.length; idx++) {
2453
- const raw = list[idx]
2454
- if (typeof raw !== 'string') continue
2455
- const trimmed = raw.trim()
2456
- if (!trimmed || seen.has(trimmed)) continue
2457
- seen.add(trimmed)
2458
- normalized.push(trimmed)
2459
- }
2460
- return normalized
2461
- }
2462
-
2463
-
2464
- function ensureCoreRuleOrder(md, ruleNames) {
2465
- if (!md || !md.core || !md.core.ruler) return
2466
- if (!ruleNames || ruleNames.length === 0) return
2467
- for (let idx = 0; idx < ruleNames.length; idx++) {
2468
- moveRuleBefore(md.core.ruler, ruleNames[idx], 'strong_ja_postprocess')
2469
- }
2470
- }
2471
-
2472
-
2473
- function moveRuleBefore(ruler, ruleName, beforeName) {
2474
- if (!ruler || !ruler.__rules__) return
2475
- const rules = ruler.__rules__
2476
- let fromIdx = -1
2477
- let beforeIdx = -1
2478
- for (let idx = 0; idx < rules.length; idx++) {
2479
- if (rules[idx].name === ruleName) fromIdx = idx
2480
- if (rules[idx].name === beforeName) beforeIdx = idx
2481
- if (fromIdx !== -1 && beforeIdx !== -1) break
2482
- }
2483
- if (fromIdx === -1 || beforeIdx === -1 || fromIdx < beforeIdx) return
2484
-
2485
- const rule = rules.splice(fromIdx, 1)[0]
2486
- rules.splice(beforeIdx, 0, rule)
2487
- ruler.__cache__ = null
2488
- }
2489
-
2490
- function moveRuleAfter(ruler, ruleName, afterName) {
2491
- if (!ruler || !ruler.__rules__) return
2492
- const rules = ruler.__rules__
2493
- let fromIdx = -1
2494
- let afterIdx = -1
2495
- for (let idx = 0; idx < rules.length; idx++) {
2496
- if (rules[idx].name === ruleName) fromIdx = idx
2497
- if (rules[idx].name === afterName) afterIdx = idx
2498
- if (fromIdx !== -1 && afterIdx !== -1) break
2499
- }
2500
- if (fromIdx === -1 || afterIdx === -1 || fromIdx === afterIdx + 1) return
2501
-
2502
- const rule = rules.splice(fromIdx, 1)[0]
2503
- const targetIdx = fromIdx < afterIdx ? afterIdx - 1 : afterIdx
2504
- rules.splice(targetIdx + 1, 0, rule)
2505
- ruler.__cache__ = null
2506
- }