@peaceroad/markdown-it-strong-ja 0.8.0 → 0.8.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.
@@ -1,5 +1,6 @@
1
1
  import Token from 'markdown-it/lib/token.mjs'
2
2
  import { isWhiteSpace } from 'markdown-it/lib/common/utils.mjs'
3
+ import { getReferenceCount } from './token-utils.js'
3
4
 
4
5
  const CHAR_OPEN_BRACKET = 0x5B // [
5
6
  const CHAR_CLOSE_BRACKET = 0x5D // ]
@@ -28,6 +29,26 @@ const isWhitespaceToken = (token) => {
28
29
  return isWhitespace
29
30
  }
30
31
 
32
+ const hasReferenceLabelMarkerRange = (tokens, startIdx, endIdx) => {
33
+ if (startIdx > endIdx) return false
34
+ for (let idx = startIdx; idx <= endIdx; idx++) {
35
+ const token = tokens[idx]
36
+ if (!token || !token.type) continue
37
+ if (token.type === 'text' || token.type === 'code_inline') {
38
+ const content = token.content
39
+ if (content && (content.indexOf('*') !== -1 || content.indexOf('_') !== -1)) return true
40
+ continue
41
+ }
42
+ if (token.type === 'softbreak' || token.type === 'hardbreak') continue
43
+ if (token.markup &&
44
+ (token.type.endsWith('_open') || token.type.endsWith('_close')) &&
45
+ (token.markup.indexOf('*') !== -1 || token.markup.indexOf('_') !== -1)) {
46
+ return true
47
+ }
48
+ }
49
+ return false
50
+ }
51
+
31
52
  const buildReferenceLabelRange = (tokens, startIdx, endIdx) => {
32
53
  if (startIdx > endIdx) return ''
33
54
  let label = ''
@@ -58,11 +79,6 @@ const getNormalizeRef = (state) => {
58
79
  return normalize
59
80
  }
60
81
 
61
- const adjustTokenLevels = (tokens, startIdx, endIdx, delta) => {
62
- for (let i = startIdx; i < endIdx; i++) {
63
- if (tokens[i]) tokens[i].level += delta
64
- }
65
- }
66
82
 
67
83
  const cloneMap = (map) => {
68
84
  if (!map || !Array.isArray(map)) return null
@@ -109,7 +125,7 @@ const applyBracketSegmentFlags = (token, seg) => {
109
125
  token.__strongJaBracketAtomic = true
110
126
  } else if (seg === '[]') {
111
127
  token.__strongJaHasBracket = true
112
- token.__strongJaBracketAtomic = false
128
+ token.__strongJaBracketAtomic = true
113
129
  } else {
114
130
  token.__strongJaHasBracket = false
115
131
  token.__strongJaBracketAtomic = false
@@ -204,13 +220,15 @@ const buildLinkCloseMap = (tokens, startIdx, endIdx) => {
204
220
  return closeMap
205
221
  }
206
222
 
207
- const wrapLabelTokensWithLink = (tokens, labelStartIdx, labelEndIdx, linkOpenToken, linkCloseToken) => {
223
+ const collectWrappedLabelPairs = (tokens, collapsedStartIdx, collapsedEndIdx) => {
208
224
  const wrapperPairs = []
209
- let startIdx = labelStartIdx
210
- let endIdx = labelEndIdx
211
- while (startIdx > 0) {
212
- const prevToken = tokens[startIdx - 1]
213
- const nextToken = tokens[endIdx + 1]
225
+ while (true) {
226
+ const wrapperOffset = wrapperPairs.length
227
+ const closeIdx = collapsedStartIdx - 1 - wrapperOffset
228
+ const openIdx = collapsedEndIdx + 1 + wrapperOffset
229
+ if (closeIdx < 0 || openIdx >= tokens.length) break
230
+ const prevToken = tokens[closeIdx]
231
+ const nextToken = tokens[openIdx]
214
232
  if (!prevToken || !nextToken) break
215
233
  if (!prevToken.type || !prevToken.type.endsWith('_close')) break
216
234
  const expectedOpen = prevToken.type.replace('_close', '_open')
@@ -220,214 +238,363 @@ const wrapLabelTokensWithLink = (tokens, labelStartIdx, labelEndIdx, linkOpenTok
220
238
  tag: prevToken.tag,
221
239
  markup: prevToken.markup,
222
240
  openMap: cloneMap(nextToken.map),
223
- closeMap: cloneMap(prevToken.map)
241
+ closeMap: cloneMap(prevToken.map),
242
+ closeIdx,
243
+ openIdx
224
244
  })
225
- tokens.splice(endIdx + 1, 1)
226
- tokens.splice(startIdx - 1, 1)
227
- startIdx -= 1
228
- endIdx -= 1
229
245
  }
246
+ return wrapperPairs
247
+ }
248
+
249
+ const resolveWrappedLabelReplaceRange = (wrapperPairs, collapsedStartIdx, collapsedEndIdx) => {
250
+ if (wrapperPairs.length === 0) {
251
+ return {
252
+ replaceStart: collapsedStartIdx,
253
+ replaceEnd: collapsedEndIdx
254
+ }
255
+ }
256
+ const outerPair = wrapperPairs[wrapperPairs.length - 1]
257
+ return {
258
+ replaceStart: outerPair.closeIdx,
259
+ replaceEnd: outerPair.openIdx
260
+ }
261
+ }
230
262
 
231
- if (startIdx > endIdx) return startIdx
263
+ const resolveInsertedWrapperMap = (pairMap, labelMap) => {
264
+ return pairMap || labelMap
265
+ }
232
266
 
233
- let labelLength = endIdx - startIdx + 1
234
- const firstLabelToken = tokens[startIdx]
267
+ const buildWrappedLabelReplacement = (labelTokens, linkOpenToken, linkCloseToken, wrapperPairs, labelMap) => {
268
+ const firstLabelToken = labelTokens[0]
235
269
  const linkLevel = firstLabelToken ? Math.max(firstLabelToken.level - 1, 0) : 0
236
270
  linkOpenToken.level = linkLevel
237
271
  linkCloseToken.level = linkLevel
238
- const labelMap = getMapFromTokenRange(tokens, startIdx, endIdx) || getNearbyMap(tokens, startIdx, endIdx)
239
272
  if (labelMap) {
240
273
  if (!linkOpenToken.map) linkOpenToken.map = cloneMap(labelMap)
241
274
  if (!linkCloseToken.map) linkCloseToken.map = cloneMap(labelMap)
242
275
  }
243
- tokens.splice(startIdx, 0, linkOpenToken)
244
- tokens.splice(startIdx + labelLength + 1, 0, linkCloseToken)
245
-
246
- adjustTokenLevels(tokens, startIdx + 1, startIdx + labelLength + 1, 1)
247
-
248
- if (wrapperPairs.length > 0) {
249
- let insertIdx = startIdx + 1
250
- for (let wp = 0; wp < wrapperPairs.length; wp++) {
251
- const pair = wrapperPairs[wp]
252
- const innerOpen = new Token(pair.base + '_open', pair.tag, 1)
253
- innerOpen.markup = pair.markup
254
- innerOpen.level = linkLevel + 1 + wp
255
- if (pair.openMap && !innerOpen.map) innerOpen.map = cloneMap(pair.openMap)
256
- tokens.splice(insertIdx, 0, innerOpen)
257
- insertIdx++
258
- labelLength++
276
+ for (let idx = 0; idx < labelTokens.length; idx++) {
277
+ if (labelTokens[idx]) labelTokens[idx].level += 1
278
+ }
279
+
280
+ const replacement = [linkOpenToken]
281
+ for (let wp = 0; wp < wrapperPairs.length; wp++) {
282
+ const pair = wrapperPairs[wp]
283
+ const innerOpen = new Token(pair.base + '_open', pair.tag, 1)
284
+ innerOpen.markup = pair.markup
285
+ innerOpen.level = linkLevel + 1 + wp
286
+ const openMap = resolveInsertedWrapperMap(pair.openMap, labelMap)
287
+ if (openMap && !innerOpen.map) innerOpen.map = cloneMap(openMap)
288
+ replacement.push(innerOpen)
289
+ }
290
+ replacement.push(...labelTokens)
291
+ for (let wp = 0; wp < wrapperPairs.length; wp++) {
292
+ const pair = wrapperPairs[wp]
293
+ const innerClose = new Token(pair.base + '_close', pair.tag, -1)
294
+ innerClose.markup = pair.markup
295
+ innerClose.level = linkLevel + 1 + wp
296
+ const closeMap = resolveInsertedWrapperMap(pair.closeMap, labelMap)
297
+ if (closeMap && !innerClose.map) innerClose.map = cloneMap(closeMap)
298
+ replacement.push(innerClose)
299
+ }
300
+ replacement.push(linkCloseToken)
301
+ return replacement
302
+ }
303
+
304
+ const wrapLabelTokensWithLink = (
305
+ tokens,
306
+ collapsedStartIdx,
307
+ collapsedEndIdx,
308
+ labelStartIdx,
309
+ labelEndIdx,
310
+ linkOpenToken,
311
+ linkCloseToken
312
+ ) => {
313
+ if (labelStartIdx > labelEndIdx) return collapsedStartIdx
314
+ const labelTokens = tokens.slice(labelStartIdx, labelEndIdx + 1)
315
+ const wrapperPairs = collectWrappedLabelPairs(tokens, collapsedStartIdx, collapsedEndIdx)
316
+ const { replaceStart, replaceEnd } = resolveWrappedLabelReplaceRange(
317
+ wrapperPairs,
318
+ collapsedStartIdx,
319
+ collapsedEndIdx
320
+ )
321
+ const labelMap = getMapFromTokenRange(tokens, labelStartIdx, labelEndIdx) || getNearbyMap(tokens, replaceStart, replaceEnd)
322
+ const replacement = buildWrappedLabelReplacement(
323
+ labelTokens,
324
+ linkOpenToken,
325
+ linkCloseToken,
326
+ wrapperPairs,
327
+ labelMap
328
+ )
329
+ tokens.splice(replaceStart, replaceEnd - replaceStart + 1, ...replacement)
330
+ return replaceStart + replacement.length
331
+ }
332
+
333
+ const resolveCollapsedReferenceTarget = (
334
+ tokens,
335
+ state,
336
+ refRemoveStart,
337
+ getLabelText,
338
+ getLinkCloseMap
339
+ ) => {
340
+ let refKey = null
341
+ let refRemoveCount = 0
342
+ let existingLinkOpen = null
343
+ let existingLinkClose = null
344
+ const nextToken = tokens[refRemoveStart]
345
+ if (isBracketToken(nextToken, '[]')) {
346
+ refKey = normalizeReferenceCandidate(state, getLabelText())
347
+ refRemoveCount = 1
348
+ } else if (isBracketToken(nextToken, '[')) {
349
+ let refCloseIdx = refRemoveStart + 1
350
+ while (refCloseIdx < tokens.length && !isBracketToken(tokens[refCloseIdx], ']')) {
351
+ refCloseIdx++
259
352
  }
260
- let linkClosePos = startIdx + labelLength + 1
261
- for (let wp = wrapperPairs.length - 1; wp >= 0; wp--) {
262
- const pair = wrapperPairs[wp]
263
- const innerClose = new Token(pair.base + '_close', pair.tag, -1)
264
- innerClose.markup = pair.markup
265
- innerClose.level = linkLevel + 1 + wp
266
- if (pair.closeMap && !innerClose.map) innerClose.map = cloneMap(pair.closeMap)
267
- tokens.splice(linkClosePos, 0, innerClose)
268
- labelLength++
353
+ if (refCloseIdx >= tokens.length) return null
354
+ const refStart = refRemoveStart + 1
355
+ const refEnd = refCloseIdx - 1
356
+ if (refStart > refEnd) {
357
+ refKey = normalizeReferenceCandidate(state, getLabelText())
358
+ } else {
359
+ const refLabelText = buildReferenceLabelRange(tokens, refStart, refEnd)
360
+ refKey = normalizeReferenceCandidate(state, refLabelText)
269
361
  }
362
+ refRemoveCount = refCloseIdx - refRemoveStart + 1
363
+ } else if (nextToken && nextToken.type === 'link_open') {
364
+ const linkCloseMap = getLinkCloseMap(refRemoveStart)
365
+ const linkCloseIdx = linkCloseMap.get(refRemoveStart) ?? -1
366
+ if (linkCloseIdx === -1) return null
367
+ existingLinkOpen = tokens[refRemoveStart]
368
+ existingLinkClose = tokens[linkCloseIdx]
369
+ refRemoveCount = linkCloseIdx - refRemoveStart + 1
370
+ } else {
371
+ return null
270
372
  }
373
+ return {
374
+ refKey,
375
+ refRemoveCount,
376
+ existingLinkOpen,
377
+ existingLinkClose
378
+ }
379
+ }
271
380
 
272
- return startIdx + labelLength + 2
381
+ const buildAutoCollapsedReferenceLinkPair = (ref) => {
382
+ if (!ref) return null
383
+ const linkOpenToken = new Token('link_open', 'a', 1)
384
+ linkOpenToken.attrs = [['href', ref.href]]
385
+ if (ref.title) linkOpenToken.attrPush(['title', ref.title])
386
+ linkOpenToken.markup = '[]'
387
+ linkOpenToken.info = 'auto'
388
+
389
+ const linkCloseToken = new Token('link_close', 'a', -1)
390
+ linkCloseToken.markup = '[]'
391
+ linkCloseToken.info = 'auto'
392
+ return { linkOpenToken, linkCloseToken }
273
393
  }
274
394
 
275
- const convertCollapsedReferenceLinks = (tokens, state) => {
276
- const references = state.env && state.env.references
277
- if (!references) return
278
- let referenceCount = state.__strongJaReferenceCount
279
- if (referenceCount === undefined) {
280
- referenceCount = Object.keys(references).length
281
- state.__strongJaReferenceCount = referenceCount
282
- }
283
- if (referenceCount === 0) {
284
- return
395
+ const resolveCollapsedReferenceLinkPair = (references, target) => {
396
+ if (!target) return null
397
+ if (target.existingLinkOpen && target.existingLinkClose) {
398
+ return {
399
+ linkOpenToken: target.existingLinkOpen,
400
+ linkCloseToken: target.existingLinkClose
401
+ }
285
402
  }
403
+ if (!target.refKey) return null
404
+ return buildAutoCollapsedReferenceLinkPair(references[target.refKey])
405
+ }
286
406
 
287
- let i = 0
407
+ const applyCollapsedReferenceRewrite = (
408
+ tokens,
409
+ startIdx,
410
+ labelStart,
411
+ labelEnd,
412
+ suffixRemoveCount,
413
+ linkOpenToken,
414
+ linkCloseToken
415
+ ) => {
416
+ const labelLength = labelEnd - labelStart + 1
417
+ const collapsedReplaceCount = labelLength + 2 + suffixRemoveCount
418
+ const collapsedEnd = startIdx + collapsedReplaceCount - 1
419
+ linkOpenToken.__strongJaMergeMarksAroundLink = true
420
+ linkCloseToken.__strongJaMergeMarksAroundLink = true
421
+ return wrapLabelTokensWithLink(
422
+ tokens,
423
+ startIdx,
424
+ collapsedEnd,
425
+ labelStart,
426
+ labelEnd,
427
+ linkOpenToken,
428
+ linkCloseToken
429
+ )
430
+ }
431
+
432
+ const COLLAPSED_REFERENCE_SCAN_RETRY = Symbol('collapsed-reference-scan-retry')
433
+ const COLLAPSED_REFERENCE_SCAN_SKIP = Symbol('collapsed-reference-scan-skip')
434
+
435
+ const createCollapsedReferenceLinkCloseMapAccessors = (tokens, cache = null) => {
288
436
  let linkCloseMap = null
289
- while (i < tokens.length) {
290
- if (splitBracketToken(tokens, i)) {
291
- linkCloseMap = null
292
- continue
293
- }
294
- if (!isBracketToken(tokens[i], '[')) {
295
- i++
296
- continue
297
- }
298
- let closeIdx = i + 1
299
- while (closeIdx < tokens.length && !isBracketToken(tokens[closeIdx], ']')) {
300
- const closeToken = tokens[closeIdx]
301
- if (closeToken && closeToken.type === 'text' && splitBracketToken(tokens, closeIdx)) {
302
- linkCloseMap = null
303
- continue
304
- }
305
- if (closeToken && closeToken.type === 'link_open') {
306
- closeIdx = -1
307
- break
437
+ const getLinkCloseMap = (startIdx = 0) => {
438
+ if (cache) {
439
+ if (cache.linkCloseMap === undefined) {
440
+ cache.linkCloseMap = buildLinkCloseMap(tokens, 0, tokens.length - 1)
308
441
  }
309
- closeIdx++
442
+ return cache.linkCloseMap
310
443
  }
311
- if (closeIdx === -1 || closeIdx >= tokens.length) {
312
- i++
313
- continue
444
+ if (linkCloseMap === null) {
445
+ linkCloseMap = buildLinkCloseMap(tokens, startIdx, tokens.length - 1)
314
446
  }
447
+ return linkCloseMap
448
+ }
449
+ const invalidateLinkCloseMap = () => {
450
+ linkCloseMap = null
451
+ if (cache) cache.linkCloseMap = undefined
452
+ }
453
+ return { getLinkCloseMap, invalidateLinkCloseMap }
454
+ }
315
455
 
316
- if (closeIdx === i + 1) {
317
- i++
318
- continue
456
+ const findCollapsedReferenceLabelClose = (tokens, startIdx, invalidateLinkCloseMap) => {
457
+ let closeIdx = startIdx + 1
458
+ while (closeIdx < tokens.length) {
459
+ if (isBracketToken(tokens[closeIdx], ']')) return closeIdx
460
+ const closeToken = tokens[closeIdx]
461
+ if (closeToken && closeToken.type === 'text' && splitBracketToken(tokens, closeIdx)) {
462
+ invalidateLinkCloseMap()
463
+ return COLLAPSED_REFERENCE_SCAN_RETRY
319
464
  }
465
+ if (closeToken && closeToken.type === 'link_open') return -1
466
+ closeIdx++
467
+ }
468
+ return -1
469
+ }
320
470
 
321
- const labelStart = i + 1
322
- const labelEnd = closeIdx - 1
323
- const labelLength = closeIdx - i - 1
324
- const labelText = buildReferenceLabelRange(tokens, labelStart, labelEnd)
325
- if (labelText.indexOf('*') === -1 && labelText.indexOf('_') === -1) {
326
- i++
327
- continue
328
- }
329
- const whitespaceStart = closeIdx + 1
330
- let refRemoveStart = whitespaceStart
331
- while (refRemoveStart < tokens.length && isWhitespaceToken(tokens[refRemoveStart])) {
332
- refRemoveStart++
333
- }
334
- const refStartToken = tokens[refRemoveStart]
335
- if (refStartToken && refStartToken.type === 'text' && splitBracketToken(tokens, refRemoveStart)) {
336
- linkCloseMap = null
337
- continue
338
- }
339
- const whitespaceCount = refRemoveStart - whitespaceStart
340
- let refKey = null
341
- let refRemoveCount = 0
342
- let existingLinkOpen = null
343
- let existingLinkClose = null
344
- const nextToken = tokens[refRemoveStart]
345
- if (isBracketToken(nextToken, '[]')) {
346
- refKey = normalizeReferenceCandidate(state, labelText)
347
- refRemoveCount = 1
348
- } else if (isBracketToken(nextToken, '[')) {
349
- let refCloseIdx = refRemoveStart + 1
350
- while (refCloseIdx < tokens.length && !isBracketToken(tokens[refCloseIdx], ']')) {
351
- refCloseIdx++
352
- }
353
- if (refCloseIdx >= tokens.length) {
354
- i++
355
- continue
356
- }
357
- const refStart = refRemoveStart + 1
358
- const refEnd = refCloseIdx - 1
359
- if (refStart > refEnd) {
360
- refKey = normalizeReferenceCandidate(state, labelText)
361
- } else {
362
- const refLabelText = buildReferenceLabelRange(tokens, refStart, refEnd)
363
- refKey = normalizeReferenceCandidate(state, refLabelText)
364
- }
365
- refRemoveCount = refCloseIdx - refRemoveStart + 1
366
- } else if (nextToken && nextToken.type === 'link_open') {
367
- if (linkCloseMap === null) {
368
- linkCloseMap = buildLinkCloseMap(tokens, refRemoveStart, tokens.length - 1)
369
- }
370
- const linkCloseIdx = linkCloseMap.get(refRemoveStart) ?? -1
371
- if (linkCloseIdx === -1) {
372
- i++
373
- continue
374
- }
375
- existingLinkOpen = tokens[refRemoveStart]
376
- existingLinkClose = tokens[linkCloseIdx]
377
- refRemoveCount = linkCloseIdx - refRemoveStart + 1
378
- } else {
471
+ const buildCollapsedReferenceCandidate = (
472
+ tokens,
473
+ state,
474
+ startIdx,
475
+ closeIdx,
476
+ getLinkCloseMap,
477
+ invalidateLinkCloseMap
478
+ ) => {
479
+ if (closeIdx === startIdx + 1) return null
480
+
481
+ const labelStart = startIdx + 1
482
+ const labelEnd = closeIdx - 1
483
+ if (!hasReferenceLabelMarkerRange(tokens, labelStart, labelEnd)) return null
484
+
485
+ let labelText = null
486
+ const getLabelText = () => {
487
+ if (labelText === null) labelText = buildReferenceLabelRange(tokens, labelStart, labelEnd)
488
+ return labelText
489
+ }
490
+
491
+ const whitespaceStart = closeIdx + 1
492
+ let refRemoveStart = whitespaceStart
493
+ while (refRemoveStart < tokens.length && isWhitespaceToken(tokens[refRemoveStart])) {
494
+ refRemoveStart++
495
+ }
496
+ const refStartToken = tokens[refRemoveStart]
497
+ if (refStartToken && refStartToken.type === 'text' && splitBracketToken(tokens, refRemoveStart)) {
498
+ invalidateLinkCloseMap()
499
+ return COLLAPSED_REFERENCE_SCAN_RETRY
500
+ }
501
+
502
+ const target = resolveCollapsedReferenceTarget(
503
+ tokens,
504
+ state,
505
+ refRemoveStart,
506
+ getLabelText,
507
+ getLinkCloseMap
508
+ )
509
+ if (!target) return null
510
+
511
+ return {
512
+ labelStart,
513
+ labelEnd,
514
+ suffixRemoveCount: (refRemoveStart - whitespaceStart) + target.refRemoveCount,
515
+ target
516
+ }
517
+ }
518
+
519
+ const tryConvertCollapsedReferenceAt = (
520
+ tokens,
521
+ state,
522
+ references,
523
+ startIdx,
524
+ getLinkCloseMap,
525
+ invalidateLinkCloseMap,
526
+ onChangeStart = null
527
+ ) => {
528
+ if (splitBracketToken(tokens, startIdx)) {
529
+ invalidateLinkCloseMap()
530
+ return COLLAPSED_REFERENCE_SCAN_RETRY
531
+ }
532
+ if (!isBracketToken(tokens[startIdx], '[')) return COLLAPSED_REFERENCE_SCAN_SKIP
533
+
534
+ const closeIdx = findCollapsedReferenceLabelClose(tokens, startIdx, invalidateLinkCloseMap)
535
+ if (closeIdx === COLLAPSED_REFERENCE_SCAN_RETRY) return COLLAPSED_REFERENCE_SCAN_RETRY
536
+ if (closeIdx === -1) return COLLAPSED_REFERENCE_SCAN_SKIP
537
+
538
+ const candidate = buildCollapsedReferenceCandidate(
539
+ tokens,
540
+ state,
541
+ startIdx,
542
+ closeIdx,
543
+ getLinkCloseMap,
544
+ invalidateLinkCloseMap
545
+ )
546
+ if (candidate === COLLAPSED_REFERENCE_SCAN_RETRY) return COLLAPSED_REFERENCE_SCAN_RETRY
547
+ if (!candidate) return COLLAPSED_REFERENCE_SCAN_SKIP
548
+
549
+ const linkPair = resolveCollapsedReferenceLinkPair(references, candidate.target)
550
+ if (!linkPair) return COLLAPSED_REFERENCE_SCAN_SKIP
551
+
552
+ if (onChangeStart) onChangeStart(startIdx)
553
+ invalidateLinkCloseMap()
554
+ return applyCollapsedReferenceRewrite(
555
+ tokens,
556
+ startIdx,
557
+ candidate.labelStart,
558
+ candidate.labelEnd,
559
+ candidate.suffixRemoveCount,
560
+ linkPair.linkOpenToken,
561
+ linkPair.linkCloseToken
562
+ )
563
+ }
564
+
565
+ const convertCollapsedReferenceLinks = (tokens, state, cache = null, onChangeStart = null) => {
566
+ const references = state.env && state.env.references
567
+ if (!references) return false
568
+ if (getReferenceCount(state) === 0) {
569
+ return false
570
+ }
571
+
572
+ let changed = false
573
+ let i = 0
574
+ const { getLinkCloseMap, invalidateLinkCloseMap } = createCollapsedReferenceLinkCloseMapAccessors(tokens, cache)
575
+ while (i < tokens.length) {
576
+ const nextIndex = tryConvertCollapsedReferenceAt(
577
+ tokens,
578
+ state,
579
+ references,
580
+ i,
581
+ getLinkCloseMap,
582
+ invalidateLinkCloseMap,
583
+ onChangeStart
584
+ )
585
+ if (nextIndex === COLLAPSED_REFERENCE_SCAN_RETRY) continue
586
+ if (nextIndex === COLLAPSED_REFERENCE_SCAN_SKIP) {
379
587
  i++
380
588
  continue
381
589
  }
382
- let linkOpenToken = null
383
- let linkCloseToken = null
384
- if (existingLinkOpen && existingLinkClose) {
385
- linkCloseMap = null
386
- if (whitespaceCount > 0) {
387
- tokens.splice(whitespaceStart, whitespaceCount)
388
- refRemoveStart -= whitespaceCount
389
- }
390
- if (refRemoveCount > 0) {
391
- tokens.splice(refRemoveStart, refRemoveCount)
392
- }
393
- linkOpenToken = existingLinkOpen
394
- linkCloseToken = existingLinkClose
395
- } else {
396
- if (!refKey) {
397
- i++
398
- continue
399
- }
400
- const ref = references[refKey]
401
- if (!ref) {
402
- i++
403
- continue
404
- }
405
- linkCloseMap = null
406
- if (whitespaceCount > 0) {
407
- tokens.splice(whitespaceStart, whitespaceCount)
408
- refRemoveStart -= whitespaceCount
409
- }
410
- if (refRemoveCount > 0) {
411
- tokens.splice(refRemoveStart, refRemoveCount)
412
- }
413
- linkOpenToken = new Token('link_open', 'a', 1)
414
- linkOpenToken.attrs = [['href', ref.href]]
415
- if (ref.title) linkOpenToken.attrPush(['title', ref.title])
416
- linkOpenToken.markup = '[]'
417
- linkOpenToken.info = 'auto'
418
- linkCloseToken = new Token('link_close', 'a', -1)
419
- linkCloseToken.markup = '[]'
420
- linkCloseToken.info = 'auto'
421
- }
422
- tokens.splice(closeIdx, 1)
423
- tokens.splice(i, 1)
424
-
425
- const nextIndex = wrapLabelTokensWithLink(tokens, i, i + labelLength - 1, linkOpenToken, linkCloseToken)
590
+ changed = true
426
591
  i = nextIndex
427
592
  }
593
+ return changed
428
594
  }
429
595
 
430
- const mergeBrokenMarksAroundLinks = (tokens) => {
596
+ const collectBrokenMarkLinkMergeRemovals = (tokens) => {
597
+ const removals = []
431
598
  let i = 0
432
599
  while (i < tokens.length) {
433
600
  const closeToken = tokens[i]
@@ -439,7 +606,9 @@ const mergeBrokenMarksAroundLinks = (tokens) => {
439
606
  const openType = closeToken.type.replace('_close', '_open')
440
607
  let j = i + 1
441
608
  while (j < tokens.length && isWhitespaceToken(tokens[j])) j++
442
- if (j >= tokens.length || tokens[j].type !== 'link_open') {
609
+ if (j >= tokens.length ||
610
+ tokens[j].type !== 'link_open' ||
611
+ tokens[j].__strongJaMergeMarksAroundLink !== true) {
443
612
  i++
444
613
  continue
445
614
  }
@@ -464,9 +633,31 @@ const mergeBrokenMarksAroundLinks = (tokens) => {
464
633
  i++
465
634
  continue
466
635
  }
467
- tokens.splice(j, 1)
468
- tokens.splice(i, 1)
636
+ removals.push({ closeIdx: i, reopenIdx: j })
637
+ i = j + 1
469
638
  }
639
+ return removals
640
+ }
641
+
642
+ const applyBrokenMarkLinkMergeRemovals = (tokens, removals, onChangeStart = null) => {
643
+ if (!removals || removals.length === 0) return false
644
+ const removeFlags = new Array(tokens.length).fill(false)
645
+ for (let idx = removals.length - 1; idx >= 0; idx--) {
646
+ const removal = removals[idx]
647
+ if (onChangeStart) onChangeStart(removal.closeIdx)
648
+ removeFlags[removal.closeIdx] = true
649
+ removeFlags[removal.reopenIdx] = true
650
+ }
651
+ const kept = []
652
+ for (let idx = 0; idx < tokens.length; idx++) {
653
+ if (!removeFlags[idx]) kept.push(tokens[idx])
654
+ }
655
+ tokens.splice(0, tokens.length, ...kept)
656
+ return true
657
+ }
658
+
659
+ const mergeBrokenMarksAroundLinks = (tokens, onChangeStart = null) => {
660
+ return applyBrokenMarkLinkMergeRemovals(tokens, collectBrokenMarkLinkMergeRemovals(tokens), onChangeStart)
470
661
  }
471
662
 
472
663
  export {