@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
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
import Token from 'markdown-it/lib/token.mjs'
|
|
2
|
+
import { buildLinkCloseMap, convertCollapsedReferenceLinks, mergeBrokenMarksAroundLinks } from '../token-link-utils.js'
|
|
3
|
+
import { computeMaxBrokenRefRepairPass, runBrokenRefRepairs } from './broken-ref.js'
|
|
4
|
+
import {
|
|
5
|
+
rebuildInlineLevels,
|
|
6
|
+
rebuildInlineLevelsFrom,
|
|
7
|
+
fixEmOuterStrongSequence,
|
|
8
|
+
fixLeadingAsteriskEm,
|
|
9
|
+
fixTrailingStrong
|
|
10
|
+
} from '../token-core.js'
|
|
11
|
+
import {
|
|
12
|
+
getRuntimeOpt,
|
|
13
|
+
getReferenceCount
|
|
14
|
+
} from '../token-utils.js'
|
|
15
|
+
import {
|
|
16
|
+
hasMarkerChars,
|
|
17
|
+
hasJapaneseContextInRange,
|
|
18
|
+
hasEmphasisSignalInRange,
|
|
19
|
+
buildAsteriskWrapperPrefixStats,
|
|
20
|
+
scanInlinePostprocessSignals
|
|
21
|
+
} from './guards.js'
|
|
22
|
+
import {
|
|
23
|
+
tryFixTailPatternTokenOnly,
|
|
24
|
+
tryFixTailDanglingStrongCloseTokenOnly
|
|
25
|
+
} from './fastpaths.js'
|
|
26
|
+
|
|
27
|
+
const fallbackMarkupByType = (type) => {
|
|
28
|
+
if (type === 'strong_open' || type === 'strong_close') return '**'
|
|
29
|
+
if (type === 'em_open' || type === 'em_close') return '*'
|
|
30
|
+
return ''
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const makeTokenLiteralText = (token) => {
|
|
34
|
+
if (!token) return
|
|
35
|
+
const literal = token.markup || fallbackMarkupByType(token.type)
|
|
36
|
+
token.type = 'text'
|
|
37
|
+
token.tag = ''
|
|
38
|
+
token.nesting = 0
|
|
39
|
+
token.content = literal
|
|
40
|
+
token.markup = ''
|
|
41
|
+
token.info = ''
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const sanitizeEmStrongBalance = (tokens, onChangeStart = null) => {
|
|
45
|
+
if (!tokens || tokens.length === 0) return false
|
|
46
|
+
const stack = []
|
|
47
|
+
let changed = false
|
|
48
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
49
|
+
const token = tokens[i]
|
|
50
|
+
if (!token || !token.type) continue
|
|
51
|
+
if (token.type === 'strong_open' || token.type === 'em_open') {
|
|
52
|
+
stack.push({ type: token.type, idx: i })
|
|
53
|
+
continue
|
|
54
|
+
}
|
|
55
|
+
if (token.type !== 'strong_close' && token.type !== 'em_close') continue
|
|
56
|
+
const expected = token.type === 'strong_close' ? 'strong_open' : 'em_open'
|
|
57
|
+
if (stack.length > 0 && stack[stack.length - 1].type === expected) {
|
|
58
|
+
stack.pop()
|
|
59
|
+
continue
|
|
60
|
+
}
|
|
61
|
+
if (onChangeStart) onChangeStart(i)
|
|
62
|
+
makeTokenLiteralText(token)
|
|
63
|
+
changed = true
|
|
64
|
+
}
|
|
65
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
66
|
+
const entry = stack[i]
|
|
67
|
+
const token = tokens[entry.idx]
|
|
68
|
+
if (!token) continue
|
|
69
|
+
if (onChangeStart) onChangeStart(entry.idx)
|
|
70
|
+
makeTokenLiteralText(token)
|
|
71
|
+
changed = true
|
|
72
|
+
}
|
|
73
|
+
return changed
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const getPostprocessMetrics = (state) => {
|
|
77
|
+
if (!state || !state.env) return null
|
|
78
|
+
const metrics = state.env.__strongJaPostprocessMetrics
|
|
79
|
+
if (!metrics || typeof metrics !== 'object') return null
|
|
80
|
+
return metrics
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const buildInlinePostprocessFacts = (children, inlineContent) => {
|
|
84
|
+
const preScan = scanInlinePostprocessSignals(children)
|
|
85
|
+
return {
|
|
86
|
+
hasBracketText: inlineContent.indexOf('[') !== -1 || inlineContent.indexOf(']') !== -1,
|
|
87
|
+
hasEmphasis: preScan.hasEmphasis,
|
|
88
|
+
hasLinkOpen: preScan.hasLinkOpen,
|
|
89
|
+
hasLinkClose: preScan.hasLinkClose,
|
|
90
|
+
hasCodeInline: undefined,
|
|
91
|
+
referenceCount: undefined,
|
|
92
|
+
metrics: undefined,
|
|
93
|
+
linkCloseMap: undefined,
|
|
94
|
+
wrapperPrefixStats: undefined,
|
|
95
|
+
rebuildLevelStart: undefined
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const ensureInlineHasCodeInline = (facts, tokens) => {
|
|
100
|
+
if (facts.hasCodeInline !== undefined) return facts.hasCodeInline
|
|
101
|
+
let hasCodeInline = false
|
|
102
|
+
if (tokens && tokens.length > 0) {
|
|
103
|
+
for (let idx = 0; idx < tokens.length; idx++) {
|
|
104
|
+
if (tokens[idx] && tokens[idx].type === 'code_inline') {
|
|
105
|
+
hasCodeInline = true
|
|
106
|
+
break
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
facts.hasCodeInline = hasCodeInline
|
|
111
|
+
return hasCodeInline
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const ensureInlineReferenceCount = (facts, state) => {
|
|
115
|
+
if (!facts || !facts.hasBracketText) return 0
|
|
116
|
+
if (facts.referenceCount === undefined) {
|
|
117
|
+
facts.referenceCount = getReferenceCount(state)
|
|
118
|
+
}
|
|
119
|
+
return facts.referenceCount
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const ensureInlineMetrics = (facts, state) => {
|
|
123
|
+
if (!facts) return null
|
|
124
|
+
if (facts.metrics === undefined) {
|
|
125
|
+
facts.metrics = getPostprocessMetrics(state)
|
|
126
|
+
}
|
|
127
|
+
return facts.metrics
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const ensureInlineLinkCloseMap = (facts, tokens) => {
|
|
131
|
+
if (!tokens || tokens.length === 0) return new Map()
|
|
132
|
+
if (!facts) return buildLinkCloseMap(tokens, 0, tokens.length - 1)
|
|
133
|
+
if (facts.linkCloseMap === undefined) {
|
|
134
|
+
facts.linkCloseMap = buildLinkCloseMap(tokens, 0, tokens.length - 1)
|
|
135
|
+
}
|
|
136
|
+
return facts.linkCloseMap
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const ensureInlineWrapperPrefixStats = (facts, tokens) => {
|
|
140
|
+
if (!tokens || tokens.length === 0) return null
|
|
141
|
+
if (!facts) return buildAsteriskWrapperPrefixStats(tokens)
|
|
142
|
+
if (facts.wrapperPrefixStats === undefined) {
|
|
143
|
+
facts.wrapperPrefixStats = buildAsteriskWrapperPrefixStats(tokens)
|
|
144
|
+
}
|
|
145
|
+
return facts.wrapperPrefixStats
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const invalidateInlineDerivedCaches = (facts) => {
|
|
149
|
+
if (!facts) return
|
|
150
|
+
facts.linkCloseMap = undefined
|
|
151
|
+
facts.wrapperPrefixStats = undefined
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const markInlineLevelRebuildFrom = (facts, startIdx) => {
|
|
155
|
+
if (!facts) return
|
|
156
|
+
const from = startIdx > 0 ? startIdx : 0
|
|
157
|
+
if (facts.rebuildLevelStart === undefined || from < facts.rebuildLevelStart) {
|
|
158
|
+
facts.rebuildLevelStart = from
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const rebuildInlineLevelsForFacts = (tokens, facts) => {
|
|
163
|
+
if (!facts || facts.rebuildLevelStart === undefined) {
|
|
164
|
+
rebuildInlineLevels(tokens)
|
|
165
|
+
} else {
|
|
166
|
+
rebuildInlineLevelsFrom(tokens, facts.rebuildLevelStart)
|
|
167
|
+
}
|
|
168
|
+
if (facts) {
|
|
169
|
+
facts.rebuildLevelStart = undefined
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const createInlineChangeMarker = (facts) => {
|
|
174
|
+
return (startIdx) => {
|
|
175
|
+
markInlineLevelRebuildFrom(facts, startIdx)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const finalizeInlineLinkRepairStage = (children, facts, markChangedFrom) => {
|
|
180
|
+
invalidateInlineDerivedCaches(facts)
|
|
181
|
+
if (!mergeBrokenMarksAroundLinks(children, markChangedFrom)) return false
|
|
182
|
+
invalidateInlineDerivedCaches(facts)
|
|
183
|
+
rebuildInlineLevelsForFacts(children, facts)
|
|
184
|
+
return true
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const BROKEN_REF_REPAIR_HOOKS = {
|
|
188
|
+
ensureLinkCloseMap: ensureInlineLinkCloseMap,
|
|
189
|
+
ensureWrapperPrefixStats: ensureInlineWrapperPrefixStats,
|
|
190
|
+
invalidateDerivedCaches: invalidateInlineDerivedCaches,
|
|
191
|
+
markLevelRebuildFrom: markInlineLevelRebuildFrom
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const bumpPostprocessMetric = (metrics, bucket, key) => {
|
|
195
|
+
if (!metrics || !bucket || !key) return
|
|
196
|
+
let table = metrics[bucket]
|
|
197
|
+
if (!table || typeof table !== 'object') {
|
|
198
|
+
table = Object.create(null)
|
|
199
|
+
metrics[bucket] = table
|
|
200
|
+
}
|
|
201
|
+
table[key] = (table[key] || 0) + 1
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const scanTailRepairCandidateAfterLinkClose = (tokens, linkCloseIdx) => {
|
|
205
|
+
if (!tokens || linkCloseIdx < 0 || linkCloseIdx >= tokens.length) return null
|
|
206
|
+
let startIdx = -1
|
|
207
|
+
let foundStrongClose = -1
|
|
208
|
+
let foundStrongOpen = -1
|
|
209
|
+
for (let j = linkCloseIdx + 1; j < tokens.length; j++) {
|
|
210
|
+
const node = tokens[j]
|
|
211
|
+
if (!node) continue
|
|
212
|
+
if (node.type === 'strong_open') {
|
|
213
|
+
foundStrongOpen = j
|
|
214
|
+
break
|
|
215
|
+
}
|
|
216
|
+
if (node.type === 'strong_close') {
|
|
217
|
+
foundStrongClose = j
|
|
218
|
+
break
|
|
219
|
+
}
|
|
220
|
+
if (node.type === 'text' && node.content && startIdx === -1) {
|
|
221
|
+
startIdx = j
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (foundStrongClose === -1 || foundStrongOpen !== -1) return null
|
|
225
|
+
if (startIdx === -1) startIdx = foundStrongClose
|
|
226
|
+
return { startIdx, strongCloseIdx: foundStrongClose }
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const tryRepairTailCandidate = (tokens, candidate, isJapaneseMode, metrics = null, onChangeStart = null) => {
|
|
230
|
+
if (!tokens || !candidate) return false
|
|
231
|
+
const startIdx = candidate.startIdx
|
|
232
|
+
const strongCloseIdx = candidate.strongCloseIdx
|
|
233
|
+
const endIdx = tokens.length - 1
|
|
234
|
+
if (isJapaneseMode && !hasJapaneseContextInRange(tokens, startIdx, endIdx)) return false
|
|
235
|
+
if (!hasEmphasisSignalInRange(tokens, startIdx, endIdx)) return false
|
|
236
|
+
if (tryFixTailPatternTokenOnly(tokens, startIdx, endIdx)) {
|
|
237
|
+
if (onChangeStart) onChangeStart(startIdx)
|
|
238
|
+
bumpPostprocessMetric(metrics, 'tailFastPaths', 'tail-pattern')
|
|
239
|
+
return true
|
|
240
|
+
}
|
|
241
|
+
if (tryFixTailDanglingStrongCloseTokenOnly(tokens, startIdx, strongCloseIdx)) {
|
|
242
|
+
if (onChangeStart) onChangeStart(startIdx)
|
|
243
|
+
bumpPostprocessMetric(metrics, 'tailFastPaths', 'tail-dangling-strong-close')
|
|
244
|
+
return true
|
|
245
|
+
}
|
|
246
|
+
return false
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const fixTailAfterLinkStrongClose = (tokens, isJapaneseMode, metrics = null, onChangeStart = null) => {
|
|
250
|
+
let strongDepth = 0
|
|
251
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
252
|
+
const t = tokens[i]
|
|
253
|
+
if (!t) continue
|
|
254
|
+
if (t.type === 'strong_open') strongDepth++
|
|
255
|
+
if (t.type === 'strong_close') {
|
|
256
|
+
if (strongDepth > 0) strongDepth--
|
|
257
|
+
}
|
|
258
|
+
if (t.type !== 'link_close') continue
|
|
259
|
+
if (strongDepth !== 0) continue
|
|
260
|
+
const candidate = scanTailRepairCandidateAfterLinkClose(tokens, i)
|
|
261
|
+
if (!candidate) continue
|
|
262
|
+
if (tryRepairTailCandidate(tokens, candidate, isJapaneseMode, metrics, onChangeStart)) return true
|
|
263
|
+
}
|
|
264
|
+
return false
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const cloneMap = (map) => {
|
|
268
|
+
if (!map || !Array.isArray(map)) return null
|
|
269
|
+
return [map[0], map[1]]
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const cloneTextToken = (source, content) => {
|
|
273
|
+
const token = new Token('text', '', 0)
|
|
274
|
+
Object.assign(token, source)
|
|
275
|
+
token.content = content
|
|
276
|
+
if (source.meta) token.meta = { ...source.meta }
|
|
277
|
+
return token
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const isSoftSpaceCode = (code) => {
|
|
281
|
+
return code === 0x20 || code === 0x09 || code === 0x3000
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const isAsciiWordCode = (code) => {
|
|
285
|
+
return (code >= 0x30 && code <= 0x39) ||
|
|
286
|
+
(code >= 0x41 && code <= 0x5A) ||
|
|
287
|
+
(code >= 0x61 && code <= 0x7A)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const textEndsAsciiWord = (text) => {
|
|
291
|
+
if (!text || text.length === 0) return false
|
|
292
|
+
return isAsciiWordCode(text.charCodeAt(text.length - 1))
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const textStartsAsciiWord = (text) => {
|
|
296
|
+
if (!text || text.length === 0) return false
|
|
297
|
+
return isAsciiWordCode(text.charCodeAt(0))
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const isEscapedMarkerAt = (content, index) => {
|
|
301
|
+
let slashCount = 0
|
|
302
|
+
for (let i = index - 1; i >= 0 && content.charCodeAt(i) === 0x5C; i--) {
|
|
303
|
+
slashCount++
|
|
304
|
+
}
|
|
305
|
+
return (slashCount % 2) === 1
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const findLastStandaloneStrongMarker = (content) => {
|
|
309
|
+
if (!content || content.length < 2) return -1
|
|
310
|
+
let pos = content.lastIndexOf('**')
|
|
311
|
+
while (pos !== -1) {
|
|
312
|
+
const prev = pos > 0 ? content.charCodeAt(pos - 1) : 0
|
|
313
|
+
const next = pos + 2 < content.length ? content.charCodeAt(pos + 2) : 0
|
|
314
|
+
if (prev !== 0x2A &&
|
|
315
|
+
next !== 0x2A &&
|
|
316
|
+
!isEscapedMarkerAt(content, pos)) {
|
|
317
|
+
return pos
|
|
318
|
+
}
|
|
319
|
+
pos = content.lastIndexOf('**', pos - 1)
|
|
320
|
+
}
|
|
321
|
+
return -1
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const hasLeadingStandaloneStrongMarker = (content) => {
|
|
325
|
+
if (!content || content.length < 2) return false
|
|
326
|
+
if (content.charCodeAt(0) !== 0x2A || content.charCodeAt(1) !== 0x2A) return false
|
|
327
|
+
if (content.length > 2 && content.charCodeAt(2) === 0x2A) return false
|
|
328
|
+
return true
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const tryPromoteStrongAroundInlineLink = (tokens, strictAsciiStrongGuard = false, facts = null) => {
|
|
332
|
+
if (!tokens || tokens.length < 3) return false
|
|
333
|
+
let changed = false
|
|
334
|
+
for (let i = 1; i < tokens.length - 1; i++) {
|
|
335
|
+
const linkOpen = tokens[i]
|
|
336
|
+
if (!linkOpen || linkOpen.type !== 'link_open') continue
|
|
337
|
+
const linkCloseMap = ensureInlineLinkCloseMap(facts, tokens)
|
|
338
|
+
const closeIdx = linkCloseMap.get(i) ?? -1
|
|
339
|
+
if (closeIdx === -1 || closeIdx + 1 >= tokens.length) continue
|
|
340
|
+
|
|
341
|
+
const left = tokens[i - 1]
|
|
342
|
+
const right = tokens[closeIdx + 1]
|
|
343
|
+
if (!left || left.type !== 'text' || !left.content) {
|
|
344
|
+
i = closeIdx
|
|
345
|
+
continue
|
|
346
|
+
}
|
|
347
|
+
if (!right || right.type !== 'text' || !right.content) {
|
|
348
|
+
i = closeIdx
|
|
349
|
+
continue
|
|
350
|
+
}
|
|
351
|
+
if (!hasLeadingStandaloneStrongMarker(right.content)) {
|
|
352
|
+
i = closeIdx
|
|
353
|
+
continue
|
|
354
|
+
}
|
|
355
|
+
const markerPos = findLastStandaloneStrongMarker(left.content)
|
|
356
|
+
if (markerPos === -1) {
|
|
357
|
+
i = closeIdx
|
|
358
|
+
continue
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const prefix = left.content.slice(0, markerPos)
|
|
362
|
+
const leftInner = left.content.slice(markerPos + 2)
|
|
363
|
+
if (leftInner && isSoftSpaceCode(leftInner.charCodeAt(0))) {
|
|
364
|
+
i = closeIdx
|
|
365
|
+
continue
|
|
366
|
+
}
|
|
367
|
+
const rightTail = right.content.slice(2)
|
|
368
|
+
if (strictAsciiStrongGuard &&
|
|
369
|
+
(textEndsAsciiWord(prefix) || textStartsAsciiWord(rightTail))) {
|
|
370
|
+
i = closeIdx
|
|
371
|
+
continue
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const replacement = []
|
|
375
|
+
if (prefix) replacement.push(cloneTextToken(left, prefix))
|
|
376
|
+
|
|
377
|
+
const strongOpen = new Token('strong_open', 'strong', 1)
|
|
378
|
+
strongOpen.markup = '**'
|
|
379
|
+
strongOpen.map = cloneMap(left.map) || cloneMap(linkOpen.map) || cloneMap(right.map) || null
|
|
380
|
+
replacement.push(strongOpen)
|
|
381
|
+
|
|
382
|
+
if (leftInner) replacement.push(cloneTextToken(left, leftInner))
|
|
383
|
+
for (let j = i; j <= closeIdx; j++) replacement.push(tokens[j])
|
|
384
|
+
|
|
385
|
+
const strongClose = new Token('strong_close', 'strong', -1)
|
|
386
|
+
strongClose.markup = '**'
|
|
387
|
+
strongClose.map = cloneMap(right.map) || cloneMap(linkOpen.map) || cloneMap(left.map) || null
|
|
388
|
+
replacement.push(strongClose)
|
|
389
|
+
|
|
390
|
+
if (rightTail) replacement.push(cloneTextToken(right, rightTail))
|
|
391
|
+
|
|
392
|
+
tokens.splice(i - 1, closeIdx - i + 3, ...replacement)
|
|
393
|
+
changed = true
|
|
394
|
+
invalidateInlineDerivedCaches(facts)
|
|
395
|
+
markInlineLevelRebuildFrom(facts, i - 1)
|
|
396
|
+
i = i - 1 + replacement.length - 1
|
|
397
|
+
}
|
|
398
|
+
return changed
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const tryPromoteStrongAroundInlineCode = (
|
|
402
|
+
tokens,
|
|
403
|
+
strictAsciiCodeGuard = false,
|
|
404
|
+
strictAsciiStrongGuard = false,
|
|
405
|
+
facts = null
|
|
406
|
+
) => {
|
|
407
|
+
if (!tokens || tokens.length < 3) return false
|
|
408
|
+
let changed = false
|
|
409
|
+
for (let i = 0; i <= tokens.length - 3; i++) {
|
|
410
|
+
const left = tokens[i]
|
|
411
|
+
const code = tokens[i + 1]
|
|
412
|
+
const right = tokens[i + 2]
|
|
413
|
+
if (!left || !code || !right) continue
|
|
414
|
+
if (left.type !== 'text' || !left.content) continue
|
|
415
|
+
if (code.type !== 'code_inline') continue
|
|
416
|
+
if (right.type !== 'text' || !right.content) continue
|
|
417
|
+
if (!hasLeadingStandaloneStrongMarker(right.content)) continue
|
|
418
|
+
const markerPos = findLastStandaloneStrongMarker(left.content)
|
|
419
|
+
if (markerPos === -1) continue
|
|
420
|
+
|
|
421
|
+
const prefix = left.content.slice(0, markerPos)
|
|
422
|
+
const leftInner = left.content.slice(markerPos + 2)
|
|
423
|
+
const rightTail = right.content.slice(2)
|
|
424
|
+
if (strictAsciiStrongGuard &&
|
|
425
|
+
(textEndsAsciiWord(prefix) || textStartsAsciiWord(rightTail))) {
|
|
426
|
+
continue
|
|
427
|
+
}
|
|
428
|
+
if (strictAsciiCodeGuard &&
|
|
429
|
+
leftInner &&
|
|
430
|
+
isSoftSpaceCode(leftInner.charCodeAt(0)) &&
|
|
431
|
+
code.content &&
|
|
432
|
+
isAsciiWordCode(code.content.charCodeAt(0))) {
|
|
433
|
+
continue
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const replacement = []
|
|
437
|
+
if (prefix) replacement.push(cloneTextToken(left, prefix))
|
|
438
|
+
|
|
439
|
+
const strongOpen = new Token('strong_open', 'strong', 1)
|
|
440
|
+
strongOpen.markup = '**'
|
|
441
|
+
strongOpen.map = cloneMap(left.map) || cloneMap(code.map) || cloneMap(right.map) || null
|
|
442
|
+
replacement.push(strongOpen)
|
|
443
|
+
|
|
444
|
+
if (leftInner) replacement.push(cloneTextToken(left, leftInner))
|
|
445
|
+
replacement.push(code)
|
|
446
|
+
|
|
447
|
+
const strongClose = new Token('strong_close', 'strong', -1)
|
|
448
|
+
strongClose.markup = '**'
|
|
449
|
+
strongClose.map = cloneMap(right.map) || cloneMap(code.map) || cloneMap(left.map) || null
|
|
450
|
+
replacement.push(strongClose)
|
|
451
|
+
|
|
452
|
+
if (rightTail) replacement.push(cloneTextToken(right, rightTail))
|
|
453
|
+
|
|
454
|
+
tokens.splice(i, 3, ...replacement)
|
|
455
|
+
changed = true
|
|
456
|
+
invalidateInlineDerivedCaches(facts)
|
|
457
|
+
markInlineLevelRebuildFrom(facts, i)
|
|
458
|
+
i += replacement.length - 1
|
|
459
|
+
}
|
|
460
|
+
return changed
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const tryActivateInlineEmphasis = (
|
|
464
|
+
children,
|
|
465
|
+
facts,
|
|
466
|
+
strictAsciiCodeGuard,
|
|
467
|
+
strictAsciiStrongGuard
|
|
468
|
+
) => {
|
|
469
|
+
if (!facts || facts.hasEmphasis) return false
|
|
470
|
+
if (facts.hasLinkOpen &&
|
|
471
|
+
facts.hasLinkClose &&
|
|
472
|
+
tryPromoteStrongAroundInlineLink(children, strictAsciiStrongGuard, facts)) {
|
|
473
|
+
facts.hasEmphasis = true
|
|
474
|
+
return true
|
|
475
|
+
}
|
|
476
|
+
if (facts.hasBracketText || facts.hasLinkOpen || facts.hasLinkClose) return false
|
|
477
|
+
if (!ensureInlineHasCodeInline(facts, children)) return false
|
|
478
|
+
if (tryPromoteStrongAroundInlineCode(children, strictAsciiCodeGuard, strictAsciiStrongGuard, facts)) {
|
|
479
|
+
facts.hasEmphasis = true
|
|
480
|
+
return true
|
|
481
|
+
}
|
|
482
|
+
return false
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const shouldRunInlineBrokenRefRepair = (facts, inlineContent, state) => {
|
|
486
|
+
if (!facts || !facts.hasLinkOpen || !facts.hasLinkClose || !facts.hasBracketText) return false
|
|
487
|
+
if (inlineContent.indexOf('***') !== -1) return false
|
|
488
|
+
return ensureInlineReferenceCount(facts, state) > 0
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const applyBrokenRefRepairFacts = (facts, repairs) => {
|
|
492
|
+
if (!facts || !repairs) return
|
|
493
|
+
facts.hasBracketText = repairs.hasBracketText
|
|
494
|
+
facts.hasEmphasis = repairs.hasEmphasis
|
|
495
|
+
facts.hasLinkClose = repairs.hasLinkClose
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const createBrokenRefScanState = () => {
|
|
499
|
+
return { depth: 0, brokenEnd: false, tailOpen: -1 }
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const runInlineBrokenRefRepairStage = (children, facts, inlineContent, state) => {
|
|
503
|
+
if (!shouldRunInlineBrokenRefRepair(facts, inlineContent, state)) return false
|
|
504
|
+
const scanState = createBrokenRefScanState()
|
|
505
|
+
const maxRepairPass = computeMaxBrokenRefRepairPass(children, scanState)
|
|
506
|
+
if (maxRepairPass <= 0) return false
|
|
507
|
+
const repairs = runBrokenRefRepairs(
|
|
508
|
+
children,
|
|
509
|
+
maxRepairPass,
|
|
510
|
+
scanState,
|
|
511
|
+
ensureInlineMetrics(facts, state),
|
|
512
|
+
facts,
|
|
513
|
+
BROKEN_REF_REPAIR_HOOKS
|
|
514
|
+
)
|
|
515
|
+
applyBrokenRefRepairFacts(facts, repairs)
|
|
516
|
+
return repairs.changed
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const runInlineEmphasisRepairStage = (children, facts, state, isJapaneseMode) => {
|
|
520
|
+
if (!facts.hasEmphasis) return false
|
|
521
|
+
let changed = false
|
|
522
|
+
const markChangedFrom = createInlineChangeMarker(facts)
|
|
523
|
+
if (fixEmOuterStrongSequence(children, markChangedFrom)) changed = true
|
|
524
|
+
if (facts.hasLinkClose) {
|
|
525
|
+
const metrics = ensureInlineMetrics(facts, state)
|
|
526
|
+
if (fixTailAfterLinkStrongClose(children, isJapaneseMode, metrics, markChangedFrom)) changed = true
|
|
527
|
+
if (fixLeadingAsteriskEm(children, markChangedFrom)) changed = true
|
|
528
|
+
}
|
|
529
|
+
if (fixTrailingStrong(children, markChangedFrom)) changed = true
|
|
530
|
+
if (sanitizeEmStrongBalance(children, markChangedFrom)) changed = true
|
|
531
|
+
return changed
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const shouldRunInlineCollapsedRefRepair = (facts, state) => {
|
|
535
|
+
if (!facts || !facts.hasBracketText) return false
|
|
536
|
+
return ensureInlineReferenceCount(facts, state) > 0
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const applyCollapsedRefRepairFacts = (facts) => {
|
|
540
|
+
if (!facts) return
|
|
541
|
+
facts.hasLinkOpen = true
|
|
542
|
+
facts.hasLinkClose = true
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const rewriteInlineCollapsedReferences = (children, facts, state, markChangedFrom) => {
|
|
546
|
+
const changed = convertCollapsedReferenceLinks(
|
|
547
|
+
children,
|
|
548
|
+
state,
|
|
549
|
+
facts,
|
|
550
|
+
markChangedFrom
|
|
551
|
+
)
|
|
552
|
+
if (!changed) return false
|
|
553
|
+
applyCollapsedRefRepairFacts(facts)
|
|
554
|
+
return true
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const runInlineCollapsedRefStage = (children, facts, state) => {
|
|
558
|
+
if (!shouldRunInlineCollapsedRefRepair(facts, state)) return false
|
|
559
|
+
const markChangedFrom = createInlineChangeMarker(facts)
|
|
560
|
+
if (!rewriteInlineCollapsedReferences(children, facts, state, markChangedFrom)) return false
|
|
561
|
+
finalizeInlineLinkRepairStage(children, facts, markChangedFrom)
|
|
562
|
+
return true
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const shouldSkipInlinePostprocessToken = (children, facts, isJapaneseMode) => {
|
|
566
|
+
if (!facts.hasEmphasis &&
|
|
567
|
+
!facts.hasBracketText &&
|
|
568
|
+
!facts.hasLinkOpen &&
|
|
569
|
+
!facts.hasLinkClose &&
|
|
570
|
+
!ensureInlineHasCodeInline(facts, children)) {
|
|
571
|
+
return true
|
|
572
|
+
}
|
|
573
|
+
if (isJapaneseMode &&
|
|
574
|
+
!hasJapaneseContextInRange(children, 0, children.length - 1)) {
|
|
575
|
+
return true
|
|
576
|
+
}
|
|
577
|
+
return false
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const runInlineCoreRepairStages = (
|
|
581
|
+
children,
|
|
582
|
+
facts,
|
|
583
|
+
inlineContent,
|
|
584
|
+
state,
|
|
585
|
+
isJapaneseMode,
|
|
586
|
+
strictAsciiCodeGuard,
|
|
587
|
+
strictAsciiStrongGuard
|
|
588
|
+
) => {
|
|
589
|
+
let changed = false
|
|
590
|
+
if (!facts.hasEmphasis && tryActivateInlineEmphasis(
|
|
591
|
+
children,
|
|
592
|
+
facts,
|
|
593
|
+
strictAsciiCodeGuard,
|
|
594
|
+
strictAsciiStrongGuard
|
|
595
|
+
)) {
|
|
596
|
+
changed = true
|
|
597
|
+
} else if (!facts.hasEmphasis && !facts.hasBracketText) {
|
|
598
|
+
return false
|
|
599
|
+
}
|
|
600
|
+
if (runInlineBrokenRefRepairStage(children, facts, inlineContent, state)) changed = true
|
|
601
|
+
if (runInlineEmphasisRepairStage(children, facts, state, isJapaneseMode)) changed = true
|
|
602
|
+
return changed
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const processInlinePostprocessToken = (
|
|
606
|
+
token,
|
|
607
|
+
inlineContent,
|
|
608
|
+
state,
|
|
609
|
+
isJapaneseMode,
|
|
610
|
+
strictAsciiCodeGuard,
|
|
611
|
+
strictAsciiStrongGuard
|
|
612
|
+
) => {
|
|
613
|
+
if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) return
|
|
614
|
+
const children = token.children
|
|
615
|
+
const facts = buildInlinePostprocessFacts(children, inlineContent)
|
|
616
|
+
if (shouldSkipInlinePostprocessToken(children, facts, isJapaneseMode)) return
|
|
617
|
+
const changed = runInlineCoreRepairStages(
|
|
618
|
+
children,
|
|
619
|
+
facts,
|
|
620
|
+
inlineContent,
|
|
621
|
+
state,
|
|
622
|
+
isJapaneseMode,
|
|
623
|
+
strictAsciiCodeGuard,
|
|
624
|
+
strictAsciiStrongGuard
|
|
625
|
+
)
|
|
626
|
+
if (changed) rebuildInlineLevelsForFacts(children, facts)
|
|
627
|
+
runInlineCollapsedRefStage(children, facts, state)
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const processInlinePostprocessStateTokens = (
|
|
631
|
+
state,
|
|
632
|
+
isJapaneseMode,
|
|
633
|
+
strictAsciiCodeGuard,
|
|
634
|
+
strictAsciiStrongGuard
|
|
635
|
+
) => {
|
|
636
|
+
for (let i = 0; i < state.tokens.length; i++) {
|
|
637
|
+
const token = state.tokens[i]
|
|
638
|
+
if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) continue
|
|
639
|
+
const inlineContent = typeof token.content === 'string' ? token.content : ''
|
|
640
|
+
if (!hasMarkerChars(inlineContent)) continue
|
|
641
|
+
processInlinePostprocessToken(
|
|
642
|
+
token,
|
|
643
|
+
inlineContent,
|
|
644
|
+
state,
|
|
645
|
+
isJapaneseMode,
|
|
646
|
+
strictAsciiCodeGuard,
|
|
647
|
+
strictAsciiStrongGuard
|
|
648
|
+
)
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const registerTokenPostprocess = (md, baseOpt) => {
|
|
653
|
+
if (md.__strongJaTokenPostprocessRegistered) return
|
|
654
|
+
md.__strongJaTokenPostprocessRegistered = true
|
|
655
|
+
md.core.ruler.after('inline', 'strong_ja_token_postprocess', (state) => {
|
|
656
|
+
if (!state || !state.tokens) return
|
|
657
|
+
const overrideOpt = state.env && state.env.__strongJaTokenOpt
|
|
658
|
+
const opt = overrideOpt ? getRuntimeOpt(state, baseOpt) : baseOpt
|
|
659
|
+
if (!opt.__strongJaPostprocessActive) return
|
|
660
|
+
const isJapaneseMode = opt.__strongJaIsJapaneseMode
|
|
661
|
+
const strictAsciiCodeGuard = opt.__strongJaStrictAsciiCodeGuard
|
|
662
|
+
const strictAsciiStrongGuard = opt.__strongJaStrictAsciiStrongGuard
|
|
663
|
+
processInlinePostprocessStateTokens(
|
|
664
|
+
state,
|
|
665
|
+
isJapaneseMode,
|
|
666
|
+
strictAsciiCodeGuard,
|
|
667
|
+
strictAsciiStrongGuard
|
|
668
|
+
)
|
|
669
|
+
})
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
export { registerTokenPostprocess }
|