@peaceroad/markdown-it-strong-ja 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
@@ -104,12 +120,9 @@ const cloneTextToken = (source, content) => {
104
120
  }
105
121
 
106
122
  const applyBracketSegmentFlags = (token, seg) => {
107
- if (seg === '[' || seg === ']') {
123
+ if (seg === '[' || seg === ']' || seg === '[]') {
108
124
  token.__strongJaHasBracket = true
109
125
  token.__strongJaBracketAtomic = true
110
- } else if (seg === '[]') {
111
- token.__strongJaHasBracket = true
112
- token.__strongJaBracketAtomic = false
113
126
  } else {
114
127
  token.__strongJaHasBracket = false
115
128
  token.__strongJaBracketAtomic = false
@@ -204,13 +217,15 @@ const buildLinkCloseMap = (tokens, startIdx, endIdx) => {
204
217
  return closeMap
205
218
  }
206
219
 
207
- const wrapLabelTokensWithLink = (tokens, labelStartIdx, labelEndIdx, linkOpenToken, linkCloseToken) => {
220
+ const collectWrappedLabelPairs = (tokens, collapsedStartIdx, collapsedEndIdx) => {
208
221
  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]
222
+ while (true) {
223
+ const wrapperOffset = wrapperPairs.length
224
+ const closeIdx = collapsedStartIdx - 1 - wrapperOffset
225
+ const openIdx = collapsedEndIdx + 1 + wrapperOffset
226
+ if (closeIdx < 0 || openIdx >= tokens.length) break
227
+ const prevToken = tokens[closeIdx]
228
+ const nextToken = tokens[openIdx]
214
229
  if (!prevToken || !nextToken) break
215
230
  if (!prevToken.type || !prevToken.type.endsWith('_close')) break
216
231
  const expectedOpen = prevToken.type.replace('_close', '_open')
@@ -220,214 +235,363 @@ const wrapLabelTokensWithLink = (tokens, labelStartIdx, labelEndIdx, linkOpenTok
220
235
  tag: prevToken.tag,
221
236
  markup: prevToken.markup,
222
237
  openMap: cloneMap(nextToken.map),
223
- closeMap: cloneMap(prevToken.map)
238
+ closeMap: cloneMap(prevToken.map),
239
+ closeIdx,
240
+ openIdx
224
241
  })
225
- tokens.splice(endIdx + 1, 1)
226
- tokens.splice(startIdx - 1, 1)
227
- startIdx -= 1
228
- endIdx -= 1
229
242
  }
243
+ return wrapperPairs
244
+ }
245
+
246
+ const resolveWrappedLabelReplaceRange = (wrapperPairs, collapsedStartIdx, collapsedEndIdx) => {
247
+ if (wrapperPairs.length === 0) {
248
+ return {
249
+ replaceStart: collapsedStartIdx,
250
+ replaceEnd: collapsedEndIdx
251
+ }
252
+ }
253
+ const outerPair = wrapperPairs[wrapperPairs.length - 1]
254
+ return {
255
+ replaceStart: outerPair.closeIdx,
256
+ replaceEnd: outerPair.openIdx
257
+ }
258
+ }
230
259
 
231
- if (startIdx > endIdx) return startIdx
260
+ const resolveInsertedWrapperMap = (pairMap, labelMap) => {
261
+ return pairMap || labelMap
262
+ }
232
263
 
233
- let labelLength = endIdx - startIdx + 1
234
- const firstLabelToken = tokens[startIdx]
264
+ const buildWrappedLabelReplacement = (labelTokens, linkOpenToken, linkCloseToken, wrapperPairs, labelMap) => {
265
+ const firstLabelToken = labelTokens[0]
235
266
  const linkLevel = firstLabelToken ? Math.max(firstLabelToken.level - 1, 0) : 0
236
267
  linkOpenToken.level = linkLevel
237
268
  linkCloseToken.level = linkLevel
238
- const labelMap = getMapFromTokenRange(tokens, startIdx, endIdx) || getNearbyMap(tokens, startIdx, endIdx)
239
269
  if (labelMap) {
240
270
  if (!linkOpenToken.map) linkOpenToken.map = cloneMap(labelMap)
241
271
  if (!linkCloseToken.map) linkCloseToken.map = cloneMap(labelMap)
242
272
  }
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++
273
+ for (let idx = 0; idx < labelTokens.length; idx++) {
274
+ if (labelTokens[idx]) labelTokens[idx].level += 1
275
+ }
276
+
277
+ const replacement = [linkOpenToken]
278
+ for (let wp = 0; wp < wrapperPairs.length; wp++) {
279
+ const pair = wrapperPairs[wp]
280
+ const innerOpen = new Token(pair.base + '_open', pair.tag, 1)
281
+ innerOpen.markup = pair.markup
282
+ innerOpen.level = linkLevel + 1 + wp
283
+ const openMap = resolveInsertedWrapperMap(pair.openMap, labelMap)
284
+ if (openMap && !innerOpen.map) innerOpen.map = cloneMap(openMap)
285
+ replacement.push(innerOpen)
286
+ }
287
+ replacement.push(...labelTokens)
288
+ for (let wp = 0; wp < wrapperPairs.length; wp++) {
289
+ const pair = wrapperPairs[wp]
290
+ const innerClose = new Token(pair.base + '_close', pair.tag, -1)
291
+ innerClose.markup = pair.markup
292
+ innerClose.level = linkLevel + 1 + wp
293
+ const closeMap = resolveInsertedWrapperMap(pair.closeMap, labelMap)
294
+ if (closeMap && !innerClose.map) innerClose.map = cloneMap(closeMap)
295
+ replacement.push(innerClose)
296
+ }
297
+ replacement.push(linkCloseToken)
298
+ return replacement
299
+ }
300
+
301
+ const wrapLabelTokensWithLink = (
302
+ tokens,
303
+ collapsedStartIdx,
304
+ collapsedEndIdx,
305
+ labelStartIdx,
306
+ labelEndIdx,
307
+ linkOpenToken,
308
+ linkCloseToken
309
+ ) => {
310
+ if (labelStartIdx > labelEndIdx) return collapsedStartIdx
311
+ const labelTokens = tokens.slice(labelStartIdx, labelEndIdx + 1)
312
+ const wrapperPairs = collectWrappedLabelPairs(tokens, collapsedStartIdx, collapsedEndIdx)
313
+ const { replaceStart, replaceEnd } = resolveWrappedLabelReplaceRange(
314
+ wrapperPairs,
315
+ collapsedStartIdx,
316
+ collapsedEndIdx
317
+ )
318
+ const labelMap = getMapFromTokenRange(tokens, labelStartIdx, labelEndIdx) || getNearbyMap(tokens, replaceStart, replaceEnd)
319
+ const replacement = buildWrappedLabelReplacement(
320
+ labelTokens,
321
+ linkOpenToken,
322
+ linkCloseToken,
323
+ wrapperPairs,
324
+ labelMap
325
+ )
326
+ tokens.splice(replaceStart, replaceEnd - replaceStart + 1, ...replacement)
327
+ return replaceStart + replacement.length
328
+ }
329
+
330
+ const resolveCollapsedReferenceTarget = (
331
+ tokens,
332
+ state,
333
+ refRemoveStart,
334
+ getLabelText,
335
+ getLinkCloseMap
336
+ ) => {
337
+ let refKey = null
338
+ let refRemoveCount = 0
339
+ let existingLinkOpen = null
340
+ let existingLinkClose = null
341
+ const nextToken = tokens[refRemoveStart]
342
+ if (isBracketToken(nextToken, '[]')) {
343
+ refKey = normalizeReferenceCandidate(state, getLabelText())
344
+ refRemoveCount = 1
345
+ } else if (isBracketToken(nextToken, '[')) {
346
+ let refCloseIdx = refRemoveStart + 1
347
+ while (refCloseIdx < tokens.length && !isBracketToken(tokens[refCloseIdx], ']')) {
348
+ refCloseIdx++
259
349
  }
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++
350
+ if (refCloseIdx >= tokens.length) return null
351
+ const refStart = refRemoveStart + 1
352
+ const refEnd = refCloseIdx - 1
353
+ if (refStart > refEnd) {
354
+ refKey = normalizeReferenceCandidate(state, getLabelText())
355
+ } else {
356
+ const refLabelText = buildReferenceLabelRange(tokens, refStart, refEnd)
357
+ refKey = normalizeReferenceCandidate(state, refLabelText)
269
358
  }
359
+ refRemoveCount = refCloseIdx - refRemoveStart + 1
360
+ } else if (nextToken && nextToken.type === 'link_open') {
361
+ const linkCloseMap = getLinkCloseMap(refRemoveStart)
362
+ const linkCloseIdx = linkCloseMap.get(refRemoveStart) ?? -1
363
+ if (linkCloseIdx === -1) return null
364
+ existingLinkOpen = tokens[refRemoveStart]
365
+ existingLinkClose = tokens[linkCloseIdx]
366
+ refRemoveCount = linkCloseIdx - refRemoveStart + 1
367
+ } else {
368
+ return null
270
369
  }
370
+ return {
371
+ refKey,
372
+ refRemoveCount,
373
+ existingLinkOpen,
374
+ existingLinkClose
375
+ }
376
+ }
271
377
 
272
- return startIdx + labelLength + 2
378
+ const buildAutoCollapsedReferenceLinkPair = (ref) => {
379
+ if (!ref) return null
380
+ const linkOpenToken = new Token('link_open', 'a', 1)
381
+ linkOpenToken.attrs = [['href', ref.href]]
382
+ if (ref.title) linkOpenToken.attrPush(['title', ref.title])
383
+ linkOpenToken.markup = '[]'
384
+ linkOpenToken.info = 'auto'
385
+
386
+ const linkCloseToken = new Token('link_close', 'a', -1)
387
+ linkCloseToken.markup = '[]'
388
+ linkCloseToken.info = 'auto'
389
+ return { linkOpenToken, linkCloseToken }
273
390
  }
274
391
 
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
392
+ const resolveCollapsedReferenceLinkPair = (references, target) => {
393
+ if (!target) return null
394
+ if (target.existingLinkOpen && target.existingLinkClose) {
395
+ return {
396
+ linkOpenToken: target.existingLinkOpen,
397
+ linkCloseToken: target.existingLinkClose
398
+ }
285
399
  }
400
+ if (!target.refKey) return null
401
+ return buildAutoCollapsedReferenceLinkPair(references[target.refKey])
402
+ }
286
403
 
287
- let i = 0
404
+ const applyCollapsedReferenceRewrite = (
405
+ tokens,
406
+ startIdx,
407
+ labelStart,
408
+ labelEnd,
409
+ suffixRemoveCount,
410
+ linkOpenToken,
411
+ linkCloseToken
412
+ ) => {
413
+ const labelLength = labelEnd - labelStart + 1
414
+ const collapsedReplaceCount = labelLength + 2 + suffixRemoveCount
415
+ const collapsedEnd = startIdx + collapsedReplaceCount - 1
416
+ linkOpenToken.__strongJaMergeMarksAroundLink = true
417
+ linkCloseToken.__strongJaMergeMarksAroundLink = true
418
+ return wrapLabelTokensWithLink(
419
+ tokens,
420
+ startIdx,
421
+ collapsedEnd,
422
+ labelStart,
423
+ labelEnd,
424
+ linkOpenToken,
425
+ linkCloseToken
426
+ )
427
+ }
428
+
429
+ const COLLAPSED_REFERENCE_SCAN_RETRY = Symbol('collapsed-reference-scan-retry')
430
+ const COLLAPSED_REFERENCE_SCAN_SKIP = Symbol('collapsed-reference-scan-skip')
431
+
432
+ const createCollapsedReferenceLinkCloseMapAccessors = (tokens, cache = null) => {
288
433
  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
434
+ const getLinkCloseMap = (startIdx = 0) => {
435
+ if (cache) {
436
+ if (cache.linkCloseMap === undefined) {
437
+ cache.linkCloseMap = buildLinkCloseMap(tokens, 0, tokens.length - 1)
308
438
  }
309
- closeIdx++
439
+ return cache.linkCloseMap
310
440
  }
311
- if (closeIdx === -1 || closeIdx >= tokens.length) {
312
- i++
313
- continue
441
+ if (linkCloseMap === null) {
442
+ linkCloseMap = buildLinkCloseMap(tokens, startIdx, tokens.length - 1)
314
443
  }
444
+ return linkCloseMap
445
+ }
446
+ const invalidateLinkCloseMap = () => {
447
+ linkCloseMap = null
448
+ if (cache) cache.linkCloseMap = undefined
449
+ }
450
+ return { getLinkCloseMap, invalidateLinkCloseMap }
451
+ }
315
452
 
316
- if (closeIdx === i + 1) {
317
- i++
318
- continue
453
+ const findCollapsedReferenceLabelClose = (tokens, startIdx, invalidateLinkCloseMap) => {
454
+ let closeIdx = startIdx + 1
455
+ while (closeIdx < tokens.length) {
456
+ if (isBracketToken(tokens[closeIdx], ']')) return closeIdx
457
+ const closeToken = tokens[closeIdx]
458
+ if (closeToken && closeToken.type === 'text' && splitBracketToken(tokens, closeIdx)) {
459
+ invalidateLinkCloseMap()
460
+ return COLLAPSED_REFERENCE_SCAN_RETRY
319
461
  }
462
+ if (closeToken && closeToken.type === 'link_open') return -1
463
+ closeIdx++
464
+ }
465
+ return -1
466
+ }
320
467
 
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 {
468
+ const buildCollapsedReferenceCandidate = (
469
+ tokens,
470
+ state,
471
+ startIdx,
472
+ closeIdx,
473
+ getLinkCloseMap,
474
+ invalidateLinkCloseMap
475
+ ) => {
476
+ if (closeIdx === startIdx + 1) return null
477
+
478
+ const labelStart = startIdx + 1
479
+ const labelEnd = closeIdx - 1
480
+ if (!hasReferenceLabelMarkerRange(tokens, labelStart, labelEnd)) return null
481
+
482
+ let labelText = null
483
+ const getLabelText = () => {
484
+ if (labelText === null) labelText = buildReferenceLabelRange(tokens, labelStart, labelEnd)
485
+ return labelText
486
+ }
487
+
488
+ const whitespaceStart = closeIdx + 1
489
+ let refRemoveStart = whitespaceStart
490
+ while (refRemoveStart < tokens.length && isWhitespaceToken(tokens[refRemoveStart])) {
491
+ refRemoveStart++
492
+ }
493
+ const refStartToken = tokens[refRemoveStart]
494
+ if (refStartToken && refStartToken.type === 'text' && splitBracketToken(tokens, refRemoveStart)) {
495
+ invalidateLinkCloseMap()
496
+ return COLLAPSED_REFERENCE_SCAN_RETRY
497
+ }
498
+
499
+ const target = resolveCollapsedReferenceTarget(
500
+ tokens,
501
+ state,
502
+ refRemoveStart,
503
+ getLabelText,
504
+ getLinkCloseMap
505
+ )
506
+ if (!target) return null
507
+
508
+ return {
509
+ labelStart,
510
+ labelEnd,
511
+ suffixRemoveCount: (refRemoveStart - whitespaceStart) + target.refRemoveCount,
512
+ target
513
+ }
514
+ }
515
+
516
+ const tryConvertCollapsedReferenceAt = (
517
+ tokens,
518
+ state,
519
+ references,
520
+ startIdx,
521
+ getLinkCloseMap,
522
+ invalidateLinkCloseMap,
523
+ onChangeStart = null
524
+ ) => {
525
+ if (splitBracketToken(tokens, startIdx)) {
526
+ invalidateLinkCloseMap()
527
+ return COLLAPSED_REFERENCE_SCAN_RETRY
528
+ }
529
+ if (!isBracketToken(tokens[startIdx], '[')) return COLLAPSED_REFERENCE_SCAN_SKIP
530
+
531
+ const closeIdx = findCollapsedReferenceLabelClose(tokens, startIdx, invalidateLinkCloseMap)
532
+ if (closeIdx === COLLAPSED_REFERENCE_SCAN_RETRY) return COLLAPSED_REFERENCE_SCAN_RETRY
533
+ if (closeIdx === -1) return COLLAPSED_REFERENCE_SCAN_SKIP
534
+
535
+ const candidate = buildCollapsedReferenceCandidate(
536
+ tokens,
537
+ state,
538
+ startIdx,
539
+ closeIdx,
540
+ getLinkCloseMap,
541
+ invalidateLinkCloseMap
542
+ )
543
+ if (candidate === COLLAPSED_REFERENCE_SCAN_RETRY) return COLLAPSED_REFERENCE_SCAN_RETRY
544
+ if (!candidate) return COLLAPSED_REFERENCE_SCAN_SKIP
545
+
546
+ const linkPair = resolveCollapsedReferenceLinkPair(references, candidate.target)
547
+ if (!linkPair) return COLLAPSED_REFERENCE_SCAN_SKIP
548
+
549
+ if (onChangeStart) onChangeStart(startIdx)
550
+ invalidateLinkCloseMap()
551
+ return applyCollapsedReferenceRewrite(
552
+ tokens,
553
+ startIdx,
554
+ candidate.labelStart,
555
+ candidate.labelEnd,
556
+ candidate.suffixRemoveCount,
557
+ linkPair.linkOpenToken,
558
+ linkPair.linkCloseToken
559
+ )
560
+ }
561
+
562
+ const convertCollapsedReferenceLinks = (tokens, state, cache = null, onChangeStart = null) => {
563
+ const references = state.env && state.env.references
564
+ if (!references) return false
565
+ if (getReferenceCount(state) === 0) {
566
+ return false
567
+ }
568
+
569
+ let changed = false
570
+ let i = 0
571
+ const { getLinkCloseMap, invalidateLinkCloseMap } = createCollapsedReferenceLinkCloseMapAccessors(tokens, cache)
572
+ while (i < tokens.length) {
573
+ const nextIndex = tryConvertCollapsedReferenceAt(
574
+ tokens,
575
+ state,
576
+ references,
577
+ i,
578
+ getLinkCloseMap,
579
+ invalidateLinkCloseMap,
580
+ onChangeStart
581
+ )
582
+ if (nextIndex === COLLAPSED_REFERENCE_SCAN_RETRY) continue
583
+ if (nextIndex === COLLAPSED_REFERENCE_SCAN_SKIP) {
379
584
  i++
380
585
  continue
381
586
  }
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)
587
+ changed = true
426
588
  i = nextIndex
427
589
  }
590
+ return changed
428
591
  }
429
592
 
430
- const mergeBrokenMarksAroundLinks = (tokens) => {
593
+ const collectBrokenMarkLinkMergeRemovals = (tokens) => {
594
+ const removals = []
431
595
  let i = 0
432
596
  while (i < tokens.length) {
433
597
  const closeToken = tokens[i]
@@ -439,7 +603,9 @@ const mergeBrokenMarksAroundLinks = (tokens) => {
439
603
  const openType = closeToken.type.replace('_close', '_open')
440
604
  let j = i + 1
441
605
  while (j < tokens.length && isWhitespaceToken(tokens[j])) j++
442
- if (j >= tokens.length || tokens[j].type !== 'link_open') {
606
+ if (j >= tokens.length ||
607
+ tokens[j].type !== 'link_open' ||
608
+ tokens[j].__strongJaMergeMarksAroundLink !== true) {
443
609
  i++
444
610
  continue
445
611
  }
@@ -464,9 +630,36 @@ const mergeBrokenMarksAroundLinks = (tokens) => {
464
630
  i++
465
631
  continue
466
632
  }
467
- tokens.splice(j, 1)
468
- tokens.splice(i, 1)
633
+ removals.push({ closeIdx: i, reopenIdx: j })
634
+ i = j + 1
635
+ }
636
+ return removals
637
+ }
638
+
639
+ const applyBrokenMarkLinkMergeRemovals = (tokens, removals, onChangeStart = null) => {
640
+ if (!removals || removals.length === 0) return false
641
+ for (let idx = removals.length - 1; idx >= 0; idx--) {
642
+ if (onChangeStart) onChangeStart(removals[idx].closeIdx)
643
+ }
644
+ const kept = []
645
+ let removalIdx = 0
646
+ let nextRemoval = removals[removalIdx]
647
+ for (let idx = 0; idx < tokens.length; idx++) {
648
+ if (nextRemoval && (idx === nextRemoval.closeIdx || idx === nextRemoval.reopenIdx)) {
649
+ if (idx === nextRemoval.reopenIdx) {
650
+ removalIdx++
651
+ nextRemoval = removals[removalIdx]
652
+ }
653
+ continue
654
+ }
655
+ kept.push(tokens[idx])
469
656
  }
657
+ tokens.splice(0, tokens.length, ...kept)
658
+ return true
659
+ }
660
+
661
+ const mergeBrokenMarksAroundLinks = (tokens, onChangeStart = null) => {
662
+ return applyBrokenMarkLinkMergeRemovals(tokens, collectBrokenMarkLinkMergeRemovals(tokens), onChangeStart)
470
663
  }
471
664
 
472
665
  export {