@stream-mdx/react 0.3.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 -14
- 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 +511 -176
- package/dist/components/index.d.cts +3 -1
- package/dist/components/index.d.ts +3 -1
- package/dist/components/index.mjs +358 -26
- package/dist/{index-D7px9jug.d.cts → index-BVhp_pcF.d.ts} +4 -1
- package/dist/{index-D7px9jug.d.ts → index-EaeouYp2.d.cts} +4 -1
- package/dist/index.cjs +2146 -1145
- package/dist/index.d.cts +6 -4
- package/dist/index.d.ts +6 -4
- package/dist/index.mjs +3835 -2836
- 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 +273 -24
- package/dist/renderer/node-views.d.cts +3 -1
- package/dist/renderer/node-views.d.ts +3 -1
- package/dist/renderer/node-views.mjs +274 -25
- 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 +3386 -2727
- package/dist/renderer.d.cts +15 -12
- package/dist/renderer.d.ts +15 -12
- package/dist/renderer.mjs +3410 -2751
- package/dist/server.cjs +4842 -0
- package/dist/server.d.cts +254 -0
- package/dist/server.d.ts +254 -0
- package/dist/server.mjs +4824 -0
- package/dist/{streaming-markdown-Dp1IDgMT.d.ts → streaming-markdown-CEI9eDZm.d.ts} +3 -1
- package/dist/{streaming-markdown-DSC4L0xR.d.cts → streaming-markdown-DRisClXb.d.cts} +3 -1
- package/dist/streaming-markdown.cjs +3476 -2794
- package/dist/streaming-markdown.d.cts +4 -2
- package/dist/streaming-markdown.d.ts +4 -2
- package/dist/streaming-markdown.mjs +3436 -2754
- package/dist/utils/inline-html.d.cts +3 -1
- package/dist/utils/inline-html.d.ts +3 -1
- package/package.json +19 -3
- package/shadcn/README.md +28 -0
- package/shadcn/bottom-stick-scroll-area.tsx +358 -0
package/dist/server.cjs
ADDED
|
@@ -0,0 +1,4842 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
|
|
33
|
+
// src/mdx-client.ts
|
|
34
|
+
var mdx_client_exports = {};
|
|
35
|
+
__export(mdx_client_exports, {
|
|
36
|
+
MDXClient: () => MDXClient,
|
|
37
|
+
MDXComponentFactory: () => MDXComponentFactory,
|
|
38
|
+
getMDXClient: () => getMDXClient,
|
|
39
|
+
getMDXComponentFactory: () => getMDXComponentFactory,
|
|
40
|
+
registerInlineMdxModule: () => registerInlineMdxModule,
|
|
41
|
+
registerMDXComponents: () => registerMDXComponents
|
|
42
|
+
});
|
|
43
|
+
function assignMdxChildKey(child, index) {
|
|
44
|
+
if (child == null) return child;
|
|
45
|
+
if (Array.isArray(child)) {
|
|
46
|
+
return child.map((entry, subIndex) => assignMdxChildKey(entry, `${index}-${subIndex}`));
|
|
47
|
+
}
|
|
48
|
+
if (import_react3.default.isValidElement(child)) {
|
|
49
|
+
if (child.key !== null && child.key !== void 0) {
|
|
50
|
+
return child;
|
|
51
|
+
}
|
|
52
|
+
return import_react3.default.cloneElement(child, { key: index });
|
|
53
|
+
}
|
|
54
|
+
return child;
|
|
55
|
+
}
|
|
56
|
+
function getMDXClient(compileEndpoint) {
|
|
57
|
+
if (!globalMDXClient) {
|
|
58
|
+
globalMDXClient = new MDXClient(compileEndpoint);
|
|
59
|
+
}
|
|
60
|
+
return globalMDXClient;
|
|
61
|
+
}
|
|
62
|
+
function getMDXComponentFactory(compileEndpoint) {
|
|
63
|
+
if (!globalComponentFactory) {
|
|
64
|
+
const client = getMDXClient(compileEndpoint);
|
|
65
|
+
globalComponentFactory = new MDXComponentFactory(client);
|
|
66
|
+
}
|
|
67
|
+
return globalComponentFactory;
|
|
68
|
+
}
|
|
69
|
+
function registerMDXComponents(components, options) {
|
|
70
|
+
const factory = getMDXComponentFactory();
|
|
71
|
+
factory.registerComponents(components, options);
|
|
72
|
+
}
|
|
73
|
+
function registerInlineMdxModule(compiled) {
|
|
74
|
+
const factory = getMDXComponentFactory();
|
|
75
|
+
factory.registerInlineModule({
|
|
76
|
+
id: compiled.id,
|
|
77
|
+
code: compiled.code,
|
|
78
|
+
dependencies: compiled.dependencies ?? []
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
var import_react3, ReactJsxRuntime, withClassName, MDXClient, MDXComponentFactory, globalMDXClient, globalComponentFactory;
|
|
82
|
+
var init_mdx_client = __esm({
|
|
83
|
+
"src/mdx-client.ts"() {
|
|
84
|
+
"use strict";
|
|
85
|
+
import_react3 = __toESM(require("react"), 1);
|
|
86
|
+
ReactJsxRuntime = __toESM(require("react/jsx-runtime"), 1);
|
|
87
|
+
withClassName = (tag, className) => {
|
|
88
|
+
return (props) => import_react3.default.createElement(tag, { className, ...props });
|
|
89
|
+
};
|
|
90
|
+
MDXClient = class {
|
|
91
|
+
constructor(compileEndpoint = "/api/mdx-compile-v2") {
|
|
92
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
93
|
+
this.inlineModules = /* @__PURE__ */ new Map();
|
|
94
|
+
this.compiledCache = /* @__PURE__ */ new Map();
|
|
95
|
+
this.compiledInflight = /* @__PURE__ */ new Map();
|
|
96
|
+
this.compileEndpoint = compileEndpoint;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Compile MDX block on server
|
|
100
|
+
*/
|
|
101
|
+
async compile(block) {
|
|
102
|
+
if (block.type !== "mdx") {
|
|
103
|
+
throw new Error("Block is not MDX type");
|
|
104
|
+
}
|
|
105
|
+
const cacheKey = `${block.id}\0${block.payload.raw ?? ""}`;
|
|
106
|
+
const cachedPromise = this.cache.get(cacheKey);
|
|
107
|
+
if (cachedPromise) {
|
|
108
|
+
return cachedPromise;
|
|
109
|
+
}
|
|
110
|
+
const compilationPromise = this.doCompile(block);
|
|
111
|
+
this.cache.set(cacheKey, compilationPromise);
|
|
112
|
+
try {
|
|
113
|
+
const result = await compilationPromise;
|
|
114
|
+
return result;
|
|
115
|
+
} catch (error) {
|
|
116
|
+
this.cache.delete(cacheKey);
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async getCompiledInternal(ref) {
|
|
121
|
+
const inline = this.inlineModules.get(ref.id);
|
|
122
|
+
if (inline) {
|
|
123
|
+
return this.cloneCompiled(inline);
|
|
124
|
+
}
|
|
125
|
+
const cached = this.compiledCache.get(ref.id);
|
|
126
|
+
if (cached) {
|
|
127
|
+
return this.cloneCompiled(cached);
|
|
128
|
+
}
|
|
129
|
+
const inflight = this.compiledInflight.get(ref.id);
|
|
130
|
+
if (inflight) {
|
|
131
|
+
const resolved = await inflight;
|
|
132
|
+
return this.cloneCompiled(resolved);
|
|
133
|
+
}
|
|
134
|
+
const fetchPromise = this.fetchCompiled(ref);
|
|
135
|
+
this.compiledInflight.set(ref.id, fetchPromise);
|
|
136
|
+
try {
|
|
137
|
+
const data = await fetchPromise;
|
|
138
|
+
this.compiledCache.set(ref.id, data);
|
|
139
|
+
return this.cloneCompiled(data);
|
|
140
|
+
} finally {
|
|
141
|
+
this.compiledInflight.delete(ref.id);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async getCompiled(ref, options) {
|
|
145
|
+
if (options?.signal) {
|
|
146
|
+
const inline = this.inlineModules.get(ref.id);
|
|
147
|
+
if (inline) {
|
|
148
|
+
return this.cloneCompiled(inline);
|
|
149
|
+
}
|
|
150
|
+
const cached = this.compiledCache.get(ref.id);
|
|
151
|
+
if (cached) {
|
|
152
|
+
return this.cloneCompiled(cached);
|
|
153
|
+
}
|
|
154
|
+
const data = await this.fetchCompiled(ref, options);
|
|
155
|
+
this.compiledCache.set(ref.id, data);
|
|
156
|
+
return this.cloneCompiled(data);
|
|
157
|
+
}
|
|
158
|
+
return this.getCompiledInternal(ref);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Clear compilation cache
|
|
162
|
+
*/
|
|
163
|
+
clearCache() {
|
|
164
|
+
this.cache.clear();
|
|
165
|
+
this.compiledCache.clear();
|
|
166
|
+
this.compiledInflight.clear();
|
|
167
|
+
}
|
|
168
|
+
registerInlineModule(compiled) {
|
|
169
|
+
this.inlineModules.set(compiled.id, {
|
|
170
|
+
...compiled,
|
|
171
|
+
dependencies: Array.isArray(compiled.dependencies) ? [...compiled.dependencies] : []
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
clearInlineModules() {
|
|
175
|
+
this.inlineModules.clear();
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Actual compilation implementation
|
|
179
|
+
*/
|
|
180
|
+
async doCompile(block) {
|
|
181
|
+
const response = await fetch(this.compileEndpoint, {
|
|
182
|
+
method: "POST",
|
|
183
|
+
headers: {
|
|
184
|
+
"Content-Type": "application/json"
|
|
185
|
+
},
|
|
186
|
+
body: JSON.stringify({
|
|
187
|
+
content: block.payload.raw,
|
|
188
|
+
blockId: block.id
|
|
189
|
+
})
|
|
190
|
+
});
|
|
191
|
+
if (!response.ok) {
|
|
192
|
+
const error = await response.json().catch(() => ({ error: "Unknown error" }));
|
|
193
|
+
throw new Error(`MDX compilation failed: ${error.error || response.statusText}`);
|
|
194
|
+
}
|
|
195
|
+
const data = await response.json();
|
|
196
|
+
return {
|
|
197
|
+
id: data.id,
|
|
198
|
+
code: data.code,
|
|
199
|
+
dependencies: data.dependencies || [],
|
|
200
|
+
cached: data.cached || false
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
async fetchCompiled(ref, options) {
|
|
204
|
+
const response = await fetch(`${this.compileEndpoint}?id=${ref.id}`, {
|
|
205
|
+
signal: options?.signal
|
|
206
|
+
});
|
|
207
|
+
if (!response.ok) {
|
|
208
|
+
throw new Error(`Failed to get compiled MDX: ${response.statusText}`);
|
|
209
|
+
}
|
|
210
|
+
const data = await response.json();
|
|
211
|
+
return {
|
|
212
|
+
id: data.id,
|
|
213
|
+
code: data.code,
|
|
214
|
+
dependencies: data.dependencies || [],
|
|
215
|
+
timestamp: data.timestamp
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
cloneCompiled(compiled) {
|
|
219
|
+
return {
|
|
220
|
+
...compiled,
|
|
221
|
+
dependencies: Array.isArray(compiled.dependencies) ? [...compiled.dependencies] : []
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
MDXComponentFactory = class {
|
|
226
|
+
constructor(mdxClient) {
|
|
227
|
+
this.components = /* @__PURE__ */ new Map();
|
|
228
|
+
this.customComponents = /* @__PURE__ */ new Map();
|
|
229
|
+
this.mdxClient = mdxClient;
|
|
230
|
+
this.baseComponents = this.createBaseComponents();
|
|
231
|
+
}
|
|
232
|
+
registerInlineModule(module2) {
|
|
233
|
+
this.mdxClient.registerInlineModule(module2);
|
|
234
|
+
this.components.delete(module2.id);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Create React component from compiled MDX
|
|
238
|
+
*/
|
|
239
|
+
async createComponent(ref) {
|
|
240
|
+
const cached = this.components.get(ref.id);
|
|
241
|
+
if (cached) {
|
|
242
|
+
return cached;
|
|
243
|
+
}
|
|
244
|
+
const compiled = await this.mdxClient.getCompiled(ref);
|
|
245
|
+
const component = this.evaluateCompiledMDX(compiled);
|
|
246
|
+
this.components.set(ref.id, component);
|
|
247
|
+
return component;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Safely evaluate compiled MDX code
|
|
251
|
+
*/
|
|
252
|
+
evaluateCompiledMDX(compiled) {
|
|
253
|
+
try {
|
|
254
|
+
const context = this.createEvaluationContext();
|
|
255
|
+
const argNames = Object.keys(context);
|
|
256
|
+
const argValues = Object.values(context);
|
|
257
|
+
const moduleExports = new Function(...argNames, compiled.code)(...argValues);
|
|
258
|
+
const MDXComponent = this.resolveEvaluatedModule(moduleExports);
|
|
259
|
+
return (props) => {
|
|
260
|
+
const registryComponents = this.getComponents();
|
|
261
|
+
const incomingComponents = props?.components;
|
|
262
|
+
const mergedComponents = incomingComponents ? { ...registryComponents, ...incomingComponents } : registryComponents;
|
|
263
|
+
return import_react3.default.createElement(MDXComponent, {
|
|
264
|
+
...props,
|
|
265
|
+
components: mergedComponents
|
|
266
|
+
});
|
|
267
|
+
};
|
|
268
|
+
} catch (error) {
|
|
269
|
+
console.error("Failed to evaluate MDX:", error);
|
|
270
|
+
return () => import_react3.default.createElement(
|
|
271
|
+
"div",
|
|
272
|
+
{
|
|
273
|
+
className: "mdx-error"
|
|
274
|
+
},
|
|
275
|
+
`MDX Error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Create safe evaluation context for MDX
|
|
281
|
+
*/
|
|
282
|
+
createEvaluationContext() {
|
|
283
|
+
const runtime = ReactJsxRuntime;
|
|
284
|
+
const createElementWithChildren = (type, props, key) => {
|
|
285
|
+
const baseProps = props ?? {};
|
|
286
|
+
const finalProps = key === void 0 || key === null ? baseProps : { ...baseProps, key };
|
|
287
|
+
if (Array.isArray(finalProps.children)) {
|
|
288
|
+
finalProps.children = finalProps.children.map((child, index) => assignMdxChildKey(child, index));
|
|
289
|
+
}
|
|
290
|
+
return import_react3.default.createElement(type, finalProps);
|
|
291
|
+
};
|
|
292
|
+
const runtimeJsx = typeof runtime.jsx === "function" ? runtime.jsx.bind(runtime) : createElementWithChildren;
|
|
293
|
+
const runtimeJsxs = typeof runtime.jsxs === "function" ? runtime.jsxs.bind(runtime) : createElementWithChildren;
|
|
294
|
+
const runtimeJsxDEV = typeof runtime.jsxDEV === "function" ? runtime.jsxDEV.bind(runtime) : (type, props, key, ..._rest) => createElementWithChildren(type, props, key);
|
|
295
|
+
const jsxRuntime = {
|
|
296
|
+
jsx: runtimeJsx,
|
|
297
|
+
jsxs: runtimeJsxs,
|
|
298
|
+
jsxDEV: runtimeJsxDEV,
|
|
299
|
+
Fragment: runtime.Fragment ?? import_react3.default.Fragment
|
|
300
|
+
};
|
|
301
|
+
return {
|
|
302
|
+
__mdx_runtime: jsxRuntime,
|
|
303
|
+
React: import_react3.default,
|
|
304
|
+
Fragment: import_react3.default.Fragment,
|
|
305
|
+
useState: import_react3.default.useState,
|
|
306
|
+
useEffect: import_react3.default.useEffect,
|
|
307
|
+
useMemo: import_react3.default.useMemo,
|
|
308
|
+
useCallback: import_react3.default.useCallback,
|
|
309
|
+
useRef: import_react3.default.useRef,
|
|
310
|
+
useContext: import_react3.default.useContext,
|
|
311
|
+
useReducer: import_react3.default.useReducer,
|
|
312
|
+
useLayoutEffect: import_react3.default.useLayoutEffect,
|
|
313
|
+
console: {
|
|
314
|
+
log: console.log,
|
|
315
|
+
warn: console.warn,
|
|
316
|
+
error: console.error
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
resolveEvaluatedModule(output) {
|
|
321
|
+
if (!output) {
|
|
322
|
+
throw new Error("Compiled MDX module returned no exports");
|
|
323
|
+
}
|
|
324
|
+
if (typeof output === "function") {
|
|
325
|
+
return output;
|
|
326
|
+
}
|
|
327
|
+
if (typeof output.default === "function") {
|
|
328
|
+
return output.default;
|
|
329
|
+
}
|
|
330
|
+
if (typeof output.MDXContent === "function") {
|
|
331
|
+
return output.MDXContent;
|
|
332
|
+
}
|
|
333
|
+
throw new Error("Compiled MDX module did not export a component");
|
|
334
|
+
}
|
|
335
|
+
getComponents() {
|
|
336
|
+
if (this.customComponents.size === 0) {
|
|
337
|
+
return { ...this.baseComponents };
|
|
338
|
+
}
|
|
339
|
+
const merged = { ...this.baseComponents };
|
|
340
|
+
for (const [key, component] of this.customComponents) {
|
|
341
|
+
merged[key] = component;
|
|
342
|
+
}
|
|
343
|
+
return merged;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Register or replace custom MDX components
|
|
347
|
+
*/
|
|
348
|
+
registerComponents(components, options) {
|
|
349
|
+
if (options?.replace) {
|
|
350
|
+
this.customComponents.clear();
|
|
351
|
+
}
|
|
352
|
+
for (const [key, component] of Object.entries(components)) {
|
|
353
|
+
if (!component) continue;
|
|
354
|
+
this.customComponents.set(key, component);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Get base MDX components
|
|
359
|
+
*/
|
|
360
|
+
createBaseComponents() {
|
|
361
|
+
return {
|
|
362
|
+
// Standard HTML elements
|
|
363
|
+
h1: withClassName("h1", "mdx-h1"),
|
|
364
|
+
h2: withClassName("h2", "mdx-h2"),
|
|
365
|
+
h3: withClassName("h3", "mdx-h3"),
|
|
366
|
+
h4: withClassName("h4", "mdx-h4"),
|
|
367
|
+
h5: withClassName("h5", "mdx-h5"),
|
|
368
|
+
h6: withClassName("h6", "mdx-h6"),
|
|
369
|
+
p: withClassName("p", "mdx-p"),
|
|
370
|
+
strong: withClassName("strong", "mdx-strong"),
|
|
371
|
+
em: withClassName("em", "mdx-em"),
|
|
372
|
+
code: withClassName("code", "mdx-code"),
|
|
373
|
+
pre: withClassName("pre", "mdx-pre"),
|
|
374
|
+
ul: withClassName("ul", "mdx-ul"),
|
|
375
|
+
ol: withClassName("ol", "mdx-ol"),
|
|
376
|
+
li: withClassName("li", "mdx-li"),
|
|
377
|
+
blockquote: withClassName("blockquote", "mdx-blockquote"),
|
|
378
|
+
a: (props) => import_react3.default.createElement("a", {
|
|
379
|
+
className: "mdx-link",
|
|
380
|
+
target: props.href?.startsWith("http") ? "_blank" : void 0,
|
|
381
|
+
rel: props.href?.startsWith("http") ? "noopener noreferrer" : void 0,
|
|
382
|
+
...props
|
|
383
|
+
}),
|
|
384
|
+
img: (props) => import_react3.default.createElement("img", { className: "mdx-img", ...props }),
|
|
385
|
+
table: withClassName("table", "mdx-table"),
|
|
386
|
+
thead: withClassName("thead", "mdx-thead"),
|
|
387
|
+
tbody: withClassName("tbody", "mdx-tbody"),
|
|
388
|
+
tr: withClassName("tr", "mdx-tr"),
|
|
389
|
+
th: withClassName("th", "mdx-th"),
|
|
390
|
+
td: withClassName("td", "mdx-td")
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Clear component cache
|
|
395
|
+
*/
|
|
396
|
+
clearCache() {
|
|
397
|
+
this.components.clear();
|
|
398
|
+
}
|
|
399
|
+
clearCustomComponents() {
|
|
400
|
+
this.customComponents.clear();
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
globalMDXClient = null;
|
|
404
|
+
globalComponentFactory = null;
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// src/server.tsx
|
|
409
|
+
var server_exports = {};
|
|
410
|
+
__export(server_exports, {
|
|
411
|
+
ComponentRegistry: () => ComponentRegistry,
|
|
412
|
+
MarkdownBlocksRenderer: () => MarkdownBlocksRenderer,
|
|
413
|
+
createRendererStore: () => createRendererStore
|
|
414
|
+
});
|
|
415
|
+
module.exports = __toCommonJS(server_exports);
|
|
416
|
+
|
|
417
|
+
// src/renderer/blocks-renderer.tsx
|
|
418
|
+
var import_react11 = __toESM(require("react"), 1);
|
|
419
|
+
var import_core6 = require("@stream-mdx/core");
|
|
420
|
+
|
|
421
|
+
// src/renderer/hooks.ts
|
|
422
|
+
var import_react = require("react");
|
|
423
|
+
function useRendererNode(store, id) {
|
|
424
|
+
const selector = (0, import_react.useMemo)(() => {
|
|
425
|
+
return () => store.getNodeWithVersion(id);
|
|
426
|
+
}, [store, id]);
|
|
427
|
+
const snapshot = (0, import_react.useSyncExternalStore)(store.subscribe, selector, selector);
|
|
428
|
+
return snapshot.node;
|
|
429
|
+
}
|
|
430
|
+
function useRendererChildren(store, id) {
|
|
431
|
+
const selector = (0, import_react.useMemo)(() => {
|
|
432
|
+
return () => store.getChildrenWithVersion(id);
|
|
433
|
+
}, [store, id]);
|
|
434
|
+
const snapshot = (0, import_react.useSyncExternalStore)(store.subscribe, selector, selector);
|
|
435
|
+
return snapshot.children;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// src/renderer/node-views.tsx
|
|
439
|
+
var import_core5 = require("@stream-mdx/core");
|
|
440
|
+
var import_react10 = __toESM(require("react"), 1);
|
|
441
|
+
|
|
442
|
+
// src/components/index.ts
|
|
443
|
+
var import_core = require("@stream-mdx/core");
|
|
444
|
+
|
|
445
|
+
// src/mdx-hydration-metrics.ts
|
|
446
|
+
var entries = /* @__PURE__ */ new Map();
|
|
447
|
+
var lastHydrationMs = null;
|
|
448
|
+
function now() {
|
|
449
|
+
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
450
|
+
return performance.now();
|
|
451
|
+
}
|
|
452
|
+
return Date.now();
|
|
453
|
+
}
|
|
454
|
+
function markMdxHydrationStart(id) {
|
|
455
|
+
if (!id) return;
|
|
456
|
+
const entry = entries.get(id) ?? { status: "compiled" };
|
|
457
|
+
if (entry.startedAt === void 0) {
|
|
458
|
+
entry.startedAt = now();
|
|
459
|
+
}
|
|
460
|
+
entry.status = "compiled";
|
|
461
|
+
entries.set(id, entry);
|
|
462
|
+
}
|
|
463
|
+
function markMdxHydrated(id) {
|
|
464
|
+
if (!id) return;
|
|
465
|
+
const entry = entries.get(id) ?? { status: "hydrated" };
|
|
466
|
+
if (entry.startedAt === void 0) {
|
|
467
|
+
entry.startedAt = now();
|
|
468
|
+
}
|
|
469
|
+
entry.hydratedAt = now();
|
|
470
|
+
entry.status = "hydrated";
|
|
471
|
+
entries.set(id, entry);
|
|
472
|
+
lastHydrationMs = entry.startedAt ? entry.hydratedAt - entry.startedAt : null;
|
|
473
|
+
}
|
|
474
|
+
function markMdxHydrationError(id) {
|
|
475
|
+
if (!id) return;
|
|
476
|
+
const entry = entries.get(id) ?? { status: "error" };
|
|
477
|
+
entry.status = "error";
|
|
478
|
+
entries.set(id, entry);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// src/mdx-hydration-context.ts
|
|
482
|
+
var import_react2 = __toESM(require("react"), 1);
|
|
483
|
+
var MdxHydrationContext = import_react2.default.createContext({ controller: null });
|
|
484
|
+
|
|
485
|
+
// src/mdx-runtime.ts
|
|
486
|
+
var mdxRuntimePromise = null;
|
|
487
|
+
function loadMdxRuntime() {
|
|
488
|
+
if (!mdxRuntimePromise) {
|
|
489
|
+
mdxRuntimePromise = Promise.resolve().then(() => (init_mdx_client(), mdx_client_exports));
|
|
490
|
+
}
|
|
491
|
+
return mdxRuntimePromise;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// src/renderer/use-deferred-render.ts
|
|
495
|
+
var import_react4 = require("react");
|
|
496
|
+
function useDeferredRender(ref, options) {
|
|
497
|
+
const enabled = options?.enabled ?? true;
|
|
498
|
+
const [shouldRender, setShouldRender] = (0, import_react4.useState)(!enabled);
|
|
499
|
+
(0, import_react4.useEffect)(() => {
|
|
500
|
+
if (!enabled) {
|
|
501
|
+
setShouldRender(true);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
const target = ref.current;
|
|
505
|
+
if (!target) {
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
let cancelled = false;
|
|
509
|
+
let idleId = null;
|
|
510
|
+
let timerId = null;
|
|
511
|
+
const scheduleRender = () => {
|
|
512
|
+
if (cancelled) return;
|
|
513
|
+
setShouldRender(true);
|
|
514
|
+
};
|
|
515
|
+
const runWithIdle = () => {
|
|
516
|
+
if (typeof globalThis.requestIdleCallback === "function") {
|
|
517
|
+
idleId = globalThis.requestIdleCallback(scheduleRender, { timeout: options?.idleTimeoutMs ?? 200 });
|
|
518
|
+
} else {
|
|
519
|
+
timerId = window.setTimeout(scheduleRender, options?.idleTimeoutMs ?? 200);
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
const observer = new IntersectionObserver(
|
|
523
|
+
(entries2) => {
|
|
524
|
+
for (const entry of entries2) {
|
|
525
|
+
if (entry.isIntersecting || entry.intersectionRatio > 0) {
|
|
526
|
+
observer.disconnect();
|
|
527
|
+
const debounceMs = options?.debounceMs ?? 80;
|
|
528
|
+
timerId = window.setTimeout(runWithIdle, debounceMs);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
},
|
|
533
|
+
{ rootMargin: options?.rootMargin ?? "200px 0px" }
|
|
534
|
+
);
|
|
535
|
+
observer.observe(target);
|
|
536
|
+
return () => {
|
|
537
|
+
cancelled = true;
|
|
538
|
+
observer.disconnect();
|
|
539
|
+
if (idleId !== null && typeof globalThis.cancelIdleCallback === "function") {
|
|
540
|
+
globalThis.cancelIdleCallback(idleId);
|
|
541
|
+
}
|
|
542
|
+
if (timerId !== null) {
|
|
543
|
+
window.clearTimeout(timerId);
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
}, [enabled, options?.debounceMs, options?.idleTimeoutMs, options?.rootMargin, ref]);
|
|
547
|
+
return shouldRender;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// src/components/index.ts
|
|
551
|
+
var import_core2 = require("@stream-mdx/core");
|
|
552
|
+
var import_react6 = __toESM(require("react"), 1);
|
|
553
|
+
|
|
554
|
+
// src/utils/inline-html.ts
|
|
555
|
+
var import_react5 = __toESM(require("react"), 1);
|
|
556
|
+
var BACKTICK_CONTENT = /^`([\s\S]+)`$/;
|
|
557
|
+
var DEFAULT_INLINE_HTML_RENDERERS = {
|
|
558
|
+
kbd: (descriptor, ctx) => {
|
|
559
|
+
const trimmed = descriptor.rawInner.trim();
|
|
560
|
+
const match = BACKTICK_CONTENT.exec(trimmed);
|
|
561
|
+
if (match) {
|
|
562
|
+
const codeText = decodeHtmlEntities(match[1]);
|
|
563
|
+
const props = mapAttributesToProps(descriptor.attributes);
|
|
564
|
+
if (ctx.key !== void 0 && ctx.key !== null) {
|
|
565
|
+
props.key = ctx.key;
|
|
566
|
+
}
|
|
567
|
+
return import_react5.default.createElement("kbd", props, import_react5.default.createElement("code", { className: "inline-code" }, codeText));
|
|
568
|
+
}
|
|
569
|
+
return ctx.defaultRender();
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
function renderInlineHtmlSegment(raw, sanitized, options = {}) {
|
|
573
|
+
const descriptor = parseInlineHtmlDescriptor(raw, sanitized);
|
|
574
|
+
if (!descriptor) {
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
const className = options.className ?? "markdown-inline-html";
|
|
578
|
+
const isInlineTag = INLINE_ELEMENTS.has(descriptor.tagName);
|
|
579
|
+
const defaultRender = () => {
|
|
580
|
+
if (!descriptor.sanitized || descriptor.sanitized.length === 0) {
|
|
581
|
+
if (!descriptor.text) {
|
|
582
|
+
return null;
|
|
583
|
+
}
|
|
584
|
+
if (options.key !== void 0 && options.key !== null) {
|
|
585
|
+
return import_react5.default.createElement(import_react5.default.Fragment, { key: options.key }, descriptor.text);
|
|
586
|
+
}
|
|
587
|
+
return import_react5.default.createElement(import_react5.default.Fragment, {}, descriptor.text);
|
|
588
|
+
}
|
|
589
|
+
if (isInlineTag) {
|
|
590
|
+
const parsedElement = renderSanitizedHtmlTree(descriptor.sanitized, options.key);
|
|
591
|
+
if (parsedElement) {
|
|
592
|
+
return parsedElement;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
const props = {
|
|
596
|
+
className,
|
|
597
|
+
dangerouslySetInnerHTML: { __html: descriptor.sanitized }
|
|
598
|
+
};
|
|
599
|
+
if (options.key !== void 0 && options.key !== null) {
|
|
600
|
+
props.key = options.key;
|
|
601
|
+
}
|
|
602
|
+
return import_react5.default.createElement("span", props);
|
|
603
|
+
};
|
|
604
|
+
const allRenderers = options.renderers ? { ...DEFAULT_INLINE_HTML_RENDERERS, ...options.renderers } : DEFAULT_INLINE_HTML_RENDERERS;
|
|
605
|
+
const renderer = allRenderers[descriptor.tagName];
|
|
606
|
+
if (renderer) {
|
|
607
|
+
const rendered = renderer(descriptor, { key: options.key, defaultRender });
|
|
608
|
+
if (rendered) {
|
|
609
|
+
if (options.key !== void 0 && options.key !== null && import_react5.default.isValidElement(rendered) && rendered.key === null) {
|
|
610
|
+
return import_react5.default.cloneElement(rendered, { key: options.key });
|
|
611
|
+
}
|
|
612
|
+
return rendered;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return defaultRender();
|
|
616
|
+
}
|
|
617
|
+
function parseInlineHtmlDescriptor(raw, sanitized) {
|
|
618
|
+
const normalizedRaw = typeof raw === "string" ? raw.trim() : "";
|
|
619
|
+
const normalizedSanitized = typeof sanitized === "string" && sanitized.length > 0 ? sanitized.trim() : normalizedRaw;
|
|
620
|
+
if (!normalizedSanitized) {
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
623
|
+
const match = normalizedSanitized.match(/^<([A-Za-z][\w:-]*)(\s[^>]*)?>([\s\S]*)<\/\1>$/);
|
|
624
|
+
if (!match) {
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
const [, tagNameRaw, attrString = "", sanitizedInner = ""] = match;
|
|
628
|
+
const tagName = tagNameRaw.toLowerCase();
|
|
629
|
+
const attributes = parseAttributes(attrString);
|
|
630
|
+
let rawInner = "";
|
|
631
|
+
if (normalizedRaw) {
|
|
632
|
+
const rawMatch = normalizedRaw.match(/^<([A-Za-z][\w:-]*)(\s[^>]*)?>([\s\S]*)<\/\1>$/);
|
|
633
|
+
rawInner = rawMatch ? rawMatch[3] ?? "" : normalizedRaw;
|
|
634
|
+
}
|
|
635
|
+
const text = extractPlainText(rawInner || sanitizedInner);
|
|
636
|
+
return {
|
|
637
|
+
tagName,
|
|
638
|
+
attributes,
|
|
639
|
+
raw: normalizedRaw || normalizedSanitized,
|
|
640
|
+
sanitized: normalizedSanitized,
|
|
641
|
+
rawInner,
|
|
642
|
+
sanitizedInner,
|
|
643
|
+
text
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
function parseAttributes(input) {
|
|
647
|
+
const attrs = {};
|
|
648
|
+
if (!input) return attrs;
|
|
649
|
+
const attrPattern = /([A-Za-z_:][\w:.\-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
|
|
650
|
+
let match = attrPattern.exec(input);
|
|
651
|
+
while (match !== null) {
|
|
652
|
+
const name = match[1];
|
|
653
|
+
if (!name) continue;
|
|
654
|
+
const value = match[2] ?? match[3] ?? match[4] ?? "";
|
|
655
|
+
attrs[name] = value;
|
|
656
|
+
match = attrPattern.exec(input);
|
|
657
|
+
}
|
|
658
|
+
return attrs;
|
|
659
|
+
}
|
|
660
|
+
function mapAttributesToProps(attrs) {
|
|
661
|
+
const props = {};
|
|
662
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
663
|
+
if (key === "class") {
|
|
664
|
+
props.className = value;
|
|
665
|
+
} else if (key === "style") {
|
|
666
|
+
props.style = parseStyleAttribute(value);
|
|
667
|
+
} else if (key === "tabindex" || key === "tabIndex") {
|
|
668
|
+
const normalized = normalizeTabIndex(value);
|
|
669
|
+
if (normalized !== void 0) {
|
|
670
|
+
props.tabIndex = normalized;
|
|
671
|
+
}
|
|
672
|
+
} else {
|
|
673
|
+
props[key] = value;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return props;
|
|
677
|
+
}
|
|
678
|
+
function parseStyleAttribute(value) {
|
|
679
|
+
const style = {};
|
|
680
|
+
if (!value) return style;
|
|
681
|
+
const pairs = value.split(";");
|
|
682
|
+
for (const pair of pairs) {
|
|
683
|
+
if (!pair.trim()) continue;
|
|
684
|
+
const [property, rawVal] = pair.split(":");
|
|
685
|
+
if (!property || rawVal === void 0) continue;
|
|
686
|
+
const camelProp = property.trim().replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
|
687
|
+
style[camelProp] = rawVal.trim();
|
|
688
|
+
}
|
|
689
|
+
return style;
|
|
690
|
+
}
|
|
691
|
+
function normalizeTabIndex(value) {
|
|
692
|
+
const numeric = Number(value);
|
|
693
|
+
return Number.isNaN(numeric) ? void 0 : numeric;
|
|
694
|
+
}
|
|
695
|
+
function decodeHtmlEntities(value) {
|
|
696
|
+
return value.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&").replace(/"/g, '"').replace(/'/g, "'");
|
|
697
|
+
}
|
|
698
|
+
function extractPlainText(value) {
|
|
699
|
+
if (!value) return "";
|
|
700
|
+
const withoutTags = value.replace(/<\/?[^>]+>/g, "");
|
|
701
|
+
return decodeHtmlEntities(withoutTags);
|
|
702
|
+
}
|
|
703
|
+
function renderSanitizedHtmlTree(html, key) {
|
|
704
|
+
if (typeof DOMParser === "undefined") {
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
try {
|
|
708
|
+
const parser = new DOMParser();
|
|
709
|
+
const doc = parser.parseFromString(html, "text/html");
|
|
710
|
+
const body = doc.body;
|
|
711
|
+
const nodes = Array.from(body.childNodes);
|
|
712
|
+
if (nodes.length === 0) {
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
const baseKey = typeof key === "string" || typeof key === "number" ? String(key) : "inline-html";
|
|
716
|
+
const children = nodes.map((node, index) => convertDomNode(node, `${baseKey}-${index}`)).filter((child) => child !== null && child !== void 0);
|
|
717
|
+
if (children.length === 0) {
|
|
718
|
+
return null;
|
|
719
|
+
}
|
|
720
|
+
if (children.length === 1) {
|
|
721
|
+
const [child] = children;
|
|
722
|
+
if (import_react5.default.isValidElement(child)) {
|
|
723
|
+
if (key !== void 0 && key !== null) {
|
|
724
|
+
return import_react5.default.cloneElement(child, { key });
|
|
725
|
+
}
|
|
726
|
+
return child;
|
|
727
|
+
}
|
|
728
|
+
if (typeof child === "string" || typeof child === "number") {
|
|
729
|
+
if (key !== void 0 && key !== null) {
|
|
730
|
+
return import_react5.default.createElement(import_react5.default.Fragment, { key }, child);
|
|
731
|
+
}
|
|
732
|
+
return import_react5.default.createElement(import_react5.default.Fragment, {}, child);
|
|
733
|
+
}
|
|
734
|
+
return import_react5.default.createElement(import_react5.default.Fragment, { key }, child);
|
|
735
|
+
}
|
|
736
|
+
return import_react5.default.createElement(import_react5.default.Fragment, { key }, ...children);
|
|
737
|
+
} catch {
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
function convertDomNode(node, key) {
|
|
742
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
743
|
+
return node.textContent ?? "";
|
|
744
|
+
}
|
|
745
|
+
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
746
|
+
return null;
|
|
747
|
+
}
|
|
748
|
+
const element = node;
|
|
749
|
+
const tagName = element.tagName.toLowerCase();
|
|
750
|
+
const attrs = getElementAttributes(element);
|
|
751
|
+
const props = mapAttributesToProps(attrs);
|
|
752
|
+
props.key = key;
|
|
753
|
+
const children = [];
|
|
754
|
+
element.childNodes.forEach((child, index) => {
|
|
755
|
+
const converted = convertDomNode(child, `${key}-${index}`);
|
|
756
|
+
if (converted !== null && converted !== void 0) {
|
|
757
|
+
children.push(converted);
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
if (children.length === 0) {
|
|
761
|
+
return import_react5.default.createElement(tagName, props);
|
|
762
|
+
}
|
|
763
|
+
return import_react5.default.createElement(tagName, props, ...children);
|
|
764
|
+
}
|
|
765
|
+
function getElementAttributes(element) {
|
|
766
|
+
const attrs = {};
|
|
767
|
+
for (const attr of Array.from(element.attributes)) {
|
|
768
|
+
attrs[attr.name] = attr.value;
|
|
769
|
+
}
|
|
770
|
+
return attrs;
|
|
771
|
+
}
|
|
772
|
+
var INLINE_ELEMENTS = /* @__PURE__ */ new Set([
|
|
773
|
+
"a",
|
|
774
|
+
"abbr",
|
|
775
|
+
"b",
|
|
776
|
+
"bdi",
|
|
777
|
+
"bdo",
|
|
778
|
+
"br",
|
|
779
|
+
"cite",
|
|
780
|
+
"code",
|
|
781
|
+
"data",
|
|
782
|
+
"dfn",
|
|
783
|
+
"em",
|
|
784
|
+
"i",
|
|
785
|
+
"kbd",
|
|
786
|
+
"mark",
|
|
787
|
+
"q",
|
|
788
|
+
"rp",
|
|
789
|
+
"rt",
|
|
790
|
+
"ruby",
|
|
791
|
+
"s",
|
|
792
|
+
"samp",
|
|
793
|
+
"small",
|
|
794
|
+
"span",
|
|
795
|
+
"strong",
|
|
796
|
+
"sub",
|
|
797
|
+
"sup",
|
|
798
|
+
"time",
|
|
799
|
+
"u",
|
|
800
|
+
"var",
|
|
801
|
+
// MathML inline elements commonly emitted by KaTeX
|
|
802
|
+
"annotation",
|
|
803
|
+
"semantics",
|
|
804
|
+
"mtext",
|
|
805
|
+
"mn",
|
|
806
|
+
"mo",
|
|
807
|
+
"mi",
|
|
808
|
+
"mspace",
|
|
809
|
+
"mrow",
|
|
810
|
+
"msup",
|
|
811
|
+
"msub",
|
|
812
|
+
"msubsup",
|
|
813
|
+
"munder",
|
|
814
|
+
"mover",
|
|
815
|
+
"munderover",
|
|
816
|
+
"msqrt",
|
|
817
|
+
"mroot",
|
|
818
|
+
"mtable",
|
|
819
|
+
"mtr",
|
|
820
|
+
"mtd"
|
|
821
|
+
]);
|
|
822
|
+
|
|
823
|
+
// src/components/index.ts
|
|
824
|
+
var MdxRuntimeBoundary = class extends import_react6.default.Component {
|
|
825
|
+
constructor() {
|
|
826
|
+
super(...arguments);
|
|
827
|
+
this.state = { hasError: false };
|
|
828
|
+
}
|
|
829
|
+
static getDerivedStateFromError() {
|
|
830
|
+
return { hasError: true };
|
|
831
|
+
}
|
|
832
|
+
componentDidCatch(error) {
|
|
833
|
+
this.props.onError(error);
|
|
834
|
+
}
|
|
835
|
+
componentDidUpdate(prevProps) {
|
|
836
|
+
if (prevProps.resetKey !== this.props.resetKey && this.state.hasError) {
|
|
837
|
+
this.setState({ hasError: false });
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
render() {
|
|
841
|
+
if (this.state.hasError) {
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
return this.props.children;
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
function renderInlineNodes(nodes, components) {
|
|
848
|
+
return nodes.map((node, index) => {
|
|
849
|
+
const key = `${node.kind}-${index}`;
|
|
850
|
+
switch (node.kind) {
|
|
851
|
+
case "text": {
|
|
852
|
+
const textComponent = components.text;
|
|
853
|
+
if (textComponent && textComponent !== defaultInlineComponents.text) {
|
|
854
|
+
return import_react6.default.createElement(textComponent, { key, text: node.text });
|
|
855
|
+
}
|
|
856
|
+
return import_react6.default.createElement(import_react6.default.Fragment, { key, children: node.text });
|
|
857
|
+
}
|
|
858
|
+
case "strong":
|
|
859
|
+
return import_react6.default.createElement(components.strong, {
|
|
860
|
+
key,
|
|
861
|
+
children: renderInlineNodes(node.children, components)
|
|
862
|
+
});
|
|
863
|
+
case "em":
|
|
864
|
+
return import_react6.default.createElement(components.em, {
|
|
865
|
+
key,
|
|
866
|
+
children: renderInlineNodes(node.children, components)
|
|
867
|
+
});
|
|
868
|
+
case "strike": {
|
|
869
|
+
const StrikeComponent = components.strike ?? ((props) => import_react6.default.createElement("del", {}, props.children));
|
|
870
|
+
return import_react6.default.createElement(StrikeComponent, {
|
|
871
|
+
key,
|
|
872
|
+
children: renderInlineNodes(node.children, components)
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
case "code":
|
|
876
|
+
return import_react6.default.createElement(components.code, { key, text: node.text });
|
|
877
|
+
case "link":
|
|
878
|
+
return import_react6.default.createElement(
|
|
879
|
+
components.link,
|
|
880
|
+
{
|
|
881
|
+
key,
|
|
882
|
+
href: node.href,
|
|
883
|
+
title: node.title,
|
|
884
|
+
children: renderInlineNodes(node.children, components)
|
|
885
|
+
}
|
|
886
|
+
);
|
|
887
|
+
case "image":
|
|
888
|
+
return import_react6.default.createElement(components.image, {
|
|
889
|
+
key,
|
|
890
|
+
src: node.src,
|
|
891
|
+
alt: node.alt,
|
|
892
|
+
title: node.title
|
|
893
|
+
});
|
|
894
|
+
case "br":
|
|
895
|
+
return import_react6.default.createElement(import_react6.default.Fragment, { key }, import_react6.default.createElement(components.br, {}));
|
|
896
|
+
default: {
|
|
897
|
+
const component = components[node.kind];
|
|
898
|
+
if (component) {
|
|
899
|
+
return import_react6.default.createElement(component, { key, ...node });
|
|
900
|
+
}
|
|
901
|
+
return null;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
var defaultInlineComponents = {
|
|
907
|
+
text: ({ text }) => import_react6.default.createElement(import_react6.default.Fragment, {}, text),
|
|
908
|
+
strong: ({ children }) => import_react6.default.createElement("strong", {}, children),
|
|
909
|
+
em: ({ children }) => import_react6.default.createElement("em", {}, children),
|
|
910
|
+
strike: ({ children }) => import_react6.default.createElement("del", {}, children),
|
|
911
|
+
code: ({ text }) => import_react6.default.createElement(
|
|
912
|
+
"code",
|
|
913
|
+
{
|
|
914
|
+
className: "inline-code"
|
|
915
|
+
},
|
|
916
|
+
text
|
|
917
|
+
),
|
|
918
|
+
link: ({ href, title, children }) => import_react6.default.createElement(
|
|
919
|
+
"a",
|
|
920
|
+
{
|
|
921
|
+
href,
|
|
922
|
+
title,
|
|
923
|
+
className: "markdown-link",
|
|
924
|
+
target: href?.startsWith("http") ? "_blank" : void 0,
|
|
925
|
+
rel: href?.startsWith("http") ? "noopener noreferrer" : void 0
|
|
926
|
+
},
|
|
927
|
+
children
|
|
928
|
+
),
|
|
929
|
+
image: ({ src, alt, title }) => import_react6.default.createElement("img", {
|
|
930
|
+
src,
|
|
931
|
+
alt,
|
|
932
|
+
title,
|
|
933
|
+
className: "markdown-image"
|
|
934
|
+
}),
|
|
935
|
+
br: () => import_react6.default.createElement("br"),
|
|
936
|
+
// Custom extensible components
|
|
937
|
+
mention: ({ handle }) => import_react6.default.createElement(
|
|
938
|
+
"span",
|
|
939
|
+
{
|
|
940
|
+
className: "mention",
|
|
941
|
+
"data-handle": handle
|
|
942
|
+
},
|
|
943
|
+
`@${handle}`
|
|
944
|
+
),
|
|
945
|
+
citation: ({ id }) => import_react6.default.createElement(
|
|
946
|
+
"span",
|
|
947
|
+
{
|
|
948
|
+
className: "citation",
|
|
949
|
+
"data-citation-id": id
|
|
950
|
+
},
|
|
951
|
+
`[${id}]`
|
|
952
|
+
),
|
|
953
|
+
"math-inline": ({ tex }) => import_react6.default.createElement(
|
|
954
|
+
"span",
|
|
955
|
+
{
|
|
956
|
+
className: "math-inline",
|
|
957
|
+
"data-tex": tex
|
|
958
|
+
},
|
|
959
|
+
`$${tex}$`
|
|
960
|
+
),
|
|
961
|
+
"math-display": ({ tex }) => import_react6.default.createElement(
|
|
962
|
+
"div",
|
|
963
|
+
{
|
|
964
|
+
className: "math-display markdown-math-display",
|
|
965
|
+
"data-tex": tex,
|
|
966
|
+
style: { overflowX: "auto" }
|
|
967
|
+
},
|
|
968
|
+
import_react6.default.createElement("span", { className: "math-display-content" }, `$$${tex}$$`)
|
|
969
|
+
),
|
|
970
|
+
// Footnote inline reference (superscript anchor)
|
|
971
|
+
"footnote-ref": ({ label, number }) => {
|
|
972
|
+
const n = number ?? "?";
|
|
973
|
+
const href = number ? `#fn:${n}` : void 0;
|
|
974
|
+
const id = number ? `fnref:${n}` : void 0;
|
|
975
|
+
return import_react6.default.createElement(
|
|
976
|
+
"sup",
|
|
977
|
+
{ className: "footnote-ref" },
|
|
978
|
+
import_react6.default.createElement("a", { href, id, "data-label": label, style: { color: "var(--foreground)", textDecoration: "underline" } }, String(n))
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
var defaultBlockComponents = {
|
|
983
|
+
paragraph: ({ inlines, raw, meta, children }) => {
|
|
984
|
+
if (children !== void 0) {
|
|
985
|
+
return import_react6.default.createElement(
|
|
986
|
+
"p",
|
|
987
|
+
{
|
|
988
|
+
className: "markdown-paragraph"
|
|
989
|
+
},
|
|
990
|
+
children
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
const segments = Array.isArray(meta?.mixedSegments) ? meta?.mixedSegments : void 0;
|
|
994
|
+
if (segments && segments.length > 0) {
|
|
995
|
+
const structured = renderParagraphMixedSegments(segments, defaultInlineComponents, DEFAULT_INLINE_HTML_RENDERERS);
|
|
996
|
+
if (structured.length === 1) {
|
|
997
|
+
return structured[0];
|
|
998
|
+
}
|
|
999
|
+
return import_react6.default.createElement(import_react6.default.Fragment, {}, ...structured);
|
|
1000
|
+
}
|
|
1001
|
+
const filteredInlines = inlines && raw && inlines.length > 0 ? inlines.filter((node) => !(node.kind === "text" && node.text === raw)) : inlines ?? [];
|
|
1002
|
+
const effectiveInlines = filteredInlines.length > 0 ? filteredInlines : raw ? [
|
|
1003
|
+
{
|
|
1004
|
+
kind: "text",
|
|
1005
|
+
text: raw
|
|
1006
|
+
}
|
|
1007
|
+
] : [];
|
|
1008
|
+
return import_react6.default.createElement(
|
|
1009
|
+
"p",
|
|
1010
|
+
{
|
|
1011
|
+
className: "markdown-paragraph"
|
|
1012
|
+
},
|
|
1013
|
+
effectiveInlines.length > 0 ? renderInlineNodes(effectiveInlines, defaultInlineComponents) : raw ?? ""
|
|
1014
|
+
);
|
|
1015
|
+
},
|
|
1016
|
+
heading: ({ level, inlines, text, meta }) => {
|
|
1017
|
+
const tag = `h${level}`;
|
|
1018
|
+
const onlyPlainText = Array.isArray(inlines) && inlines.length > 0 && inlines.every((node) => node.kind === "text");
|
|
1019
|
+
const children = onlyPlainText ? inlines.map((node) => node.kind === "text" ? node.text : "").join("") : renderInlineNodes(inlines, defaultInlineComponents);
|
|
1020
|
+
const headingId = typeof meta?.headingId === "string" && meta.headingId.length > 0 ? meta.headingId : void 0;
|
|
1021
|
+
return import_react6.default.createElement(
|
|
1022
|
+
tag,
|
|
1023
|
+
{
|
|
1024
|
+
className: `markdown-heading markdown-h${level}`,
|
|
1025
|
+
...text ? { "data-heading-text": text } : {},
|
|
1026
|
+
...headingId ? { id: headingId, "data-heading-id": headingId } : {}
|
|
1027
|
+
},
|
|
1028
|
+
children
|
|
1029
|
+
);
|
|
1030
|
+
},
|
|
1031
|
+
code: ({ html, meta, lines, lang, preAttrs, codeAttrs }) => {
|
|
1032
|
+
const language = typeof lang === "string" ? lang : typeof meta?.lang === "string" ? String(meta.lang) : "text";
|
|
1033
|
+
if (html) {
|
|
1034
|
+
return import_react6.default.createElement("div", {
|
|
1035
|
+
className: `markdown-code-block language-${language || "text"}`,
|
|
1036
|
+
/* biome-ignore lint/security/noDangerouslySetInnerHtml: highlighted HTML is generated by our sanitizer */
|
|
1037
|
+
dangerouslySetInnerHTML: { __html: html }
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
const code = meta?.code || "";
|
|
1041
|
+
if (lines && lines.length > 0) {
|
|
1042
|
+
const preAttributes = attrsToProps(preAttrs);
|
|
1043
|
+
const codeAttributes = attrsToProps(codeAttrs);
|
|
1044
|
+
return import_react6.default.createElement(
|
|
1045
|
+
"pre",
|
|
1046
|
+
{
|
|
1047
|
+
...preAttributes,
|
|
1048
|
+
className: preAttributes.className ? `${preAttributes.className} markdown-code-block language-${language || "text"}` : `markdown-code-block language-${language || "text"}`
|
|
1049
|
+
},
|
|
1050
|
+
import_react6.default.createElement(
|
|
1051
|
+
"code",
|
|
1052
|
+
{
|
|
1053
|
+
...codeAttributes,
|
|
1054
|
+
className: codeAttributes.className ? `${codeAttributes.className} language-${language || "text"}` : `language-${language || "text"}`
|
|
1055
|
+
},
|
|
1056
|
+
lines.map(
|
|
1057
|
+
(line) => import_react6.default.createElement("span", {
|
|
1058
|
+
key: line.id,
|
|
1059
|
+
className: "line",
|
|
1060
|
+
/* biome-ignore lint/security/noDangerouslySetInnerHtml: line HTML is sanitized server-side */
|
|
1061
|
+
dangerouslySetInnerHTML: { __html: line.html ?? escapeHtml(line.text) }
|
|
1062
|
+
})
|
|
1063
|
+
)
|
|
1064
|
+
)
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
return import_react6.default.createElement("pre", {
|
|
1068
|
+
className: `markdown-code-block language-${language || "text"}`,
|
|
1069
|
+
children: code
|
|
1070
|
+
});
|
|
1071
|
+
},
|
|
1072
|
+
blockquote: ({ inlines, renderedContent }) => import_react6.default.createElement(
|
|
1073
|
+
"blockquote",
|
|
1074
|
+
{
|
|
1075
|
+
className: "markdown-blockquote"
|
|
1076
|
+
},
|
|
1077
|
+
renderedContent ?? renderInlineNodes(inlines, defaultInlineComponents)
|
|
1078
|
+
),
|
|
1079
|
+
hr: (props) => import_react6.default.createElement("hr", props),
|
|
1080
|
+
list: ({ ordered, items }) => {
|
|
1081
|
+
if (items.length === 0) {
|
|
1082
|
+
return null;
|
|
1083
|
+
}
|
|
1084
|
+
const tag = ordered ? "ol" : "ul";
|
|
1085
|
+
const listStyle = ordered ? {
|
|
1086
|
+
["--list-indent"]: `calc(2.5rem + ${String(items.length).length}ch)`
|
|
1087
|
+
} : void 0;
|
|
1088
|
+
const listItems = items.map(
|
|
1089
|
+
(item, index) => import_react6.default.createElement(
|
|
1090
|
+
"li",
|
|
1091
|
+
{
|
|
1092
|
+
key: index,
|
|
1093
|
+
className: "markdown-list-item",
|
|
1094
|
+
"data-counter-text": ordered ? `${index + 1}.` : void 0,
|
|
1095
|
+
"data-list-depth": 0
|
|
1096
|
+
},
|
|
1097
|
+
renderInlineNodes(item, defaultInlineComponents)
|
|
1098
|
+
)
|
|
1099
|
+
);
|
|
1100
|
+
return import_react6.default.createElement(
|
|
1101
|
+
tag,
|
|
1102
|
+
{
|
|
1103
|
+
className: `markdown-list ${ordered ? "ordered" : "unordered"}`,
|
|
1104
|
+
style: listStyle
|
|
1105
|
+
},
|
|
1106
|
+
listItems
|
|
1107
|
+
);
|
|
1108
|
+
},
|
|
1109
|
+
html: ({ __trustedHtml, elements }) => {
|
|
1110
|
+
const raw = typeof __trustedHtml === "string" ? __trustedHtml : __trustedHtml.toString();
|
|
1111
|
+
const htmlString = typeof window === "undefined" ? raw : (0, import_core.sanitizeHTML)(raw);
|
|
1112
|
+
if (typeof window !== "undefined" && elements) {
|
|
1113
|
+
const content = mapHtmlToReact(htmlString, elements);
|
|
1114
|
+
return import_react6.default.createElement("div", { className: "markdown-html" }, content);
|
|
1115
|
+
}
|
|
1116
|
+
return import_react6.default.createElement("div", {
|
|
1117
|
+
className: "markdown-html",
|
|
1118
|
+
/* biome-ignore lint/security/noDangerouslySetInnerHtml: htmlString is sanitized before injection */
|
|
1119
|
+
dangerouslySetInnerHTML: { __html: htmlString }
|
|
1120
|
+
});
|
|
1121
|
+
},
|
|
1122
|
+
mdx: ({ compiledRef, compiledModule, status, errorMessage }) => {
|
|
1123
|
+
const [Comp, setComp] = import_react6.default.useState(null);
|
|
1124
|
+
const [internalError, setInternalError] = import_react6.default.useState(null);
|
|
1125
|
+
const [renderError, setRenderError] = import_react6.default.useState(null);
|
|
1126
|
+
const [retryKey, setRetryKey] = import_react6.default.useState(0);
|
|
1127
|
+
const hydrationContext = import_react6.default.useContext(MdxHydrationContext);
|
|
1128
|
+
const hydrationController = hydrationContext?.controller ?? null;
|
|
1129
|
+
const hydrationOptions = hydrationContext?.options;
|
|
1130
|
+
const containerRef = import_react6.default.useRef(null);
|
|
1131
|
+
const deferHydration = hydrationOptions?.strategy === "visible";
|
|
1132
|
+
const shouldHydrate = useDeferredRender(
|
|
1133
|
+
containerRef,
|
|
1134
|
+
deferHydration ? {
|
|
1135
|
+
enabled: true,
|
|
1136
|
+
rootMargin: hydrationOptions?.rootMargin,
|
|
1137
|
+
idleTimeoutMs: hydrationOptions?.idleTimeoutMs,
|
|
1138
|
+
debounceMs: hydrationOptions?.debounceMs
|
|
1139
|
+
} : { enabled: false }
|
|
1140
|
+
);
|
|
1141
|
+
const resolvedId = compiledModule?.id ?? (compiledRef?.id && compiledRef.id !== "pending" ? compiledRef.id : void 0);
|
|
1142
|
+
const handleRetry = import_react6.default.useCallback(() => {
|
|
1143
|
+
setInternalError(null);
|
|
1144
|
+
setRenderError(null);
|
|
1145
|
+
setComp(null);
|
|
1146
|
+
setRetryKey((prev) => prev + 1);
|
|
1147
|
+
}, []);
|
|
1148
|
+
const handleRuntimeError = import_react6.default.useCallback(
|
|
1149
|
+
(error) => {
|
|
1150
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
1151
|
+
setRenderError(message2);
|
|
1152
|
+
markMdxHydrationError(resolvedId ?? compiledRef?.id);
|
|
1153
|
+
},
|
|
1154
|
+
[resolvedId, compiledRef?.id]
|
|
1155
|
+
);
|
|
1156
|
+
const effectiveStatus = status ? status : resolvedId ? "compiled" : "pending";
|
|
1157
|
+
const failureMessage = errorMessage ?? internalError ?? renderError ?? null;
|
|
1158
|
+
const moduleDependencies = compiledModule?.dependencies;
|
|
1159
|
+
import_react6.default.useEffect(() => {
|
|
1160
|
+
if (!shouldHydrate) {
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
if (status === "error") {
|
|
1164
|
+
markMdxHydrationError(resolvedId ?? compiledRef?.id);
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
let cancelled = false;
|
|
1168
|
+
async function ensure() {
|
|
1169
|
+
let permit = null;
|
|
1170
|
+
try {
|
|
1171
|
+
if (status === "error" || !resolvedId || resolvedId === "pending") {
|
|
1172
|
+
if (!cancelled) {
|
|
1173
|
+
setComp(null);
|
|
1174
|
+
setInternalError(null);
|
|
1175
|
+
}
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
if (hydrationController) {
|
|
1179
|
+
permit = await hydrationController.acquire();
|
|
1180
|
+
if (cancelled) {
|
|
1181
|
+
permit.release();
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
setInternalError(null);
|
|
1186
|
+
setRenderError(null);
|
|
1187
|
+
markMdxHydrationStart(resolvedId);
|
|
1188
|
+
const { getMDXComponentFactory: getMDXComponentFactory2, registerInlineMdxModule: registerInlineMdxModule2 } = await loadMdxRuntime();
|
|
1189
|
+
if (compiledModule?.code) {
|
|
1190
|
+
registerInlineMdxModule2({
|
|
1191
|
+
id: compiledModule.id,
|
|
1192
|
+
code: compiledModule.code,
|
|
1193
|
+
dependencies: moduleDependencies ?? []
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
const factory = getMDXComponentFactory2();
|
|
1197
|
+
const C = await factory.createComponent({ id: resolvedId });
|
|
1198
|
+
if (!cancelled) {
|
|
1199
|
+
setComp(() => C);
|
|
1200
|
+
setInternalError(null);
|
|
1201
|
+
markMdxHydrated(resolvedId);
|
|
1202
|
+
}
|
|
1203
|
+
} catch (error) {
|
|
1204
|
+
if (!cancelled) {
|
|
1205
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
1206
|
+
setInternalError(message2);
|
|
1207
|
+
markMdxHydrationError(resolvedId);
|
|
1208
|
+
}
|
|
1209
|
+
} finally {
|
|
1210
|
+
permit?.release();
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
ensure();
|
|
1214
|
+
return () => {
|
|
1215
|
+
cancelled = true;
|
|
1216
|
+
};
|
|
1217
|
+
}, [compiledModule?.id, compiledModule?.code, moduleDependencies, resolvedId, status, shouldHydrate, retryKey]);
|
|
1218
|
+
const compileMode = compiledModule?.source === "worker" || compiledRef?.id?.startsWith("worker:") ? "worker" : "server";
|
|
1219
|
+
const pendingId = resolvedId && resolvedId !== "pending" ? resolvedId : compiledRef?.id ?? "pending";
|
|
1220
|
+
const waitingForVisibility = deferHydration && !shouldHydrate && Boolean(resolvedId);
|
|
1221
|
+
const pendingLabel = waitingForVisibility ? "MDX ready (hydrate on view)\u2026" : compileMode === "worker" ? "Compiling MDX (client)\u2026" : "Compiling MDX (server)\u2026";
|
|
1222
|
+
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.");
|
|
1223
|
+
const allowRetry = Boolean(internalError || renderError);
|
|
1224
|
+
const retryButton = allowRetry ? import_react6.default.createElement(
|
|
1225
|
+
"button",
|
|
1226
|
+
{
|
|
1227
|
+
type: "button",
|
|
1228
|
+
className: "markdown-mdx-retry",
|
|
1229
|
+
onClick: handleRetry
|
|
1230
|
+
},
|
|
1231
|
+
"Retry hydration"
|
|
1232
|
+
) : null;
|
|
1233
|
+
const errorPanel = import_react6.default.createElement(
|
|
1234
|
+
import_react6.default.Fragment,
|
|
1235
|
+
null,
|
|
1236
|
+
import_react6.default.createElement("strong", null, "MDX failed"),
|
|
1237
|
+
import_react6.default.createElement("div", null, message)
|
|
1238
|
+
);
|
|
1239
|
+
if (Comp && !failureMessage) {
|
|
1240
|
+
return import_react6.default.createElement(
|
|
1241
|
+
"div",
|
|
1242
|
+
{
|
|
1243
|
+
className: "markdown-mdx",
|
|
1244
|
+
ref: containerRef,
|
|
1245
|
+
"data-mdx-ref": resolvedId ?? compiledRef?.id ?? "compiled",
|
|
1246
|
+
"data-mdx-status": failureMessage ? "error" : "compiled",
|
|
1247
|
+
"data-mdx-mode": compileMode
|
|
1248
|
+
},
|
|
1249
|
+
import_react6.default.createElement(
|
|
1250
|
+
MdxRuntimeBoundary,
|
|
1251
|
+
{ resetKey: retryKey, onError: handleRuntimeError },
|
|
1252
|
+
import_react6.default.createElement(Comp, {})
|
|
1253
|
+
)
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
if (failureMessage) {
|
|
1257
|
+
return import_react6.default.createElement(
|
|
1258
|
+
"div",
|
|
1259
|
+
{
|
|
1260
|
+
className: "markdown-mdx error",
|
|
1261
|
+
ref: containerRef,
|
|
1262
|
+
"data-mdx-ref": pendingId,
|
|
1263
|
+
"data-mdx-status": "error",
|
|
1264
|
+
"data-mdx-mode": compileMode
|
|
1265
|
+
},
|
|
1266
|
+
errorPanel,
|
|
1267
|
+
retryButton
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
if (!Comp) {
|
|
1271
|
+
return import_react6.default.createElement(
|
|
1272
|
+
"div",
|
|
1273
|
+
{
|
|
1274
|
+
className: "markdown-mdx",
|
|
1275
|
+
ref: containerRef,
|
|
1276
|
+
"data-mdx-ref": pendingId,
|
|
1277
|
+
"data-mdx-status": "pending",
|
|
1278
|
+
"data-mdx-mode": compileMode
|
|
1279
|
+
},
|
|
1280
|
+
pendingLabel
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1283
|
+
},
|
|
1284
|
+
// Footnotes block rendered at page end
|
|
1285
|
+
footnotes: ({ items }) => {
|
|
1286
|
+
if (!items || items.length === 0) return import_react6.default.createElement(import_react6.default.Fragment, null);
|
|
1287
|
+
const listItems = items.map(
|
|
1288
|
+
(item) => import_react6.default.createElement(
|
|
1289
|
+
"li",
|
|
1290
|
+
{ key: item.number, id: `fn:${item.number}` },
|
|
1291
|
+
renderInlineNodes(item.inlines, defaultInlineComponents),
|
|
1292
|
+
" ",
|
|
1293
|
+
import_react6.default.createElement(
|
|
1294
|
+
"a",
|
|
1295
|
+
{
|
|
1296
|
+
href: `#fnref:${item.number}`,
|
|
1297
|
+
className: "footnote-backref",
|
|
1298
|
+
"aria-label": "Back to content",
|
|
1299
|
+
style: { color: "var(--foreground)", textDecoration: "underline" }
|
|
1300
|
+
},
|
|
1301
|
+
"\u21A9"
|
|
1302
|
+
)
|
|
1303
|
+
)
|
|
1304
|
+
);
|
|
1305
|
+
return import_react6.default.createElement("section", { className: "footnotes" }, import_react6.default.createElement("hr", {}), import_react6.default.createElement("ol", {}, listItems));
|
|
1306
|
+
},
|
|
1307
|
+
// Footnote definition placeholders (render nothing)
|
|
1308
|
+
"footnote-def": () => import_react6.default.createElement(import_react6.default.Fragment, null),
|
|
1309
|
+
// Callouts block
|
|
1310
|
+
callout: ({ kind, inlines = [] }) => {
|
|
1311
|
+
const tone = (kind || "NOTE").toUpperCase();
|
|
1312
|
+
const title = tone.charAt(0) + tone.slice(1).toLowerCase();
|
|
1313
|
+
return import_react6.default.createElement(
|
|
1314
|
+
"div",
|
|
1315
|
+
{ className: `markdown-callout markdown-callout-${tone.toLowerCase()} border rounded p-3 my-3` },
|
|
1316
|
+
import_react6.default.createElement("div", { className: "font-semibold mb-1" }, title),
|
|
1317
|
+
inlines.length > 0 ? renderInlineNodes(inlines, defaultInlineComponents) : null
|
|
1318
|
+
);
|
|
1319
|
+
},
|
|
1320
|
+
// Structured table block (GFM)
|
|
1321
|
+
table: ({
|
|
1322
|
+
header,
|
|
1323
|
+
rows,
|
|
1324
|
+
align,
|
|
1325
|
+
elements
|
|
1326
|
+
}) => {
|
|
1327
|
+
const El = getDefaultTableElements(elements);
|
|
1328
|
+
const renderCells = (cells, tag, rowIdx) => cells.map((cell, i) => {
|
|
1329
|
+
const columnAlign = align?.[i] ?? void 0;
|
|
1330
|
+
const cellStyle = columnAlign ? { textAlign: columnAlign } : void 0;
|
|
1331
|
+
return import_react6.default.createElement(
|
|
1332
|
+
tag === "th" ? El.Th : El.Td,
|
|
1333
|
+
{
|
|
1334
|
+
key: `${rowIdx}-${i}`,
|
|
1335
|
+
style: cellStyle,
|
|
1336
|
+
// Also pass align attribute so CSS selectors like [&[align=center]] can be used by consumers
|
|
1337
|
+
align: columnAlign ?? void 0
|
|
1338
|
+
},
|
|
1339
|
+
renderInlineNodes(cell, defaultInlineComponents)
|
|
1340
|
+
);
|
|
1341
|
+
});
|
|
1342
|
+
return import_react6.default.createElement(
|
|
1343
|
+
El.Table,
|
|
1344
|
+
{ className: "markdown-table" },
|
|
1345
|
+
header && header.length > 0 ? import_react6.default.createElement(El.Thead, {}, import_react6.default.createElement(El.Tr, {}, renderCells(header, "th", -1))) : null,
|
|
1346
|
+
import_react6.default.createElement(
|
|
1347
|
+
El.Tbody,
|
|
1348
|
+
{},
|
|
1349
|
+
rows.map((row, r) => import_react6.default.createElement(El.Tr, { key: r }, renderCells(row, "td", r)))
|
|
1350
|
+
)
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
1353
|
+
};
|
|
1354
|
+
var ComponentRegistry = class {
|
|
1355
|
+
constructor(blockComponents = {}, inlineComponents = {}) {
|
|
1356
|
+
this.blockComponentMapCache = null;
|
|
1357
|
+
this.inlineComponentMapCache = null;
|
|
1358
|
+
this.blockComponents = { ...defaultBlockComponents, ...blockComponents };
|
|
1359
|
+
this.inlineComponents = { ...defaultInlineComponents, ...inlineComponents };
|
|
1360
|
+
this.htmlElements = getDefaultHtmlElements();
|
|
1361
|
+
this.tableElements = getDefaultTableElements();
|
|
1362
|
+
}
|
|
1363
|
+
/**
|
|
1364
|
+
* Update block components
|
|
1365
|
+
*/
|
|
1366
|
+
setBlockComponents(components) {
|
|
1367
|
+
this.blockComponents = { ...this.blockComponents, ...components };
|
|
1368
|
+
this.blockComponentMapCache = null;
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Update inline components
|
|
1372
|
+
*/
|
|
1373
|
+
setInlineComponents(components) {
|
|
1374
|
+
this.inlineComponents = { ...this.inlineComponents, ...components };
|
|
1375
|
+
this.inlineComponentMapCache = null;
|
|
1376
|
+
}
|
|
1377
|
+
/** Update HTML element mapping for html blocks */
|
|
1378
|
+
setHtmlElements(map) {
|
|
1379
|
+
this.htmlElements = { ...this.htmlElements, ...map };
|
|
1380
|
+
}
|
|
1381
|
+
/** Update table element mapping for table blocks */
|
|
1382
|
+
setTableElements(map) {
|
|
1383
|
+
this.tableElements = { ...this.tableElements, ...map };
|
|
1384
|
+
}
|
|
1385
|
+
/**
|
|
1386
|
+
* Get block component
|
|
1387
|
+
*/
|
|
1388
|
+
getBlockComponent(type) {
|
|
1389
|
+
return this.blockComponents[type] || this.blockComponents.paragraph;
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* Get inline components
|
|
1393
|
+
*/
|
|
1394
|
+
getInlineComponents() {
|
|
1395
|
+
return this.inlineComponents;
|
|
1396
|
+
}
|
|
1397
|
+
getBlockComponentMap() {
|
|
1398
|
+
if (!this.blockComponentMapCache) {
|
|
1399
|
+
this.blockComponentMapCache = { ...this.blockComponents };
|
|
1400
|
+
}
|
|
1401
|
+
return this.blockComponentMapCache;
|
|
1402
|
+
}
|
|
1403
|
+
getInlineComponentMap() {
|
|
1404
|
+
if (!this.inlineComponentMapCache) {
|
|
1405
|
+
this.inlineComponentMapCache = { ...this.inlineComponents };
|
|
1406
|
+
}
|
|
1407
|
+
return this.inlineComponentMapCache;
|
|
1408
|
+
}
|
|
1409
|
+
getTableElements() {
|
|
1410
|
+
return this.tableElements;
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* Render a block
|
|
1414
|
+
*/
|
|
1415
|
+
renderBlock(block) {
|
|
1416
|
+
const Component = this.getBlockComponent(block.type);
|
|
1417
|
+
const props = this.getBlockProps(block);
|
|
1418
|
+
return import_react6.default.createElement(Component, {
|
|
1419
|
+
key: block.id,
|
|
1420
|
+
...props
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* Get props for a block based on its type
|
|
1425
|
+
*/
|
|
1426
|
+
getBlockProps(block) {
|
|
1427
|
+
switch (block.type) {
|
|
1428
|
+
case "heading": {
|
|
1429
|
+
const meta = block.payload.meta ?? {};
|
|
1430
|
+
const inlineNodes = Array.isArray(block.payload.inline) ? block.payload.inline : [];
|
|
1431
|
+
const levelFromMeta = typeof meta.headingLevel === "number" ? meta.headingLevel : void 0;
|
|
1432
|
+
const normalizedLevel = Number.isFinite(levelFromMeta) ? Math.min(Math.max(levelFromMeta, 1), 6) : void 0;
|
|
1433
|
+
const rawHeading = typeof block.payload.raw === "string" ? block.payload.raw : "";
|
|
1434
|
+
const headingText = typeof meta.headingText === "string" && meta.headingText.trim().length > 0 ? meta.headingText : (0, import_core2.removeHeadingMarkers)(rawHeading).trim();
|
|
1435
|
+
const level = normalizedLevel ?? this.extractHeadingLevel(rawHeading);
|
|
1436
|
+
return {
|
|
1437
|
+
level,
|
|
1438
|
+
inlines: inlineNodes,
|
|
1439
|
+
text: headingText,
|
|
1440
|
+
meta
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
case "paragraph":
|
|
1444
|
+
return {
|
|
1445
|
+
inlines: block.payload.inline || [],
|
|
1446
|
+
raw: block.payload.raw,
|
|
1447
|
+
meta: block.payload.meta
|
|
1448
|
+
};
|
|
1449
|
+
case "blockquote":
|
|
1450
|
+
return {
|
|
1451
|
+
inlines: block.payload.inline || []
|
|
1452
|
+
};
|
|
1453
|
+
case "hr": {
|
|
1454
|
+
const range = block.payload.range;
|
|
1455
|
+
return {
|
|
1456
|
+
className: "markdown-hr",
|
|
1457
|
+
"data-start": typeof range?.from === "number" ? range.from : void 0,
|
|
1458
|
+
"data-end": typeof range?.to === "number" ? range.to : void 0
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
case "code":
|
|
1462
|
+
return {
|
|
1463
|
+
html: block.payload.highlightedHtml,
|
|
1464
|
+
meta: block.payload.meta,
|
|
1465
|
+
lang: typeof block.payload.meta?.lang === "string" ? String(block.payload.meta?.lang) : void 0
|
|
1466
|
+
};
|
|
1467
|
+
case "list": {
|
|
1468
|
+
const items = Array.isArray(block.payload.meta?.items) ? block.payload.meta?.items : [];
|
|
1469
|
+
return {
|
|
1470
|
+
ordered: Boolean(block.payload.meta?.ordered),
|
|
1471
|
+
items
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
case "html": {
|
|
1475
|
+
const trusted = block.payload.sanitizedHtml ?? block.payload.raw;
|
|
1476
|
+
return {
|
|
1477
|
+
// Server render should not depend on DOMPurify (which requires a DOM `window`).
|
|
1478
|
+
// In worker-compiled flows, `sanitizedHtml` is already sanitized at the source.
|
|
1479
|
+
__trustedHtml: typeof window === "undefined" ? String(trusted ?? "") : (0, import_core.createTrustedHTML)(String(trusted ?? "")),
|
|
1480
|
+
elements: this.htmlElements
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
case "mdx":
|
|
1484
|
+
return (() => {
|
|
1485
|
+
const meta = block.payload.meta;
|
|
1486
|
+
const rawStatus = typeof meta?.mdxStatus === "string" ? meta.mdxStatus : void 0;
|
|
1487
|
+
const status = rawStatus === "pending" || rawStatus === "compiled" || rawStatus === "error" ? rawStatus : void 0;
|
|
1488
|
+
const errorMessage = typeof meta?.mdxError === "string" ? String(meta.mdxError) : void 0;
|
|
1489
|
+
const compiledModule = block.payload.compiledMdxModule ?? null;
|
|
1490
|
+
const compiledRef = block.payload.compiledMdxRef ?? (compiledModule ? { id: compiledModule.id } : void 0);
|
|
1491
|
+
return {
|
|
1492
|
+
compiledRef: compiledRef ?? { id: "pending" },
|
|
1493
|
+
compiledModule,
|
|
1494
|
+
status,
|
|
1495
|
+
errorMessage,
|
|
1496
|
+
raw: typeof block.payload.raw === "string" ? block.payload.raw : void 0
|
|
1497
|
+
};
|
|
1498
|
+
})();
|
|
1499
|
+
case "table": {
|
|
1500
|
+
const header = Array.isArray(block.payload.meta?.header) ? block.payload.meta?.header : void 0;
|
|
1501
|
+
const rows = Array.isArray(block.payload.meta?.rows) ? block.payload.meta?.rows : [];
|
|
1502
|
+
const align = Array.isArray(block.payload.meta?.align) ? block.payload.meta?.align : void 0;
|
|
1503
|
+
return {
|
|
1504
|
+
header,
|
|
1505
|
+
rows,
|
|
1506
|
+
align,
|
|
1507
|
+
elements: this.tableElements
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
case "footnotes": {
|
|
1511
|
+
const items = Array.isArray(block.payload.meta?.items) ? block.payload.meta?.items : [];
|
|
1512
|
+
return {
|
|
1513
|
+
items
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
case "footnote-def":
|
|
1517
|
+
return {};
|
|
1518
|
+
case "callout": {
|
|
1519
|
+
const kind = typeof block.payload.meta?.kind === "string" ? String(block.payload.meta.kind) : void 0;
|
|
1520
|
+
const inlineNodes = Array.isArray(block.payload.inline) ? block.payload.inline : [];
|
|
1521
|
+
return {
|
|
1522
|
+
kind,
|
|
1523
|
+
inlines: inlineNodes
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
default:
|
|
1527
|
+
return {
|
|
1528
|
+
content: block.payload.raw,
|
|
1529
|
+
meta: block.payload.meta
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
/**
|
|
1534
|
+
* Extract heading level from raw markdown
|
|
1535
|
+
*/
|
|
1536
|
+
extractHeadingLevel(raw) {
|
|
1537
|
+
const match = raw.match(/^(#{1,6})\s/);
|
|
1538
|
+
if (match) {
|
|
1539
|
+
return Math.min(match[1].length, 6);
|
|
1540
|
+
}
|
|
1541
|
+
return 1;
|
|
1542
|
+
}
|
|
1543
|
+
};
|
|
1544
|
+
function getDefaultHtmlElements() {
|
|
1545
|
+
return {
|
|
1546
|
+
// Text semantics
|
|
1547
|
+
p: (props) => import_react6.default.createElement("p", props),
|
|
1548
|
+
strong: (props) => import_react6.default.createElement("strong", props),
|
|
1549
|
+
em: (props) => import_react6.default.createElement("em", props),
|
|
1550
|
+
code: (props) => import_react6.default.createElement("code", props),
|
|
1551
|
+
pre: (props) => import_react6.default.createElement("pre", props),
|
|
1552
|
+
span: (props) => import_react6.default.createElement("span", props),
|
|
1553
|
+
br: (props) => import_react6.default.createElement("br", props),
|
|
1554
|
+
hr: (props) => import_react6.default.createElement("hr", props),
|
|
1555
|
+
// Lists
|
|
1556
|
+
ul: (props) => import_react6.default.createElement("ul", props),
|
|
1557
|
+
ol: (props) => import_react6.default.createElement("ol", props),
|
|
1558
|
+
li: (props) => import_react6.default.createElement("li", props),
|
|
1559
|
+
// Links & media
|
|
1560
|
+
a: (props) => import_react6.default.createElement("a", props),
|
|
1561
|
+
img: (props) => import_react6.default.createElement("img", props),
|
|
1562
|
+
// Blockquotes
|
|
1563
|
+
blockquote: (props) => import_react6.default.createElement("blockquote", props),
|
|
1564
|
+
// Tables
|
|
1565
|
+
table: (props) => import_react6.default.createElement("table", props),
|
|
1566
|
+
thead: (props) => import_react6.default.createElement("thead", props),
|
|
1567
|
+
tbody: (props) => import_react6.default.createElement("tbody", props),
|
|
1568
|
+
tr: (props) => import_react6.default.createElement("tr", props),
|
|
1569
|
+
th: (props) => import_react6.default.createElement("th", props),
|
|
1570
|
+
td: (props) => import_react6.default.createElement("td", props),
|
|
1571
|
+
// Generic
|
|
1572
|
+
div: (props) => import_react6.default.createElement("div", props)
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
function getDefaultTableElements(overrides) {
|
|
1576
|
+
const base = {
|
|
1577
|
+
Table: (props) => import_react6.default.createElement("table", props),
|
|
1578
|
+
Thead: (props) => import_react6.default.createElement("thead", props),
|
|
1579
|
+
Tbody: (props) => import_react6.default.createElement("tbody", props),
|
|
1580
|
+
Tr: (props) => import_react6.default.createElement("tr", props),
|
|
1581
|
+
Th: (props) => import_react6.default.createElement("th", props),
|
|
1582
|
+
Td: (props) => import_react6.default.createElement("td", props)
|
|
1583
|
+
};
|
|
1584
|
+
return { ...base, ...overrides || {} };
|
|
1585
|
+
}
|
|
1586
|
+
function mapHtmlToReact(html, elements) {
|
|
1587
|
+
const parser = new DOMParser();
|
|
1588
|
+
const doc = parser.parseFromString(html, "text/html");
|
|
1589
|
+
const children = [];
|
|
1590
|
+
doc.body.childNodes.forEach((node, index) => {
|
|
1591
|
+
const element = toReact(node, elements, `root-${index}`);
|
|
1592
|
+
if (element) {
|
|
1593
|
+
children.push(element);
|
|
1594
|
+
}
|
|
1595
|
+
});
|
|
1596
|
+
return import_react6.default.createElement(import_react6.default.Fragment, {}, ...children);
|
|
1597
|
+
}
|
|
1598
|
+
function toReact(node, elements, key) {
|
|
1599
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
1600
|
+
const text = node.textContent ?? "";
|
|
1601
|
+
return import_react6.default.createElement(import_react6.default.Fragment, { key }, text);
|
|
1602
|
+
}
|
|
1603
|
+
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
1604
|
+
return null;
|
|
1605
|
+
}
|
|
1606
|
+
const el = node;
|
|
1607
|
+
const tag = el.tagName.toLowerCase();
|
|
1608
|
+
const props = {};
|
|
1609
|
+
for (const attr of Array.from(el.attributes)) {
|
|
1610
|
+
const name = attr.name === "class" ? "className" : attr.name;
|
|
1611
|
+
props[name] = attr.value;
|
|
1612
|
+
}
|
|
1613
|
+
const children = [];
|
|
1614
|
+
el.childNodes.forEach((childNode, childIndex) => {
|
|
1615
|
+
const child = toReact(childNode, elements, `${key}-${childIndex}`);
|
|
1616
|
+
if (child) {
|
|
1617
|
+
children.push(child);
|
|
1618
|
+
}
|
|
1619
|
+
});
|
|
1620
|
+
const component = elements[tag];
|
|
1621
|
+
const elementChildren = children.length > 0 ? children : void 0;
|
|
1622
|
+
if (component) {
|
|
1623
|
+
return import_react6.default.createElement(component, { ...props, key }, elementChildren);
|
|
1624
|
+
}
|
|
1625
|
+
return import_react6.default.createElement(tag, { ...props, key }, elementChildren);
|
|
1626
|
+
}
|
|
1627
|
+
function attrsToProps(attrs) {
|
|
1628
|
+
if (!attrs) return {};
|
|
1629
|
+
const result = {};
|
|
1630
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
1631
|
+
if (key === "class") {
|
|
1632
|
+
result.className = value;
|
|
1633
|
+
} else if (key === "style") {
|
|
1634
|
+
result.style = parseStyleAttribute2(value);
|
|
1635
|
+
} else if (key === "tabindex" || key === "tabIndex") {
|
|
1636
|
+
const numericValue = Number(value);
|
|
1637
|
+
if (!Number.isNaN(numericValue)) {
|
|
1638
|
+
result.tabIndex = numericValue;
|
|
1639
|
+
}
|
|
1640
|
+
} else {
|
|
1641
|
+
result[key] = value;
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
return result;
|
|
1645
|
+
}
|
|
1646
|
+
function escapeHtml(value) {
|
|
1647
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1648
|
+
}
|
|
1649
|
+
function parseStyleAttribute2(value) {
|
|
1650
|
+
const style = {};
|
|
1651
|
+
if (!value) return style;
|
|
1652
|
+
const parts = value.split(";");
|
|
1653
|
+
for (const part of parts) {
|
|
1654
|
+
if (!part.trim()) continue;
|
|
1655
|
+
const [prop, val] = part.split(":");
|
|
1656
|
+
if (!prop || !val) continue;
|
|
1657
|
+
const camelProp = prop.trim().replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
|
1658
|
+
style[camelProp] = val.trim();
|
|
1659
|
+
}
|
|
1660
|
+
return style;
|
|
1661
|
+
}
|
|
1662
|
+
function renderParagraphMixedSegments(segments, inlineComponents, inlineHtmlRenderers) {
|
|
1663
|
+
const result = [];
|
|
1664
|
+
let inlineBuffer = [];
|
|
1665
|
+
let inlineGroupIndex = 0;
|
|
1666
|
+
const pushRenderedNodes = (nodes, target, baseKey) => {
|
|
1667
|
+
nodes.forEach((node, idx) => {
|
|
1668
|
+
if (node === null || node === void 0) return;
|
|
1669
|
+
const key = `${baseKey}-${idx}`;
|
|
1670
|
+
if (import_react6.default.isValidElement(node)) {
|
|
1671
|
+
target.push(node.key === key ? node : import_react6.default.cloneElement(node, { key }));
|
|
1672
|
+
} else if (typeof node === "string" || typeof node === "number") {
|
|
1673
|
+
target.push(import_react6.default.createElement(import_react6.default.Fragment, { key }, node));
|
|
1674
|
+
} else {
|
|
1675
|
+
target.push(import_react6.default.createElement(import_react6.default.Fragment, { key }, node));
|
|
1676
|
+
}
|
|
1677
|
+
});
|
|
1678
|
+
};
|
|
1679
|
+
const ensureArray2 = (value) => {
|
|
1680
|
+
if (value === null || value === void 0) {
|
|
1681
|
+
return [];
|
|
1682
|
+
}
|
|
1683
|
+
return Array.isArray(value) ? value : [value];
|
|
1684
|
+
};
|
|
1685
|
+
const flushInline = () => {
|
|
1686
|
+
if (inlineBuffer.length === 0) {
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
const key = `paragraph-inline-group-${inlineGroupIndex++}`;
|
|
1690
|
+
result.push(
|
|
1691
|
+
import_react6.default.createElement(
|
|
1692
|
+
"p",
|
|
1693
|
+
{
|
|
1694
|
+
key,
|
|
1695
|
+
className: "markdown-paragraph"
|
|
1696
|
+
},
|
|
1697
|
+
inlineBuffer
|
|
1698
|
+
)
|
|
1699
|
+
);
|
|
1700
|
+
inlineBuffer = [];
|
|
1701
|
+
};
|
|
1702
|
+
segments.forEach((segment, index) => {
|
|
1703
|
+
const key = `paragraph-segment-${index}`;
|
|
1704
|
+
switch (segment.kind) {
|
|
1705
|
+
case "text": {
|
|
1706
|
+
const inlineNodes = Array.isArray(segment.inline) ? segment.inline : [];
|
|
1707
|
+
if (inlineNodes.length === 0) {
|
|
1708
|
+
if (segment.value) {
|
|
1709
|
+
inlineBuffer.push(import_react6.default.createElement(import_react6.default.Fragment, { key }, segment.value));
|
|
1710
|
+
}
|
|
1711
|
+
break;
|
|
1712
|
+
}
|
|
1713
|
+
let chunk = [];
|
|
1714
|
+
const pushChunk = (chunkNodes, chunkKey) => {
|
|
1715
|
+
if (chunkNodes.length === 0) return;
|
|
1716
|
+
const rendered = renderInlineNodes(chunkNodes, inlineComponents);
|
|
1717
|
+
pushRenderedNodes(ensureArray2(rendered), inlineBuffer, chunkKey);
|
|
1718
|
+
};
|
|
1719
|
+
inlineNodes.forEach((inlineNode, inlineIdx) => {
|
|
1720
|
+
if (inlineNode.kind === "math-display") {
|
|
1721
|
+
pushChunk(chunk, `${key}-chunk-${inlineIdx}`);
|
|
1722
|
+
chunk = [];
|
|
1723
|
+
flushInline();
|
|
1724
|
+
const rendered = renderInlineNodes([inlineNode], inlineComponents);
|
|
1725
|
+
pushRenderedNodes(ensureArray2(rendered), result, `${key}-math-${inlineIdx}`);
|
|
1726
|
+
} else {
|
|
1727
|
+
chunk.push(inlineNode);
|
|
1728
|
+
}
|
|
1729
|
+
});
|
|
1730
|
+
pushChunk(chunk, `${key}-chunk-tail`);
|
|
1731
|
+
break;
|
|
1732
|
+
}
|
|
1733
|
+
case "mdx": {
|
|
1734
|
+
const status = segment.status ?? "pending";
|
|
1735
|
+
let element;
|
|
1736
|
+
if (status === "compiled") {
|
|
1737
|
+
element = import_react6.default.createElement("span", { className: "markdown-mdx-inline", "data-mdx-status": "compiled" });
|
|
1738
|
+
} else if (status === "error") {
|
|
1739
|
+
const message = segment.value || "MDX error";
|
|
1740
|
+
element = import_react6.default.createElement("span", { className: "markdown-mdx-inline text-destructive", "data-mdx-status": "error" }, message);
|
|
1741
|
+
} else {
|
|
1742
|
+
element = import_react6.default.createElement("span", { className: "markdown-mdx-inline", "data-mdx-status": status }, segment.value);
|
|
1743
|
+
}
|
|
1744
|
+
inlineBuffer.push(import_react6.default.createElement(import_react6.default.Fragment, { key }, element));
|
|
1745
|
+
break;
|
|
1746
|
+
}
|
|
1747
|
+
case "html": {
|
|
1748
|
+
const html = segment.sanitized ?? segment.value;
|
|
1749
|
+
const isBlockLevel = isBlockLevelHtml(html);
|
|
1750
|
+
const element = renderInlineHtmlSegment(segment.value, segment.sanitized, {
|
|
1751
|
+
key,
|
|
1752
|
+
renderers: inlineHtmlRenderers
|
|
1753
|
+
});
|
|
1754
|
+
if (isBlockLevel) {
|
|
1755
|
+
flushInline();
|
|
1756
|
+
if (element) {
|
|
1757
|
+
result.push(element);
|
|
1758
|
+
} else if (html) {
|
|
1759
|
+
result.push(
|
|
1760
|
+
import_react6.default.createElement("div", {
|
|
1761
|
+
key,
|
|
1762
|
+
className: "markdown-inline-html-block",
|
|
1763
|
+
dangerouslySetInnerHTML: { __html: html }
|
|
1764
|
+
})
|
|
1765
|
+
);
|
|
1766
|
+
}
|
|
1767
|
+
} else if (element) {
|
|
1768
|
+
inlineBuffer.push(element);
|
|
1769
|
+
}
|
|
1770
|
+
break;
|
|
1771
|
+
}
|
|
1772
|
+
default:
|
|
1773
|
+
break;
|
|
1774
|
+
}
|
|
1775
|
+
});
|
|
1776
|
+
flushInline();
|
|
1777
|
+
if (result.length === 0) {
|
|
1778
|
+
return [
|
|
1779
|
+
import_react6.default.createElement(
|
|
1780
|
+
"p",
|
|
1781
|
+
{
|
|
1782
|
+
key: "paragraph-inline-group-0",
|
|
1783
|
+
className: "markdown-paragraph"
|
|
1784
|
+
},
|
|
1785
|
+
inlineBuffer
|
|
1786
|
+
)
|
|
1787
|
+
];
|
|
1788
|
+
}
|
|
1789
|
+
return result;
|
|
1790
|
+
}
|
|
1791
|
+
function isBlockLevelHtml(html) {
|
|
1792
|
+
if (!html) return false;
|
|
1793
|
+
const trimmed = html.trim();
|
|
1794
|
+
if (!trimmed.startsWith("<")) {
|
|
1795
|
+
return false;
|
|
1796
|
+
}
|
|
1797
|
+
const match = trimmed.match(/^<([A-Za-z][\w:-]*)\b/);
|
|
1798
|
+
if (!match) {
|
|
1799
|
+
return false;
|
|
1800
|
+
}
|
|
1801
|
+
const tag = match[1].toLowerCase();
|
|
1802
|
+
return BLOCK_LEVEL_TAGS.has(tag);
|
|
1803
|
+
}
|
|
1804
|
+
var BLOCK_LEVEL_TAGS = /* @__PURE__ */ new Set([
|
|
1805
|
+
"address",
|
|
1806
|
+
"article",
|
|
1807
|
+
"aside",
|
|
1808
|
+
"blockquote",
|
|
1809
|
+
"canvas",
|
|
1810
|
+
"div",
|
|
1811
|
+
"dl",
|
|
1812
|
+
"fieldset",
|
|
1813
|
+
"figcaption",
|
|
1814
|
+
"figure",
|
|
1815
|
+
"footer",
|
|
1816
|
+
"form",
|
|
1817
|
+
"h1",
|
|
1818
|
+
"h2",
|
|
1819
|
+
"h3",
|
|
1820
|
+
"h4",
|
|
1821
|
+
"h5",
|
|
1822
|
+
"h6",
|
|
1823
|
+
"header",
|
|
1824
|
+
"hr",
|
|
1825
|
+
"li",
|
|
1826
|
+
"main",
|
|
1827
|
+
"nav",
|
|
1828
|
+
"ol",
|
|
1829
|
+
"p",
|
|
1830
|
+
"pre",
|
|
1831
|
+
"section",
|
|
1832
|
+
"table",
|
|
1833
|
+
"ul",
|
|
1834
|
+
"math"
|
|
1835
|
+
]);
|
|
1836
|
+
|
|
1837
|
+
// src/renderer/code-highlight-context.tsx
|
|
1838
|
+
var import_react7 = __toESM(require("react"), 1);
|
|
1839
|
+
var CodeHighlightRequestContext = import_react7.default.createContext(null);
|
|
1840
|
+
function useCodeHighlightRequester() {
|
|
1841
|
+
return import_react7.default.useContext(CodeHighlightRequestContext);
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
// src/renderer/deferred-render-context.tsx
|
|
1845
|
+
var import_react8 = __toESM(require("react"), 1);
|
|
1846
|
+
var DeferredRenderContext = import_react8.default.createContext(null);
|
|
1847
|
+
|
|
1848
|
+
// src/renderer/store.ts
|
|
1849
|
+
var import_core4 = require("@stream-mdx/core");
|
|
1850
|
+
var import_patch_batching = require("@stream-mdx/core/perf/patch-batching");
|
|
1851
|
+
|
|
1852
|
+
// src/renderer/list-utils.ts
|
|
1853
|
+
function updateNodeDepth(node, depth) {
|
|
1854
|
+
if (!node) return false;
|
|
1855
|
+
const currentDepth = typeof node.props?.depth === "number" ? node.props.depth : void 0;
|
|
1856
|
+
if (currentDepth === depth) {
|
|
1857
|
+
return false;
|
|
1858
|
+
}
|
|
1859
|
+
const nextProps = { ...node.props ?? {} };
|
|
1860
|
+
nextProps.depth = depth;
|
|
1861
|
+
node.props = nextProps;
|
|
1862
|
+
node.version++;
|
|
1863
|
+
return true;
|
|
1864
|
+
}
|
|
1865
|
+
function normalizeAllListDepths(map, touched, rootId = "__root__") {
|
|
1866
|
+
const root = map.get(rootId);
|
|
1867
|
+
if (!root) return;
|
|
1868
|
+
for (const childId of root.children) {
|
|
1869
|
+
const child = map.get(childId);
|
|
1870
|
+
if (child && child.type === "list") {
|
|
1871
|
+
normalizeListDepthRecursive(map, child, 0, touched);
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
function normalizeListDepthsForIds(map, touched, listIds, rootId = "__root__") {
|
|
1876
|
+
if (listIds.size === 0) {
|
|
1877
|
+
normalizeAllListDepths(map, touched, rootId);
|
|
1878
|
+
return;
|
|
1879
|
+
}
|
|
1880
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1881
|
+
for (const listId of listIds) {
|
|
1882
|
+
const base = map.get(listId);
|
|
1883
|
+
if (!base) continue;
|
|
1884
|
+
const listNode = base.type === "list" ? base : findNearestListAncestor(map, base);
|
|
1885
|
+
if (!listNode || visited.has(listNode.id)) continue;
|
|
1886
|
+
visited.add(listNode.id);
|
|
1887
|
+
const depth = computeListDepth(map, listNode, rootId);
|
|
1888
|
+
normalizeListDepthRecursive(map, listNode, depth, touched);
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
function normalizeListDepthRecursive(map, listNode, depth, touched) {
|
|
1892
|
+
if (!listNode) return;
|
|
1893
|
+
if (updateNodeDepth(listNode, depth)) {
|
|
1894
|
+
touched.add(listNode.id);
|
|
1895
|
+
}
|
|
1896
|
+
for (const childId of listNode.children) {
|
|
1897
|
+
const child = map.get(childId);
|
|
1898
|
+
if (!child) continue;
|
|
1899
|
+
if (child.type === "list-item") {
|
|
1900
|
+
if (updateNodeDepth(child, depth)) {
|
|
1901
|
+
touched.add(child.id);
|
|
1902
|
+
}
|
|
1903
|
+
for (const grandchildId of child.children) {
|
|
1904
|
+
const grandchild = map.get(grandchildId);
|
|
1905
|
+
if (!grandchild) continue;
|
|
1906
|
+
if (grandchild.type === "list") {
|
|
1907
|
+
normalizeListDepthRecursive(map, grandchild, depth + 1, touched);
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
} else if (child.type === "list") {
|
|
1911
|
+
normalizeListDepthRecursive(map, child, depth + 1, touched);
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
function findNearestListAncestor(map, node) {
|
|
1916
|
+
let current = node;
|
|
1917
|
+
while (current) {
|
|
1918
|
+
if (current.type === "list") return current;
|
|
1919
|
+
if (!current.parentId) return void 0;
|
|
1920
|
+
current = map.get(current.parentId);
|
|
1921
|
+
}
|
|
1922
|
+
return void 0;
|
|
1923
|
+
}
|
|
1924
|
+
function computeListDepth(map, listNode, rootId) {
|
|
1925
|
+
let depth = 0;
|
|
1926
|
+
let current = listNode;
|
|
1927
|
+
while (current && current.parentId && current.parentId !== rootId) {
|
|
1928
|
+
const parent = map.get(current.parentId);
|
|
1929
|
+
if (!parent) break;
|
|
1930
|
+
if (parent.type === "list") {
|
|
1931
|
+
depth += 1;
|
|
1932
|
+
}
|
|
1933
|
+
current = parent;
|
|
1934
|
+
}
|
|
1935
|
+
return depth;
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
// src/renderer/patch-coalescing.ts
|
|
1939
|
+
var import_core3 = require("@stream-mdx/core");
|
|
1940
|
+
|
|
1941
|
+
// src/renderer/store.ts
|
|
1942
|
+
function snapshotToBlock(node) {
|
|
1943
|
+
const candidate = node.props?.block;
|
|
1944
|
+
if (candidate && typeof candidate === "object") {
|
|
1945
|
+
return (0, import_core4.cloneBlock)(candidate);
|
|
1946
|
+
}
|
|
1947
|
+
return null;
|
|
1948
|
+
}
|
|
1949
|
+
var nodeSnapshotCacheByMap = /* @__PURE__ */ new WeakMap();
|
|
1950
|
+
var childrenSnapshotCacheByMap = /* @__PURE__ */ new WeakMap();
|
|
1951
|
+
var NODE_SNAPSHOT_CACHE_LIMIT = 5e4;
|
|
1952
|
+
var CHILDREN_SNAPSHOT_CACHE_LIMIT = 5e4;
|
|
1953
|
+
var NODE_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(NODE_SNAPSHOT_CACHE_LIMIT * 0.1));
|
|
1954
|
+
var CHILDREN_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(CHILDREN_SNAPSHOT_CACHE_LIMIT * 0.1));
|
|
1955
|
+
function getNodeSnapshotCacheForMap(map) {
|
|
1956
|
+
let cache = nodeSnapshotCacheByMap.get(map);
|
|
1957
|
+
if (!cache) {
|
|
1958
|
+
cache = /* @__PURE__ */ new Map();
|
|
1959
|
+
nodeSnapshotCacheByMap.set(map, cache);
|
|
1960
|
+
}
|
|
1961
|
+
return cache;
|
|
1962
|
+
}
|
|
1963
|
+
function getChildrenSnapshotCacheForMap(map) {
|
|
1964
|
+
let cache = childrenSnapshotCacheByMap.get(map);
|
|
1965
|
+
if (!cache) {
|
|
1966
|
+
cache = /* @__PURE__ */ new Map();
|
|
1967
|
+
childrenSnapshotCacheByMap.set(map, cache);
|
|
1968
|
+
}
|
|
1969
|
+
return cache;
|
|
1970
|
+
}
|
|
1971
|
+
function pruneCache(cache, max, buffer) {
|
|
1972
|
+
if (cache.size <= max + buffer) return;
|
|
1973
|
+
let remaining = cache.size - max;
|
|
1974
|
+
for (const key of cache.keys()) {
|
|
1975
|
+
cache.delete(key);
|
|
1976
|
+
remaining -= 1;
|
|
1977
|
+
if (remaining <= 0) break;
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
function deleteSnapshotCacheEntry(map, id) {
|
|
1981
|
+
getNodeSnapshotCacheForMap(map).delete(id);
|
|
1982
|
+
getChildrenSnapshotCacheForMap(map).delete(id);
|
|
1983
|
+
}
|
|
1984
|
+
function clearSnapshotCaches(map) {
|
|
1985
|
+
getNodeSnapshotCacheForMap(map).clear();
|
|
1986
|
+
getChildrenSnapshotCacheForMap(map).clear();
|
|
1987
|
+
}
|
|
1988
|
+
var EMPTY_CHILDREN = Object.freeze([]);
|
|
1989
|
+
var EMPTY_NODE_SNAPSHOT = Object.freeze({ version: -1, node: void 0 });
|
|
1990
|
+
var EMPTY_CHILDREN_SNAPSHOT = Object.freeze({
|
|
1991
|
+
version: -1,
|
|
1992
|
+
children: EMPTY_CHILDREN
|
|
1993
|
+
});
|
|
1994
|
+
function isListPatchDebugEnabled() {
|
|
1995
|
+
if (typeof globalThis !== "undefined") {
|
|
1996
|
+
const flag = globalThis.__STREAMING_LIST_DEBUG__;
|
|
1997
|
+
if (flag === true) return true;
|
|
1998
|
+
}
|
|
1999
|
+
if (typeof process !== "undefined" && process.env.NEXT_PUBLIC_STREAMING_LIST_DEBUG === "true") {
|
|
2000
|
+
return true;
|
|
2001
|
+
}
|
|
2002
|
+
return false;
|
|
2003
|
+
}
|
|
2004
|
+
var CODE_BLOCK_DEBUG_ENABLED = (() => {
|
|
2005
|
+
try {
|
|
2006
|
+
if (typeof process !== "undefined" && process.env && process.env.NEXT_PUBLIC_STREAMING_DEBUG_CODELINES === "1") {
|
|
2007
|
+
return true;
|
|
2008
|
+
}
|
|
2009
|
+
if (typeof globalThis !== "undefined") {
|
|
2010
|
+
const debug = globalThis?.__STREAMING_DEBUG__;
|
|
2011
|
+
if (debug?.codeLines) {
|
|
2012
|
+
return true;
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
} catch {
|
|
2016
|
+
}
|
|
2017
|
+
return false;
|
|
2018
|
+
})() || false;
|
|
2019
|
+
var CODE_BLOCK_VALIDATION_ENABLED = typeof process !== "undefined" && process.env ? process.env.NODE_ENV !== "production" : true;
|
|
2020
|
+
var PATCH_PERF_HISTORY_LIMIT = 120;
|
|
2021
|
+
var getPerfNow = (() => {
|
|
2022
|
+
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
2023
|
+
return () => performance.now();
|
|
2024
|
+
}
|
|
2025
|
+
return () => Date.now();
|
|
2026
|
+
})();
|
|
2027
|
+
function isPatchPerfDebugEnabled() {
|
|
2028
|
+
try {
|
|
2029
|
+
if (typeof process !== "undefined" && process.env) {
|
|
2030
|
+
const value = process.env.NEXT_PUBLIC_STREAMING_DEBUG_PATCH_PERF;
|
|
2031
|
+
if (value === "1" || value === "true") {
|
|
2032
|
+
return true;
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
} catch {
|
|
2036
|
+
}
|
|
2037
|
+
try {
|
|
2038
|
+
const debug = globalThis.__STREAMING_DEBUG__;
|
|
2039
|
+
if (debug?.patchPerf) {
|
|
2040
|
+
return true;
|
|
2041
|
+
}
|
|
2042
|
+
} catch {
|
|
2043
|
+
}
|
|
2044
|
+
return false;
|
|
2045
|
+
}
|
|
2046
|
+
function recordPatchPerf(summary) {
|
|
2047
|
+
try {
|
|
2048
|
+
if (typeof globalThis === "undefined") return;
|
|
2049
|
+
const root = globalThis;
|
|
2050
|
+
const history = root.__STREAM_MDX_PATCH_STATS__ ?? [];
|
|
2051
|
+
history.push(summary);
|
|
2052
|
+
if (history.length > PATCH_PERF_HISTORY_LIMIT) {
|
|
2053
|
+
history.splice(0, history.length - PATCH_PERF_HISTORY_LIMIT);
|
|
2054
|
+
}
|
|
2055
|
+
root.__STREAM_MDX_PATCH_STATS__ = history;
|
|
2056
|
+
root.__STREAM_MDX_PATCH_STATS_LAST__ = summary;
|
|
2057
|
+
const totals = root.__STREAM_MDX_PATCH_STATS_TOTALS__ ?? {
|
|
2058
|
+
calls: 0,
|
|
2059
|
+
totalMs: 0,
|
|
2060
|
+
applyMs: 0,
|
|
2061
|
+
coalesceMs: 0,
|
|
2062
|
+
listNormalizeMs: 0,
|
|
2063
|
+
patches: 0,
|
|
2064
|
+
coalescedPatches: 0,
|
|
2065
|
+
ops: {},
|
|
2066
|
+
setPropsByType: {},
|
|
2067
|
+
setPropsBySize: {}
|
|
2068
|
+
};
|
|
2069
|
+
totals.calls += 1;
|
|
2070
|
+
totals.totalMs += summary.totalMs;
|
|
2071
|
+
totals.applyMs += summary.applyMs;
|
|
2072
|
+
totals.coalesceMs += summary.coalesceMs;
|
|
2073
|
+
totals.listNormalizeMs += summary.listNormalizeMs;
|
|
2074
|
+
totals.patches += summary.patches;
|
|
2075
|
+
totals.coalescedPatches += summary.coalescedPatches;
|
|
2076
|
+
for (const [op, stats] of Object.entries(summary.ops)) {
|
|
2077
|
+
const entry = totals.ops[op] ?? { count: 0, durationMs: 0 };
|
|
2078
|
+
entry.count += stats.count;
|
|
2079
|
+
entry.durationMs += stats.durationMs;
|
|
2080
|
+
totals.ops[op] = entry;
|
|
2081
|
+
}
|
|
2082
|
+
if (summary.setPropsByType) {
|
|
2083
|
+
for (const [key, stats] of Object.entries(summary.setPropsByType)) {
|
|
2084
|
+
const entry = totals.setPropsByType[key] ?? { count: 0, durationMs: 0 };
|
|
2085
|
+
entry.count += stats.count;
|
|
2086
|
+
entry.durationMs += stats.durationMs;
|
|
2087
|
+
totals.setPropsByType[key] = entry;
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
if (summary.setPropsBySize) {
|
|
2091
|
+
for (const [key, stats] of Object.entries(summary.setPropsBySize)) {
|
|
2092
|
+
const entry = totals.setPropsBySize[key] ?? { count: 0, durationMs: 0 };
|
|
2093
|
+
entry.count += stats.count;
|
|
2094
|
+
entry.durationMs += stats.durationMs;
|
|
2095
|
+
totals.setPropsBySize[key] = entry;
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
root.__STREAM_MDX_PATCH_STATS_TOTALS__ = totals;
|
|
2099
|
+
} catch {
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
function estimateBlockPayloadSize(block) {
|
|
2103
|
+
if (!block || !block.payload) return 0;
|
|
2104
|
+
let size = 0;
|
|
2105
|
+
const payload = block.payload;
|
|
2106
|
+
if (typeof payload.raw === "string") size += payload.raw.length;
|
|
2107
|
+
if (typeof payload.highlightedHtml === "string") size += payload.highlightedHtml.length;
|
|
2108
|
+
if (typeof payload.sanitizedHtml === "string") size += payload.sanitizedHtml.length;
|
|
2109
|
+
if (typeof payload.compiledMdxModule?.code === "string") size += payload.compiledMdxModule.code.length;
|
|
2110
|
+
if (Array.isArray(payload.inline)) size += payload.inline.length * 8;
|
|
2111
|
+
return size;
|
|
2112
|
+
}
|
|
2113
|
+
function estimatePropsSize(props, block) {
|
|
2114
|
+
let size = 0;
|
|
2115
|
+
if (props) {
|
|
2116
|
+
for (const [key, value] of Object.entries(props)) {
|
|
2117
|
+
if (key === "block") continue;
|
|
2118
|
+
if (typeof value === "string") {
|
|
2119
|
+
size += value.length;
|
|
2120
|
+
} else if (typeof value === "number") {
|
|
2121
|
+
size += 8;
|
|
2122
|
+
} else if (typeof value === "boolean") {
|
|
2123
|
+
size += 4;
|
|
2124
|
+
} else if (Array.isArray(value)) {
|
|
2125
|
+
size += value.length * 4;
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
if (block) {
|
|
2130
|
+
size += estimateBlockPayloadSize(block);
|
|
2131
|
+
}
|
|
2132
|
+
return size;
|
|
2133
|
+
}
|
|
2134
|
+
function bucketBySize(size) {
|
|
2135
|
+
if (size < 1024) return "<1k";
|
|
2136
|
+
if (size < 10 * 1024) return "1-10k";
|
|
2137
|
+
if (size < 50 * 1024) return "10-50k";
|
|
2138
|
+
if (size < 200 * 1024) return "50-200k";
|
|
2139
|
+
return ">=200k";
|
|
2140
|
+
}
|
|
2141
|
+
function debugCodeBlock(event, payload) {
|
|
2142
|
+
if (!CODE_BLOCK_DEBUG_ENABLED) return;
|
|
2143
|
+
try {
|
|
2144
|
+
console.debug(`[renderer-store][code-block] ${event}`, payload);
|
|
2145
|
+
} catch {
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
function createRootRecord() {
|
|
2149
|
+
return {
|
|
2150
|
+
id: import_core4.PATCH_ROOT_ID,
|
|
2151
|
+
type: "__root__",
|
|
2152
|
+
parentId: null,
|
|
2153
|
+
children: [],
|
|
2154
|
+
props: {},
|
|
2155
|
+
version: 0,
|
|
2156
|
+
blockEpoch: 0
|
|
2157
|
+
};
|
|
2158
|
+
}
|
|
2159
|
+
function readNumericEpoch(value) {
|
|
2160
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
2161
|
+
return void 0;
|
|
2162
|
+
}
|
|
2163
|
+
return value;
|
|
2164
|
+
}
|
|
2165
|
+
function readSnapshotBlockEpoch(snapshot) {
|
|
2166
|
+
return readNumericEpoch(snapshot.meta?.blockEpoch);
|
|
2167
|
+
}
|
|
2168
|
+
function readPatchBlockEpoch(patch) {
|
|
2169
|
+
if (patch.op === "setHTML") {
|
|
2170
|
+
return readNumericEpoch(patch.patchMeta?.blockEpoch);
|
|
2171
|
+
}
|
|
2172
|
+
return readNumericEpoch(patch.meta?.blockEpoch);
|
|
2173
|
+
}
|
|
2174
|
+
function readPatchParseEpoch(patch) {
|
|
2175
|
+
if (patch.op === "setHTML") {
|
|
2176
|
+
return readNumericEpoch(patch.patchMeta?.parseEpoch);
|
|
2177
|
+
}
|
|
2178
|
+
return readNumericEpoch(patch.meta?.parseEpoch);
|
|
2179
|
+
}
|
|
2180
|
+
function readPatchTx(patch) {
|
|
2181
|
+
if (patch.op === "setHTML") {
|
|
2182
|
+
return readNumericEpoch(patch.patchMeta?.tx);
|
|
2183
|
+
}
|
|
2184
|
+
return readNumericEpoch(patch.meta?.tx);
|
|
2185
|
+
}
|
|
2186
|
+
function assignSubtreeBlockEpoch(map, rootId, blockEpoch, touched) {
|
|
2187
|
+
const root = map.get(rootId);
|
|
2188
|
+
if (!root) return;
|
|
2189
|
+
const stack = [root];
|
|
2190
|
+
while (stack.length > 0) {
|
|
2191
|
+
const node = stack.pop();
|
|
2192
|
+
if (!node) continue;
|
|
2193
|
+
if (node.blockEpoch !== blockEpoch) {
|
|
2194
|
+
node.blockEpoch = blockEpoch;
|
|
2195
|
+
node.version += 1;
|
|
2196
|
+
touched?.add(node.id);
|
|
2197
|
+
}
|
|
2198
|
+
for (const childId of node.children) {
|
|
2199
|
+
const child = map.get(childId);
|
|
2200
|
+
if (child) {
|
|
2201
|
+
stack.push(child);
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
function ensureArray(value) {
|
|
2207
|
+
return Array.isArray(value) ? value : [];
|
|
2208
|
+
}
|
|
2209
|
+
function shallowEqualRecords(a, b) {
|
|
2210
|
+
if (a === b) return true;
|
|
2211
|
+
if (!a || !b) {
|
|
2212
|
+
return !a && !b;
|
|
2213
|
+
}
|
|
2214
|
+
const keysA = Object.keys(a);
|
|
2215
|
+
const keysB = Object.keys(b);
|
|
2216
|
+
if (keysA.length !== keysB.length) return false;
|
|
2217
|
+
for (const key of keysA) {
|
|
2218
|
+
if (!Object.prototype.hasOwnProperty.call(b, key)) {
|
|
2219
|
+
return false;
|
|
2220
|
+
}
|
|
2221
|
+
if (a[key] !== b[key]) {
|
|
2222
|
+
return false;
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
return true;
|
|
2226
|
+
}
|
|
2227
|
+
function ensureUniqueChildren(parent, touched) {
|
|
2228
|
+
if (!parent || parent.children.length <= 1) {
|
|
2229
|
+
return;
|
|
2230
|
+
}
|
|
2231
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2232
|
+
const deduped = [];
|
|
2233
|
+
let mutated = false;
|
|
2234
|
+
for (const childId of parent.children) {
|
|
2235
|
+
if (seen.has(childId)) {
|
|
2236
|
+
mutated = true;
|
|
2237
|
+
continue;
|
|
2238
|
+
}
|
|
2239
|
+
seen.add(childId);
|
|
2240
|
+
deduped.push(childId);
|
|
2241
|
+
}
|
|
2242
|
+
if (mutated) {
|
|
2243
|
+
parent.children = deduped;
|
|
2244
|
+
parent.version++;
|
|
2245
|
+
if (touched) {
|
|
2246
|
+
touched.add(parent.id);
|
|
2247
|
+
}
|
|
2248
|
+
console.error("[renderer-store] removed duplicate children", {
|
|
2249
|
+
parentId: parent.id,
|
|
2250
|
+
deduped: parent.children.slice()
|
|
2251
|
+
});
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
function parseLineIndexFromId(id) {
|
|
2255
|
+
const match = id.match(/::line:(\d+)$/);
|
|
2256
|
+
if (!match) return Number.NaN;
|
|
2257
|
+
const value = Number(match[1]);
|
|
2258
|
+
return Number.isFinite(value) ? value : Number.NaN;
|
|
2259
|
+
}
|
|
2260
|
+
function escapeHtml2(value) {
|
|
2261
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2262
|
+
}
|
|
2263
|
+
function stripOuterLineSpan(html) {
|
|
2264
|
+
if (!html) return null;
|
|
2265
|
+
const openTagMatch = html.match(/<span[^>]*class="[^"]*\bline\b[^"]*"[^>]*>/i);
|
|
2266
|
+
if (!openTagMatch) {
|
|
2267
|
+
return null;
|
|
2268
|
+
}
|
|
2269
|
+
const openTag = openTagMatch[0];
|
|
2270
|
+
const startIndex = html.indexOf(openTag);
|
|
2271
|
+
if (startIndex === -1) {
|
|
2272
|
+
return null;
|
|
2273
|
+
}
|
|
2274
|
+
const contentStart = startIndex + openTag.length;
|
|
2275
|
+
const endIndex = html.lastIndexOf("</span>");
|
|
2276
|
+
if (endIndex === -1 || endIndex < contentStart) {
|
|
2277
|
+
return null;
|
|
2278
|
+
}
|
|
2279
|
+
return html.slice(contentStart, endIndex);
|
|
2280
|
+
}
|
|
2281
|
+
function sanitizeLineInnerHtml(innerHtml, fallbackText) {
|
|
2282
|
+
if (innerHtml && innerHtml.trim().length > 0) {
|
|
2283
|
+
const wrapped = `<span class="line">${innerHtml}</span>`;
|
|
2284
|
+
const sanitized = (0, import_core4.sanitizeCodeHTML)(wrapped);
|
|
2285
|
+
const inner = stripOuterLineSpan(typeof sanitized === "string" ? sanitized : String(sanitized));
|
|
2286
|
+
if (inner !== null) {
|
|
2287
|
+
return inner;
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
return escapeHtml2(fallbackText);
|
|
2291
|
+
}
|
|
2292
|
+
function normalizeCodeBlockChildren(nodes, parent, touched) {
|
|
2293
|
+
if (parent.type !== "code") return;
|
|
2294
|
+
const previousChildren = parent.children.slice();
|
|
2295
|
+
const originalOrder = /* @__PURE__ */ new Map();
|
|
2296
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
2297
|
+
const codeLineNodes = [];
|
|
2298
|
+
const anomalies = [];
|
|
2299
|
+
for (let idx = 0; idx < previousChildren.length; idx++) {
|
|
2300
|
+
const childId = previousChildren[idx];
|
|
2301
|
+
const node = nodes.get(childId);
|
|
2302
|
+
if (!node) {
|
|
2303
|
+
anomalies.push(`missing:${childId}`);
|
|
2304
|
+
continue;
|
|
2305
|
+
}
|
|
2306
|
+
if (node.parentId !== parent.id) {
|
|
2307
|
+
node.parentId = parent.id;
|
|
2308
|
+
node.version++;
|
|
2309
|
+
touched.add(node.id);
|
|
2310
|
+
}
|
|
2311
|
+
if (node.type !== "code-line") {
|
|
2312
|
+
anomalies.push(`type-mismatch:${node.id}:${node.type}`);
|
|
2313
|
+
nodes.delete(node.id);
|
|
2314
|
+
deleteSnapshotCacheEntry(nodes, node.id);
|
|
2315
|
+
touched.add(node.id);
|
|
2316
|
+
continue;
|
|
2317
|
+
}
|
|
2318
|
+
if (seenIds.has(childId)) {
|
|
2319
|
+
anomalies.push(`duplicate-id:${childId}`);
|
|
2320
|
+
continue;
|
|
2321
|
+
}
|
|
2322
|
+
seenIds.add(childId);
|
|
2323
|
+
codeLineNodes.push(node);
|
|
2324
|
+
originalOrder.set(node.id, idx);
|
|
2325
|
+
}
|
|
2326
|
+
if (anomalies.length > 0) {
|
|
2327
|
+
debugCodeBlock("code-block-anomalies", {
|
|
2328
|
+
parentId: parent.id,
|
|
2329
|
+
anomalies,
|
|
2330
|
+
children: previousChildren
|
|
2331
|
+
});
|
|
2332
|
+
if (anomalies.some((entry) => entry.startsWith("duplicate-id"))) {
|
|
2333
|
+
console.warn("[renderer-store] duplicate code-line ids detected", {
|
|
2334
|
+
parentId: parent.id,
|
|
2335
|
+
anomalies,
|
|
2336
|
+
children: previousChildren
|
|
2337
|
+
});
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
const sorted = codeLineNodes.slice().sort((a, b) => {
|
|
2341
|
+
const ai = typeof a.props?.index === "number" ? a.props.index : parseLineIndexFromId(a.id);
|
|
2342
|
+
const bi = typeof b.props?.index === "number" ? b.props.index : parseLineIndexFromId(b.id);
|
|
2343
|
+
if (Number.isFinite(ai) && Number.isFinite(bi) && ai !== bi) {
|
|
2344
|
+
return ai - bi;
|
|
2345
|
+
}
|
|
2346
|
+
const ao = originalOrder.get(a.id) ?? 0;
|
|
2347
|
+
const bo = originalOrder.get(b.id) ?? 0;
|
|
2348
|
+
return ao - bo;
|
|
2349
|
+
});
|
|
2350
|
+
const nextChildren = [];
|
|
2351
|
+
let mutated = false;
|
|
2352
|
+
for (let idx = 0; idx < sorted.length; idx++) {
|
|
2353
|
+
const node = sorted[idx];
|
|
2354
|
+
const desiredId = `${parent.id}::line:${idx}`;
|
|
2355
|
+
const existing = nodes.get(desiredId);
|
|
2356
|
+
if (existing && existing !== node) {
|
|
2357
|
+
nodes.delete(desiredId);
|
|
2358
|
+
deleteSnapshotCacheEntry(nodes, desiredId);
|
|
2359
|
+
touched.add(desiredId);
|
|
2360
|
+
mutated = true;
|
|
2361
|
+
}
|
|
2362
|
+
if (node.id !== desiredId) {
|
|
2363
|
+
const previousId = node.id;
|
|
2364
|
+
nodes.delete(node.id);
|
|
2365
|
+
deleteSnapshotCacheEntry(nodes, previousId);
|
|
2366
|
+
node.id = desiredId;
|
|
2367
|
+
nodes.set(desiredId, node);
|
|
2368
|
+
touched.add(desiredId);
|
|
2369
|
+
mutated = true;
|
|
2370
|
+
} else if (!nodes.has(desiredId)) {
|
|
2371
|
+
nodes.set(desiredId, node);
|
|
2372
|
+
}
|
|
2373
|
+
if (node.parentId !== parent.id) {
|
|
2374
|
+
node.parentId = parent.id;
|
|
2375
|
+
node.version++;
|
|
2376
|
+
touched.add(node.id);
|
|
2377
|
+
mutated = true;
|
|
2378
|
+
}
|
|
2379
|
+
const incoming = { index: idx, text: node.props?.text, html: node.props?.html };
|
|
2380
|
+
if (Object.prototype.hasOwnProperty.call(node.props ?? {}, "tokens")) {
|
|
2381
|
+
incoming.tokens = node.props.tokens;
|
|
2382
|
+
}
|
|
2383
|
+
const normalizedProps = normalizeCodeLineProps(incoming, node.props);
|
|
2384
|
+
if (normalizedProps !== node.props) {
|
|
2385
|
+
node.props = normalizedProps;
|
|
2386
|
+
node.version++;
|
|
2387
|
+
touched.add(node.id);
|
|
2388
|
+
mutated = true;
|
|
2389
|
+
}
|
|
2390
|
+
nextChildren.push(desiredId);
|
|
2391
|
+
}
|
|
2392
|
+
const allowed = new Set(nextChildren);
|
|
2393
|
+
for (const [id, record] of nodes) {
|
|
2394
|
+
if (record.parentId === parent.id && record.type === "code-line" && !allowed.has(id)) {
|
|
2395
|
+
nodes.delete(id);
|
|
2396
|
+
deleteSnapshotCacheEntry(nodes, id);
|
|
2397
|
+
touched.add(id);
|
|
2398
|
+
mutated = true;
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
if (!arraysEqual(previousChildren, nextChildren)) {
|
|
2402
|
+
parent.children = nextChildren;
|
|
2403
|
+
parent.version++;
|
|
2404
|
+
touched.add(parent.id);
|
|
2405
|
+
mutated = true;
|
|
2406
|
+
}
|
|
2407
|
+
const duplicateIds = [];
|
|
2408
|
+
const postSeenIds = /* @__PURE__ */ new Set();
|
|
2409
|
+
for (const childId of parent.children) {
|
|
2410
|
+
if (postSeenIds.has(childId)) {
|
|
2411
|
+
duplicateIds.push(childId);
|
|
2412
|
+
} else {
|
|
2413
|
+
postSeenIds.add(childId);
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
if (duplicateIds.length > 0) {
|
|
2417
|
+
console.error("[renderer-store] duplicate code-line children detected post-normalize", {
|
|
2418
|
+
parentId: parent.id,
|
|
2419
|
+
duplicates: duplicateIds,
|
|
2420
|
+
children: parent.children.slice()
|
|
2421
|
+
});
|
|
2422
|
+
}
|
|
2423
|
+
if (mutated) {
|
|
2424
|
+
debugCodeBlock("code-block-normalized", {
|
|
2425
|
+
parentId: parent.id,
|
|
2426
|
+
children: parent.children.slice()
|
|
2427
|
+
});
|
|
2428
|
+
}
|
|
2429
|
+
if (CODE_BLOCK_VALIDATION_ENABLED) {
|
|
2430
|
+
const validation = validateCodeBlockChildren(nodes, parent);
|
|
2431
|
+
if (!validation.ok) {
|
|
2432
|
+
debugCodeBlock("code-block-validation-failed", {
|
|
2433
|
+
parentId: parent.id,
|
|
2434
|
+
issues: validation.issues
|
|
2435
|
+
});
|
|
2436
|
+
if (!rebuildCodeBlockFromSnapshot(nodes, parent, touched, validation.issues)) {
|
|
2437
|
+
console.warn("[renderer-store] unable to fully normalize code block", {
|
|
2438
|
+
parentId: parent.id,
|
|
2439
|
+
issues: validation.issues
|
|
2440
|
+
});
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
function normalizeCodeLineProps(incoming, previous) {
|
|
2446
|
+
const previousIndex = typeof previous?.index === "number" ? previous?.index : 0;
|
|
2447
|
+
const index = typeof incoming.index === "number" ? incoming.index : previousIndex;
|
|
2448
|
+
const previousText = typeof previous?.text === "string" ? previous?.text : "";
|
|
2449
|
+
const previousHtml = typeof previous?.html === "string" ? previous?.html : null;
|
|
2450
|
+
const previousTokens = Object.prototype.hasOwnProperty.call(previous ?? {}, "tokens") ? previous?.tokens : void 0;
|
|
2451
|
+
const hasIncomingText = typeof incoming.text === "string";
|
|
2452
|
+
const hasIncomingHtml = typeof incoming.html === "string";
|
|
2453
|
+
const hasIncomingTokens = Object.prototype.hasOwnProperty.call(incoming, "tokens");
|
|
2454
|
+
const incomingTokens = hasIncomingTokens ? incoming.tokens : void 0;
|
|
2455
|
+
const rawText = hasIncomingText ? incoming.text : previousText;
|
|
2456
|
+
if (previous && !hasIncomingHtml && !hasIncomingTokens && index === previousIndex && (!hasIncomingText || rawText === previousText)) {
|
|
2457
|
+
return previous;
|
|
2458
|
+
}
|
|
2459
|
+
if (previous && hasIncomingHtml && !hasIncomingTokens && incoming.html === previousHtml && index === previousIndex && (!hasIncomingText || rawText === previousText)) {
|
|
2460
|
+
return previous;
|
|
2461
|
+
}
|
|
2462
|
+
let highlight = null;
|
|
2463
|
+
if (hasIncomingHtml) {
|
|
2464
|
+
highlight = incoming.html;
|
|
2465
|
+
} else if (!hasIncomingText || rawText === previousText) {
|
|
2466
|
+
highlight = previousHtml;
|
|
2467
|
+
}
|
|
2468
|
+
let tokens = void 0;
|
|
2469
|
+
if (hasIncomingTokens) {
|
|
2470
|
+
tokens = incomingTokens ?? null;
|
|
2471
|
+
} else if (!hasIncomingText || rawText === previousText) {
|
|
2472
|
+
tokens = previousTokens;
|
|
2473
|
+
} else {
|
|
2474
|
+
tokens = null;
|
|
2475
|
+
}
|
|
2476
|
+
return createCodeLineProps(index, rawText, highlight, tokens);
|
|
2477
|
+
}
|
|
2478
|
+
function createCodeLineProps(index, text, highlight, tokens) {
|
|
2479
|
+
const safeText = typeof text === "string" ? text : "";
|
|
2480
|
+
const hasIncomingHighlight = typeof highlight === "string" && highlight.trim().length > 0;
|
|
2481
|
+
const sanitized = hasIncomingHighlight ? sanitizeLineInnerHtml(highlight, safeText) : null;
|
|
2482
|
+
const hasRenderableTokens = tokens !== void 0 && tokens !== null;
|
|
2483
|
+
const html = sanitized && sanitized.length >= safeText.length ? sanitized : hasRenderableTokens ? null : escapeHtml2(safeText);
|
|
2484
|
+
const props = {
|
|
2485
|
+
index,
|
|
2486
|
+
text: safeText,
|
|
2487
|
+
html
|
|
2488
|
+
};
|
|
2489
|
+
if (tokens !== void 0) {
|
|
2490
|
+
props.tokens = tokens;
|
|
2491
|
+
}
|
|
2492
|
+
return props;
|
|
2493
|
+
}
|
|
2494
|
+
function clampIndex(index, length) {
|
|
2495
|
+
if (!Number.isFinite(index)) return 0;
|
|
2496
|
+
if (length <= 0) return 0;
|
|
2497
|
+
return Math.max(0, Math.min(index, length));
|
|
2498
|
+
}
|
|
2499
|
+
var INLINE_SEGMENT_TYPES = /* @__PURE__ */ new Set(["paragraph-text", "blockquote-text", "list-item-text"]);
|
|
2500
|
+
function isInlineSegmentType(type) {
|
|
2501
|
+
return INLINE_SEGMENT_TYPES.has(type);
|
|
2502
|
+
}
|
|
2503
|
+
function mergeInlineSegmentProps(previous, incoming) {
|
|
2504
|
+
const result = previous ? { ...previous } : {};
|
|
2505
|
+
for (const [key, value] of Object.entries(incoming)) {
|
|
2506
|
+
if (value === void 0) {
|
|
2507
|
+
delete result[key];
|
|
2508
|
+
} else {
|
|
2509
|
+
result[key] = value;
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
const inlineProvided = Object.prototype.hasOwnProperty.call(incoming, "inline");
|
|
2513
|
+
if (inlineProvided) {
|
|
2514
|
+
const inlineValue = Array.isArray(incoming.inline) ? incoming.inline : void 0;
|
|
2515
|
+
if (inlineValue) {
|
|
2516
|
+
result.inline = inlineValue;
|
|
2517
|
+
} else {
|
|
2518
|
+
result.inline = void 0;
|
|
2519
|
+
}
|
|
2520
|
+
} else if (previous && Array.isArray(previous.inline) && !Array.isArray(result.inline)) {
|
|
2521
|
+
result.inline = previous.inline;
|
|
2522
|
+
}
|
|
2523
|
+
const textProvided = Object.prototype.hasOwnProperty.call(incoming, "text");
|
|
2524
|
+
if (textProvided) {
|
|
2525
|
+
result.text = typeof incoming.text === "string" ? incoming.text : "";
|
|
2526
|
+
} else if (previous && typeof previous.text === "string" && typeof result.text !== "string") {
|
|
2527
|
+
result.text = previous.text;
|
|
2528
|
+
}
|
|
2529
|
+
return result;
|
|
2530
|
+
}
|
|
2531
|
+
function applyCodeBlockMetadata(record) {
|
|
2532
|
+
const block = record.block;
|
|
2533
|
+
const highlightedHtml = block?.payload.highlightedHtml ?? "";
|
|
2534
|
+
const lang = typeof block?.payload.meta?.lang === "string" ? block?.payload.meta?.lang : record.props?.lang;
|
|
2535
|
+
let { preAttrs, codeAttrs } = (0, import_core4.extractCodeWrapperAttributes)(highlightedHtml);
|
|
2536
|
+
if (!preAttrs || !codeAttrs) {
|
|
2537
|
+
const defaults = (0, import_core4.getDefaultCodeWrapperAttributes)(typeof lang === "string" ? lang : void 0);
|
|
2538
|
+
preAttrs = preAttrs ?? defaults.preAttrs;
|
|
2539
|
+
codeAttrs = codeAttrs ?? defaults.codeAttrs;
|
|
2540
|
+
}
|
|
2541
|
+
record.props = {
|
|
2542
|
+
...record.props ?? {},
|
|
2543
|
+
lang,
|
|
2544
|
+
preAttrs,
|
|
2545
|
+
codeAttrs
|
|
2546
|
+
};
|
|
2547
|
+
}
|
|
2548
|
+
function normalizeNodeProps(snapshot) {
|
|
2549
|
+
if (snapshot.type === "code-line") {
|
|
2550
|
+
const index = typeof snapshot.props?.index === "number" ? snapshot.props?.index : 0;
|
|
2551
|
+
const text = typeof snapshot.props?.text === "string" ? snapshot.props?.text : "";
|
|
2552
|
+
const html = typeof snapshot.props?.html === "string" ? snapshot.props?.html : null;
|
|
2553
|
+
const tokens = Object.prototype.hasOwnProperty.call(snapshot.props ?? {}, "tokens") ? snapshot.props?.tokens : void 0;
|
|
2554
|
+
return createCodeLineProps(index, text, html, tokens);
|
|
2555
|
+
}
|
|
2556
|
+
if (isInlineSegmentType(snapshot.type)) {
|
|
2557
|
+
const incoming = snapshot.props ? { ...snapshot.props } : {};
|
|
2558
|
+
return mergeInlineSegmentProps(void 0, incoming);
|
|
2559
|
+
}
|
|
2560
|
+
return snapshot.props ? { ...snapshot.props } : {};
|
|
2561
|
+
}
|
|
2562
|
+
function validateCodeBlockChildren(nodes, parent) {
|
|
2563
|
+
const issues = [];
|
|
2564
|
+
const expectedPrefix = `${parent.id}::line:`;
|
|
2565
|
+
const allowed = /* @__PURE__ */ new Set();
|
|
2566
|
+
for (let idx = 0; idx < parent.children.length; idx++) {
|
|
2567
|
+
const childId = parent.children[idx];
|
|
2568
|
+
allowed.add(childId);
|
|
2569
|
+
const node = nodes.get(childId);
|
|
2570
|
+
if (!node) {
|
|
2571
|
+
issues.push({ kind: "missing-node", id: childId, expected: `${expectedPrefix}${idx}` });
|
|
2572
|
+
continue;
|
|
2573
|
+
}
|
|
2574
|
+
if (node.parentId !== parent.id) {
|
|
2575
|
+
issues.push({ kind: "parent-mismatch", id: childId, expected: parent.id, actual: node.parentId });
|
|
2576
|
+
}
|
|
2577
|
+
if (node.type !== "code-line") {
|
|
2578
|
+
issues.push({ kind: "unexpected-type", id: childId, expected: "code-line", actual: node.type });
|
|
2579
|
+
}
|
|
2580
|
+
const expectedId = `${expectedPrefix}${idx}`;
|
|
2581
|
+
if (childId !== expectedId) {
|
|
2582
|
+
issues.push({ kind: "id-mismatch", id: childId, expected: expectedId, actual: childId });
|
|
2583
|
+
}
|
|
2584
|
+
const indexProp = typeof node.props?.index === "number" ? node.props.index : null;
|
|
2585
|
+
if (indexProp !== idx) {
|
|
2586
|
+
issues.push({ kind: "index-mismatch", id: childId, expected: idx, actual: indexProp });
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
for (const [id, record] of nodes) {
|
|
2590
|
+
if (record.parentId === parent.id && record.type === "code-line" && !allowed.has(id)) {
|
|
2591
|
+
issues.push({ kind: "dangling-node", id });
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
if (parent.block?.isFinalized) {
|
|
2595
|
+
const snapshot = (0, import_core4.createBlockSnapshot)((0, import_core4.cloneBlock)(parent.block));
|
|
2596
|
+
const expectedChildren = ensureArray(snapshot.children).filter((child) => child.type === "code-line");
|
|
2597
|
+
if (expectedChildren.length !== parent.children.length) {
|
|
2598
|
+
issues.push({
|
|
2599
|
+
kind: "line-count-mismatch",
|
|
2600
|
+
id: parent.id,
|
|
2601
|
+
expected: expectedChildren.length,
|
|
2602
|
+
actual: parent.children.length
|
|
2603
|
+
});
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
return { ok: issues.length === 0, issues };
|
|
2607
|
+
}
|
|
2608
|
+
function rebuildCodeBlockFromSnapshot(nodes, parent, touched, issues) {
|
|
2609
|
+
if (!parent.block) {
|
|
2610
|
+
return false;
|
|
2611
|
+
}
|
|
2612
|
+
const ancestor = parent.parentId ? nodes.get(parent.parentId) : void 0;
|
|
2613
|
+
if (!ancestor) {
|
|
2614
|
+
return false;
|
|
2615
|
+
}
|
|
2616
|
+
const index = ancestor.children.indexOf(parent.id);
|
|
2617
|
+
if (index === -1) {
|
|
2618
|
+
return false;
|
|
2619
|
+
}
|
|
2620
|
+
const snapshot = (0, import_core4.createBlockSnapshot)((0, import_core4.cloneBlock)(parent.block));
|
|
2621
|
+
removeSubtree(nodes, parent.id, touched);
|
|
2622
|
+
const inserted = insertSnapshotAt(nodes, ancestor, index, snapshot, touched, parent.blockEpoch);
|
|
2623
|
+
if (!inserted) {
|
|
2624
|
+
return false;
|
|
2625
|
+
}
|
|
2626
|
+
for (const id of inserted.insertedIds) {
|
|
2627
|
+
touched.add(id);
|
|
2628
|
+
}
|
|
2629
|
+
ancestor.version++;
|
|
2630
|
+
touched.add(ancestor.id);
|
|
2631
|
+
console.warn("[renderer-store] rebuilt code block from snapshot", {
|
|
2632
|
+
parentId: parent.id,
|
|
2633
|
+
issues
|
|
2634
|
+
});
|
|
2635
|
+
return true;
|
|
2636
|
+
}
|
|
2637
|
+
function arraysEqual(a, b) {
|
|
2638
|
+
if (a === b) return true;
|
|
2639
|
+
if (a.length !== b.length) return false;
|
|
2640
|
+
for (let i = 0; i < a.length; i++) {
|
|
2641
|
+
if (a[i] !== b[i]) return false;
|
|
2642
|
+
}
|
|
2643
|
+
return true;
|
|
2644
|
+
}
|
|
2645
|
+
function expectedCodeLineId(parentId, index) {
|
|
2646
|
+
return `${parentId}::line:${index}`;
|
|
2647
|
+
}
|
|
2648
|
+
function collectStoreInvariantViolations(nodes) {
|
|
2649
|
+
const violations = [];
|
|
2650
|
+
for (const [id, node] of nodes) {
|
|
2651
|
+
if (node.type === "__root__") continue;
|
|
2652
|
+
if (node.children.length > 1) {
|
|
2653
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2654
|
+
for (const childId of node.children) {
|
|
2655
|
+
if (seen.has(childId)) {
|
|
2656
|
+
violations.push(`duplicate child id:${id}:${childId}`);
|
|
2657
|
+
} else {
|
|
2658
|
+
seen.add(childId);
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
if (node.type === "list") {
|
|
2663
|
+
const parent = node.parentId ? nodes.get(node.parentId) : void 0;
|
|
2664
|
+
if (parent?.type === "list-item" && node.children.length === 0) {
|
|
2665
|
+
violations.push(`empty nested list:${id}`);
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
if (node.type === "code") {
|
|
2669
|
+
for (let index = 0; index < node.children.length; index += 1) {
|
|
2670
|
+
const childId = node.children[index];
|
|
2671
|
+
const child = nodes.get(childId);
|
|
2672
|
+
if (!child) {
|
|
2673
|
+
violations.push(`missing code-line node:${node.id}:${childId}`);
|
|
2674
|
+
continue;
|
|
2675
|
+
}
|
|
2676
|
+
if (child.type !== "code-line") {
|
|
2677
|
+
violations.push(`non-code child in code block:${node.id}:${child.id}:${child.type}`);
|
|
2678
|
+
continue;
|
|
2679
|
+
}
|
|
2680
|
+
const expectedId = expectedCodeLineId(node.id, index);
|
|
2681
|
+
if (child.id !== expectedId) {
|
|
2682
|
+
violations.push(`code-line id mismatch:${node.id}:${child.id}:${expectedId}`);
|
|
2683
|
+
}
|
|
2684
|
+
const indexProp = typeof child.props?.index === "number" ? child.props.index : null;
|
|
2685
|
+
if (indexProp !== index) {
|
|
2686
|
+
violations.push(`code-line index mismatch:${node.id}:${child.id}:${indexProp}:${index}`);
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
return violations;
|
|
2692
|
+
}
|
|
2693
|
+
function buildNodeFromSnapshot(map, snapshot, parentId, blockEpoch) {
|
|
2694
|
+
const record = {
|
|
2695
|
+
id: snapshot.id,
|
|
2696
|
+
type: snapshot.type,
|
|
2697
|
+
parentId,
|
|
2698
|
+
children: [],
|
|
2699
|
+
props: normalizeNodeProps(snapshot),
|
|
2700
|
+
meta: snapshot.meta ? { ...snapshot.meta } : void 0,
|
|
2701
|
+
range: snapshot.range ? { ...snapshot.range } : void 0,
|
|
2702
|
+
version: 0,
|
|
2703
|
+
blockEpoch,
|
|
2704
|
+
block: snapshotToBlock(snapshot) ?? void 0
|
|
2705
|
+
};
|
|
2706
|
+
if (record.type === "code") {
|
|
2707
|
+
applyCodeBlockMetadata(record);
|
|
2708
|
+
}
|
|
2709
|
+
map.set(record.id, record);
|
|
2710
|
+
const childSnapshots = ensureArray(snapshot.children);
|
|
2711
|
+
for (const child of childSnapshots) {
|
|
2712
|
+
const childRecord = buildNodeFromSnapshot(map, child, record.id, blockEpoch);
|
|
2713
|
+
record.children.push(childRecord.id);
|
|
2714
|
+
}
|
|
2715
|
+
return record;
|
|
2716
|
+
}
|
|
2717
|
+
function removeSubtree(map, id, touched) {
|
|
2718
|
+
const record = map.get(id);
|
|
2719
|
+
if (!record) return;
|
|
2720
|
+
const stack = [record];
|
|
2721
|
+
const affectedParents = /* @__PURE__ */ new Set();
|
|
2722
|
+
while (stack.length > 0) {
|
|
2723
|
+
const node = stack.pop();
|
|
2724
|
+
if (!node) {
|
|
2725
|
+
continue;
|
|
2726
|
+
}
|
|
2727
|
+
if (node.parentId) {
|
|
2728
|
+
const parent = map.get(node.parentId);
|
|
2729
|
+
if (parent) {
|
|
2730
|
+
const idx = parent.children.indexOf(node.id);
|
|
2731
|
+
if (idx !== -1) {
|
|
2732
|
+
parent.children.splice(idx, 1);
|
|
2733
|
+
parent.version++;
|
|
2734
|
+
touched.add(parent.id);
|
|
2735
|
+
affectedParents.add(parent.id);
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
map.delete(node.id);
|
|
2740
|
+
deleteSnapshotCacheEntry(map, node.id);
|
|
2741
|
+
touched.add(node.id);
|
|
2742
|
+
for (const childId of node.children) {
|
|
2743
|
+
const child = map.get(childId);
|
|
2744
|
+
if (child) stack.push(child);
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
for (const parentId of affectedParents) {
|
|
2748
|
+
const parent = map.get(parentId);
|
|
2749
|
+
if (parent && parent.type === "code") {
|
|
2750
|
+
normalizeCodeBlockChildren(map, parent, touched);
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
function convertNodeToBlocks(map, root) {
|
|
2755
|
+
const result = [];
|
|
2756
|
+
for (const childId of root.children) {
|
|
2757
|
+
const child = map.get(childId);
|
|
2758
|
+
if (!child || !child.block) continue;
|
|
2759
|
+
result.push((0, import_core4.cloneBlock)(child.block));
|
|
2760
|
+
}
|
|
2761
|
+
return result;
|
|
2762
|
+
}
|
|
2763
|
+
function resolveParent(map, path) {
|
|
2764
|
+
if (path.nodeId) {
|
|
2765
|
+
return map.get(path.nodeId);
|
|
2766
|
+
}
|
|
2767
|
+
if (path.blockId === import_core4.PATCH_ROOT_ID) {
|
|
2768
|
+
return map.get(import_core4.PATCH_ROOT_ID);
|
|
2769
|
+
}
|
|
2770
|
+
return map.get(path.blockId);
|
|
2771
|
+
}
|
|
2772
|
+
function insertSnapshotAt(map, parent, index, snapshot, touched, blockEpoch) {
|
|
2773
|
+
if (!parent) return null;
|
|
2774
|
+
const existingIndex = parent.children.indexOf(snapshot.id);
|
|
2775
|
+
if (existingIndex !== -1) {
|
|
2776
|
+
removeSubtree(map, parent.children[existingIndex], touched ?? /* @__PURE__ */ new Set());
|
|
2777
|
+
}
|
|
2778
|
+
const record = buildNodeFromSnapshot(map, snapshot, parent.id, blockEpoch ?? readSnapshotBlockEpoch(snapshot) ?? parent.blockEpoch);
|
|
2779
|
+
const insertedIds = [];
|
|
2780
|
+
const queue = [record];
|
|
2781
|
+
while (queue.length > 0) {
|
|
2782
|
+
const node = queue.pop();
|
|
2783
|
+
if (!node) continue;
|
|
2784
|
+
insertedIds.push(node.id);
|
|
2785
|
+
for (const childId of node.children) {
|
|
2786
|
+
const child = map.get(childId);
|
|
2787
|
+
if (child) queue.push(child);
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
const clampedIndex = Math.min(Math.max(index, 0), parent.children.length);
|
|
2791
|
+
parent.children.splice(clampedIndex, 0, record.id);
|
|
2792
|
+
parent.version++;
|
|
2793
|
+
ensureUniqueChildren(parent, touched);
|
|
2794
|
+
return { record, insertedIds };
|
|
2795
|
+
}
|
|
2796
|
+
function replaceSnapshotAt(map, parent, index, snapshot, touched, blockEpoch) {
|
|
2797
|
+
if (!parent) return;
|
|
2798
|
+
if (index < 0 || index >= parent.children.length) return;
|
|
2799
|
+
const targetId = parent.children[index];
|
|
2800
|
+
parent.children.splice(index, 1);
|
|
2801
|
+
removeSubtree(map, targetId, touched);
|
|
2802
|
+
const inserted = insertSnapshotAt(map, parent, index, snapshot, touched, blockEpoch);
|
|
2803
|
+
if (inserted) {
|
|
2804
|
+
for (const id of inserted.insertedIds) {
|
|
2805
|
+
touched.add(id);
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
if (parent.type === "code") {
|
|
2809
|
+
normalizeCodeBlockChildren(map, parent, touched);
|
|
2810
|
+
} else {
|
|
2811
|
+
ensureUniqueChildren(parent, touched);
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
function createRendererStore(initialBlocks = []) {
|
|
2815
|
+
let nodes = /* @__PURE__ */ new Map();
|
|
2816
|
+
let version = 0;
|
|
2817
|
+
let blocksCache = [];
|
|
2818
|
+
let blocksDirty = true;
|
|
2819
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2820
|
+
let notifyScheduled = false;
|
|
2821
|
+
let lastCoalescingMetrics = null;
|
|
2822
|
+
let invariantViolations = [];
|
|
2823
|
+
let transientDiagnostics = [];
|
|
2824
|
+
let nextBlockEpoch = 1;
|
|
2825
|
+
const debugCounters = {
|
|
2826
|
+
appendLineGuardRejected: 0,
|
|
2827
|
+
staleEpochRejected: 0,
|
|
2828
|
+
storeInvariantViolationCount: 0
|
|
2829
|
+
};
|
|
2830
|
+
const allocateBlockEpoch = (preferred) => {
|
|
2831
|
+
const resolved = readNumericEpoch(preferred);
|
|
2832
|
+
if (resolved !== void 0) {
|
|
2833
|
+
nextBlockEpoch = Math.max(nextBlockEpoch, resolved + 1);
|
|
2834
|
+
return resolved;
|
|
2835
|
+
}
|
|
2836
|
+
const epoch = nextBlockEpoch;
|
|
2837
|
+
nextBlockEpoch += 1;
|
|
2838
|
+
return epoch;
|
|
2839
|
+
};
|
|
2840
|
+
const getRaf = () => {
|
|
2841
|
+
if (typeof globalThis !== "undefined" && typeof globalThis.requestAnimationFrame === "function") {
|
|
2842
|
+
return globalThis.requestAnimationFrame;
|
|
2843
|
+
}
|
|
2844
|
+
return null;
|
|
2845
|
+
};
|
|
2846
|
+
function scheduleNotify() {
|
|
2847
|
+
if (notifyScheduled) return;
|
|
2848
|
+
notifyScheduled = true;
|
|
2849
|
+
const raf = getRaf();
|
|
2850
|
+
if (raf) {
|
|
2851
|
+
raf(() => {
|
|
2852
|
+
notifyScheduled = false;
|
|
2853
|
+
notify();
|
|
2854
|
+
});
|
|
2855
|
+
} else {
|
|
2856
|
+
setTimeout(() => {
|
|
2857
|
+
notifyScheduled = false;
|
|
2858
|
+
notify();
|
|
2859
|
+
}, 0);
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
function notify() {
|
|
2863
|
+
for (const listener of listeners) {
|
|
2864
|
+
try {
|
|
2865
|
+
listener();
|
|
2866
|
+
} catch (error) {
|
|
2867
|
+
console.error("RendererStore listener failed", error);
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
function appendDiagnostic(message) {
|
|
2872
|
+
transientDiagnostics = [...transientDiagnostics, message];
|
|
2873
|
+
invariantViolations = [...invariantViolations, message];
|
|
2874
|
+
}
|
|
2875
|
+
function rebuildFromBlocks(blocks) {
|
|
2876
|
+
clearSnapshotCaches(nodes);
|
|
2877
|
+
nodes = /* @__PURE__ */ new Map();
|
|
2878
|
+
const root = createRootRecord();
|
|
2879
|
+
nodes.set(root.id, root);
|
|
2880
|
+
root.children = [];
|
|
2881
|
+
lastCoalescingMetrics = null;
|
|
2882
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
2883
|
+
const block = (0, import_core4.cloneBlock)(blocks[i]);
|
|
2884
|
+
const snapshot = (0, import_core4.createBlockSnapshot)(block);
|
|
2885
|
+
insertSnapshotAt(nodes, root, root.children.length, snapshot, void 0, allocateBlockEpoch(readSnapshotBlockEpoch(snapshot)));
|
|
2886
|
+
}
|
|
2887
|
+
normalizeAllListDepths(nodes, /* @__PURE__ */ new Set(), import_core4.PATCH_ROOT_ID);
|
|
2888
|
+
transientDiagnostics = [];
|
|
2889
|
+
invariantViolations = collectStoreInvariantViolations(nodes);
|
|
2890
|
+
debugCounters.storeInvariantViolationCount += invariantViolations.length;
|
|
2891
|
+
version++;
|
|
2892
|
+
blocksDirty = true;
|
|
2893
|
+
scheduleNotify();
|
|
2894
|
+
}
|
|
2895
|
+
rebuildFromBlocks(initialBlocks);
|
|
2896
|
+
const store = {
|
|
2897
|
+
reset(nextBlocks) {
|
|
2898
|
+
rebuildFromBlocks(nextBlocks);
|
|
2899
|
+
},
|
|
2900
|
+
applyPatches(patches, options) {
|
|
2901
|
+
const touched = /* @__PURE__ */ new Set();
|
|
2902
|
+
if (!patches || patches.length === 0) {
|
|
2903
|
+
lastCoalescingMetrics = options?.metrics ?? null;
|
|
2904
|
+
return touched;
|
|
2905
|
+
}
|
|
2906
|
+
const perfEnabled = isPatchPerfDebugEnabled();
|
|
2907
|
+
const perfStart = perfEnabled ? getPerfNow() : 0;
|
|
2908
|
+
const opStats = perfEnabled ? {} : {};
|
|
2909
|
+
const setPropsByType = perfEnabled ? {} : {};
|
|
2910
|
+
const setPropsBySize = perfEnabled ? {} : {};
|
|
2911
|
+
let applyMs = 0;
|
|
2912
|
+
let listNormalizeMs = 0;
|
|
2913
|
+
let coalescedPatches = patches;
|
|
2914
|
+
let metrics = options?.metrics ?? null;
|
|
2915
|
+
if (!options?.coalesced) {
|
|
2916
|
+
const result = (0, import_core3.coalescePatchesWithMetrics)(patches, import_core3.DEFAULT_COALESCE_CONFIG);
|
|
2917
|
+
coalescedPatches = result.patches;
|
|
2918
|
+
metrics = result.metrics;
|
|
2919
|
+
}
|
|
2920
|
+
lastCoalescingMetrics = metrics;
|
|
2921
|
+
const root = nodes.get(import_core4.PATCH_ROOT_ID);
|
|
2922
|
+
if (!root) {
|
|
2923
|
+
nodes.set(import_core4.PATCH_ROOT_ID, createRootRecord());
|
|
2924
|
+
}
|
|
2925
|
+
let mutatedBlocks = false;
|
|
2926
|
+
let listDepthDirty = false;
|
|
2927
|
+
const listDepthDirtyNodes = /* @__PURE__ */ new Set();
|
|
2928
|
+
const debugListPatches = isListPatchDebugEnabled();
|
|
2929
|
+
const isListRelated = (type) => type === "list" || type === "list-item";
|
|
2930
|
+
const logListPatch = (label, details) => {
|
|
2931
|
+
if (!debugListPatches) return;
|
|
2932
|
+
try {
|
|
2933
|
+
console.debug(`[list-patch] ${label}`, details);
|
|
2934
|
+
} catch {
|
|
2935
|
+
}
|
|
2936
|
+
};
|
|
2937
|
+
const findNearestListNode = (nodeId) => {
|
|
2938
|
+
if (!nodeId) return void 0;
|
|
2939
|
+
let current = nodes.get(nodeId);
|
|
2940
|
+
while (current) {
|
|
2941
|
+
if (current.type === "list") return current;
|
|
2942
|
+
if (!current.parentId) return void 0;
|
|
2943
|
+
current = nodes.get(current.parentId);
|
|
2944
|
+
}
|
|
2945
|
+
return void 0;
|
|
2946
|
+
};
|
|
2947
|
+
const markListDepthDirty = (nodeId) => {
|
|
2948
|
+
const listNode = findNearestListNode(nodeId);
|
|
2949
|
+
if (listNode) {
|
|
2950
|
+
listDepthDirtyNodes.add(listNode.id);
|
|
2951
|
+
} else {
|
|
2952
|
+
listDepthDirty = true;
|
|
2953
|
+
}
|
|
2954
|
+
};
|
|
2955
|
+
const semanticTxEpochs = /* @__PURE__ */ new Map();
|
|
2956
|
+
const resolveBlockEpochTarget = (patch, at) => {
|
|
2957
|
+
if ((patch.op === "insertChild" || patch.op === "replaceChild") && at.blockId === import_core4.PATCH_ROOT_ID) {
|
|
2958
|
+
const nodeId = patch.node?.id;
|
|
2959
|
+
return nodeId ? nodes.get(nodeId) : void 0;
|
|
2960
|
+
}
|
|
2961
|
+
return nodes.get(at.blockId) ?? (at.nodeId ? nodes.get(at.nodeId) : void 0);
|
|
2962
|
+
};
|
|
2963
|
+
const rejectStaleEpochPatch = (patch, at) => {
|
|
2964
|
+
const expectedBlockEpoch = readPatchBlockEpoch(patch);
|
|
2965
|
+
const patchTx = readPatchTx(patch);
|
|
2966
|
+
if (expectedBlockEpoch === void 0) {
|
|
2967
|
+
return false;
|
|
2968
|
+
}
|
|
2969
|
+
if ((0, import_patch_batching.getPatchKind)(patch) !== "semantic" || !at) {
|
|
2970
|
+
return false;
|
|
2971
|
+
}
|
|
2972
|
+
const target = resolveBlockEpochTarget(patch, at);
|
|
2973
|
+
if (!target || target.blockEpoch === expectedBlockEpoch) {
|
|
2974
|
+
return false;
|
|
2975
|
+
}
|
|
2976
|
+
const txEpoch = at.blockId && patchTx !== void 0 ? semanticTxEpochs.get(at.blockId) : void 0;
|
|
2977
|
+
if (txEpoch && txEpoch.tx === patchTx && txEpoch.blockEpoch === target.blockEpoch) {
|
|
2978
|
+
return false;
|
|
2979
|
+
}
|
|
2980
|
+
debugCounters.staleEpochRejected += 1;
|
|
2981
|
+
appendDiagnostic(
|
|
2982
|
+
`stale epoch rejected:${patch.op}:${target.id}:expected=${expectedBlockEpoch}:actual=${target.blockEpoch}`
|
|
2983
|
+
);
|
|
2984
|
+
return true;
|
|
2985
|
+
};
|
|
2986
|
+
const advanceBlockEpoch = (blockId, touchedSet, preferredEpoch, tx) => {
|
|
2987
|
+
if (!blockId || blockId === import_core4.PATCH_ROOT_ID) {
|
|
2988
|
+
return null;
|
|
2989
|
+
}
|
|
2990
|
+
const blockRoot = nodes.get(blockId);
|
|
2991
|
+
if (!blockRoot) {
|
|
2992
|
+
return null;
|
|
2993
|
+
}
|
|
2994
|
+
const nextEpoch = allocateBlockEpoch(preferredEpoch);
|
|
2995
|
+
assignSubtreeBlockEpoch(nodes, blockRoot.id, nextEpoch, touchedSet);
|
|
2996
|
+
if (tx !== void 0) {
|
|
2997
|
+
semanticTxEpochs.set(blockRoot.id, { tx, blockEpoch: nextEpoch });
|
|
2998
|
+
}
|
|
2999
|
+
return nextEpoch;
|
|
3000
|
+
};
|
|
3001
|
+
const applyPropsUpdate = (at, props, preferredEpoch, tx, semanticUpdate = false) => {
|
|
3002
|
+
const perfStartLocal = perfEnabled ? getPerfNow() : 0;
|
|
3003
|
+
const targetId = at.nodeId ?? at.blockId;
|
|
3004
|
+
if (!targetId) return;
|
|
3005
|
+
let target = nodes.get(targetId);
|
|
3006
|
+
if (!target) return;
|
|
3007
|
+
const parentType = target.parentId ? nodes.get(target.parentId)?.type : void 0;
|
|
3008
|
+
if (debugListPatches && (isListRelated(target.type) || isListRelated(parentType))) {
|
|
3009
|
+
logListPatch("setProps", {
|
|
3010
|
+
id: target.id,
|
|
3011
|
+
type: target.type,
|
|
3012
|
+
parentType,
|
|
3013
|
+
keys: props ? Object.keys(props) : []
|
|
3014
|
+
});
|
|
3015
|
+
}
|
|
3016
|
+
let propsChanged = false;
|
|
3017
|
+
if (target.type === "code-line") {
|
|
3018
|
+
const incoming = {
|
|
3019
|
+
index: props?.index,
|
|
3020
|
+
text: props?.text,
|
|
3021
|
+
html: props?.html
|
|
3022
|
+
};
|
|
3023
|
+
if (Object.prototype.hasOwnProperty.call(props ?? {}, "tokens")) {
|
|
3024
|
+
incoming.tokens = props.tokens;
|
|
3025
|
+
}
|
|
3026
|
+
const normalized = normalizeCodeLineProps(incoming, target.props);
|
|
3027
|
+
if (normalized !== target.props) {
|
|
3028
|
+
target.props = normalized;
|
|
3029
|
+
propsChanged = true;
|
|
3030
|
+
}
|
|
3031
|
+
if (propsChanged) {
|
|
3032
|
+
if (semanticUpdate) {
|
|
3033
|
+
advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
|
|
3034
|
+
}
|
|
3035
|
+
target.version++;
|
|
3036
|
+
touched.add(target.id);
|
|
3037
|
+
const parent = target.parentId ? nodes.get(target.parentId) : void 0;
|
|
3038
|
+
if (parent) {
|
|
3039
|
+
parent.version++;
|
|
3040
|
+
touched.add(parent.id);
|
|
3041
|
+
if (parent.type === "code") {
|
|
3042
|
+
normalizeCodeBlockChildren(nodes, parent, touched);
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
if (perfEnabled) {
|
|
3047
|
+
const duration = getPerfNow() - perfStartLocal;
|
|
3048
|
+
const typeKey = target.type || "unknown";
|
|
3049
|
+
const sizeKey = bucketBySize(estimatePropsSize(props, void 0));
|
|
3050
|
+
const typeEntry = setPropsByType[typeKey] ?? { count: 0, durationMs: 0 };
|
|
3051
|
+
typeEntry.count += 1;
|
|
3052
|
+
typeEntry.durationMs += duration;
|
|
3053
|
+
setPropsByType[typeKey] = typeEntry;
|
|
3054
|
+
const sizeEntry = setPropsBySize[sizeKey] ?? { count: 0, durationMs: 0 };
|
|
3055
|
+
sizeEntry.count += 1;
|
|
3056
|
+
sizeEntry.durationMs += duration;
|
|
3057
|
+
setPropsBySize[sizeKey] = sizeEntry;
|
|
3058
|
+
}
|
|
3059
|
+
return;
|
|
3060
|
+
} else if (isInlineSegmentType(target.type)) {
|
|
3061
|
+
const nextProps = props ? { ...props } : {};
|
|
3062
|
+
if ("block" in nextProps) {
|
|
3063
|
+
delete nextProps.block;
|
|
3064
|
+
}
|
|
3065
|
+
const mergedSegments = mergeInlineSegmentProps(target.props, nextProps);
|
|
3066
|
+
if (!shallowEqualRecords(target.props, mergedSegments)) {
|
|
3067
|
+
target.props = mergedSegments;
|
|
3068
|
+
propsChanged = true;
|
|
3069
|
+
}
|
|
3070
|
+
} else {
|
|
3071
|
+
const nextProps = props ? { ...props } : {};
|
|
3072
|
+
if ("block" in nextProps) {
|
|
3073
|
+
delete nextProps.block;
|
|
3074
|
+
}
|
|
3075
|
+
const base = target.props ? { ...target.props } : {};
|
|
3076
|
+
let localChanged = false;
|
|
3077
|
+
if (Object.prototype.hasOwnProperty.call(base, "block")) {
|
|
3078
|
+
delete base.block;
|
|
3079
|
+
localChanged = true;
|
|
3080
|
+
}
|
|
3081
|
+
for (const [key, value] of Object.entries(nextProps)) {
|
|
3082
|
+
if (value === void 0) {
|
|
3083
|
+
if (Object.prototype.hasOwnProperty.call(base, key)) {
|
|
3084
|
+
delete base[key];
|
|
3085
|
+
localChanged = true;
|
|
3086
|
+
}
|
|
3087
|
+
} else if (base[key] !== value) {
|
|
3088
|
+
base[key] = value;
|
|
3089
|
+
localChanged = true;
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
if (localChanged) {
|
|
3093
|
+
target.props = base;
|
|
3094
|
+
propsChanged = true;
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
let blockChanged = false;
|
|
3098
|
+
let typeChanged = false;
|
|
3099
|
+
let rebuiltFromFinalizedBlock = false;
|
|
3100
|
+
let blockEpochAdvanced = false;
|
|
3101
|
+
const incomingBlock = props?.block;
|
|
3102
|
+
if (incomingBlock && typeof incomingBlock === "object") {
|
|
3103
|
+
target.block = (0, import_core4.cloneBlock)(incomingBlock);
|
|
3104
|
+
const previousType = target.type;
|
|
3105
|
+
const incomingType = typeof target.block.type === "string" ? target.block.type : target.type;
|
|
3106
|
+
if (incomingType !== target.type) {
|
|
3107
|
+
target.type = incomingType;
|
|
3108
|
+
typeChanged = true;
|
|
3109
|
+
if (isListRelated(previousType) || isListRelated(target.type)) {
|
|
3110
|
+
markListDepthDirty(target.id);
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
if (target.type === "code") {
|
|
3114
|
+
applyCodeBlockMetadata(target);
|
|
3115
|
+
}
|
|
3116
|
+
if (semanticUpdate) {
|
|
3117
|
+
advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
|
|
3118
|
+
blockEpochAdvanced = true;
|
|
3119
|
+
}
|
|
3120
|
+
const shouldRebuildFinalizedTree = !at.nodeId && target.id === at.blockId && Boolean(target.block?.isFinalized) && target.type !== "code" && Boolean(target.parentId);
|
|
3121
|
+
if (shouldRebuildFinalizedTree) {
|
|
3122
|
+
const parent = target.parentId ? nodes.get(target.parentId) : void 0;
|
|
3123
|
+
const index = parent ? parent.children.indexOf(target.id) : -1;
|
|
3124
|
+
if (parent && index !== -1) {
|
|
3125
|
+
const snapshot = (0, import_core4.createBlockSnapshot)((0, import_core4.cloneBlock)(target.block));
|
|
3126
|
+
replaceSnapshotAt(nodes, parent, index, snapshot, touched, target.blockEpoch);
|
|
3127
|
+
mutatedBlocks = true;
|
|
3128
|
+
blockChanged = true;
|
|
3129
|
+
rebuiltFromFinalizedBlock = true;
|
|
3130
|
+
const rebuilt = nodes.get(target.id);
|
|
3131
|
+
if (isListRelated(previousType) || isListRelated(rebuilt?.type)) {
|
|
3132
|
+
markListDepthDirty(target.id);
|
|
3133
|
+
}
|
|
3134
|
+
target = rebuilt ?? target;
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
mutatedBlocks = true;
|
|
3138
|
+
blockChanged = true;
|
|
3139
|
+
}
|
|
3140
|
+
if (rebuiltFromFinalizedBlock) {
|
|
3141
|
+
return;
|
|
3142
|
+
}
|
|
3143
|
+
if (propsChanged || blockChanged || typeChanged) {
|
|
3144
|
+
if (semanticUpdate && !blockEpochAdvanced) {
|
|
3145
|
+
advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
|
|
3146
|
+
blockEpochAdvanced = true;
|
|
3147
|
+
}
|
|
3148
|
+
target.version++;
|
|
3149
|
+
touched.add(target.id);
|
|
3150
|
+
let parentForNormalization;
|
|
3151
|
+
if (target.parentId) {
|
|
3152
|
+
const parent = nodes.get(target.parentId);
|
|
3153
|
+
if (parent) {
|
|
3154
|
+
if (target.type === "code-line") {
|
|
3155
|
+
parent.version++;
|
|
3156
|
+
touched.add(parent.id);
|
|
3157
|
+
}
|
|
3158
|
+
if (parent.type === "code") {
|
|
3159
|
+
parentForNormalization = parent;
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
if (target.type === "code") {
|
|
3164
|
+
normalizeCodeBlockChildren(nodes, target, touched);
|
|
3165
|
+
} else if (parentForNormalization) {
|
|
3166
|
+
normalizeCodeBlockChildren(nodes, parentForNormalization, touched);
|
|
3167
|
+
}
|
|
3168
|
+
const parentType2 = target.parentId ? nodes.get(target.parentId)?.type : void 0;
|
|
3169
|
+
if (isListRelated(target.type) || isListRelated(parentType2)) {
|
|
3170
|
+
markListDepthDirty(target.id);
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
if (perfEnabled) {
|
|
3174
|
+
const duration = getPerfNow() - perfStartLocal;
|
|
3175
|
+
const typeKey = target.type || "unknown";
|
|
3176
|
+
const sizeKey = bucketBySize(estimatePropsSize(props, incomingBlock && typeof incomingBlock === "object" ? incomingBlock : void 0));
|
|
3177
|
+
const typeEntry = setPropsByType[typeKey] ?? { count: 0, durationMs: 0 };
|
|
3178
|
+
typeEntry.count += 1;
|
|
3179
|
+
typeEntry.durationMs += duration;
|
|
3180
|
+
setPropsByType[typeKey] = typeEntry;
|
|
3181
|
+
const sizeEntry = setPropsBySize[sizeKey] ?? { count: 0, durationMs: 0 };
|
|
3182
|
+
sizeEntry.count += 1;
|
|
3183
|
+
sizeEntry.durationMs += duration;
|
|
3184
|
+
setPropsBySize[sizeKey] = sizeEntry;
|
|
3185
|
+
}
|
|
3186
|
+
};
|
|
3187
|
+
const applyPatch = (patch) => {
|
|
3188
|
+
switch (patch.op) {
|
|
3189
|
+
case "insertChild": {
|
|
3190
|
+
if (rejectStaleEpochPatch(patch, patch.at)) break;
|
|
3191
|
+
const parent = resolveParent(nodes, patch.at);
|
|
3192
|
+
const inserted = insertSnapshotAt(
|
|
3193
|
+
nodes,
|
|
3194
|
+
parent,
|
|
3195
|
+
patch.index,
|
|
3196
|
+
patch.node,
|
|
3197
|
+
touched,
|
|
3198
|
+
allocateBlockEpoch(readPatchParseEpoch(patch) ?? readSnapshotBlockEpoch(patch.node))
|
|
3199
|
+
);
|
|
3200
|
+
if (inserted) {
|
|
3201
|
+
for (const id of inserted.insertedIds) {
|
|
3202
|
+
touched.add(id);
|
|
3203
|
+
}
|
|
3204
|
+
mutatedBlocks = true;
|
|
3205
|
+
advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
|
|
3206
|
+
if (isListRelated(inserted.record.type) || isListRelated(parent?.type)) {
|
|
3207
|
+
markListDepthDirty(inserted.record.id);
|
|
3208
|
+
if (parent) {
|
|
3209
|
+
markListDepthDirty(parent.id);
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
if (debugListPatches && (isListRelated(inserted.record.type) || isListRelated(parent?.type))) {
|
|
3213
|
+
logListPatch("insertChild", {
|
|
3214
|
+
id: inserted.record.id,
|
|
3215
|
+
type: inserted.record.type,
|
|
3216
|
+
parentId: parent?.id,
|
|
3217
|
+
parentType: parent?.type,
|
|
3218
|
+
index: patch.index
|
|
3219
|
+
});
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
break;
|
|
3223
|
+
}
|
|
3224
|
+
case "deleteChild": {
|
|
3225
|
+
if (rejectStaleEpochPatch(patch, patch.at)) break;
|
|
3226
|
+
const parent = resolveParent(nodes, patch.at);
|
|
3227
|
+
if (!parent) break;
|
|
3228
|
+
if (patch.index < 0 || patch.index >= parent.children.length) break;
|
|
3229
|
+
const childId = parent.children.splice(patch.index, 1)[0];
|
|
3230
|
+
if (childId) {
|
|
3231
|
+
const removedNode = nodes.get(childId);
|
|
3232
|
+
removeSubtree(nodes, childId, touched);
|
|
3233
|
+
parent.version++;
|
|
3234
|
+
touched.add(parent.id);
|
|
3235
|
+
ensureUniqueChildren(parent, touched);
|
|
3236
|
+
mutatedBlocks = true;
|
|
3237
|
+
advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
|
|
3238
|
+
if (isListRelated(parent.type) || isListRelated(removedNode?.type)) {
|
|
3239
|
+
markListDepthDirty(parent.id);
|
|
3240
|
+
}
|
|
3241
|
+
if (debugListPatches && (isListRelated(parent.type) || isListRelated(removedNode?.type))) {
|
|
3242
|
+
logListPatch("deleteChild", {
|
|
3243
|
+
id: childId,
|
|
3244
|
+
type: removedNode?.type,
|
|
3245
|
+
parentId: parent.id,
|
|
3246
|
+
parentType: parent.type,
|
|
3247
|
+
index: patch.index
|
|
3248
|
+
});
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
break;
|
|
3252
|
+
}
|
|
3253
|
+
case "replaceChild": {
|
|
3254
|
+
if (rejectStaleEpochPatch(patch, patch.at)) break;
|
|
3255
|
+
const parent = resolveParent(nodes, patch.at);
|
|
3256
|
+
if (!parent) break;
|
|
3257
|
+
replaceSnapshotAt(
|
|
3258
|
+
nodes,
|
|
3259
|
+
parent,
|
|
3260
|
+
patch.index,
|
|
3261
|
+
patch.node,
|
|
3262
|
+
touched,
|
|
3263
|
+
allocateBlockEpoch(readPatchParseEpoch(patch) ?? readSnapshotBlockEpoch(patch.node))
|
|
3264
|
+
);
|
|
3265
|
+
touched.add(parent.id);
|
|
3266
|
+
mutatedBlocks = true;
|
|
3267
|
+
advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
|
|
3268
|
+
if (isListRelated(parent.type)) {
|
|
3269
|
+
markListDepthDirty(parent.id);
|
|
3270
|
+
}
|
|
3271
|
+
if (debugListPatches && isListRelated(parent.type)) {
|
|
3272
|
+
logListPatch("replaceChild", {
|
|
3273
|
+
parentId: parent.id,
|
|
3274
|
+
parentType: parent.type,
|
|
3275
|
+
index: patch.index
|
|
3276
|
+
});
|
|
3277
|
+
}
|
|
3278
|
+
break;
|
|
3279
|
+
}
|
|
3280
|
+
case "setProps": {
|
|
3281
|
+
if (rejectStaleEpochPatch(patch, patch.at)) break;
|
|
3282
|
+
applyPropsUpdate(patch.at, patch.props, readPatchParseEpoch(patch), readPatchTx(patch), (0, import_patch_batching.getPatchKind)(patch) === "semantic");
|
|
3283
|
+
break;
|
|
3284
|
+
}
|
|
3285
|
+
case "setPropsBatch": {
|
|
3286
|
+
const entries2 = Array.isArray(patch.entries) ? patch.entries : [];
|
|
3287
|
+
for (const entry of entries2) {
|
|
3288
|
+
if (!entry || !entry.at) continue;
|
|
3289
|
+
const entryPatch = {
|
|
3290
|
+
op: "setProps",
|
|
3291
|
+
at: entry.at,
|
|
3292
|
+
props: entry.props,
|
|
3293
|
+
meta: entry.meta ?? patch.meta
|
|
3294
|
+
};
|
|
3295
|
+
if (rejectStaleEpochPatch(entryPatch, entry.at)) continue;
|
|
3296
|
+
applyPropsUpdate(
|
|
3297
|
+
entry.at,
|
|
3298
|
+
entry.props,
|
|
3299
|
+
readPatchParseEpoch(entryPatch),
|
|
3300
|
+
readPatchTx(entryPatch),
|
|
3301
|
+
(0, import_patch_batching.getPatchKind)(entryPatch) === "semantic"
|
|
3302
|
+
);
|
|
3303
|
+
}
|
|
3304
|
+
break;
|
|
3305
|
+
}
|
|
3306
|
+
case "finalize": {
|
|
3307
|
+
if (rejectStaleEpochPatch(patch, patch.at)) break;
|
|
3308
|
+
const target = nodes.get(patch.at.nodeId ?? patch.at.blockId);
|
|
3309
|
+
if (!target || !target.block) break;
|
|
3310
|
+
if (!target.block.isFinalized) {
|
|
3311
|
+
target.block = (0, import_core4.cloneBlock)(target.block);
|
|
3312
|
+
target.block.isFinalized = true;
|
|
3313
|
+
target.version++;
|
|
3314
|
+
touched.add(target.id);
|
|
3315
|
+
mutatedBlocks = true;
|
|
3316
|
+
if (!patch.at.nodeId && target.id === patch.at.blockId && target.type !== "code" && target.parentId) {
|
|
3317
|
+
const parent = nodes.get(target.parentId);
|
|
3318
|
+
const index = parent ? parent.children.indexOf(target.id) : -1;
|
|
3319
|
+
if (parent && index !== -1) {
|
|
3320
|
+
const snapshot = (0, import_core4.createBlockSnapshot)((0, import_core4.cloneBlock)(target.block));
|
|
3321
|
+
replaceSnapshotAt(nodes, parent, index, snapshot, touched, target.blockEpoch);
|
|
3322
|
+
if (isListRelated(target.type) || isListRelated(snapshot.type)) {
|
|
3323
|
+
markListDepthDirty(target.id);
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
if (isListRelated(target.type)) {
|
|
3329
|
+
markListDepthDirty(target.id);
|
|
3330
|
+
}
|
|
3331
|
+
if (debugListPatches && isListRelated(target.type)) {
|
|
3332
|
+
logListPatch("finalize", {
|
|
3333
|
+
id: target.id,
|
|
3334
|
+
type: target.type
|
|
3335
|
+
});
|
|
3336
|
+
}
|
|
3337
|
+
break;
|
|
3338
|
+
}
|
|
3339
|
+
case "reorder": {
|
|
3340
|
+
if (rejectStaleEpochPatch(patch, patch.at)) break;
|
|
3341
|
+
const parent = resolveParent(nodes, patch.at);
|
|
3342
|
+
if (!parent) break;
|
|
3343
|
+
const length = parent.children.length;
|
|
3344
|
+
if (length === 0 || patch.count <= 0) break;
|
|
3345
|
+
const from = clampIndex(patch.from, length - 1);
|
|
3346
|
+
const count = Math.max(1, Math.min(patch.count, length - from));
|
|
3347
|
+
let to = clampIndex(patch.to, length);
|
|
3348
|
+
if (to > from) {
|
|
3349
|
+
to = Math.max(0, to - count);
|
|
3350
|
+
}
|
|
3351
|
+
const moved = parent.children.splice(from, count);
|
|
3352
|
+
parent.children.splice(to, 0, ...moved);
|
|
3353
|
+
parent.version++;
|
|
3354
|
+
touched.add(parent.id);
|
|
3355
|
+
if ((0, import_patch_batching.getPatchKind)(patch) === "semantic") {
|
|
3356
|
+
advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
|
|
3357
|
+
}
|
|
3358
|
+
if (isListRelated(parent.type)) {
|
|
3359
|
+
markListDepthDirty(parent.id);
|
|
3360
|
+
}
|
|
3361
|
+
if (debugListPatches && isListRelated(parent.type)) {
|
|
3362
|
+
logListPatch("reorder", {
|
|
3363
|
+
parentId: parent.id,
|
|
3364
|
+
parentType: parent.type,
|
|
3365
|
+
from: patch.from,
|
|
3366
|
+
to: patch.to,
|
|
3367
|
+
count: patch.count
|
|
3368
|
+
});
|
|
3369
|
+
}
|
|
3370
|
+
break;
|
|
3371
|
+
}
|
|
3372
|
+
case "appendLines": {
|
|
3373
|
+
if (rejectStaleEpochPatch(patch, patch.at)) break;
|
|
3374
|
+
const parent = resolveParent(nodes, patch.at);
|
|
3375
|
+
if (!parent) break;
|
|
3376
|
+
if (parent.type !== "code") {
|
|
3377
|
+
debugCounters.appendLineGuardRejected += 1;
|
|
3378
|
+
appendDiagnostic(`appendLines target not code:${parent.id}:${parent.type}`);
|
|
3379
|
+
break;
|
|
3380
|
+
}
|
|
3381
|
+
const requestedStartIndex = Math.max(0, patch.startIndex);
|
|
3382
|
+
const startIndex = Math.max(0, Math.min(requestedStartIndex, parent.children.length));
|
|
3383
|
+
const lineCount = patch.lines?.length ?? 0;
|
|
3384
|
+
if (lineCount === 0) break;
|
|
3385
|
+
const prefixMatches = parent.children.every((childId, index) => childId === expectedCodeLineId(parent.id, index));
|
|
3386
|
+
const isStrictTailAppend = requestedStartIndex === parent.children.length;
|
|
3387
|
+
if (!isStrictTailAppend || !prefixMatches) {
|
|
3388
|
+
debugCounters.appendLineGuardRejected += 1;
|
|
3389
|
+
appendDiagnostic(
|
|
3390
|
+
`appendLines guard rejected:${parent.id}:requestedStart=${requestedStartIndex}:childCount=${parent.children.length}:prefixMatches=${prefixMatches}`
|
|
3391
|
+
);
|
|
3392
|
+
break;
|
|
3393
|
+
}
|
|
3394
|
+
const originalChildren = parent.children.slice();
|
|
3395
|
+
const insertedIds = [];
|
|
3396
|
+
const insertedSet = /* @__PURE__ */ new Set();
|
|
3397
|
+
const isTrailingAppend = true;
|
|
3398
|
+
let parentChildrenChanged = false;
|
|
3399
|
+
let codeLinesMutated = false;
|
|
3400
|
+
for (let offset = 0; offset < lineCount; offset++) {
|
|
3401
|
+
const absoluteIndex = startIndex + offset;
|
|
3402
|
+
const lineId = `${parent.id}::line:${absoluteIndex}`;
|
|
3403
|
+
insertedIds.push(lineId);
|
|
3404
|
+
insertedSet.add(lineId);
|
|
3405
|
+
const hasTokens = Object.prototype.hasOwnProperty.call(patch, "tokens");
|
|
3406
|
+
const nextTokens = hasTokens ? patch.tokens?.[offset] ?? null : void 0;
|
|
3407
|
+
const nextLineProps = createCodeLineProps(
|
|
3408
|
+
absoluteIndex,
|
|
3409
|
+
patch.lines[offset] ?? "",
|
|
3410
|
+
patch.highlight?.[offset] ?? null,
|
|
3411
|
+
nextTokens
|
|
3412
|
+
);
|
|
3413
|
+
const existing = nodes.get(lineId);
|
|
3414
|
+
if (existing && existing.type === "code-line") {
|
|
3415
|
+
const existingIndex = parent.children.indexOf(lineId);
|
|
3416
|
+
if (existingIndex !== -1) {
|
|
3417
|
+
parent.children.splice(existingIndex, 1);
|
|
3418
|
+
parentChildrenChanged = true;
|
|
3419
|
+
}
|
|
3420
|
+
if (existing.parentId !== parent.id) {
|
|
3421
|
+
existing.parentId = parent.id;
|
|
3422
|
+
existing.version++;
|
|
3423
|
+
touched.add(lineId);
|
|
3424
|
+
codeLinesMutated = true;
|
|
3425
|
+
}
|
|
3426
|
+
if (!shallowEqualRecords(existing.props, nextLineProps)) {
|
|
3427
|
+
existing.props = nextLineProps;
|
|
3428
|
+
existing.version++;
|
|
3429
|
+
touched.add(lineId);
|
|
3430
|
+
codeLinesMutated = true;
|
|
3431
|
+
}
|
|
3432
|
+
} else {
|
|
3433
|
+
const record = {
|
|
3434
|
+
id: lineId,
|
|
3435
|
+
type: "code-line",
|
|
3436
|
+
parentId: parent.id,
|
|
3437
|
+
children: [],
|
|
3438
|
+
props: nextLineProps,
|
|
3439
|
+
version: 0,
|
|
3440
|
+
blockEpoch: parent.blockEpoch
|
|
3441
|
+
};
|
|
3442
|
+
nodes.set(lineId, record);
|
|
3443
|
+
touched.add(lineId);
|
|
3444
|
+
codeLinesMutated = true;
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
if (insertedIds.length > 0) {
|
|
3448
|
+
let nextChildren;
|
|
3449
|
+
if (isTrailingAppend) {
|
|
3450
|
+
nextChildren = [...originalChildren, ...insertedIds];
|
|
3451
|
+
} else {
|
|
3452
|
+
const cleanedChildren = originalChildren.filter((childId) => !insertedSet.has(childId));
|
|
3453
|
+
const clampedStart = Math.max(0, Math.min(startIndex, cleanedChildren.length));
|
|
3454
|
+
nextChildren = [...cleanedChildren.slice(0, clampedStart), ...insertedIds, ...cleanedChildren.slice(clampedStart)];
|
|
3455
|
+
}
|
|
3456
|
+
if (!arraysEqual(originalChildren, nextChildren)) {
|
|
3457
|
+
parent.children = nextChildren;
|
|
3458
|
+
parentChildrenChanged = true;
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
if (!isTrailingAppend) {
|
|
3462
|
+
for (let idx = 0; idx < parent.children.length; idx++) {
|
|
3463
|
+
const childId = parent.children[idx];
|
|
3464
|
+
const child = nodes.get(childId);
|
|
3465
|
+
if (child && child.type === "code-line") {
|
|
3466
|
+
const incoming = { index: idx, text: child.props?.text, html: child.props?.html };
|
|
3467
|
+
if (Object.prototype.hasOwnProperty.call(child.props ?? {}, "tokens")) {
|
|
3468
|
+
incoming.tokens = child.props.tokens;
|
|
3469
|
+
}
|
|
3470
|
+
const normalized = normalizeCodeLineProps(incoming, child.props);
|
|
3471
|
+
if (normalized !== child.props) {
|
|
3472
|
+
child.props = normalized;
|
|
3473
|
+
child.version++;
|
|
3474
|
+
touched.add(child.id);
|
|
3475
|
+
codeLinesMutated = true;
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
if (parentChildrenChanged || codeLinesMutated) {
|
|
3481
|
+
parent.version++;
|
|
3482
|
+
touched.add(parent.id);
|
|
3483
|
+
if (!isTrailingAppend) {
|
|
3484
|
+
normalizeCodeBlockChildren(nodes, parent, touched);
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
break;
|
|
3488
|
+
}
|
|
3489
|
+
case "setHTML": {
|
|
3490
|
+
if (rejectStaleEpochPatch(patch, patch.at)) break;
|
|
3491
|
+
const target = nodes.get(patch.at.nodeId ?? patch.at.blockId);
|
|
3492
|
+
if (!target) break;
|
|
3493
|
+
const sanitized = patch.sanitized ? patch.html : (0, import_core4.sanitizeHTML)(patch.html);
|
|
3494
|
+
target.props = {
|
|
3495
|
+
...target.props ?? {},
|
|
3496
|
+
html: sanitized,
|
|
3497
|
+
policy: patch.policy || "markdown-renderer-v2",
|
|
3498
|
+
meta: patch.meta ? { ...patch.meta } : target.props?.meta
|
|
3499
|
+
};
|
|
3500
|
+
if (patch.block) {
|
|
3501
|
+
target.block = (0, import_core4.cloneBlock)(patch.block);
|
|
3502
|
+
if (!target.block.payload.meta) {
|
|
3503
|
+
target.block.payload.meta = {};
|
|
3504
|
+
}
|
|
3505
|
+
target.block.payload.sanitizedHtml = sanitized;
|
|
3506
|
+
mutatedBlocks = true;
|
|
3507
|
+
}
|
|
3508
|
+
target.version++;
|
|
3509
|
+
touched.add(target.id);
|
|
3510
|
+
break;
|
|
3511
|
+
}
|
|
3512
|
+
default:
|
|
3513
|
+
break;
|
|
3514
|
+
}
|
|
3515
|
+
};
|
|
3516
|
+
if (perfEnabled) {
|
|
3517
|
+
const loopStart = getPerfNow();
|
|
3518
|
+
for (const patch of coalescedPatches) {
|
|
3519
|
+
const opStart = getPerfNow();
|
|
3520
|
+
applyPatch(patch);
|
|
3521
|
+
const opDuration = getPerfNow() - opStart;
|
|
3522
|
+
const entry = opStats[patch.op] ?? { count: 0, durationMs: 0 };
|
|
3523
|
+
entry.count += 1;
|
|
3524
|
+
entry.durationMs += opDuration;
|
|
3525
|
+
opStats[patch.op] = entry;
|
|
3526
|
+
}
|
|
3527
|
+
applyMs = getPerfNow() - loopStart;
|
|
3528
|
+
} else {
|
|
3529
|
+
for (const patch of coalescedPatches) {
|
|
3530
|
+
applyPatch(patch);
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
const finalizedTableRoots = /* @__PURE__ */ new Set();
|
|
3534
|
+
for (const touchedId of touched) {
|
|
3535
|
+
let cursor = nodes.get(touchedId);
|
|
3536
|
+
while (cursor && cursor.id !== import_core4.PATCH_ROOT_ID) {
|
|
3537
|
+
if (cursor.type === "table" && cursor.block?.isFinalized && cursor.parentId) {
|
|
3538
|
+
finalizedTableRoots.add(cursor.id);
|
|
3539
|
+
break;
|
|
3540
|
+
}
|
|
3541
|
+
cursor = cursor.parentId ? nodes.get(cursor.parentId) : void 0;
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
if (finalizedTableRoots.size > 0) {
|
|
3545
|
+
for (const tableId of finalizedTableRoots) {
|
|
3546
|
+
const tableNode = nodes.get(tableId);
|
|
3547
|
+
if (!tableNode || !tableNode.block || !tableNode.parentId) continue;
|
|
3548
|
+
const parent = nodes.get(tableNode.parentId);
|
|
3549
|
+
if (!parent) continue;
|
|
3550
|
+
const index = parent.children.indexOf(tableNode.id);
|
|
3551
|
+
if (index === -1) continue;
|
|
3552
|
+
const snapshot = (0, import_core4.createBlockSnapshot)((0, import_core4.cloneBlock)(tableNode.block));
|
|
3553
|
+
replaceSnapshotAt(nodes, parent, index, snapshot, touched, tableNode.blockEpoch);
|
|
3554
|
+
mutatedBlocks = true;
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
if (mutatedBlocks) {
|
|
3558
|
+
blocksDirty = true;
|
|
3559
|
+
}
|
|
3560
|
+
if (listDepthDirty) {
|
|
3561
|
+
if (perfEnabled) {
|
|
3562
|
+
const start = getPerfNow();
|
|
3563
|
+
normalizeAllListDepths(nodes, touched, import_core4.PATCH_ROOT_ID);
|
|
3564
|
+
listNormalizeMs = getPerfNow() - start;
|
|
3565
|
+
} else {
|
|
3566
|
+
normalizeAllListDepths(nodes, touched, import_core4.PATCH_ROOT_ID);
|
|
3567
|
+
}
|
|
3568
|
+
} else if (listDepthDirtyNodes.size > 0) {
|
|
3569
|
+
if (perfEnabled) {
|
|
3570
|
+
const start = getPerfNow();
|
|
3571
|
+
normalizeListDepthsForIds(nodes, touched, listDepthDirtyNodes, import_core4.PATCH_ROOT_ID);
|
|
3572
|
+
listNormalizeMs = getPerfNow() - start;
|
|
3573
|
+
} else {
|
|
3574
|
+
normalizeListDepthsForIds(nodes, touched, listDepthDirtyNodes, import_core4.PATCH_ROOT_ID);
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
invariantViolations = [...collectStoreInvariantViolations(nodes), ...transientDiagnostics];
|
|
3578
|
+
debugCounters.storeInvariantViolationCount += invariantViolations.length;
|
|
3579
|
+
if (touched.size > 0 || mutatedBlocks) {
|
|
3580
|
+
version++;
|
|
3581
|
+
scheduleNotify();
|
|
3582
|
+
}
|
|
3583
|
+
if (perfEnabled) {
|
|
3584
|
+
const totalMs = getPerfNow() - perfStart;
|
|
3585
|
+
const coalesceMs = typeof metrics?.durationMs === "number" ? metrics.durationMs : 0;
|
|
3586
|
+
recordPatchPerf({
|
|
3587
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3588
|
+
totalMs,
|
|
3589
|
+
applyMs,
|
|
3590
|
+
coalesceMs,
|
|
3591
|
+
listNormalizeMs,
|
|
3592
|
+
patches: patches.length,
|
|
3593
|
+
coalescedPatches: coalescedPatches.length,
|
|
3594
|
+
touchedCount: touched.size,
|
|
3595
|
+
mutatedBlocks,
|
|
3596
|
+
ops: opStats,
|
|
3597
|
+
setPropsByType,
|
|
3598
|
+
setPropsBySize
|
|
3599
|
+
});
|
|
3600
|
+
}
|
|
3601
|
+
return touched;
|
|
3602
|
+
},
|
|
3603
|
+
getBlocks() {
|
|
3604
|
+
if (!blocksDirty) {
|
|
3605
|
+
return blocksCache;
|
|
3606
|
+
}
|
|
3607
|
+
const root = nodes.get(import_core4.PATCH_ROOT_ID);
|
|
3608
|
+
if (!root) {
|
|
3609
|
+
blocksCache = [];
|
|
3610
|
+
} else {
|
|
3611
|
+
blocksCache = convertNodeToBlocks(nodes, root);
|
|
3612
|
+
}
|
|
3613
|
+
blocksDirty = false;
|
|
3614
|
+
return blocksCache;
|
|
3615
|
+
},
|
|
3616
|
+
getVersion() {
|
|
3617
|
+
return version;
|
|
3618
|
+
},
|
|
3619
|
+
getNode(id) {
|
|
3620
|
+
return nodes.get(id);
|
|
3621
|
+
},
|
|
3622
|
+
getNodeWithVersion(id) {
|
|
3623
|
+
const record = nodes.get(id);
|
|
3624
|
+
if (!record) {
|
|
3625
|
+
return EMPTY_NODE_SNAPSHOT;
|
|
3626
|
+
}
|
|
3627
|
+
const nodeSnapshotCache = getNodeSnapshotCacheForMap(nodes);
|
|
3628
|
+
const cached = nodeSnapshotCache.get(id);
|
|
3629
|
+
if (cached && cached.version === record.version && cached.node === record) {
|
|
3630
|
+
return cached;
|
|
3631
|
+
}
|
|
3632
|
+
const snapshot = { version: record.version, node: record };
|
|
3633
|
+
nodeSnapshotCache.set(id, snapshot);
|
|
3634
|
+
pruneCache(nodeSnapshotCache, NODE_SNAPSHOT_CACHE_LIMIT, NODE_SNAPSHOT_CACHE_BUFFER);
|
|
3635
|
+
return snapshot;
|
|
3636
|
+
},
|
|
3637
|
+
getChildren(id) {
|
|
3638
|
+
const record = nodes.get(id);
|
|
3639
|
+
if (!record) return EMPTY_CHILDREN;
|
|
3640
|
+
return record.children;
|
|
3641
|
+
},
|
|
3642
|
+
getChildrenWithVersion(id) {
|
|
3643
|
+
const record = nodes.get(id);
|
|
3644
|
+
if (!record) {
|
|
3645
|
+
return EMPTY_CHILDREN_SNAPSHOT;
|
|
3646
|
+
}
|
|
3647
|
+
const childrenSnapshotCache = getChildrenSnapshotCacheForMap(nodes);
|
|
3648
|
+
const cached = childrenSnapshotCache.get(id);
|
|
3649
|
+
if (cached && cached.version === record.version) {
|
|
3650
|
+
return cached;
|
|
3651
|
+
}
|
|
3652
|
+
const snapshot = { version: record.version, children: record.children.slice() };
|
|
3653
|
+
childrenSnapshotCache.set(id, snapshot);
|
|
3654
|
+
pruneCache(childrenSnapshotCache, CHILDREN_SNAPSHOT_CACHE_LIMIT, CHILDREN_SNAPSHOT_CACHE_BUFFER);
|
|
3655
|
+
return snapshot;
|
|
3656
|
+
},
|
|
3657
|
+
getInvariantViolations() {
|
|
3658
|
+
return invariantViolations.slice();
|
|
3659
|
+
},
|
|
3660
|
+
getDebugCounters() {
|
|
3661
|
+
return { ...debugCounters };
|
|
3662
|
+
},
|
|
3663
|
+
getLastCoalescingMetrics() {
|
|
3664
|
+
return lastCoalescingMetrics;
|
|
3665
|
+
},
|
|
3666
|
+
subscribe(listener) {
|
|
3667
|
+
listeners.add(listener);
|
|
3668
|
+
return () => {
|
|
3669
|
+
listeners.delete(listener);
|
|
3670
|
+
};
|
|
3671
|
+
}
|
|
3672
|
+
};
|
|
3673
|
+
return store;
|
|
3674
|
+
}
|
|
3675
|
+
|
|
3676
|
+
// src/renderer/virtualized-code.tsx
|
|
3677
|
+
var import_react9 = require("react");
|
|
3678
|
+
var DEFAULT_VIRTUALIZED_CODE_CONFIG = {
|
|
3679
|
+
enabled: true,
|
|
3680
|
+
windowSize: 100,
|
|
3681
|
+
// Render 100 lines at a time
|
|
3682
|
+
bufferSize: 50,
|
|
3683
|
+
// +50 lines buffer above/below
|
|
3684
|
+
virtualizeThreshold: 200
|
|
3685
|
+
// Only virtualize if 200+ lines
|
|
3686
|
+
};
|
|
3687
|
+
function calculateCodeWindow(lines, scrollTop, containerHeight, lineHeight, config) {
|
|
3688
|
+
const totalLines = lines.length;
|
|
3689
|
+
if (!config.enabled || totalLines < config.virtualizeThreshold) {
|
|
3690
|
+
return {
|
|
3691
|
+
startIndex: 0,
|
|
3692
|
+
endIndex: totalLines,
|
|
3693
|
+
visibleStart: 0,
|
|
3694
|
+
visibleEnd: totalLines,
|
|
3695
|
+
visibleLines: lines,
|
|
3696
|
+
totalLines,
|
|
3697
|
+
mountedLines: totalLines
|
|
3698
|
+
};
|
|
3699
|
+
}
|
|
3700
|
+
const firstVisibleLine = Math.floor(scrollTop / lineHeight);
|
|
3701
|
+
const lastVisibleLine = Math.ceil((scrollTop + containerHeight) / lineHeight);
|
|
3702
|
+
const visibleStart = Math.max(0, firstVisibleLine);
|
|
3703
|
+
const visibleEnd = Math.min(totalLines, Math.max(visibleStart, lastVisibleLine));
|
|
3704
|
+
const startIndex = Math.max(0, visibleStart - config.bufferSize);
|
|
3705
|
+
const endIndex = Math.min(totalLines, visibleEnd + config.bufferSize);
|
|
3706
|
+
return {
|
|
3707
|
+
startIndex,
|
|
3708
|
+
endIndex,
|
|
3709
|
+
visibleStart,
|
|
3710
|
+
visibleEnd,
|
|
3711
|
+
visibleLines: lines.slice(startIndex, endIndex),
|
|
3712
|
+
totalLines,
|
|
3713
|
+
mountedLines: endIndex - startIndex
|
|
3714
|
+
};
|
|
3715
|
+
}
|
|
3716
|
+
function useVirtualizedCode(lines, config = DEFAULT_VIRTUALIZED_CODE_CONFIG) {
|
|
3717
|
+
const containerRef = (0, import_react9.useRef)(null);
|
|
3718
|
+
const [scrollTop, setScrollTop] = (0, import_react9.useState)(0);
|
|
3719
|
+
const [containerHeight, setContainerHeight] = (0, import_react9.useState)(0);
|
|
3720
|
+
const lineHeightRef = (0, import_react9.useRef)(20);
|
|
3721
|
+
(0, import_react9.useEffect)(() => {
|
|
3722
|
+
if (!containerRef.current || lines.length === 0) return;
|
|
3723
|
+
const timeoutId = setTimeout(() => {
|
|
3724
|
+
if (!containerRef.current) return;
|
|
3725
|
+
const firstLine = containerRef.current.querySelector(".line");
|
|
3726
|
+
if (firstLine) {
|
|
3727
|
+
const measuredHeight = firstLine.offsetHeight || 20;
|
|
3728
|
+
if (measuredHeight !== lineHeightRef.current) {
|
|
3729
|
+
lineHeightRef.current = measuredHeight;
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3732
|
+
}, 0);
|
|
3733
|
+
return () => clearTimeout(timeoutId);
|
|
3734
|
+
}, [lines.length]);
|
|
3735
|
+
const handleScroll = (0, import_react9.useCallback)((e) => {
|
|
3736
|
+
const target = e.currentTarget;
|
|
3737
|
+
setScrollTop(target.scrollTop);
|
|
3738
|
+
setContainerHeight(target.clientHeight);
|
|
3739
|
+
}, []);
|
|
3740
|
+
(0, import_react9.useEffect)(() => {
|
|
3741
|
+
if (!containerRef.current) return;
|
|
3742
|
+
const resizeObserver = new ResizeObserver((entries2) => {
|
|
3743
|
+
for (const entry of entries2) {
|
|
3744
|
+
setContainerHeight(entry.contentRect.height);
|
|
3745
|
+
}
|
|
3746
|
+
});
|
|
3747
|
+
resizeObserver.observe(containerRef.current);
|
|
3748
|
+
return () => resizeObserver.disconnect();
|
|
3749
|
+
}, []);
|
|
3750
|
+
const window2 = (0, import_react9.useMemo)(() => {
|
|
3751
|
+
return calculateCodeWindow(lines, scrollTop, containerHeight, lineHeightRef.current, config);
|
|
3752
|
+
}, [lines, scrollTop, containerHeight, config]);
|
|
3753
|
+
const scrollToLine = (0, import_react9.useCallback)((lineIndex) => {
|
|
3754
|
+
if (!containerRef.current) return;
|
|
3755
|
+
const targetScrollTop = lineIndex * lineHeightRef.current;
|
|
3756
|
+
containerRef.current.scrollTop = targetScrollTop;
|
|
3757
|
+
setScrollTop(targetScrollTop);
|
|
3758
|
+
}, []);
|
|
3759
|
+
return {
|
|
3760
|
+
containerRef,
|
|
3761
|
+
window: window2,
|
|
3762
|
+
handleScroll,
|
|
3763
|
+
scrollToLine,
|
|
3764
|
+
lineHeight: lineHeightRef.current,
|
|
3765
|
+
config
|
|
3766
|
+
};
|
|
3767
|
+
}
|
|
3768
|
+
|
|
3769
|
+
// src/renderer/node-views.tsx
|
|
3770
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
3771
|
+
var BlockNodeRenderer = ({ store, blockId, registry }) => {
|
|
3772
|
+
const node = useRendererNode(store, blockId);
|
|
3773
|
+
const block = node?.block;
|
|
3774
|
+
if (!node || !block) return null;
|
|
3775
|
+
switch (block.type) {
|
|
3776
|
+
case "paragraph":
|
|
3777
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ParagraphBlockView, { store, blockId: block.id, registry });
|
|
3778
|
+
case "blockquote":
|
|
3779
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BlockquoteBlockView, { store, blockId: block.id, registry });
|
|
3780
|
+
case "list":
|
|
3781
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ListBlockView, { store, blockId: block.id, registry, depth: 0 });
|
|
3782
|
+
case "table":
|
|
3783
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableBlockView, { store, blockId: block.id, registry });
|
|
3784
|
+
case "code":
|
|
3785
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CodeBlockView, { store, blockId: block.id, registry });
|
|
3786
|
+
case "html":
|
|
3787
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HtmlBlockView, { store, blockId: block.id, registry });
|
|
3788
|
+
case "mdx":
|
|
3789
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MdxBlockView, { store, blockId: block.id, registry });
|
|
3790
|
+
default:
|
|
3791
|
+
return registry.renderBlock(block);
|
|
3792
|
+
}
|
|
3793
|
+
};
|
|
3794
|
+
var ParagraphBlockView = import_react10.default.memo(({ store, blockId, registry }) => {
|
|
3795
|
+
const node = useRendererNode(store, blockId);
|
|
3796
|
+
const block = node?.block;
|
|
3797
|
+
const childIds = useRendererChildren(store, blockId);
|
|
3798
|
+
if (!node || !block) {
|
|
3799
|
+
return null;
|
|
3800
|
+
}
|
|
3801
|
+
const inlineComponents = registry.getInlineComponents();
|
|
3802
|
+
if (!block.isFinalized) {
|
|
3803
|
+
const raw = typeof block.payload.raw === "string" ? block.payload.raw : "";
|
|
3804
|
+
const meta = block.payload.meta ?? {};
|
|
3805
|
+
const segments2 = Array.isArray(meta.mixedSegments) ? meta.mixedSegments : [];
|
|
3806
|
+
if (segments2.length > 0) {
|
|
3807
|
+
const allowMixedStreaming = Boolean(meta.allowMixedStreaming);
|
|
3808
|
+
if (!allowMixedStreaming) {
|
|
3809
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "markdown-paragraph streaming-partial", children: raw });
|
|
3810
|
+
}
|
|
3811
|
+
const structured = renderParagraphMixedSegments(segments2, inlineComponents, DEFAULT_INLINE_HTML_RENDERERS);
|
|
3812
|
+
if (structured.length === 1) {
|
|
3813
|
+
const [single] = structured;
|
|
3814
|
+
return import_react10.default.isValidElement(single) ? single : single;
|
|
3815
|
+
}
|
|
3816
|
+
return structured;
|
|
3817
|
+
}
|
|
3818
|
+
if (meta.inlineStatus === "complete" || meta.inlineStatus === "anticipated") {
|
|
3819
|
+
const inlineNodes2 = block.payload.inline ?? [];
|
|
3820
|
+
const containsDisplayMath2 = inlineNodes2.some((node2) => node2.kind === "math-display");
|
|
3821
|
+
if (containsDisplayMath2) {
|
|
3822
|
+
const derivedSegments = [
|
|
3823
|
+
{
|
|
3824
|
+
kind: "text",
|
|
3825
|
+
value: raw,
|
|
3826
|
+
inline: inlineNodes2
|
|
3827
|
+
}
|
|
3828
|
+
];
|
|
3829
|
+
const structured = renderParagraphMixedSegments(derivedSegments, inlineComponents, DEFAULT_INLINE_HTML_RENDERERS);
|
|
3830
|
+
if (structured.length === 1) {
|
|
3831
|
+
const [single] = structured;
|
|
3832
|
+
return import_react10.default.isValidElement(single) ? single : single;
|
|
3833
|
+
}
|
|
3834
|
+
return structured;
|
|
3835
|
+
}
|
|
3836
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "markdown-paragraph streaming-partial", children: renderInlineNodes(inlineNodes2, inlineComponents) });
|
|
3837
|
+
}
|
|
3838
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "markdown-paragraph streaming-partial", children: raw });
|
|
3839
|
+
}
|
|
3840
|
+
const propsBlock = node.props?.block;
|
|
3841
|
+
const inlineStatus = propsBlock?.payload?.meta?.inlineStatus ?? block.payload.meta?.inlineStatus ?? node.props?.inlineStatus;
|
|
3842
|
+
if (inlineStatus && inlineStatus !== "complete") {
|
|
3843
|
+
const raw = typeof block.payload.raw === "string" ? block.payload.raw : "";
|
|
3844
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "markdown-paragraph", children: raw });
|
|
3845
|
+
}
|
|
3846
|
+
const segments = Array.isArray(block.payload.meta?.mixedSegments) ? block.payload.meta.mixedSegments : void 0;
|
|
3847
|
+
if (segments && segments.length > 0) {
|
|
3848
|
+
const structured = renderParagraphMixedSegments(segments, inlineComponents, DEFAULT_INLINE_HTML_RENDERERS);
|
|
3849
|
+
if (structured.length === 1) {
|
|
3850
|
+
const [single] = structured;
|
|
3851
|
+
return import_react10.default.isValidElement(single) ? single : single;
|
|
3852
|
+
}
|
|
3853
|
+
return structured;
|
|
3854
|
+
}
|
|
3855
|
+
const inlineNodes = block.payload.inline ?? [];
|
|
3856
|
+
const containsDisplayMath = inlineNodes.some((node2) => node2.kind === "math-display");
|
|
3857
|
+
if (containsDisplayMath) {
|
|
3858
|
+
const derivedSegments = [
|
|
3859
|
+
{
|
|
3860
|
+
kind: "text",
|
|
3861
|
+
value: typeof block.payload.raw === "string" ? block.payload.raw : "",
|
|
3862
|
+
inline: inlineNodes
|
|
3863
|
+
}
|
|
3864
|
+
];
|
|
3865
|
+
const structured = renderParagraphMixedSegments(derivedSegments, inlineComponents, DEFAULT_INLINE_HTML_RENDERERS);
|
|
3866
|
+
if (structured.length === 1) {
|
|
3867
|
+
const [single] = structured;
|
|
3868
|
+
return import_react10.default.isValidElement(single) ? single : single;
|
|
3869
|
+
}
|
|
3870
|
+
return structured;
|
|
3871
|
+
}
|
|
3872
|
+
const ParagraphComponent = registry.getBlockComponent("paragraph");
|
|
3873
|
+
const baseElement = import_react10.default.createElement(ParagraphComponent, {
|
|
3874
|
+
inlines: block.payload.inline ?? [],
|
|
3875
|
+
raw: block.payload.raw,
|
|
3876
|
+
meta: block.payload.meta
|
|
3877
|
+
});
|
|
3878
|
+
if (childIds.length > 0) {
|
|
3879
|
+
const renderedChildren = childIds.map((childId) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MixedSegmentView, { store, nodeId: childId, inlineComponents }, childId));
|
|
3880
|
+
if (import_react10.default.isValidElement(baseElement)) {
|
|
3881
|
+
const { children: _ignored, className = "markdown-paragraph", inlines: _inlines, raw: _raw, meta: _meta, ...rest } = baseElement.props ?? {};
|
|
3882
|
+
return import_react10.default.createElement("p", { ...rest, className }, renderedChildren);
|
|
3883
|
+
}
|
|
3884
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "markdown-paragraph", children: renderedChildren });
|
|
3885
|
+
}
|
|
3886
|
+
if (import_react10.default.isValidElement(baseElement)) {
|
|
3887
|
+
return baseElement;
|
|
3888
|
+
}
|
|
3889
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "markdown-paragraph", children: renderInlineNodes(block.payload.inline ?? [], inlineComponents) });
|
|
3890
|
+
});
|
|
3891
|
+
ParagraphBlockView.displayName = "ParagraphBlockView";
|
|
3892
|
+
var BlockquoteBlockView = import_react10.default.memo(({ store, blockId, registry }) => {
|
|
3893
|
+
const node = useRendererNode(store, blockId);
|
|
3894
|
+
const block = node?.block;
|
|
3895
|
+
const childIds = useRendererChildren(store, blockId);
|
|
3896
|
+
if (!node || !block) {
|
|
3897
|
+
return null;
|
|
3898
|
+
}
|
|
3899
|
+
const meta = block.payload.meta ?? {};
|
|
3900
|
+
const mixedSegments = Array.isArray(meta.mixedSegments) ? meta.mixedSegments : [];
|
|
3901
|
+
const normalizedText = typeof meta.normalizedText === "string" && meta.normalizedText.length > 0 ? meta.normalizedText : typeof block.payload.raw === "string" ? block.payload.raw : "";
|
|
3902
|
+
const inlineComponents = registry.getInlineComponents();
|
|
3903
|
+
const renderTextWithBreaks = (text, keyPrefix) => {
|
|
3904
|
+
const lines = text.split("\n");
|
|
3905
|
+
const lastIndex = lines.length - 1;
|
|
3906
|
+
let offset = 0;
|
|
3907
|
+
return lines.map((line, idx) => {
|
|
3908
|
+
const key = `${keyPrefix}${offset}`;
|
|
3909
|
+
offset += line.length + 1;
|
|
3910
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "markdown-blockquote-line", children: [
|
|
3911
|
+
line,
|
|
3912
|
+
idx < lastIndex ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("br", {}) : null
|
|
3913
|
+
] }, key);
|
|
3914
|
+
});
|
|
3915
|
+
};
|
|
3916
|
+
const renderInlineContent = (inline, keyPrefix) => {
|
|
3917
|
+
if (inline && inline.length > 0) {
|
|
3918
|
+
return renderInlineNodes(inline, inlineComponents);
|
|
3919
|
+
}
|
|
3920
|
+
return null;
|
|
3921
|
+
};
|
|
3922
|
+
const renderMixedSegments = () => {
|
|
3923
|
+
if (!mixedSegments.length) {
|
|
3924
|
+
return [];
|
|
3925
|
+
}
|
|
3926
|
+
const elements = [];
|
|
3927
|
+
mixedSegments.forEach((segment, segmentIndex) => {
|
|
3928
|
+
switch (segment.kind) {
|
|
3929
|
+
case "html": {
|
|
3930
|
+
const htmlNode = renderInlineHtmlSegment(segment.value, segment.sanitized ?? segment.value, {
|
|
3931
|
+
key: `blockquote-html-${block.id}-${segmentIndex}`,
|
|
3932
|
+
renderers: DEFAULT_INLINE_HTML_RENDERERS
|
|
3933
|
+
});
|
|
3934
|
+
if (htmlNode) {
|
|
3935
|
+
elements.push(htmlNode);
|
|
3936
|
+
} else {
|
|
3937
|
+
elements.push(
|
|
3938
|
+
import_react10.default.createElement("span", {
|
|
3939
|
+
key: `blockquote-html-${block.id}-${segmentIndex}`,
|
|
3940
|
+
className: "markdown-inline-html",
|
|
3941
|
+
/* biome-ignore lint/security/noDangerouslySetInnerHtml: segment content is sanitized by the worker before streaming */
|
|
3942
|
+
dangerouslySetInnerHTML: { __html: segment.sanitized ?? segment.value }
|
|
3943
|
+
})
|
|
3944
|
+
);
|
|
3945
|
+
}
|
|
3946
|
+
break;
|
|
3947
|
+
}
|
|
3948
|
+
case "mdx":
|
|
3949
|
+
elements.push(
|
|
3950
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "data-mdx-status": segment.status ?? "pending", children: segment.value }, `blockquote-mdx-${block.id}-${segmentIndex}`)
|
|
3951
|
+
);
|
|
3952
|
+
break;
|
|
3953
|
+
default: {
|
|
3954
|
+
const inline = renderInlineContent(segment.inline, `blockquote-text-inline-${block.id}-${segmentIndex}-`);
|
|
3955
|
+
if (inline) {
|
|
3956
|
+
elements.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react10.default.Fragment, { children: inline }, `blockquote-inline-${block.id}-${segmentIndex}`));
|
|
3957
|
+
} else {
|
|
3958
|
+
elements.push(
|
|
3959
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react10.default.Fragment, { children: renderTextWithBreaks(segment.value, `blockquote-text-${block.id}-${segmentIndex}-`) }, `blockquote-text-${block.id}-${segmentIndex}`)
|
|
3960
|
+
);
|
|
3961
|
+
}
|
|
3962
|
+
break;
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
});
|
|
3966
|
+
return elements;
|
|
3967
|
+
};
|
|
3968
|
+
const mixedSegmentElements = renderMixedSegments();
|
|
3969
|
+
const fallbackElements = normalizedText ? renderTextWithBreaks(normalizedText, `blockquote-fallback-${block.id}-`) : null;
|
|
3970
|
+
const inlineNodes = block.payload.inline ?? [];
|
|
3971
|
+
const inlineElements = inlineNodes.length > 0 ? renderInlineNodes(inlineNodes, inlineComponents) : null;
|
|
3972
|
+
if (!block.isFinalized) {
|
|
3973
|
+
const streamingContent = mixedSegmentElements.length ? mixedSegmentElements : inlineElements ? inlineElements : fallbackElements;
|
|
3974
|
+
const shouldShowStreaming = !block.isFinalized && (!streamingContent || Array.isArray(streamingContent) && streamingContent.length === 0);
|
|
3975
|
+
const className = shouldShowStreaming ? "markdown-blockquote streaming-partial" : "markdown-blockquote";
|
|
3976
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("blockquote", { className, children: streamingContent });
|
|
3977
|
+
}
|
|
3978
|
+
const childContent = childIds.map((childId) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MixedSegmentView, { store, nodeId: childId, inlineComponents }, childId));
|
|
3979
|
+
const renderedContent = (() => {
|
|
3980
|
+
if (mixedSegmentElements.length > 0) {
|
|
3981
|
+
return mixedSegmentElements;
|
|
3982
|
+
}
|
|
3983
|
+
if (childIds.length > 0) {
|
|
3984
|
+
const resolved = childContent.filter((child) => child !== null);
|
|
3985
|
+
if (resolved.length > 0) {
|
|
3986
|
+
return resolved;
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
if (inlineElements) {
|
|
3990
|
+
return inlineElements;
|
|
3991
|
+
}
|
|
3992
|
+
if (fallbackElements && fallbackElements.length > 0) {
|
|
3993
|
+
return fallbackElements;
|
|
3994
|
+
}
|
|
3995
|
+
return null;
|
|
3996
|
+
})();
|
|
3997
|
+
const BlockquoteComponent = registry.getBlockComponent("blockquote");
|
|
3998
|
+
const baseElement = import_react10.default.createElement(
|
|
3999
|
+
BlockquoteComponent,
|
|
4000
|
+
{
|
|
4001
|
+
inlines: block.payload.inline ?? [],
|
|
4002
|
+
renderedContent
|
|
4003
|
+
},
|
|
4004
|
+
renderedContent
|
|
4005
|
+
);
|
|
4006
|
+
if (import_react10.default.isValidElement(baseElement)) {
|
|
4007
|
+
return baseElement;
|
|
4008
|
+
}
|
|
4009
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("blockquote", { className: "markdown-blockquote", children: renderedContent });
|
|
4010
|
+
});
|
|
4011
|
+
BlockquoteBlockView.displayName = "BlockquoteBlockView";
|
|
4012
|
+
var MixedSegmentView = import_react10.default.memo(({ store, nodeId, inlineComponents }) => {
|
|
4013
|
+
const node = useRendererNode(store, nodeId);
|
|
4014
|
+
if (!node) return null;
|
|
4015
|
+
switch (node.type) {
|
|
4016
|
+
case "paragraph-text":
|
|
4017
|
+
case "blockquote-text":
|
|
4018
|
+
case "list-item-text": {
|
|
4019
|
+
const inline = node.props?.inline ?? [];
|
|
4020
|
+
if (inline.length > 0) {
|
|
4021
|
+
return renderInlineNodes(inline, inlineComponents);
|
|
4022
|
+
}
|
|
4023
|
+
const text = typeof node.props?.text === "string" ? node.props?.text : "";
|
|
4024
|
+
if (!text) return null;
|
|
4025
|
+
return text;
|
|
4026
|
+
}
|
|
4027
|
+
case "paragraph-html":
|
|
4028
|
+
case "blockquote-html":
|
|
4029
|
+
case "list-item-html": {
|
|
4030
|
+
const raw = typeof node.props?.raw === "string" ? node.props.raw : void 0;
|
|
4031
|
+
const html = typeof node.props?.html === "string" ? node.props?.html : "";
|
|
4032
|
+
const rendered = renderInlineHtmlSegment(raw, html, {
|
|
4033
|
+
key: node.id,
|
|
4034
|
+
renderers: DEFAULT_INLINE_HTML_RENDERERS
|
|
4035
|
+
});
|
|
4036
|
+
if (rendered) {
|
|
4037
|
+
return rendered;
|
|
4038
|
+
}
|
|
4039
|
+
if (!html) return null;
|
|
4040
|
+
return import_react10.default.createElement("span", {
|
|
4041
|
+
className: "markdown-inline-html",
|
|
4042
|
+
/* biome-ignore lint/security/noDangerouslySetInnerHtml: html is sanitized upstream before reaching the renderer */
|
|
4043
|
+
dangerouslySetInnerHTML: { __html: html }
|
|
4044
|
+
});
|
|
4045
|
+
}
|
|
4046
|
+
case "paragraph-mdx":
|
|
4047
|
+
case "blockquote-mdx":
|
|
4048
|
+
case "list-item-mdx": {
|
|
4049
|
+
const status = node.props?.status ?? "pending";
|
|
4050
|
+
const raw = typeof node.props?.raw === "string" ? node.props?.raw : "";
|
|
4051
|
+
if (status === "compiled") {
|
|
4052
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "markdown-mdx-inline", "data-mdx-status": "compiled" });
|
|
4053
|
+
}
|
|
4054
|
+
if (status === "error") {
|
|
4055
|
+
const message = raw || "MDX error";
|
|
4056
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "markdown-mdx-inline text-destructive", "data-mdx-status": "error", children: message });
|
|
4057
|
+
}
|
|
4058
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "markdown-mdx-inline", "data-mdx-status": status, children: status === "compiled" ? "" : raw });
|
|
4059
|
+
}
|
|
4060
|
+
default:
|
|
4061
|
+
return null;
|
|
4062
|
+
}
|
|
4063
|
+
});
|
|
4064
|
+
MixedSegmentView.displayName = "MixedSegmentView";
|
|
4065
|
+
var HtmlBlockView = import_react10.default.memo(({ store, blockId, registry }) => {
|
|
4066
|
+
const node = useRendererNode(store, blockId);
|
|
4067
|
+
if (!node || !node.block) return null;
|
|
4068
|
+
return registry.renderBlock(node.block);
|
|
4069
|
+
});
|
|
4070
|
+
HtmlBlockView.displayName = "HtmlBlockView";
|
|
4071
|
+
var MdxBlockView = import_react10.default.memo(({ store, blockId, registry }) => {
|
|
4072
|
+
const node = useRendererNode(store, blockId);
|
|
4073
|
+
if (!node || !node.block) return null;
|
|
4074
|
+
return registry.renderBlock(node.block);
|
|
4075
|
+
});
|
|
4076
|
+
MdxBlockView.displayName = "MdxBlockView";
|
|
4077
|
+
var ListBlockView = import_react10.default.memo(
|
|
4078
|
+
({ store, blockId, registry, depth = 0 }) => {
|
|
4079
|
+
const node = useRendererNode(store, blockId);
|
|
4080
|
+
const block = node?.block;
|
|
4081
|
+
const childIds = useRendererChildren(store, blockId);
|
|
4082
|
+
const inlineComponents = registry.getInlineComponents();
|
|
4083
|
+
const ordered = Boolean(node?.props?.ordered ?? block?.payload.meta?.ordered);
|
|
4084
|
+
const Tag = ordered ? "ol" : "ul";
|
|
4085
|
+
const listDebugEnabled = isListPatchDebugEnabled();
|
|
4086
|
+
const listItemIds = import_react10.default.useMemo(() => {
|
|
4087
|
+
const ids = [];
|
|
4088
|
+
for (const childId of childIds) {
|
|
4089
|
+
const childNode = store.getNode(childId);
|
|
4090
|
+
if (childNode?.type === "list-item") {
|
|
4091
|
+
ids.push(childId);
|
|
4092
|
+
}
|
|
4093
|
+
}
|
|
4094
|
+
return ids;
|
|
4095
|
+
}, [childIds, store]);
|
|
4096
|
+
import_react10.default.useEffect(() => {
|
|
4097
|
+
if (!listDebugEnabled) return;
|
|
4098
|
+
const childTypes = childIds.map((childId) => store.getNode(childId)?.type ?? "missing");
|
|
4099
|
+
console.info("[stream-mdx:list-render]", {
|
|
4100
|
+
listId: blockId,
|
|
4101
|
+
depth,
|
|
4102
|
+
ordered,
|
|
4103
|
+
childIds,
|
|
4104
|
+
childTypes,
|
|
4105
|
+
listItemIds,
|
|
4106
|
+
listItems: listItemIds.length,
|
|
4107
|
+
totalChildren: childIds.length,
|
|
4108
|
+
isFinalized: block?.isFinalized ?? false
|
|
4109
|
+
});
|
|
4110
|
+
}, [listDebugEnabled, blockId, depth, ordered, childIds, listItemIds, store, block?.isFinalized]);
|
|
4111
|
+
const { listStyle, markerDigits } = import_react10.default.useMemo(() => {
|
|
4112
|
+
if (!ordered || listItemIds.length === 0) {
|
|
4113
|
+
return { listStyle: void 0, markerDigits: 1 };
|
|
4114
|
+
}
|
|
4115
|
+
let maxDigits = 1;
|
|
4116
|
+
listItemIds.forEach((childId, index) => {
|
|
4117
|
+
const childNode = store.getNode(childId);
|
|
4118
|
+
const raw = typeof childNode?.block?.payload?.raw === "string" ? childNode.block.payload.raw : void 0;
|
|
4119
|
+
const markerMatch = raw ? raw.match(/^([^\s]+)\s+/) : null;
|
|
4120
|
+
const marker = markerMatch?.[1]?.trim();
|
|
4121
|
+
const text = marker ?? `${index + 1}.`;
|
|
4122
|
+
const digitMatch = text.match(/\d/g);
|
|
4123
|
+
const digits = digitMatch ? digitMatch.length : text.length;
|
|
4124
|
+
if (digits > maxDigits) maxDigits = digits;
|
|
4125
|
+
});
|
|
4126
|
+
const baseIndent = depth === 0 ? "2rem" : depth === 1 ? "1.75rem" : "1.5rem";
|
|
4127
|
+
const extraDigits = Math.max(0, maxDigits - 1);
|
|
4128
|
+
return {
|
|
4129
|
+
markerDigits: maxDigits,
|
|
4130
|
+
listStyle: {
|
|
4131
|
+
["--list-indent"]: extraDigits > 0 ? `calc(${baseIndent} + ${extraDigits}ch)` : baseIndent,
|
|
4132
|
+
["--list-marker-digits"]: String(maxDigits)
|
|
4133
|
+
}
|
|
4134
|
+
};
|
|
4135
|
+
}, [ordered, listItemIds, store, depth]);
|
|
4136
|
+
if (listItemIds.length === 0) return null;
|
|
4137
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
4138
|
+
Tag,
|
|
4139
|
+
{
|
|
4140
|
+
className: `markdown-list ${ordered ? "ordered" : "unordered"}`,
|
|
4141
|
+
"data-list-depth": depth,
|
|
4142
|
+
"data-marker-digits": ordered ? markerDigits : void 0,
|
|
4143
|
+
"data-list-id": listDebugEnabled ? blockId : void 0,
|
|
4144
|
+
style: listStyle,
|
|
4145
|
+
children: listItemIds.map((childId, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
4146
|
+
ListItemView,
|
|
4147
|
+
{
|
|
4148
|
+
store,
|
|
4149
|
+
nodeId: childId,
|
|
4150
|
+
inlineComponents,
|
|
4151
|
+
registry,
|
|
4152
|
+
ordered,
|
|
4153
|
+
index,
|
|
4154
|
+
depth
|
|
4155
|
+
},
|
|
4156
|
+
childId
|
|
4157
|
+
))
|
|
4158
|
+
}
|
|
4159
|
+
);
|
|
4160
|
+
}
|
|
4161
|
+
);
|
|
4162
|
+
ListBlockView.displayName = "ListBlockView";
|
|
4163
|
+
var ListItemView = import_react10.default.memo(({ store, nodeId, inlineComponents, registry, ordered, index, depth }) => {
|
|
4164
|
+
const node = useRendererNode(store, nodeId);
|
|
4165
|
+
const childIds = useRendererChildren(store, nodeId);
|
|
4166
|
+
const inline = node?.props?.inline ?? [];
|
|
4167
|
+
const isTask = Boolean(node?.props?.task);
|
|
4168
|
+
const isChecked = Boolean(node?.props?.checked);
|
|
4169
|
+
const block = node?.block;
|
|
4170
|
+
const raw = typeof block?.payload?.raw === "string" ? block.payload.raw : void 0;
|
|
4171
|
+
const markerMatch = raw ? raw.match(/^([^\s]+)\s+/) : null;
|
|
4172
|
+
const marker = markerMatch?.[1]?.trim();
|
|
4173
|
+
const inferredIndex = typeof node?.props?.index === "number" ? Number(node?.props?.index) : index;
|
|
4174
|
+
const isOrdered = Boolean(node?.props?.ordered ?? ordered);
|
|
4175
|
+
const counterText = !isTask ? isOrdered ? marker ? marker : `${inferredIndex + 1}.` : "\u2022" : void 0;
|
|
4176
|
+
const listDebugEnabled = isListPatchDebugEnabled();
|
|
4177
|
+
const inlineLength = inline.length;
|
|
4178
|
+
const [segmentChildIds, contentChildIds] = import_react10.default.useMemo(() => {
|
|
4179
|
+
const segments = [];
|
|
4180
|
+
const structural = [];
|
|
4181
|
+
for (const childId of childIds) {
|
|
4182
|
+
const childNode = store.getNode(childId);
|
|
4183
|
+
if (!childNode) continue;
|
|
4184
|
+
if (childNode.type?.startsWith("list-item-")) {
|
|
4185
|
+
segments.push(childId);
|
|
4186
|
+
} else {
|
|
4187
|
+
structural.push(childId);
|
|
4188
|
+
}
|
|
4189
|
+
}
|
|
4190
|
+
return [segments, structural];
|
|
4191
|
+
}, [childIds, store]);
|
|
4192
|
+
const segmentElements = import_react10.default.useMemo(() => {
|
|
4193
|
+
return segmentChildIds.map((childId) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MixedSegmentView, { store, nodeId: childId, inlineComponents }, childId));
|
|
4194
|
+
}, [segmentChildIds, store, inlineComponents]);
|
|
4195
|
+
const primaryContent = import_react10.default.useMemo(() => {
|
|
4196
|
+
if (segmentChildIds.length > 0) {
|
|
4197
|
+
return segmentElements;
|
|
4198
|
+
}
|
|
4199
|
+
return renderInlineNodes(inline, inlineComponents);
|
|
4200
|
+
}, [segmentChildIds, segmentElements, inline, inlineComponents]);
|
|
4201
|
+
const trailingChildren = import_react10.default.useMemo(() => {
|
|
4202
|
+
return contentChildIds.map((childId) => {
|
|
4203
|
+
const childNode = store.getNode(childId);
|
|
4204
|
+
if (!childNode) return null;
|
|
4205
|
+
switch (childNode.type) {
|
|
4206
|
+
case "paragraph": {
|
|
4207
|
+
const childInline = childNode.props?.inline ?? [];
|
|
4208
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "markdown-list-item-paragraph", children: renderInlineNodes(childInline, inlineComponents) }, childId);
|
|
4209
|
+
}
|
|
4210
|
+
case "list":
|
|
4211
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ListBlockView, { store, blockId: childId, registry, depth: depth + 1 }, childId);
|
|
4212
|
+
default:
|
|
4213
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BlockNodeRenderer, { store, blockId: childId, registry }, childId);
|
|
4214
|
+
}
|
|
4215
|
+
});
|
|
4216
|
+
}, [contentChildIds, store, inlineComponents, registry, depth]);
|
|
4217
|
+
const filteredChildren = trailingChildren.filter((child) => Boolean(child));
|
|
4218
|
+
const hasChildren = filteredChildren.length > 0;
|
|
4219
|
+
import_react10.default.useEffect(() => {
|
|
4220
|
+
if (!listDebugEnabled) return;
|
|
4221
|
+
const childTypes = childIds.map((childId) => store.getNode(childId)?.type ?? "missing");
|
|
4222
|
+
console.info("[stream-mdx:list-item-render]", {
|
|
4223
|
+
itemId: nodeId,
|
|
4224
|
+
depth,
|
|
4225
|
+
ordered: isOrdered,
|
|
4226
|
+
inferredIndex,
|
|
4227
|
+
marker,
|
|
4228
|
+
isTask,
|
|
4229
|
+
isChecked,
|
|
4230
|
+
childIds,
|
|
4231
|
+
childTypes,
|
|
4232
|
+
segmentChildIds,
|
|
4233
|
+
contentChildIds,
|
|
4234
|
+
inlineLength,
|
|
4235
|
+
hasChildren,
|
|
4236
|
+
isFinalized: block?.isFinalized ?? false
|
|
4237
|
+
});
|
|
4238
|
+
}, [
|
|
4239
|
+
listDebugEnabled,
|
|
4240
|
+
nodeId,
|
|
4241
|
+
depth,
|
|
4242
|
+
isOrdered,
|
|
4243
|
+
inferredIndex,
|
|
4244
|
+
marker,
|
|
4245
|
+
isTask,
|
|
4246
|
+
isChecked,
|
|
4247
|
+
childIds,
|
|
4248
|
+
segmentChildIds,
|
|
4249
|
+
contentChildIds,
|
|
4250
|
+
inlineLength,
|
|
4251
|
+
hasChildren,
|
|
4252
|
+
store,
|
|
4253
|
+
block?.isFinalized
|
|
4254
|
+
]);
|
|
4255
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
4256
|
+
"li",
|
|
4257
|
+
{
|
|
4258
|
+
className: `markdown-list-item${isTask ? " markdown-list-item-task" : ""}`,
|
|
4259
|
+
"data-counter-text": counterText,
|
|
4260
|
+
"data-list-depth": depth,
|
|
4261
|
+
"data-list-item-id": listDebugEnabled ? nodeId : void 0,
|
|
4262
|
+
children: [
|
|
4263
|
+
isTask ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "markdown-task", children: [
|
|
4264
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", className: "markdown-task-checkbox", checked: isChecked, readOnly: true, disabled: true, tabIndex: -1, "aria-checked": isChecked }),
|
|
4265
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "markdown-task-content", children: primaryContent })
|
|
4266
|
+
] }) : primaryContent,
|
|
4267
|
+
hasChildren ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "markdown-list-item-children", children: filteredChildren }) : null
|
|
4268
|
+
]
|
|
4269
|
+
}
|
|
4270
|
+
);
|
|
4271
|
+
});
|
|
4272
|
+
ListItemView.displayName = "ListItemView";
|
|
4273
|
+
var TableBlockView = import_react10.default.memo(({ store, blockId, registry }) => {
|
|
4274
|
+
const tableElements = registry.getTableElements();
|
|
4275
|
+
const childIds = useRendererChildren(store, blockId);
|
|
4276
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(tableElements.Table, { className: "markdown-table w-full caption-bottom text-base", children: childIds.map((sectionId) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableSectionView, { store, nodeId: sectionId, registry }, sectionId)) });
|
|
4277
|
+
});
|
|
4278
|
+
TableBlockView.displayName = "TableBlockView";
|
|
4279
|
+
var TableSectionView = import_react10.default.memo(({ store, nodeId, registry }) => {
|
|
4280
|
+
const node = useRendererNode(store, nodeId);
|
|
4281
|
+
if (!node) return null;
|
|
4282
|
+
const tableElements = registry.getTableElements();
|
|
4283
|
+
if (node.type === "table-header") {
|
|
4284
|
+
const childIds = useRendererChildren(store, nodeId);
|
|
4285
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(tableElements.Thead, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(tableElements.Tr, { children: childIds.map((cellId) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCellView, { store, nodeId: cellId, registry }, cellId)) }) });
|
|
4286
|
+
}
|
|
4287
|
+
if (node.type === "table-body") {
|
|
4288
|
+
const rowIds = useRendererChildren(store, nodeId);
|
|
4289
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(tableElements.Tbody, { children: rowIds.map((rowId) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableRowView, { store, nodeId: rowId, registry }, rowId)) });
|
|
4290
|
+
}
|
|
4291
|
+
return null;
|
|
4292
|
+
});
|
|
4293
|
+
TableSectionView.displayName = "TableSectionView";
|
|
4294
|
+
var TableRowView = import_react10.default.memo(({ store, nodeId, registry }) => {
|
|
4295
|
+
const tableElements = registry.getTableElements();
|
|
4296
|
+
const cellIds = useRendererChildren(store, nodeId);
|
|
4297
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(tableElements.Tr, { children: cellIds.map((cellId) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCellView, { store, nodeId: cellId, registry }, cellId)) });
|
|
4298
|
+
});
|
|
4299
|
+
TableRowView.displayName = "TableRowView";
|
|
4300
|
+
var TableCellView = import_react10.default.memo(({ store, nodeId, registry }) => {
|
|
4301
|
+
const node = useRendererNode(store, nodeId);
|
|
4302
|
+
const inlineComponents = registry.getInlineComponents();
|
|
4303
|
+
const tableElements = registry.getTableElements();
|
|
4304
|
+
if (!node) return null;
|
|
4305
|
+
const Tag = node.type === "table-header-cell" ? tableElements.Th : tableElements.Td;
|
|
4306
|
+
const inline = node.props?.inline ?? [];
|
|
4307
|
+
const align = node.props?.align;
|
|
4308
|
+
const content = renderInlineNodes(inline, inlineComponents);
|
|
4309
|
+
const alignProps = align ? { align } : void 0;
|
|
4310
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tag, { ...alignProps, children: content });
|
|
4311
|
+
});
|
|
4312
|
+
TableCellView.displayName = "TableCellView";
|
|
4313
|
+
var CodeBlockView = import_react10.default.memo(({ store, blockId, registry }) => {
|
|
4314
|
+
const node = useRendererNode(store, blockId);
|
|
4315
|
+
const childIds = useRendererChildren(store, blockId);
|
|
4316
|
+
const CodeComponent = registry.getBlockComponent("code");
|
|
4317
|
+
const deferredConfig = import_react10.default.useContext(DeferredRenderContext);
|
|
4318
|
+
const deferredRef = import_react10.default.useRef(null);
|
|
4319
|
+
const shouldRenderDeferred = useDeferredRender(
|
|
4320
|
+
deferredRef,
|
|
4321
|
+
deferredConfig ? { ...deferredConfig, enabled: true } : { enabled: false }
|
|
4322
|
+
);
|
|
4323
|
+
if (!node || !node.block) return null;
|
|
4324
|
+
const blockIsFinalized = node.block.isFinalized;
|
|
4325
|
+
const nodeVersion = node.version;
|
|
4326
|
+
const lines = import_react10.default.useMemo(() => {
|
|
4327
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
4328
|
+
const seenIndices = /* @__PURE__ */ new Set();
|
|
4329
|
+
const deduped = [];
|
|
4330
|
+
for (const childId of childIds) {
|
|
4331
|
+
const child = store.getNode(childId);
|
|
4332
|
+
if (!child) continue;
|
|
4333
|
+
const index = typeof child.props?.index === "number" ? child.props?.index : deduped.length;
|
|
4334
|
+
const text = typeof child.props?.text === "string" ? child.props?.text : "";
|
|
4335
|
+
const html = typeof child.props?.html === "string" ? child.props?.html : null;
|
|
4336
|
+
const tokens = Object.prototype.hasOwnProperty.call(child.props ?? {}, "tokens") ? child.props?.tokens : void 0;
|
|
4337
|
+
if (seenIds.has(child.id) || seenIndices.has(index)) {
|
|
4338
|
+
console.warn("[renderer-view] duplicate code line detected", {
|
|
4339
|
+
blockId,
|
|
4340
|
+
childId: child.id,
|
|
4341
|
+
index
|
|
4342
|
+
});
|
|
4343
|
+
continue;
|
|
4344
|
+
}
|
|
4345
|
+
seenIds.add(child.id);
|
|
4346
|
+
seenIndices.add(index);
|
|
4347
|
+
deduped.push({ id: child.id, index, text, html, tokens });
|
|
4348
|
+
}
|
|
4349
|
+
deduped.sort((a, b) => a.index - b.index);
|
|
4350
|
+
return deduped;
|
|
4351
|
+
}, [childIds, store, blockId, nodeVersion]);
|
|
4352
|
+
const lang = typeof node.props?.lang === "string" ? node.props?.lang : node.block.payload.meta?.lang;
|
|
4353
|
+
const preAttrs = node.props?.preAttrs ?? void 0;
|
|
4354
|
+
const codeAttrs = node.props?.codeAttrs ?? void 0;
|
|
4355
|
+
const blockMeta = node.block.payload.meta ?? void 0;
|
|
4356
|
+
const lazyEnabled = Boolean(blockMeta?.lazyTokenization);
|
|
4357
|
+
const lazyTokenizedUntil = typeof blockMeta?.lazyTokenizedUntil === "number" ? blockMeta.lazyTokenizedUntil : 0;
|
|
4358
|
+
const lazyHighlightedLines = Array.isArray(blockMeta?.highlightedLines) ? blockMeta.highlightedLines.length : 0;
|
|
4359
|
+
const lazyTokenLines = Array.isArray(blockMeta?.tokenLines) ? blockMeta.tokenLines.length : 0;
|
|
4360
|
+
const lazySourceTextLineCount = import_react10.default.useMemo(() => {
|
|
4361
|
+
const raw = typeof node.block?.payload.raw === "string" ? node.block.payload.raw : "";
|
|
4362
|
+
const metaCode = typeof blockMeta?.code === "string" ? blockMeta.code : null;
|
|
4363
|
+
const sourceText = resolveCodeSourceText(raw, metaCode);
|
|
4364
|
+
if (sourceText.length === 0) return 0;
|
|
4365
|
+
return sourceText.split("\n").length;
|
|
4366
|
+
}, [blockMeta, node.block?.payload.raw]);
|
|
4367
|
+
const totalLazyLineCount = Math.max(lines.length, lazyHighlightedLines, lazyTokenLines, lazySourceTextLineCount);
|
|
4368
|
+
const virtualizationConfig = DEFAULT_VIRTUALIZED_CODE_CONFIG;
|
|
4369
|
+
const virtualizationDisabled = typeof process !== "undefined" && typeof process.env === "object" && process.env.STREAM_MDX_DISABLE_VIRTUALIZED_CODE === "true";
|
|
4370
|
+
const canVirtualize = node.block.isFinalized && !lazyEnabled;
|
|
4371
|
+
const shouldVirtualize = canVirtualize && !virtualizationDisabled && virtualizationConfig.enabled && lines.length >= virtualizationConfig.virtualizeThreshold;
|
|
4372
|
+
const virtualization = useVirtualizedCode(lines, shouldVirtualize ? virtualizationConfig : { ...virtualizationConfig, enabled: false });
|
|
4373
|
+
const highlightRequester = useCodeHighlightRequester();
|
|
4374
|
+
const lastRangeRef = import_react10.default.useRef(null);
|
|
4375
|
+
const rafRef = import_react10.default.useRef(null);
|
|
4376
|
+
const idleRef = import_react10.default.useRef(null);
|
|
4377
|
+
import_react10.default.useEffect(() => {
|
|
4378
|
+
if (!shouldVirtualize || !lazyEnabled || !highlightRequester) return;
|
|
4379
|
+
const { visibleStart, visibleEnd, startIndex, endIndex } = virtualization.window;
|
|
4380
|
+
if (endIndex <= lazyTokenizedUntil) return;
|
|
4381
|
+
const nextRange = {
|
|
4382
|
+
visibleStart,
|
|
4383
|
+
visibleEnd,
|
|
4384
|
+
prefetchStart: startIndex,
|
|
4385
|
+
prefetchEnd: endIndex,
|
|
4386
|
+
tokenizedUntil: lazyTokenizedUntil
|
|
4387
|
+
};
|
|
4388
|
+
const last = lastRangeRef.current;
|
|
4389
|
+
if (last && last.visibleStart === nextRange.visibleStart && last.visibleEnd === nextRange.visibleEnd && last.prefetchStart === nextRange.prefetchStart && last.prefetchEnd === nextRange.prefetchEnd && last.tokenizedUntil === nextRange.tokenizedUntil) {
|
|
4390
|
+
return;
|
|
4391
|
+
}
|
|
4392
|
+
lastRangeRef.current = nextRange;
|
|
4393
|
+
if (rafRef.current !== null && typeof cancelAnimationFrame === "function") {
|
|
4394
|
+
cancelAnimationFrame(rafRef.current);
|
|
4395
|
+
}
|
|
4396
|
+
if (typeof requestAnimationFrame === "function") {
|
|
4397
|
+
rafRef.current = requestAnimationFrame(() => {
|
|
4398
|
+
highlightRequester({
|
|
4399
|
+
blockId,
|
|
4400
|
+
startLine: visibleStart,
|
|
4401
|
+
endLine: visibleEnd,
|
|
4402
|
+
priority: "visible",
|
|
4403
|
+
reason: "scroll"
|
|
4404
|
+
});
|
|
4405
|
+
rafRef.current = null;
|
|
4406
|
+
});
|
|
4407
|
+
} else {
|
|
4408
|
+
highlightRequester({
|
|
4409
|
+
blockId,
|
|
4410
|
+
startLine: visibleStart,
|
|
4411
|
+
endLine: visibleEnd,
|
|
4412
|
+
priority: "visible",
|
|
4413
|
+
reason: "scroll"
|
|
4414
|
+
});
|
|
4415
|
+
}
|
|
4416
|
+
const prefetchNeeded = startIndex < visibleStart || endIndex > visibleEnd;
|
|
4417
|
+
if (prefetchNeeded) {
|
|
4418
|
+
const idleCallback = globalThis.requestIdleCallback;
|
|
4419
|
+
const cancelIdle = globalThis.cancelIdleCallback;
|
|
4420
|
+
if (idleRef.current !== null) {
|
|
4421
|
+
if (typeof cancelIdle === "function") {
|
|
4422
|
+
cancelIdle(idleRef.current);
|
|
4423
|
+
} else if (typeof clearTimeout === "function") {
|
|
4424
|
+
clearTimeout(idleRef.current);
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
const schedule = () => {
|
|
4428
|
+
highlightRequester({
|
|
4429
|
+
blockId,
|
|
4430
|
+
startLine: startIndex,
|
|
4431
|
+
endLine: endIndex,
|
|
4432
|
+
priority: "prefetch",
|
|
4433
|
+
reason: "buffer"
|
|
4434
|
+
});
|
|
4435
|
+
idleRef.current = null;
|
|
4436
|
+
};
|
|
4437
|
+
if (typeof idleCallback === "function") {
|
|
4438
|
+
idleRef.current = idleCallback(() => schedule());
|
|
4439
|
+
} else if (typeof setTimeout === "function") {
|
|
4440
|
+
idleRef.current = setTimeout(schedule, 80);
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
}, [
|
|
4444
|
+
blockId,
|
|
4445
|
+
highlightRequester,
|
|
4446
|
+
lazyEnabled,
|
|
4447
|
+
lazyTokenizedUntil,
|
|
4448
|
+
shouldVirtualize,
|
|
4449
|
+
virtualization.window.visibleStart,
|
|
4450
|
+
virtualization.window.visibleEnd,
|
|
4451
|
+
virtualization.window.startIndex,
|
|
4452
|
+
virtualization.window.endIndex
|
|
4453
|
+
]);
|
|
4454
|
+
import_react10.default.useEffect(() => {
|
|
4455
|
+
if (shouldVirtualize || !blockIsFinalized || !lazyEnabled || !highlightRequester) return;
|
|
4456
|
+
if (totalLazyLineCount <= 0 || lazyTokenizedUntil >= totalLazyLineCount) return;
|
|
4457
|
+
const nextRange = {
|
|
4458
|
+
visibleStart: 0,
|
|
4459
|
+
visibleEnd: totalLazyLineCount,
|
|
4460
|
+
prefetchStart: 0,
|
|
4461
|
+
prefetchEnd: totalLazyLineCount,
|
|
4462
|
+
tokenizedUntil: lazyTokenizedUntil
|
|
4463
|
+
};
|
|
4464
|
+
const last = lastRangeRef.current;
|
|
4465
|
+
if (last && last.visibleStart === nextRange.visibleStart && last.visibleEnd === nextRange.visibleEnd && last.prefetchStart === nextRange.prefetchStart && last.prefetchEnd === nextRange.prefetchEnd && last.tokenizedUntil === nextRange.tokenizedUntil) {
|
|
4466
|
+
return;
|
|
4467
|
+
}
|
|
4468
|
+
lastRangeRef.current = nextRange;
|
|
4469
|
+
if (rafRef.current !== null && typeof cancelAnimationFrame === "function") {
|
|
4470
|
+
cancelAnimationFrame(rafRef.current);
|
|
4471
|
+
}
|
|
4472
|
+
const requestFullHighlight = () => {
|
|
4473
|
+
highlightRequester({
|
|
4474
|
+
blockId,
|
|
4475
|
+
startLine: 0,
|
|
4476
|
+
endLine: totalLazyLineCount,
|
|
4477
|
+
priority: "visible",
|
|
4478
|
+
reason: "finalize-full"
|
|
4479
|
+
});
|
|
4480
|
+
rafRef.current = null;
|
|
4481
|
+
};
|
|
4482
|
+
if (typeof requestAnimationFrame === "function") {
|
|
4483
|
+
rafRef.current = requestAnimationFrame(requestFullHighlight);
|
|
4484
|
+
} else {
|
|
4485
|
+
requestFullHighlight();
|
|
4486
|
+
}
|
|
4487
|
+
}, [blockId, blockIsFinalized, highlightRequester, lazyEnabled, lazyTokenizedUntil, shouldVirtualize, totalLazyLineCount]);
|
|
4488
|
+
const highlightedHtml = import_react10.default.useMemo(() => {
|
|
4489
|
+
const blockHtml = node.block?.payload.highlightedHtml ?? "";
|
|
4490
|
+
const raw = typeof node.block?.payload.raw === "string" ? node.block.payload.raw : "";
|
|
4491
|
+
const metaCode = typeof node.block?.payload.meta?.code === "string" ? (node.block?.payload.meta).code : null;
|
|
4492
|
+
const sourceText = resolveCodeSourceText(raw, metaCode);
|
|
4493
|
+
const sourceEndsWithNewline = sourceText.endsWith("\n");
|
|
4494
|
+
const sourceLines = sourceText.length > 0 ? sourceText.split("\n") : [];
|
|
4495
|
+
const rawFallbackLines = buildCodeFallbackLines(raw);
|
|
4496
|
+
const deterministicWrapperAttrs = (0, import_core5.getDefaultCodeWrapperAttributes)(lang);
|
|
4497
|
+
const composedPreAttrs = deterministicWrapperAttrs.preAttrs;
|
|
4498
|
+
const composedCodeAttrs = deterministicWrapperAttrs.codeAttrs;
|
|
4499
|
+
const shouldAppendTerminalNewline = (renderedLines) => {
|
|
4500
|
+
if (!sourceEndsWithNewline || renderedLines.length === 0 || lines.length === 0) {
|
|
4501
|
+
return false;
|
|
4502
|
+
}
|
|
4503
|
+
const lastRendered = renderedLines[renderedLines.length - 1];
|
|
4504
|
+
const lastKnown = lines[lines.length - 1];
|
|
4505
|
+
if (!lastRendered || !lastKnown || lastRendered.index !== lastKnown.index || lastRendered.text === "") {
|
|
4506
|
+
return false;
|
|
4507
|
+
}
|
|
4508
|
+
const expectedRenderedLineCount = Math.max(0, sourceLines.length - 1);
|
|
4509
|
+
const expectedLastRenderedLine = sourceLines.length >= 2 ? sourceLines[sourceLines.length - 2] ?? "" : "";
|
|
4510
|
+
return renderedLines.length === expectedRenderedLineCount && lastRendered.text === expectedLastRenderedLine;
|
|
4511
|
+
};
|
|
4512
|
+
if (lines.length > 0) {
|
|
4513
|
+
const hasLineHtml = lines.some((line) => typeof line.html === "string" && line.html.length > 0);
|
|
4514
|
+
if (!hasLineHtml && blockHtml) {
|
|
4515
|
+
return blockHtml;
|
|
4516
|
+
}
|
|
4517
|
+
if (shouldVirtualize) {
|
|
4518
|
+
return composeHighlightedHtml(
|
|
4519
|
+
virtualization.window.visibleLines,
|
|
4520
|
+
composedPreAttrs,
|
|
4521
|
+
composedCodeAttrs,
|
|
4522
|
+
shouldAppendTerminalNewline(virtualization.window.visibleLines)
|
|
4523
|
+
);
|
|
4524
|
+
}
|
|
4525
|
+
return composeHighlightedHtml(lines, composedPreAttrs, composedCodeAttrs, shouldAppendTerminalNewline(lines));
|
|
4526
|
+
}
|
|
4527
|
+
if (!shouldVirtualize && blockHtml) {
|
|
4528
|
+
const hasHighlightedLines = blockHtml.includes('class="line"');
|
|
4529
|
+
if (!hasHighlightedLines && rawFallbackLines.length > 0) {
|
|
4530
|
+
return composeHighlightedHtml(
|
|
4531
|
+
rawFallbackLines,
|
|
4532
|
+
composedPreAttrs,
|
|
4533
|
+
composedCodeAttrs,
|
|
4534
|
+
sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
|
|
4535
|
+
);
|
|
4536
|
+
}
|
|
4537
|
+
return blockHtml;
|
|
4538
|
+
}
|
|
4539
|
+
if (blockHtml?.trim().match(/^```[\w-]*\s*$/)) {
|
|
4540
|
+
if (rawFallbackLines.length > 0) {
|
|
4541
|
+
return composeHighlightedHtml(
|
|
4542
|
+
rawFallbackLines,
|
|
4543
|
+
composedPreAttrs,
|
|
4544
|
+
composedCodeAttrs,
|
|
4545
|
+
sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
|
|
4546
|
+
);
|
|
4547
|
+
}
|
|
4548
|
+
}
|
|
4549
|
+
if (!blockHtml && rawFallbackLines.length > 0) {
|
|
4550
|
+
return composeHighlightedHtml(
|
|
4551
|
+
rawFallbackLines,
|
|
4552
|
+
composedPreAttrs,
|
|
4553
|
+
composedCodeAttrs,
|
|
4554
|
+
sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
|
|
4555
|
+
);
|
|
4556
|
+
}
|
|
4557
|
+
return blockHtml;
|
|
4558
|
+
}, [blockIsFinalized, shouldVirtualize, node.block?.payload.highlightedHtml, node.block?.payload.meta, lines, lang, virtualization.window.visibleLines, node.block?.payload.raw]);
|
|
4559
|
+
const rendered = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
4560
|
+
CodeComponent,
|
|
4561
|
+
{
|
|
4562
|
+
html: highlightedHtml,
|
|
4563
|
+
meta: node.block.payload.meta,
|
|
4564
|
+
lines: shouldVirtualize ? virtualization.window.visibleLines : lines,
|
|
4565
|
+
lang,
|
|
4566
|
+
preAttrs,
|
|
4567
|
+
codeAttrs
|
|
4568
|
+
}
|
|
4569
|
+
);
|
|
4570
|
+
const effectiveLineCount = import_react10.default.useMemo(() => {
|
|
4571
|
+
if (lines.length > 0) {
|
|
4572
|
+
return lines.length;
|
|
4573
|
+
}
|
|
4574
|
+
return 1;
|
|
4575
|
+
}, [lines.length]);
|
|
4576
|
+
const codeFrameClass = "not-prose flex flex-col rounded-lg border border-input pt-1 font-mono text-sm";
|
|
4577
|
+
const codeMetricsAttrs = {
|
|
4578
|
+
"data-code-block": "true",
|
|
4579
|
+
"data-block-id": blockId,
|
|
4580
|
+
"data-code-virtualized": shouldVirtualize ? "true" : "false",
|
|
4581
|
+
"data-code-total-lines": String(effectiveLineCount),
|
|
4582
|
+
"data-code-mounted-lines": String(shouldVirtualize ? virtualization.window.mountedLines : effectiveLineCount),
|
|
4583
|
+
"data-code-window-size": String(virtualization.config.windowSize)
|
|
4584
|
+
};
|
|
4585
|
+
const codeView = shouldVirtualize ? (() => {
|
|
4586
|
+
const { containerRef, window: window2, handleScroll, lineHeight } = virtualization;
|
|
4587
|
+
const spacerTop = window2.startIndex * lineHeight;
|
|
4588
|
+
const spacerBottom = (window2.totalLines - window2.endIndex) * lineHeight;
|
|
4589
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: codeFrameClass, ...codeMetricsAttrs, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
4590
|
+
"div",
|
|
4591
|
+
{
|
|
4592
|
+
ref: containerRef,
|
|
4593
|
+
className: "markdown-code-block-container relative",
|
|
4594
|
+
style: { overflowY: "auto", overflowX: "hidden", maxHeight: "600px" },
|
|
4595
|
+
onScroll: handleScroll,
|
|
4596
|
+
children: [
|
|
4597
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { height: spacerTop }, "aria-hidden": "true" }),
|
|
4598
|
+
rendered,
|
|
4599
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { height: spacerBottom }, "aria-hidden": "true" })
|
|
4600
|
+
]
|
|
4601
|
+
}
|
|
4602
|
+
) });
|
|
4603
|
+
})() : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: codeFrameClass, ...codeMetricsAttrs, children: rendered });
|
|
4604
|
+
const blockComponentMap = registry.getBlockComponentMap();
|
|
4605
|
+
const MermaidComponent = Object.prototype.hasOwnProperty.call(blockComponentMap, "mermaid") ? blockComponentMap.mermaid : null;
|
|
4606
|
+
if ((lang ?? "").toLowerCase() === "mermaid" && MermaidComponent) {
|
|
4607
|
+
const raw = typeof node.block.payload.raw === "string" ? node.block.payload.raw : "";
|
|
4608
|
+
const fenced = (0, import_core5.stripCodeFence)(raw);
|
|
4609
|
+
const code = fenced.hadFence ? fenced.code : raw;
|
|
4610
|
+
if (deferredConfig) {
|
|
4611
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: deferredRef, children: shouldRenderDeferred ? import_react10.default.createElement(MermaidComponent, {
|
|
4612
|
+
code,
|
|
4613
|
+
renderCode: codeView,
|
|
4614
|
+
meta: node.block.payload.meta,
|
|
4615
|
+
isFinalized: node.block.isFinalized
|
|
4616
|
+
}) : codeView });
|
|
4617
|
+
}
|
|
4618
|
+
return import_react10.default.createElement(MermaidComponent, {
|
|
4619
|
+
code,
|
|
4620
|
+
renderCode: codeView,
|
|
4621
|
+
meta: node.block.payload.meta,
|
|
4622
|
+
isFinalized: node.block.isFinalized
|
|
4623
|
+
});
|
|
4624
|
+
}
|
|
4625
|
+
return codeView;
|
|
4626
|
+
});
|
|
4627
|
+
CodeBlockView.displayName = "CodeBlockView";
|
|
4628
|
+
function composeHighlightedHtml(lines, preAttrs, codeAttrs, appendTerminalNewline = false) {
|
|
4629
|
+
const lineMarkup = lines.map((line) => {
|
|
4630
|
+
const content = line.html ?? renderTokenLineHtml(line.tokens) ?? escapeHtml3(line.text);
|
|
4631
|
+
const dataLine = Number.isFinite(line.index) ? ` data-line="${line.index + 1}"` : "";
|
|
4632
|
+
return `<span class="line"${dataLine}>${content}</span>`;
|
|
4633
|
+
}).join("\n");
|
|
4634
|
+
const preAttr = attrsToString(preAttrs);
|
|
4635
|
+
const codeAttr = attrsToString(codeAttrs);
|
|
4636
|
+
return `<pre${preAttr}><code${codeAttr}>${lineMarkup}${appendTerminalNewline ? "\n" : ""}</code></pre>`;
|
|
4637
|
+
}
|
|
4638
|
+
var TOKEN_FONT_STYLE_ITALIC = 1;
|
|
4639
|
+
var TOKEN_FONT_STYLE_BOLD = 2;
|
|
4640
|
+
var TOKEN_FONT_STYLE_UNDERLINE = 4;
|
|
4641
|
+
var TOKEN_FONT_STYLE_STRIKETHROUGH = 8;
|
|
4642
|
+
function renderTokenLineHtml(tokens) {
|
|
4643
|
+
if (!tokens || !Array.isArray(tokens.spans)) {
|
|
4644
|
+
return null;
|
|
4645
|
+
}
|
|
4646
|
+
return mergeRenderableTokenSpans(tokens.spans).map((span) => renderTokenSpanHtml(span)).join("");
|
|
4647
|
+
}
|
|
4648
|
+
function renderTokenSpanHtml(span) {
|
|
4649
|
+
const dark = span.v?.dark;
|
|
4650
|
+
const light = span.v?.light;
|
|
4651
|
+
const single = span.s;
|
|
4652
|
+
const styles = [];
|
|
4653
|
+
if (dark?.fg) {
|
|
4654
|
+
styles.push(`--shiki-dark:${dark.fg}`);
|
|
4655
|
+
}
|
|
4656
|
+
if (light?.fg) {
|
|
4657
|
+
styles.push(`--shiki-light:${light.fg}`);
|
|
4658
|
+
}
|
|
4659
|
+
if (!light?.fg && !dark?.fg && single?.fg) {
|
|
4660
|
+
styles.push(`color:${single.fg}`);
|
|
4661
|
+
}
|
|
4662
|
+
const fontStyle = light?.fs ?? dark?.fs ?? single?.fs;
|
|
4663
|
+
if (typeof fontStyle === "number" && fontStyle > 0) {
|
|
4664
|
+
if (fontStyle & TOKEN_FONT_STYLE_ITALIC) {
|
|
4665
|
+
styles.push("font-style: italic");
|
|
4666
|
+
}
|
|
4667
|
+
if (fontStyle & TOKEN_FONT_STYLE_BOLD) {
|
|
4668
|
+
styles.push("font-weight: bold");
|
|
4669
|
+
}
|
|
4670
|
+
const decorations = [];
|
|
4671
|
+
if (fontStyle & TOKEN_FONT_STYLE_UNDERLINE) {
|
|
4672
|
+
decorations.push("underline");
|
|
4673
|
+
}
|
|
4674
|
+
if (fontStyle & TOKEN_FONT_STYLE_STRIKETHROUGH) {
|
|
4675
|
+
decorations.push("line-through");
|
|
4676
|
+
}
|
|
4677
|
+
if (decorations.length > 0) {
|
|
4678
|
+
styles.push(`text-decoration: ${decorations.join(" ")}`);
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4681
|
+
const content = escapeHtml3(span.t ?? "");
|
|
4682
|
+
if (styles.length === 0) {
|
|
4683
|
+
return content;
|
|
4684
|
+
}
|
|
4685
|
+
return `<span style="${styles.join(";")}">${content}</span>`;
|
|
4686
|
+
}
|
|
4687
|
+
function mergeRenderableTokenSpans(spans) {
|
|
4688
|
+
if (spans.length <= 1) {
|
|
4689
|
+
return spans.slice();
|
|
4690
|
+
}
|
|
4691
|
+
const merged = [];
|
|
4692
|
+
let pendingWhitespace = "";
|
|
4693
|
+
for (const span of spans) {
|
|
4694
|
+
const text = typeof span.t === "string" ? span.t : "";
|
|
4695
|
+
if (/^[\t ]+$/.test(text)) {
|
|
4696
|
+
pendingWhitespace += text;
|
|
4697
|
+
continue;
|
|
4698
|
+
}
|
|
4699
|
+
const nextSpan = pendingWhitespace ? { ...span, t: `${pendingWhitespace}${text}` } : span;
|
|
4700
|
+
pendingWhitespace = "";
|
|
4701
|
+
const previous = merged[merged.length - 1];
|
|
4702
|
+
if (previous && sameTokenSpanStyle(previous, nextSpan)) {
|
|
4703
|
+
previous.t += nextSpan.t;
|
|
4704
|
+
} else {
|
|
4705
|
+
merged.push({ ...nextSpan });
|
|
4706
|
+
}
|
|
4707
|
+
}
|
|
4708
|
+
if (pendingWhitespace) {
|
|
4709
|
+
const previous = merged[merged.length - 1];
|
|
4710
|
+
if (previous) {
|
|
4711
|
+
previous.t += pendingWhitespace;
|
|
4712
|
+
} else {
|
|
4713
|
+
merged.push({ t: pendingWhitespace });
|
|
4714
|
+
}
|
|
4715
|
+
}
|
|
4716
|
+
return merged;
|
|
4717
|
+
}
|
|
4718
|
+
function sameTokenSpanStyle(a, b) {
|
|
4719
|
+
const variantKeys = /* @__PURE__ */ new Set([
|
|
4720
|
+
...Object.keys(a.v ?? {}),
|
|
4721
|
+
...Object.keys(b.v ?? {})
|
|
4722
|
+
]);
|
|
4723
|
+
for (const key of variantKeys) {
|
|
4724
|
+
const aStyle = a.v?.[key];
|
|
4725
|
+
const bStyle = b.v?.[key];
|
|
4726
|
+
if ((aStyle?.fg ?? null) !== (bStyle?.fg ?? null)) return false;
|
|
4727
|
+
if ((aStyle?.bg ?? null) !== (bStyle?.bg ?? null)) return false;
|
|
4728
|
+
if ((aStyle?.fs ?? null) !== (bStyle?.fs ?? null)) return false;
|
|
4729
|
+
}
|
|
4730
|
+
if ((a.s?.fg ?? null) !== (b.s?.fg ?? null)) return false;
|
|
4731
|
+
if ((a.s?.bg ?? null) !== (b.s?.bg ?? null)) return false;
|
|
4732
|
+
if ((a.s?.fs ?? null) !== (b.s?.fs ?? null)) return false;
|
|
4733
|
+
return true;
|
|
4734
|
+
}
|
|
4735
|
+
function resolveCodeSourceText(raw, metaCode) {
|
|
4736
|
+
if (metaCode !== null) {
|
|
4737
|
+
return metaCode.replace(/\r\n?/g, "\n");
|
|
4738
|
+
}
|
|
4739
|
+
if (!raw) return "";
|
|
4740
|
+
const normalized = raw.replace(/\r\n?/g, "\n");
|
|
4741
|
+
const fenced = (0, import_core5.stripCodeFence)(normalized);
|
|
4742
|
+
return fenced.hadFence ? fenced.code : normalized;
|
|
4743
|
+
}
|
|
4744
|
+
function attrsToString(attrs) {
|
|
4745
|
+
if (!attrs) return "";
|
|
4746
|
+
return Object.entries(attrs).map(([key, value]) => ` ${key}="${escapeAttribute(value)}"`).join("");
|
|
4747
|
+
}
|
|
4748
|
+
function buildCodeFallbackLines(raw) {
|
|
4749
|
+
const normalized = normalizeCodeFallbackRaw(raw);
|
|
4750
|
+
if (normalized.length === 0) {
|
|
4751
|
+
if (/^\s*```[^\n]*\n?\s*$/.test(raw)) {
|
|
4752
|
+
return [{ id: "temp-0", index: 0, text: "", html: null }];
|
|
4753
|
+
}
|
|
4754
|
+
return [];
|
|
4755
|
+
}
|
|
4756
|
+
return normalized.split("\n").map((line, index) => ({
|
|
4757
|
+
id: `temp-${index}`,
|
|
4758
|
+
index,
|
|
4759
|
+
text: line,
|
|
4760
|
+
html: null
|
|
4761
|
+
}));
|
|
4762
|
+
}
|
|
4763
|
+
function normalizeCodeFallbackRaw(raw) {
|
|
4764
|
+
if (!raw) return "";
|
|
4765
|
+
const normalized = raw.replace(/\r\n?/g, "\n");
|
|
4766
|
+
const fenced = (0, import_core5.stripCodeFence)(normalized);
|
|
4767
|
+
if (!fenced.hadFence) {
|
|
4768
|
+
return normalized.replace(/\n+$/, "");
|
|
4769
|
+
}
|
|
4770
|
+
let code = fenced.code;
|
|
4771
|
+
code = code.replace(/\n+$/, "");
|
|
4772
|
+
return code;
|
|
4773
|
+
}
|
|
4774
|
+
function escapeHtml3(value) {
|
|
4775
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
4776
|
+
}
|
|
4777
|
+
function escapeAttribute(value) {
|
|
4778
|
+
return value.replace(/"/g, """);
|
|
4779
|
+
}
|
|
4780
|
+
|
|
4781
|
+
// src/renderer/blocks-renderer.tsx
|
|
4782
|
+
var MarkdownBlocksRenderer = import_react11.default.memo(({ blocks, componentRegistry, className = "markdown-renderer", style, store }) => {
|
|
4783
|
+
if (store) {
|
|
4784
|
+
return import_react11.default.createElement(
|
|
4785
|
+
"div",
|
|
4786
|
+
{
|
|
4787
|
+
className,
|
|
4788
|
+
style: { contain: "content", ...style ?? {} }
|
|
4789
|
+
// CSS containment for performance
|
|
4790
|
+
},
|
|
4791
|
+
import_react11.default.createElement(StoreBackedBlocks, { store, componentRegistry })
|
|
4792
|
+
);
|
|
4793
|
+
}
|
|
4794
|
+
const renderedBlocks = (0, import_react11.useMemo)(() => {
|
|
4795
|
+
return blocks.map((block) => {
|
|
4796
|
+
const key = (0, import_core6.getBlockKey)(block);
|
|
4797
|
+
return import_react11.default.createElement(BlockRenderer, {
|
|
4798
|
+
key,
|
|
4799
|
+
block,
|
|
4800
|
+
componentRegistry,
|
|
4801
|
+
isFinalized: block.isFinalized
|
|
4802
|
+
});
|
|
4803
|
+
});
|
|
4804
|
+
}, [blocks, componentRegistry]);
|
|
4805
|
+
return import_react11.default.createElement(
|
|
4806
|
+
"div",
|
|
4807
|
+
{
|
|
4808
|
+
className,
|
|
4809
|
+
style: { contain: "content", ...style ?? {} }
|
|
4810
|
+
// CSS containment for performance
|
|
4811
|
+
},
|
|
4812
|
+
renderedBlocks
|
|
4813
|
+
);
|
|
4814
|
+
});
|
|
4815
|
+
var StoreBackedBlocks = import_react11.default.memo(
|
|
4816
|
+
({ store, componentRegistry }) => {
|
|
4817
|
+
const blockIds = useRendererChildren(store, import_core6.PATCH_ROOT_ID);
|
|
4818
|
+
return import_react11.default.createElement(
|
|
4819
|
+
import_react11.default.Fragment,
|
|
4820
|
+
null,
|
|
4821
|
+
blockIds.map((blockId) => import_react11.default.createElement(BlockNodeRenderer, { key: blockId, store, blockId, registry: componentRegistry }))
|
|
4822
|
+
);
|
|
4823
|
+
}
|
|
4824
|
+
);
|
|
4825
|
+
StoreBackedBlocks.displayName = "StoreBackedBlocks";
|
|
4826
|
+
var BlockRenderer = import_react11.default.memo(({ block, componentRegistry, isFinalized }) => {
|
|
4827
|
+
const element = (0, import_react11.useMemo)(() => {
|
|
4828
|
+
return componentRegistry.renderBlock(block);
|
|
4829
|
+
}, [block, componentRegistry]);
|
|
4830
|
+
const className = `markdown-block markdown-block-${block.type} ${isFinalized ? "finalized" : "dirty"}`;
|
|
4831
|
+
return import_react11.default.cloneElement(element, {
|
|
4832
|
+
className: `${element.props.className || ""} ${className}`.trim(),
|
|
4833
|
+
"data-block-id": block.id,
|
|
4834
|
+
"data-finalized": isFinalized
|
|
4835
|
+
});
|
|
4836
|
+
});
|
|
4837
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
4838
|
+
0 && (module.exports = {
|
|
4839
|
+
ComponentRegistry,
|
|
4840
|
+
MarkdownBlocksRenderer,
|
|
4841
|
+
createRendererStore
|
|
4842
|
+
});
|