@peaceroad/markdown-it-strong-ja 0.7.1 → 0.8.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,23 +1,33 @@
1
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'
2
+ import { isWhiteSpace } from 'markdown-it/lib/common/utils.mjs'
4
3
 
5
4
  const CHAR_OPEN_BRACKET = 0x5B // [
6
5
  const CHAR_CLOSE_BRACKET = 0x5D // ]
7
- const CHAR_OPEN_PAREN = 0x28 // (
8
- const CHAR_CLOSE_PAREN = 0x29 // )
9
6
 
10
7
  const isWhitespaceToken = (token) => {
11
8
  if (!token || token.type !== 'text') return false
12
9
  const content = token.content
13
- if (!content) return true
10
+ if (token.__strongJaWhitespaceSource === content &&
11
+ typeof token.__strongJaIsWhitespace === 'boolean') {
12
+ return token.__strongJaIsWhitespace
13
+ }
14
+ if (!content) {
15
+ token.__strongJaWhitespaceSource = content
16
+ token.__strongJaIsWhitespace = true
17
+ return true
18
+ }
19
+ let isWhitespace = true
14
20
  for (let i = 0; i < content.length; i++) {
15
- if (!isWhiteSpace(content.charCodeAt(i))) return false
21
+ if (!isWhiteSpace(content.charCodeAt(i))) {
22
+ isWhitespace = false
23
+ break
24
+ }
16
25
  }
17
- return true
26
+ token.__strongJaWhitespaceSource = content
27
+ token.__strongJaIsWhitespace = isWhitespace
28
+ return isWhitespace
18
29
  }
19
30
 
20
- // Collapsed reference helpers
21
31
  const buildReferenceLabelRange = (tokens, startIdx, endIdx) => {
22
32
  if (startIdx > endIdx) return ''
23
33
  let label = ''
@@ -28,23 +38,15 @@ const buildReferenceLabelRange = (tokens, startIdx, endIdx) => {
28
38
  label += token.content
29
39
  } else if (token.type === 'softbreak' || token.type === 'hardbreak') {
30
40
  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) {
41
+ } else if (token.type && token.markup && (token.type.endsWith('_open') || token.type.endsWith('_close'))) {
34
42
  label += token.markup
35
43
  }
36
44
  }
37
45
  return label
38
46
  }
39
47
 
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
+ const normalizeReferenceCandidate = (state, text) => {
49
+ return getNormalizeRef(state)(text)
48
50
  }
49
51
 
