@peaceroad/markdown-it-strong-ja 0.8.1 → 0.9.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 +4 -4
- package/index.js +9 -17
- package/package.json +5 -5
- package/src/token-compat.js +13 -17
- package/src/token-core.js +148 -56
- package/src/token-link-utils.js +13 -16
- package/src/token-postprocess/broken-ref.js +123 -27
- package/src/token-postprocess/emphasis-balance.js +50 -0
- package/src/token-postprocess/fastpaths.js +1 -5
- package/src/token-postprocess/guards.js +121 -107
- package/src/token-postprocess/orchestrator.js +110 -169
- package/src/token-utils.js +182 -139
package/README.md
CHANGED
|
@@ -396,10 +396,10 @@ Supporting visuals:
|
|
|
396
396
|
|
|
397
397
|
## Notes
|
|
398
398
|
|
|
399
|
-
- Use `state.env.__strongJaTokenOpt` to override options per render.
|
|
400
|
-
-
|
|
401
|
-
-
|
|
399
|
+
- Use `state.env.__strongJaTokenOpt` to override runtime-effective options per render.
|
|
400
|
+
- Repeated `.use(...)` on the same `MarkdownIt` instance is treated as first-install-wins no-op. Create a new `MarkdownIt` instance for a different plugin option set.
|
|
401
|
+
- Runtime-effective override keys are merged with plugin options, but setup-time behavior (such as rule registration/order) cannot be switched at render time and cannot be retrofitted after the first `.use(...)` on the same `MarkdownIt` instance.
|
|
402
|
+
- `mode` and `postprocess` are runtime-effective via initial install or per-render override. `mditAttrs`, `patchCorePush`, and `coreRulesBeforePostprocess` are setup-time effective after the first `.use(...)` on a `MarkdownIt` instance.
|
|
402
403
|
- This is an ESM plugin (`type: module`) and is tested against `markdown-it` 14.x in Node.js, browser bundlers, and VS Code extension pipelines that use `markdown-it` ESM.
|
|
403
404
|
- The implementation relies on `markdown-it` internal ESM modules / core rule internals (`lib/token.mjs`, `lib/common/utils.mjs`, `ruler.__rules__`) plus a `scanDelims` prototype patch, so internal `markdown-it` changes may require plugin updates.
|
|
404
405
|
- `scanDelims` patch is applied once per `MarkdownIt` prototype in the same process.
|
|
405
|
-
|
package/index.js
CHANGED
|
@@ -19,31 +19,23 @@ const buildNormalizedOption = (md, option) => {
|
|
|
19
19
|
return opt
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const writeSharedOption = (target, source) => {
|
|
23
|
-
for (const key of Object.keys(target)) {
|
|
24
|
-
delete target[key]
|
|
25
|
-
}
|
|
26
|
-
Object.assign(target, source)
|
|
27
|
-
return target
|
|
28
|
-
}
|
|
29
|
-
|
|
30
22
|
const mditStrongJa = (md, option) => {
|
|
31
23
|
if (option && typeof option.engine === 'string' && option.engine !== 'token') {
|
|
32
24
|
throw new Error('mditStrongJa: legacy engine was removed; use token (default)')
|
|
33
25
|
}
|
|
26
|
+
if (md.__strongJaTokenInstalled) {
|
|
27
|
+
return md
|
|
28
|
+
}
|
|
34
29
|
const nextOpt = buildNormalizedOption(md, option)
|
|
35
|
-
|
|
36
|
-
? writeSharedOption(md.__strongJaTokenOpt, nextOpt)
|
|
37
|
-
: nextOpt
|
|
38
|
-
|
|
39
|
-
md.__strongJaTokenOpt = opt
|
|
30
|
+
md.__strongJaTokenOpt = nextOpt
|
|
40
31
|
patchScanDelims(md)
|
|
41
|
-
registerTokenCompat(md,
|
|
32
|
+
registerTokenCompat(md, nextOpt)
|
|
42
33
|
|
|
43
|
-
registerTokenPostprocess(md,
|
|
44
|
-
ensureCoreRuleOrder(md,
|
|
34
|
+
registerTokenPostprocess(md, nextOpt)
|
|
35
|
+
ensureCoreRuleOrder(md, nextOpt.__strongJaNormalizedCoreRulesBeforePostprocess, 'strong_ja_token_postprocess')
|
|
36
|
+
md.__strongJaTokenInstalled = true
|
|
45
37
|
|
|
46
38
|
return md
|
|
47
39
|
}
|
|
48
40
|
|
|
49
|
-
export default mditStrongJa
|
|
41
|
+
export default mditStrongJa
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peaceroad/markdown-it-strong-ja",
|
|
3
3
|
"description": "Extends asterisk emphasis handling for Japanese text while keeping markdown-it behavior as close as practical.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.9.1",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"test:all": "node test/test-all.js",
|
|
27
27
|
"bench:scan": "node test/material/perf-scan-delims.mjs",
|
|
28
28
|
"bench:postprocess": "node test/material/perf-postprocess.mjs",
|
|
29
|
+
"bench:isolated": "node test/material/bench-isolated.mjs",
|
|
29
30
|
"analyze:postprocess-calls": "node test/material/analyze-postprocess-calls.mjs",
|
|
30
31
|
"analyze:fastpath": "node test/material/analyze-fastpath-hits.mjs"
|
|
31
32
|
},
|
|
@@ -36,14 +37,13 @@
|
|
|
36
37
|
"markdown-it": "^14.1.0"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
|
-
"@peaceroad/markdown-it-cjk-breaks-mod": "^0.1.
|
|
40
|
-
"@peaceroad/markdown-it-hr-sandwiched-semantic-container": "^0.
|
|
40
|
+
"@peaceroad/markdown-it-cjk-breaks-mod": "^0.1.10",
|
|
41
|
+
"@peaceroad/markdown-it-hr-sandwiched-semantic-container": "^0.11.0",
|
|
41
42
|
"@peaceroad/markdown-it-renderer-image": "^0.12.0",
|
|
42
|
-
"@peaceroad/markdown-it-renderer-inline-text": "^0.
|
|
43
|
+
"@peaceroad/markdown-it-renderer-inline-text": "^0.8.0",
|
|
43
44
|
"markdown-it-attrs": "^4.3.1",
|
|
44
45
|
"markdown-it-sub": "^2.0.0",
|
|
45
46
|
"markdown-it-sup": "^2.0.0",
|
|
46
47
|
"p7d-markdown-it-p-captions": "^0.21.0"
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
|
-
|
package/src/token-compat.js
CHANGED
|
@@ -2,18 +2,14 @@ import Token from 'markdown-it/lib/token.mjs'
|
|
|
2
2
|
import {
|
|
3
3
|
REG_ATTRS,
|
|
4
4
|
isJapaneseChar,
|
|
5
|
+
isAsciiWordCode,
|
|
5
6
|
hasCjkBreaksRule,
|
|
6
7
|
isCjkBreaksRuleName,
|
|
7
8
|
getRuntimeOpt,
|
|
9
|
+
hasRuntimeOverride,
|
|
8
10
|
moveRuleAfter
|
|
9
11
|
} from './token-utils.js'
|
|
10
12
|
|
|
11
|
-
const isAsciiWordCode = (code) => {
|
|
12
|
-
return (code >= 0x30 && code <= 0x39) ||
|
|
13
|
-
(code >= 0x41 && code <= 0x5A) ||
|
|
14
|
-
(code >= 0x61 && code <= 0x7A)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
13
|
const trimTrailingSpaceTab = (text) => {
|
|
18
14
|
if (!text) return text
|
|
19
15
|
let end = text.length
|
|
@@ -25,10 +21,14 @@ const trimTrailingSpaceTab = (text) => {
|
|
|
25
21
|
return end === text.length ? text : text.slice(0, end)
|
|
26
22
|
}
|
|
27
23
|
|
|
24
|
+
const getStateSource = (state) => {
|
|
25
|
+
return state && typeof state.src === 'string' ? state.src : ''
|
|
26
|
+
}
|
|
27
|
+
|
|
28
28
|
const registerTokenCompat = (md, baseOpt) => {
|
|
29
29
|
const isCompatibleMode = (state) => {
|
|
30
30
|
const override = state && state.env && state.env.__strongJaTokenOpt
|
|
31
|
-
if (!override) return baseOpt.__strongJaIsCompatibleMode === true
|
|
31
|
+
if (!hasRuntimeOverride(override)) return baseOpt.__strongJaIsCompatibleMode === true
|
|
32
32
|
const opt = getRuntimeOpt(state, baseOpt)
|
|
33
33
|
return opt.__strongJaIsCompatibleMode === true
|
|
34
34
|
}
|
|
@@ -82,6 +82,8 @@ const registerTokenCompat = (md, baseOpt) => {
|
|
|
82
82
|
const normalizeSoftbreakSpacing = (state) => {
|
|
83
83
|
if (isCompatibleMode(state)) return
|
|
84
84
|
if (!state) return
|
|
85
|
+
const src = getStateSource(state)
|
|
86
|
+
if (!src || src.indexOf('\n') === -1) return
|
|
85
87
|
if (baseOpt.hasCjkBreaks !== true && state.md) {
|
|
86
88
|
baseOpt.hasCjkBreaks = hasCjkBreaksRule(state.md)
|
|
87
89
|
}
|
|
@@ -164,11 +166,8 @@ const registerTokenCompat = (md, baseOpt) => {
|
|
|
164
166
|
const restoreSoftbreaksAfterCjk = (state) => {
|
|
165
167
|
if (isCompatibleMode(state)) return
|
|
166
168
|
if (!state) return
|
|
167
|
-
const
|
|
168
|
-
if (
|
|
169
|
-
const opt = getRuntimeOpt(state, baseOpt)
|
|
170
|
-
if (opt.mditAttrs !== false) return
|
|
171
|
-
}
|
|
169
|
+
const src = getStateSource(state)
|
|
170
|
+
if (!src || src.indexOf('\n') === -1 || src.indexOf('{') === -1) return
|
|
172
171
|
if (!state.md || state.md.__strongJaRestoreSoftbreaksForAttrs !== true) return
|
|
173
172
|
if (baseOpt.hasCjkBreaks !== true && state.md) {
|
|
174
173
|
baseOpt.hasCjkBreaks = hasCjkBreaksRule(state.md)
|
|
@@ -239,11 +238,8 @@ const registerTokenCompat = (md, baseOpt) => {
|
|
|
239
238
|
md.core.ruler.before('linkify', 'strong_ja_token_pre_attrs', (state) => {
|
|
240
239
|
if (isCompatibleMode(state)) return
|
|
241
240
|
if (!state || !state.tokens) return
|
|
242
|
-
const
|
|
243
|
-
if (
|
|
244
|
-
const opt = getRuntimeOpt(state, baseOpt)
|
|
245
|
-
if (opt.mditAttrs === false) return
|
|
246
|
-
}
|
|
241
|
+
const src = getStateSource(state)
|
|
242
|
+
if (!src || src.indexOf('{') === -1 || src.indexOf('}') === -1) return
|
|
247
243
|
for (let i = 0; i < state.tokens.length; i++) {
|
|
248
244
|
const token = state.tokens[i]
|
|
249
245
|
if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) continue
|
package/src/token-core.js
CHANGED
|
@@ -2,14 +2,14 @@ import { isWhiteSpace } from 'markdown-it/lib/common/utils.mjs'
|
|
|
2
2
|
import Token from 'markdown-it/lib/token.mjs'
|
|
3
3
|
import {
|
|
4
4
|
CHAR_ASTERISK,
|
|
5
|
-
CHAR_SPACE,
|
|
6
|
-
CHAR_TAB,
|
|
7
5
|
CHAR_NEWLINE,
|
|
8
|
-
CHAR_IDEOGRAPHIC_SPACE,
|
|
9
6
|
isJapaneseChar,
|
|
7
|
+
isAsciiWordCode,
|
|
8
|
+
isSoftSpaceCode,
|
|
10
9
|
MODE_FLAG_COMPATIBLE,
|
|
11
10
|
MODE_FLAG_AGGRESSIVE,
|
|
12
11
|
MODE_FLAG_JAPANESE_PLUS,
|
|
12
|
+
hasRuntimeOverride,
|
|
13
13
|
getRuntimeOpt
|
|
14
14
|
} from './token-utils.js'
|
|
15
15
|
|
|
@@ -17,10 +17,7 @@ const SCAN_DELIMS_PATCHED = Symbol.for('strongJaTokenScanDelimsPatched')
|
|
|
17
17
|
const SINGLE_STAR_LOOKAROUND_MAX = 16
|
|
18
18
|
const PREV_STAR_HAS_OPENER = 1
|
|
19
19
|
const PREV_STAR_HAS_JP_BETWEEN = 2
|
|
20
|
-
|
|
21
|
-
const isSoftSpaceCode = (code) => {
|
|
22
|
-
return code === CHAR_SPACE || code === CHAR_TAB || code === CHAR_IDEOGRAPHIC_SPACE
|
|
23
|
-
}
|
|
20
|
+
const SCAN_DELIMS_LOOKUP_KEY = Symbol.for('strongJaTokenScanDelimsLookup')
|
|
24
21
|
|
|
25
22
|
const isPlusQuoteWrapperOpen = (code) => {
|
|
26
23
|
return code === 0x2018 || // ‘
|
|
@@ -254,12 +251,6 @@ const isSingleStarClosingBoundary = (code) => {
|
|
|
254
251
|
isClosingBracketLike(code)
|
|
255
252
|
}
|
|
256
253
|
|
|
257
|
-
const isAsciiAlphaNum = (code) => {
|
|
258
|
-
return (code >= 0x30 && code <= 0x39) ||
|
|
259
|
-
(code >= 0x41 && code <= 0x5A) ||
|
|
260
|
-
(code >= 0x61 && code <= 0x7A)
|
|
261
|
-
}
|
|
262
|
-
|
|
263
254
|
const isAsciiGuardOpenWrapper = (code) => {
|
|
264
255
|
return code === 0x22 || // "
|
|
265
256
|
code === 0x27 || // '
|
|
@@ -280,7 +271,58 @@ const isAsciiGuardCloseWrapper = (code) => {
|
|
|
280
271
|
code === 0x60 // `
|
|
281
272
|
}
|
|
282
273
|
|
|
283
|
-
const
|
|
274
|
+
const buildScanDelimsLookupCache = (src) => {
|
|
275
|
+
const len = typeof src === 'string' ? src.length : 0
|
|
276
|
+
const prevNonSpaceSameLine = new Int32Array(len)
|
|
277
|
+
const nextNonSpaceSameLine = new Int32Array(len)
|
|
278
|
+
prevNonSpaceSameLine.fill(-1)
|
|
279
|
+
nextNonSpaceSameLine.fill(-1)
|
|
280
|
+
|
|
281
|
+
let prev = -1
|
|
282
|
+
for (let i = 0; i < len; i++) {
|
|
283
|
+
const code = src.charCodeAt(i)
|
|
284
|
+
if (code === CHAR_NEWLINE) {
|
|
285
|
+
prev = -1
|
|
286
|
+
continue
|
|
287
|
+
}
|
|
288
|
+
if (!isSoftSpaceCode(code)) prev = i
|
|
289
|
+
prevNonSpaceSameLine[i] = prev
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
let next = -1
|
|
293
|
+
for (let i = len - 1; i >= 0; i--) {
|
|
294
|
+
const code = src.charCodeAt(i)
|
|
295
|
+
if (code === CHAR_NEWLINE) {
|
|
296
|
+
next = -1
|
|
297
|
+
continue
|
|
298
|
+
}
|
|
299
|
+
if (!isSoftSpaceCode(code)) next = i
|
|
300
|
+
nextNonSpaceSameLine[i] = next
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
src,
|
|
305
|
+
prevNonSpaceSameLine,
|
|
306
|
+
nextNonSpaceSameLine
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const getScanDelimsLookupCache = (state) => {
|
|
311
|
+
if (!state || typeof state.src !== 'string') return null
|
|
312
|
+
const cached = state[SCAN_DELIMS_LOOKUP_KEY]
|
|
313
|
+
if (cached && cached.src === state.src) return cached
|
|
314
|
+
const next = buildScanDelimsLookupCache(state.src)
|
|
315
|
+
state[SCAN_DELIMS_LOOKUP_KEY] = next
|
|
316
|
+
return next
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const findPrevNonSpaceIndex = (src, start, lookupCache = null) => {
|
|
320
|
+
if (start < 0) return -1
|
|
321
|
+
if (lookupCache &&
|
|
322
|
+
lookupCache.src === src &&
|
|
323
|
+
start < lookupCache.prevNonSpaceSameLine.length) {
|
|
324
|
+
return lookupCache.prevNonSpaceSameLine[start]
|
|
325
|
+
}
|
|
284
326
|
for (let i = start; i >= 0; i--) {
|
|
285
327
|
const code = src.charCodeAt(i)
|
|
286
328
|
if (code === CHAR_NEWLINE) return -1
|
|
@@ -290,7 +332,14 @@ const findPrevNonSpaceIndex = (src, start) => {
|
|
|
290
332
|
return -1
|
|
291
333
|
}
|
|
292
334
|
|
|
293
|
-
const findNextNonSpaceIndex = (src, start, max) => {
|
|
335
|
+
const findNextNonSpaceIndex = (src, start, max, lookupCache = null) => {
|
|
336
|
+
if (lookupCache &&
|
|
337
|
+
lookupCache.src === src &&
|
|
338
|
+
start >= 0 &&
|
|
339
|
+
start < lookupCache.nextNonSpaceSameLine.length) {
|
|
340
|
+
const next = lookupCache.nextNonSpaceSameLine[start]
|
|
341
|
+
return next !== -1 && next < max ? next : -1
|
|
342
|
+
}
|
|
294
343
|
for (let i = start; i < max; i++) {
|
|
295
344
|
const code = src.charCodeAt(i)
|
|
296
345
|
if (code === CHAR_NEWLINE) return -1
|
|
@@ -300,30 +349,30 @@ const findNextNonSpaceIndex = (src, start, max) => {
|
|
|
300
349
|
return -1
|
|
301
350
|
}
|
|
302
351
|
|
|
303
|
-
const hasAsciiStartAfterOptionalOpenWrappers = (src, index, max) => {
|
|
352
|
+
const hasAsciiStartAfterOptionalOpenWrappers = (src, index, max, lookupCache = null) => {
|
|
304
353
|
let i = index
|
|
305
354
|
// Two wrappers are enough for common shapes: * [ "word" ]*
|
|
306
355
|
for (let wrappers = 0; wrappers < 2 && i >= 0 && i < max; wrappers++) {
|
|
307
356
|
const code = src.charCodeAt(i)
|
|
308
357
|
if (!isAsciiGuardOpenWrapper(code)) break
|
|
309
|
-
i = findNextNonSpaceIndex(src, i + 1, max)
|
|
358
|
+
i = findNextNonSpaceIndex(src, i + 1, max, lookupCache)
|
|
310
359
|
if (i === -1) return false
|
|
311
360
|
}
|
|
312
361
|
if (i < 0 || i >= max) return false
|
|
313
|
-
return
|
|
362
|
+
return isAsciiWordCode(src.charCodeAt(i))
|
|
314
363
|
}
|
|
315
364
|
|
|
316
|
-
const hasAsciiEndBeforeOptionalCloseWrappers = (src, index) => {
|
|
365
|
+
const hasAsciiEndBeforeOptionalCloseWrappers = (src, index, lookupCache = null) => {
|
|
317
366
|
let i = index
|
|
318
367
|
// Two wrappers are enough for common shapes: *["word"] *
|
|
319
368
|
for (let wrappers = 0; wrappers < 2 && i >= 0; wrappers++) {
|
|
320
369
|
const code = src.charCodeAt(i)
|
|
321
370
|
if (!isAsciiGuardCloseWrapper(code)) break
|
|
322
|
-
i = findPrevNonSpaceIndex(src, i - 1)
|
|
371
|
+
i = findPrevNonSpaceIndex(src, i - 1, lookupCache)
|
|
323
372
|
if (i === -1) return false
|
|
324
373
|
}
|
|
325
374
|
if (i < 0) return false
|
|
326
|
-
return
|
|
375
|
+
return isAsciiWordCode(src.charCodeAt(i))
|
|
327
376
|
}
|
|
328
377
|
|
|
329
378
|
const isMarkdownStructuralOpenWrapper = (code) => {
|
|
@@ -353,7 +402,17 @@ const isSentenceBoundaryStop = (code) => {
|
|
|
353
402
|
code === 0x2049 // ⁉
|
|
354
403
|
}
|
|
355
404
|
|
|
356
|
-
const findPrevNonSpaceLimited = (src, start, maxLook) => {
|
|
405
|
+
const findPrevNonSpaceLimited = (src, start, maxLook, lookupCache = null) => {
|
|
406
|
+
if (lookupCache &&
|
|
407
|
+
lookupCache.src === src &&
|
|
408
|
+
start >= 0 &&
|
|
409
|
+
start < lookupCache.prevNonSpaceSameLine.length) {
|
|
410
|
+
const prev = lookupCache.prevNonSpaceSameLine[start]
|
|
411
|
+
if (prev !== -1 && (start - prev) < maxLook) {
|
|
412
|
+
return src.charCodeAt(prev)
|
|
413
|
+
}
|
|
414
|
+
return 0
|
|
415
|
+
}
|
|
357
416
|
let looked = 0
|
|
358
417
|
for (let i = start; i >= 0; i--) {
|
|
359
418
|
if (looked >= maxLook) break
|
|
@@ -366,7 +425,17 @@ const findPrevNonSpaceLimited = (src, start, maxLook) => {
|
|
|
366
425
|
return 0
|
|
367
426
|
}
|
|
368
427
|
|
|
369
|
-
const findNextNonSpaceLimited = (src, start, max, maxLook) => {
|
|
428
|
+
const findNextNonSpaceLimited = (src, start, max, maxLook, lookupCache = null) => {
|
|
429
|
+
if (lookupCache &&
|
|
430
|
+
lookupCache.src === src &&
|
|
431
|
+
start >= 0 &&
|
|
432
|
+
start < lookupCache.nextNonSpaceSameLine.length) {
|
|
433
|
+
const next = lookupCache.nextNonSpaceSameLine[start]
|
|
434
|
+
if (next !== -1 && next < max && (next - start) < maxLook) {
|
|
435
|
+
return src.charCodeAt(next)
|
|
436
|
+
}
|
|
437
|
+
return 0
|
|
438
|
+
}
|
|
370
439
|
let looked = 0
|
|
371
440
|
for (let i = start; i < max; i++) {
|
|
372
441
|
if (looked >= maxLook) break
|
|
@@ -379,13 +448,13 @@ const findNextNonSpaceLimited = (src, start, max, maxLook) => {
|
|
|
379
448
|
return 0
|
|
380
449
|
}
|
|
381
450
|
|
|
382
|
-
const hasJapaneseContextForBracketWrapper = (src, start, pos, max, lastChar, nextChar) => {
|
|
451
|
+
const hasJapaneseContextForBracketWrapper = (src, start, pos, max, lastChar, nextChar, lookupCache = null) => {
|
|
383
452
|
if (isWrapperOpenLike(nextChar)) {
|
|
384
|
-
const right = findNextNonSpaceLimited(src, pos, max, SINGLE_STAR_LOOKAROUND_MAX)
|
|
453
|
+
const right = findNextNonSpaceLimited(src, pos, max, SINGLE_STAR_LOOKAROUND_MAX, lookupCache)
|
|
385
454
|
if (isJapaneseChar(right)) return true
|
|
386
455
|
}
|
|
387
456
|
if (isWrapperCloseLike(lastChar)) {
|
|
388
|
-
const left = findPrevNonSpaceLimited(src, start - 2, SINGLE_STAR_LOOKAROUND_MAX)
|
|
457
|
+
const left = findPrevNonSpaceLimited(src, start - 2, SINGLE_STAR_LOOKAROUND_MAX, lookupCache)
|
|
389
458
|
if (isJapaneseChar(left)) return true
|
|
390
459
|
}
|
|
391
460
|
return false
|
|
@@ -415,7 +484,8 @@ const scanPrevSingleStarContextFlags = (src, start) => {
|
|
|
415
484
|
}
|
|
416
485
|
|
|
417
486
|
const ensurePrevStarFlags = (src, start, prevStarFlags) => {
|
|
418
|
-
|
|
487
|
+
if (prevStarFlags >= 0) return prevStarFlags
|
|
488
|
+
return scanPrevSingleStarContextFlags(src, start)
|
|
419
489
|
}
|
|
420
490
|
|
|
421
491
|
const fixTrailingStrong = (tokens, onChangeStart = null) => {
|
|
@@ -696,7 +766,7 @@ const patchScanDelims = (md) => {
|
|
|
696
766
|
|
|
697
767
|
const baseOpt = this.md ? this.md.__strongJaTokenOpt : null
|
|
698
768
|
const overrideOpt = this.env && this.env.__strongJaTokenOpt
|
|
699
|
-
const opt = overrideOpt ? getRuntimeOpt(this, baseOpt) : baseOpt
|
|
769
|
+
const opt = hasRuntimeOverride(overrideOpt) ? getRuntimeOpt(this, baseOpt) : baseOpt
|
|
700
770
|
if (!opt) {
|
|
701
771
|
return base
|
|
702
772
|
}
|
|
@@ -707,6 +777,7 @@ const patchScanDelims = (md) => {
|
|
|
707
777
|
const plusMode = (modeFlags & MODE_FLAG_JAPANESE_PLUS) !== 0
|
|
708
778
|
const aggressiveMode = (modeFlags & MODE_FLAG_AGGRESSIVE) !== 0
|
|
709
779
|
const max = this.posMax
|
|
780
|
+
let lookupCache = null
|
|
710
781
|
const lastChar = start > 0 ? src.charCodeAt(start - 1) : 0x20
|
|
711
782
|
|
|
712
783
|
const count = base && base.length ? base.length : 1
|
|
@@ -719,7 +790,15 @@ const patchScanDelims = (md) => {
|
|
|
719
790
|
const rightJapanese = isJapaneseChar(nextChar)
|
|
720
791
|
let hasJapaneseContext = leftJapanese || rightJapanese
|
|
721
792
|
if (!hasJapaneseContext && count === 1) {
|
|
722
|
-
hasJapaneseContext = hasJapaneseContextForBracketWrapper(
|
|
793
|
+
hasJapaneseContext = hasJapaneseContextForBracketWrapper(
|
|
794
|
+
src,
|
|
795
|
+
start,
|
|
796
|
+
pos,
|
|
797
|
+
max,
|
|
798
|
+
lastChar,
|
|
799
|
+
nextChar,
|
|
800
|
+
lookupCache || (lookupCache = getScanDelimsLookupCache(this))
|
|
801
|
+
)
|
|
723
802
|
}
|
|
724
803
|
if (!hasJapaneseContext && count === 1 && isExtraSingleStarClosePunct(lastChar)) {
|
|
725
804
|
prevStarFlags = ensurePrevStarFlags(src, start, prevStarFlags)
|
|
@@ -734,22 +813,31 @@ const patchScanDelims = (md) => {
|
|
|
734
813
|
let isLastWhiteSpace = isWhiteSpace(lastChar) || isSoftSpaceCode(lastChar)
|
|
735
814
|
let isNextWhiteSpace = isWhiteSpace(nextChar) || isSoftSpaceCode(nextChar)
|
|
736
815
|
if (isLastWhiteSpace && isSoftSpaceCode(lastChar)) {
|
|
737
|
-
const prevNonSpaceIdx = findPrevNonSpaceIndex(
|
|
816
|
+
const prevNonSpaceIdx = findPrevNonSpaceIndex(
|
|
817
|
+
src,
|
|
818
|
+
start - 2,
|
|
819
|
+
lookupCache || (lookupCache = getScanDelimsLookupCache(this))
|
|
820
|
+
)
|
|
738
821
|
if (prevNonSpaceIdx !== -1) {
|
|
739
822
|
const prevNonSpaceLocal = src.charCodeAt(prevNonSpaceIdx)
|
|
740
823
|
const plusStrictAsciiBoundary = plusMode &&
|
|
741
|
-
hasAsciiEndBeforeOptionalCloseWrappers(src, prevNonSpaceIdx)
|
|
824
|
+
hasAsciiEndBeforeOptionalCloseWrappers(src, prevNonSpaceIdx, lookupCache)
|
|
742
825
|
if (prevNonSpaceLocal !== CHAR_ASTERISK && !plusStrictAsciiBoundary) {
|
|
743
826
|
isLastWhiteSpace = false
|
|
744
827
|
}
|
|
745
828
|
}
|
|
746
829
|
}
|
|
747
830
|
if (isNextWhiteSpace && isSoftSpaceCode(nextChar)) {
|
|
748
|
-
const nextNonSpaceIdx = findNextNonSpaceIndex(
|
|
831
|
+
const nextNonSpaceIdx = findNextNonSpaceIndex(
|
|
832
|
+
src,
|
|
833
|
+
pos,
|
|
834
|
+
max,
|
|
835
|
+
lookupCache || (lookupCache = getScanDelimsLookupCache(this))
|
|
836
|
+
)
|
|
749
837
|
if (nextNonSpaceIdx !== -1) {
|
|
750
838
|
const nextNonSpace = src.charCodeAt(nextNonSpaceIdx)
|
|
751
839
|
const plusStrictAsciiBoundary = plusMode &&
|
|
752
|
-
hasAsciiStartAfterOptionalOpenWrappers(src, nextNonSpaceIdx, max)
|
|
840
|
+
hasAsciiStartAfterOptionalOpenWrappers(src, nextNonSpaceIdx, max, lookupCache)
|
|
753
841
|
if (nextNonSpace !== CHAR_ASTERISK && !plusStrictAsciiBoundary) {
|
|
754
842
|
isNextWhiteSpace = false
|
|
755
843
|
}
|
|
@@ -771,8 +859,10 @@ const patchScanDelims = (md) => {
|
|
|
771
859
|
if (!aggressiveMode && count === 1) {
|
|
772
860
|
// Keep local directionality to avoid degrading markdown-it-valid runs,
|
|
773
861
|
// e.g. `[。*a**](u)` where the first `*` should remain opener-only.
|
|
774
|
-
const
|
|
775
|
-
const
|
|
862
|
+
const rightIsOpenWrapper = isWrapperOpenLike(nextChar)
|
|
863
|
+
const leftIsCloseWrapper = isWrapperCloseLike(lastChar)
|
|
864
|
+
const rightIsBoundary = isSingleStarClosingBoundary(nextChar) || rightIsOpenWrapper
|
|
865
|
+
const leftIsBoundary = isSingleStarBoundary(lastChar) || leftIsCloseWrapper
|
|
776
866
|
if (leftJapanese && !rightJapanese && !rightIsBoundary) {
|
|
777
867
|
prevStarFlags = ensurePrevStarFlags(src, start, prevStarFlags)
|
|
778
868
|
if ((prevStarFlags & PREV_STAR_HAS_OPENER) === 0) {
|
|
@@ -781,28 +871,30 @@ const patchScanDelims = (md) => {
|
|
|
781
871
|
} else if (!leftJapanese && rightJapanese && !leftIsBoundary) {
|
|
782
872
|
relaxedOpen = false
|
|
783
873
|
}
|
|
784
|
-
const rightIsOpenWrapper = isWrapperOpenLike(nextChar)
|
|
785
|
-
const leftIsCloseWrapper = isWrapperCloseLike(lastChar)
|
|
786
|
-
prevStarFlags = ensurePrevStarFlags(src, start, prevStarFlags)
|
|
787
|
-
const hasPrevJapaneseOpener = (prevStarFlags & PREV_STAR_HAS_OPENER) !== 0
|
|
788
|
-
const hasJapaneseSincePrevStar = (prevStarFlags & PREV_STAR_HAS_JP_BETWEEN) !== 0
|
|
789
874
|
const leftIsExtraClosePunct = isExtraSingleStarClosePunct(lastChar)
|
|
790
|
-
const
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
875
|
+
const canCheckForceOpen =
|
|
876
|
+
leftJapanese && rightIsOpenWrapper && !isMarkdownStructuralOpenWrapper(nextChar)
|
|
877
|
+
const canCheckForceClose =
|
|
878
|
+
(leftIsCloseWrapper && rightJapanese) ||
|
|
879
|
+
((leftIsCloseWrapper || leftIsExtraClosePunct) && !rightJapanese && !rightIsBoundary)
|
|
880
|
+
if (canCheckForceOpen || canCheckForceClose) {
|
|
881
|
+
prevStarFlags = ensurePrevStarFlags(src, start, prevStarFlags)
|
|
882
|
+
const hasPrevJapaneseOpener = (prevStarFlags & PREV_STAR_HAS_OPENER) !== 0
|
|
883
|
+
const hasJapaneseSincePrevStar = (prevStarFlags & PREV_STAR_HAS_JP_BETWEEN) !== 0
|
|
884
|
+
const canForceCloseByPunct = leftIsExtraClosePunct && hasJapaneseSincePrevStar
|
|
885
|
+
if (canCheckForceOpen && !hasPrevJapaneseOpener) {
|
|
886
|
+
forceOpen = true
|
|
887
|
+
forceClose = false
|
|
888
|
+
} else if (leftIsCloseWrapper && rightJapanese && hasPrevJapaneseOpener) {
|
|
889
|
+
forceOpen = false
|
|
890
|
+
forceClose = true
|
|
891
|
+
} else if ((leftIsCloseWrapper || canForceCloseByPunct) &&
|
|
892
|
+
!rightJapanese &&
|
|
893
|
+
!rightIsBoundary &&
|
|
894
|
+
hasPrevJapaneseOpener) {
|
|
895
|
+
forceOpen = false
|
|
896
|
+
forceClose = true
|
|
897
|
+
}
|
|
806
898
|
}
|
|
807
899
|
}
|
|
808
900
|
const finalOpen = forceOpen === null ? ((base && base.can_open) || relaxedOpen) : forceOpen
|
package/src/token-link-utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Token from 'markdown-it/lib/token.mjs'
|
|
2
2
|
import { isWhiteSpace } from 'markdown-it/lib/common/utils.mjs'
|
|
3
|
-
import { getReferenceCount } from './token-utils.js'
|
|
3
|
+
import { cloneMap, getReferenceCount } from './token-utils.js'
|
|
4
4
|
|
|
5
5
|
const CHAR_OPEN_BRACKET = 0x5B // [
|
|
6
6
|
const CHAR_CLOSE_BRACKET = 0x5D // ]
|
|
@@ -80,11 +80,6 @@ const getNormalizeRef = (state) => {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
const cloneMap = (map) => {
|
|
84
|
-
if (!map || !Array.isArray(map)) return null
|
|
85
|
-
return [map[0], map[1]]
|
|
86
|
-
}
|
|
87
|
-
|
|
88
83
|
const getMapFromTokenRange = (tokens, startIdx, endIdx) => {
|
|
89
84
|
if (!tokens || startIdx > endIdx) return null
|
|
90
85
|
let startLine = null
|
|
@@ -120,10 +115,7 @@ const cloneTextToken = (source, content) => {
|
|
|
120
115
|
}
|
|
121
116
|
|
|
122
117
|
const applyBracketSegmentFlags = (token, seg) => {
|
|
123
|
-
if (seg === '[' || seg === ']') {
|
|
124
|
-
token.__strongJaHasBracket = true
|
|
125
|
-
token.__strongJaBracketAtomic = true
|
|
126
|
-
} else if (seg === '[]') {
|
|
118
|
+
if (seg === '[' || seg === ']' || seg === '[]') {
|
|
127
119
|
token.__strongJaHasBracket = true
|
|
128
120
|
token.__strongJaBracketAtomic = true
|
|
129
121
|
} else {
|
|
@@ -641,16 +633,21 @@ const collectBrokenMarkLinkMergeRemovals = (tokens) => {
|
|
|
641
633
|
|
|
642
634
|
const applyBrokenMarkLinkMergeRemovals = (tokens, removals, onChangeStart = null) => {
|
|
643
635
|
if (!removals || removals.length === 0) return false
|
|
644
|
-
const removeFlags = new Array(tokens.length).fill(false)
|
|
645
636
|
for (let idx = removals.length - 1; idx >= 0; idx--) {
|
|
646
|
-
|
|
647
|
-
if (onChangeStart) onChangeStart(removal.closeIdx)
|
|
648
|
-
removeFlags[removal.closeIdx] = true
|
|
649
|
-
removeFlags[removal.reopenIdx] = true
|
|
637
|
+
if (onChangeStart) onChangeStart(removals[idx].closeIdx)
|
|
650
638
|
}
|
|
651
639
|
const kept = []
|
|
640
|
+
let removalIdx = 0
|
|
641
|
+
let nextRemoval = removals[removalIdx]
|
|
652
642
|
for (let idx = 0; idx < tokens.length; idx++) {
|
|
653
|
-
if (
|
|
643
|
+
if (nextRemoval && (idx === nextRemoval.closeIdx || idx === nextRemoval.reopenIdx)) {
|
|
644
|
+
if (idx === nextRemoval.reopenIdx) {
|
|
645
|
+
removalIdx++
|
|
646
|
+
nextRemoval = removals[removalIdx]
|
|
647
|
+
}
|
|
648
|
+
continue
|
|
649
|
+
}
|
|
650
|
+
kept.push(tokens[idx])
|
|
654
651
|
}
|
|
655
652
|
tokens.splice(0, tokens.length, ...kept)
|
|
656
653
|
return true
|