@stream-mdx/react 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 +14 -4
- package/README.md +65 -15
- package/dist/components/bottom-stick-scroll-area.cjs +344 -0
- package/dist/components/bottom-stick-scroll-area.d.cts +36 -0
- package/dist/components/bottom-stick-scroll-area.d.ts +36 -0
- package/dist/components/bottom-stick-scroll-area.mjs +320 -0
- package/dist/components/index.cjs +503 -172
- package/dist/components/index.d.cts +3 -1
- package/dist/components/index.d.ts +3 -1
- package/dist/components/index.mjs +350 -22
- package/dist/{index-D2MYusAM.d.cts → index-BVhp_pcF.d.ts} +3 -0
- package/dist/{index-D2MYusAM.d.ts → index-EaeouYp2.d.cts} +3 -0
- package/dist/index.cjs +1472 -486
- package/dist/index.d.cts +6 -4
- package/dist/index.d.ts +6 -4
- package/dist/index.mjs +1256 -272
- package/dist/mdx-client.cjs +1 -1
- package/dist/mdx-client.mjs +1 -1
- package/dist/mdx-coordinator.cjs +28 -7
- package/dist/mdx-coordinator.d.cts +2 -2
- package/dist/mdx-coordinator.d.ts +2 -2
- package/dist/mdx-coordinator.mjs +28 -7
- package/dist/renderer/node-views.cjs +271 -26
- package/dist/renderer/node-views.d.cts +3 -1
- package/dist/renderer/node-views.d.ts +3 -1
- package/dist/renderer/node-views.mjs +272 -27
- package/dist/renderer/patch-batching.cjs +2 -0
- package/dist/renderer/patch-batching.d.cts +1 -1
- package/dist/renderer/patch-batching.d.ts +1 -1
- package/dist/renderer/patch-batching.mjs +2 -1
- package/dist/renderer/patch-commit-scheduler.cjs +82 -30
- package/dist/renderer/patch-commit-scheduler.d.cts +15 -3
- package/dist/renderer/patch-commit-scheduler.d.ts +15 -3
- package/dist/renderer/patch-commit-scheduler.mjs +84 -30
- package/dist/renderer/store.cjs +382 -36
- package/dist/renderer/store.d.cts +3 -0
- package/dist/renderer/store.d.ts +3 -0
- package/dist/renderer/store.mjs +382 -36
- package/dist/renderer.cjs +758 -114
- package/dist/renderer.d.cts +3 -1
- package/dist/renderer.d.ts +3 -1
- package/dist/renderer.mjs +760 -116
- package/dist/server.cjs +669 -71
- package/dist/server.d.cts +5 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.mjs +670 -72
- package/dist/{streaming-markdown-DOc2uLiP.d.ts → streaming-markdown-CEI9eDZm.d.ts} +3 -1
- package/dist/{streaming-markdown-D-JcByd9.d.cts → streaming-markdown-DRisClXb.d.cts} +3 -1
- package/dist/streaming-markdown.cjs +788 -121
- package/dist/streaming-markdown.d.cts +4 -2
- package/dist/streaming-markdown.d.ts +4 -2
- package/dist/streaming-markdown.mjs +790 -123
- package/dist/utils/inline-html.d.cts +3 -1
- package/dist/utils/inline-html.d.ts +3 -1
- package/package.json +14 -3
- package/shadcn/README.md +28 -0
- package/shadcn/bottom-stick-scroll-area.tsx +358 -0
package/dist/server.mjs
CHANGED
|
@@ -80,7 +80,7 @@ var init_mdx_client = __esm({
|
|
|
80
80
|
if (block.type !== "mdx") {
|
|
81
81
|
throw new Error("Block is not MDX type");
|
|
82
82
|
}
|
|
83
|
-
const cacheKey = block.id
|
|
83
|
+
const cacheKey = `${block.id}\0${block.payload.raw ?? ""}`;
|
|
84
84
|
const cachedPromise = this.cache.get(cacheKey);
|
|
85
85
|
if (cachedPromise) {
|
|
86
86
|
return cachedPromise;
|
|
@@ -405,7 +405,7 @@ function useRendererChildren(store, id) {
|
|
|
405
405
|
}
|
|
406
406
|
|
|
407
407
|
// src/renderer/node-views.tsx
|
|
408
|
-
import { stripCodeFence } from "@stream-mdx/core";
|
|
408
|
+
import { getDefaultCodeWrapperAttributes as getDefaultCodeWrapperAttributes2, stripCodeFence } from "@stream-mdx/core";
|
|
409
409
|
import React7 from "react";
|
|
410
410
|
|
|
411
411
|
// src/components/index.ts
|
|
@@ -941,7 +941,11 @@ var defaultInlineComponents = {
|
|
|
941
941
|
const n = number ?? "?";
|
|
942
942
|
const href = number ? `#fn:${n}` : void 0;
|
|
943
943
|
const id = number ? `fnref:${n}` : void 0;
|
|
944
|
-
return React4.createElement(
|
|
944
|
+
return React4.createElement(
|
|
945
|
+
"sup",
|
|
946
|
+
{ className: "footnote-ref" },
|
|
947
|
+
React4.createElement("a", { href, id, "data-label": label, style: { color: "var(--foreground)", textDecoration: "underline" } }, String(n))
|
|
948
|
+
);
|
|
945
949
|
}
|
|
946
950
|
};
|
|
947
951
|
var defaultBlockComponents = {
|
|
@@ -1084,7 +1088,7 @@ var defaultBlockComponents = {
|
|
|
1084
1088
|
dangerouslySetInnerHTML: { __html: htmlString }
|
|
1085
1089
|
});
|
|
1086
1090
|
},
|
|
1087
|
-
mdx: ({ compiledRef, compiledModule, status, errorMessage
|
|
1091
|
+
mdx: ({ compiledRef, compiledModule, status, errorMessage }) => {
|
|
1088
1092
|
const [Comp, setComp] = React4.useState(null);
|
|
1089
1093
|
const [internalError, setInternalError] = React4.useState(null);
|
|
1090
1094
|
const [renderError, setRenderError] = React4.useState(null);
|
|
@@ -1185,7 +1189,6 @@ var defaultBlockComponents = {
|
|
|
1185
1189
|
const waitingForVisibility = deferHydration && !shouldHydrate && Boolean(resolvedId);
|
|
1186
1190
|
const pendingLabel = waitingForVisibility ? "MDX ready (hydrate on view)\u2026" : compileMode === "worker" ? "Compiling MDX (client)\u2026" : "Compiling MDX (server)\u2026";
|
|
1187
1191
|
const message = failureMessage ?? (compileMode === "worker" ? "MDX compilation failed in client mode. Try switching back to server rendering or restarting the stream." : "MDX compilation failed. Try restarting the stream.");
|
|
1188
|
-
const rawFallback = typeof raw === "string" && raw.trim().length > 0 ? React4.createElement("pre", { className: "markdown-mdx-fallback" }, raw) : null;
|
|
1189
1192
|
const allowRetry = Boolean(internalError || renderError);
|
|
1190
1193
|
const retryButton = allowRetry ? React4.createElement(
|
|
1191
1194
|
"button",
|
|
@@ -1197,9 +1200,10 @@ var defaultBlockComponents = {
|
|
|
1197
1200
|
"Retry hydration"
|
|
1198
1201
|
) : null;
|
|
1199
1202
|
const errorPanel = React4.createElement(
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
React4.createElement(
|
|
1203
|
+
React4.Fragment,
|
|
1204
|
+
null,
|
|
1205
|
+
React4.createElement("strong", null, "MDX failed"),
|
|
1206
|
+
React4.createElement("div", null, message)
|
|
1203
1207
|
);
|
|
1204
1208
|
if (Comp && !failureMessage) {
|
|
1205
1209
|
return React4.createElement(
|
|
@@ -1222,14 +1226,13 @@ var defaultBlockComponents = {
|
|
|
1222
1226
|
return React4.createElement(
|
|
1223
1227
|
"div",
|
|
1224
1228
|
{
|
|
1225
|
-
className: "markdown-mdx",
|
|
1229
|
+
className: "markdown-mdx error",
|
|
1226
1230
|
ref: containerRef,
|
|
1227
1231
|
"data-mdx-ref": pendingId,
|
|
1228
1232
|
"data-mdx-status": "error",
|
|
1229
1233
|
"data-mdx-mode": compileMode
|
|
1230
1234
|
},
|
|
1231
1235
|
errorPanel,
|
|
1232
|
-
rawFallback,
|
|
1233
1236
|
retryButton
|
|
1234
1237
|
);
|
|
1235
1238
|
}
|
|
@@ -1256,7 +1259,16 @@ var defaultBlockComponents = {
|
|
|
1256
1259
|
{ key: item.number, id: `fn:${item.number}` },
|
|
1257
1260
|
renderInlineNodes(item.inlines, defaultInlineComponents),
|
|
1258
1261
|
" ",
|
|
1259
|
-
React4.createElement(
|
|
1262
|
+
React4.createElement(
|
|
1263
|
+
"a",
|
|
1264
|
+
{
|
|
1265
|
+
href: `#fnref:${item.number}`,
|
|
1266
|
+
className: "footnote-backref",
|
|
1267
|
+
"aria-label": "Back to content",
|
|
1268
|
+
style: { color: "var(--foreground)", textDecoration: "underline" }
|
|
1269
|
+
},
|
|
1270
|
+
"\u21A9"
|
|
1271
|
+
)
|
|
1260
1272
|
)
|
|
1261
1273
|
);
|
|
1262
1274
|
return React4.createElement("section", { className: "footnotes" }, React4.createElement("hr", {}), React4.createElement("ol", {}, listItems));
|
|
@@ -1812,6 +1824,7 @@ import {
|
|
|
1812
1824
|
sanitizeHTML as sanitizeHTML2,
|
|
1813
1825
|
PATCH_ROOT_ID
|
|
1814
1826
|
} from "@stream-mdx/core";
|
|
1827
|
+
import { getPatchKind } from "@stream-mdx/core/perf/patch-batching";
|
|
1815
1828
|
|
|
1816
1829
|
// src/renderer/list-utils.ts
|
|
1817
1830
|
function updateNodeDepth(node, depth) {
|
|
@@ -1916,12 +1929,28 @@ function snapshotToBlock(node) {
|
|
|
1916
1929
|
}
|
|
1917
1930
|
return null;
|
|
1918
1931
|
}
|
|
1919
|
-
var
|
|
1920
|
-
var
|
|
1932
|
+
var nodeSnapshotCacheByMap = /* @__PURE__ */ new WeakMap();
|
|
1933
|
+
var childrenSnapshotCacheByMap = /* @__PURE__ */ new WeakMap();
|
|
1921
1934
|
var NODE_SNAPSHOT_CACHE_LIMIT = 5e4;
|
|
1922
1935
|
var CHILDREN_SNAPSHOT_CACHE_LIMIT = 5e4;
|
|
1923
1936
|
var NODE_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(NODE_SNAPSHOT_CACHE_LIMIT * 0.1));
|
|
1924
1937
|
var CHILDREN_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(CHILDREN_SNAPSHOT_CACHE_LIMIT * 0.1));
|
|
1938
|
+
function getNodeSnapshotCacheForMap(map) {
|
|
1939
|
+
let cache = nodeSnapshotCacheByMap.get(map);
|
|
1940
|
+
if (!cache) {
|
|
1941
|
+
cache = /* @__PURE__ */ new Map();
|
|
1942
|
+
nodeSnapshotCacheByMap.set(map, cache);
|
|
1943
|
+
}
|
|
1944
|
+
return cache;
|
|
1945
|
+
}
|
|
1946
|
+
function getChildrenSnapshotCacheForMap(map) {
|
|
1947
|
+
let cache = childrenSnapshotCacheByMap.get(map);
|
|
1948
|
+
if (!cache) {
|
|
1949
|
+
cache = /* @__PURE__ */ new Map();
|
|
1950
|
+
childrenSnapshotCacheByMap.set(map, cache);
|
|
1951
|
+
}
|
|
1952
|
+
return cache;
|
|
1953
|
+
}
|
|
1925
1954
|
function pruneCache(cache, max, buffer) {
|
|
1926
1955
|
if (cache.size <= max + buffer) return;
|
|
1927
1956
|
let remaining = cache.size - max;
|
|
@@ -1931,6 +1960,14 @@ function pruneCache(cache, max, buffer) {
|
|
|
1931
1960
|
if (remaining <= 0) break;
|
|
1932
1961
|
}
|
|
1933
1962
|
}
|
|
1963
|
+
function deleteSnapshotCacheEntry(map, id) {
|
|
1964
|
+
getNodeSnapshotCacheForMap(map).delete(id);
|
|
1965
|
+
getChildrenSnapshotCacheForMap(map).delete(id);
|
|
1966
|
+
}
|
|
1967
|
+
function clearSnapshotCaches(map) {
|
|
1968
|
+
getNodeSnapshotCacheForMap(map).clear();
|
|
1969
|
+
getChildrenSnapshotCacheForMap(map).clear();
|
|
1970
|
+
}
|
|
1934
1971
|
var EMPTY_CHILDREN = Object.freeze([]);
|
|
1935
1972
|
var EMPTY_NODE_SNAPSHOT = Object.freeze({ version: -1, node: void 0 });
|
|
1936
1973
|
var EMPTY_CHILDREN_SNAPSHOT = Object.freeze({
|
|
@@ -2098,9 +2135,57 @@ function createRootRecord() {
|
|
|
2098
2135
|
parentId: null,
|
|
2099
2136
|
children: [],
|
|
2100
2137
|
props: {},
|
|
2101
|
-
version: 0
|
|
2138
|
+
version: 0,
|
|
2139
|
+
blockEpoch: 0
|
|
2102
2140
|
};
|
|
2103
2141
|
}
|
|
2142
|
+
function readNumericEpoch(value) {
|
|
2143
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
2144
|
+
return void 0;
|
|
2145
|
+
}
|
|
2146
|
+
return value;
|
|
2147
|
+
}
|
|
2148
|
+
function readSnapshotBlockEpoch(snapshot) {
|
|
2149
|
+
return readNumericEpoch(snapshot.meta?.blockEpoch);
|
|
2150
|
+
}
|
|
2151
|
+
function readPatchBlockEpoch(patch) {
|
|
2152
|
+
if (patch.op === "setHTML") {
|
|
2153
|
+
return readNumericEpoch(patch.patchMeta?.blockEpoch);
|
|
2154
|
+
}
|
|
2155
|
+
return readNumericEpoch(patch.meta?.blockEpoch);
|
|
2156
|
+
}
|
|
2157
|
+
function readPatchParseEpoch(patch) {
|
|
2158
|
+
if (patch.op === "setHTML") {
|
|
2159
|
+
return readNumericEpoch(patch.patchMeta?.parseEpoch);
|
|
2160
|
+
}
|
|
2161
|
+
return readNumericEpoch(patch.meta?.parseEpoch);
|
|
2162
|
+
}
|
|
2163
|
+
function readPatchTx(patch) {
|
|
2164
|
+
if (patch.op === "setHTML") {
|
|
2165
|
+
return readNumericEpoch(patch.patchMeta?.tx);
|
|
2166
|
+
}
|
|
2167
|
+
return readNumericEpoch(patch.meta?.tx);
|
|
2168
|
+
}
|
|
2169
|
+
function assignSubtreeBlockEpoch(map, rootId, blockEpoch, touched) {
|
|
2170
|
+
const root = map.get(rootId);
|
|
2171
|
+
if (!root) return;
|
|
2172
|
+
const stack = [root];
|
|
2173
|
+
while (stack.length > 0) {
|
|
2174
|
+
const node = stack.pop();
|
|
2175
|
+
if (!node) continue;
|
|
2176
|
+
if (node.blockEpoch !== blockEpoch) {
|
|
2177
|
+
node.blockEpoch = blockEpoch;
|
|
2178
|
+
node.version += 1;
|
|
2179
|
+
touched?.add(node.id);
|
|
2180
|
+
}
|
|
2181
|
+
for (const childId of node.children) {
|
|
2182
|
+
const child = map.get(childId);
|
|
2183
|
+
if (child) {
|
|
2184
|
+
stack.push(child);
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2104
2189
|
function ensureArray(value) {
|
|
2105
2190
|
return Array.isArray(value) ? value : [];
|
|
2106
2191
|
}
|
|
@@ -2160,7 +2245,7 @@ function escapeHtml2(value) {
|
|
|
2160
2245
|
}
|
|
2161
2246
|
function stripOuterLineSpan(html) {
|
|
2162
2247
|
if (!html) return null;
|
|
2163
|
-
const openTagMatch = html.match(/<span[^>]*>/i);
|
|
2248
|
+
const openTagMatch = html.match(/<span[^>]*class="[^"]*\bline\b[^"]*"[^>]*>/i);
|
|
2164
2249
|
if (!openTagMatch) {
|
|
2165
2250
|
return null;
|
|
2166
2251
|
}
|
|
@@ -2209,8 +2294,7 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
|
|
|
2209
2294
|
if (node.type !== "code-line") {
|
|
2210
2295
|
anomalies.push(`type-mismatch:${node.id}:${node.type}`);
|
|
2211
2296
|
nodes.delete(node.id);
|
|
2212
|
-
|
|
2213
|
-
childrenSnapshotCache.delete(node.id);
|
|
2297
|
+
deleteSnapshotCacheEntry(nodes, node.id);
|
|
2214
2298
|
touched.add(node.id);
|
|
2215
2299
|
continue;
|
|
2216
2300
|
}
|
|
@@ -2254,16 +2338,14 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
|
|
|
2254
2338
|
const existing = nodes.get(desiredId);
|
|
2255
2339
|
if (existing && existing !== node) {
|
|
2256
2340
|
nodes.delete(desiredId);
|
|
2257
|
-
|
|
2258
|
-
childrenSnapshotCache.delete(desiredId);
|
|
2341
|
+
deleteSnapshotCacheEntry(nodes, desiredId);
|
|
2259
2342
|
touched.add(desiredId);
|
|
2260
2343
|
mutated = true;
|
|
2261
2344
|
}
|
|
2262
2345
|
if (node.id !== desiredId) {
|
|
2263
2346
|
const previousId = node.id;
|
|
2264
2347
|
nodes.delete(node.id);
|
|
2265
|
-
|
|
2266
|
-
childrenSnapshotCache.delete(previousId);
|
|
2348
|
+
deleteSnapshotCacheEntry(nodes, previousId);
|
|
2267
2349
|
node.id = desiredId;
|
|
2268
2350
|
nodes.set(desiredId, node);
|
|
2269
2351
|
touched.add(desiredId);
|
|
@@ -2294,8 +2376,7 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
|
|
|
2294
2376
|
for (const [id, record] of nodes) {
|
|
2295
2377
|
if (record.parentId === parent.id && record.type === "code-line" && !allowed.has(id)) {
|
|
2296
2378
|
nodes.delete(id);
|
|
2297
|
-
|
|
2298
|
-
childrenSnapshotCache.delete(id);
|
|
2379
|
+
deleteSnapshotCacheEntry(nodes, id);
|
|
2299
2380
|
touched.add(id);
|
|
2300
2381
|
mutated = true;
|
|
2301
2382
|
}
|
|
@@ -2379,8 +2460,10 @@ function normalizeCodeLineProps(incoming, previous) {
|
|
|
2379
2460
|
}
|
|
2380
2461
|
function createCodeLineProps(index, text, highlight, tokens) {
|
|
2381
2462
|
const safeText = typeof text === "string" ? text : "";
|
|
2382
|
-
const
|
|
2383
|
-
const
|
|
2463
|
+
const hasIncomingHighlight = typeof highlight === "string" && highlight.trim().length > 0;
|
|
2464
|
+
const sanitized = hasIncomingHighlight ? sanitizeLineInnerHtml(highlight, safeText) : null;
|
|
2465
|
+
const hasRenderableTokens = tokens !== void 0 && tokens !== null;
|
|
2466
|
+
const html = sanitized && sanitized.length >= safeText.length ? sanitized : hasRenderableTokens ? null : escapeHtml2(safeText);
|
|
2384
2467
|
const props = {
|
|
2385
2468
|
index,
|
|
2386
2469
|
text: safeText,
|
|
@@ -2491,6 +2574,18 @@ function validateCodeBlockChildren(nodes, parent) {
|
|
|
2491
2574
|
issues.push({ kind: "dangling-node", id });
|
|
2492
2575
|
}
|
|
2493
2576
|
}
|
|
2577
|
+
if (parent.block?.isFinalized) {
|
|
2578
|
+
const snapshot = createBlockSnapshot(cloneBlock(parent.block));
|
|
2579
|
+
const expectedChildren = ensureArray(snapshot.children).filter((child) => child.type === "code-line");
|
|
2580
|
+
if (expectedChildren.length !== parent.children.length) {
|
|
2581
|
+
issues.push({
|
|
2582
|
+
kind: "line-count-mismatch",
|
|
2583
|
+
id: parent.id,
|
|
2584
|
+
expected: expectedChildren.length,
|
|
2585
|
+
actual: parent.children.length
|
|
2586
|
+
});
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2494
2589
|
return { ok: issues.length === 0, issues };
|
|
2495
2590
|
}
|
|
2496
2591
|
function rebuildCodeBlockFromSnapshot(nodes, parent, touched, issues) {
|
|
@@ -2507,7 +2602,7 @@ function rebuildCodeBlockFromSnapshot(nodes, parent, touched, issues) {
|
|
|
2507
2602
|
}
|
|
2508
2603
|
const snapshot = createBlockSnapshot(cloneBlock(parent.block));
|
|
2509
2604
|
removeSubtree(nodes, parent.id, touched);
|
|
2510
|
-
const inserted = insertSnapshotAt(nodes, ancestor, index, snapshot);
|
|
2605
|
+
const inserted = insertSnapshotAt(nodes, ancestor, index, snapshot, touched, parent.blockEpoch);
|
|
2511
2606
|
if (!inserted) {
|
|
2512
2607
|
return false;
|
|
2513
2608
|
}
|
|
@@ -2530,7 +2625,55 @@ function arraysEqual(a, b) {
|
|
|
2530
2625
|
}
|
|
2531
2626
|
return true;
|
|
2532
2627
|
}
|
|
2533
|
-
function
|
|
2628
|
+
function expectedCodeLineId(parentId, index) {
|
|
2629
|
+
return `${parentId}::line:${index}`;
|
|
2630
|
+
}
|
|
2631
|
+
function collectStoreInvariantViolations(nodes) {
|
|
2632
|
+
const violations = [];
|
|
2633
|
+
for (const [id, node] of nodes) {
|
|
2634
|
+
if (node.type === "__root__") continue;
|
|
2635
|
+
if (node.children.length > 1) {
|
|
2636
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2637
|
+
for (const childId of node.children) {
|
|
2638
|
+
if (seen.has(childId)) {
|
|
2639
|
+
violations.push(`duplicate child id:${id}:${childId}`);
|
|
2640
|
+
} else {
|
|
2641
|
+
seen.add(childId);
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
if (node.type === "list") {
|
|
2646
|
+
const parent = node.parentId ? nodes.get(node.parentId) : void 0;
|
|
2647
|
+
if (parent?.type === "list-item" && node.children.length === 0) {
|
|
2648
|
+
violations.push(`empty nested list:${id}`);
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
if (node.type === "code") {
|
|
2652
|
+
for (let index = 0; index < node.children.length; index += 1) {
|
|
2653
|
+
const childId = node.children[index];
|
|
2654
|
+
const child = nodes.get(childId);
|
|
2655
|
+
if (!child) {
|
|
2656
|
+
violations.push(`missing code-line node:${node.id}:${childId}`);
|
|
2657
|
+
continue;
|
|
2658
|
+
}
|
|
2659
|
+
if (child.type !== "code-line") {
|
|
2660
|
+
violations.push(`non-code child in code block:${node.id}:${child.id}:${child.type}`);
|
|
2661
|
+
continue;
|
|
2662
|
+
}
|
|
2663
|
+
const expectedId = expectedCodeLineId(node.id, index);
|
|
2664
|
+
if (child.id !== expectedId) {
|
|
2665
|
+
violations.push(`code-line id mismatch:${node.id}:${child.id}:${expectedId}`);
|
|
2666
|
+
}
|
|
2667
|
+
const indexProp = typeof child.props?.index === "number" ? child.props.index : null;
|
|
2668
|
+
if (indexProp !== index) {
|
|
2669
|
+
violations.push(`code-line index mismatch:${node.id}:${child.id}:${indexProp}:${index}`);
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
return violations;
|
|
2675
|
+
}
|
|
2676
|
+
function buildNodeFromSnapshot(map, snapshot, parentId, blockEpoch) {
|
|
2534
2677
|
const record = {
|
|
2535
2678
|
id: snapshot.id,
|
|
2536
2679
|
type: snapshot.type,
|
|
@@ -2540,6 +2683,7 @@ function buildNodeFromSnapshot(map, snapshot, parentId) {
|
|
|
2540
2683
|
meta: snapshot.meta ? { ...snapshot.meta } : void 0,
|
|
2541
2684
|
range: snapshot.range ? { ...snapshot.range } : void 0,
|
|
2542
2685
|
version: 0,
|
|
2686
|
+
blockEpoch,
|
|
2543
2687
|
block: snapshotToBlock(snapshot) ?? void 0
|
|
2544
2688
|
};
|
|
2545
2689
|
if (record.type === "code") {
|
|
@@ -2548,7 +2692,7 @@ function buildNodeFromSnapshot(map, snapshot, parentId) {
|
|
|
2548
2692
|
map.set(record.id, record);
|
|
2549
2693
|
const childSnapshots = ensureArray(snapshot.children);
|
|
2550
2694
|
for (const child of childSnapshots) {
|
|
2551
|
-
const childRecord = buildNodeFromSnapshot(map, child, record.id);
|
|
2695
|
+
const childRecord = buildNodeFromSnapshot(map, child, record.id, blockEpoch);
|
|
2552
2696
|
record.children.push(childRecord.id);
|
|
2553
2697
|
}
|
|
2554
2698
|
return record;
|
|
@@ -2576,8 +2720,7 @@ function removeSubtree(map, id, touched) {
|
|
|
2576
2720
|
}
|
|
2577
2721
|
}
|
|
2578
2722
|
map.delete(node.id);
|
|
2579
|
-
|
|
2580
|
-
childrenSnapshotCache.delete(node.id);
|
|
2723
|
+
deleteSnapshotCacheEntry(map, node.id);
|
|
2581
2724
|
touched.add(node.id);
|
|
2582
2725
|
for (const childId of node.children) {
|
|
2583
2726
|
const child = map.get(childId);
|
|
@@ -2609,13 +2752,13 @@ function resolveParent(map, path) {
|
|
|
2609
2752
|
}
|
|
2610
2753
|
return map.get(path.blockId);
|
|
2611
2754
|
}
|
|
2612
|
-
function insertSnapshotAt(map, parent, index, snapshot, touched) {
|
|
2755
|
+
function insertSnapshotAt(map, parent, index, snapshot, touched, blockEpoch) {
|
|
2613
2756
|
if (!parent) return null;
|
|
2614
2757
|
const existingIndex = parent.children.indexOf(snapshot.id);
|
|
2615
2758
|
if (existingIndex !== -1) {
|
|
2616
2759
|
removeSubtree(map, parent.children[existingIndex], touched ?? /* @__PURE__ */ new Set());
|
|
2617
2760
|
}
|
|
2618
|
-
const record = buildNodeFromSnapshot(map, snapshot, parent.id);
|
|
2761
|
+
const record = buildNodeFromSnapshot(map, snapshot, parent.id, blockEpoch ?? readSnapshotBlockEpoch(snapshot) ?? parent.blockEpoch);
|
|
2619
2762
|
const insertedIds = [];
|
|
2620
2763
|
const queue = [record];
|
|
2621
2764
|
while (queue.length > 0) {
|
|
@@ -2633,13 +2776,13 @@ function insertSnapshotAt(map, parent, index, snapshot, touched) {
|
|
|
2633
2776
|
ensureUniqueChildren(parent, touched);
|
|
2634
2777
|
return { record, insertedIds };
|
|
2635
2778
|
}
|
|
2636
|
-
function replaceSnapshotAt(map, parent, index, snapshot, touched) {
|
|
2779
|
+
function replaceSnapshotAt(map, parent, index, snapshot, touched, blockEpoch) {
|
|
2637
2780
|
if (!parent) return;
|
|
2638
2781
|
if (index < 0 || index >= parent.children.length) return;
|
|
2639
2782
|
const targetId = parent.children[index];
|
|
2640
2783
|
parent.children.splice(index, 1);
|
|
2641
2784
|
removeSubtree(map, targetId, touched);
|
|
2642
|
-
const inserted = insertSnapshotAt(map, parent, index, snapshot, touched);
|
|
2785
|
+
const inserted = insertSnapshotAt(map, parent, index, snapshot, touched, blockEpoch);
|
|
2643
2786
|
if (inserted) {
|
|
2644
2787
|
for (const id of inserted.insertedIds) {
|
|
2645
2788
|
touched.add(id);
|
|
@@ -2659,6 +2802,24 @@ function createRendererStore(initialBlocks = []) {
|
|
|
2659
2802
|
const listeners = /* @__PURE__ */ new Set();
|
|
2660
2803
|
let notifyScheduled = false;
|
|
2661
2804
|
let lastCoalescingMetrics = null;
|
|
2805
|
+
let invariantViolations = [];
|
|
2806
|
+
let transientDiagnostics = [];
|
|
2807
|
+
let nextBlockEpoch = 1;
|
|
2808
|
+
const debugCounters = {
|
|
2809
|
+
appendLineGuardRejected: 0,
|
|
2810
|
+
staleEpochRejected: 0,
|
|
2811
|
+
storeInvariantViolationCount: 0
|
|
2812
|
+
};
|
|
2813
|
+
const allocateBlockEpoch = (preferred) => {
|
|
2814
|
+
const resolved = readNumericEpoch(preferred);
|
|
2815
|
+
if (resolved !== void 0) {
|
|
2816
|
+
nextBlockEpoch = Math.max(nextBlockEpoch, resolved + 1);
|
|
2817
|
+
return resolved;
|
|
2818
|
+
}
|
|
2819
|
+
const epoch = nextBlockEpoch;
|
|
2820
|
+
nextBlockEpoch += 1;
|
|
2821
|
+
return epoch;
|
|
2822
|
+
};
|
|
2662
2823
|
const getRaf = () => {
|
|
2663
2824
|
if (typeof globalThis !== "undefined" && typeof globalThis.requestAnimationFrame === "function") {
|
|
2664
2825
|
return globalThis.requestAnimationFrame;
|
|
@@ -2690,9 +2851,12 @@ function createRendererStore(initialBlocks = []) {
|
|
|
2690
2851
|
}
|
|
2691
2852
|
}
|
|
2692
2853
|
}
|
|
2854
|
+
function appendDiagnostic(message) {
|
|
2855
|
+
transientDiagnostics = [...transientDiagnostics, message];
|
|
2856
|
+
invariantViolations = [...invariantViolations, message];
|
|
2857
|
+
}
|
|
2693
2858
|
function rebuildFromBlocks(blocks) {
|
|
2694
|
-
|
|
2695
|
-
childrenSnapshotCache.clear();
|
|
2859
|
+
clearSnapshotCaches(nodes);
|
|
2696
2860
|
nodes = /* @__PURE__ */ new Map();
|
|
2697
2861
|
const root = createRootRecord();
|
|
2698
2862
|
nodes.set(root.id, root);
|
|
@@ -2701,9 +2865,12 @@ function createRendererStore(initialBlocks = []) {
|
|
|
2701
2865
|
for (let i = 0; i < blocks.length; i++) {
|
|
2702
2866
|
const block = cloneBlock(blocks[i]);
|
|
2703
2867
|
const snapshot = createBlockSnapshot(block);
|
|
2704
|
-
insertSnapshotAt(nodes, root, root.children.length, snapshot);
|
|
2868
|
+
insertSnapshotAt(nodes, root, root.children.length, snapshot, void 0, allocateBlockEpoch(readSnapshotBlockEpoch(snapshot)));
|
|
2705
2869
|
}
|
|
2706
2870
|
normalizeAllListDepths(nodes, /* @__PURE__ */ new Set(), PATCH_ROOT_ID);
|
|
2871
|
+
transientDiagnostics = [];
|
|
2872
|
+
invariantViolations = collectStoreInvariantViolations(nodes);
|
|
2873
|
+
debugCounters.storeInvariantViolationCount += invariantViolations.length;
|
|
2707
2874
|
version++;
|
|
2708
2875
|
blocksDirty = true;
|
|
2709
2876
|
scheduleNotify();
|
|
@@ -2768,11 +2935,57 @@ function createRendererStore(initialBlocks = []) {
|
|
|
2768
2935
|
listDepthDirty = true;
|
|
2769
2936
|
}
|
|
2770
2937
|
};
|
|
2771
|
-
const
|
|
2938
|
+
const semanticTxEpochs = /* @__PURE__ */ new Map();
|
|
2939
|
+
const resolveBlockEpochTarget = (patch, at) => {
|
|
2940
|
+
if ((patch.op === "insertChild" || patch.op === "replaceChild") && at.blockId === PATCH_ROOT_ID) {
|
|
2941
|
+
const nodeId = patch.node?.id;
|
|
2942
|
+
return nodeId ? nodes.get(nodeId) : void 0;
|
|
2943
|
+
}
|
|
2944
|
+
return nodes.get(at.blockId) ?? (at.nodeId ? nodes.get(at.nodeId) : void 0);
|
|
2945
|
+
};
|
|
2946
|
+
const rejectStaleEpochPatch = (patch, at) => {
|
|
2947
|
+
const expectedBlockEpoch = readPatchBlockEpoch(patch);
|
|
2948
|
+
const patchTx = readPatchTx(patch);
|
|
2949
|
+
if (expectedBlockEpoch === void 0) {
|
|
2950
|
+
return false;
|
|
2951
|
+
}
|
|
2952
|
+
if (getPatchKind(patch) !== "semantic" || !at) {
|
|
2953
|
+
return false;
|
|
2954
|
+
}
|
|
2955
|
+
const target = resolveBlockEpochTarget(patch, at);
|
|
2956
|
+
if (!target || target.blockEpoch === expectedBlockEpoch) {
|
|
2957
|
+
return false;
|
|
2958
|
+
}
|
|
2959
|
+
const txEpoch = at.blockId && patchTx !== void 0 ? semanticTxEpochs.get(at.blockId) : void 0;
|
|
2960
|
+
if (txEpoch && txEpoch.tx === patchTx && txEpoch.blockEpoch === target.blockEpoch) {
|
|
2961
|
+
return false;
|
|
2962
|
+
}
|
|
2963
|
+
debugCounters.staleEpochRejected += 1;
|
|
2964
|
+
appendDiagnostic(
|
|
2965
|
+
`stale epoch rejected:${patch.op}:${target.id}:expected=${expectedBlockEpoch}:actual=${target.blockEpoch}`
|
|
2966
|
+
);
|
|
2967
|
+
return true;
|
|
2968
|
+
};
|
|
2969
|
+
const advanceBlockEpoch = (blockId, touchedSet, preferredEpoch, tx) => {
|
|
2970
|
+
if (!blockId || blockId === PATCH_ROOT_ID) {
|
|
2971
|
+
return null;
|
|
2972
|
+
}
|
|
2973
|
+
const blockRoot = nodes.get(blockId);
|
|
2974
|
+
if (!blockRoot) {
|
|
2975
|
+
return null;
|
|
2976
|
+
}
|
|
2977
|
+
const nextEpoch = allocateBlockEpoch(preferredEpoch);
|
|
2978
|
+
assignSubtreeBlockEpoch(nodes, blockRoot.id, nextEpoch, touchedSet);
|
|
2979
|
+
if (tx !== void 0) {
|
|
2980
|
+
semanticTxEpochs.set(blockRoot.id, { tx, blockEpoch: nextEpoch });
|
|
2981
|
+
}
|
|
2982
|
+
return nextEpoch;
|
|
2983
|
+
};
|
|
2984
|
+
const applyPropsUpdate = (at, props, preferredEpoch, tx, semanticUpdate = false) => {
|
|
2772
2985
|
const perfStartLocal = perfEnabled ? getPerfNow() : 0;
|
|
2773
2986
|
const targetId = at.nodeId ?? at.blockId;
|
|
2774
2987
|
if (!targetId) return;
|
|
2775
|
-
|
|
2988
|
+
let target = nodes.get(targetId);
|
|
2776
2989
|
if (!target) return;
|
|
2777
2990
|
const parentType = target.parentId ? nodes.get(target.parentId)?.type : void 0;
|
|
2778
2991
|
if (debugListPatches && (isListRelated(target.type) || isListRelated(parentType))) {
|
|
@@ -2799,6 +3012,9 @@ function createRendererStore(initialBlocks = []) {
|
|
|
2799
3012
|
propsChanged = true;
|
|
2800
3013
|
}
|
|
2801
3014
|
if (propsChanged) {
|
|
3015
|
+
if (semanticUpdate) {
|
|
3016
|
+
advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
|
|
3017
|
+
}
|
|
2802
3018
|
target.version++;
|
|
2803
3019
|
touched.add(target.id);
|
|
2804
3020
|
const parent = target.parentId ? nodes.get(target.parentId) : void 0;
|
|
@@ -2862,16 +3078,56 @@ function createRendererStore(initialBlocks = []) {
|
|
|
2862
3078
|
}
|
|
2863
3079
|
}
|
|
2864
3080
|
let blockChanged = false;
|
|
3081
|
+
let typeChanged = false;
|
|
3082
|
+
let rebuiltFromFinalizedBlock = false;
|
|
3083
|
+
let blockEpochAdvanced = false;
|
|
2865
3084
|
const incomingBlock = props?.block;
|
|
2866
3085
|
if (incomingBlock && typeof incomingBlock === "object") {
|
|
2867
3086
|
target.block = cloneBlock(incomingBlock);
|
|
3087
|
+
const previousType = target.type;
|
|
3088
|
+
const incomingType = typeof target.block.type === "string" ? target.block.type : target.type;
|
|
3089
|
+
if (incomingType !== target.type) {
|
|
3090
|
+
target.type = incomingType;
|
|
3091
|
+
typeChanged = true;
|
|
3092
|
+
if (isListRelated(previousType) || isListRelated(target.type)) {
|
|
3093
|
+
markListDepthDirty(target.id);
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
2868
3096
|
if (target.type === "code") {
|
|
2869
3097
|
applyCodeBlockMetadata(target);
|
|
2870
3098
|
}
|
|
3099
|
+
if (semanticUpdate) {
|
|
3100
|
+
advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
|
|
3101
|
+
blockEpochAdvanced = true;
|
|
3102
|
+
}
|
|
3103
|
+
const shouldRebuildFinalizedTree = !at.nodeId && target.id === at.blockId && Boolean(target.block?.isFinalized) && target.type !== "code" && Boolean(target.parentId);
|
|
3104
|
+
if (shouldRebuildFinalizedTree) {
|
|
3105
|
+
const parent = target.parentId ? nodes.get(target.parentId) : void 0;
|
|
3106
|
+
const index = parent ? parent.children.indexOf(target.id) : -1;
|
|
3107
|
+
if (parent && index !== -1) {
|
|
3108
|
+
const snapshot = createBlockSnapshot(cloneBlock(target.block));
|
|
3109
|
+
replaceSnapshotAt(nodes, parent, index, snapshot, touched, target.blockEpoch);
|
|
3110
|
+
mutatedBlocks = true;
|
|
3111
|
+
blockChanged = true;
|
|
3112
|
+
rebuiltFromFinalizedBlock = true;
|
|
3113
|
+
const rebuilt = nodes.get(target.id);
|
|
3114
|
+
if (isListRelated(previousType) || isListRelated(rebuilt?.type)) {
|
|
3115
|
+
markListDepthDirty(target.id);
|
|
3116
|
+
}
|
|
3117
|
+
target = rebuilt ?? target;
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
2871
3120
|
mutatedBlocks = true;
|
|
2872
3121
|
blockChanged = true;
|
|
2873
3122
|
}
|
|
2874
|
-
if (
|
|
3123
|
+
if (rebuiltFromFinalizedBlock) {
|
|
3124
|
+
return;
|
|
3125
|
+
}
|
|
3126
|
+
if (propsChanged || blockChanged || typeChanged) {
|
|
3127
|
+
if (semanticUpdate && !blockEpochAdvanced) {
|
|
3128
|
+
advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
|
|
3129
|
+
blockEpochAdvanced = true;
|
|
3130
|
+
}
|
|
2875
3131
|
target.version++;
|
|
2876
3132
|
touched.add(target.id);
|
|
2877
3133
|
let parentForNormalization;
|
|
@@ -2914,13 +3170,22 @@ function createRendererStore(initialBlocks = []) {
|
|
|
2914
3170
|
const applyPatch = (patch) => {
|
|
2915
3171
|
switch (patch.op) {
|
|
2916
3172
|
case "insertChild": {
|
|
3173
|
+
if (rejectStaleEpochPatch(patch, patch.at)) break;
|
|
2917
3174
|
const parent = resolveParent(nodes, patch.at);
|
|
2918
|
-
const inserted = insertSnapshotAt(
|
|
3175
|
+
const inserted = insertSnapshotAt(
|
|
3176
|
+
nodes,
|
|
3177
|
+
parent,
|
|
3178
|
+
patch.index,
|
|
3179
|
+
patch.node,
|
|
3180
|
+
touched,
|
|
3181
|
+
allocateBlockEpoch(readPatchParseEpoch(patch) ?? readSnapshotBlockEpoch(patch.node))
|
|
3182
|
+
);
|
|
2919
3183
|
if (inserted) {
|
|
2920
3184
|
for (const id of inserted.insertedIds) {
|
|
2921
3185
|
touched.add(id);
|
|
2922
3186
|
}
|
|
2923
3187
|
mutatedBlocks = true;
|
|
3188
|
+
advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
|
|
2924
3189
|
if (isListRelated(inserted.record.type) || isListRelated(parent?.type)) {
|
|
2925
3190
|
markListDepthDirty(inserted.record.id);
|
|
2926
3191
|
if (parent) {
|
|
@@ -2940,6 +3205,7 @@ function createRendererStore(initialBlocks = []) {
|
|
|
2940
3205
|
break;
|
|
2941
3206
|
}
|
|
2942
3207
|
case "deleteChild": {
|
|
3208
|
+
if (rejectStaleEpochPatch(patch, patch.at)) break;
|
|
2943
3209
|
const parent = resolveParent(nodes, patch.at);
|
|
2944
3210
|
if (!parent) break;
|
|
2945
3211
|
if (patch.index < 0 || patch.index >= parent.children.length) break;
|
|
@@ -2951,6 +3217,7 @@ function createRendererStore(initialBlocks = []) {
|
|
|
2951
3217
|
touched.add(parent.id);
|
|
2952
3218
|
ensureUniqueChildren(parent, touched);
|
|
2953
3219
|
mutatedBlocks = true;
|
|
3220
|
+
advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
|
|
2954
3221
|
if (isListRelated(parent.type) || isListRelated(removedNode?.type)) {
|
|
2955
3222
|
markListDepthDirty(parent.id);
|
|
2956
3223
|
}
|
|
@@ -2967,11 +3234,20 @@ function createRendererStore(initialBlocks = []) {
|
|
|
2967
3234
|
break;
|
|
2968
3235
|
}
|
|
2969
3236
|
case "replaceChild": {
|
|
3237
|
+
if (rejectStaleEpochPatch(patch, patch.at)) break;
|
|
2970
3238
|
const parent = resolveParent(nodes, patch.at);
|
|
2971
3239
|
if (!parent) break;
|
|
2972
|
-
replaceSnapshotAt(
|
|
3240
|
+
replaceSnapshotAt(
|
|
3241
|
+
nodes,
|
|
3242
|
+
parent,
|
|
3243
|
+
patch.index,
|
|
3244
|
+
patch.node,
|
|
3245
|
+
touched,
|
|
3246
|
+
allocateBlockEpoch(readPatchParseEpoch(patch) ?? readSnapshotBlockEpoch(patch.node))
|
|
3247
|
+
);
|
|
2973
3248
|
touched.add(parent.id);
|
|
2974
3249
|
mutatedBlocks = true;
|
|
3250
|
+
advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
|
|
2975
3251
|
if (isListRelated(parent.type)) {
|
|
2976
3252
|
markListDepthDirty(parent.id);
|
|
2977
3253
|
}
|
|
@@ -2985,18 +3261,33 @@ function createRendererStore(initialBlocks = []) {
|
|
|
2985
3261
|
break;
|
|
2986
3262
|
}
|
|
2987
3263
|
case "setProps": {
|
|
2988
|
-
|
|
3264
|
+
if (rejectStaleEpochPatch(patch, patch.at)) break;
|
|
3265
|
+
applyPropsUpdate(patch.at, patch.props, readPatchParseEpoch(patch), readPatchTx(patch), getPatchKind(patch) === "semantic");
|
|
2989
3266
|
break;
|
|
2990
3267
|
}
|
|
2991
3268
|
case "setPropsBatch": {
|
|
2992
3269
|
const entries2 = Array.isArray(patch.entries) ? patch.entries : [];
|
|
2993
3270
|
for (const entry of entries2) {
|
|
2994
3271
|
if (!entry || !entry.at) continue;
|
|
2995
|
-
|
|
3272
|
+
const entryPatch = {
|
|
3273
|
+
op: "setProps",
|
|
3274
|
+
at: entry.at,
|
|
3275
|
+
props: entry.props,
|
|
3276
|
+
meta: entry.meta ?? patch.meta
|
|
3277
|
+
};
|
|
3278
|
+
if (rejectStaleEpochPatch(entryPatch, entry.at)) continue;
|
|
3279
|
+
applyPropsUpdate(
|
|
3280
|
+
entry.at,
|
|
3281
|
+
entry.props,
|
|
3282
|
+
readPatchParseEpoch(entryPatch),
|
|
3283
|
+
readPatchTx(entryPatch),
|
|
3284
|
+
getPatchKind(entryPatch) === "semantic"
|
|
3285
|
+
);
|
|
2996
3286
|
}
|
|
2997
3287
|
break;
|
|
2998
3288
|
}
|
|
2999
3289
|
case "finalize": {
|
|
3290
|
+
if (rejectStaleEpochPatch(patch, patch.at)) break;
|
|
3000
3291
|
const target = nodes.get(patch.at.nodeId ?? patch.at.blockId);
|
|
3001
3292
|
if (!target || !target.block) break;
|
|
3002
3293
|
if (!target.block.isFinalized) {
|
|
@@ -3005,6 +3296,17 @@ function createRendererStore(initialBlocks = []) {
|
|
|
3005
3296
|
target.version++;
|
|
3006
3297
|
touched.add(target.id);
|
|
3007
3298
|
mutatedBlocks = true;
|
|
3299
|
+
if (!patch.at.nodeId && target.id === patch.at.blockId && target.type !== "code" && target.parentId) {
|
|
3300
|
+
const parent = nodes.get(target.parentId);
|
|
3301
|
+
const index = parent ? parent.children.indexOf(target.id) : -1;
|
|
3302
|
+
if (parent && index !== -1) {
|
|
3303
|
+
const snapshot = createBlockSnapshot(cloneBlock(target.block));
|
|
3304
|
+
replaceSnapshotAt(nodes, parent, index, snapshot, touched, target.blockEpoch);
|
|
3305
|
+
if (isListRelated(target.type) || isListRelated(snapshot.type)) {
|
|
3306
|
+
markListDepthDirty(target.id);
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3008
3310
|
}
|
|
3009
3311
|
if (isListRelated(target.type)) {
|
|
3010
3312
|
markListDepthDirty(target.id);
|
|
@@ -3018,6 +3320,7 @@ function createRendererStore(initialBlocks = []) {
|
|
|
3018
3320
|
break;
|
|
3019
3321
|
}
|
|
3020
3322
|
case "reorder": {
|
|
3323
|
+
if (rejectStaleEpochPatch(patch, patch.at)) break;
|
|
3021
3324
|
const parent = resolveParent(nodes, patch.at);
|
|
3022
3325
|
if (!parent) break;
|
|
3023
3326
|
const length = parent.children.length;
|
|
@@ -3032,6 +3335,9 @@ function createRendererStore(initialBlocks = []) {
|
|
|
3032
3335
|
parent.children.splice(to, 0, ...moved);
|
|
3033
3336
|
parent.version++;
|
|
3034
3337
|
touched.add(parent.id);
|
|
3338
|
+
if (getPatchKind(patch) === "semantic") {
|
|
3339
|
+
advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
|
|
3340
|
+
}
|
|
3035
3341
|
if (isListRelated(parent.type)) {
|
|
3036
3342
|
markListDepthDirty(parent.id);
|
|
3037
3343
|
}
|
|
@@ -3047,15 +3353,31 @@ function createRendererStore(initialBlocks = []) {
|
|
|
3047
3353
|
break;
|
|
3048
3354
|
}
|
|
3049
3355
|
case "appendLines": {
|
|
3356
|
+
if (rejectStaleEpochPatch(patch, patch.at)) break;
|
|
3050
3357
|
const parent = resolveParent(nodes, patch.at);
|
|
3051
3358
|
if (!parent) break;
|
|
3052
|
-
|
|
3359
|
+
if (parent.type !== "code") {
|
|
3360
|
+
debugCounters.appendLineGuardRejected += 1;
|
|
3361
|
+
appendDiagnostic(`appendLines target not code:${parent.id}:${parent.type}`);
|
|
3362
|
+
break;
|
|
3363
|
+
}
|
|
3364
|
+
const requestedStartIndex = Math.max(0, patch.startIndex);
|
|
3365
|
+
const startIndex = Math.max(0, Math.min(requestedStartIndex, parent.children.length));
|
|
3053
3366
|
const lineCount = patch.lines?.length ?? 0;
|
|
3054
3367
|
if (lineCount === 0) break;
|
|
3368
|
+
const prefixMatches = parent.children.every((childId, index) => childId === expectedCodeLineId(parent.id, index));
|
|
3369
|
+
const isStrictTailAppend = requestedStartIndex === parent.children.length;
|
|
3370
|
+
if (!isStrictTailAppend || !prefixMatches) {
|
|
3371
|
+
debugCounters.appendLineGuardRejected += 1;
|
|
3372
|
+
appendDiagnostic(
|
|
3373
|
+
`appendLines guard rejected:${parent.id}:requestedStart=${requestedStartIndex}:childCount=${parent.children.length}:prefixMatches=${prefixMatches}`
|
|
3374
|
+
);
|
|
3375
|
+
break;
|
|
3376
|
+
}
|
|
3055
3377
|
const originalChildren = parent.children.slice();
|
|
3056
3378
|
const insertedIds = [];
|
|
3057
3379
|
const insertedSet = /* @__PURE__ */ new Set();
|
|
3058
|
-
const isTrailingAppend =
|
|
3380
|
+
const isTrailingAppend = true;
|
|
3059
3381
|
let parentChildrenChanged = false;
|
|
3060
3382
|
let codeLinesMutated = false;
|
|
3061
3383
|
for (let offset = 0; offset < lineCount; offset++) {
|
|
@@ -3097,7 +3419,8 @@ function createRendererStore(initialBlocks = []) {
|
|
|
3097
3419
|
parentId: parent.id,
|
|
3098
3420
|
children: [],
|
|
3099
3421
|
props: nextLineProps,
|
|
3100
|
-
version: 0
|
|
3422
|
+
version: 0,
|
|
3423
|
+
blockEpoch: parent.blockEpoch
|
|
3101
3424
|
};
|
|
3102
3425
|
nodes.set(lineId, record);
|
|
3103
3426
|
touched.add(lineId);
|
|
@@ -3147,6 +3470,7 @@ function createRendererStore(initialBlocks = []) {
|
|
|
3147
3470
|
break;
|
|
3148
3471
|
}
|
|
3149
3472
|
case "setHTML": {
|
|
3473
|
+
if (rejectStaleEpochPatch(patch, patch.at)) break;
|
|
3150
3474
|
const target = nodes.get(patch.at.nodeId ?? patch.at.blockId);
|
|
3151
3475
|
if (!target) break;
|
|
3152
3476
|
const sanitized = patch.sanitized ? patch.html : sanitizeHTML2(patch.html);
|
|
@@ -3189,6 +3513,30 @@ function createRendererStore(initialBlocks = []) {
|
|
|
3189
3513
|
applyPatch(patch);
|
|
3190
3514
|
}
|
|
3191
3515
|
}
|
|
3516
|
+
const finalizedTableRoots = /* @__PURE__ */ new Set();
|
|
3517
|
+
for (const touchedId of touched) {
|
|
3518
|
+
let cursor = nodes.get(touchedId);
|
|
3519
|
+
while (cursor && cursor.id !== PATCH_ROOT_ID) {
|
|
3520
|
+
if (cursor.type === "table" && cursor.block?.isFinalized && cursor.parentId) {
|
|
3521
|
+
finalizedTableRoots.add(cursor.id);
|
|
3522
|
+
break;
|
|
3523
|
+
}
|
|
3524
|
+
cursor = cursor.parentId ? nodes.get(cursor.parentId) : void 0;
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
if (finalizedTableRoots.size > 0) {
|
|
3528
|
+
for (const tableId of finalizedTableRoots) {
|
|
3529
|
+
const tableNode = nodes.get(tableId);
|
|
3530
|
+
if (!tableNode || !tableNode.block || !tableNode.parentId) continue;
|
|
3531
|
+
const parent = nodes.get(tableNode.parentId);
|
|
3532
|
+
if (!parent) continue;
|
|
3533
|
+
const index = parent.children.indexOf(tableNode.id);
|
|
3534
|
+
if (index === -1) continue;
|
|
3535
|
+
const snapshot = createBlockSnapshot(cloneBlock(tableNode.block));
|
|
3536
|
+
replaceSnapshotAt(nodes, parent, index, snapshot, touched, tableNode.blockEpoch);
|
|
3537
|
+
mutatedBlocks = true;
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3192
3540
|
if (mutatedBlocks) {
|
|
3193
3541
|
blocksDirty = true;
|
|
3194
3542
|
}
|
|
@@ -3209,6 +3557,8 @@ function createRendererStore(initialBlocks = []) {
|
|
|
3209
3557
|
normalizeListDepthsForIds(nodes, touched, listDepthDirtyNodes, PATCH_ROOT_ID);
|
|
3210
3558
|
}
|
|
3211
3559
|
}
|
|
3560
|
+
invariantViolations = [...collectStoreInvariantViolations(nodes), ...transientDiagnostics];
|
|
3561
|
+
debugCounters.storeInvariantViolationCount += invariantViolations.length;
|
|
3212
3562
|
if (touched.size > 0 || mutatedBlocks) {
|
|
3213
3563
|
version++;
|
|
3214
3564
|
scheduleNotify();
|
|
@@ -3257,6 +3607,7 @@ function createRendererStore(initialBlocks = []) {
|
|
|
3257
3607
|
if (!record) {
|
|
3258
3608
|
return EMPTY_NODE_SNAPSHOT;
|
|
3259
3609
|
}
|
|
3610
|
+
const nodeSnapshotCache = getNodeSnapshotCacheForMap(nodes);
|
|
3260
3611
|
const cached = nodeSnapshotCache.get(id);
|
|
3261
3612
|
if (cached && cached.version === record.version && cached.node === record) {
|
|
3262
3613
|
return cached;
|
|
@@ -3276,6 +3627,7 @@ function createRendererStore(initialBlocks = []) {
|
|
|
3276
3627
|
if (!record) {
|
|
3277
3628
|
return EMPTY_CHILDREN_SNAPSHOT;
|
|
3278
3629
|
}
|
|
3630
|
+
const childrenSnapshotCache = getChildrenSnapshotCacheForMap(nodes);
|
|
3279
3631
|
const cached = childrenSnapshotCache.get(id);
|
|
3280
3632
|
if (cached && cached.version === record.version) {
|
|
3281
3633
|
return cached;
|
|
@@ -3285,6 +3637,12 @@ function createRendererStore(initialBlocks = []) {
|
|
|
3285
3637
|
pruneCache(childrenSnapshotCache, CHILDREN_SNAPSHOT_CACHE_LIMIT, CHILDREN_SNAPSHOT_CACHE_BUFFER);
|
|
3286
3638
|
return snapshot;
|
|
3287
3639
|
},
|
|
3640
|
+
getInvariantViolations() {
|
|
3641
|
+
return invariantViolations.slice();
|
|
3642
|
+
},
|
|
3643
|
+
getDebugCounters() {
|
|
3644
|
+
return { ...debugCounters };
|
|
3645
|
+
},
|
|
3288
3646
|
getLastCoalescingMetrics() {
|
|
3289
3647
|
return lastCoalescingMetrics;
|
|
3290
3648
|
},
|
|
@@ -3733,8 +4091,10 @@ var ListBlockView = React7.memo(
|
|
|
3733
4091
|
isFinalized: block?.isFinalized ?? false
|
|
3734
4092
|
});
|
|
3735
4093
|
}, [listDebugEnabled, blockId, depth, ordered, childIds, listItemIds, store, block?.isFinalized]);
|
|
3736
|
-
const listStyle = React7.useMemo(() => {
|
|
3737
|
-
if (!ordered || listItemIds.length === 0)
|
|
4094
|
+
const { listStyle, markerDigits } = React7.useMemo(() => {
|
|
4095
|
+
if (!ordered || listItemIds.length === 0) {
|
|
4096
|
+
return { listStyle: void 0, markerDigits: 1 };
|
|
4097
|
+
}
|
|
3738
4098
|
let maxDigits = 1;
|
|
3739
4099
|
listItemIds.forEach((childId, index) => {
|
|
3740
4100
|
const childNode = store.getNode(childId);
|
|
@@ -3746,9 +4106,14 @@ var ListBlockView = React7.memo(
|
|
|
3746
4106
|
const digits = digitMatch ? digitMatch.length : text.length;
|
|
3747
4107
|
if (digits > maxDigits) maxDigits = digits;
|
|
3748
4108
|
});
|
|
3749
|
-
const baseIndent = depth === 0 ? "
|
|
4109
|
+
const baseIndent = depth === 0 ? "2rem" : depth === 1 ? "1.75rem" : "1.5rem";
|
|
4110
|
+
const extraDigits = Math.max(0, maxDigits - 1);
|
|
3750
4111
|
return {
|
|
3751
|
-
|
|
4112
|
+
markerDigits: maxDigits,
|
|
4113
|
+
listStyle: {
|
|
4114
|
+
["--list-indent"]: extraDigits > 0 ? `calc(${baseIndent} + ${extraDigits}ch)` : baseIndent,
|
|
4115
|
+
["--list-marker-digits"]: String(maxDigits)
|
|
4116
|
+
}
|
|
3752
4117
|
};
|
|
3753
4118
|
}, [ordered, listItemIds, store, depth]);
|
|
3754
4119
|
if (listItemIds.length === 0) return null;
|
|
@@ -3757,6 +4122,7 @@ var ListBlockView = React7.memo(
|
|
|
3757
4122
|
{
|
|
3758
4123
|
className: `markdown-list ${ordered ? "ordered" : "unordered"}`,
|
|
3759
4124
|
"data-list-depth": depth,
|
|
4125
|
+
"data-marker-digits": ordered ? markerDigits : void 0,
|
|
3760
4126
|
"data-list-id": listDebugEnabled ? blockId : void 0,
|
|
3761
4127
|
style: listStyle,
|
|
3762
4128
|
children: listItemIds.map((childId, index) => /* @__PURE__ */ jsx(
|
|
@@ -3938,6 +4304,7 @@ var CodeBlockView = React7.memo(({ store, blockId, registry }) => {
|
|
|
3938
4304
|
deferredConfig ? { ...deferredConfig, enabled: true } : { enabled: false }
|
|
3939
4305
|
);
|
|
3940
4306
|
if (!node || !node.block) return null;
|
|
4307
|
+
const blockIsFinalized = node.block.isFinalized;
|
|
3941
4308
|
const nodeVersion = node.version;
|
|
3942
4309
|
const lines = React7.useMemo(() => {
|
|
3943
4310
|
const seenIds = /* @__PURE__ */ new Set();
|
|
@@ -3968,13 +4335,25 @@ var CodeBlockView = React7.memo(({ store, blockId, registry }) => {
|
|
|
3968
4335
|
const lang = typeof node.props?.lang === "string" ? node.props?.lang : node.block.payload.meta?.lang;
|
|
3969
4336
|
const preAttrs = node.props?.preAttrs ?? void 0;
|
|
3970
4337
|
const codeAttrs = node.props?.codeAttrs ?? void 0;
|
|
4338
|
+
const blockMeta = node.block.payload.meta ?? void 0;
|
|
4339
|
+
const lazyEnabled = Boolean(blockMeta?.lazyTokenization);
|
|
4340
|
+
const lazyTokenizedUntil = typeof blockMeta?.lazyTokenizedUntil === "number" ? blockMeta.lazyTokenizedUntil : 0;
|
|
4341
|
+
const lazyHighlightedLines = Array.isArray(blockMeta?.highlightedLines) ? blockMeta.highlightedLines.length : 0;
|
|
4342
|
+
const lazyTokenLines = Array.isArray(blockMeta?.tokenLines) ? blockMeta.tokenLines.length : 0;
|
|
4343
|
+
const lazySourceTextLineCount = React7.useMemo(() => {
|
|
4344
|
+
const raw = typeof node.block?.payload.raw === "string" ? node.block.payload.raw : "";
|
|
4345
|
+
const metaCode = typeof blockMeta?.code === "string" ? blockMeta.code : null;
|
|
4346
|
+
const sourceText = resolveCodeSourceText(raw, metaCode);
|
|
4347
|
+
if (sourceText.length === 0) return 0;
|
|
4348
|
+
return sourceText.split("\n").length;
|
|
4349
|
+
}, [blockMeta, node.block?.payload.raw]);
|
|
4350
|
+
const totalLazyLineCount = Math.max(lines.length, lazyHighlightedLines, lazyTokenLines, lazySourceTextLineCount);
|
|
3971
4351
|
const virtualizationConfig = DEFAULT_VIRTUALIZED_CODE_CONFIG;
|
|
3972
4352
|
const virtualizationDisabled = typeof process !== "undefined" && typeof process.env === "object" && process.env.STREAM_MDX_DISABLE_VIRTUALIZED_CODE === "true";
|
|
3973
|
-
const
|
|
4353
|
+
const canVirtualize = node.block.isFinalized && !lazyEnabled;
|
|
4354
|
+
const shouldVirtualize = canVirtualize && !virtualizationDisabled && virtualizationConfig.enabled && lines.length >= virtualizationConfig.virtualizeThreshold;
|
|
3974
4355
|
const virtualization = useVirtualizedCode(lines, shouldVirtualize ? virtualizationConfig : { ...virtualizationConfig, enabled: false });
|
|
3975
4356
|
const highlightRequester = useCodeHighlightRequester();
|
|
3976
|
-
const lazyEnabled = Boolean(node.block.payload.meta?.lazyTokenization);
|
|
3977
|
-
const lazyTokenizedUntil = typeof node.block.payload.meta?.lazyTokenizedUntil === "number" ? node.block.payload.meta.lazyTokenizedUntil : 0;
|
|
3978
4357
|
const lastRangeRef = React7.useRef(null);
|
|
3979
4358
|
const rafRef = React7.useRef(null);
|
|
3980
4359
|
const idleRef = React7.useRef(null);
|
|
@@ -4055,29 +4434,111 @@ var CodeBlockView = React7.memo(({ store, blockId, registry }) => {
|
|
|
4055
4434
|
virtualization.window.startIndex,
|
|
4056
4435
|
virtualization.window.endIndex
|
|
4057
4436
|
]);
|
|
4058
|
-
|
|
4059
|
-
if (
|
|
4060
|
-
|
|
4437
|
+
React7.useEffect(() => {
|
|
4438
|
+
if (shouldVirtualize || !blockIsFinalized || !lazyEnabled || !highlightRequester) return;
|
|
4439
|
+
if (totalLazyLineCount <= 0 || lazyTokenizedUntil >= totalLazyLineCount) return;
|
|
4440
|
+
const nextRange = {
|
|
4441
|
+
visibleStart: 0,
|
|
4442
|
+
visibleEnd: totalLazyLineCount,
|
|
4443
|
+
prefetchStart: 0,
|
|
4444
|
+
prefetchEnd: totalLazyLineCount,
|
|
4445
|
+
tokenizedUntil: lazyTokenizedUntil
|
|
4446
|
+
};
|
|
4447
|
+
const last = lastRangeRef.current;
|
|
4448
|
+
if (last && last.visibleStart === nextRange.visibleStart && last.visibleEnd === nextRange.visibleEnd && last.prefetchStart === nextRange.prefetchStart && last.prefetchEnd === nextRange.prefetchEnd && last.tokenizedUntil === nextRange.tokenizedUntil) {
|
|
4449
|
+
return;
|
|
4061
4450
|
}
|
|
4451
|
+
lastRangeRef.current = nextRange;
|
|
4452
|
+
if (rafRef.current !== null && typeof cancelAnimationFrame === "function") {
|
|
4453
|
+
cancelAnimationFrame(rafRef.current);
|
|
4454
|
+
}
|
|
4455
|
+
const requestFullHighlight = () => {
|
|
4456
|
+
highlightRequester({
|
|
4457
|
+
blockId,
|
|
4458
|
+
startLine: 0,
|
|
4459
|
+
endLine: totalLazyLineCount,
|
|
4460
|
+
priority: "visible",
|
|
4461
|
+
reason: "finalize-full"
|
|
4462
|
+
});
|
|
4463
|
+
rafRef.current = null;
|
|
4464
|
+
};
|
|
4465
|
+
if (typeof requestAnimationFrame === "function") {
|
|
4466
|
+
rafRef.current = requestAnimationFrame(requestFullHighlight);
|
|
4467
|
+
} else {
|
|
4468
|
+
requestFullHighlight();
|
|
4469
|
+
}
|
|
4470
|
+
}, [blockId, blockIsFinalized, highlightRequester, lazyEnabled, lazyTokenizedUntil, shouldVirtualize, totalLazyLineCount]);
|
|
4471
|
+
const highlightedHtml = React7.useMemo(() => {
|
|
4472
|
+
const blockHtml = node.block?.payload.highlightedHtml ?? "";
|
|
4473
|
+
const raw = typeof node.block?.payload.raw === "string" ? node.block.payload.raw : "";
|
|
4474
|
+
const metaCode = typeof node.block?.payload.meta?.code === "string" ? (node.block?.payload.meta).code : null;
|
|
4475
|
+
const sourceText = resolveCodeSourceText(raw, metaCode);
|
|
4476
|
+
const sourceEndsWithNewline = sourceText.endsWith("\n");
|
|
4477
|
+
const sourceLines = sourceText.length > 0 ? sourceText.split("\n") : [];
|
|
4478
|
+
const rawFallbackLines = buildCodeFallbackLines(raw);
|
|
4479
|
+
const deterministicWrapperAttrs = getDefaultCodeWrapperAttributes2(lang);
|
|
4480
|
+
const composedPreAttrs = deterministicWrapperAttrs.preAttrs;
|
|
4481
|
+
const composedCodeAttrs = deterministicWrapperAttrs.codeAttrs;
|
|
4482
|
+
const shouldAppendTerminalNewline = (renderedLines) => {
|
|
4483
|
+
if (!sourceEndsWithNewline || renderedLines.length === 0 || lines.length === 0) {
|
|
4484
|
+
return false;
|
|
4485
|
+
}
|
|
4486
|
+
const lastRendered = renderedLines[renderedLines.length - 1];
|
|
4487
|
+
const lastKnown = lines[lines.length - 1];
|
|
4488
|
+
if (!lastRendered || !lastKnown || lastRendered.index !== lastKnown.index || lastRendered.text === "") {
|
|
4489
|
+
return false;
|
|
4490
|
+
}
|
|
4491
|
+
const expectedRenderedLineCount = Math.max(0, sourceLines.length - 1);
|
|
4492
|
+
const expectedLastRenderedLine = sourceLines.length >= 2 ? sourceLines[sourceLines.length - 2] ?? "" : "";
|
|
4493
|
+
return renderedLines.length === expectedRenderedLineCount && lastRendered.text === expectedLastRenderedLine;
|
|
4494
|
+
};
|
|
4062
4495
|
if (lines.length > 0) {
|
|
4063
4496
|
const hasLineHtml = lines.some((line) => typeof line.html === "string" && line.html.length > 0);
|
|
4064
|
-
if (!hasLineHtml &&
|
|
4065
|
-
return
|
|
4497
|
+
if (!hasLineHtml && blockHtml) {
|
|
4498
|
+
return blockHtml;
|
|
4066
4499
|
}
|
|
4067
4500
|
if (shouldVirtualize) {
|
|
4068
|
-
return composeHighlightedHtml(
|
|
4501
|
+
return composeHighlightedHtml(
|
|
4502
|
+
virtualization.window.visibleLines,
|
|
4503
|
+
composedPreAttrs,
|
|
4504
|
+
composedCodeAttrs,
|
|
4505
|
+
shouldAppendTerminalNewline(virtualization.window.visibleLines)
|
|
4506
|
+
);
|
|
4069
4507
|
}
|
|
4070
|
-
return composeHighlightedHtml(lines,
|
|
4508
|
+
return composeHighlightedHtml(lines, composedPreAttrs, composedCodeAttrs, shouldAppendTerminalNewline(lines));
|
|
4509
|
+
}
|
|
4510
|
+
if (!shouldVirtualize && blockHtml) {
|
|
4511
|
+
const hasHighlightedLines = blockHtml.includes('class="line"');
|
|
4512
|
+
if (!hasHighlightedLines && rawFallbackLines.length > 0) {
|
|
4513
|
+
return composeHighlightedHtml(
|
|
4514
|
+
rawFallbackLines,
|
|
4515
|
+
composedPreAttrs,
|
|
4516
|
+
composedCodeAttrs,
|
|
4517
|
+
sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
|
|
4518
|
+
);
|
|
4519
|
+
}
|
|
4520
|
+
return blockHtml;
|
|
4071
4521
|
}
|
|
4072
|
-
const blockHtml = node.block?.payload.highlightedHtml ?? "";
|
|
4073
4522
|
if (blockHtml?.trim().match(/^```[\w-]*\s*$/)) {
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4523
|
+
if (rawFallbackLines.length > 0) {
|
|
4524
|
+
return composeHighlightedHtml(
|
|
4525
|
+
rawFallbackLines,
|
|
4526
|
+
composedPreAttrs,
|
|
4527
|
+
composedCodeAttrs,
|
|
4528
|
+
sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
|
|
4529
|
+
);
|
|
4077
4530
|
}
|
|
4078
4531
|
}
|
|
4532
|
+
if (!blockHtml && rawFallbackLines.length > 0) {
|
|
4533
|
+
return composeHighlightedHtml(
|
|
4534
|
+
rawFallbackLines,
|
|
4535
|
+
composedPreAttrs,
|
|
4536
|
+
composedCodeAttrs,
|
|
4537
|
+
sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
|
|
4538
|
+
);
|
|
4539
|
+
}
|
|
4079
4540
|
return blockHtml;
|
|
4080
|
-
}, [shouldVirtualize, node.block?.payload.highlightedHtml,
|
|
4541
|
+
}, [blockIsFinalized, shouldVirtualize, node.block?.payload.highlightedHtml, node.block?.payload.meta, lines, lang, virtualization.window.visibleLines, node.block?.payload.raw]);
|
|
4081
4542
|
const rendered = /* @__PURE__ */ jsx(
|
|
4082
4543
|
CodeComponent,
|
|
4083
4544
|
{
|
|
@@ -4089,13 +4550,19 @@ var CodeBlockView = React7.memo(({ store, blockId, registry }) => {
|
|
|
4089
4550
|
codeAttrs
|
|
4090
4551
|
}
|
|
4091
4552
|
);
|
|
4553
|
+
const effectiveLineCount = React7.useMemo(() => {
|
|
4554
|
+
if (lines.length > 0) {
|
|
4555
|
+
return lines.length;
|
|
4556
|
+
}
|
|
4557
|
+
return 1;
|
|
4558
|
+
}, [lines.length]);
|
|
4092
4559
|
const codeFrameClass = "not-prose flex flex-col rounded-lg border border-input pt-1 font-mono text-sm";
|
|
4093
4560
|
const codeMetricsAttrs = {
|
|
4094
4561
|
"data-code-block": "true",
|
|
4095
4562
|
"data-block-id": blockId,
|
|
4096
4563
|
"data-code-virtualized": shouldVirtualize ? "true" : "false",
|
|
4097
|
-
"data-code-total-lines": String(
|
|
4098
|
-
"data-code-mounted-lines": String(shouldVirtualize ? virtualization.window.mountedLines :
|
|
4564
|
+
"data-code-total-lines": String(effectiveLineCount),
|
|
4565
|
+
"data-code-mounted-lines": String(shouldVirtualize ? virtualization.window.mountedLines : effectiveLineCount),
|
|
4099
4566
|
"data-code-window-size": String(virtualization.config.windowSize)
|
|
4100
4567
|
};
|
|
4101
4568
|
const codeView = shouldVirtualize ? (() => {
|
|
@@ -4141,21 +4608,152 @@ var CodeBlockView = React7.memo(({ store, blockId, registry }) => {
|
|
|
4141
4608
|
return codeView;
|
|
4142
4609
|
});
|
|
4143
4610
|
CodeBlockView.displayName = "CodeBlockView";
|
|
4144
|
-
function composeHighlightedHtml(lines, preAttrs, codeAttrs) {
|
|
4611
|
+
function composeHighlightedHtml(lines, preAttrs, codeAttrs, appendTerminalNewline = false) {
|
|
4145
4612
|
const lineMarkup = lines.map((line) => {
|
|
4146
|
-
const content = line.html ?? escapeHtml3(line.text);
|
|
4613
|
+
const content = line.html ?? renderTokenLineHtml(line.tokens) ?? escapeHtml3(line.text);
|
|
4147
4614
|
const dataLine = Number.isFinite(line.index) ? ` data-line="${line.index + 1}"` : "";
|
|
4148
4615
|
return `<span class="line"${dataLine}>${content}</span>`;
|
|
4149
4616
|
}).join("\n");
|
|
4150
4617
|
const preAttr = attrsToString(preAttrs);
|
|
4151
4618
|
const codeAttr = attrsToString(codeAttrs);
|
|
4152
|
-
return `<pre${preAttr}><code${codeAttr}>${lineMarkup}
|
|
4153
|
-
|
|
4619
|
+
return `<pre${preAttr}><code${codeAttr}>${lineMarkup}${appendTerminalNewline ? "\n" : ""}</code></pre>`;
|
|
4620
|
+
}
|
|
4621
|
+
var TOKEN_FONT_STYLE_ITALIC = 1;
|
|
4622
|
+
var TOKEN_FONT_STYLE_BOLD = 2;
|
|
4623
|
+
var TOKEN_FONT_STYLE_UNDERLINE = 4;
|
|
4624
|
+
var TOKEN_FONT_STYLE_STRIKETHROUGH = 8;
|
|
4625
|
+
function renderTokenLineHtml(tokens) {
|
|
4626
|
+
if (!tokens || !Array.isArray(tokens.spans)) {
|
|
4627
|
+
return null;
|
|
4628
|
+
}
|
|
4629
|
+
return mergeRenderableTokenSpans(tokens.spans).map((span) => renderTokenSpanHtml(span)).join("");
|
|
4630
|
+
}
|
|
4631
|
+
function renderTokenSpanHtml(span) {
|
|
4632
|
+
const dark = span.v?.dark;
|
|
4633
|
+
const light = span.v?.light;
|
|
4634
|
+
const single = span.s;
|
|
4635
|
+
const styles = [];
|
|
4636
|
+
if (dark?.fg) {
|
|
4637
|
+
styles.push(`--shiki-dark:${dark.fg}`);
|
|
4638
|
+
}
|
|
4639
|
+
if (light?.fg) {
|
|
4640
|
+
styles.push(`--shiki-light:${light.fg}`);
|
|
4641
|
+
}
|
|
4642
|
+
if (!light?.fg && !dark?.fg && single?.fg) {
|
|
4643
|
+
styles.push(`color:${single.fg}`);
|
|
4644
|
+
}
|
|
4645
|
+
const fontStyle = light?.fs ?? dark?.fs ?? single?.fs;
|
|
4646
|
+
if (typeof fontStyle === "number" && fontStyle > 0) {
|
|
4647
|
+
if (fontStyle & TOKEN_FONT_STYLE_ITALIC) {
|
|
4648
|
+
styles.push("font-style: italic");
|
|
4649
|
+
}
|
|
4650
|
+
if (fontStyle & TOKEN_FONT_STYLE_BOLD) {
|
|
4651
|
+
styles.push("font-weight: bold");
|
|
4652
|
+
}
|
|
4653
|
+
const decorations = [];
|
|
4654
|
+
if (fontStyle & TOKEN_FONT_STYLE_UNDERLINE) {
|
|
4655
|
+
decorations.push("underline");
|
|
4656
|
+
}
|
|
4657
|
+
if (fontStyle & TOKEN_FONT_STYLE_STRIKETHROUGH) {
|
|
4658
|
+
decorations.push("line-through");
|
|
4659
|
+
}
|
|
4660
|
+
if (decorations.length > 0) {
|
|
4661
|
+
styles.push(`text-decoration: ${decorations.join(" ")}`);
|
|
4662
|
+
}
|
|
4663
|
+
}
|
|
4664
|
+
const content = escapeHtml3(span.t ?? "");
|
|
4665
|
+
if (styles.length === 0) {
|
|
4666
|
+
return content;
|
|
4667
|
+
}
|
|
4668
|
+
return `<span style="${styles.join(";")}">${content}</span>`;
|
|
4669
|
+
}
|
|
4670
|
+
function mergeRenderableTokenSpans(spans) {
|
|
4671
|
+
if (spans.length <= 1) {
|
|
4672
|
+
return spans.slice();
|
|
4673
|
+
}
|
|
4674
|
+
const merged = [];
|
|
4675
|
+
let pendingWhitespace = "";
|
|
4676
|
+
for (const span of spans) {
|
|
4677
|
+
const text = typeof span.t === "string" ? span.t : "";
|
|
4678
|
+
if (/^[\t ]+$/.test(text)) {
|
|
4679
|
+
pendingWhitespace += text;
|
|
4680
|
+
continue;
|
|
4681
|
+
}
|
|
4682
|
+
const nextSpan = pendingWhitespace ? { ...span, t: `${pendingWhitespace}${text}` } : span;
|
|
4683
|
+
pendingWhitespace = "";
|
|
4684
|
+
const previous = merged[merged.length - 1];
|
|
4685
|
+
if (previous && sameTokenSpanStyle(previous, nextSpan)) {
|
|
4686
|
+
previous.t += nextSpan.t;
|
|
4687
|
+
} else {
|
|
4688
|
+
merged.push({ ...nextSpan });
|
|
4689
|
+
}
|
|
4690
|
+
}
|
|
4691
|
+
if (pendingWhitespace) {
|
|
4692
|
+
const previous = merged[merged.length - 1];
|
|
4693
|
+
if (previous) {
|
|
4694
|
+
previous.t += pendingWhitespace;
|
|
4695
|
+
} else {
|
|
4696
|
+
merged.push({ t: pendingWhitespace });
|
|
4697
|
+
}
|
|
4698
|
+
}
|
|
4699
|
+
return merged;
|
|
4700
|
+
}
|
|
4701
|
+
function sameTokenSpanStyle(a, b) {
|
|
4702
|
+
const variantKeys = /* @__PURE__ */ new Set([
|
|
4703
|
+
...Object.keys(a.v ?? {}),
|
|
4704
|
+
...Object.keys(b.v ?? {})
|
|
4705
|
+
]);
|
|
4706
|
+
for (const key of variantKeys) {
|
|
4707
|
+
const aStyle = a.v?.[key];
|
|
4708
|
+
const bStyle = b.v?.[key];
|
|
4709
|
+
if ((aStyle?.fg ?? null) !== (bStyle?.fg ?? null)) return false;
|
|
4710
|
+
if ((aStyle?.bg ?? null) !== (bStyle?.bg ?? null)) return false;
|
|
4711
|
+
if ((aStyle?.fs ?? null) !== (bStyle?.fs ?? null)) return false;
|
|
4712
|
+
}
|
|
4713
|
+
if ((a.s?.fg ?? null) !== (b.s?.fg ?? null)) return false;
|
|
4714
|
+
if ((a.s?.bg ?? null) !== (b.s?.bg ?? null)) return false;
|
|
4715
|
+
if ((a.s?.fs ?? null) !== (b.s?.fs ?? null)) return false;
|
|
4716
|
+
return true;
|
|
4717
|
+
}
|
|
4718
|
+
function resolveCodeSourceText(raw, metaCode) {
|
|
4719
|
+
if (metaCode !== null) {
|
|
4720
|
+
return metaCode.replace(/\r\n?/g, "\n");
|
|
4721
|
+
}
|
|
4722
|
+
if (!raw) return "";
|
|
4723
|
+
const normalized = raw.replace(/\r\n?/g, "\n");
|
|
4724
|
+
const fenced = stripCodeFence(normalized);
|
|
4725
|
+
return fenced.hadFence ? fenced.code : normalized;
|
|
4154
4726
|
}
|
|
4155
4727
|
function attrsToString(attrs) {
|
|
4156
4728
|
if (!attrs) return "";
|
|
4157
4729
|
return Object.entries(attrs).map(([key, value]) => ` ${key}="${escapeAttribute(value)}"`).join("");
|
|
4158
4730
|
}
|
|
4731
|
+
function buildCodeFallbackLines(raw) {
|
|
4732
|
+
const normalized = normalizeCodeFallbackRaw(raw);
|
|
4733
|
+
if (normalized.length === 0) {
|
|
4734
|
+
if (/^\s*```[^\n]*\n?\s*$/.test(raw)) {
|
|
4735
|
+
return [{ id: "temp-0", index: 0, text: "", html: null }];
|
|
4736
|
+
}
|
|
4737
|
+
return [];
|
|
4738
|
+
}
|
|
4739
|
+
return normalized.split("\n").map((line, index) => ({
|
|
4740
|
+
id: `temp-${index}`,
|
|
4741
|
+
index,
|
|
4742
|
+
text: line,
|
|
4743
|
+
html: null
|
|
4744
|
+
}));
|
|
4745
|
+
}
|
|
4746
|
+
function normalizeCodeFallbackRaw(raw) {
|
|
4747
|
+
if (!raw) return "";
|
|
4748
|
+
const normalized = raw.replace(/\r\n?/g, "\n");
|
|
4749
|
+
const fenced = stripCodeFence(normalized);
|
|
4750
|
+
if (!fenced.hadFence) {
|
|
4751
|
+
return normalized.replace(/\n+$/, "");
|
|
4752
|
+
}
|
|
4753
|
+
let code = fenced.code;
|
|
4754
|
+
code = code.replace(/\n+$/, "");
|
|
4755
|
+
return code;
|
|
4756
|
+
}
|
|
4159
4757
|
function escapeHtml3(value) {
|
|
4160
4758
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
4161
4759
|
}
|