@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.
@@ -0,0 +1,774 @@
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'
4
+
5
+ const CHAR_OPEN_BRACKET = 0x5B // [
6
+ const CHAR_CLOSE_BRACKET = 0x5D // ]
7
+ const CHAR_OPEN_PAREN = 0x28 // (
8
+ const CHAR_CLOSE_PAREN = 0x29 // )
9
+
10
+ const isWhitespaceToken = (token) => {
11
+ if (!token || token.type !== 'text') return false
12
+ const content = token.content
13
+ if (!content) return true
14
+ for (let i = 0; i < content.length; i++) {
15
+ if (!isWhiteSpace(content.charCodeAt(i))) return false
16
+ }
17
+ return true
18
+ }
19
+
20
+ // Collapsed reference helpers
21
+ const buildReferenceLabelRange = (tokens, startIdx, endIdx) => {
22
+ if (startIdx > endIdx) return ''
23
+ let label = ''
24
+ for (let idx = startIdx; idx <= endIdx; idx++) {
25
+ const token = tokens[idx]
26
+ if (!token) continue
27
+ if (token.type === 'text' || token.type === 'code_inline') {
28
+ label += token.content
29
+ } else if (token.type === 'softbreak' || token.type === 'hardbreak') {
30
+ label += ' '
31
+ } else if (token.type && token.type.endsWith('_open') && token.markup) {
32
+ label += token.markup
33
+ } else if (token.type && token.type.endsWith('_close') && token.markup) {
34
+ label += token.markup
35
+ }
36
+ }
37
+ return label
38
+ }
39
+
40
+ const cleanLabelText = (label) => {
41
+ if (label.indexOf('*') === -1 && label.indexOf('_') === -1) return label
42
+ return label.replace(/^[*_]+/, '').replace(/[*_]+$/, '')
43
+ }
44
+
45
+ const normalizeReferenceCandidate = (state, text, { useClean = false } = {}) => {
46
+ const source = useClean ? cleanLabelText(text) : text
47
+ return normalizeRefKey(state, source)
48
+ }
49
+
50
+ const getNormalizeRef = (state) => {
51
+ if (state.__strongJaNormalizeRef) return state.__strongJaNormalizeRef
52
+ const normalize = state.md && state.md.utils && state.md.utils.normalizeReference
53
+ ? state.md.utils.normalizeReference
54
+ : (str) => str.trim().replace(/\s+/g, ' ').toUpperCase()
55
+ state.__strongJaNormalizeRef = normalize
56
+ return normalize
57
+ }
58
+
59
+ const normalizeRefKey = (state, label) => {
60
+ return getNormalizeRef(state)(label)
61
+ }
62
+
63
+ const adjustTokenLevels = (tokens, startIdx, endIdx, delta) => {
64
+ for (let i = startIdx; i < endIdx; i++) {
65
+ if (tokens[i]) tokens[i].level += delta
66
+ }
67
+ }
68
+
69
+ const cloneMap = (map) => {
70
+ if (!map || !Array.isArray(map)) return null
71
+ return [map[0], map[1]]
72
+ }
73
+
74
+ const getMapFromTokenRange = (tokens, startIdx, endIdx) => {
75
+ if (!tokens || startIdx > endIdx) return null
76
+ let startLine = null
77
+ let endLine = null
78
+ for (let i = startIdx; i <= endIdx && i < tokens.length; i++) {
79
+ const token = tokens[i]
80
+ if (!token || !token.map || !Array.isArray(token.map)) continue
81
+ const map = token.map
82
+ if (startLine === null || map[0] < startLine) startLine = map[0]
83
+ if (endLine === null || map[1] > endLine) endLine = map[1]
84
+ }
85
+ if (startLine === null || endLine === null) return null
86
+ return [startLine, endLine]
87
+ }
88
+
89
+ const getNearbyMap = (tokens, startIdx, endIdx) => {
90
+ if (!tokens) return null
91
+ for (let i = startIdx - 1; i >= 0; i--) {
92
+ if (tokens[i] && tokens[i].map) return cloneMap(tokens[i].map)
93
+ }
94
+ for (let i = endIdx + 1; i < tokens.length; i++) {
95
+ if (tokens[i] && tokens[i].map) return cloneMap(tokens[i].map)
96
+ }
97
+ return null
98
+ }
99
+
100
+ const cloneTextToken = (source, content) => {
101
+ const newToken = new Token('text', '', 0)
102
+ Object.assign(newToken, source)
103
+ newToken.content = content
104
+ if (source.meta) newToken.meta = { ...source.meta }
105
+ if (source.map) newToken.map = source.map
106
+ return newToken
107
+ }
108
+
109
+ // Split only text tokens that actually contain bracket characters
110
+ const splitBracketToken = (tokens, index, options) => {
111
+ const token = tokens[index]
112
+ if (!token || token.type !== 'text') return false
113
+ if (token.__strongJaBracketAtomic) return false
114
+ if (token.__strongJaHasBracket === false) return false
115
+ const content = token.content
116
+ if (!content) {
117
+ token.__strongJaHasBracket = false
118
+ token.__strongJaBracketAtomic = false
119
+ return false
120
+ }
121
+ if (token.__strongJaHasBracket !== true) {
122
+ if (content.indexOf('[') === -1 && content.indexOf(']') === -1) {
123
+ token.__strongJaHasBracket = false
124
+ token.__strongJaBracketAtomic = false
125
+ return false
126
+ }
127
+ token.__strongJaHasBracket = true
128
+ }
129
+ const splitEmptyPair = options && options.splitEmptyPair
130
+ const segments = []
131
+ let buffer = ''
132
+ let pos = 0
133
+ while (pos < content.length) {
134
+ if (!splitEmptyPair &&
135
+ content.charCodeAt(pos) === CHAR_OPEN_BRACKET &&
136
+ content.charCodeAt(pos + 1) === CHAR_CLOSE_BRACKET) {
137
+ if (buffer) {
138
+ segments.push(buffer)
139
+ buffer = ''
140
+ }
141
+ segments.push('[]')
142
+ pos += 2
143
+ continue
144
+ }
145
+ const ch = content[pos]
146
+ if (ch === '[' || ch === ']') {
147
+ if (buffer) {
148
+ segments.push(buffer)
149
+ buffer = ''
150
+ }
151
+ segments.push(ch)
152
+ pos++
153
+ continue
154
+ }
155
+ buffer += ch
156
+ pos++
157
+ }
158
+ if (buffer) segments.push(buffer)
159
+ if (segments.length <= 1) {
160
+ if (segments.length === 0) {
161
+ token.__strongJaHasBracket = false
162
+ token.__strongJaBracketAtomic = false
163
+ } else {
164
+ const seg = segments[0]
165
+ if (seg === '[' || seg === ']') {
166
+ token.__strongJaHasBracket = true
167
+ token.__strongJaBracketAtomic = true
168
+ } else if (seg === '[]') {
169
+ token.__strongJaHasBracket = true
170
+ token.__strongJaBracketAtomic = false
171
+ } else {
172
+ token.__strongJaHasBracket = false
173
+ token.__strongJaBracketAtomic = false
174
+ }
175
+ }
176
+ return false
177
+ }
178
+ token.content = segments[0]
179
+ if (token.content === '[' || token.content === ']') {
180
+ token.__strongJaHasBracket = true
181
+ token.__strongJaBracketAtomic = true
182
+ } else if (token.content === '[]') {
183
+ token.__strongJaHasBracket = true
184
+ token.__strongJaBracketAtomic = false
185
+ } else {
186
+ token.__strongJaHasBracket = false
187
+ token.__strongJaBracketAtomic = false
188
+ }
189
+ let insertIdx = index + 1
190
+ for (let s = 1; s < segments.length; s++) {
191
+ const newToken = cloneTextToken(token, segments[s])
192
+ if (segments[s] === '[' || segments[s] === ']') {
193
+ newToken.__strongJaHasBracket = true
194
+ newToken.__strongJaBracketAtomic = true
195
+ } else if (segments[s] === '[]') {
196
+ newToken.__strongJaHasBracket = true
197
+ newToken.__strongJaBracketAtomic = false
198
+ } else {
199
+ newToken.__strongJaHasBracket = false
200
+ newToken.__strongJaBracketAtomic = false
201
+ }
202
+ tokens.splice(insertIdx, 0, newToken)
203
+ insertIdx++
204
+ }
205
+ return true
206
+ }
207
+
208
+ const isBracketToken = (token, bracket) => {
209
+ return token && token.type === 'text' && token.content === bracket
210
+ }
211
+
212
+ const findLinkCloseIndex = (tokens, startIdx) => {
213
+ let depth = 0
214
+ for (let idx = startIdx; idx < tokens.length; idx++) {
215
+ const token = tokens[idx]
216
+ if (token.type === 'link_open') depth++
217
+ if (token.type === 'link_close') {
218
+ depth--
219
+ if (depth === 0) return idx
220
+ }
221
+ }
222
+ return -1
223
+ }
224
+
225
+ const consumeCharactersFromTokens = (tokens, startIdx, count) => {
226
+ let remaining = count
227
+ let idx = startIdx
228
+ while (idx < tokens.length && remaining > 0) {
229
+ const token = tokens[idx]
230
+ if (!token || token.type !== 'text') {
231
+ return false
232
+ }
233
+ const len = token.content.length
234
+ if (remaining >= len) {
235
+ remaining -= len
236
+ tokens.splice(idx, 1)
237
+ continue
238
+ }
239
+ token.content = token.content.slice(remaining)
240
+ remaining = 0
241
+ }
242
+ return remaining === 0
243
+ }
244
+
245
+ const wrapLabelTokensWithLink = (tokens, labelStartIdx, labelEndIdx, linkOpenToken, linkCloseToken, labelSource) => {
246
+ const wrapperPairs = []
247
+ let startIdx = labelStartIdx
248
+ let endIdx = labelEndIdx
249
+ while (startIdx > 0) {
250
+ const prevToken = tokens[startIdx - 1]
251
+ const nextToken = tokens[endIdx + 1]
252
+ if (!prevToken || !nextToken) break
253
+ if (!/_close$/.test(prevToken.type)) break
254
+ const expectedOpen = prevToken.type.replace('_close', '_open')
255
+ if (nextToken.type !== expectedOpen) break
256
+ wrapperPairs.push({
257
+ base: prevToken.type.replace('_close', ''),
258
+ tag: prevToken.tag,
259
+ markup: prevToken.markup,
260
+ openMap: cloneMap(nextToken.map),
261
+ closeMap: cloneMap(prevToken.map)
262
+ })
263
+ tokens.splice(endIdx + 1, 1)
264
+ tokens.splice(startIdx - 1, 1)
265
+ startIdx -= 1
266
+ endIdx -= 1
267
+ }
268
+
269
+ if (startIdx > endIdx) {
270
+ if (labelSource !== undefined && labelSource !== null) {
271
+ const placeholder = new Token('text', '', 0)
272
+ placeholder.content = labelSource
273
+ placeholder.level = linkOpenToken.level + 1
274
+ tokens.splice(startIdx, 0, placeholder)
275
+ endIdx = startIdx
276
+ } else {
277
+ return startIdx
278
+ }
279
+ }
280
+
281
+ let labelLength = endIdx - startIdx + 1
282
+ const firstLabelToken = tokens[startIdx]
283
+ const linkLevel = firstLabelToken ? Math.max(firstLabelToken.level - 1, 0) : 0
284
+ linkOpenToken.level = linkLevel
285
+ linkCloseToken.level = linkLevel
286
+ const labelMap = getMapFromTokenRange(tokens, startIdx, endIdx) || getNearbyMap(tokens, startIdx, endIdx)
287
+ if (labelMap) {
288
+ if (!linkOpenToken.map) linkOpenToken.map = cloneMap(labelMap)
289
+ if (!linkCloseToken.map) linkCloseToken.map = cloneMap(labelMap)
290
+ }
291
+ tokens.splice(startIdx, 0, linkOpenToken)
292
+ tokens.splice(startIdx + labelLength + 1, 0, linkCloseToken)
293
+
294
+ adjustTokenLevels(tokens, startIdx + 1, startIdx + labelLength + 1, 1)
295
+
296
+ if (wrapperPairs.length > 0) {
297
+ let insertIdx = startIdx + 1
298
+ for (let wp = 0; wp < wrapperPairs.length; wp++) {
299
+ const pair = wrapperPairs[wp]
300
+ const innerOpen = new Token(pair.base + '_open', pair.tag, 1)
301
+ innerOpen.markup = pair.markup
302
+ innerOpen.level = linkLevel + 1 + wp
303
+ if (pair.openMap && !innerOpen.map) innerOpen.map = cloneMap(pair.openMap)
304
+ tokens.splice(insertIdx, 0, innerOpen)
305
+ insertIdx++
306
+ labelLength++
307
+ }
308
+ let linkClosePos = startIdx + labelLength + 1
309
+ for (let wp = wrapperPairs.length - 1; wp >= 0; wp--) {
310
+ const pair = wrapperPairs[wp]
311
+ const innerClose = new Token(pair.base + '_close', pair.tag, -1)
312
+ innerClose.markup = pair.markup
313
+ innerClose.level = linkLevel + 1 + wp
314
+ if (pair.closeMap && !innerClose.map) innerClose.map = cloneMap(pair.closeMap)
315
+ tokens.splice(linkClosePos, 0, innerClose)
316
+ labelLength++
317
+ }
318
+ }
319
+
320
+ return startIdx + labelLength + 2
321
+ }
322
+
323
+ const parseInlineLinkTail = (content, md) => {
324
+ if (!content || content.charCodeAt(0) !== CHAR_OPEN_PAREN) return null
325
+ const max = content.length
326
+ let pos = 1
327
+ while (pos < max) {
328
+ const code = content.charCodeAt(pos)
329
+ if (!isSpace(code) && code !== 0x0A) break
330
+ pos++
331
+ }
332
+ if (pos >= max) return null
333
+
334
+ let href = ''
335
+ let destPos = pos
336
+ if (pos < max && content.charCodeAt(pos) === CHAR_CLOSE_PAREN) {
337
+ href = ''
338
+ } else {
339
+ const dest = parseLinkDestination(content, pos, max)
340
+ if (!dest.ok) return null
341
+ href = md.normalizeLink(dest.str)
342
+ if (!md.validateLink(href)) {
343
+ return null
344
+ }
345
+ pos = dest.pos
346
+ destPos = dest.pos
347
+ }
348
+
349
+ while (pos < max) {
350
+ const code = content.charCodeAt(pos)
351
+ if (!isSpace(code) && code !== 0x0A) break
352
+ pos++
353
+ }
354
+
355
+ let title = ''
356
+ const titleRes = parseLinkTitle(content, pos, max)
357
+ if (pos < max && pos !== destPos && titleRes.ok) {
358
+ title = titleRes.str
359
+ pos = titleRes.pos
360
+ while (pos < max) {
361
+ const code = content.charCodeAt(pos)
362
+ if (!isSpace(code) && code !== 0x0A) break
363
+ pos++
364
+ }
365
+ }
366
+
367
+ if (pos >= max || content.charCodeAt(pos) !== CHAR_CLOSE_PAREN) {
368
+ return null
369
+ }
370
+ pos++
371
+ return { href, title, consumed: pos }
372
+ }
373
+
374
+ const INLINE_LINK_BRACKET_SPLIT_OPTIONS = { splitEmptyPair: true }
375
+
376
+ const removeGhostLabelText = (tokens, linkCloseIndex, labelText) => {
377
+ if (!labelText) return
378
+ if (linkCloseIndex === null || linkCloseIndex === undefined) return
379
+ if (linkCloseIndex < 0 || linkCloseIndex >= tokens.length) return
380
+ const closeToken = tokens[linkCloseIndex]
381
+ if (!closeToken || closeToken.type !== 'link_close') return
382
+ let idx = linkCloseIndex + 1
383
+ while (idx < tokens.length) {
384
+ const token = tokens[idx]
385
+ if (!token) {
386
+ idx++
387
+ continue
388
+ }
389
+ if (token.type === 'text') {
390
+ if (token.content.startsWith(labelText)) {
391
+ if (token.content.length === labelText.length) {
392
+ tokens.splice(idx, 1)
393
+ } else {
394
+ token.content = token.content.slice(labelText.length)
395
+ }
396
+ }
397
+ break
398
+ }
399
+ if (!/_close$/.test(token.type)) {
400
+ break
401
+ }
402
+ idx++
403
+ }
404
+ }
405
+
406
+ const restoreLabelWhitespace = (tokens, labelSources) => {
407
+ if (!tokens || !labelSources || labelSources.length === 0) return
408
+ let labelIdx = 0
409
+ for (let i = 0; i < tokens.length && labelIdx < labelSources.length; i++) {
410
+ if (tokens[i].type !== 'link_open') continue
411
+ const closeIdx = findLinkCloseIndex(tokens, i)
412
+ if (closeIdx === -1) continue
413
+ const labelSource = labelSources[labelIdx] || ''
414
+ if (!labelSource) {
415
+ labelIdx++
416
+ continue
417
+ }
418
+ let cursor = 0
419
+ for (let pos = i + 1; pos < closeIdx; pos++) {
420
+ const t = tokens[pos]
421
+ const markup = t.markup || ''
422
+ const text = t.content || ''
423
+ const startPos = cursor
424
+ if (t.type === 'text') {
425
+ cursor += text.length
426
+ } else if (t.type === 'code_inline') {
427
+ cursor += markup.length + text.length + markup.length
428
+ } else if (markup) {
429
+ cursor += markup.length
430
+ }
431
+ if ((t.type === 'strong_open' || t.type === 'em_open') && startPos > 0) {
432
+ const prevToken = tokens[pos - 1]
433
+ if (prevToken && prevToken.type === 'text' && prevToken.content && !prevToken.content.endsWith(' ')) {
434
+ const hasSpaceBefore = startPos - 1 >= 0 && startPos - 1 < labelSource.length && labelSource[startPos - 1] === ' '
435
+ const hasSpaceAt = startPos >= 0 && startPos < labelSource.length && labelSource[startPos] === ' '
436
+ if (hasSpaceBefore || hasSpaceAt) {
437
+ prevToken.content += ' '
438
+ }
439
+ }
440
+ }
441
+ }
442
+ labelIdx++
443
+ }
444
+ }
445
+
446
+ const convertInlineLinks = (tokens, state) => {
447
+ if (!tokens || tokens.length === 0) return
448
+ let labelSources = tokens.__strongJaInlineLabelSources
449
+ if ((!labelSources || labelSources.length === 0) && state && state.env && Array.isArray(state.env.__strongJaInlineLabelSourceList) && state.env.__strongJaInlineLabelSourceList.length > 0) {
450
+ labelSources = state.env.__strongJaInlineLabelSourceList.shift()
451
+ }
452
+ let labelSourceIndex = tokens.__strongJaInlineLabelIndex || 0
453
+ let i = 0
454
+ while (i < tokens.length) {
455
+ if (splitBracketToken(tokens, i, INLINE_LINK_BRACKET_SPLIT_OPTIONS)) {
456
+ continue
457
+ }
458
+ if (!isBracketToken(tokens[i], '[')) {
459
+ i++
460
+ continue
461
+ }
462
+ let closeIdx = i + 1
463
+ let invalid = false
464
+ while (closeIdx < tokens.length && !isBracketToken(tokens[closeIdx], ']')) {
465
+ if (splitBracketToken(tokens, closeIdx, INLINE_LINK_BRACKET_SPLIT_OPTIONS)) {
466
+ continue
467
+ }
468
+ if (tokens[closeIdx].type === 'link_open') {
469
+ invalid = true
470
+ break
471
+ }
472
+ closeIdx++
473
+ }
474
+ if (invalid || closeIdx >= tokens.length) {
475
+ i++
476
+ continue
477
+ }
478
+ const currentLabelSource = labelSources && labelSourceIndex < labelSources.length
479
+ ? labelSources[labelSourceIndex]
480
+ : undefined
481
+
482
+ const labelLength = closeIdx - i - 1
483
+ const needsPlaceholder = labelLength <= 0
484
+ if (needsPlaceholder && !currentLabelSource) {
485
+ i++
486
+ continue
487
+ }
488
+
489
+ let tailIdx = closeIdx + 1
490
+ let tailContent = ''
491
+ let parsedTail = null
492
+ let tailHasCloseParen = false
493
+ while (tailIdx < tokens.length) {
494
+ if (splitBracketToken(tokens, tailIdx, INLINE_LINK_BRACKET_SPLIT_OPTIONS)) {
495
+ continue
496
+ }
497
+ const tailToken = tokens[tailIdx]
498
+ if (tailToken.type !== 'text' || !tailToken.content) {
499
+ break
500
+ }
501
+ tailContent += tailToken.content
502
+ if (!tailHasCloseParen) {
503
+ if (tailToken.content.indexOf(')') === -1) {
504
+ tailIdx++
505
+ continue
506
+ }
507
+ tailHasCloseParen = true
508
+ }
509
+ parsedTail = parseInlineLinkTail(tailContent, state.md)
510
+ if (parsedTail) break
511
+ tailIdx++
512
+ }
513
+
514
+ if (!parsedTail) {
515
+ i++
516
+ continue
517
+ }
518
+
519
+ if (!consumeCharactersFromTokens(tokens, closeIdx + 1, parsedTail.consumed)) {
520
+ i++
521
+ continue
522
+ }
523
+
524
+ tokens.splice(closeIdx, 1)
525
+ tokens.splice(i, 1)
526
+
527
+ const linkOpenToken = new Token('link_open', 'a', 1)
528
+ linkOpenToken.attrs = [['href', parsedTail.href]]
529
+ if (parsedTail.title) linkOpenToken.attrPush(['title', parsedTail.title])
530
+ linkOpenToken.markup = '[]()'
531
+ linkOpenToken.info = 'auto'
532
+ const linkCloseToken = new Token('link_close', 'a', -1)
533
+ linkCloseToken.markup = '[]()'
534
+ linkCloseToken.info = 'auto'
535
+
536
+ const nextIndex = wrapLabelTokensWithLink(tokens, i, i + labelLength - 1, linkOpenToken, linkCloseToken, currentLabelSource)
537
+ if (nextIndex === i) {
538
+ i++
539
+ continue
540
+ }
541
+ if (currentLabelSource) {
542
+ const linkCloseIdx = findLinkCloseIndex(tokens, i)
543
+ if (linkCloseIdx !== -1) {
544
+ let cursor = 0
545
+ for (let pos = i + 1; pos < linkCloseIdx; pos++) {
546
+ const t = tokens[pos]
547
+ const markup = t.markup || ''
548
+ const text = t.content || ''
549
+ const startPos = cursor
550
+ if (t.type === 'text') {
551
+ cursor += text.length
552
+ } else if (t.type === 'code_inline') {
553
+ cursor += markup.length + text.length + markup.length
554
+ } else if (markup) {
555
+ cursor += markup.length
556
+ }
557
+ if ((t.type === 'strong_open' || t.type === 'em_open') && startPos > 0) {
558
+ const prevToken = tokens[pos - 1]
559
+ if (prevToken && prevToken.type === 'text' && prevToken.content && !prevToken.content.endsWith(' ')) {
560
+ const labelHasSpaceBefore = startPos - 1 >= 0 && startPos - 1 < currentLabelSource.length && currentLabelSource[startPos - 1] === ' '
561
+ const labelHasSpaceAt = startPos >= 0 && startPos < currentLabelSource.length && currentLabelSource[startPos] === ' '
562
+ if (labelHasSpaceBefore || labelHasSpaceAt) {
563
+ prevToken.content += ' '
564
+ }
565
+ }
566
+ }
567
+ }
568
+ }
569
+ }
570
+ if (needsPlaceholder && currentLabelSource) {
571
+ removeGhostLabelText(tokens, nextIndex - 1, currentLabelSource)
572
+ }
573
+
574
+ if (labelSources && labelSources.length > 0) {
575
+ if (labelSourceIndex < labelSources.length) {
576
+ labelSourceIndex++
577
+ }
578
+ }
579
+ i = nextIndex
580
+ }
581
+ if (labelSources) {
582
+ tokens.__strongJaInlineLabelIndex = labelSourceIndex
583
+ }
584
+ }
585
+
586
+ const convertCollapsedReferenceLinks = (tokens, state) => {
587
+ const references = state.env && state.env.references
588
+ if (!references) return
589
+ const referenceCount = state.__strongJaReferenceCount
590
+ if (referenceCount !== undefined) {
591
+ if (referenceCount === 0) return
592
+ } else if (Object.keys(references).length === 0) {
593
+ return
594
+ }
595
+
596
+ let i = 0
597
+ while (i < tokens.length) {
598
+ if (splitBracketToken(tokens, i)) {
599
+ continue
600
+ }
601
+ if (!isBracketToken(tokens[i], '[')) {
602
+ i++
603
+ continue
604
+ }
605
+ let closeIdx = i + 1
606
+ while (closeIdx < tokens.length && !isBracketToken(tokens[closeIdx], ']')) {
607
+ if (splitBracketToken(tokens, closeIdx)) {
608
+ continue
609
+ }
610
+ if (tokens[closeIdx].type === 'link_open') {
611
+ closeIdx = -1
612
+ break
613
+ }
614
+ closeIdx++
615
+ }
616
+ if (closeIdx === -1 || closeIdx >= tokens.length) {
617
+ i++
618
+ continue
619
+ }
620
+
621
+ if (closeIdx === i + 1) {
622
+ i++
623
+ continue
624
+ }
625
+
626
+ const labelStart = i + 1
627
+ const labelEnd = closeIdx - 1
628
+ const labelLength = closeIdx - i - 1
629
+ const labelText = buildReferenceLabelRange(tokens, labelStart, labelEnd)
630
+ const cleanedLabel = cleanLabelText(labelText)
631
+ const whitespaceStart = closeIdx + 1
632
+ let refRemoveStart = whitespaceStart
633
+ while (refRemoveStart < tokens.length && isWhitespaceToken(tokens[refRemoveStart])) {
634
+ refRemoveStart++
635
+ }
636
+ if (splitBracketToken(tokens, refRemoveStart)) {
637
+ continue
638
+ }
639
+ const whitespaceCount = refRemoveStart - whitespaceStart
640
+ let refKey = null
641
+ let refRemoveCount = 0
642
+ let existingLinkOpen = null
643
+ let existingLinkClose = null
644
+ const nextToken = tokens[refRemoveStart]
645
+ if (isBracketToken(nextToken, '[]')) {
646
+ refKey = normalizeReferenceCandidate(state, cleanedLabel)
647
+ refRemoveCount = 1
648
+ } else if (isBracketToken(nextToken, '[')) {
649
+ let refCloseIdx = refRemoveStart + 1
650
+ while (refCloseIdx < tokens.length && !isBracketToken(tokens[refCloseIdx], ']')) {
651
+ refCloseIdx++
652
+ }
653
+ if (refCloseIdx >= tokens.length) {
654
+ i++
655
+ continue
656
+ }
657
+ const refStart = refRemoveStart + 1
658
+ const refEnd = refCloseIdx - 1
659
+ if (refStart > refEnd) {
660
+ refKey = normalizeReferenceCandidate(state, cleanedLabel)
661
+ } else {
662
+ const refLabelText = buildReferenceLabelRange(tokens, refStart, refEnd)
663
+ refKey = normalizeReferenceCandidate(state, refLabelText)
664
+ }
665
+ refRemoveCount = refCloseIdx - refRemoveStart + 1
666
+ } else if (nextToken && nextToken.type === 'link_open') {
667
+ const linkCloseIdx = findLinkCloseIndex(tokens, refRemoveStart)
668
+ if (linkCloseIdx === -1) {
669
+ i++
670
+ continue
671
+ }
672
+ existingLinkOpen = tokens[refRemoveStart]
673
+ existingLinkClose = tokens[linkCloseIdx]
674
+ refRemoveCount = linkCloseIdx - refRemoveStart + 1
675
+ } else {
676
+ i++
677
+ continue
678
+ }
679
+ let linkOpenToken = null
680
+ let linkCloseToken = null
681
+ if (existingLinkOpen && existingLinkClose) {
682
+ if (whitespaceCount > 0) {
683
+ tokens.splice(whitespaceStart, whitespaceCount)
684
+ refRemoveStart -= whitespaceCount
685
+ }
686
+ if (refRemoveCount > 0) {
687
+ tokens.splice(refRemoveStart, refRemoveCount)
688
+ }
689
+ linkOpenToken = existingLinkOpen
690
+ linkCloseToken = existingLinkClose
691
+ } else {
692
+ if (!refKey) {
693
+ i++
694
+ continue
695
+ }
696
+ const ref = references[refKey]
697
+ if (!ref) {
698
+ i++
699
+ continue
700
+ }
701
+ if (whitespaceCount > 0) {
702
+ tokens.splice(whitespaceStart, whitespaceCount)
703
+ refRemoveStart -= whitespaceCount
704
+ }
705
+ if (refRemoveCount > 0) {
706
+ tokens.splice(refRemoveStart, refRemoveCount)
707
+ }
708
+ linkOpenToken = new Token('link_open', 'a', 1)
709
+ linkOpenToken.attrs = [['href', ref.href]]
710
+ if (ref.title) linkOpenToken.attrPush(['title', ref.title])
711
+ linkOpenToken.markup = '[]'
712
+ linkOpenToken.info = 'auto'
713
+ linkCloseToken = new Token('link_close', 'a', -1)
714
+ linkCloseToken.markup = '[]'
715
+ linkCloseToken.info = 'auto'
716
+ }
717
+ tokens.splice(closeIdx, 1)
718
+ tokens.splice(i, 1)
719
+
720
+ const nextIndex = wrapLabelTokensWithLink(tokens, i, i + labelLength - 1, linkOpenToken, linkCloseToken)
721
+ i = nextIndex
722
+ }
723
+ }
724
+
725
+ // Link cleanup helpers
726
+ const mergeBrokenMarksAroundLinks = (tokens) => {
727
+ let i = 0
728
+ while (i < tokens.length) {
729
+ const closeToken = tokens[i]
730
+ if (!closeToken || !/_close$/.test(closeToken.type)) {
731
+ i++
732
+ continue
733
+ }
734
+ const openType = closeToken.type.replace('_close', '_open')
735
+ let j = i + 1
736
+ while (j < tokens.length && isWhitespaceToken(tokens[j])) j++
737
+ if (j >= tokens.length || tokens[j].type !== 'link_open') {
738
+ i++
739
+ continue
740
+ }
741
+ let linkDepth = 1
742
+ j++
743
+ while (j < tokens.length && linkDepth > 0) {
744
+ if (tokens[j].type === 'link_open') linkDepth++
745
+ if (tokens[j].type === 'link_close') linkDepth--
746
+ j++
747
+ }
748
+ if (linkDepth !== 0) {
749
+ i++
750
+ continue
751
+ }
752
+ while (j < tokens.length && isWhitespaceToken(tokens[j])) j++
753
+ if (j >= tokens.length) {
754
+ i++
755
+ continue
756
+ }
757
+ const reopenToken = tokens[j]
758
+ if (reopenToken.type !== openType || reopenToken.level !== closeToken.level) {
759
+ i++
760
+ continue
761
+ }
762
+ tokens.splice(j, 1)
763
+ tokens.splice(i, 1)
764
+ }
765
+ }
766
+
767
+ export {
768
+ normalizeReferenceCandidate,
769
+ restoreLabelWhitespace,
770
+ convertInlineLinks,
771
+ convertCollapsedReferenceLinks,
772
+ mergeBrokenMarksAroundLinks,
773
+ getMapFromTokenRange
774
+ }