@peaceroad/markdown-it-strong-ja 0.9.0 → 0.9.2
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 +77 -0
- package/package.json +9 -9
- package/src/token-compat.js +9 -12
- package/src/token-core.js +85 -73
- package/src/token-link-utils.js +1 -6
- package/src/token-postprocess/broken-ref.js +113 -24
- package/src/token-postprocess/emphasis-balance.js +50 -0
- package/src/token-postprocess/fastpaths.js +1 -5
- package/src/token-postprocess/guards.js +75 -15
- package/src/token-postprocess/orchestrator.js +89 -119
- package/src/token-utils.js +222 -130
package/README.md
CHANGED
|
@@ -357,6 +357,83 @@ Supporting visuals:
|
|
|
357
357
|
- `aggressive`:
|
|
358
358
|
`<p>broken **tail <a href="https://x.test">aa<strong>aa</strong><em>Text</em><strong>and<em>More</em>bb</strong>bb</a> after</p>`
|
|
359
359
|
|
|
360
|
+
## Compatibility Notes
|
|
361
|
+
|
|
362
|
+
### `markdown-it-attrs` 5.x parity
|
|
363
|
+
|
|
364
|
+
When `markdown-it-attrs` is installed, strong-ja follows the token stream produced by that plugin and does not reinterpret where `{...}` attributes should be attached. This is intentional: strong-ja should not make attribute syntax mean something different from `markdown-it-attrs` alone.
|
|
365
|
+
|
|
366
|
+
One edge case to be aware of is a tight list item followed by an emphasized line:
|
|
367
|
+
|
|
368
|
+
```markdown
|
|
369
|
+
- e {.li-style}
|
|
370
|
+
*{.ul-style}*
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
With `markdown-it-attrs` 5.x, the first attribute block is consumed as a block-level attribute on the hidden `paragraph_open` inside the tight list. Because that paragraph token is hidden by markdown-it's tight-list rendering, the class is not visible in the final HTML. The second `{.ul-style}` is inside emphasis text, not a suffix after a closed inline token, so it remains literal text:
|
|
374
|
+
|
|
375
|
+
```html
|
|
376
|
+
<ul>
|
|
377
|
+
<li>e
|
|
378
|
+
<em>{.ul-style}</em></li>
|
|
379
|
+
</ul>
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
This output matches `markdown-it-attrs` alone. To attach attributes intentionally, use the syntax owned by `markdown-it-attrs`, for example:
|
|
383
|
+
|
|
384
|
+
```markdown
|
|
385
|
+
- e
|
|
386
|
+
{.ul-style}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
```html
|
|
390
|
+
<ul class="ul-style">
|
|
391
|
+
<li>e</li>
|
|
392
|
+
</ul>
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
or attach inline attributes after the closing inline token:
|
|
396
|
+
|
|
397
|
+
```markdown
|
|
398
|
+
- e
|
|
399
|
+
*x*{.ul-style}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
```html
|
|
403
|
+
<ul>
|
|
404
|
+
<li>e
|
|
405
|
+
<em class="ul-style">x</em></li>
|
|
406
|
+
</ul>
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
strong-ja keeps this as dependency parity rather than adding a local workaround.
|
|
410
|
+
|
|
411
|
+
### `markdown-it` 14.2 astral delimiter policy
|
|
412
|
+
|
|
413
|
+
`markdown-it` 14.2 recognizes astral characters (surrogate pairs) as full Unicode code points when scanning emphasis delimiters. strong-ja keeps `compatible` mode aligned with that upstream behavior.
|
|
414
|
+
|
|
415
|
+
In Japanese modes, strong-ja still only adds its own delimiter relaxation when Japanese/CJK context is present. Astral Han characters, such as CJK Extension B, are treated as CJK context:
|
|
416
|
+
|
|
417
|
+
```markdown
|
|
418
|
+
*𠀋?*abc*
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
```html
|
|
422
|
+
<p><em>𠀋?</em>abc*</p>
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
Emoji or symbol-only English contexts remain aligned with `markdown-it` and are not promoted just because they are astral characters:
|
|
426
|
+
|
|
427
|
+
```markdown
|
|
428
|
+
*😀?*abc*
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
```html
|
|
432
|
+
<p>*😀?<em>abc</em></p>
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
Symbols inside Japanese prose may still be emphasized by the existing Japanese-context rule, for example `**😀**です` can render as `<p><strong>😀</strong>です</p>`. Use `mode: 'compatible'` when exact `markdown-it` 14.2 delimiter behavior is required.
|
|
436
|
+
|
|
360
437
|
## Options
|
|
361
438
|
|
|
362
439
|
### `mode`
|
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.9.
|
|
4
|
+
"version": "0.9.2",
|
|
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
|
},
|
|
@@ -33,17 +34,16 @@
|
|
|
33
34
|
"author": "peaceroad <peaceroad@gmail.com>",
|
|
34
35
|
"license": "MIT",
|
|
35
36
|
"dependencies": {
|
|
36
|
-
"markdown-it": "^14.
|
|
37
|
+
"markdown-it": "^14.2.0"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
|
-
"@peaceroad/markdown-it-cjk-breaks-mod": "^0.1.
|
|
40
|
-
"@peaceroad/markdown-it-hr-sandwiched-semantic-container": "^0.
|
|
41
|
-
"@peaceroad/markdown-it-renderer-image": "^0.
|
|
42
|
-
"@peaceroad/markdown-it-renderer-inline-text": "^0.8.
|
|
43
|
-
"markdown-it-attrs": "^
|
|
40
|
+
"@peaceroad/markdown-it-cjk-breaks-mod": "^0.1.11",
|
|
41
|
+
"@peaceroad/markdown-it-hr-sandwiched-semantic-container": "^0.12.0",
|
|
42
|
+
"@peaceroad/markdown-it-renderer-image": "^0.16.0",
|
|
43
|
+
"@peaceroad/markdown-it-renderer-inline-text": "^0.8.1",
|
|
44
|
+
"markdown-it-attrs": "^5.0.0",
|
|
44
45
|
"markdown-it-sub": "^2.0.0",
|
|
45
46
|
"markdown-it-sup": "^2.0.0",
|
|
46
|
-
"p7d-markdown-it-p-captions": "^0.
|
|
47
|
+
"p7d-markdown-it-p-captions": "^0.23.0"
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
|
-
|
package/src/token-compat.js
CHANGED
|
@@ -2,6 +2,9 @@ import Token from 'markdown-it/lib/token.mjs'
|
|
|
2
2
|
import {
|
|
3
3
|
REG_ATTRS,
|
|
4
4
|
isJapaneseChar,
|
|
5
|
+
isAsciiWordCode,
|
|
6
|
+
codePointAtSafe,
|
|
7
|
+
codePointBeforeSafe,
|
|
5
8
|
hasCjkBreaksRule,
|
|
6
9
|
isCjkBreaksRuleName,
|
|
7
10
|
getRuntimeOpt,
|
|
@@ -9,12 +12,6 @@ import {
|
|
|
9
12
|
moveRuleAfter
|
|
10
13
|
} from './token-utils.js'
|
|
11
14
|
|
|
12
|
-
const isAsciiWordCode = (code) => {
|
|
13
|
-
return (code >= 0x30 && code <= 0x39) ||
|
|
14
|
-
(code >= 0x41 && code <= 0x5A) ||
|
|
15
|
-
(code >= 0x61 && code <= 0x7A)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
15
|
const trimTrailingSpaceTab = (text) => {
|
|
19
16
|
if (!text) return text
|
|
20
17
|
let end = text.length
|
|
@@ -124,8 +121,8 @@ const registerTokenCompat = (md, baseOpt) => {
|
|
|
124
121
|
if (!prevToken || !nextToken) continue
|
|
125
122
|
if (prevToken.type !== 'text' || !prevToken.content) continue
|
|
126
123
|
if (nextToken.type !== 'text' || !nextToken.content) continue
|
|
127
|
-
const prevCharCode = prevToken.content
|
|
128
|
-
const nextCharCode = nextToken.content
|
|
124
|
+
const prevCharCode = codePointBeforeSafe(prevToken.content, prevToken.content.length, 0)
|
|
125
|
+
const nextCharCode = codePointAtSafe(nextToken.content, 0, 0)
|
|
129
126
|
const isAsciiWord = isAsciiWordCode(nextCharCode)
|
|
130
127
|
const shouldReplace = isAsciiWord &&
|
|
131
128
|
isJapaneseChar(prevCharCode) && !isJapaneseChar(nextCharCode)
|
|
@@ -143,8 +140,8 @@ const registerTokenCompat = (md, baseOpt) => {
|
|
|
143
140
|
for (let idx = 0; idx < child.content.length; idx++) {
|
|
144
141
|
const ch = child.content[idx]
|
|
145
142
|
if (ch === '\n') {
|
|
146
|
-
const prevCharCode =
|
|
147
|
-
const nextCharCode =
|
|
143
|
+
const prevCharCode = codePointBeforeSafe(child.content, idx, 0)
|
|
144
|
+
const nextCharCode = codePointAtSafe(child.content, idx + 1, 0)
|
|
148
145
|
const isAsciiWord = isAsciiWordCode(nextCharCode)
|
|
149
146
|
const shouldReplace = isAsciiWord &&
|
|
150
147
|
isJapaneseChar(prevCharCode) && !isJapaneseChar(nextCharCode)
|
|
@@ -192,7 +189,7 @@ const registerTokenCompat = (md, baseOpt) => {
|
|
|
192
189
|
if (!prevTextCharCode || !isJapaneseChar(prevTextCharCode)) continue
|
|
193
190
|
const next = children[j + 1]
|
|
194
191
|
if (!next || next.type !== 'text' || !next.content) continue
|
|
195
|
-
const nextCharCode = next.content
|
|
192
|
+
const nextCharCode = codePointAtSafe(next.content, 0, 0)
|
|
196
193
|
if (nextCharCode !== 0x7B) continue
|
|
197
194
|
child.type = 'softbreak'
|
|
198
195
|
child.tag = ''
|
|
@@ -201,7 +198,7 @@ const registerTokenCompat = (md, baseOpt) => {
|
|
|
201
198
|
child.info = ''
|
|
202
199
|
continue
|
|
203
200
|
}
|
|
204
|
-
prevTextCharCode = child.content
|
|
201
|
+
prevTextCharCode = codePointBeforeSafe(child.content, child.content.length, 0)
|
|
205
202
|
}
|
|
206
203
|
}
|
|
207
204
|
}
|
package/src/token-core.js
CHANGED
|
@@ -2,16 +2,19 @@ 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
|
-
|
|
6
|
+
codePointAtSafe,
|
|
7
|
+
codePointBeforeSafe,
|
|
8
|
+
codePointStartBefore,
|
|
9
|
+
codePointSize,
|
|
9
10
|
isJapaneseChar,
|
|
11
|
+
isAsciiWordCode,
|
|
12
|
+
isSoftSpaceCode,
|
|
10
13
|
MODE_FLAG_COMPATIBLE,
|
|
11
14
|
MODE_FLAG_AGGRESSIVE,
|
|
12
15
|
MODE_FLAG_JAPANESE_PLUS,
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
hasRuntimeOverride,
|
|
17
|
+
getRuntimeOpt
|
|
15
18
|
} from './token-utils.js'
|
|
16
19
|
|
|
17
20
|
const SCAN_DELIMS_PATCHED = Symbol.for('strongJaTokenScanDelimsPatched')
|
|
@@ -20,10 +23,6 @@ const PREV_STAR_HAS_OPENER = 1
|
|
|
20
23
|
const PREV_STAR_HAS_JP_BETWEEN = 2
|
|
21
24
|
const SCAN_DELIMS_LOOKUP_KEY = Symbol.for('strongJaTokenScanDelimsLookup')
|
|
22
25
|
|
|
23
|
-
const isSoftSpaceCode = (code) => {
|
|
24
|
-
return code === CHAR_SPACE || code === CHAR_TAB || code === CHAR_IDEOGRAPHIC_SPACE
|
|
25
|
-
}
|
|
26
|
-
|
|
27
26
|
const isPlusQuoteWrapperOpen = (code) => {
|
|
28
27
|
return code === 0x2018 || // ‘
|
|
29
28
|
code === 0x201C || // “
|
|
@@ -256,12 +255,6 @@ const isSingleStarClosingBoundary = (code) => {
|
|
|
256
255
|
isClosingBracketLike(code)
|
|
257
256
|
}
|
|
258
257
|
|
|
259
|
-
const isAsciiAlphaNum = (code) => {
|
|
260
|
-
return (code >= 0x30 && code <= 0x39) ||
|
|
261
|
-
(code >= 0x41 && code <= 0x5A) ||
|
|
262
|
-
(code >= 0x61 && code <= 0x7A)
|
|
263
|
-
}
|
|
264
|
-
|
|
265
258
|
const isAsciiGuardOpenWrapper = (code) => {
|
|
266
259
|
return code === 0x22 || // "
|
|
267
260
|
code === 0x27 || // '
|
|
@@ -291,24 +284,34 @@ const buildScanDelimsLookupCache = (src) => {
|
|
|
291
284
|
|
|
292
285
|
let prev = -1
|
|
293
286
|
for (let i = 0; i < len; i++) {
|
|
294
|
-
const code = src
|
|
287
|
+
const code = codePointAtSafe(src, i)
|
|
295
288
|
if (code === CHAR_NEWLINE) {
|
|
296
289
|
prev = -1
|
|
297
290
|
continue
|
|
298
291
|
}
|
|
292
|
+
const size = codePointSize(code)
|
|
299
293
|
if (!isSoftSpaceCode(code)) prev = i
|
|
300
294
|
prevNonSpaceSameLine[i] = prev
|
|
295
|
+
if (size === 2 && i + 1 < len) {
|
|
296
|
+
prevNonSpaceSameLine[i + 1] = prev
|
|
297
|
+
i++
|
|
298
|
+
}
|
|
301
299
|
}
|
|
302
300
|
|
|
303
301
|
let next = -1
|
|
304
302
|
for (let i = len - 1; i >= 0; i--) {
|
|
305
|
-
const
|
|
303
|
+
const cpStart = codePointStartBefore(src, i + 1)
|
|
304
|
+
const code = cpStart === -1 ? 0 : codePointAtSafe(src, cpStart)
|
|
306
305
|
if (code === CHAR_NEWLINE) {
|
|
307
306
|
next = -1
|
|
308
307
|
continue
|
|
309
308
|
}
|
|
310
|
-
if (!isSoftSpaceCode(code)) next =
|
|
309
|
+
if (!isSoftSpaceCode(code)) next = cpStart
|
|
311
310
|
nextNonSpaceSameLine[i] = next
|
|
311
|
+
if (cpStart !== i) {
|
|
312
|
+
nextNonSpaceSameLine[cpStart] = next
|
|
313
|
+
i = cpStart
|
|
314
|
+
}
|
|
312
315
|
}
|
|
313
316
|
|
|
314
317
|
return {
|
|
@@ -334,11 +337,13 @@ const findPrevNonSpaceIndex = (src, start, lookupCache = null) => {
|
|
|
334
337
|
start < lookupCache.prevNonSpaceSameLine.length) {
|
|
335
338
|
return lookupCache.prevNonSpaceSameLine[start]
|
|
336
339
|
}
|
|
337
|
-
for (let i = start; i >= 0;
|
|
338
|
-
const
|
|
340
|
+
for (let i = start; i >= 0;) {
|
|
341
|
+
const cpStart = codePointStartBefore(src, i + 1)
|
|
342
|
+
if (cpStart === -1) return -1
|
|
343
|
+
const code = codePointAtSafe(src, cpStart)
|
|
339
344
|
if (code === CHAR_NEWLINE) return -1
|
|
340
|
-
if (isSoftSpaceCode(code))
|
|
341
|
-
|
|
345
|
+
if (!isSoftSpaceCode(code)) return cpStart
|
|
346
|
+
i = cpStart - 1
|
|
342
347
|
}
|
|
343
348
|
return -1
|
|
344
349
|
}
|
|
@@ -351,11 +356,11 @@ const findNextNonSpaceIndex = (src, start, max, lookupCache = null) => {
|
|
|
351
356
|
const next = lookupCache.nextNonSpaceSameLine[start]
|
|
352
357
|
return next !== -1 && next < max ? next : -1
|
|
353
358
|
}
|
|
354
|
-
for (let i = start; i < max;
|
|
355
|
-
const code = src
|
|
359
|
+
for (let i = start; i < max;) {
|
|
360
|
+
const code = codePointAtSafe(src, i)
|
|
356
361
|
if (code === CHAR_NEWLINE) return -1
|
|
357
|
-
if (isSoftSpaceCode(code))
|
|
358
|
-
|
|
362
|
+
if (!isSoftSpaceCode(code)) return i
|
|
363
|
+
i += codePointSize(code)
|
|
359
364
|
}
|
|
360
365
|
return -1
|
|
361
366
|
}
|
|
@@ -364,26 +369,26 @@ const hasAsciiStartAfterOptionalOpenWrappers = (src, index, max, lookupCache = n
|
|
|
364
369
|
let i = index
|
|
365
370
|
// Two wrappers are enough for common shapes: * [ "word" ]*
|
|
366
371
|
for (let wrappers = 0; wrappers < 2 && i >= 0 && i < max; wrappers++) {
|
|
367
|
-
const code = src
|
|
372
|
+
const code = codePointAtSafe(src, i)
|
|
368
373
|
if (!isAsciiGuardOpenWrapper(code)) break
|
|
369
374
|
i = findNextNonSpaceIndex(src, i + 1, max, lookupCache)
|
|
370
375
|
if (i === -1) return false
|
|
371
376
|
}
|
|
372
377
|
if (i < 0 || i >= max) return false
|
|
373
|
-
return
|
|
378
|
+
return isAsciiWordCode(codePointAtSafe(src, i))
|
|
374
379
|
}
|
|
375
380
|
|
|
376
381
|
const hasAsciiEndBeforeOptionalCloseWrappers = (src, index, lookupCache = null) => {
|
|
377
382
|
let i = index
|
|
378
383
|
// Two wrappers are enough for common shapes: *["word"] *
|
|
379
384
|
for (let wrappers = 0; wrappers < 2 && i >= 0; wrappers++) {
|
|
380
|
-
const code = src
|
|
385
|
+
const code = codePointAtSafe(src, i)
|
|
381
386
|
if (!isAsciiGuardCloseWrapper(code)) break
|
|
382
387
|
i = findPrevNonSpaceIndex(src, i - 1, lookupCache)
|
|
383
388
|
if (i === -1) return false
|
|
384
389
|
}
|
|
385
390
|
if (i < 0) return false
|
|
386
|
-
return
|
|
391
|
+
return isAsciiWordCode(codePointAtSafe(src, i))
|
|
387
392
|
}
|
|
388
393
|
|
|
389
394
|
const isMarkdownStructuralOpenWrapper = (code) => {
|
|
@@ -420,18 +425,20 @@ const findPrevNonSpaceLimited = (src, start, maxLook, lookupCache = null) => {
|
|
|
420
425
|
start < lookupCache.prevNonSpaceSameLine.length) {
|
|
421
426
|
const prev = lookupCache.prevNonSpaceSameLine[start]
|
|
422
427
|
if (prev !== -1 && (start - prev) < maxLook) {
|
|
423
|
-
return src
|
|
428
|
+
return codePointAtSafe(src, prev)
|
|
424
429
|
}
|
|
425
430
|
return 0
|
|
426
431
|
}
|
|
427
432
|
let looked = 0
|
|
428
|
-
for (let i = start; i >= 0;
|
|
433
|
+
for (let i = start; i >= 0;) {
|
|
429
434
|
if (looked >= maxLook) break
|
|
430
|
-
const
|
|
431
|
-
|
|
435
|
+
const cpStart = codePointStartBefore(src, i + 1)
|
|
436
|
+
if (cpStart === -1) break
|
|
437
|
+
const code = codePointAtSafe(src, cpStart)
|
|
438
|
+
looked += i - cpStart + 1
|
|
432
439
|
if (code === CHAR_NEWLINE) return 0
|
|
433
|
-
if (isSoftSpaceCode(code))
|
|
434
|
-
|
|
440
|
+
if (!isSoftSpaceCode(code)) return code
|
|
441
|
+
i = cpStart - 1
|
|
435
442
|
}
|
|
436
443
|
return 0
|
|
437
444
|
}
|
|
@@ -443,18 +450,19 @@ const findNextNonSpaceLimited = (src, start, max, maxLook, lookupCache = null) =
|
|
|
443
450
|
start < lookupCache.nextNonSpaceSameLine.length) {
|
|
444
451
|
const next = lookupCache.nextNonSpaceSameLine[start]
|
|
445
452
|
if (next !== -1 && next < max && (next - start) < maxLook) {
|
|
446
|
-
return src
|
|
453
|
+
return codePointAtSafe(src, next)
|
|
447
454
|
}
|
|
448
455
|
return 0
|
|
449
456
|
}
|
|
450
457
|
let looked = 0
|
|
451
|
-
for (let i = start; i < max;
|
|
458
|
+
for (let i = start; i < max;) {
|
|
452
459
|
if (looked >= maxLook) break
|
|
453
|
-
const code = src
|
|
454
|
-
|
|
460
|
+
const code = codePointAtSafe(src, i)
|
|
461
|
+
const size = codePointSize(code)
|
|
462
|
+
looked += size
|
|
455
463
|
if (code === CHAR_NEWLINE) return 0
|
|
456
|
-
if (isSoftSpaceCode(code))
|
|
457
|
-
|
|
464
|
+
if (!isSoftSpaceCode(code)) return code
|
|
465
|
+
i += size
|
|
458
466
|
}
|
|
459
467
|
return 0
|
|
460
468
|
}
|
|
@@ -473,8 +481,8 @@ const hasJapaneseContextForBracketWrapper = (src, start, pos, max, lastChar, nex
|
|
|
473
481
|
|
|
474
482
|
const scanPrevSingleStarContextFlags = (src, start) => {
|
|
475
483
|
let hasJapaneseBetween = false
|
|
476
|
-
for (let i = start
|
|
477
|
-
const code = src
|
|
484
|
+
for (let i = codePointStartBefore(src, start); i >= 0; i = codePointStartBefore(src, i)) {
|
|
485
|
+
const code = codePointAtSafe(src, i)
|
|
478
486
|
if (code === CHAR_NEWLINE) break
|
|
479
487
|
if (isSentenceBoundaryStop(code) && i < start - 1) break
|
|
480
488
|
if (code !== CHAR_ASTERISK) {
|
|
@@ -486,8 +494,8 @@ const scanPrevSingleStarContextFlags = (src, start) => {
|
|
|
486
494
|
backslashCount++
|
|
487
495
|
}
|
|
488
496
|
if ((backslashCount % 2) === 1) continue
|
|
489
|
-
const prevCode = i
|
|
490
|
-
const nextCode =
|
|
497
|
+
const prevCode = codePointBeforeSafe(src, i, 0)
|
|
498
|
+
const nextCode = codePointAtSafe(src, i + 1, 0)
|
|
491
499
|
if (prevCode === CHAR_ASTERISK || nextCode === CHAR_ASTERISK) continue
|
|
492
500
|
return hasJapaneseBetween ? PREV_STAR_HAS_OPENER | PREV_STAR_HAS_JP_BETWEEN : PREV_STAR_HAS_OPENER
|
|
493
501
|
}
|
|
@@ -789,12 +797,12 @@ const patchScanDelims = (md) => {
|
|
|
789
797
|
const aggressiveMode = (modeFlags & MODE_FLAG_AGGRESSIVE) !== 0
|
|
790
798
|
const max = this.posMax
|
|
791
799
|
let lookupCache = null
|
|
792
|
-
const lastChar =
|
|
800
|
+
const lastChar = codePointBeforeSafe(src, start, 0x20)
|
|
793
801
|
|
|
794
802
|
const count = base && base.length ? base.length : 1
|
|
795
803
|
const pos = start + count
|
|
796
804
|
|
|
797
|
-
const nextChar =
|
|
805
|
+
const nextChar = codePointAtSafe(src, pos, 0x20)
|
|
798
806
|
let prevStarFlags = -1
|
|
799
807
|
|
|
800
808
|
const leftJapanese = isJapaneseChar(lastChar)
|
|
@@ -830,7 +838,7 @@ const patchScanDelims = (md) => {
|
|
|
830
838
|
lookupCache || (lookupCache = getScanDelimsLookupCache(this))
|
|
831
839
|
)
|
|
832
840
|
if (prevNonSpaceIdx !== -1) {
|
|
833
|
-
const prevNonSpaceLocal = src
|
|
841
|
+
const prevNonSpaceLocal = codePointAtSafe(src, prevNonSpaceIdx)
|
|
834
842
|
const plusStrictAsciiBoundary = plusMode &&
|
|
835
843
|
hasAsciiEndBeforeOptionalCloseWrappers(src, prevNonSpaceIdx, lookupCache)
|
|
836
844
|
if (prevNonSpaceLocal !== CHAR_ASTERISK && !plusStrictAsciiBoundary) {
|
|
@@ -846,7 +854,7 @@ const patchScanDelims = (md) => {
|
|
|
846
854
|
lookupCache || (lookupCache = getScanDelimsLookupCache(this))
|
|
847
855
|
)
|
|
848
856
|
if (nextNonSpaceIdx !== -1) {
|
|
849
|
-
const nextNonSpace = src
|
|
857
|
+
const nextNonSpace = codePointAtSafe(src, nextNonSpaceIdx)
|
|
850
858
|
const plusStrictAsciiBoundary = plusMode &&
|
|
851
859
|
hasAsciiStartAfterOptionalOpenWrappers(src, nextNonSpaceIdx, max, lookupCache)
|
|
852
860
|
if (nextNonSpace !== CHAR_ASTERISK && !plusStrictAsciiBoundary) {
|
|
@@ -870,8 +878,10 @@ const patchScanDelims = (md) => {
|
|
|
870
878
|
if (!aggressiveMode && count === 1) {
|
|
871
879
|
// Keep local directionality to avoid degrading markdown-it-valid runs,
|
|
872
880
|
// e.g. `[。*a**](u)` where the first `*` should remain opener-only.
|
|
873
|
-
const
|
|
874
|
-
const
|
|
881
|
+
const rightIsOpenWrapper = isWrapperOpenLike(nextChar)
|
|
882
|
+
const leftIsCloseWrapper = isWrapperCloseLike(lastChar)
|
|
883
|
+
const rightIsBoundary = isSingleStarClosingBoundary(nextChar) || rightIsOpenWrapper
|
|
884
|
+
const leftIsBoundary = isSingleStarBoundary(lastChar) || leftIsCloseWrapper
|
|
875
885
|
if (leftJapanese && !rightJapanese && !rightIsBoundary) {
|
|
876
886
|
prevStarFlags = ensurePrevStarFlags(src, start, prevStarFlags)
|
|
877
887
|
if ((prevStarFlags & PREV_STAR_HAS_OPENER) === 0) {
|
|
@@ -880,28 +890,30 @@ const patchScanDelims = (md) => {
|
|
|
880
890
|
} else if (!leftJapanese && rightJapanese && !leftIsBoundary) {
|
|
881
891
|
relaxedOpen = false
|
|
882
892
|
}
|
|
883
|
-
const rightIsOpenWrapper = isWrapperOpenLike(nextChar)
|
|
884
|
-
const leftIsCloseWrapper = isWrapperCloseLike(lastChar)
|
|
885
|
-
prevStarFlags = ensurePrevStarFlags(src, start, prevStarFlags)
|
|
886
|
-
const hasPrevJapaneseOpener = (prevStarFlags & PREV_STAR_HAS_OPENER) !== 0
|
|
887
|
-
const hasJapaneseSincePrevStar = (prevStarFlags & PREV_STAR_HAS_JP_BETWEEN) !== 0
|
|
888
893
|
const leftIsExtraClosePunct = isExtraSingleStarClosePunct(lastChar)
|
|
889
|
-
const
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
894
|
+
const canCheckForceOpen =
|
|
895
|
+
leftJapanese && rightIsOpenWrapper && !isMarkdownStructuralOpenWrapper(nextChar)
|
|
896
|
+
const canCheckForceClose =
|
|
897
|
+
(leftIsCloseWrapper && rightJapanese) ||
|
|
898
|
+
((leftIsCloseWrapper || leftIsExtraClosePunct) && !rightJapanese && !rightIsBoundary)
|
|
899
|
+
if (canCheckForceOpen || canCheckForceClose) {
|
|
900
|
+
prevStarFlags = ensurePrevStarFlags(src, start, prevStarFlags)
|
|
901
|
+
const hasPrevJapaneseOpener = (prevStarFlags & PREV_STAR_HAS_OPENER) !== 0
|
|
902
|
+
const hasJapaneseSincePrevStar = (prevStarFlags & PREV_STAR_HAS_JP_BETWEEN) !== 0
|
|
903
|
+
const canForceCloseByPunct = leftIsExtraClosePunct && hasJapaneseSincePrevStar
|
|
904
|
+
if (canCheckForceOpen && !hasPrevJapaneseOpener) {
|
|
905
|
+
forceOpen = true
|
|
906
|
+
forceClose = false
|
|
907
|
+
} else if (leftIsCloseWrapper && rightJapanese && hasPrevJapaneseOpener) {
|
|
908
|
+
forceOpen = false
|
|
909
|
+
forceClose = true
|
|
910
|
+
} else if ((leftIsCloseWrapper || canForceCloseByPunct) &&
|
|
911
|
+
!rightJapanese &&
|
|
912
|
+
!rightIsBoundary &&
|
|
913
|
+
hasPrevJapaneseOpener) {
|
|
914
|
+
forceOpen = false
|
|
915
|
+
forceClose = true
|
|
916
|
+
}
|
|
905
917
|
}
|
|
906
918
|
}
|
|
907
919
|
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
|