50
52
  const getNormalizeRef = (state) => {
@@ -56,10 +58,6 @@ const getNormalizeRef = (state) => {
56
58
  return normalize
57
59
  }
58
60
 
59
- const normalizeRefKey = (state, label) => {
60
- return getNormalizeRef(state)(label)
61
- }
62
-
63
61
  const adjustTokenLevels = (tokens, startIdx, endIdx, delta) => {
64
62
  for (let i = startIdx; i < endIdx; i++) {
65
63
  if (tokens[i]) tokens[i].level += delta
@@ -102,12 +100,23 @@ const cloneTextToken = (source, content) => {
102
100
  Object.assign(newToken, source)
103
101
  newToken.content = content
104
102
  if (source.meta) newToken.meta = { ...source.meta }
105
- if (source.map) newToken.map = source.map
106
103
  return newToken
107
104
  }
108
105
 
109
- // Split only text tokens that actually contain bracket characters
110
- const splitBracketToken = (tokens, index, options) => {
106
+ const applyBracketSegmentFlags = (token, seg) => {
107
+ if (seg === '[' || seg === ']') {
108
+ token.__strongJaHasBracket = true
109
+ token.__strongJaBracketAtomic = true
110
+ } else if (seg === '[]') {
111
+ token.__strongJaHasBracket = true
112
+ token.__strongJaBracketAtomic = false
113
+ } else {
114
+ token.__strongJaHasBracket = false
115
+ token.__strongJaBracketAtomic = false
116
+ }
117
+ }
118
+
119
+ const splitBracketToken = (tokens, index) => {
111
120
  const token = tokens[index]
112
121
  if (!token || token.type !== 'text') return false
113
122
  if (token.__strongJaBracketAtomic) return false
@@ -126,82 +135,49 @@ const splitBracketToken = (tokens, index, options) => {
126
135
  }
127
136
  token.__strongJaHasBracket = true
128
137
  }
129
- const splitEmptyPair = options && options.splitEmptyPair
130
138
  const segments = []
131
- let buffer = ''
139
+ const contentLen = content.length
132
140
  let pos = 0
133
- while (pos < content.length) {
134
- if (!splitEmptyPair &&
135
- content.charCodeAt(pos) === CHAR_OPEN_BRACKET &&
141
+ let segmentStart = 0
142
+ while (pos < contentLen) {
143
+ const code = content.charCodeAt(pos)
144
+ if (code === CHAR_OPEN_BRACKET &&
136
145
  content.charCodeAt(pos + 1) === CHAR_CLOSE_BRACKET) {
137
- if (buffer) {
138
- segments.push(buffer)
139
- buffer = ''
146
+ if (segmentStart < pos) {
147
+ segments.push(content.slice(segmentStart, pos))
140
148
  }
141
149
  segments.push('[]')
142
150
  pos += 2
151
+ segmentStart = pos
143
152
  continue
144
153
  }
145
- const ch = content[pos]
146
- if (ch === '[' || ch === ']') {
147
- if (buffer) {
148
- segments.push(buffer)
149
- buffer = ''
154
+ if (code === CHAR_OPEN_BRACKET || code === CHAR_CLOSE_BRACKET) {
155
+ if (segmentStart < pos) {
156
+ segments.push(content.slice(segmentStart, pos))
150
157
  }
151
- segments.push(ch)
158
+ segments.push(code === CHAR_OPEN_BRACKET ? '[' : ']')
152
159
  pos++
160
+ segmentStart = pos
153
161
  continue
154
162
  }
155
- buffer += ch
156
163
  pos++
157
164
  }
158
- if (buffer) segments.push(buffer)
165
+ if (segmentStart < contentLen) segments.push(content.slice(segmentStart))
159
166
  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
- }
167
+ applyBracketSegmentFlags(token, segments[0])
176
168
  return false
177
169
  }
170
+
178
171
  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
172
+ applyBracketSegmentFlags(token, token.content)
173
+
174
+ const replacements = [token]
190
175
  for (let s = 1; s < segments.length; s++) {
191
176
  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++
177
+ applyBracketSegmentFlags(newToken, segments[s])
178
+ replacements.push(newToken)
204
179
  }
180
+ tokens.splice(index, 1, ...replacements)
205
181
  return true
206
182
  }
207
183
 
@@ -209,40 +185,26 @@ const isBracketToken = (token, bracket) => {
209
185
  return token && token.type === 'text' && token.content === bracket
210
186
  }
211
187
 
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)
188
+ const buildLinkCloseMap = (tokens, startIdx, endIdx) => {
189
+ const closeMap = new Map()
190
+ const stack = []
191
+ const max = tokens ? tokens.length - 1 : -1
192
+ const from = startIdx > 0 ? startIdx : 0
193
+ const to = endIdx < max ? endIdx : max
194
+ for (let i = from; i <= to; i++) {
195
+ const token = tokens[i]
196
+ if (!token) continue
197
+ if (token.type === 'link_open') {
198
+ stack.push(i)
237
199
  continue
238
200
  }
239
- token.content = token.content.slice(remaining)
240
- remaining = 0
201
+ if (token.type !== 'link_close' || stack.length === 0) continue
202
+ closeMap.set(stack.pop(), i)
241
203
  }
242
- return remaining === 0
204
+ return closeMap
243
205
  }
244
206
 
245
- const wrapLabelTokensWithLink = (tokens, labelStartIdx, labelEndIdx, linkOpenToken, linkCloseToken, labelSource) => {
207
+ const wrapLabelTokensWithLink = (tokens, labelStartIdx, labelEndIdx, linkOpenToken, linkCloseToken) => {
246
208
  const wrapperPairs = []
247
209
  let startIdx = labelStartIdx
248
210
  let endIdx = labelEndIdx
@@ -250,7 +212,7 @@ const wrapLabelTokensWithLink = (tokens, labelStartIdx, labelEndIdx, linkOpenTok
250
212
  const prevToken = tokens[startIdx - 1]
251
213
  const nextToken = tokens[endIdx + 1]
252
214
  if (!prevToken || !nextToken) break
253
- if (!/_close$/.test(prevToken.type)) break
215
+ if (!prevToken.type || !prevToken.type.endsWith('_close')) break
254
216
  const expectedOpen = prevToken.type.replace('_close', '_open')
255
217
  if (nextToken.type !== expectedOpen) break
256
218
  wrapperPairs.push({
@@ -266,17 +228,7 @@ const wrapLabelTokensWithLink = (tokens, labelStartIdx, labelEndIdx, linkOpenTok
266
228
  endIdx -= 1
267
229
  }
268
230
 
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
- }
231
+ if (startIdx > endIdx) return startIdx
280
232
 
281
233
  let labelLength = endIdx - startIdx + 1
282
234
  const firstLabelToken = tokens[startIdx]
@@ -320,282 +272,23 @@ const wrapLabelTokensWithLink = (tokens, labelStartIdx, labelEndIdx, linkOpenTok
320
272
  return startIdx + labelLength + 2
321
273
  }
322
274
 
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
275
  const convertCollapsedReferenceLinks = (tokens, state) => {
587
276
  const references = state.env && state.env.references
588
277
  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) {
278
+ let referenceCount = state.__strongJaReferenceCount
279
+ if (referenceCount === undefined) {
280
+ referenceCount = Object.keys(references).length
281
+ state.__strongJaReferenceCount = referenceCount
282
+ }
283
+ if (referenceCount === 0) {
593
284
  return
594
285
  }
595
286
 
596
287
  let i = 0
288
+ let linkCloseMap = null
597
289
  while (i < tokens.length) {
598
290
  if (splitBracketToken(tokens, i)) {
291
+ linkCloseMap = null
599
292
  continue
600
293
  }
601
294
  if (!isBracketToken(tokens[i], '[')) {
@@ -604,10 +297,12 @@ const convertCollapsedReferenceLinks = (tokens, state) => {
604
297
  }
605
298
  let closeIdx = i + 1
606
299
  while (closeIdx < tokens.length && !isBracketToken(tokens[closeIdx], ']')) {
607
- if (splitBracketToken(tokens, closeIdx)) {
300
+ const closeToken = tokens[closeIdx]
301
+ if (closeToken && closeToken.type === 'text' && splitBracketToken(tokens, closeIdx)) {
302
+ linkCloseMap = null
608
303
  continue
609
304
  }
610
- if (tokens[closeIdx].type === 'link_open') {
305
+ if (closeToken && closeToken.type === 'link_open') {
611
306
  closeIdx = -1
612
307
  break
613
308
  }
@@ -627,13 +322,18 @@ const convertCollapsedReferenceLinks = (tokens, state) => {
627
322
  const labelEnd = closeIdx - 1
628
323
  const labelLength = closeIdx - i - 1
629
324
  const labelText = buildReferenceLabelRange(tokens, labelStart, labelEnd)
630
- const cleanedLabel = cleanLabelText(labelText)
325
+ if (labelText.indexOf('*') === -1 && labelText.indexOf('_') === -1) {
326
+ i++
327
+ continue
328
+ }
631
329
  const whitespaceStart = closeIdx + 1
632
330
  let refRemoveStart = whitespaceStart
633
331
  while (refRemoveStart < tokens.length && isWhitespaceToken(tokens[refRemoveStart])) {
634
332
  refRemoveStart++
635
333
  }
636
- if (splitBracketToken(tokens, refRemoveStart)) {
334
+ const refStartToken = tokens[refRemoveStart]
335
+ if (refStartToken && refStartToken.type === 'text' && splitBracketToken(tokens, refRemoveStart)) {
336
+ linkCloseMap = null
637
337
  continue
638
338
  }
639
339
  const whitespaceCount = refRemoveStart - whitespaceStart
@@ -643,7 +343,7 @@ const convertCollapsedReferenceLinks = (tokens, state) => {
643
343
  let existingLinkClose = null
644
344
  const nextToken = tokens[refRemoveStart]
645
345
  if (isBracketToken(nextToken, '[]')) {
646
- refKey = normalizeReferenceCandidate(state, cleanedLabel)
346
+ refKey = normalizeReferenceCandidate(state, labelText)
647
347
  refRemoveCount = 1
648
348
  } else if (isBracketToken(nextToken, '[')) {
649
349
  let refCloseIdx = refRemoveStart + 1
@@ -657,14 +357,17 @@ const convertCollapsedReferenceLinks = (tokens, state) => {
657
357
  const refStart = refRemoveStart + 1
658
358
  const refEnd = refCloseIdx - 1
659
359
  if (refStart > refEnd) {
660
- refKey = normalizeReferenceCandidate(state, cleanedLabel)
360
+ refKey = normalizeReferenceCandidate(state, labelText)
661
361
  } else {
662
362
  const refLabelText = buildReferenceLabelRange(tokens, refStart, refEnd)
663
363
  refKey = normalizeReferenceCandidate(state, refLabelText)
664
364
  }
665
365
  refRemoveCount = refCloseIdx - refRemoveStart + 1
666
366
  } else if (nextToken && nextToken.type === 'link_open') {
667
- const linkCloseIdx = findLinkCloseIndex(tokens, refRemoveStart)
367
+ if (linkCloseMap === null) {
368
+ linkCloseMap = buildLinkCloseMap(tokens, refRemoveStart, tokens.length - 1)
369
+ }
370
+ const linkCloseIdx = linkCloseMap.get(refRemoveStart) ?? -1
668
371
  if (linkCloseIdx === -1) {
669
372
  i++
670
373
  continue
@@ -679,6 +382,7 @@ const convertCollapsedReferenceLinks = (tokens, state) => {
679
382
  let linkOpenToken = null
680
383
  let linkCloseToken = null
681
384
  if (existingLinkOpen && existingLinkClose) {
385
+ linkCloseMap = null
682
386
  if (whitespaceCount > 0) {
683
387
  tokens.splice(whitespaceStart, whitespaceCount)
684
388
  refRemoveStart -= whitespaceCount
@@ -698,6 +402,7 @@ const convertCollapsedReferenceLinks = (tokens, state) => {
698
402
  i++
699
403
  continue
700
404
  }
405
+ linkCloseMap = null
701
406
  if (whitespaceCount > 0) {
702
407
  tokens.splice(whitespaceStart, whitespaceCount)
703
408
  refRemoveStart -= whitespaceCount
@@ -722,12 +427,12 @@ const convertCollapsedReferenceLinks = (tokens, state) => {
722
427
  }
723
428
  }
724
429
 
725
- // Link cleanup helpers
726
430
  const mergeBrokenMarksAroundLinks = (tokens) => {
727
431
  let i = 0
728
432
  while (i < tokens.length) {
729
433
  const closeToken = tokens[i]
730
- if (!closeToken || !/_close$/.test(closeToken.type)) {
434
+ if (!closeToken || !closeToken.type ||
435
+ (closeToken.type !== 'em_close' && closeToken.type !== 'strong_close')) {
731
436
  i++
732
437
  continue
733
438
  }
@@ -766,8 +471,7 @@ const mergeBrokenMarksAroundLinks = (tokens) => {
766
471
 
767
472
  export {
768
473
  normalizeReferenceCandidate,
769
- restoreLabelWhitespace,
770
- convertInlineLinks,
474
+ buildLinkCloseMap,
771
475
  convertCollapsedReferenceLinks,
772
476
  mergeBrokenMarksAroundLinks,
773
477
  getMapFromTokenRange