@owomark/react 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -1
- package/dist/.build-manifest.json +58 -0
- package/dist/highlight.worker.js +113 -0
- package/dist/index.d.ts +133 -35
- package/dist/index.js +1723 -219
- package/dist/mdx.worker.js +97 -0
- package/package.json +6 -4
- package/dist/index.css +0 -32
package/dist/index.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
// src/OwoMarkEditor.tsx
|
|
2
2
|
import {
|
|
3
|
-
useRef as
|
|
4
|
-
useEffect as
|
|
3
|
+
useRef as useRef4,
|
|
4
|
+
useEffect as useEffect3,
|
|
5
5
|
useImperativeHandle,
|
|
6
6
|
forwardRef,
|
|
7
|
-
useCallback
|
|
7
|
+
useCallback as useCallback2
|
|
8
8
|
} from "react";
|
|
9
9
|
import { getThemeClassName } from "@owomark/view";
|
|
10
10
|
|
|
11
11
|
// src/editor/EditorBlock.tsx
|
|
12
12
|
import { memo as memo3, useMemo } from "react";
|
|
13
|
-
import { BLOCK_TYPE_TO_CLASS, BQ_STEP, buildBlockquoteBarsBoxShadow } from "@owomark/core";
|
|
13
|
+
import { BLOCK_TYPE_TO_CLASS, BQ_STEP, buildBlockquoteBarsBoxShadow } from "@owomark/core/browser";
|
|
14
14
|
|
|
15
15
|
// src/editor/EditorDecorator.tsx
|
|
16
16
|
import { memo as memo2 } from "react";
|
|
17
|
-
import { TOKEN_TO_CLASS } from "@owomark/core";
|
|
17
|
+
import { TOKEN_TO_CLASS } from "@owomark/core/browser";
|
|
18
18
|
|
|
19
19
|
// src/editor/EditorLeaf.tsx
|
|
20
20
|
import { memo } from "react";
|
|
@@ -58,6 +58,9 @@ var EditorBlock = memo3(
|
|
|
58
58
|
{
|
|
59
59
|
"data-owo-block": blockIndex,
|
|
60
60
|
"data-block-id": block.id,
|
|
61
|
+
"data-source-key": block.sourceKey,
|
|
62
|
+
"data-source-line-start": block.startLine,
|
|
63
|
+
"data-source-line-end": block.endLine,
|
|
61
64
|
className,
|
|
62
65
|
style: bqStyle,
|
|
63
66
|
...headingLevel ? { "data-owo-heading": headingLevel } : {},
|
|
@@ -224,33 +227,135 @@ function groupByCategory(commands) {
|
|
|
224
227
|
return groups;
|
|
225
228
|
}
|
|
226
229
|
|
|
227
|
-
// src/
|
|
230
|
+
// src/toolbar/Toolbar.tsx
|
|
228
231
|
import {
|
|
229
|
-
|
|
232
|
+
useMemo as useMemo2,
|
|
230
233
|
useState as useState2,
|
|
234
|
+
useRef as useRef2,
|
|
235
|
+
useEffect as useEffect2,
|
|
236
|
+
useCallback,
|
|
237
|
+
memo as memo5
|
|
238
|
+
} from "react";
|
|
239
|
+
import {
|
|
240
|
+
editorEntryRegistry
|
|
241
|
+
} from "@owomark/core";
|
|
242
|
+
import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
243
|
+
var EMPTY_IDS = [];
|
|
244
|
+
var COLLAPSE_BREAKPOINT = 420;
|
|
245
|
+
var Toolbar = memo5(function Toolbar2({ core, toolbarConfig }) {
|
|
246
|
+
const commandIds = toolbarConfig.componentCommands ?? EMPTY_IDS;
|
|
247
|
+
const cacheKey = commandIds.join(",");
|
|
248
|
+
const commands = useMemo2(() => {
|
|
249
|
+
const registry = core.getCommandRegistry();
|
|
250
|
+
const result = [];
|
|
251
|
+
for (const id of commandIds) {
|
|
252
|
+
const descriptor = editorEntryRegistry.list().find((entry) => entry.commandId === id);
|
|
253
|
+
if (!descriptor || descriptor.entryKind !== "component") {
|
|
254
|
+
if (process.env.NODE_ENV !== "production") {
|
|
255
|
+
console.warn(`[OwoMark Toolbar] Unknown component command id: "${id}", skipping.`);
|
|
256
|
+
}
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
const cmd = registry.get(id);
|
|
260
|
+
if (cmd && cmd.toolbarEligible) {
|
|
261
|
+
result.push(cmd);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return result;
|
|
265
|
+
}, [core, cacheKey]);
|
|
266
|
+
const containerRef = useRef2(null);
|
|
267
|
+
const [collapsed, setCollapsed] = useState2(false);
|
|
268
|
+
const [popoverOpen, setPopoverOpen] = useState2(false);
|
|
269
|
+
const popoverRef = useRef2(null);
|
|
270
|
+
useEffect2(() => {
|
|
271
|
+
const el = containerRef.current;
|
|
272
|
+
if (!el || typeof ResizeObserver === "undefined") return;
|
|
273
|
+
const ro = new ResizeObserver((entries) => {
|
|
274
|
+
for (const entry of entries) {
|
|
275
|
+
setCollapsed(entry.contentRect.width < COLLAPSE_BREAKPOINT);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
ro.observe(el);
|
|
279
|
+
return () => ro.disconnect();
|
|
280
|
+
}, []);
|
|
281
|
+
useEffect2(() => {
|
|
282
|
+
if (!popoverOpen) return;
|
|
283
|
+
function handlePointerDown(e) {
|
|
284
|
+
if (popoverRef.current && !popoverRef.current.contains(e.target)) {
|
|
285
|
+
setPopoverOpen(false);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
document.addEventListener("pointerdown", handlePointerDown, true);
|
|
289
|
+
return () => document.removeEventListener("pointerdown", handlePointerDown, true);
|
|
290
|
+
}, [popoverOpen]);
|
|
291
|
+
const handleCommandClick = useCallback((e, cmdId) => {
|
|
292
|
+
e.preventDefault();
|
|
293
|
+
core.executeCommandById(cmdId);
|
|
294
|
+
setPopoverOpen(false);
|
|
295
|
+
}, [core]);
|
|
296
|
+
if (commands.length === 0) return null;
|
|
297
|
+
const buttons = commands.map((cmd) => /* @__PURE__ */ jsxs2(
|
|
298
|
+
"button",
|
|
299
|
+
{
|
|
300
|
+
type: "button",
|
|
301
|
+
className: "owo-toolbar__component-button",
|
|
302
|
+
onPointerDown: (e) => handleCommandClick(e, cmd.id),
|
|
303
|
+
children: [
|
|
304
|
+
cmd.icon && /* @__PURE__ */ jsx5("span", { className: "owo-toolbar__component-icon", children: cmd.icon }),
|
|
305
|
+
/* @__PURE__ */ jsx5("span", { className: "owo-toolbar__component-label", children: cmd.label })
|
|
306
|
+
]
|
|
307
|
+
},
|
|
308
|
+
cmd.id
|
|
309
|
+
));
|
|
310
|
+
return /* @__PURE__ */ jsx5("div", { ref: containerRef, className: "owo-toolbar__components-group", role: "toolbar", "aria-label": "Components", children: collapsed ? /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
|
|
311
|
+
/* @__PURE__ */ jsxs2(
|
|
312
|
+
"button",
|
|
313
|
+
{
|
|
314
|
+
type: "button",
|
|
315
|
+
className: "owo-toolbar__collapse-trigger",
|
|
316
|
+
onPointerDown: (e) => {
|
|
317
|
+
e.preventDefault();
|
|
318
|
+
setPopoverOpen((v) => !v);
|
|
319
|
+
},
|
|
320
|
+
children: [
|
|
321
|
+
/* @__PURE__ */ jsx5("span", { className: "owo-toolbar__component-label", children: "Components" }),
|
|
322
|
+
/* @__PURE__ */ jsx5("span", { "aria-hidden": "true", children: popoverOpen ? "\u25B2" : "\u25BC" })
|
|
323
|
+
]
|
|
324
|
+
}
|
|
325
|
+
),
|
|
326
|
+
popoverOpen && /* @__PURE__ */ jsx5("div", { ref: popoverRef, className: "owo-toolbar__popover", children: buttons })
|
|
327
|
+
] }) : buttons });
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// src/useOwoMarkCore.ts
|
|
331
|
+
import {
|
|
332
|
+
useRef as useRef3,
|
|
333
|
+
useState as useState3,
|
|
231
334
|
useLayoutEffect as useLayoutEffect2
|
|
232
335
|
} from "react";
|
|
233
336
|
import {
|
|
234
337
|
createOwoMarkCore,
|
|
235
|
-
readSelection,
|
|
236
|
-
restoreSelection,
|
|
237
|
-
invalidateBlockCache,
|
|
238
338
|
virtualToLinear
|
|
239
339
|
} from "@owomark/core";
|
|
340
|
+
import {
|
|
341
|
+
readSelection,
|
|
342
|
+
restoreSelection,
|
|
343
|
+
invalidateBlockCache
|
|
344
|
+
} from "@owomark/core/browser";
|
|
240
345
|
function useOwoMarkCore(options) {
|
|
241
346
|
const { initialMarkdown, readOnly = false } = options;
|
|
242
|
-
const containerRef =
|
|
243
|
-
const coreRef =
|
|
244
|
-
const composingRef =
|
|
245
|
-
const pendingSelectionRef =
|
|
246
|
-
const onCompositionStateChangeRef =
|
|
347
|
+
const containerRef = useRef3(null);
|
|
348
|
+
const coreRef = useRef3(null);
|
|
349
|
+
const composingRef = useRef3(false);
|
|
350
|
+
const pendingSelectionRef = useRef3(null);
|
|
351
|
+
const onCompositionStateChangeRef = useRef3(options.onCompositionStateChange);
|
|
247
352
|
onCompositionStateChangeRef.current = options.onCompositionStateChange;
|
|
248
353
|
if (!coreRef.current) {
|
|
249
354
|
coreRef.current = createOwoMarkCore({ initialMarkdown });
|
|
250
355
|
}
|
|
251
356
|
const core = coreRef.current;
|
|
252
|
-
const [doc, setDoc] =
|
|
253
|
-
const [slashState, setSlashState] =
|
|
357
|
+
const [doc, setDoc] = useState3(() => core.getDocument());
|
|
358
|
+
const [slashState, setSlashState] = useState3(() => core.getSlashState());
|
|
254
359
|
useLayoutEffect2(() => {
|
|
255
360
|
const el = containerRef.current;
|
|
256
361
|
if (!el) return;
|
|
@@ -407,13 +512,25 @@ function useOwoMarkCore(options) {
|
|
|
407
512
|
}
|
|
408
513
|
|
|
409
514
|
// src/config.ts
|
|
515
|
+
var DEFAULT_TOOLBAR_COMPONENT_COMMANDS = [
|
|
516
|
+
"component-note",
|
|
517
|
+
"component-callout",
|
|
518
|
+
"component-code-demo",
|
|
519
|
+
"component-details",
|
|
520
|
+
"component-steps",
|
|
521
|
+
"component-tabs"
|
|
522
|
+
];
|
|
410
523
|
var DEFAULT_EDITOR_CONFIG = {
|
|
411
524
|
indentMode: "auto",
|
|
412
525
|
enableSideAnnotation: true,
|
|
413
526
|
enableMath: true,
|
|
527
|
+
enableComponents: true,
|
|
414
528
|
enableMarkdownSandbox: true,
|
|
415
529
|
markdownSandbox: {
|
|
416
530
|
outerFenceTicks: 4
|
|
531
|
+
},
|
|
532
|
+
toolbarConfig: {
|
|
533
|
+
componentCommands: DEFAULT_TOOLBAR_COMPONENT_COMMANDS
|
|
417
534
|
}
|
|
418
535
|
};
|
|
419
536
|
function resolveEditorConfig(config) {
|
|
@@ -423,6 +540,10 @@ function resolveEditorConfig(config) {
|
|
|
423
540
|
markdownSandbox: {
|
|
424
541
|
...DEFAULT_EDITOR_CONFIG.markdownSandbox,
|
|
425
542
|
...config?.markdownSandbox
|
|
543
|
+
},
|
|
544
|
+
toolbarConfig: {
|
|
545
|
+
...DEFAULT_EDITOR_CONFIG.toolbarConfig,
|
|
546
|
+
...config?.toolbarConfig
|
|
426
547
|
}
|
|
427
548
|
};
|
|
428
549
|
}
|
|
@@ -430,6 +551,7 @@ function deriveProcessorOptions(config) {
|
|
|
430
551
|
return {
|
|
431
552
|
enableSideAnnotation: config.enableSideAnnotation,
|
|
432
553
|
enableMath: config.enableMath,
|
|
554
|
+
enableComponents: config.enableComponents,
|
|
433
555
|
enableMarkdownSandbox: config.enableMarkdownSandbox,
|
|
434
556
|
markdownSandbox: config.markdownSandbox
|
|
435
557
|
};
|
|
@@ -437,7 +559,7 @@ function deriveProcessorOptions(config) {
|
|
|
437
559
|
|
|
438
560
|
// src/OwoMarkEditor.tsx
|
|
439
561
|
import "@owomark/view/style.css";
|
|
440
|
-
import { Fragment as Fragment2, jsx as
|
|
562
|
+
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
441
563
|
var OwoMarkEditor = forwardRef(
|
|
442
564
|
function OwoMarkEditor2(props, ref) {
|
|
443
565
|
const {
|
|
@@ -455,6 +577,7 @@ var OwoMarkEditor = forwardRef(
|
|
|
455
577
|
commandsRef,
|
|
456
578
|
coreRef: externalCoreRef,
|
|
457
579
|
controller,
|
|
580
|
+
scrollController,
|
|
458
581
|
indentMode: indentModeProp = "auto",
|
|
459
582
|
ariaLabel,
|
|
460
583
|
config: configProp
|
|
@@ -464,21 +587,24 @@ var OwoMarkEditor = forwardRef(
|
|
|
464
587
|
const theme = themeProp;
|
|
465
588
|
const enableSideAnnotation = resolved.enableSideAnnotation;
|
|
466
589
|
const enableMath = resolved.enableMath;
|
|
590
|
+
const enableComponents = resolved.enableComponents;
|
|
591
|
+
const toolbarConfig = resolved.toolbarConfig;
|
|
467
592
|
const isControlled = value !== void 0;
|
|
468
|
-
const lastExternalValue =
|
|
469
|
-
const suppressOnChange =
|
|
593
|
+
const lastExternalValue = useRef4(value ?? defaultValue ?? "");
|
|
594
|
+
const suppressOnChange = useRef4(false);
|
|
595
|
+
const boundSurfaceRef = useRef4(null);
|
|
470
596
|
const { core, doc, slashState, containerRef } = useOwoMarkCore({
|
|
471
597
|
initialMarkdown: lastExternalValue.current,
|
|
472
598
|
readOnly,
|
|
473
599
|
onCompositionStateChange
|
|
474
600
|
});
|
|
475
|
-
const handleSlashSelect =
|
|
601
|
+
const handleSlashSelect = useCallback2((index) => {
|
|
476
602
|
core.executeSlashCommand(index);
|
|
477
603
|
}, [core]);
|
|
478
|
-
const handleSlashDismiss =
|
|
604
|
+
const handleSlashDismiss = useCallback2(() => {
|
|
479
605
|
core.dismissSlashMenu();
|
|
480
606
|
}, [core]);
|
|
481
|
-
|
|
607
|
+
useEffect3(() => {
|
|
482
608
|
if (typeof externalCoreRef === "function") externalCoreRef(core);
|
|
483
609
|
else if (externalCoreRef && typeof externalCoreRef === "object") {
|
|
484
610
|
externalCoreRef.current = core;
|
|
@@ -490,7 +616,7 @@ var OwoMarkEditor = forwardRef(
|
|
|
490
616
|
}
|
|
491
617
|
};
|
|
492
618
|
}, [core, externalCoreRef]);
|
|
493
|
-
|
|
619
|
+
useEffect3(() => {
|
|
494
620
|
const unsub = core.onChange((markdown) => {
|
|
495
621
|
if (suppressOnChange.current) return;
|
|
496
622
|
lastExternalValue.current = markdown;
|
|
@@ -498,15 +624,45 @@ var OwoMarkEditor = forwardRef(
|
|
|
498
624
|
});
|
|
499
625
|
return unsub;
|
|
500
626
|
}, [core, onChange]);
|
|
501
|
-
|
|
627
|
+
useEffect3(() => {
|
|
502
628
|
if (!onSelectionChange) return;
|
|
503
629
|
return core.onSelectionChange(onSelectionChange);
|
|
504
630
|
}, [core, onSelectionChange]);
|
|
505
|
-
|
|
631
|
+
useEffect3(() => {
|
|
506
632
|
if (!controller) return;
|
|
507
633
|
return controller.connectEditor(core);
|
|
508
634
|
}, [core, controller]);
|
|
509
|
-
|
|
635
|
+
useEffect3(() => {
|
|
636
|
+
const root = containerRef.current;
|
|
637
|
+
if (!root || !scrollController) return;
|
|
638
|
+
const measure = () => {
|
|
639
|
+
scrollController.measureSurface("editor");
|
|
640
|
+
};
|
|
641
|
+
const resizeObserver = new ResizeObserver(measure);
|
|
642
|
+
resizeObserver.observe(root);
|
|
643
|
+
const mutationObserver = new MutationObserver(measure);
|
|
644
|
+
mutationObserver.observe(root, {
|
|
645
|
+
childList: true,
|
|
646
|
+
subtree: true,
|
|
647
|
+
characterData: true
|
|
648
|
+
});
|
|
649
|
+
const frame = requestAnimationFrame(measure);
|
|
650
|
+
let disposed = false;
|
|
651
|
+
if (typeof document !== "undefined" && document.fonts) {
|
|
652
|
+
document.fonts.ready.then(() => {
|
|
653
|
+
if (!disposed) {
|
|
654
|
+
measure();
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
return () => {
|
|
659
|
+
disposed = true;
|
|
660
|
+
cancelAnimationFrame(frame);
|
|
661
|
+
resizeObserver.disconnect();
|
|
662
|
+
mutationObserver.disconnect();
|
|
663
|
+
};
|
|
664
|
+
}, [doc, scrollController, containerRef]);
|
|
665
|
+
useEffect3(() => {
|
|
510
666
|
if (!isControlled) return;
|
|
511
667
|
if (value === lastExternalValue.current) return;
|
|
512
668
|
suppressOnChange.current = true;
|
|
@@ -514,21 +670,24 @@ var OwoMarkEditor = forwardRef(
|
|
|
514
670
|
lastExternalValue.current = value;
|
|
515
671
|
suppressOnChange.current = false;
|
|
516
672
|
}, [core, value, isControlled]);
|
|
517
|
-
|
|
673
|
+
useEffect3(() => {
|
|
518
674
|
core.setIndentMode(indentMode);
|
|
519
675
|
}, [core, indentMode]);
|
|
520
|
-
|
|
676
|
+
useEffect3(() => {
|
|
521
677
|
core.setEnableSideAnnotation(enableSideAnnotation);
|
|
522
678
|
}, [core, enableSideAnnotation]);
|
|
523
|
-
|
|
679
|
+
useEffect3(() => {
|
|
524
680
|
core.setEnableMath(enableMath);
|
|
525
681
|
}, [core, enableMath]);
|
|
526
|
-
|
|
682
|
+
useEffect3(() => {
|
|
683
|
+
core.setEnableComponents(enableComponents);
|
|
684
|
+
}, [core, enableComponents]);
|
|
685
|
+
useEffect3(() => {
|
|
527
686
|
if (containerRef.current && ariaLabel) {
|
|
528
687
|
containerRef.current.setAttribute("aria-label", ariaLabel);
|
|
529
688
|
}
|
|
530
689
|
}, [ariaLabel, containerRef]);
|
|
531
|
-
|
|
690
|
+
useEffect3(() => {
|
|
532
691
|
if (containerRef.current) {
|
|
533
692
|
containerRef.current.contentEditable = readOnly ? "false" : "true";
|
|
534
693
|
if (readOnly) {
|
|
@@ -558,13 +717,23 @@ var OwoMarkEditor = forwardRef(
|
|
|
558
717
|
themeClassName,
|
|
559
718
|
className
|
|
560
719
|
].filter(Boolean).join(" ");
|
|
561
|
-
const setRefs =
|
|
720
|
+
const setRefs = useCallback2((el) => {
|
|
721
|
+
if (scrollController && boundSurfaceRef.current !== el) {
|
|
722
|
+
if (boundSurfaceRef.current) {
|
|
723
|
+
scrollController.unbindSurface("editor", boundSurfaceRef.current);
|
|
724
|
+
}
|
|
725
|
+
if (el) {
|
|
726
|
+
scrollController.bindSurface("editor", el);
|
|
727
|
+
}
|
|
728
|
+
boundSurfaceRef.current = el;
|
|
729
|
+
}
|
|
562
730
|
containerRef.current = el;
|
|
563
731
|
if (typeof ref === "function") ref(el);
|
|
564
732
|
else if (ref) ref.current = el;
|
|
565
|
-
}, [ref, containerRef]);
|
|
566
|
-
return /* @__PURE__ */
|
|
567
|
-
/* @__PURE__ */
|
|
733
|
+
}, [ref, containerRef, scrollController]);
|
|
734
|
+
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
735
|
+
/* @__PURE__ */ jsx6(Toolbar, { core, toolbarConfig }),
|
|
736
|
+
/* @__PURE__ */ jsx6(
|
|
568
737
|
"div",
|
|
569
738
|
{
|
|
570
739
|
ref: setRefs,
|
|
@@ -572,10 +741,10 @@ var OwoMarkEditor = forwardRef(
|
|
|
572
741
|
"data-placeholder": placeholder,
|
|
573
742
|
onScroll,
|
|
574
743
|
suppressContentEditableWarning: true,
|
|
575
|
-
children: doc.blocks.map((block, i) => /* @__PURE__ */
|
|
744
|
+
children: doc.blocks.map((block, i) => /* @__PURE__ */ jsx6(EditorBlock, { block, blockIndex: i }, block.id))
|
|
576
745
|
}
|
|
577
746
|
),
|
|
578
|
-
/* @__PURE__ */
|
|
747
|
+
/* @__PURE__ */ jsx6(
|
|
579
748
|
SlashMenu,
|
|
580
749
|
{
|
|
581
750
|
state: slashState,
|
|
@@ -588,144 +757,1508 @@ var OwoMarkEditor = forwardRef(
|
|
|
588
757
|
);
|
|
589
758
|
|
|
590
759
|
// src/OwoMarkPreview.tsx
|
|
591
|
-
import {
|
|
760
|
+
import { forwardRef as forwardRef3, useEffect as useEffect8, useRef as useRef11 } from "react";
|
|
592
761
|
import {
|
|
593
762
|
createOwoMarkPreviewEngine
|
|
594
763
|
} from "@owomark/view";
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
764
|
+
|
|
765
|
+
// src/MdxPreview.tsx
|
|
766
|
+
import {
|
|
767
|
+
Component,
|
|
768
|
+
forwardRef as forwardRef2,
|
|
769
|
+
useEffect as useEffect7,
|
|
770
|
+
useMemo as useMemo6,
|
|
771
|
+
useRef as useRef10,
|
|
772
|
+
useState as useState7
|
|
773
|
+
} from "react";
|
|
774
|
+
import {
|
|
775
|
+
DEFAULT_MDX_COMPONENTS
|
|
776
|
+
} from "@owomark/processor";
|
|
777
|
+
|
|
778
|
+
// src/MdxSkeleton.tsx
|
|
779
|
+
import { useMemo as useMemo3, useRef as useRef5 } from "react";
|
|
780
|
+
import { createSkeletonHtml, ensureSkeletonStyles } from "@owomark/view";
|
|
781
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
782
|
+
function MdxSkeleton({ height, lines, className, style }) {
|
|
783
|
+
const injected = useRef5(false);
|
|
784
|
+
if (!injected.current && typeof document !== "undefined") {
|
|
785
|
+
ensureSkeletonStyles(document);
|
|
786
|
+
injected.current = true;
|
|
787
|
+
}
|
|
788
|
+
const html = useMemo3(
|
|
789
|
+
() => createSkeletonHtml({ height, lines: lines ?? (height == null ? 3 : void 0) }),
|
|
790
|
+
[height, lines]
|
|
791
|
+
);
|
|
792
|
+
return /* @__PURE__ */ jsx7(
|
|
793
|
+
"div",
|
|
794
|
+
{
|
|
795
|
+
className: className ?? void 0,
|
|
796
|
+
style,
|
|
797
|
+
dangerouslySetInnerHTML: { __html: html }
|
|
798
|
+
}
|
|
799
|
+
);
|
|
598
800
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
const
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
801
|
+
|
|
802
|
+
// src/MdxComponentShell.tsx
|
|
803
|
+
import {
|
|
804
|
+
useRef as useRef6,
|
|
805
|
+
useState as useState4,
|
|
806
|
+
useEffect as useEffect4,
|
|
807
|
+
useLayoutEffect as useLayoutEffect3,
|
|
808
|
+
useCallback as useCallback3
|
|
809
|
+
} from "react";
|
|
810
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
811
|
+
var componentHeightCache = /* @__PURE__ */ new Map();
|
|
812
|
+
var DEFAULT_ESTIMATED_HEIGHT = 80;
|
|
813
|
+
function shallowPropsFingerprint(props) {
|
|
814
|
+
const keys = Object.keys(props).sort();
|
|
815
|
+
const parts = [];
|
|
816
|
+
for (const key of keys) {
|
|
817
|
+
const val = props[key];
|
|
818
|
+
if (typeof val === "function" || typeof val === "object") continue;
|
|
819
|
+
parts.push(`${key}=${String(val)}`);
|
|
820
|
+
}
|
|
821
|
+
return parts.join("&");
|
|
822
|
+
}
|
|
823
|
+
function MdxComponentShell({ name, propsFingerprint, children }) {
|
|
824
|
+
const containerRef = useRef6(null);
|
|
825
|
+
const [status, setStatus] = useState4("skeleton");
|
|
826
|
+
const cacheKey = propsFingerprint ? `${name}:${propsFingerprint}` : name;
|
|
827
|
+
const estimatedHeight = componentHeightCache.get(cacheKey) ?? DEFAULT_ESTIMATED_HEIGHT;
|
|
828
|
+
useEffect4(() => {
|
|
829
|
+
const id = requestAnimationFrame(() => setStatus("mounting"));
|
|
830
|
+
return () => cancelAnimationFrame(id);
|
|
831
|
+
}, []);
|
|
832
|
+
useLayoutEffect3(() => {
|
|
833
|
+
if (status !== "mounting") return;
|
|
834
|
+
const id = requestAnimationFrame(() => setStatus("ready"));
|
|
835
|
+
return () => cancelAnimationFrame(id);
|
|
836
|
+
}, [status]);
|
|
837
|
+
const measureHeight = useCallback3(() => {
|
|
838
|
+
const el = containerRef.current;
|
|
839
|
+
if (!el) return;
|
|
840
|
+
const h = el.getBoundingClientRect().height;
|
|
841
|
+
if (h > 0) {
|
|
842
|
+
componentHeightCache.set(cacheKey, h);
|
|
843
|
+
}
|
|
844
|
+
}, [cacheKey]);
|
|
845
|
+
useEffect4(() => {
|
|
846
|
+
const el = containerRef.current;
|
|
847
|
+
if (!el || status !== "ready") return;
|
|
848
|
+
measureHeight();
|
|
849
|
+
const ro = new ResizeObserver(() => measureHeight());
|
|
850
|
+
ro.observe(el);
|
|
851
|
+
return () => ro.disconnect();
|
|
852
|
+
}, [status, measureHeight]);
|
|
853
|
+
if (status === "skeleton") {
|
|
854
|
+
return /* @__PURE__ */ jsx8(MdxSkeleton, { height: estimatedHeight });
|
|
855
|
+
}
|
|
856
|
+
return /* @__PURE__ */ jsx8("div", { ref: containerRef, children });
|
|
857
|
+
}
|
|
858
|
+
function wrapWithShell(name, Component2) {
|
|
859
|
+
function ShellWrapped(props) {
|
|
860
|
+
const fingerprint = shallowPropsFingerprint(props);
|
|
861
|
+
return /* @__PURE__ */ jsx8(MdxComponentShell, { name, propsFingerprint: fingerprint, children: /* @__PURE__ */ jsx8(Component2, { ...props }) });
|
|
862
|
+
}
|
|
863
|
+
ShellWrapped.displayName = `MdxShell(${name})`;
|
|
864
|
+
return ShellWrapped;
|
|
865
|
+
}
|
|
866
|
+
function clearComponentHeightCache() {
|
|
867
|
+
componentHeightCache.clear();
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// src/mdx-error.ts
|
|
871
|
+
function parseMdxErrorDetails(error) {
|
|
872
|
+
if (error && typeof error === "object") {
|
|
873
|
+
const value = error;
|
|
874
|
+
if (typeof value.line === "number") {
|
|
875
|
+
return {
|
|
876
|
+
message: value.message ?? String(error),
|
|
877
|
+
line: value.line,
|
|
878
|
+
column: typeof value.column === "number" ? value.column : void 0
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
const text = value.message ?? String(error);
|
|
882
|
+
const match = text.match(/\((\d+):(\d+)/);
|
|
883
|
+
if (match) {
|
|
884
|
+
return {
|
|
885
|
+
message: text,
|
|
886
|
+
line: Number(match[1]),
|
|
887
|
+
column: Number(match[2])
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
return { message: text };
|
|
891
|
+
}
|
|
892
|
+
return { message: String(error) };
|
|
893
|
+
}
|
|
894
|
+
function parseCompileError(error, kind) {
|
|
895
|
+
return {
|
|
896
|
+
...parseMdxErrorDetails(error),
|
|
897
|
+
kind
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// src/useMdxPreviewCompilation.ts
|
|
902
|
+
import { useEffect as useEffect6, useMemo as useMemo5, useRef as useRef8, useState as useState6 } from "react";
|
|
903
|
+
import { deriveRenderKey as deriveRenderKey2 } from "@owomark/core";
|
|
904
|
+
|
|
905
|
+
// src/mdx-runtime.ts
|
|
906
|
+
import { compile, run } from "@mdx-js/mdx";
|
|
907
|
+
import * as jsxRuntime from "react/jsx-runtime";
|
|
908
|
+
import {
|
|
909
|
+
getOwoMarkPlugins,
|
|
910
|
+
allBuiltinDescriptors,
|
|
911
|
+
inspectMdxSource,
|
|
912
|
+
remapMdxErrorDetails
|
|
913
|
+
} from "@owomark/processor";
|
|
914
|
+
var workerInstance = null;
|
|
915
|
+
var workerRefCount = 0;
|
|
916
|
+
var requestCounter = 0;
|
|
917
|
+
var WORKER_COMPILE_TIMEOUT_MS = 5e3;
|
|
918
|
+
var MAX_CRASHES = 3;
|
|
919
|
+
var CRASH_COOLDOWN_MS = 5e3;
|
|
920
|
+
var CRASH_WINDOW_MS = 3e4;
|
|
921
|
+
var permanentlyFailed = false;
|
|
922
|
+
var crashTimestamps = [];
|
|
923
|
+
var pendingRequests = /* @__PURE__ */ new Map();
|
|
924
|
+
function recordCrash() {
|
|
925
|
+
const now = Date.now();
|
|
926
|
+
crashTimestamps.push(now);
|
|
927
|
+
while (crashTimestamps.length > 0 && now - crashTimestamps[0] > CRASH_WINDOW_MS) {
|
|
928
|
+
crashTimestamps.shift();
|
|
929
|
+
}
|
|
930
|
+
if (crashTimestamps.length >= MAX_CRASHES) {
|
|
931
|
+
permanentlyFailed = true;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
function canCreateWorker() {
|
|
935
|
+
if (permanentlyFailed) return false;
|
|
936
|
+
if (crashTimestamps.length === 0) return true;
|
|
937
|
+
return Date.now() - crashTimestamps[crashTimestamps.length - 1] >= CRASH_COOLDOWN_MS;
|
|
938
|
+
}
|
|
939
|
+
function tryCreateWorker() {
|
|
940
|
+
if (workerInstance) return workerInstance;
|
|
941
|
+
if (!canCreateWorker()) return null;
|
|
942
|
+
try {
|
|
943
|
+
if (typeof Worker === "undefined") {
|
|
944
|
+
permanentlyFailed = true;
|
|
945
|
+
return null;
|
|
946
|
+
}
|
|
947
|
+
const worker = new Worker(
|
|
948
|
+
new URL("./mdx.worker.js", import.meta.url),
|
|
949
|
+
{ type: "module" }
|
|
950
|
+
);
|
|
951
|
+
worker.onmessage = (e) => {
|
|
952
|
+
const response = e.data;
|
|
953
|
+
const pending = pendingRequests.get(response.id);
|
|
954
|
+
if (!pending) return;
|
|
955
|
+
pendingRequests.delete(response.id);
|
|
956
|
+
pending.cleanup?.();
|
|
957
|
+
if (response.ok) {
|
|
958
|
+
pending.resolve(response.code);
|
|
959
|
+
} else {
|
|
960
|
+
pending.reject({ ...response.error, kind: "compile" });
|
|
961
|
+
}
|
|
627
962
|
};
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
963
|
+
worker.onerror = () => {
|
|
964
|
+
recordCrash();
|
|
965
|
+
worker.terminate();
|
|
966
|
+
workerInstance = null;
|
|
967
|
+
for (const [id, pending] of pendingRequests) {
|
|
968
|
+
pendingRequests.delete(id);
|
|
969
|
+
pending.cleanup?.();
|
|
970
|
+
pending.reject({ message: "MDX compile worker crashed", kind: "compile" });
|
|
971
|
+
}
|
|
636
972
|
};
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
973
|
+
workerInstance = worker;
|
|
974
|
+
return worker;
|
|
975
|
+
} catch {
|
|
976
|
+
recordCrash();
|
|
977
|
+
return null;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
function acquireMdxWorker() {
|
|
981
|
+
workerRefCount++;
|
|
982
|
+
}
|
|
983
|
+
function releaseMdxWorker() {
|
|
984
|
+
if (--workerRefCount <= 0) {
|
|
985
|
+
workerRefCount = 0;
|
|
986
|
+
if (workerInstance) {
|
|
987
|
+
workerInstance.terminate();
|
|
988
|
+
workerInstance = null;
|
|
989
|
+
}
|
|
990
|
+
for (const [, pending] of pendingRequests) {
|
|
991
|
+
pending.reject({ message: "Worker released", kind: "compile" });
|
|
992
|
+
}
|
|
993
|
+
pendingRequests.clear();
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
function compileInWorker(markdown, options, inspection) {
|
|
997
|
+
const maybeWorker = tryCreateWorker();
|
|
998
|
+
if (!maybeWorker) {
|
|
999
|
+
return {
|
|
1000
|
+
promise: Promise.reject(new Error("Worker unavailable")),
|
|
1001
|
+
cancel: () => {
|
|
1002
|
+
}
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
const workerRef = maybeWorker;
|
|
1006
|
+
const id = ++requestCounter;
|
|
1007
|
+
let cancelled = false;
|
|
1008
|
+
let timeoutId = null;
|
|
1009
|
+
const cleanup = () => {
|
|
1010
|
+
if (timeoutId !== null) {
|
|
1011
|
+
clearTimeout(timeoutId);
|
|
1012
|
+
timeoutId = null;
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
const promise = new Promise((resolve, reject) => {
|
|
1016
|
+
pendingRequests.set(id, { resolve, reject, cleanup });
|
|
1017
|
+
workerRef.postMessage({
|
|
1018
|
+
type: "compile",
|
|
1019
|
+
id,
|
|
1020
|
+
markdown,
|
|
1021
|
+
options: {
|
|
1022
|
+
enableMath: options.enableMath,
|
|
1023
|
+
enableSideAnnotation: options.enableSideAnnotation,
|
|
1024
|
+
enableCodeHighlight: options.enableCodeHighlight,
|
|
1025
|
+
sourceAnchors: options.sourceAnchors,
|
|
1026
|
+
extraRemarkDescriptors: options.extraRemarkDescriptors,
|
|
1027
|
+
extraRehypeDescriptors: options.extraRehypeDescriptors,
|
|
1028
|
+
preparedSource: inspection?.compileSource,
|
|
1029
|
+
sourceMap: inspection?.sourceMap
|
|
643
1030
|
}
|
|
644
1031
|
});
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
1032
|
+
timeoutId = setTimeout(() => {
|
|
1033
|
+
const pending = pendingRequests.get(id);
|
|
1034
|
+
if (!pending) return;
|
|
1035
|
+
pendingRequests.delete(id);
|
|
1036
|
+
cleanup();
|
|
1037
|
+
try {
|
|
1038
|
+
workerRef.postMessage({ type: "cancel", id });
|
|
1039
|
+
} catch {
|
|
1040
|
+
}
|
|
1041
|
+
pending.reject({ message: "MDX compile worker timed out", kind: "compile" });
|
|
1042
|
+
}, WORKER_COMPILE_TIMEOUT_MS);
|
|
1043
|
+
});
|
|
1044
|
+
function cancel() {
|
|
1045
|
+
if (cancelled) return;
|
|
1046
|
+
cancelled = true;
|
|
1047
|
+
const pending = pendingRequests.get(id);
|
|
1048
|
+
if (pending) {
|
|
1049
|
+
pendingRequests.delete(id);
|
|
1050
|
+
pending.cleanup?.();
|
|
1051
|
+
pending.reject({ message: "Compilation cancelled", kind: "compile" });
|
|
1052
|
+
}
|
|
1053
|
+
workerRef.postMessage({ type: "cancel", id });
|
|
1054
|
+
}
|
|
1055
|
+
return { promise, cancel };
|
|
1056
|
+
}
|
|
1057
|
+
async function compileOnMainThread(markdown, options, inspection) {
|
|
1058
|
+
const resolvedInspection = inspection ?? inspectMdxSource(markdown, {
|
|
1059
|
+
enableMath: options.enableMath,
|
|
1060
|
+
enableSideAnnotation: options.enableSideAnnotation,
|
|
1061
|
+
extraRemarkDescriptors: options.extraRemarkDescriptors,
|
|
1062
|
+
extraRemarkPlugins: options.extraRemarkPlugins
|
|
1063
|
+
});
|
|
1064
|
+
const { remarkPlugins, rehypePlugins } = getOwoMarkPlugins({
|
|
1065
|
+
mode: "production",
|
|
1066
|
+
enableMath: options.enableMath,
|
|
1067
|
+
enableSideAnnotation: options.enableSideAnnotation,
|
|
1068
|
+
enableCodeHighlight: options.enableCodeHighlight,
|
|
1069
|
+
sourceAnchors: options.sourceAnchors,
|
|
1070
|
+
extraRemarkDescriptors: options.extraRemarkDescriptors,
|
|
1071
|
+
extraRehypeDescriptors: options.extraRehypeDescriptors,
|
|
1072
|
+
extraRemarkPlugins: options.extraRemarkPlugins,
|
|
1073
|
+
extraRehypePlugins: options.extraRehypePlugins,
|
|
1074
|
+
mdxSourceMap: resolvedInspection.sourceMap
|
|
1075
|
+
});
|
|
1076
|
+
try {
|
|
1077
|
+
const result = await compile(resolvedInspection.compileSource, {
|
|
1078
|
+
outputFormat: "function-body",
|
|
1079
|
+
remarkPlugins,
|
|
1080
|
+
rehypePlugins
|
|
1081
|
+
});
|
|
1082
|
+
return String(result);
|
|
1083
|
+
} catch (error) {
|
|
1084
|
+
throw remapMdxErrorDetails(error, resolvedInspection.sourceMap);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
function requiresMainThread(options) {
|
|
1088
|
+
if (options.preferWorker !== true) return true;
|
|
1089
|
+
if (options.extraRemarkPlugins?.length || options.extraRehypePlugins?.length) return true;
|
|
1090
|
+
if (options.extraRemarkDescriptors?.length && !allBuiltinDescriptors(options.extraRemarkDescriptors)) return true;
|
|
1091
|
+
if (options.extraRehypeDescriptors?.length && !allBuiltinDescriptors(options.extraRehypeDescriptors)) return true;
|
|
1092
|
+
return false;
|
|
1093
|
+
}
|
|
1094
|
+
function compileMdx(markdown, options, inspection) {
|
|
1095
|
+
if (requiresMainThread(options)) {
|
|
1096
|
+
return {
|
|
1097
|
+
promise: compileOnMainThread(markdown, options, inspection),
|
|
1098
|
+
cancel: () => {
|
|
1099
|
+
}
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
const workerCompile = compileInWorker(markdown, options, inspection);
|
|
1103
|
+
let settled = false;
|
|
1104
|
+
const promise = workerCompile.promise.catch(() => {
|
|
1105
|
+
if (settled) throw new Error("Compilation cancelled");
|
|
1106
|
+
return compileOnMainThread(markdown, options, inspection);
|
|
1107
|
+
});
|
|
1108
|
+
return {
|
|
1109
|
+
promise,
|
|
1110
|
+
cancel: () => {
|
|
1111
|
+
settled = true;
|
|
1112
|
+
workerCompile.cancel();
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
async function runMdxCode(code) {
|
|
1117
|
+
const module = await run(code, {
|
|
1118
|
+
...jsxRuntime,
|
|
1119
|
+
baseUrl: import.meta.url
|
|
1120
|
+
});
|
|
1121
|
+
return {
|
|
1122
|
+
Content: module.default
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// src/mdx-preview-utils.tsx
|
|
1127
|
+
import { renderBlockDefault } from "@owomark/view";
|
|
1128
|
+
|
|
1129
|
+
// src/AsyncCodeBlock.tsx
|
|
1130
|
+
import {
|
|
1131
|
+
Children,
|
|
1132
|
+
isValidElement,
|
|
1133
|
+
useEffect as useEffect5,
|
|
1134
|
+
useMemo as useMemo4,
|
|
1135
|
+
useRef as useRef7,
|
|
1136
|
+
useState as useState5
|
|
1137
|
+
} from "react";
|
|
1138
|
+
|
|
1139
|
+
// src/highlight-cache-key.ts
|
|
1140
|
+
import { deriveRenderKey } from "@owomark/core";
|
|
1141
|
+
|
|
1142
|
+
// src/highlight-types.ts
|
|
1143
|
+
var HIGHLIGHT_WORKER_TIMEOUT_MS = 3500;
|
|
1144
|
+
var HIGHLIGHT_COMPONENT_DEBOUNCE_MS = 120;
|
|
1145
|
+
var HIGHLIGHT_CACHE_MAX_ENTRIES = 200;
|
|
1146
|
+
var DEFAULT_HIGHLIGHT_THEME = "vitesse-light";
|
|
1147
|
+
var HIGHLIGHTER_VERSION = "owomark-shiki-js-v1";
|
|
1148
|
+
|
|
1149
|
+
// src/highlight-cache-key.ts
|
|
1150
|
+
function buildHighlightCacheKey(input) {
|
|
1151
|
+
return deriveRenderKey(
|
|
1152
|
+
[
|
|
1153
|
+
input.code,
|
|
1154
|
+
input.language ?? "",
|
|
1155
|
+
input.themeKey,
|
|
1156
|
+
input.meta ?? "",
|
|
1157
|
+
HIGHLIGHTER_VERSION
|
|
1158
|
+
].join("\u241F"),
|
|
1159
|
+
"code-fence",
|
|
1160
|
+
""
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// src/highlight-worker-manager.ts
|
|
1165
|
+
var HighlightWorkerManager = class {
|
|
1166
|
+
timeoutMs;
|
|
1167
|
+
cacheMaxEntries;
|
|
1168
|
+
workerFactory;
|
|
1169
|
+
worker = null;
|
|
1170
|
+
workerFailureCount = 0;
|
|
1171
|
+
requestCounter = 0;
|
|
1172
|
+
cache = /* @__PURE__ */ new Map();
|
|
1173
|
+
inflightByCacheKey = /* @__PURE__ */ new Map();
|
|
1174
|
+
inflightByTaskId = /* @__PURE__ */ new Map();
|
|
1175
|
+
constructor(options = {}) {
|
|
1176
|
+
this.timeoutMs = options.timeoutMs ?? HIGHLIGHT_WORKER_TIMEOUT_MS;
|
|
1177
|
+
this.cacheMaxEntries = options.cacheMaxEntries ?? HIGHLIGHT_CACHE_MAX_ENTRIES;
|
|
1178
|
+
this.workerFactory = options.workerFactory;
|
|
1179
|
+
}
|
|
1180
|
+
highlight(input) {
|
|
1181
|
+
const request = {
|
|
1182
|
+
...input,
|
|
1183
|
+
requestId: `highlight-${++this.requestCounter}`,
|
|
1184
|
+
themeKey: input.themeKey?.trim() || DEFAULT_HIGHLIGHT_THEME
|
|
1185
|
+
};
|
|
1186
|
+
const cached = this.getCached(request.cacheKey);
|
|
1187
|
+
if (cached) {
|
|
1188
|
+
return {
|
|
1189
|
+
request,
|
|
1190
|
+
promise: Promise.resolve({
|
|
1191
|
+
...request,
|
|
1192
|
+
ok: true,
|
|
1193
|
+
html: cached
|
|
1194
|
+
}),
|
|
1195
|
+
cancel: () => {
|
|
665
1196
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
)
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
let task = this.inflightByCacheKey.get(request.cacheKey);
|
|
1200
|
+
if (!task) {
|
|
1201
|
+
task = this.startTask(request);
|
|
1202
|
+
}
|
|
1203
|
+
const promise = new Promise((resolve, reject) => {
|
|
1204
|
+
task.consumers.set(request.requestId, {
|
|
1205
|
+
request,
|
|
1206
|
+
resolve,
|
|
1207
|
+
reject
|
|
1208
|
+
});
|
|
1209
|
+
});
|
|
1210
|
+
return {
|
|
1211
|
+
request,
|
|
1212
|
+
promise,
|
|
1213
|
+
cancel: () => this.cancelRequest(request.requestId)
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
resetForTesting() {
|
|
1217
|
+
this.worker?.terminate();
|
|
1218
|
+
this.worker = null;
|
|
1219
|
+
this.workerFailureCount = 0;
|
|
1220
|
+
this.requestCounter = 0;
|
|
1221
|
+
this.cache.clear();
|
|
1222
|
+
this.inflightByCacheKey.clear();
|
|
1223
|
+
this.inflightByTaskId.clear();
|
|
1224
|
+
}
|
|
1225
|
+
ensureWorker() {
|
|
1226
|
+
if (this.worker) {
|
|
1227
|
+
return this.worker;
|
|
1228
|
+
}
|
|
1229
|
+
if (!this.workerFactory && typeof Worker === "undefined") {
|
|
1230
|
+
return null;
|
|
1231
|
+
}
|
|
1232
|
+
try {
|
|
1233
|
+
const worker = this.workerFactory ? this.workerFactory() : new Worker(
|
|
1234
|
+
new URL("./highlight.worker.js", import.meta.url),
|
|
1235
|
+
{ type: "module" }
|
|
1236
|
+
);
|
|
1237
|
+
if (!worker) {
|
|
1238
|
+
return null;
|
|
1239
|
+
}
|
|
1240
|
+
worker.onmessage = (event) => {
|
|
1241
|
+
this.handleWorkerMessage(event.data);
|
|
1242
|
+
};
|
|
1243
|
+
worker.onerror = () => {
|
|
1244
|
+
this.workerFailureCount += 1;
|
|
1245
|
+
this.worker?.terminate();
|
|
1246
|
+
this.worker = null;
|
|
1247
|
+
const tasks = [...this.inflightByTaskId.values()];
|
|
1248
|
+
for (const task of tasks) {
|
|
1249
|
+
this.finishTaskWithError(task, "Code highlight worker crashed");
|
|
680
1250
|
}
|
|
1251
|
+
};
|
|
1252
|
+
this.worker = worker;
|
|
1253
|
+
return worker;
|
|
1254
|
+
} catch {
|
|
1255
|
+
this.workerFailureCount += 1;
|
|
1256
|
+
return null;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
startTask(request) {
|
|
1260
|
+
const task = {
|
|
1261
|
+
taskId: request.requestId,
|
|
1262
|
+
cacheKey: request.cacheKey,
|
|
1263
|
+
request,
|
|
1264
|
+
consumers: /* @__PURE__ */ new Map(),
|
|
1265
|
+
timeoutId: null
|
|
1266
|
+
};
|
|
1267
|
+
this.inflightByCacheKey.set(task.cacheKey, task);
|
|
1268
|
+
this.inflightByTaskId.set(task.taskId, task);
|
|
1269
|
+
const worker = this.ensureWorker();
|
|
1270
|
+
if (!worker) {
|
|
1271
|
+
queueMicrotask(() => {
|
|
1272
|
+
this.finishTaskWithError(task, "Code highlight worker unavailable");
|
|
1273
|
+
});
|
|
1274
|
+
return task;
|
|
1275
|
+
}
|
|
1276
|
+
task.timeoutId = setTimeout(() => {
|
|
1277
|
+
this.cancelTask(task, "Code highlight timed out");
|
|
1278
|
+
}, this.timeoutMs);
|
|
1279
|
+
worker.postMessage({
|
|
1280
|
+
type: "highlight",
|
|
1281
|
+
...request
|
|
1282
|
+
});
|
|
1283
|
+
return task;
|
|
1284
|
+
}
|
|
1285
|
+
handleWorkerMessage(message) {
|
|
1286
|
+
const task = this.inflightByTaskId.get(message.taskId);
|
|
1287
|
+
if (!task) {
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
this.clearTaskTimeout(task);
|
|
1291
|
+
this.inflightByTaskId.delete(task.taskId);
|
|
1292
|
+
this.inflightByCacheKey.delete(task.cacheKey);
|
|
1293
|
+
if (message.ok) {
|
|
1294
|
+
this.setCached(task.cacheKey, message.html);
|
|
1295
|
+
}
|
|
1296
|
+
for (const consumer of task.consumers.values()) {
|
|
1297
|
+
consumer.resolve(message.ok ? {
|
|
1298
|
+
...consumer.request,
|
|
1299
|
+
ok: true,
|
|
1300
|
+
html: message.html
|
|
1301
|
+
} : {
|
|
1302
|
+
...consumer.request,
|
|
1303
|
+
ok: false,
|
|
1304
|
+
error: message.error
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
cancelRequest(requestId) {
|
|
1309
|
+
for (const task of this.inflightByTaskId.values()) {
|
|
1310
|
+
if (!task.consumers.has(requestId)) {
|
|
1311
|
+
continue;
|
|
681
1312
|
}
|
|
1313
|
+
const consumer = task.consumers.get(requestId);
|
|
1314
|
+
task.consumers.delete(requestId);
|
|
1315
|
+
consumer.reject(new Error("Highlight request cancelled"));
|
|
1316
|
+
if (task.consumers.size === 0) {
|
|
1317
|
+
this.cancelTask(task, "Highlight request cancelled");
|
|
1318
|
+
}
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
cancelTask(task, error) {
|
|
1323
|
+
this.worker?.postMessage({
|
|
1324
|
+
type: "cancel",
|
|
1325
|
+
taskId: task.taskId
|
|
1326
|
+
});
|
|
1327
|
+
this.finishTaskWithError(task, error);
|
|
1328
|
+
}
|
|
1329
|
+
finishTaskWithError(task, error) {
|
|
1330
|
+
this.clearTaskTimeout(task);
|
|
1331
|
+
this.inflightByTaskId.delete(task.taskId);
|
|
1332
|
+
this.inflightByCacheKey.delete(task.cacheKey);
|
|
1333
|
+
for (const consumer of task.consumers.values()) {
|
|
1334
|
+
consumer.resolve({
|
|
1335
|
+
...consumer.request,
|
|
1336
|
+
ok: false,
|
|
1337
|
+
error
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
task.consumers.clear();
|
|
1341
|
+
}
|
|
1342
|
+
clearTaskTimeout(task) {
|
|
1343
|
+
if (task.timeoutId !== null) {
|
|
1344
|
+
clearTimeout(task.timeoutId);
|
|
1345
|
+
task.timeoutId = null;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
getCached(cacheKey) {
|
|
1349
|
+
const cached = this.cache.get(cacheKey);
|
|
1350
|
+
if (!cached) {
|
|
1351
|
+
return null;
|
|
1352
|
+
}
|
|
1353
|
+
cached.touchedAt = Date.now();
|
|
1354
|
+
this.cache.delete(cacheKey);
|
|
1355
|
+
this.cache.set(cacheKey, cached);
|
|
1356
|
+
return cached.html;
|
|
1357
|
+
}
|
|
1358
|
+
setCached(cacheKey, html) {
|
|
1359
|
+
this.cache.delete(cacheKey);
|
|
1360
|
+
this.cache.set(cacheKey, {
|
|
1361
|
+
html,
|
|
1362
|
+
touchedAt: Date.now()
|
|
682
1363
|
});
|
|
683
|
-
|
|
1364
|
+
while (this.cache.size > this.cacheMaxEntries) {
|
|
1365
|
+
const oldestKey = this.cache.keys().next().value;
|
|
1366
|
+
if (!oldestKey) {
|
|
1367
|
+
break;
|
|
1368
|
+
}
|
|
1369
|
+
this.cache.delete(oldestKey);
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
};
|
|
1373
|
+
var singletonManager = null;
|
|
1374
|
+
function getHighlightWorkerManager() {
|
|
1375
|
+
if (!singletonManager) {
|
|
1376
|
+
singletonManager = new HighlightWorkerManager();
|
|
1377
|
+
}
|
|
1378
|
+
return singletonManager;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
// src/AsyncCodeBlock.tsx
|
|
1382
|
+
import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1383
|
+
var nextBlockInstanceId = 1;
|
|
1384
|
+
function pickProps(source, omitKeys) {
|
|
1385
|
+
if (!source) {
|
|
1386
|
+
return {};
|
|
1387
|
+
}
|
|
1388
|
+
const next = {};
|
|
1389
|
+
for (const [key, value] of Object.entries(source)) {
|
|
1390
|
+
if (omitKeys.includes(key)) {
|
|
1391
|
+
continue;
|
|
1392
|
+
}
|
|
1393
|
+
next[key] = value;
|
|
1394
|
+
}
|
|
1395
|
+
return next;
|
|
1396
|
+
}
|
|
1397
|
+
function renderPlainCode(props, status) {
|
|
1398
|
+
const preProps = pickProps(props.preProps, ["children"]);
|
|
1399
|
+
const codeProps = pickProps(props.codeProps, ["children", "className"]);
|
|
1400
|
+
return /* @__PURE__ */ jsx9(
|
|
1401
|
+
"figure",
|
|
1402
|
+
{
|
|
1403
|
+
"data-rehype-pretty-code-figure": "",
|
|
1404
|
+
"data-owo-code-block-state": status,
|
|
1405
|
+
className: "owo-async-code-block",
|
|
1406
|
+
children: /* @__PURE__ */ jsx9(
|
|
1407
|
+
"pre",
|
|
1408
|
+
{
|
|
1409
|
+
...preProps,
|
|
1410
|
+
className: ["shiki", props.themeKey, props.className].filter(Boolean).join(" "),
|
|
1411
|
+
"data-language": props.language ?? "text",
|
|
1412
|
+
"data-theme": props.themeKey,
|
|
1413
|
+
children: /* @__PURE__ */ jsx9(
|
|
1414
|
+
"code",
|
|
1415
|
+
{
|
|
1416
|
+
...codeProps,
|
|
1417
|
+
"data-language": props.language ?? "text",
|
|
1418
|
+
"data-theme": props.themeKey,
|
|
1419
|
+
style: { display: "grid" },
|
|
1420
|
+
children: props.code.split("\n").map((line, index, allLines) => /* @__PURE__ */ jsxs4("span", { "data-line": "", children: [
|
|
1421
|
+
line,
|
|
1422
|
+
index < allLines.length - 1 ? "\n" : ""
|
|
1423
|
+
] }, `${index}:${line}`))
|
|
1424
|
+
}
|
|
1425
|
+
)
|
|
1426
|
+
}
|
|
1427
|
+
)
|
|
1428
|
+
}
|
|
1429
|
+
);
|
|
1430
|
+
}
|
|
1431
|
+
function AsyncCodeBlock(props) {
|
|
1432
|
+
const {
|
|
1433
|
+
code,
|
|
1434
|
+
language,
|
|
1435
|
+
meta,
|
|
1436
|
+
themeKey,
|
|
1437
|
+
documentVersion,
|
|
1438
|
+
manager: managerProp
|
|
1439
|
+
} = props;
|
|
1440
|
+
const manager = managerProp ?? getHighlightWorkerManager();
|
|
1441
|
+
const blockInstanceIdRef = useRef7(`async-code-block-${nextBlockInstanceId++}`);
|
|
1442
|
+
const activeRequestRef = useRef7(null);
|
|
1443
|
+
const [state, setState] = useState5({ status: "plain", html: null });
|
|
1444
|
+
const cacheKey = useMemo4(() => buildHighlightCacheKey({
|
|
1445
|
+
code,
|
|
1446
|
+
language,
|
|
1447
|
+
themeKey,
|
|
1448
|
+
meta
|
|
1449
|
+
}), [code, language, meta, themeKey]);
|
|
1450
|
+
useEffect5(() => {
|
|
1451
|
+
setState((current) => current.status === "highlighted" && current.html ? current : { status: "loading", html: null });
|
|
1452
|
+
let activeCancel = null;
|
|
1453
|
+
const timer = setTimeout(() => {
|
|
1454
|
+
const task = manager.highlight({
|
|
1455
|
+
documentVersion,
|
|
1456
|
+
blockInstanceId: blockInstanceIdRef.current,
|
|
1457
|
+
cacheKey,
|
|
1458
|
+
code,
|
|
1459
|
+
language,
|
|
1460
|
+
themeKey,
|
|
1461
|
+
meta
|
|
1462
|
+
});
|
|
1463
|
+
activeRequestRef.current = task.request.requestId;
|
|
1464
|
+
activeCancel = task.cancel;
|
|
1465
|
+
void task.promise.then((response) => {
|
|
1466
|
+
if (activeRequestRef.current !== response.requestId) {
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
if (response.documentVersion !== documentVersion) {
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1472
|
+
if (response.cacheKey !== cacheKey) {
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
if (response.ok) {
|
|
1476
|
+
setState({ status: "highlighted", html: response.html });
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
setState({ status: "plain", html: null });
|
|
1480
|
+
}).catch(() => {
|
|
1481
|
+
if (activeRequestRef.current === task.request.requestId) {
|
|
1482
|
+
setState({ status: "plain", html: null });
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1485
|
+
}, HIGHLIGHT_COMPONENT_DEBOUNCE_MS);
|
|
684
1486
|
return () => {
|
|
685
|
-
|
|
686
|
-
|
|
1487
|
+
clearTimeout(timer);
|
|
1488
|
+
activeCancel?.();
|
|
1489
|
+
activeRequestRef.current = null;
|
|
687
1490
|
};
|
|
688
|
-
}, [
|
|
689
|
-
|
|
690
|
-
|
|
1491
|
+
}, [cacheKey, code, documentVersion, language, manager, meta, themeKey]);
|
|
1492
|
+
if (state.status === "highlighted") {
|
|
1493
|
+
return /* @__PURE__ */ jsx9(
|
|
1494
|
+
"figure",
|
|
1495
|
+
{
|
|
1496
|
+
"data-rehype-pretty-code-figure": "",
|
|
1497
|
+
"data-owo-code-block-state": "highlighted",
|
|
1498
|
+
className: "owo-async-code-block",
|
|
1499
|
+
dangerouslySetInnerHTML: { __html: state.html }
|
|
1500
|
+
}
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
return renderPlainCode(props, state.status);
|
|
1504
|
+
}
|
|
1505
|
+
function readCodeText(children) {
|
|
1506
|
+
if (typeof children === "string") {
|
|
1507
|
+
return children;
|
|
1508
|
+
}
|
|
1509
|
+
if (Array.isArray(children)) {
|
|
1510
|
+
return children.map(readCodeText).join("");
|
|
1511
|
+
}
|
|
1512
|
+
if (isValidElement(children)) {
|
|
1513
|
+
return readCodeText(children.props.children);
|
|
1514
|
+
}
|
|
1515
|
+
return "";
|
|
1516
|
+
}
|
|
1517
|
+
function getLanguageFromClassName(className) {
|
|
1518
|
+
if (!className) {
|
|
1519
|
+
return null;
|
|
1520
|
+
}
|
|
1521
|
+
const match = className.match(/language-([A-Za-z0-9_-]+)/);
|
|
1522
|
+
return match?.[1] ?? null;
|
|
1523
|
+
}
|
|
1524
|
+
function MdxAsyncPre(props) {
|
|
1525
|
+
const {
|
|
1526
|
+
themeKey,
|
|
1527
|
+
documentVersion,
|
|
1528
|
+
children,
|
|
1529
|
+
...nativePreProps
|
|
1530
|
+
} = props;
|
|
1531
|
+
if (Children.count(children) !== 1) {
|
|
1532
|
+
return /* @__PURE__ */ jsx9("pre", { ...nativePreProps, children });
|
|
1533
|
+
}
|
|
1534
|
+
const child = Children.only(children);
|
|
1535
|
+
if (!isValidElement(child)) {
|
|
1536
|
+
return /* @__PURE__ */ jsx9("pre", { ...nativePreProps, children });
|
|
1537
|
+
}
|
|
1538
|
+
const language = getLanguageFromClassName(child.props.className);
|
|
1539
|
+
const meta = typeof child.props["data-meta"] === "string" ? child.props["data-meta"] : null;
|
|
1540
|
+
const code = readCodeText(child.props.children);
|
|
1541
|
+
return /* @__PURE__ */ jsx9(
|
|
1542
|
+
AsyncCodeBlock,
|
|
691
1543
|
{
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
1544
|
+
code,
|
|
1545
|
+
language,
|
|
1546
|
+
meta,
|
|
1547
|
+
themeKey,
|
|
1548
|
+
documentVersion,
|
|
1549
|
+
className: child.props.className,
|
|
1550
|
+
codeProps: child.props,
|
|
1551
|
+
preProps: nativePreProps
|
|
697
1552
|
}
|
|
698
1553
|
);
|
|
699
|
-
}
|
|
1554
|
+
}
|
|
1555
|
+
function MdxInlineCode(props) {
|
|
1556
|
+
return /* @__PURE__ */ jsx9("code", { ...props });
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
// src/mdx-preview-utils.tsx
|
|
1560
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
1561
|
+
var objectIdentityMap = /* @__PURE__ */ new WeakMap();
|
|
1562
|
+
var nextObjectIdentity = 1;
|
|
1563
|
+
function getObjectIdentity(value) {
|
|
1564
|
+
if (!value) return "0";
|
|
1565
|
+
const existing = objectIdentityMap.get(value);
|
|
1566
|
+
if (existing) return String(existing);
|
|
1567
|
+
const id = nextObjectIdentity++;
|
|
1568
|
+
objectIdentityMap.set(value, id);
|
|
1569
|
+
return String(id);
|
|
1570
|
+
}
|
|
1571
|
+
function buildRuntimeComponents(mdxAnalysis, baseComponents, runtimeContext) {
|
|
1572
|
+
const components = {
|
|
1573
|
+
...baseComponents,
|
|
1574
|
+
pre: function RuntimeAsyncPre(props) {
|
|
1575
|
+
return /* @__PURE__ */ jsx10(
|
|
1576
|
+
MdxAsyncPre,
|
|
1577
|
+
{
|
|
1578
|
+
...props,
|
|
1579
|
+
themeKey: runtimeContext.themeKey,
|
|
1580
|
+
documentVersion: runtimeContext.documentVersion
|
|
1581
|
+
}
|
|
1582
|
+
);
|
|
1583
|
+
},
|
|
1584
|
+
code: MdxInlineCode
|
|
1585
|
+
};
|
|
1586
|
+
function registerMissingComponentPath(componentName) {
|
|
1587
|
+
const parts = componentName.split(".");
|
|
1588
|
+
let cursor = components;
|
|
1589
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
1590
|
+
const part = parts[index];
|
|
1591
|
+
const isLeaf = index === parts.length - 1;
|
|
1592
|
+
const existing = cursor[part];
|
|
1593
|
+
if (isLeaf) {
|
|
1594
|
+
if (existing === void 0) {
|
|
1595
|
+
cursor[part] = function MissingComponent(props) {
|
|
1596
|
+
const { children, ...restProps } = props;
|
|
1597
|
+
return /* @__PURE__ */ jsx10("div", { "data-mdx-missing": componentName, ...restProps, children });
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
if (existing == null) {
|
|
1603
|
+
const nested = {};
|
|
1604
|
+
cursor[part] = nested;
|
|
1605
|
+
cursor = nested;
|
|
1606
|
+
continue;
|
|
1607
|
+
}
|
|
1608
|
+
if (typeof existing === "function") {
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
cursor = existing;
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
for (const name of mdxAnalysis.componentNames) {
|
|
1615
|
+
registerMissingComponentPath(name);
|
|
1616
|
+
}
|
|
1617
|
+
return components;
|
|
1618
|
+
}
|
|
1619
|
+
async function renderMarkdownBlocksCached(blocks, themeKey, cache, renderBlock) {
|
|
1620
|
+
const nextCache = /* @__PURE__ */ new Map();
|
|
1621
|
+
const results = await Promise.all(blocks.map(async (block) => {
|
|
1622
|
+
const cached = cache.get(block.renderKey);
|
|
1623
|
+
if (cached !== void 0) {
|
|
1624
|
+
nextCache.set(block.renderKey, cached);
|
|
1625
|
+
return { blockId: block.blockId, html: cached };
|
|
1626
|
+
}
|
|
1627
|
+
const context = {
|
|
1628
|
+
version: 0,
|
|
1629
|
+
themeKey,
|
|
1630
|
+
sourceLineOffset: block.startLine - 1
|
|
1631
|
+
};
|
|
1632
|
+
const html = renderBlock ? await renderBlock(block, context) : await renderBlockDefault(block);
|
|
1633
|
+
nextCache.set(block.renderKey, html);
|
|
1634
|
+
return { blockId: block.blockId, html };
|
|
1635
|
+
}));
|
|
1636
|
+
cache.clear();
|
|
1637
|
+
for (const [k, v] of nextCache) cache.set(k, v);
|
|
1638
|
+
return results;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
// src/useMdxPreviewCompilation.ts
|
|
1642
|
+
import { inspectMdxSource as inspectMdxSource2 } from "@owomark/processor";
|
|
1643
|
+
function useMdxPreviewCompilation({
|
|
1644
|
+
snapshot,
|
|
1645
|
+
themeKey,
|
|
1646
|
+
renderBlock,
|
|
1647
|
+
mdx,
|
|
1648
|
+
wrappedComponents
|
|
1649
|
+
}) {
|
|
1650
|
+
const requestIdRef = useRef8(0);
|
|
1651
|
+
const cancelRef = useRef8(null);
|
|
1652
|
+
const blockCacheRef = useRef8(/* @__PURE__ */ new Map());
|
|
1653
|
+
const [mdxDisplay, setMdxDisplay] = useState6({
|
|
1654
|
+
code: null,
|
|
1655
|
+
Content: null,
|
|
1656
|
+
runtimeComponents: null,
|
|
1657
|
+
compileKey: null,
|
|
1658
|
+
renderKey: null,
|
|
1659
|
+
pending: false,
|
|
1660
|
+
error: null
|
|
1661
|
+
});
|
|
1662
|
+
const [markdownHtml, setMarkdownHtml] = useState6(null);
|
|
1663
|
+
useEffect6(() => {
|
|
1664
|
+
acquireMdxWorker();
|
|
1665
|
+
return () => {
|
|
1666
|
+
cancelRef.current?.();
|
|
1667
|
+
cancelRef.current = null;
|
|
1668
|
+
releaseMdxWorker();
|
|
1669
|
+
};
|
|
1670
|
+
}, []);
|
|
1671
|
+
const mdxInspection = useMemo5(
|
|
1672
|
+
() => inspectMdxSource2(snapshot.markdown, {
|
|
1673
|
+
enableMath: mdx?.enableMath,
|
|
1674
|
+
enableSideAnnotation: mdx?.enableSideAnnotation,
|
|
1675
|
+
extraRemarkDescriptors: mdx?.extraRemarkDescriptors,
|
|
1676
|
+
extraRemarkPlugins: mdx?.extraRemarkPlugins
|
|
1677
|
+
}),
|
|
1678
|
+
[
|
|
1679
|
+
mdx?.enableMath,
|
|
1680
|
+
mdx?.enableSideAnnotation,
|
|
1681
|
+
mdx?.extraRemarkDescriptors,
|
|
1682
|
+
mdx?.extraRemarkPlugins,
|
|
1683
|
+
snapshot.markdown
|
|
1684
|
+
]
|
|
1685
|
+
);
|
|
1686
|
+
const hasMdx = mdx?.forceFullDocumentCompile === true || mdxInspection.hasMdxSyntax;
|
|
1687
|
+
const optionsFingerprint = useMemo5(() => {
|
|
1688
|
+
const b = (v) => v === true ? "1" : v === false ? "0" : "_";
|
|
1689
|
+
const ds = (arr) => arr?.length ? arr.map((d) => d.options ? `${d.name}(${JSON.stringify(d.options)})` : d.name).join(",") : "";
|
|
1690
|
+
return [
|
|
1691
|
+
b(mdx?.enableMath),
|
|
1692
|
+
b(mdx?.enableSideAnnotation),
|
|
1693
|
+
b(mdx?.sourceAnchors),
|
|
1694
|
+
ds(mdx?.extraRemarkDescriptors),
|
|
1695
|
+
ds(mdx?.extraRehypeDescriptors),
|
|
1696
|
+
`rp${getObjectIdentity(mdx?.extraRemarkPlugins)}`,
|
|
1697
|
+
`hp${getObjectIdentity(mdx?.extraRehypePlugins)}`
|
|
1698
|
+
].join("|");
|
|
1699
|
+
}, [
|
|
1700
|
+
mdx?.enableMath,
|
|
1701
|
+
mdx?.enableSideAnnotation,
|
|
1702
|
+
mdx?.sourceAnchors,
|
|
1703
|
+
mdx?.extraRemarkDescriptors,
|
|
1704
|
+
mdx?.extraRehypeDescriptors,
|
|
1705
|
+
mdx?.extraRemarkPlugins,
|
|
1706
|
+
mdx?.extraRehypePlugins
|
|
1707
|
+
]);
|
|
1708
|
+
const compileKey = useMemo5(
|
|
1709
|
+
() => deriveRenderKey2(`${snapshot.markdown}:${optionsFingerprint}`, "custom", ""),
|
|
1710
|
+
[snapshot.markdown, optionsFingerprint]
|
|
1711
|
+
);
|
|
1712
|
+
const componentFingerprint = useMemo5(
|
|
1713
|
+
() => `cp${getObjectIdentity(mdx?.components)}:theme:${themeKey}`,
|
|
1714
|
+
[mdx?.components, themeKey]
|
|
1715
|
+
);
|
|
1716
|
+
const renderKey = useMemo5(
|
|
1717
|
+
() => `${compileKey}:${componentFingerprint}`,
|
|
1718
|
+
[compileKey, componentFingerprint]
|
|
1719
|
+
);
|
|
1720
|
+
useEffect6(() => {
|
|
1721
|
+
if (!snapshot.markdown.trim()) {
|
|
1722
|
+
cancelRef.current?.();
|
|
1723
|
+
cancelRef.current = null;
|
|
1724
|
+
setMarkdownHtml(null);
|
|
1725
|
+
setMdxDisplay({
|
|
1726
|
+
code: null,
|
|
1727
|
+
Content: null,
|
|
1728
|
+
runtimeComponents: null,
|
|
1729
|
+
compileKey: null,
|
|
1730
|
+
renderKey: null,
|
|
1731
|
+
pending: false,
|
|
1732
|
+
error: null
|
|
1733
|
+
});
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
const requestId = ++requestIdRef.current;
|
|
1737
|
+
if (!hasMdx) {
|
|
1738
|
+
cancelRef.current?.();
|
|
1739
|
+
cancelRef.current = null;
|
|
1740
|
+
void renderMarkdownBlocksCached(snapshot.previewBlocks, themeKey, blockCacheRef.current, renderBlock).then((blocks) => {
|
|
1741
|
+
if (requestIdRef.current !== requestId) return;
|
|
1742
|
+
setMarkdownHtml(blocks);
|
|
1743
|
+
setMdxDisplay({
|
|
1744
|
+
code: null,
|
|
1745
|
+
Content: null,
|
|
1746
|
+
runtimeComponents: null,
|
|
1747
|
+
compileKey: null,
|
|
1748
|
+
renderKey: null,
|
|
1749
|
+
pending: false,
|
|
1750
|
+
error: null
|
|
1751
|
+
});
|
|
1752
|
+
});
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
if (mdxDisplay.compileKey === compileKey && mdxDisplay.code) {
|
|
1756
|
+
if (mdxDisplay.renderKey !== renderKey) {
|
|
1757
|
+
const rerunId = ++requestIdRef.current;
|
|
1758
|
+
const cachedCode = mdxDisplay.code;
|
|
1759
|
+
setMdxDisplay((prev) => ({ ...prev, pending: true, error: null }));
|
|
1760
|
+
void (async () => {
|
|
1761
|
+
try {
|
|
1762
|
+
const { Content } = await runMdxCode(cachedCode);
|
|
1763
|
+
if (requestIdRef.current !== rerunId) return;
|
|
1764
|
+
setMdxDisplay((prev) => {
|
|
1765
|
+
if (prev.compileKey !== compileKey || prev.code !== cachedCode) return prev;
|
|
1766
|
+
return {
|
|
1767
|
+
...prev,
|
|
1768
|
+
Content,
|
|
1769
|
+
runtimeComponents: buildRuntimeComponents(mdxInspection, wrappedComponents, {
|
|
1770
|
+
themeKey,
|
|
1771
|
+
documentVersion: snapshot.contentVersion
|
|
1772
|
+
}),
|
|
1773
|
+
renderKey,
|
|
1774
|
+
pending: false,
|
|
1775
|
+
error: null
|
|
1776
|
+
};
|
|
1777
|
+
});
|
|
1778
|
+
} catch (error) {
|
|
1779
|
+
if (requestIdRef.current !== rerunId) return;
|
|
1780
|
+
setMdxDisplay((prev) => ({
|
|
1781
|
+
...prev,
|
|
1782
|
+
pending: false,
|
|
1783
|
+
error: parseCompileError(error, "runtime")
|
|
1784
|
+
}));
|
|
1785
|
+
}
|
|
1786
|
+
})();
|
|
1787
|
+
}
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1790
|
+
setMdxDisplay((prev) => ({ ...prev, pending: true, error: null }));
|
|
1791
|
+
setMarkdownHtml(null);
|
|
1792
|
+
cancelRef.current?.();
|
|
1793
|
+
cancelRef.current = null;
|
|
1794
|
+
const timer = setTimeout(() => {
|
|
1795
|
+
const { promise, cancel } = compileMdx(snapshot.markdown, {
|
|
1796
|
+
enableMath: mdx?.enableMath,
|
|
1797
|
+
enableSideAnnotation: mdx?.enableSideAnnotation,
|
|
1798
|
+
enableCodeHighlight: false,
|
|
1799
|
+
sourceAnchors: mdx?.sourceAnchors,
|
|
1800
|
+
extraRemarkDescriptors: mdx?.extraRemarkDescriptors,
|
|
1801
|
+
extraRehypeDescriptors: mdx?.extraRehypeDescriptors,
|
|
1802
|
+
extraRemarkPlugins: mdx?.extraRemarkPlugins,
|
|
1803
|
+
extraRehypePlugins: mdx?.extraRehypePlugins
|
|
1804
|
+
}, mdxInspection);
|
|
1805
|
+
cancelRef.current = cancel;
|
|
1806
|
+
void (async () => {
|
|
1807
|
+
try {
|
|
1808
|
+
const code = await promise;
|
|
1809
|
+
if (requestIdRef.current !== requestId) return;
|
|
1810
|
+
const { Content } = await runMdxCode(code);
|
|
1811
|
+
if (requestIdRef.current !== requestId) return;
|
|
1812
|
+
setMdxDisplay({
|
|
1813
|
+
code,
|
|
1814
|
+
Content,
|
|
1815
|
+
runtimeComponents: buildRuntimeComponents(mdxInspection, wrappedComponents, {
|
|
1816
|
+
themeKey,
|
|
1817
|
+
documentVersion: snapshot.contentVersion
|
|
1818
|
+
}),
|
|
1819
|
+
compileKey,
|
|
1820
|
+
renderKey,
|
|
1821
|
+
pending: false,
|
|
1822
|
+
error: null
|
|
1823
|
+
});
|
|
1824
|
+
} catch (error) {
|
|
1825
|
+
if (requestIdRef.current !== requestId) return;
|
|
1826
|
+
setMdxDisplay((prev) => ({
|
|
1827
|
+
...prev,
|
|
1828
|
+
pending: false,
|
|
1829
|
+
error: parseCompileError(error, "compile")
|
|
1830
|
+
}));
|
|
1831
|
+
}
|
|
1832
|
+
})();
|
|
1833
|
+
}, 200);
|
|
1834
|
+
return () => {
|
|
1835
|
+
clearTimeout(timer);
|
|
1836
|
+
cancelRef.current?.();
|
|
1837
|
+
cancelRef.current = null;
|
|
1838
|
+
};
|
|
1839
|
+
}, [
|
|
1840
|
+
compileKey,
|
|
1841
|
+
hasMdx,
|
|
1842
|
+
mdx?.enableMath,
|
|
1843
|
+
mdx?.enableSideAnnotation,
|
|
1844
|
+
mdx?.forceFullDocumentCompile,
|
|
1845
|
+
mdx?.extraRehypeDescriptors,
|
|
1846
|
+
mdx?.extraRehypePlugins,
|
|
1847
|
+
mdx?.extraRemarkDescriptors,
|
|
1848
|
+
mdx?.extraRemarkPlugins,
|
|
1849
|
+
mdxInspection,
|
|
1850
|
+
mdx?.sourceAnchors,
|
|
1851
|
+
mdxDisplay.compileKey,
|
|
1852
|
+
mdxDisplay.Content,
|
|
1853
|
+
mdxDisplay.renderKey,
|
|
1854
|
+
renderBlock,
|
|
1855
|
+
renderKey,
|
|
1856
|
+
snapshot.markdown,
|
|
1857
|
+
snapshot.contentVersion,
|
|
1858
|
+
snapshot.previewBlocks,
|
|
1859
|
+
themeKey,
|
|
1860
|
+
wrappedComponents
|
|
1861
|
+
]);
|
|
1862
|
+
return {
|
|
1863
|
+
hasMdx,
|
|
1864
|
+
markdownHtml,
|
|
1865
|
+
mdxDisplay
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
700
1868
|
|
|
701
1869
|
// src/useOwoMarkSharedState.ts
|
|
702
|
-
import { useRef as
|
|
1870
|
+
import { useRef as useRef9, useSyncExternalStore, useCallback as useCallback4 } from "react";
|
|
703
1871
|
import {
|
|
704
1872
|
createSharedStateStore
|
|
705
1873
|
} from "@owomark/core";
|
|
706
1874
|
function useOwoMarkSharedState(options) {
|
|
707
|
-
const controllerRef =
|
|
1875
|
+
const controllerRef = useRef9(null);
|
|
708
1876
|
if (!controllerRef.current) {
|
|
709
1877
|
controllerRef.current = createSharedStateStore(options);
|
|
710
1878
|
}
|
|
711
1879
|
return controllerRef.current;
|
|
712
1880
|
}
|
|
713
1881
|
function useSharedStateSnapshot(controller) {
|
|
714
|
-
const subscribe =
|
|
1882
|
+
const subscribe = useCallback4(
|
|
715
1883
|
(onStoreChange) => controller.subscribe(onStoreChange),
|
|
716
1884
|
[controller]
|
|
717
1885
|
);
|
|
718
|
-
const getSnapshot =
|
|
1886
|
+
const getSnapshot = useCallback4(() => controller.getState(), [controller]);
|
|
1887
|
+
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
1888
|
+
}
|
|
1889
|
+
function useSharedStateSelector(store, selector, isEqual = Object.is) {
|
|
1890
|
+
const selectedRef = useRef9(selector(store.getState()));
|
|
1891
|
+
const selected = selector(store.getState());
|
|
1892
|
+
if (!isEqual(selectedRef.current, selected)) {
|
|
1893
|
+
selectedRef.current = selected;
|
|
1894
|
+
}
|
|
1895
|
+
const subscribe = useCallback4((onStoreChange) => store.subscribeWithSelector(selector, (nextSelected) => {
|
|
1896
|
+
if (isEqual(selectedRef.current, nextSelected)) return;
|
|
1897
|
+
selectedRef.current = nextSelected;
|
|
1898
|
+
onStoreChange();
|
|
1899
|
+
}, isEqual), [isEqual, selector, store]);
|
|
1900
|
+
const getSnapshot = useCallback4(() => selectedRef.current, []);
|
|
719
1901
|
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
720
1902
|
}
|
|
721
1903
|
|
|
1904
|
+
// src/MdxPreview.tsx
|
|
1905
|
+
import { jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1906
|
+
function locationText(error) {
|
|
1907
|
+
if (error.line == null) return null;
|
|
1908
|
+
if (error.column == null) return `\u884C ${error.line}`;
|
|
1909
|
+
return `\u884C ${error.line}:${error.column}`;
|
|
1910
|
+
}
|
|
1911
|
+
function ErrorNotice({ error }) {
|
|
1912
|
+
const location = locationText(error);
|
|
1913
|
+
const label = error.kind === "compile" ? "MDX Compile" : "MDX Runtime";
|
|
1914
|
+
return /* @__PURE__ */ jsx11("div", { className: "sticky top-0 z-10 mb-4 rounded-md border border-[var(--owo-cmp-danger-border)] bg-[var(--owo-cmp-danger-bg)] px-4 py-3 text-sm text-[var(--owo-cmp-danger-text)]", children: /* @__PURE__ */ jsxs5("div", { className: "flex items-start gap-2", children: [
|
|
1915
|
+
/* @__PURE__ */ jsx11("span", { className: "mt-0.5 shrink-0 font-mono text-[11px] font-bold uppercase", children: label }),
|
|
1916
|
+
/* @__PURE__ */ jsxs5("div", { className: "min-w-0 flex-1", children: [
|
|
1917
|
+
location && /* @__PURE__ */ jsx11("span", { className: "mr-2 rounded bg-[var(--owo-cmp-danger-border)] px-1.5 py-0.5 font-mono text-xs", children: location }),
|
|
1918
|
+
/* @__PURE__ */ jsx11("span", { className: "break-words", children: error.message })
|
|
1919
|
+
] })
|
|
1920
|
+
] }) });
|
|
1921
|
+
}
|
|
1922
|
+
var RuntimeErrorBoundary = class extends Component {
|
|
1923
|
+
state = { error: null };
|
|
1924
|
+
static getDerivedStateFromError(error) {
|
|
1925
|
+
return { error: parseCompileError(error, "runtime") };
|
|
1926
|
+
}
|
|
1927
|
+
componentDidCatch(error) {
|
|
1928
|
+
this.props.onError(parseCompileError(error, "runtime"));
|
|
1929
|
+
}
|
|
1930
|
+
render() {
|
|
1931
|
+
if (this.state.error) {
|
|
1932
|
+
return /* @__PURE__ */ jsx11(ErrorNotice, { error: this.state.error });
|
|
1933
|
+
}
|
|
1934
|
+
return this.props.children;
|
|
1935
|
+
}
|
|
1936
|
+
};
|
|
1937
|
+
function useMdxPreviewContentSnapshot(store, markdownOverride) {
|
|
1938
|
+
return useSharedStateSelector(
|
|
1939
|
+
store,
|
|
1940
|
+
(snapshot) => ({
|
|
1941
|
+
markdown: markdownOverride ?? snapshot.markdown,
|
|
1942
|
+
previewBlocks: snapshot.previewBlocks,
|
|
1943
|
+
contentVersion: snapshot.contentVersion
|
|
1944
|
+
}),
|
|
1945
|
+
(prev, next) => prev.markdown === next.markdown && prev.previewBlocks === next.previewBlocks && prev.contentVersion === next.contentVersion
|
|
1946
|
+
);
|
|
1947
|
+
}
|
|
1948
|
+
var MdxPreview = forwardRef2(function MdxPreview2(props, ref) {
|
|
1949
|
+
const {
|
|
1950
|
+
state,
|
|
1951
|
+
markdown,
|
|
1952
|
+
className,
|
|
1953
|
+
themeKey = "",
|
|
1954
|
+
ariaLabel,
|
|
1955
|
+
onScroll,
|
|
1956
|
+
scrollController,
|
|
1957
|
+
scrollSync = "bidirectional",
|
|
1958
|
+
renderBlock,
|
|
1959
|
+
mdx,
|
|
1960
|
+
onContentUpdate
|
|
1961
|
+
} = props;
|
|
1962
|
+
const snapshot = useMdxPreviewContentSnapshot(state, markdown);
|
|
1963
|
+
const rootRef = useRef10(null);
|
|
1964
|
+
const boundSurfaceRef = useRef10(null);
|
|
1965
|
+
const [runtimeError, setRuntimeError] = useState7(null);
|
|
1966
|
+
const wrappedComponents = useMemo6(() => {
|
|
1967
|
+
const wrapComponentMap = (componentMap) => {
|
|
1968
|
+
const wrapped = {};
|
|
1969
|
+
for (const [name, component] of Object.entries(componentMap)) {
|
|
1970
|
+
if (typeof component === "function") {
|
|
1971
|
+
wrapped[name] = wrapWithShell(name, component);
|
|
1972
|
+
continue;
|
|
1973
|
+
}
|
|
1974
|
+
wrapped[name] = wrapComponentMap(component);
|
|
1975
|
+
}
|
|
1976
|
+
return wrapped;
|
|
1977
|
+
};
|
|
1978
|
+
const merged = {
|
|
1979
|
+
...DEFAULT_MDX_COMPONENTS,
|
|
1980
|
+
...mdx?.components ?? {}
|
|
1981
|
+
};
|
|
1982
|
+
return wrapComponentMap(merged);
|
|
1983
|
+
}, [mdx?.components]);
|
|
1984
|
+
const { markdownHtml, mdxDisplay } = useMdxPreviewCompilation({
|
|
1985
|
+
snapshot,
|
|
1986
|
+
themeKey,
|
|
1987
|
+
renderBlock,
|
|
1988
|
+
mdx,
|
|
1989
|
+
wrappedComponents
|
|
1990
|
+
});
|
|
1991
|
+
useEffect7(() => {
|
|
1992
|
+
setRuntimeError(null);
|
|
1993
|
+
}, [mdxDisplay.renderKey]);
|
|
1994
|
+
useEffect7(() => {
|
|
1995
|
+
const root = rootRef.current;
|
|
1996
|
+
if (!root || !onContentUpdate && (!scrollController || scrollSync === "off")) return;
|
|
1997
|
+
const notify = () => {
|
|
1998
|
+
onContentUpdate?.();
|
|
1999
|
+
if (scrollController && scrollSync !== "off") {
|
|
2000
|
+
scrollController.measureSurface("preview");
|
|
2001
|
+
}
|
|
2002
|
+
};
|
|
2003
|
+
const observer = new ResizeObserver(() => notify());
|
|
2004
|
+
observer.observe(root);
|
|
2005
|
+
return () => observer.disconnect();
|
|
2006
|
+
}, [onContentUpdate, scrollController, scrollSync]);
|
|
2007
|
+
useEffect7(() => {
|
|
2008
|
+
if (!onContentUpdate && (!scrollController || scrollSync === "off")) return;
|
|
2009
|
+
const id = requestAnimationFrame(() => {
|
|
2010
|
+
onContentUpdate?.();
|
|
2011
|
+
if (scrollController && scrollSync !== "off") {
|
|
2012
|
+
scrollController.measureSurface("preview");
|
|
2013
|
+
}
|
|
2014
|
+
});
|
|
2015
|
+
return () => cancelAnimationFrame(id);
|
|
2016
|
+
}, [mdxDisplay, markdownHtml, runtimeError, onContentUpdate, scrollController, scrollSync]);
|
|
2017
|
+
const hasContent = mdxDisplay.Content !== null || markdownHtml !== null;
|
|
2018
|
+
return /* @__PURE__ */ jsxs5(
|
|
2019
|
+
"div",
|
|
2020
|
+
{
|
|
2021
|
+
ref: (node) => {
|
|
2022
|
+
if (scrollController && scrollSync !== "off" && boundSurfaceRef.current !== node) {
|
|
2023
|
+
if (boundSurfaceRef.current) {
|
|
2024
|
+
scrollController.unbindSurface("preview", boundSurfaceRef.current);
|
|
2025
|
+
}
|
|
2026
|
+
if (node) {
|
|
2027
|
+
scrollController.bindSurface("preview", node);
|
|
2028
|
+
}
|
|
2029
|
+
boundSurfaceRef.current = node;
|
|
2030
|
+
} else if ((!scrollController || scrollSync === "off") && boundSurfaceRef.current) {
|
|
2031
|
+
scrollController?.unbindSurface("preview", boundSurfaceRef.current);
|
|
2032
|
+
boundSurfaceRef.current = null;
|
|
2033
|
+
}
|
|
2034
|
+
rootRef.current = node;
|
|
2035
|
+
if (typeof ref === "function") ref(node);
|
|
2036
|
+
else if (ref) ref.current = node;
|
|
2037
|
+
},
|
|
2038
|
+
className,
|
|
2039
|
+
role: "document",
|
|
2040
|
+
"aria-label": ariaLabel ?? "Preview",
|
|
2041
|
+
"aria-live": "polite",
|
|
2042
|
+
onScroll,
|
|
2043
|
+
children: [
|
|
2044
|
+
mdxDisplay.pending && hasContent && /* @__PURE__ */ jsx11("div", { className: "pointer-events-none sticky top-0 z-10 mb-3 h-0.5 w-full animate-pulse rounded-full bg-p400" }),
|
|
2045
|
+
mdxDisplay.error && /* @__PURE__ */ jsx11(ErrorNotice, { error: mdxDisplay.error }),
|
|
2046
|
+
runtimeError && /* @__PURE__ */ jsx11(ErrorNotice, { error: runtimeError }),
|
|
2047
|
+
!hasContent && mdxDisplay.pending && /* @__PURE__ */ jsx11(MdxSkeleton, { lines: 8 }),
|
|
2048
|
+
markdownHtml !== null && markdownHtml.map(({ blockId, html }) => /* @__PURE__ */ jsx11("div", { dangerouslySetInnerHTML: { __html: html } }, blockId)),
|
|
2049
|
+
mdxDisplay.Content && mdxDisplay.runtimeComponents && /* @__PURE__ */ jsx11(RuntimeErrorBoundary, { onError: setRuntimeError, children: /* @__PURE__ */ jsx11(mdxDisplay.Content, { components: mdxDisplay.runtimeComponents }) }, mdxDisplay.renderKey)
|
|
2050
|
+
]
|
|
2051
|
+
}
|
|
2052
|
+
);
|
|
2053
|
+
});
|
|
2054
|
+
|
|
2055
|
+
// src/OwoMarkPreview.tsx
|
|
2056
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
2057
|
+
function isController(store) {
|
|
2058
|
+
return typeof store.updateVisibleBlockIds === "function";
|
|
2059
|
+
}
|
|
2060
|
+
function usePreviewSurfaceAdapter(rootRef, scrollController, scrollSync = "bidirectional", observeDeps = []) {
|
|
2061
|
+
useEffect8(() => {
|
|
2062
|
+
const root = rootRef.current;
|
|
2063
|
+
if (!root || !scrollController || scrollSync === "off") return;
|
|
2064
|
+
const measure = () => {
|
|
2065
|
+
scrollController.measureSurface("preview");
|
|
2066
|
+
};
|
|
2067
|
+
const resizeObserver = new ResizeObserver(() => measure());
|
|
2068
|
+
resizeObserver.observe(root);
|
|
2069
|
+
const observeChildren = () => {
|
|
2070
|
+
resizeObserver.disconnect();
|
|
2071
|
+
resizeObserver.observe(root);
|
|
2072
|
+
Array.from(root.children).forEach((child) => resizeObserver.observe(child));
|
|
2073
|
+
};
|
|
2074
|
+
observeChildren();
|
|
2075
|
+
const mutationObserver = new MutationObserver(() => {
|
|
2076
|
+
observeChildren();
|
|
2077
|
+
measure();
|
|
2078
|
+
});
|
|
2079
|
+
mutationObserver.observe(root, { childList: true, subtree: true, characterData: true });
|
|
2080
|
+
const onLoad = (event) => {
|
|
2081
|
+
if (event.target?.tagName === "IMG") {
|
|
2082
|
+
measure();
|
|
2083
|
+
}
|
|
2084
|
+
};
|
|
2085
|
+
root.addEventListener("load", onLoad, true);
|
|
2086
|
+
const frame = requestAnimationFrame(measure);
|
|
2087
|
+
let disposed = false;
|
|
2088
|
+
if (typeof document !== "undefined" && document.fonts) {
|
|
2089
|
+
document.fonts.ready.then(() => {
|
|
2090
|
+
if (!disposed) measure();
|
|
2091
|
+
});
|
|
2092
|
+
}
|
|
2093
|
+
return () => {
|
|
2094
|
+
disposed = true;
|
|
2095
|
+
cancelAnimationFrame(frame);
|
|
2096
|
+
resizeObserver.disconnect();
|
|
2097
|
+
mutationObserver.disconnect();
|
|
2098
|
+
root.removeEventListener("load", onLoad, true);
|
|
2099
|
+
};
|
|
2100
|
+
}, [rootRef, scrollController, scrollSync, ...observeDeps]);
|
|
2101
|
+
}
|
|
2102
|
+
var OwoMarkPreview = forwardRef3(
|
|
2103
|
+
function OwoMarkPreview2(props, ref) {
|
|
2104
|
+
const {
|
|
2105
|
+
state,
|
|
2106
|
+
markdown,
|
|
2107
|
+
className,
|
|
2108
|
+
strategy,
|
|
2109
|
+
themeKey,
|
|
2110
|
+
registry,
|
|
2111
|
+
viewportFirst,
|
|
2112
|
+
ariaLabel,
|
|
2113
|
+
scrollController,
|
|
2114
|
+
scrollSync = "bidirectional",
|
|
2115
|
+
onScroll,
|
|
2116
|
+
renderBlock,
|
|
2117
|
+
mdx,
|
|
2118
|
+
onContentUpdate
|
|
2119
|
+
} = props;
|
|
2120
|
+
if (strategy === "mdx") {
|
|
2121
|
+
return /* @__PURE__ */ jsx12(
|
|
2122
|
+
MdxPreview,
|
|
2123
|
+
{
|
|
2124
|
+
ref,
|
|
2125
|
+
state,
|
|
2126
|
+
markdown,
|
|
2127
|
+
className,
|
|
2128
|
+
themeKey,
|
|
2129
|
+
ariaLabel,
|
|
2130
|
+
onScroll,
|
|
2131
|
+
scrollController,
|
|
2132
|
+
scrollSync,
|
|
2133
|
+
onContentUpdate,
|
|
2134
|
+
renderBlock,
|
|
2135
|
+
mdx
|
|
2136
|
+
}
|
|
2137
|
+
);
|
|
2138
|
+
}
|
|
2139
|
+
const renderBlockRef = useRef11(renderBlock);
|
|
2140
|
+
renderBlockRef.current = renderBlock;
|
|
2141
|
+
const hasRenderBlock = !!renderBlock;
|
|
2142
|
+
const containerRef = useRef11(null);
|
|
2143
|
+
const boundSurfaceRef = useRef11(null);
|
|
2144
|
+
const engineRef = useRef11(null);
|
|
2145
|
+
usePreviewSurfaceAdapter(containerRef, scrollController, scrollSync, [
|
|
2146
|
+
strategy,
|
|
2147
|
+
themeKey,
|
|
2148
|
+
registry,
|
|
2149
|
+
viewportFirst,
|
|
2150
|
+
hasRenderBlock
|
|
2151
|
+
]);
|
|
2152
|
+
useEffect8(() => {
|
|
2153
|
+
if (!containerRef.current) return;
|
|
2154
|
+
const engineOptions = {
|
|
2155
|
+
strategy,
|
|
2156
|
+
themeKey,
|
|
2157
|
+
registry,
|
|
2158
|
+
viewportFirst,
|
|
2159
|
+
renderBlock: hasRenderBlock ? (block, ctx) => renderBlockRef.current(block, ctx) : void 0,
|
|
2160
|
+
onContentUpdate
|
|
2161
|
+
};
|
|
2162
|
+
const engine = createOwoMarkPreviewEngine(engineOptions);
|
|
2163
|
+
engine.mount(containerRef.current);
|
|
2164
|
+
engineRef.current = engine;
|
|
2165
|
+
void engine.update(state.getState());
|
|
2166
|
+
return () => {
|
|
2167
|
+
engine.destroy();
|
|
2168
|
+
engineRef.current = null;
|
|
2169
|
+
};
|
|
2170
|
+
}, [strategy, themeKey, registry, viewportFirst, hasRenderBlock]);
|
|
2171
|
+
useEffect8(() => {
|
|
2172
|
+
const unsub = state.subscribe((newState) => {
|
|
2173
|
+
const engine = engineRef.current;
|
|
2174
|
+
if (engine) {
|
|
2175
|
+
engine.update(newState);
|
|
2176
|
+
requestAnimationFrame(() => {
|
|
2177
|
+
if (scrollController && scrollSync !== "off") {
|
|
2178
|
+
scrollController.measureSurface("preview");
|
|
2179
|
+
}
|
|
2180
|
+
onContentUpdate?.();
|
|
2181
|
+
});
|
|
2182
|
+
}
|
|
2183
|
+
});
|
|
2184
|
+
return unsub;
|
|
2185
|
+
}, [state, scrollController, scrollSync, onContentUpdate]);
|
|
2186
|
+
useEffect8(() => {
|
|
2187
|
+
const container = containerRef.current;
|
|
2188
|
+
if (!viewportFirst || !container || !isController(state)) return;
|
|
2189
|
+
const controller = state;
|
|
2190
|
+
const observer = new IntersectionObserver(
|
|
2191
|
+
(entries) => {
|
|
2192
|
+
const visible = /* @__PURE__ */ new Set();
|
|
2193
|
+
for (const id of controller.getState().visibleBlockIds) {
|
|
2194
|
+
visible.add(id);
|
|
2195
|
+
}
|
|
2196
|
+
for (const entry of entries) {
|
|
2197
|
+
const blockId = entry.target.getAttribute("data-block-id");
|
|
2198
|
+
if (!blockId) continue;
|
|
2199
|
+
if (entry.isIntersecting) visible.add(blockId);
|
|
2200
|
+
else visible.delete(blockId);
|
|
2201
|
+
}
|
|
2202
|
+
controller.updateVisibleBlockIds([...visible]);
|
|
2203
|
+
},
|
|
2204
|
+
{ root: container, rootMargin: "200px 0px" }
|
|
2205
|
+
);
|
|
2206
|
+
const wrappers = container.querySelectorAll("[data-block-id]");
|
|
2207
|
+
for (const wrapper of wrappers) {
|
|
2208
|
+
observer.observe(wrapper);
|
|
2209
|
+
}
|
|
2210
|
+
const mutation = new MutationObserver((mutations) => {
|
|
2211
|
+
for (const m of mutations) {
|
|
2212
|
+
for (const node of m.addedNodes) {
|
|
2213
|
+
if (node instanceof HTMLElement && node.hasAttribute("data-block-id")) {
|
|
2214
|
+
observer.observe(node);
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
});
|
|
2219
|
+
mutation.observe(container, { childList: true, subtree: true });
|
|
2220
|
+
return () => {
|
|
2221
|
+
observer.disconnect();
|
|
2222
|
+
mutation.disconnect();
|
|
2223
|
+
};
|
|
2224
|
+
}, [viewportFirst, state]);
|
|
2225
|
+
return /* @__PURE__ */ jsx12(
|
|
2226
|
+
"div",
|
|
2227
|
+
{
|
|
2228
|
+
ref: (node) => {
|
|
2229
|
+
if (scrollController && scrollSync !== "off" && boundSurfaceRef.current !== node) {
|
|
2230
|
+
if (boundSurfaceRef.current) {
|
|
2231
|
+
scrollController.unbindSurface("preview", boundSurfaceRef.current);
|
|
2232
|
+
}
|
|
2233
|
+
if (node) {
|
|
2234
|
+
scrollController.bindSurface("preview", node);
|
|
2235
|
+
}
|
|
2236
|
+
boundSurfaceRef.current = node;
|
|
2237
|
+
} else if ((!scrollController || scrollSync === "off") && boundSurfaceRef.current) {
|
|
2238
|
+
scrollController?.unbindSurface("preview", boundSurfaceRef.current);
|
|
2239
|
+
boundSurfaceRef.current = null;
|
|
2240
|
+
}
|
|
2241
|
+
containerRef.current = node;
|
|
2242
|
+
if (typeof ref === "function") ref(node);
|
|
2243
|
+
else if (ref) ref.current = node;
|
|
2244
|
+
},
|
|
2245
|
+
className,
|
|
2246
|
+
role: "document",
|
|
2247
|
+
"aria-label": ariaLabel ?? "Preview",
|
|
2248
|
+
"aria-live": "polite",
|
|
2249
|
+
onScroll
|
|
2250
|
+
}
|
|
2251
|
+
);
|
|
2252
|
+
}
|
|
2253
|
+
);
|
|
2254
|
+
|
|
722
2255
|
// src/useBlockContext.ts
|
|
723
|
-
import { useState as
|
|
2256
|
+
import { useState as useState8, useEffect as useEffect9 } from "react";
|
|
724
2257
|
function useBlockContext(core) {
|
|
725
|
-
const [blockContext, setBlockContext] =
|
|
2258
|
+
const [blockContext, setBlockContext] = useState8(
|
|
726
2259
|
() => core.getBlockContext()
|
|
727
2260
|
);
|
|
728
|
-
|
|
2261
|
+
useEffect9(() => {
|
|
729
2262
|
setBlockContext(core.getBlockContext());
|
|
730
2263
|
return core.onBlockContextChange((ctx) => {
|
|
731
2264
|
setBlockContext(ctx);
|
|
@@ -737,24 +2270,52 @@ function useBlockContext(core) {
|
|
|
737
2270
|
// src/index.ts
|
|
738
2271
|
import { getThemeClassName as getThemeClassName2, THEME_LIGHT_CLASS, THEME_DARK_CLASS } from "@owomark/view";
|
|
739
2272
|
|
|
2273
|
+
// src/useScrollController.ts
|
|
2274
|
+
import { useEffect as useEffect10, useRef as useRef12 } from "react";
|
|
2275
|
+
import {
|
|
2276
|
+
createScrollController
|
|
2277
|
+
} from "@owomark/core/browser";
|
|
2278
|
+
function useScrollController(controller) {
|
|
2279
|
+
const scrollControllerRef = useRef12(null);
|
|
2280
|
+
if (!scrollControllerRef.current) {
|
|
2281
|
+
scrollControllerRef.current = createScrollController();
|
|
2282
|
+
}
|
|
2283
|
+
const sc = scrollControllerRef.current;
|
|
2284
|
+
useEffect10(() => {
|
|
2285
|
+
const state = controller.getState();
|
|
2286
|
+
sc.updateProjection(state.previewBlocks, state.contentVersion);
|
|
2287
|
+
return controller.subscribeWithSelector(
|
|
2288
|
+
(snapshot) => ({
|
|
2289
|
+
previewBlocks: snapshot.previewBlocks,
|
|
2290
|
+
contentVersion: snapshot.contentVersion
|
|
2291
|
+
}),
|
|
2292
|
+
({ previewBlocks, contentVersion }) => {
|
|
2293
|
+
sc.updateProjection(previewBlocks, contentVersion);
|
|
2294
|
+
},
|
|
2295
|
+
(prev, next) => prev.contentVersion === next.contentVersion
|
|
2296
|
+
);
|
|
2297
|
+
}, [controller, sc]);
|
|
2298
|
+
return sc;
|
|
2299
|
+
}
|
|
2300
|
+
|
|
740
2301
|
// src/useVirtualList.ts
|
|
741
|
-
import { useState as
|
|
2302
|
+
import { useState as useState9, useCallback as useCallback5, useRef as useRef13, useEffect as useEffect11, useMemo as useMemo7 } from "react";
|
|
742
2303
|
import {
|
|
743
2304
|
buildVirtualRows,
|
|
744
2305
|
computeVisibleRange
|
|
745
2306
|
} from "@owomark/core";
|
|
746
2307
|
function useVirtualList(options) {
|
|
747
2308
|
const { blocks, containerRef, overscan = 5, charsPerLine = 80 } = options;
|
|
748
|
-
const heightCacheRef =
|
|
749
|
-
const [scrollTop, setScrollTop] =
|
|
750
|
-
const [viewportHeight, setViewportHeight] =
|
|
751
|
-
const [heightCacheVersion, setHeightCacheVersion] =
|
|
752
|
-
const rafRef =
|
|
753
|
-
const [container, setContainer] =
|
|
754
|
-
|
|
2309
|
+
const heightCacheRef = useRef13(/* @__PURE__ */ new Map());
|
|
2310
|
+
const [scrollTop, setScrollTop] = useState9(0);
|
|
2311
|
+
const [viewportHeight, setViewportHeight] = useState9(0);
|
|
2312
|
+
const [heightCacheVersion, setHeightCacheVersion] = useState9(0);
|
|
2313
|
+
const rafRef = useRef13(0);
|
|
2314
|
+
const [container, setContainer] = useState9(null);
|
|
2315
|
+
useEffect11(() => {
|
|
755
2316
|
setContainer(containerRef.current);
|
|
756
2317
|
});
|
|
757
|
-
|
|
2318
|
+
useEffect11(() => {
|
|
758
2319
|
if (!container) return;
|
|
759
2320
|
setViewportHeight(container.clientHeight);
|
|
760
2321
|
const ro = new ResizeObserver((entries) => {
|
|
@@ -765,7 +2326,7 @@ function useVirtualList(options) {
|
|
|
765
2326
|
ro.observe(container);
|
|
766
2327
|
return () => ro.disconnect();
|
|
767
2328
|
}, [container]);
|
|
768
|
-
|
|
2329
|
+
useEffect11(() => {
|
|
769
2330
|
if (!container) return;
|
|
770
2331
|
const onScroll = () => {
|
|
771
2332
|
if (rafRef.current) cancelAnimationFrame(rafRef.current);
|
|
@@ -779,27 +2340,27 @@ function useVirtualList(options) {
|
|
|
779
2340
|
if (rafRef.current) cancelAnimationFrame(rafRef.current);
|
|
780
2341
|
};
|
|
781
2342
|
}, [container]);
|
|
782
|
-
const rows =
|
|
2343
|
+
const rows = useMemo7(
|
|
783
2344
|
() => buildVirtualRows(blocks, heightCacheRef.current, charsPerLine),
|
|
784
2345
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
785
2346
|
[blocks, charsPerLine, heightCacheVersion]
|
|
786
2347
|
);
|
|
787
|
-
const visibleRange =
|
|
2348
|
+
const visibleRange = useMemo7(
|
|
788
2349
|
() => computeVisibleRange(rows, scrollTop, viewportHeight, overscan),
|
|
789
2350
|
[rows, scrollTop, viewportHeight, overscan]
|
|
790
2351
|
);
|
|
791
|
-
const updateBlockHeight =
|
|
2352
|
+
const updateBlockHeight = useCallback5((blockId, height) => {
|
|
792
2353
|
const cache = heightCacheRef.current;
|
|
793
2354
|
if (cache.get(blockId) !== height) {
|
|
794
2355
|
cache.set(blockId, height);
|
|
795
2356
|
setHeightCacheVersion((v) => v + 1);
|
|
796
2357
|
}
|
|
797
2358
|
}, []);
|
|
798
|
-
const isBlockVisible =
|
|
2359
|
+
const isBlockVisible = useCallback5(
|
|
799
2360
|
(blockIndex) => blockIndex >= visibleRange.startIndex && blockIndex <= visibleRange.endIndex,
|
|
800
2361
|
[visibleRange]
|
|
801
2362
|
);
|
|
802
|
-
const invalidateAllHeights =
|
|
2363
|
+
const invalidateAllHeights = useCallback5(() => {
|
|
803
2364
|
heightCacheRef.current.clear();
|
|
804
2365
|
setHeightCacheVersion((v) => v + 1);
|
|
805
2366
|
}, []);
|
|
@@ -813,75 +2374,12 @@ function useVirtualList(options) {
|
|
|
813
2374
|
};
|
|
814
2375
|
}
|
|
815
2376
|
|
|
816
|
-
// src/
|
|
817
|
-
import {
|
|
818
|
-
function MdxSkeleton({ height, lines = 3, className, style }) {
|
|
819
|
-
const combinedStyle = {
|
|
820
|
-
...style,
|
|
821
|
-
...height != null ? { height, minHeight: height } : {}
|
|
822
|
-
};
|
|
823
|
-
return /* @__PURE__ */ jsx7(
|
|
824
|
-
"div",
|
|
825
|
-
{
|
|
826
|
-
className: `owo-mdx-skeleton-block ${className ?? ""}`,
|
|
827
|
-
style: combinedStyle,
|
|
828
|
-
"aria-hidden": "true",
|
|
829
|
-
children: Array.from({ length: lines }, (_, i) => /* @__PURE__ */ jsx7("div", { className: "owo-mdx-skeleton owo-mdx-skeleton-line" }, i))
|
|
830
|
-
}
|
|
831
|
-
);
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
// src/MdxComponentShell.tsx
|
|
835
|
-
import {
|
|
836
|
-
useRef as useRef7,
|
|
837
|
-
useState as useState5,
|
|
838
|
-
useEffect as useEffect7,
|
|
839
|
-
useCallback as useCallback4
|
|
840
|
-
} from "react";
|
|
841
|
-
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
842
|
-
var componentHeightCache = /* @__PURE__ */ new Map();
|
|
843
|
-
var DEFAULT_ESTIMATED_HEIGHT = 80;
|
|
844
|
-
function MdxComponentShell({ name, children }) {
|
|
845
|
-
const containerRef = useRef7(null);
|
|
846
|
-
const [ready, setReady] = useState5(false);
|
|
847
|
-
const estimatedHeight = componentHeightCache.get(name) ?? DEFAULT_ESTIMATED_HEIGHT;
|
|
848
|
-
useEffect7(() => {
|
|
849
|
-
const id = requestAnimationFrame(() => setReady(true));
|
|
850
|
-
return () => cancelAnimationFrame(id);
|
|
851
|
-
}, []);
|
|
852
|
-
const measureHeight = useCallback4(() => {
|
|
853
|
-
const el = containerRef.current;
|
|
854
|
-
if (!el) return;
|
|
855
|
-
const h = el.getBoundingClientRect().height;
|
|
856
|
-
if (h > 0) {
|
|
857
|
-
componentHeightCache.set(name, h);
|
|
858
|
-
}
|
|
859
|
-
}, [name]);
|
|
860
|
-
useEffect7(() => {
|
|
861
|
-
const el = containerRef.current;
|
|
862
|
-
if (!el || !ready) return;
|
|
863
|
-
measureHeight();
|
|
864
|
-
const ro = new ResizeObserver(() => measureHeight());
|
|
865
|
-
ro.observe(el);
|
|
866
|
-
return () => ro.disconnect();
|
|
867
|
-
}, [ready, measureHeight]);
|
|
868
|
-
if (!ready) {
|
|
869
|
-
return /* @__PURE__ */ jsx8(MdxSkeleton, { height: estimatedHeight });
|
|
870
|
-
}
|
|
871
|
-
return /* @__PURE__ */ jsx8("div", { ref: containerRef, children });
|
|
872
|
-
}
|
|
873
|
-
function wrapWithShell(name, Component) {
|
|
874
|
-
function ShellWrapped(props) {
|
|
875
|
-
return /* @__PURE__ */ jsx8(MdxComponentShell, { name, children: /* @__PURE__ */ jsx8(Component, { ...props }) });
|
|
876
|
-
}
|
|
877
|
-
ShellWrapped.displayName = `MdxShell(${name})`;
|
|
878
|
-
return ShellWrapped;
|
|
879
|
-
}
|
|
880
|
-
function clearComponentHeightCache() {
|
|
881
|
-
componentHeightCache.clear();
|
|
882
|
-
}
|
|
2377
|
+
// src/index.ts
|
|
2378
|
+
import { registerPlugin } from "@owomark/processor";
|
|
883
2379
|
export {
|
|
2380
|
+
AsyncCodeBlock,
|
|
884
2381
|
DEFAULT_EDITOR_CONFIG,
|
|
2382
|
+
DEFAULT_TOOLBAR_COMPONENT_COMMANDS,
|
|
885
2383
|
EditorBlock,
|
|
886
2384
|
EditorDecorator,
|
|
887
2385
|
EditorLeaf,
|
|
@@ -892,15 +2390,21 @@ export {
|
|
|
892
2390
|
SlashMenu,
|
|
893
2391
|
THEME_DARK_CLASS,
|
|
894
2392
|
THEME_LIGHT_CLASS,
|
|
2393
|
+
Toolbar,
|
|
2394
|
+
acquireMdxWorker,
|
|
895
2395
|
clearComponentHeightCache,
|
|
896
2396
|
computeMenuPosition,
|
|
897
2397
|
deriveProcessorOptions,
|
|
898
2398
|
getCaretRect,
|
|
899
2399
|
getThemeClassName2 as getThemeClassName,
|
|
2400
|
+
registerPlugin,
|
|
2401
|
+
releaseMdxWorker,
|
|
900
2402
|
resolveEditorConfig,
|
|
901
2403
|
useBlockContext,
|
|
902
2404
|
useOwoMarkCore,
|
|
903
2405
|
useOwoMarkSharedState,
|
|
2406
|
+
useScrollController,
|
|
2407
|
+
useSharedStateSelector,
|
|
904
2408
|
useSharedStateSnapshot,
|
|
905
2409
|
useVirtualList,
|
|
906
2410
|
wrapWithShell
|