@peaceroad/markdown-it-strong-ja 0.7.2 → 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.
- package/README.md +326 -195
- package/index.js +27 -40
- package/package.json +26 -6
- package/src/token-compat.js +71 -22
- package/src/token-core.js +521 -132
- package/src/token-link-utils.js +434 -539
- package/src/token-postprocess/broken-ref.js +475 -0
- package/src/token-postprocess/fastpaths.js +349 -0
- package/src/token-postprocess/guards.js +499 -0
- package/src/token-postprocess/orchestrator.js +672 -0
- package/src/token-postprocess.js +1 -334
- package/src/token-utils.js +215 -142
package/src/token-link-utils.js
CHANGED
|
@@ -1,23 +1,54 @@
|
|
|
1
1
|
import Token from 'markdown-it/lib/token.mjs'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { isWhiteSpace } from 'markdown-it/lib/common/utils.mjs'
|
|
3
|
+
import { getReferenceCount } from './token-utils.js'
|
|
4
4
|
|
|
5
5
|
const CHAR_OPEN_BRACKET = 0x5B // [
|
|
6
6
|
const CHAR_CLOSE_BRACKET = 0x5D // ]
|
|
7
|
-
const CHAR_OPEN_PAREN = 0x28 // (
|
|
8
|
-
const CHAR_CLOSE_PAREN = 0x29 // )
|
|
9
7
|
|
|
10
8
|
const isWhitespaceToken = (token) => {
|
|
11
9
|
if (!token || token.type !== 'text') return false
|
|
12
10
|
const content = token.content
|
|
13
|
-
if (
|
|
11
|
+
if (token.__strongJaWhitespaceSource === content &&
|
|
12
|
+
typeof token.__strongJaIsWhitespace === 'boolean') {
|
|
13
|
+
return token.__strongJaIsWhitespace
|
|
14
|
+
}
|
|
15
|
+
if (!content) {
|
|
16
|
+
token.__strongJaWhitespaceSource = content
|
|
17
|
+
token.__strongJaIsWhitespace = true
|
|
18
|
+
return true
|
|
19
|
+
}
|
|
20
|
+
let isWhitespace = true
|
|
14
21
|
for (let i = 0; i < content.length; i++) {
|
|
15
|
-
if (!isWhiteSpace(content.charCodeAt(i)))
|
|
22
|
+
if (!isWhiteSpace(content.charCodeAt(i))) {
|
|
23
|
+
isWhitespace = false
|
|
24
|
+
break
|
|
25
|
+
}
|
|
16
26
|
}
|
|
17
|
-
|
|
27
|
+
token.__strongJaWhitespaceSource = content
|
|
28
|
+
token.__strongJaIsWhitespace = isWhitespace
|
|
29
|
+
return isWhitespace
|
|
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
|
|
18
50
|
}
|
|
19
51
|
|
|
20
|
-
// Collapsed reference helpers
|
|
21
52
|
const buildReferenceLabelRange = (tokens, startIdx, endIdx) => {
|
|
22
53
|
if (startIdx > endIdx) return ''
|
|
23
54
|
let label = ''
|
|
@@ -28,23 +59,15 @@ const buildReferenceLabelRange = (tokens, startIdx, endIdx) => {
|
|
|
28
59
|
label += token.content
|
|
29
60
|
} else if (token.type === 'softbreak' || token.type === 'hardbreak') {
|
|
30
61
|
label += ' '
|
|
31
|
-
} else if (token.type && token.type.endsWith('_open')
|
|
32
|
-
label += token.markup
|
|
33
|
-
} else if (token.type && token.type.endsWith('_close') && token.markup) {
|
|
62
|
+
} else if (token.type && token.markup && (token.type.endsWith('_open') || token.type.endsWith('_close'))) {
|
|
34
63
|
label += token.markup
|
|
35
64
|
}
|
|
36
65
|
}
|
|
37
66
|
return label
|
|
38
67
|
}
|
|
39
68
|
|
|
40
|
-
const
|
|
41
|
-
|
|
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)
|
|
69
|
+
const normalizeReferenceCandidate = (state, text) => {
|
|
70
|
+
return getNormalizeRef(state)(text)
|
|
48
71
|
}
|
|
49
72
|
|
|
50
73
|
const getNormalizeRef = (state) => {
|
|
@@ -56,15 +79,6 @@ const getNormalizeRef = (state) => {
|
|
|
56
79
|
return normalize
|
|
57
80
|
}
|
|
58
81
|
|
|
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
82
|
|
|
69
83
|
const cloneMap = (map) => {
|
|
70
84
|
if (!map || !Array.isArray(map)) return null
|
|
@@ -102,12 +116,23 @@ const cloneTextToken = (source, content) => {
|
|
|
102
116
|
Object.assign(newToken, source)
|
|
103
117
|
newToken.content = content
|
|
104
118
|
if (source.meta) newToken.meta = { ...source.meta }
|
|
105
|
-
if (source.map) newToken.map = source.map
|
|
106
119
|
return newToken
|
|
107
120
|
}
|
|
108
121
|
|
|
109
|
-
|
|
110
|
-
|
|
122
|
+
const applyBracketSegmentFlags = (token, seg) => {
|
|
123
|
+
if (seg === '[' || seg === ']') {
|
|
124
|
+
token.__strongJaHasBracket = true
|
|
125
|
+
token.__strongJaBracketAtomic = true
|
|
126
|
+
} else if (seg === '[]') {
|
|
127
|
+
token.__strongJaHasBracket = true
|
|
128
|
+
token.__strongJaBracketAtomic = true
|
|
129
|
+
} else {
|
|
130
|
+
token.__strongJaHasBracket = false
|
|
131
|
+
token.__strongJaBracketAtomic = false
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const splitBracketToken = (tokens, index) => {
|
|
111
136
|
const token = tokens[index]
|
|
112
137
|
if (!token || token.type !== 'text') return false
|
|
113
138
|
if (token.__strongJaBracketAtomic) return false
|
|
@@ -126,82 +151,49 @@ const splitBracketToken = (tokens, index, options) => {
|
|
|
126
151
|
}
|
|
127
152
|
token.__strongJaHasBracket = true
|
|
128
153
|
}
|
|
129
|
-
const splitEmptyPair = options && options.splitEmptyPair
|
|
130
154
|
const segments = []
|
|
131
|
-
|
|
155
|
+
const contentLen = content.length
|
|
132
156
|
let pos = 0
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
157
|
+
let segmentStart = 0
|
|
158
|
+
while (pos < contentLen) {
|
|
159
|
+
const code = content.charCodeAt(pos)
|
|
160
|
+
if (code === CHAR_OPEN_BRACKET &&
|
|
136
161
|
content.charCodeAt(pos + 1) === CHAR_CLOSE_BRACKET) {
|
|
137
|
-
if (
|
|
138
|
-
segments.push(
|
|
139
|
-
buffer = ''
|
|
162
|
+
if (segmentStart < pos) {
|
|
163
|
+
segments.push(content.slice(segmentStart, pos))
|
|
140
164
|
}
|
|
141
165
|
segments.push('[]')
|
|
142
166
|
pos += 2
|
|
167
|
+
segmentStart = pos
|
|
143
168
|
continue
|
|
144
169
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
segments.push(buffer)
|
|
149
|
-
buffer = ''
|
|
170
|
+
if (code === CHAR_OPEN_BRACKET || code === CHAR_CLOSE_BRACKET) {
|
|
171
|
+
if (segmentStart < pos) {
|
|
172
|
+
segments.push(content.slice(segmentStart, pos))
|
|
150
173
|
}
|
|
151
|
-
segments.push(
|
|
174
|
+
segments.push(code === CHAR_OPEN_BRACKET ? '[' : ']')
|
|
152
175
|
pos++
|
|
176
|
+
segmentStart = pos
|
|
153
177
|
continue
|
|
154
178
|
}
|
|
155
|
-
buffer += ch
|
|
156
179
|
pos++
|
|
157
180
|
}
|
|
158
|
-
if (
|
|
181
|
+
if (segmentStart < contentLen) segments.push(content.slice(segmentStart))
|
|
159
182
|
if (segments.length <= 1) {
|
|
160
|
-
|
|
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
|
-
}
|
|
183
|
+
applyBracketSegmentFlags(token, segments[0])
|
|
176
184
|
return false
|
|
177
185
|
}
|
|
186
|
+
|
|
178
187
|
token.content = segments[0]
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
|
188
|
+
applyBracketSegmentFlags(token, token.content)
|
|
189
|
+
|
|
190
|
+
const replacements = [token]
|
|
190
191
|
for (let s = 1; s < segments.length; s++) {
|
|
191
192
|
const newToken = cloneTextToken(token, segments[s])
|
|
192
|
-
|
|
193
|
-
|
|
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++
|
|
193
|
+
applyBracketSegmentFlags(newToken, segments[s])
|
|
194
|
+
replacements.push(newToken)
|
|
204
195
|
}
|
|
196
|
+
tokens.splice(index, 1, ...replacements)
|
|
205
197
|
return true
|
|
206
198
|
}
|
|
207
199
|
|
|
@@ -209,48 +201,36 @@ const isBracketToken = (token, bracket) => {
|
|
|
209
201
|
return token && token.type === 'text' && token.content === bracket
|
|
210
202
|
}
|
|
211
203
|
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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)
|
|
204
|
+
const buildLinkCloseMap = (tokens, startIdx, endIdx) => {
|
|
205
|
+
const closeMap = new Map()
|
|
206
|
+
const stack = []
|
|
207
|
+
const max = tokens ? tokens.length - 1 : -1
|
|
208
|
+
const from = startIdx > 0 ? startIdx : 0
|
|
209
|
+
const to = endIdx < max ? endIdx : max
|
|
210
|
+
for (let i = from; i <= to; i++) {
|
|
211
|
+
const token = tokens[i]
|
|
212
|
+
if (!token) continue
|
|
213
|
+
if (token.type === 'link_open') {
|
|
214
|
+
stack.push(i)
|
|
237
215
|
continue
|
|
238
216
|
}
|
|
239
|
-
token.
|
|
240
|
-
|
|
217
|
+
if (token.type !== 'link_close' || stack.length === 0) continue
|
|
218
|
+
closeMap.set(stack.pop(), i)
|
|
241
219
|
}
|
|
242
|
-
return
|
|
220
|
+
return closeMap
|
|
243
221
|
}
|
|
244
222
|
|
|
245
|
-
const
|
|
223
|
+
const collectWrappedLabelPairs = (tokens, collapsedStartIdx, collapsedEndIdx) => {
|
|
246
224
|
const wrapperPairs = []
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
|
|
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]
|
|
252
232
|
if (!prevToken || !nextToken) break
|
|
253
|
-
if (
|
|
233
|
+
if (!prevToken.type || !prevToken.type.endsWith('_close')) break
|
|
254
234
|
const expectedOpen = prevToken.type.replace('_close', '_open')
|
|
255
235
|
if (nextToken.type !== expectedOpen) break
|
|
256
236
|
wrapperPairs.push({
|
|
@@ -258,483 +238,377 @@ const wrapLabelTokensWithLink = (tokens, labelStartIdx, labelEndIdx, linkOpenTok
|
|
|
258
238
|
tag: prevToken.tag,
|
|
259
239
|
markup: prevToken.markup,
|
|
260
240
|
openMap: cloneMap(nextToken.map),
|
|
261
|
-
closeMap: cloneMap(prevToken.map)
|
|
241
|
+
closeMap: cloneMap(prevToken.map),
|
|
242
|
+
closeIdx,
|
|
243
|
+
openIdx
|
|
262
244
|
})
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
placeholder.content = labelSource
|
|
273
|
-
placeholder.level = linkOpenToken.level + 1
|
|
274
|
-
tokens.splice(startIdx, 0, placeholder)
|
|
275
|
-
endIdx = startIdx
|
|
276
|
-
} else {
|
|
277
|
-
return startIdx
|
|
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
|
|
278
254
|
}
|
|
279
255
|
}
|
|
256
|
+
const outerPair = wrapperPairs[wrapperPairs.length - 1]
|
|
257
|
+
return {
|
|
258
|
+
replaceStart: outerPair.closeIdx,
|
|
259
|
+
replaceEnd: outerPair.openIdx
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const resolveInsertedWrapperMap = (pairMap, labelMap) => {
|
|
264
|
+
return pairMap || labelMap
|
|
265
|
+
}
|
|
280
266
|
|
|
281
|
-
|
|
282
|
-
const firstLabelToken =
|
|
267
|
+
const buildWrappedLabelReplacement = (labelTokens, linkOpenToken, linkCloseToken, wrapperPairs, labelMap) => {
|
|
268
|
+
const firstLabelToken = labelTokens[0]
|
|
283
269
|
const linkLevel = firstLabelToken ? Math.max(firstLabelToken.level - 1, 0) : 0
|
|
284
270
|
linkOpenToken.level = linkLevel
|
|
285
271
|
linkCloseToken.level = linkLevel
|
|
286
|
-
const labelMap = getMapFromTokenRange(tokens, startIdx, endIdx) || getNearbyMap(tokens, startIdx, endIdx)
|
|
287
272
|
if (labelMap) {
|
|
288
273
|
if (!linkOpenToken.map) linkOpenToken.map = cloneMap(labelMap)
|
|
289
274
|
if (!linkCloseToken.map) linkCloseToken.map = cloneMap(labelMap)
|
|
290
275
|
}
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
}
|
|
276
|
+
for (let idx = 0; idx < labelTokens.length; idx++) {
|
|
277
|
+
if (labelTokens[idx]) labelTokens[idx].level += 1
|
|
318
278
|
}
|
|
319
279
|
|
|
320
|
-
|
|
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
|
|
321
302
|
}
|
|
322
303
|
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (
|
|
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
|
+
}
|
|
333
332
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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++
|
|
352
|
+
}
|
|
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)
|
|
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
|
|
338
370
|
} else {
|
|
339
|
-
|
|
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
|
|
371
|
+
return null
|
|
347
372
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
373
|
+
return {
|
|
374
|
+
refKey,
|
|
375
|
+
refRemoveCount,
|
|
376
|
+
existingLinkOpen,
|
|
377
|
+
existingLinkClose
|
|
353
378
|
}
|
|
379
|
+
}
|
|
354
380
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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 }
|
|
393
|
+
}
|
|
394
|
+
|
|
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
|
|
364
401
|
}
|
|
365
402
|
}
|
|
403
|
+
if (!target.refKey) return null
|
|
404
|
+
return buildAutoCollapsedReferenceLinkPair(references[target.refKey])
|
|
405
|
+
}
|
|
366
406
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
+
)
|
|
372
430
|
}
|
|
373
431
|
|
|
374
|
-
const
|
|
432
|
+
const COLLAPSED_REFERENCE_SCAN_RETRY = Symbol('collapsed-reference-scan-retry')
|
|
433
|
+
const COLLAPSED_REFERENCE_SCAN_SKIP = Symbol('collapsed-reference-scan-skip')
|
|
375
434
|
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
}
|
|
435
|
+
const createCollapsedReferenceLinkCloseMapAccessors = (tokens, cache = null) => {
|
|
436
|
+
let linkCloseMap = null
|
|
437
|
+
const getLinkCloseMap = (startIdx = 0) => {
|
|
438
|
+
if (cache) {
|
|
439
|
+
if (cache.linkCloseMap === undefined) {
|
|
440
|
+
cache.linkCloseMap = buildLinkCloseMap(tokens, 0, tokens.length - 1)
|
|
396
441
|
}
|
|
397
|
-
|
|
442
|
+
return cache.linkCloseMap
|
|
398
443
|
}
|
|
399
|
-
if (
|
|
400
|
-
|
|
444
|
+
if (linkCloseMap === null) {
|
|
445
|
+
linkCloseMap = buildLinkCloseMap(tokens, startIdx, tokens.length - 1)
|
|
401
446
|
}
|
|
402
|
-
|
|
447
|
+
return linkCloseMap
|
|
448
|
+
}
|
|
449
|
+
const invalidateLinkCloseMap = () => {
|
|
450
|
+
linkCloseMap = null
|
|
451
|
+
if (cache) cache.linkCloseMap = undefined
|
|
403
452
|
}
|
|
453
|
+
return { getLinkCloseMap, invalidateLinkCloseMap }
|
|
404
454
|
}
|
|
405
455
|
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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++
|
|
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
|
|
464
|
+
}
|
|
465
|
+
if (closeToken && closeToken.type === 'link_open') return -1
|
|
466
|
+
closeIdx++
|
|
443
467
|
}
|
|
468
|
+
return -1
|
|
444
469
|
}
|
|
445
470
|
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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
|
|
451
489
|
}
|
|
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
490
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
-
}
|
|
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
|
+
}
|
|
573
501
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
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
|
|
580
516
|
}
|
|
581
|
-
|
|
582
|
-
|
|
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
|
|
583
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
|
+
)
|
|
584
563
|
}
|
|
585
564
|
|
|
586
|
-
const convertCollapsedReferenceLinks = (tokens, state) => {
|
|
565
|
+
const convertCollapsedReferenceLinks = (tokens, state, cache = null, onChangeStart = null) => {
|
|
587
566
|
const references = state.env && state.env.references
|
|
588
|
-
if (!references) return
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
if (referenceCount === 0) return
|
|
592
|
-
} else if (Object.keys(references).length === 0) {
|
|
593
|
-
return
|
|
567
|
+
if (!references) return false
|
|
568
|
+
if (getReferenceCount(state) === 0) {
|
|
569
|
+
return false
|
|
594
570
|
}
|
|
595
571
|
|
|
572
|
+
let changed = false
|
|
596
573
|
let i = 0
|
|
574
|
+
const { getLinkCloseMap, invalidateLinkCloseMap } = createCollapsedReferenceLinkCloseMapAccessors(tokens, cache)
|
|
597
575
|
while (i < tokens.length) {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
i
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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) {
|
|
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) {
|
|
622
587
|
i++
|
|
623
588
|
continue
|
|
624
589
|
}
|
|
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)
|
|
590
|
+
changed = true
|
|
721
591
|
i = nextIndex
|
|
722
592
|
}
|
|
593
|
+
return changed
|
|
723
594
|
}
|
|
724
595
|
|
|
725
|
-
|
|
726
|
-
const
|
|
596
|
+
const collectBrokenMarkLinkMergeRemovals = (tokens) => {
|
|
597
|
+
const removals = []
|
|
727
598
|
let i = 0
|
|
728
599
|
while (i < tokens.length) {
|
|
729
600
|
const closeToken = tokens[i]
|
|
730
|
-
if (!closeToken ||
|
|
601
|
+
if (!closeToken || !closeToken.type ||
|
|
602
|
+
(closeToken.type !== 'em_close' && closeToken.type !== 'strong_close')) {
|
|
731
603
|
i++
|
|
732
604
|
continue
|
|
733
605
|
}
|
|
734
606
|
const openType = closeToken.type.replace('_close', '_open')
|
|
735
607
|
let j = i + 1
|
|
736
608
|
while (j < tokens.length && isWhitespaceToken(tokens[j])) j++
|
|
737
|
-
if (j >= tokens.length ||
|
|
609
|
+
if (j >= tokens.length ||
|
|
610
|
+
tokens[j].type !== 'link_open' ||
|
|
611
|
+
tokens[j].__strongJaMergeMarksAroundLink !== true) {
|
|
738
612
|
i++
|
|
739
613
|
continue
|
|
740
614
|
}
|
|
@@ -759,15 +633,36 @@ const mergeBrokenMarksAroundLinks = (tokens) => {
|
|
|
759
633
|
i++
|
|
760
634
|
continue
|
|
761
635
|
}
|
|
762
|
-
|
|
763
|
-
|
|
636
|
+
removals.push({ closeIdx: i, reopenIdx: j })
|
|
637
|
+
i = j + 1
|
|
764
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)
|
|
765
661
|
}
|
|
766
662
|
|
|
767
663
|
export {
|
|
768
664
|
normalizeReferenceCandidate,
|
|
769
|
-
|
|
770
|
-
convertInlineLinks,
|
|
665
|
+
buildLinkCloseMap,
|
|
771
666
|
convertCollapsedReferenceLinks,
|
|
772
667
|
mergeBrokenMarksAroundLinks,
|
|
773
668
|
getMapFromTokenRange
|