@stream-mdx/core 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -3
- package/README.md +47 -33
- package/dist/code-highlighting.cjs +6 -0
- package/dist/code-highlighting.mjs +6 -0
- package/dist/index.cjs +150 -29
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +150 -29
- package/dist/inline-parser.cjs +6 -6
- package/dist/inline-parser.mjs +6 -6
- package/dist/mixed-content.cjs +25 -5
- package/dist/mixed-content.mjs +25 -5
- package/dist/perf/patch-batching.cjs +38 -0
- package/dist/perf/patch-batching.d.cts +3 -2
- package/dist/perf/patch-batching.d.ts +3 -2
- package/dist/perf/patch-batching.mjs +37 -0
- package/dist/perf/patch-coalescing.cjs +10 -5
- package/dist/perf/patch-coalescing.mjs +10 -5
- package/dist/streaming/inline-streaming.cjs +16 -3
- package/dist/streaming/inline-streaming.mjs +16 -3
- package/dist/types.d.cts +32 -1
- package/dist/types.d.ts +32 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
# @stream-mdx/core
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
3
|
+
## 0.5.0
|
|
4
4
|
|
|
5
|
-
###
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Promote the current hardening and product-surface tranche as `0.5.0`.
|
|
8
|
+
|
|
9
|
+
This release rolls up:
|
|
6
10
|
|
|
7
|
-
-
|
|
11
|
+
- renderer/store correctness hardening
|
|
12
|
+
- seeded smoke and scheduler-parity release discipline
|
|
13
|
+
- benchmark methodology and comparison cleanup
|
|
14
|
+
- docs, showcase, and TUI/product surface maturation
|
|
8
15
|
|
|
9
16
|
## 0.1.0
|
|
10
17
|
|
package/README.md
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
# `@stream-mdx/core`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`@stream-mdx/core` is the React-free foundation of the StreamMDX stack. It provides the shared types, snapshot utilities, sanitization primitives, inline/mixed-content helpers, and performance/backpressure utilities used by the worker and renderer layers.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Most consumers should install `stream-mdx` and follow the main docs. Use `@stream-mdx/core` directly if you’re building tooling or customizing lower-level behavior.
|
|
5
|
+
If you are building apps, start with [`stream-mdx`](../stream-mdx/README.md). Use `@stream-mdx/core` directly when you are building tooling, protocol consumers, performance instrumentation, or lower-level integrations.
|
|
8
6
|
|
|
9
7
|
## Install
|
|
10
8
|
|
|
@@ -12,43 +10,59 @@ Most consumers should install `stream-mdx` and follow the main docs. Use `@strea
|
|
|
12
10
|
npm install @stream-mdx/core
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
##
|
|
13
|
+
## Export Surface
|
|
14
|
+
|
|
15
|
+
| Export | Purpose |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| `@stream-mdx/core` | Main types/helpers surface |
|
|
18
|
+
| `@stream-mdx/core/types` | Shared types |
|
|
19
|
+
| `@stream-mdx/core/utils` | General utilities |
|
|
20
|
+
| `@stream-mdx/core/code-highlighting` | Code-highlighting helpers |
|
|
21
|
+
| `@stream-mdx/core/inline-parser` | Inline parsing helpers |
|
|
22
|
+
| `@stream-mdx/core/mixed-content` | Mixed-content parsing helpers |
|
|
23
|
+
| `@stream-mdx/core/worker-html-sanitizer` | Worker-side HTML sanitization helpers |
|
|
24
|
+
| `@stream-mdx/core/security` | Security-oriented helpers |
|
|
25
|
+
| `@stream-mdx/core/perf/backpressure` | Backpressure config and helpers |
|
|
26
|
+
| `@stream-mdx/core/perf/patch-batching` | Patch batching helpers |
|
|
27
|
+
| `@stream-mdx/core/perf/patch-coalescing` | Patch coalescing helpers |
|
|
28
|
+
| `@stream-mdx/core/streaming/custom-matcher` | Custom matcher hooks |
|
|
29
|
+
| `@stream-mdx/core/streaming/inline-streaming` | Inline streaming helpers |
|
|
16
30
|
|
|
17
|
-
|
|
18
|
-
- `@stream-mdx/core/types`
|
|
19
|
-
- `@stream-mdx/core/utils`
|
|
20
|
-
- `@stream-mdx/core/code-highlighting`
|
|
21
|
-
- `@stream-mdx/core/inline-parser`
|
|
22
|
-
- `@stream-mdx/core/mixed-content`
|
|
23
|
-
- `@stream-mdx/core/worker-html-sanitizer`
|
|
24
|
-
- `@stream-mdx/core/security`
|
|
25
|
-
- `@stream-mdx/core/perf/backpressure`
|
|
26
|
-
- `@stream-mdx/core/perf/patch-batching`
|
|
27
|
-
- `@stream-mdx/core/perf/patch-coalescing`
|
|
28
|
-
- `@stream-mdx/core/streaming/custom-matcher`
|
|
31
|
+
## Typical Uses
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
### Backpressure tuning
|
|
31
34
|
|
|
32
35
|
```ts
|
|
33
36
|
import { DEFAULT_BACKPRESSURE_CONFIG } from "@stream-mdx/core/perf/backpressure";
|
|
34
37
|
|
|
35
|
-
export
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
export const rendererBackpressure = {
|
|
39
|
+
...DEFAULT_BACKPRESSURE_CONFIG,
|
|
40
|
+
maxPendingBatches: 64,
|
|
41
|
+
};
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Patch batching/coalescing experiments
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { splitPatchBatch } from "@stream-mdx/core/perf/patch-batching";
|
|
38
48
|
```
|
|
39
49
|
|
|
40
|
-
|
|
50
|
+
### Lower-level typed integrations
|
|
51
|
+
|
|
52
|
+
Use `@stream-mdx/core` together with `@stream-mdx/protocol` when you want a typed transport or a non-React consumer of StreamMDX patch data.
|
|
53
|
+
|
|
54
|
+
## Related Packages
|
|
41
55
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
-
|
|
56
|
+
| Package | Role |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| [`@stream-mdx/worker`](../markdown-v2-worker/README.md) | Worker runtime and hosted worker helpers |
|
|
59
|
+
| [`@stream-mdx/react`](../markdown-v2-react/README.md) | React renderer and server helpers |
|
|
60
|
+
| [`@stream-mdx/protocol`](../markdown-v2-protocol/README.md) | Protocol envelope/types for transport |
|
|
61
|
+
| [`@stream-mdx/tui`](../markdown-v2-tui/README.md) | TUI utilities and snapshot store |
|
|
45
62
|
|
|
46
|
-
##
|
|
63
|
+
## Documentation
|
|
47
64
|
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
- `@stream-mdx/tui` for NDJSON helpers + a snapshot store (terminal UIs)
|
|
53
|
-
- `@stream-mdx/mermaid` for Mermaid diagram rendering (optional)
|
|
54
|
-
- `@stream-mdx/theme-tailwind` for an optional Tailwind-friendly theme (optional)
|
|
65
|
+
- [`../../docs/PUBLIC_API.md`](../../docs/PUBLIC_API.md)
|
|
66
|
+
- [`../../docs/SECURITY_MODEL.md`](../../docs/SECURITY_MODEL.md)
|
|
67
|
+
- [`../../docs/STREAMMDX_JSON_DIFF_SPEC.md`](../../docs/STREAMMDX_JSON_DIFF_SPEC.md)
|
|
68
|
+
- [`../../docs/DETERMINISM.md`](../../docs/DETERMINISM.md)
|
|
@@ -35,6 +35,9 @@ function normalizeNewlines(input) {
|
|
|
35
35
|
function isFenceLine(line) {
|
|
36
36
|
return /^```/.test(line.trim());
|
|
37
37
|
}
|
|
38
|
+
function isTrailingPartialFenceLine(line) {
|
|
39
|
+
return /^[\t ]*`{1,2}[\t ]*$/.test(line);
|
|
40
|
+
}
|
|
38
41
|
function stripCodeFence(raw) {
|
|
39
42
|
if (!raw) {
|
|
40
43
|
return { code: "", info: "", hadFence: false };
|
|
@@ -58,6 +61,9 @@ function stripCodeFence(raw) {
|
|
|
58
61
|
return { code: codeLines2.join("\n"), info, hadFence: true };
|
|
59
62
|
}
|
|
60
63
|
const codeLines = lines.slice(1);
|
|
64
|
+
if (!normalized.endsWith("\n") && codeLines.length > 0 && isTrailingPartialFenceLine(codeLines[codeLines.length - 1] ?? "")) {
|
|
65
|
+
codeLines.pop();
|
|
66
|
+
}
|
|
61
67
|
return { code: codeLines.join("\n"), info, hadFence: true };
|
|
62
68
|
}
|
|
63
69
|
function getDomParser() {
|
|
@@ -5,6 +5,9 @@ function normalizeNewlines(input) {
|
|
|
5
5
|
function isFenceLine(line) {
|
|
6
6
|
return /^```/.test(line.trim());
|
|
7
7
|
}
|
|
8
|
+
function isTrailingPartialFenceLine(line) {
|
|
9
|
+
return /^[\t ]*`{1,2}[\t ]*$/.test(line);
|
|
10
|
+
}
|
|
8
11
|
function stripCodeFence(raw) {
|
|
9
12
|
if (!raw) {
|
|
10
13
|
return { code: "", info: "", hadFence: false };
|
|
@@ -28,6 +31,9 @@ function stripCodeFence(raw) {
|
|
|
28
31
|
return { code: codeLines2.join("\n"), info, hadFence: true };
|
|
29
32
|
}
|
|
30
33
|
const codeLines = lines.slice(1);
|
|
34
|
+
if (!normalized.endsWith("\n") && codeLines.length > 0 && isTrailingPartialFenceLine(codeLines[codeLines.length - 1] ?? "")) {
|
|
35
|
+
codeLines.pop();
|
|
36
|
+
}
|
|
31
37
|
return { code: codeLines.join("\n"), info, hadFence: true };
|
|
32
38
|
}
|
|
33
39
|
function getDomParser() {
|
package/dist/index.cjs
CHANGED
|
@@ -115,6 +115,9 @@ function normalizeNewlines(input) {
|
|
|
115
115
|
function isFenceLine(line) {
|
|
116
116
|
return /^```/.test(line.trim());
|
|
117
117
|
}
|
|
118
|
+
function isTrailingPartialFenceLine(line) {
|
|
119
|
+
return /^[\t ]*`{1,2}[\t ]*$/.test(line);
|
|
120
|
+
}
|
|
118
121
|
function stripCodeFence(raw) {
|
|
119
122
|
if (!raw) {
|
|
120
123
|
return { code: "", info: "", hadFence: false };
|
|
@@ -138,6 +141,9 @@ function stripCodeFence(raw) {
|
|
|
138
141
|
return { code: codeLines2.join("\n"), info, hadFence: true };
|
|
139
142
|
}
|
|
140
143
|
const codeLines = lines.slice(1);
|
|
144
|
+
if (!normalized.endsWith("\n") && codeLines.length > 0 && isTrailingPartialFenceLine(codeLines[codeLines.length - 1] ?? "")) {
|
|
145
|
+
codeLines.pop();
|
|
146
|
+
}
|
|
141
147
|
return { code: codeLines.join("\n"), info, hadFence: true };
|
|
142
148
|
}
|
|
143
149
|
function getDomParser() {
|
|
@@ -520,12 +526,12 @@ var InlineParser = class {
|
|
|
520
526
|
this.registerPlugin({
|
|
521
527
|
id: "strong-emphasis",
|
|
522
528
|
priority: 6,
|
|
523
|
-
re: /\*\*\*([^*\n]+?)\*\*\*|\*\*([^*\n]+?)
|
|
529
|
+
re: /\*\*\*([^*\n]+?)\*\*\*|\*\*([^*\n]+?)\*\*|(?<![\w\\])__([^_\n]+?)__(?!\w)/g,
|
|
524
530
|
toNode: (match) => ({
|
|
525
531
|
kind: "strong",
|
|
526
|
-
children: [{ kind: "text", text: match[1] || match[2] }]
|
|
532
|
+
children: [{ kind: "text", text: match[1] || match[2] || match[3] }]
|
|
527
533
|
}),
|
|
528
|
-
fastCheck: (text) => text.indexOf("**") !== -1
|
|
534
|
+
fastCheck: (text) => text.indexOf("**") !== -1 || text.indexOf("__") !== -1
|
|
529
535
|
});
|
|
530
536
|
this.registerPlugin({
|
|
531
537
|
id: "strikethrough",
|
|
@@ -540,12 +546,12 @@ var InlineParser = class {
|
|
|
540
546
|
this.registerPlugin({
|
|
541
547
|
id: "emphasis",
|
|
542
548
|
priority: 8,
|
|
543
|
-
re: /\*([^*\n]+?)
|
|
549
|
+
re: /\*([^*\n]+?)\*|(?<![\w\\])_([^_\n]+?)_(?!\w)/g,
|
|
544
550
|
toNode: (match) => ({
|
|
545
551
|
kind: "em",
|
|
546
|
-
children: [{ kind: "text", text: match[1] }]
|
|
552
|
+
children: [{ kind: "text", text: match[1] || match[2] }]
|
|
547
553
|
}),
|
|
548
|
-
fastCheck: (text) => text.indexOf("*") !== -1
|
|
554
|
+
fastCheck: (text) => text.indexOf("*") !== -1 || text.indexOf("_") !== -1
|
|
549
555
|
});
|
|
550
556
|
this.registerPlugin({
|
|
551
557
|
id: "citations",
|
|
@@ -696,6 +702,13 @@ function prepareInlineStreamingContent(content, options) {
|
|
|
696
702
|
};
|
|
697
703
|
let mathDisplayOpen = false;
|
|
698
704
|
let mathDisplayCrossedNewline = false;
|
|
705
|
+
const shouldOpenInlineMath = (index) => {
|
|
706
|
+
const next = content[index + 1] ?? "";
|
|
707
|
+
if (/\d/.test(next)) {
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
return true;
|
|
711
|
+
};
|
|
699
712
|
for (let i = 0; i < content.length; i++) {
|
|
700
713
|
const code = content.charCodeAt(i);
|
|
701
714
|
if (code === 10 || code === 13) {
|
|
@@ -734,7 +747,10 @@ function prepareInlineStreamingContent(content, options) {
|
|
|
734
747
|
}
|
|
735
748
|
i += 1;
|
|
736
749
|
} else {
|
|
737
|
-
|
|
750
|
+
const mathInlineOpen = stack.includes("math-inline");
|
|
751
|
+
if (mathInlineOpen || shouldOpenInlineMath(i)) {
|
|
752
|
+
toggleToken("math-inline");
|
|
753
|
+
}
|
|
738
754
|
}
|
|
739
755
|
}
|
|
740
756
|
}
|
|
@@ -746,7 +762,7 @@ function prepareInlineStreamingContent(content, options) {
|
|
|
746
762
|
if (hasIncompleteMathInline && !enableMathInlineAnticipation) {
|
|
747
763
|
return { kind: "raw", status: "raw", reason: "incomplete-math" };
|
|
748
764
|
}
|
|
749
|
-
if (hasIncompleteMathDisplay &&
|
|
765
|
+
if (hasIncompleteMathDisplay && !enableMathBlockAnticipation) {
|
|
750
766
|
return { kind: "raw", status: "raw", reason: "incomplete-math" };
|
|
751
767
|
}
|
|
752
768
|
}
|
|
@@ -769,7 +785,10 @@ function prepareInlineStreamingContent(content, options) {
|
|
|
769
785
|
case "math-inline":
|
|
770
786
|
return "$";
|
|
771
787
|
case "math-display":
|
|
772
|
-
|
|
788
|
+
if (!mathDisplayCrossedNewline) {
|
|
789
|
+
return "$$";
|
|
790
|
+
}
|
|
791
|
+
return content.endsWith("\n") || content.endsWith("\r") ? "$$" : "\n$$";
|
|
773
792
|
default:
|
|
774
793
|
return "";
|
|
775
794
|
}
|
|
@@ -987,9 +1006,25 @@ function splitByTagSegments(source, baseOffset, parseInline, options) {
|
|
|
987
1006
|
continue;
|
|
988
1007
|
}
|
|
989
1008
|
}
|
|
990
|
-
if (!isSelfClosing
|
|
991
|
-
const closingIndex = findClosingHtmlTag(lowerSource,
|
|
992
|
-
if (closingIndex
|
|
1009
|
+
if (!isSelfClosing) {
|
|
1010
|
+
const closingIndex = findClosingHtmlTag(lowerSource, tagNameLower, end);
|
|
1011
|
+
if (closingIndex !== -1) {
|
|
1012
|
+
end = closingIndex;
|
|
1013
|
+
} else if (mdxAllowed) {
|
|
1014
|
+
if (mdxAutoClose) {
|
|
1015
|
+
const tail = source.slice(end);
|
|
1016
|
+
const newlineCount = countNewlines(tail, mdxMaxNewlines + 1);
|
|
1017
|
+
if (newlineCount > mdxMaxNewlines) {
|
|
1018
|
+
tagPattern.lastIndex = start + 1;
|
|
1019
|
+
match = tagPattern.exec(source);
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
1022
|
+
} else {
|
|
1023
|
+
tagPattern.lastIndex = start + 1;
|
|
1024
|
+
match = tagPattern.exec(source);
|
|
1025
|
+
continue;
|
|
1026
|
+
}
|
|
1027
|
+
} else {
|
|
993
1028
|
if (htmlAutoClose && htmlAllowTags.has(tagNameLower)) {
|
|
994
1029
|
const tail = source.slice(end);
|
|
995
1030
|
const newlineCount = countNewlines(tail, htmlMaxNewlines + 1);
|
|
@@ -1016,7 +1051,6 @@ function splitByTagSegments(source, baseOffset, parseInline, options) {
|
|
|
1016
1051
|
match = tagPattern.exec(source);
|
|
1017
1052
|
continue;
|
|
1018
1053
|
}
|
|
1019
|
-
end = closingIndex;
|
|
1020
1054
|
}
|
|
1021
1055
|
if (start > cursor) {
|
|
1022
1056
|
const absoluteFrom = baseIsFinite ? baseOffset + cursor : void 0;
|
|
@@ -1040,7 +1074,9 @@ function splitByTagSegments(source, baseOffset, parseInline, options) {
|
|
|
1040
1074
|
match = tagPattern.exec(source);
|
|
1041
1075
|
continue;
|
|
1042
1076
|
}
|
|
1043
|
-
|
|
1077
|
+
const closingTagPattern = new RegExp(`</\\s*${escapeRegExp(tagName)}\\s*>\\s*$`, "i");
|
|
1078
|
+
const hasExplicitClose = closingTagPattern.test(rawSegment);
|
|
1079
|
+
if (mdxAutoClose && !hasExplicitClose && !rawSegment.endsWith("/>")) {
|
|
1044
1080
|
rawSegment = selfCloseTag(rawSegment);
|
|
1045
1081
|
segment.value = rawSegment;
|
|
1046
1082
|
}
|
|
@@ -1210,6 +1246,9 @@ function selfCloseTag(rawTag) {
|
|
|
1210
1246
|
if (closeIndex === -1) return rawTag;
|
|
1211
1247
|
return `${rawTag.slice(0, closeIndex)}/>`;
|
|
1212
1248
|
}
|
|
1249
|
+
function escapeRegExp(value) {
|
|
1250
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1251
|
+
}
|
|
1213
1252
|
function isLikelyMdxComponent(tagName) {
|
|
1214
1253
|
const first = tagName.charAt(0);
|
|
1215
1254
|
return first.toUpperCase() === first && first.toLowerCase() !== first;
|
|
@@ -1490,6 +1529,12 @@ function createBlockSnapshot(block) {
|
|
|
1490
1529
|
}
|
|
1491
1530
|
}
|
|
1492
1531
|
var listInlineParser = new InlineParser();
|
|
1532
|
+
var LIST_STREAMING_ANTICIPATION = {
|
|
1533
|
+
inline: true,
|
|
1534
|
+
mathInline: true,
|
|
1535
|
+
mathBlock: true,
|
|
1536
|
+
regex: true
|
|
1537
|
+
};
|
|
1493
1538
|
function enrichListSnapshot(block, snapshot) {
|
|
1494
1539
|
const raw = block.payload.raw ?? "";
|
|
1495
1540
|
const baseOffset = block.payload.range?.from ?? 0;
|
|
@@ -1596,7 +1641,7 @@ function buildListItemSnapshot(block, listItemNode, ordered, index, id, baseOffs
|
|
|
1596
1641
|
const nestedId = `${id}::list:${subListIndex++}`;
|
|
1597
1642
|
const nestedOrdered = name === "OrderedList";
|
|
1598
1643
|
const nestedSnapshot = buildListNodeSnapshot(block, cursor.node, nestedOrdered, nestedId, baseOffset, raw);
|
|
1599
|
-
if (Array.isArray(nestedSnapshot.children) && nestedSnapshot.children.length > 0) {
|
|
1644
|
+
if (nestedSnapshot && Array.isArray(nestedSnapshot.children) && nestedSnapshot.children.length > 0) {
|
|
1600
1645
|
childSnapshots.push(nestedSnapshot);
|
|
1601
1646
|
}
|
|
1602
1647
|
} else if (name === "Blockquote") {
|
|
@@ -1635,21 +1680,20 @@ function buildListItemSnapshot(block, listItemNode, ordered, index, id, baseOffs
|
|
|
1635
1680
|
return itemSnapshot;
|
|
1636
1681
|
}
|
|
1637
1682
|
function parseListInline(raw, options) {
|
|
1638
|
-
if (!options?.streaming
|
|
1683
|
+
if (!options?.streaming) {
|
|
1639
1684
|
return listInlineParser.parse(raw);
|
|
1640
1685
|
}
|
|
1641
|
-
const
|
|
1686
|
+
const effectiveFormatAnticipation = options.formatAnticipation ?? LIST_STREAMING_ANTICIPATION;
|
|
1687
|
+
const prepared = prepareInlineStreamingContent(raw, { formatAnticipation: effectiveFormatAnticipation, math: options.math ?? true });
|
|
1642
1688
|
if (prepared.kind === "raw") {
|
|
1643
1689
|
return [{ kind: "text", text: raw }];
|
|
1644
1690
|
}
|
|
1645
1691
|
let preparedContent = prepared.content;
|
|
1646
|
-
|
|
1647
|
-
const normalized = normalizeFormatAnticipation(options.formatAnticipation);
|
|
1692
|
+
const normalized = normalizeFormatAnticipation(effectiveFormatAnticipation);
|
|
1648
1693
|
if (normalized.regex) {
|
|
1649
1694
|
const regexAppend = listInlineParser.getRegexAnticipationAppend(raw);
|
|
1650
1695
|
if (regexAppend) {
|
|
1651
1696
|
preparedContent += regexAppend;
|
|
1652
|
-
appended += regexAppend;
|
|
1653
1697
|
}
|
|
1654
1698
|
}
|
|
1655
1699
|
return listInlineParser.parse(preparedContent, { cache: false });
|
|
@@ -1702,7 +1746,7 @@ function buildCodeBlockSnapshot(block, codeNode, id, baseOffset, raw, _isFenced)
|
|
|
1702
1746
|
const segment = raw.slice(codeNode.from, codeNode.to);
|
|
1703
1747
|
const normalized = stripListIndentation(segment);
|
|
1704
1748
|
const { code, info: infoString, hadFence } = stripCodeFence(normalized);
|
|
1705
|
-
const body = hadFence ? code : dedentIndentedCode2(normalized);
|
|
1749
|
+
const body = hadFence ? dedentFencedCodeByClosingIndent(normalized, code) : dedentIndentedCode2(normalized);
|
|
1706
1750
|
const { lang, meta } = parseCodeFenceInfo(infoString);
|
|
1707
1751
|
const codeBlock = {
|
|
1708
1752
|
id,
|
|
@@ -1747,6 +1791,10 @@ function buildHeadingSnapshot(block, headingNode, id, baseOffset, raw) {
|
|
|
1747
1791
|
return createBlockSnapshot(headingBlock);
|
|
1748
1792
|
}
|
|
1749
1793
|
function buildListNodeSnapshot(block, listNode, ordered, id, baseOffset, raw) {
|
|
1794
|
+
const children = buildListItemSnapshots(block, listNode, ordered, id, baseOffset, raw);
|
|
1795
|
+
if (children.length === 0) {
|
|
1796
|
+
return null;
|
|
1797
|
+
}
|
|
1750
1798
|
return {
|
|
1751
1799
|
id,
|
|
1752
1800
|
type: "list",
|
|
@@ -1754,7 +1802,7 @@ function buildListNodeSnapshot(block, listNode, ordered, id, baseOffset, raw) {
|
|
|
1754
1802
|
ordered
|
|
1755
1803
|
},
|
|
1756
1804
|
range: createRange(baseOffset + listNode.from, baseOffset + listNode.to),
|
|
1757
|
-
children
|
|
1805
|
+
children
|
|
1758
1806
|
};
|
|
1759
1807
|
}
|
|
1760
1808
|
function enrichParagraphSnapshot(block, snapshot) {
|
|
@@ -1881,6 +1929,73 @@ function dedentIndentedCode2(input) {
|
|
|
1881
1929
|
}
|
|
1882
1930
|
return lines.map((line) => line.length >= minIndent ? line.slice(minIndent) : "").join("\n").trimEnd();
|
|
1883
1931
|
}
|
|
1932
|
+
function dedentFencedCodeByClosingIndent(fencedRaw, extractedCode) {
|
|
1933
|
+
if (!fencedRaw || !extractedCode) {
|
|
1934
|
+
return extractedCode;
|
|
1935
|
+
}
|
|
1936
|
+
const lines = fencedRaw.replace(/\r\n?/g, "\n").split("\n");
|
|
1937
|
+
if (lines.length < 2) {
|
|
1938
|
+
return extractedCode;
|
|
1939
|
+
}
|
|
1940
|
+
let closingIndex = lines.length - 1;
|
|
1941
|
+
while (closingIndex > 0 && lines[closingIndex].trim().length === 0) {
|
|
1942
|
+
closingIndex -= 1;
|
|
1943
|
+
}
|
|
1944
|
+
if (closingIndex <= 0) {
|
|
1945
|
+
return extractedCode;
|
|
1946
|
+
}
|
|
1947
|
+
const closingMatch = lines[closingIndex].match(/^([ \t]*)```/);
|
|
1948
|
+
const closingIndent = closingMatch?.[1] ?? "";
|
|
1949
|
+
if (closingIndent.length === 0) {
|
|
1950
|
+
return extractedCode;
|
|
1951
|
+
}
|
|
1952
|
+
return extractedCode.split("\n").map((line) => stripIndentPrefix(line, closingIndent)).join("\n");
|
|
1953
|
+
}
|
|
1954
|
+
function stripIndentPrefix(line, prefix) {
|
|
1955
|
+
if (!line || !prefix) {
|
|
1956
|
+
return line;
|
|
1957
|
+
}
|
|
1958
|
+
if (line.startsWith(prefix)) {
|
|
1959
|
+
return line.slice(prefix.length);
|
|
1960
|
+
}
|
|
1961
|
+
const targetColumns = measureIndentColumns(prefix);
|
|
1962
|
+
if (targetColumns <= 0) {
|
|
1963
|
+
return line;
|
|
1964
|
+
}
|
|
1965
|
+
let index = 0;
|
|
1966
|
+
let columns = 0;
|
|
1967
|
+
while (index < line.length && columns < targetColumns) {
|
|
1968
|
+
const ch = line[index];
|
|
1969
|
+
if (ch === " ") {
|
|
1970
|
+
columns += 1;
|
|
1971
|
+
index += 1;
|
|
1972
|
+
continue;
|
|
1973
|
+
}
|
|
1974
|
+
if (ch === " ") {
|
|
1975
|
+
columns += 4;
|
|
1976
|
+
index += 1;
|
|
1977
|
+
continue;
|
|
1978
|
+
}
|
|
1979
|
+
break;
|
|
1980
|
+
}
|
|
1981
|
+
return columns >= targetColumns ? line.slice(index) : line;
|
|
1982
|
+
}
|
|
1983
|
+
function measureIndentColumns(value) {
|
|
1984
|
+
let columns = 0;
|
|
1985
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
1986
|
+
const ch = value[i];
|
|
1987
|
+
if (ch === " ") {
|
|
1988
|
+
columns += 1;
|
|
1989
|
+
continue;
|
|
1990
|
+
}
|
|
1991
|
+
if (ch === " ") {
|
|
1992
|
+
columns += 4;
|
|
1993
|
+
continue;
|
|
1994
|
+
}
|
|
1995
|
+
break;
|
|
1996
|
+
}
|
|
1997
|
+
return columns;
|
|
1998
|
+
}
|
|
1884
1999
|
function removeHeadingMarkers2(input) {
|
|
1885
2000
|
return input.replace(/^#{1,6}\s+/, "").replace(/\s+={2,}\s*$|\s+-{2,}\s*$/m, "").trim();
|
|
1886
2001
|
}
|
|
@@ -1941,8 +2056,9 @@ function enrichTableSnapshot(block, snapshot) {
|
|
|
1941
2056
|
return snapshot;
|
|
1942
2057
|
}
|
|
1943
2058
|
function enrichCodeSnapshot(block, snapshot) {
|
|
1944
|
-
const
|
|
1945
|
-
const
|
|
2059
|
+
const metaCode = typeof block.payload.meta?.code === "string" ? block.payload.meta?.code : null;
|
|
2060
|
+
const source = metaCode ?? (block.payload.raw ?? "");
|
|
2061
|
+
const lines = metaCode !== null ? source.length > 0 ? source.replace(/\r\n?/g, "\n").split("\n") : [] : extractCodeLines(source);
|
|
1946
2062
|
const meta = block.payload.meta;
|
|
1947
2063
|
const highlightedHtml = block.payload.highlightedHtml ?? "";
|
|
1948
2064
|
const hasBlockHighlight = typeof block.payload.highlightedHtml === "string" && block.payload.highlightedHtml.length > 0;
|
|
@@ -3053,7 +3169,8 @@ function collectSetProps(window2, startIndex) {
|
|
|
3053
3169
|
}
|
|
3054
3170
|
entries.push({
|
|
3055
3171
|
at: cloneNodePath(current.at),
|
|
3056
|
-
props: mergedProps
|
|
3172
|
+
props: mergedProps,
|
|
3173
|
+
meta: current.meta ? { ...current.meta } : void 0
|
|
3057
3174
|
});
|
|
3058
3175
|
j = k;
|
|
3059
3176
|
}
|
|
@@ -3071,7 +3188,8 @@ function collectSetProps(window2, startIndex) {
|
|
|
3071
3188
|
}
|
|
3072
3189
|
const batchPatch = {
|
|
3073
3190
|
op: "setPropsBatch",
|
|
3074
|
-
entries
|
|
3191
|
+
entries,
|
|
3192
|
+
meta: first.meta ? { ...first.meta } : void 0
|
|
3075
3193
|
};
|
|
3076
3194
|
return { patches: [batchPatch], nextIndex: j };
|
|
3077
3195
|
}
|
|
@@ -3166,7 +3284,8 @@ function coalescePatchesQuadratic(patches, config = DEFAULT_COALESCE_CONFIG) {
|
|
|
3166
3284
|
const batchEntries = [
|
|
3167
3285
|
{
|
|
3168
3286
|
at: cloneNodePath(current.at),
|
|
3169
|
-
props: mergedProps
|
|
3287
|
+
props: mergedProps,
|
|
3288
|
+
meta: current.meta ? { ...current.meta } : void 0
|
|
3170
3289
|
}
|
|
3171
3290
|
];
|
|
3172
3291
|
let k = j;
|
|
@@ -3190,14 +3309,16 @@ function coalescePatchesQuadratic(patches, config = DEFAULT_COALESCE_CONFIG) {
|
|
|
3190
3309
|
}
|
|
3191
3310
|
batchEntries.push({
|
|
3192
3311
|
at: cloneNodePath(candidate.at),
|
|
3193
|
-
props: candidateMergedProps
|
|
3312
|
+
props: candidateMergedProps,
|
|
3313
|
+
meta: candidate.meta ? { ...candidate.meta } : void 0
|
|
3194
3314
|
});
|
|
3195
3315
|
k = m;
|
|
3196
3316
|
}
|
|
3197
3317
|
if (batchEntries.length > 1) {
|
|
3198
3318
|
coalesced.push({
|
|
3199
3319
|
op: "setPropsBatch",
|
|
3200
|
-
entries: batchEntries
|
|
3320
|
+
entries: batchEntries,
|
|
3321
|
+
meta: current.meta ? { ...current.meta } : void 0
|
|
3201
3322
|
});
|
|
3202
3323
|
i = k;
|
|
3203
3324
|
continue;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Block, NodeSnapshot, InlineNode, Patch } from './types.cjs';
|
|
2
|
-
export { ASTInlinePlugin, CoalescingMetrics, CodeHighlightOutputMode, CodeHighlightingMode, CompiledMdxModule, DiffBlock, DiffKind, DiffLine, DiffLineKind, FormatAnticipationConfig, InlineHtmlDescriptor, InlinePlugin, LANGUAGE_ALIASES, LazyTokenizationPriority, MixedContentSegment, NodePath, PATCH_ROOT_ID, PatchMetrics, PerformanceMetrics, ProtectedRange, ProtectedRangeKind, RegexAnticipationPattern, RegexInlinePlugin, SetPropsBatchEntry, ThemedLine, ThemedToken, TocHeading, TokenLineV1, TokenSpan, TokenStyle, WorkerErrorPayload, WorkerIn, WorkerOut, WorkerPhase } from './types.cjs';
|
|
2
|
+
export { ASTInlinePlugin, CoalescingMetrics, CodeHighlightOutputMode, CodeHighlightingMode, CompiledMdxModule, DiffBlock, DiffKind, DiffLine, DiffLineKind, FormatAnticipationConfig, InlineHtmlDescriptor, InlinePlugin, LANGUAGE_ALIASES, LazyTokenizationPriority, MixedContentSegment, NodePath, PATCH_ROOT_ID, PatchKind, PatchMeta, PatchMetrics, PerformanceMetrics, ProtectedRange, ProtectedRangeKind, RegexAnticipationPattern, RegexInlinePlugin, SetPropsBatchEntry, ThemedLine, ThemedToken, TocHeading, TokenLineV1, TokenSpan, TokenStyle, WorkerErrorPayload, WorkerIn, WorkerOut, WorkerPhase } from './types.cjs';
|
|
3
3
|
export { HighlightedLine, dedentIndentedCode, extractCodeLines, extractCodeWrapperAttributes, extractHighlightedLines, getDefaultCodeWrapperAttributes, normalizeHighlightedLines, stripCodeFence } from './code-highlighting.cjs';
|
|
4
4
|
export { PerformanceTimer, StringBuffer, applyUpdate, debounce, detectMDX, generateBlockId, getBlockKey, normalizeBlockquoteText, normalizeLang, parseCodeFenceInfo, removeHeadingMarkers } from './utils.cjs';
|
|
5
5
|
export { MixedContentAutoCloseHtmlOptions, MixedContentAutoCloseMdxOptions, MixedContentOptions, extractMixedContentSegments, findClosingHtmlTag, isLikelyMdxComponent } from './mixed-content.cjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Block, NodeSnapshot, InlineNode, Patch } from './types.js';
|
|
2
|
-
export { ASTInlinePlugin, CoalescingMetrics, CodeHighlightOutputMode, CodeHighlightingMode, CompiledMdxModule, DiffBlock, DiffKind, DiffLine, DiffLineKind, FormatAnticipationConfig, InlineHtmlDescriptor, InlinePlugin, LANGUAGE_ALIASES, LazyTokenizationPriority, MixedContentSegment, NodePath, PATCH_ROOT_ID, PatchMetrics, PerformanceMetrics, ProtectedRange, ProtectedRangeKind, RegexAnticipationPattern, RegexInlinePlugin, SetPropsBatchEntry, ThemedLine, ThemedToken, TocHeading, TokenLineV1, TokenSpan, TokenStyle, WorkerErrorPayload, WorkerIn, WorkerOut, WorkerPhase } from './types.js';
|
|
2
|
+
export { ASTInlinePlugin, CoalescingMetrics, CodeHighlightOutputMode, CodeHighlightingMode, CompiledMdxModule, DiffBlock, DiffKind, DiffLine, DiffLineKind, FormatAnticipationConfig, InlineHtmlDescriptor, InlinePlugin, LANGUAGE_ALIASES, LazyTokenizationPriority, MixedContentSegment, NodePath, PATCH_ROOT_ID, PatchKind, PatchMeta, PatchMetrics, PerformanceMetrics, ProtectedRange, ProtectedRangeKind, RegexAnticipationPattern, RegexInlinePlugin, SetPropsBatchEntry, ThemedLine, ThemedToken, TocHeading, TokenLineV1, TokenSpan, TokenStyle, WorkerErrorPayload, WorkerIn, WorkerOut, WorkerPhase } from './types.js';
|
|
3
3
|
export { HighlightedLine, dedentIndentedCode, extractCodeLines, extractCodeWrapperAttributes, extractHighlightedLines, getDefaultCodeWrapperAttributes, normalizeHighlightedLines, stripCodeFence } from './code-highlighting.js';
|
|
4
4
|
export { PerformanceTimer, StringBuffer, applyUpdate, debounce, detectMDX, generateBlockId, getBlockKey, normalizeBlockquoteText, normalizeLang, parseCodeFenceInfo, removeHeadingMarkers } from './utils.js';
|
|
5
5
|
export { MixedContentAutoCloseHtmlOptions, MixedContentAutoCloseMdxOptions, MixedContentOptions, extractMixedContentSegments, findClosingHtmlTag, isLikelyMdxComponent } from './mixed-content.js';
|