@owomark/view 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/dist/index.js CHANGED
@@ -7,25 +7,27 @@ import {
7
7
  import {
8
8
  FALLBACK_BLOCK_HEIGHT,
9
9
  estimateBlockHeight
10
- } from "./chunk-Y72HQJQI.js";
10
+ } from "./chunk-BPOZMVU7.js";
11
11
  import {
12
- VirtualViewportManager
13
- } from "./chunk-F3LG7AML.js";
14
-
15
- // src/editor.ts
16
- import { createDomAdapter } from "@owomark/core/internal/dom-adapter";
12
+ VirtualViewportManager,
13
+ createSkeletonHtml,
14
+ ensureSkeletonStyles
15
+ } from "./chunk-656BO747.js";
17
16
 
18
17
  // src/view-engine.ts
19
18
  import {
20
- readSelection,
21
- restoreSelection,
22
- invalidateBlockCache,
23
19
  linearToVirtual,
24
20
  virtualToLinear,
25
21
  getBlockStartOffset,
26
22
  tokenizeBlock
27
23
  } from "@owomark/core";
28
- import { createBlockElement, updateBlockElement } from "@owomark/core";
24
+ import {
25
+ readSelection,
26
+ restoreSelection,
27
+ invalidateBlockCache,
28
+ createBlockElement,
29
+ updateBlockElement
30
+ } from "@owomark/core/browser";
29
31
 
30
32
  // src/dom/slash-menu.ts
31
33
  var MENU_GAP = 4;
@@ -633,22 +635,6 @@ function createOwoMarkView(core, element) {
633
635
  engine.mount(element);
634
636
  return engine;
635
637
  }
636
- function createOwoMarkVanillaEditor() {
637
- const adapter = createDomAdapter();
638
- let slashMenu = null;
639
- return {
640
- ...adapter,
641
- mount(element) {
642
- adapter.mount(element);
643
- slashMenu = createSlashMenuOverlay(element, adapter.getCore());
644
- },
645
- destroy() {
646
- slashMenu?.destroy();
647
- slashMenu = null;
648
- adapter.destroy();
649
- }
650
- };
651
- }
652
638
 
653
639
  // src/dom/patcher.ts
654
640
  var BLOCK_ATTR = "data-preview-block";
@@ -792,6 +778,11 @@ var PreviewDomPatcher = class {
792
778
  createBlockWrapper(doc, block) {
793
779
  const wrapper = doc.createElement("div");
794
780
  wrapper.setAttribute(BLOCK_ATTR, block.blockId);
781
+ if ("style" in wrapper && wrapper.style) {
782
+ wrapper.style.display = "flow-root";
783
+ } else {
784
+ wrapper.setAttribute("style", "display: flow-root;");
785
+ }
795
786
  this.updateBlockAttributes(wrapper, block);
796
787
  return wrapper;
797
788
  }
@@ -809,20 +800,13 @@ var PreviewDomPatcher = class {
809
800
  };
810
801
 
811
802
  // src/renderer/registry.ts
812
- function createRendererRegistry() {
813
- const renderers = /* @__PURE__ */ new Map();
814
- return {
815
- get(kind) {
816
- return renderers.get(kind) ?? null;
817
- },
818
- register(kind, renderer) {
819
- renderers.set(kind, renderer);
820
- },
821
- unregister(kind) {
822
- renderers.delete(kind);
823
- }
824
- };
825
- }
803
+ import {
804
+ CARDS_FAMILY_CUSTOM_BLOCK_KEY as CARDS_FAMILY_CUSTOM_BLOCK_KEY2,
805
+ runtimeBlockRegistry
806
+ } from "@owomark/core";
807
+
808
+ // src/renderer/side-annotation-renderer.ts
809
+ import { SIDE_ANNOTATION_RENDERER_KEY_BY_TYPE } from "@owomark/core/semantic/syntax";
826
810
 
827
811
  // src/renderer/default-renderer.ts
828
812
  function escapeHtml(text) {
@@ -832,6 +816,39 @@ var SAFE_URL_PATTERN = /^(?:https?:|mailto:|#|\/)/i;
832
816
  function sanitizeUrl(url) {
833
817
  return SAFE_URL_PATTERN.test(url) ? url : "";
834
818
  }
819
+ function stripCommonBlockquotePrefixes(raw) {
820
+ let lines = raw.split("\n");
821
+ let depth = 0;
822
+ while (lines.length > 0 && lines.every((line) => line.trim() === "" || /^ {0,3}>\s?/.test(line))) {
823
+ lines = lines.map((line) => line.trim() === "" ? line : line.replace(/^ {0,3}>\s?/, ""));
824
+ depth += 1;
825
+ }
826
+ return {
827
+ depth,
828
+ content: lines.join("\n")
829
+ };
830
+ }
831
+ function wrapInBlockquotes(content, depth) {
832
+ let wrapped = content;
833
+ for (let index = 0; index < depth; index += 1) {
834
+ wrapped = `<blockquote>${wrapped}</blockquote>`;
835
+ }
836
+ return wrapped;
837
+ }
838
+ function extractHeadingText(raw) {
839
+ const { depth, content } = stripCommonBlockquotePrefixes(raw);
840
+ const setextMatch = content.match(/^([^\n]+)\n {0,3}(?:=+|-+)\s*$/);
841
+ if (setextMatch) {
842
+ return {
843
+ depth,
844
+ text: setextMatch[1]
845
+ };
846
+ }
847
+ return {
848
+ depth,
849
+ text: content.replace(/^#{1,6}\s*/, "")
850
+ };
851
+ }
835
852
  function renderInline(text) {
836
853
  let html = escapeHtml(text);
837
854
  html = html.replace(/`([^`]+)`/g, "<code>$1</code>");
@@ -852,8 +869,8 @@ function renderBlockDefault(block) {
852
869
  }
853
870
  case "heading": {
854
871
  const level = block.headingLevel ?? 1;
855
- const text = block.raw.replace(/^#{1,6}\s*/, "");
856
- return `<h${level}>${renderInline(text)}</h${level}>`;
872
+ const { depth, text } = extractHeadingText(block.raw);
873
+ return wrapInBlockquotes(`<h${level}>${renderInline(text)}</h${level}>`, depth);
857
874
  }
858
875
  case "code-fence": {
859
876
  const lines = block.raw.split("\n");
@@ -895,6 +912,409 @@ function renderBlockDefault(block) {
895
912
  }
896
913
  }
897
914
 
915
+ // src/renderer/side-annotation-renderer.ts
916
+ function escapeHtml2(text) {
917
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
918
+ }
919
+ function escapeAttribute(text) {
920
+ return escapeHtml2(text).replace(/'/g, "&#39;");
921
+ }
922
+ function svgSegment(className, width, height, body, extraAttrs = "") {
923
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" class="${className}"${extraAttrs}>${body}</svg>`;
924
+ }
925
+ function stretchLineDiv() {
926
+ return '<div class="side-svg-line"></div>';
927
+ }
928
+ function segmented(width, axisPx, segments, extraClass = "") {
929
+ const className = ["side-annotation-svg", "side-svg-segmented", extraClass].filter(Boolean).join(" ");
930
+ return `<div class="${className}" style="width:${width};--side-svg-axis:${axisPx}px">${segments.join("")}</div>`;
931
+ }
932
+ function svgPath(d, extra = "") {
933
+ return `<path d="${d}" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"${extra}></path>`;
934
+ }
935
+ function svgLine(x1, y1, x2, y2, extra = "") {
936
+ return `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"${extra}></line>`;
937
+ }
938
+ function svgPolyline(points, extra = "") {
939
+ return `<polyline points="${points}" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square" stroke-linejoin="miter" shape-rendering="geometricPrecision"${extra}></polyline>`;
940
+ }
941
+ function makeSvgForSideType(sideType, orphan) {
942
+ const rendererKey = SIDE_ANNOTATION_RENDERER_KEY_BY_TYPE[sideType] ?? sideType;
943
+ if (orphan) {
944
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="4" viewBox="0 0 4 100" preserveAspectRatio="none" class="side-annotation-svg side-svg-vline">${svgLine(2, "2", 2, "98", ' vector-effect="non-scaling-stroke"')}</svg>`;
945
+ }
946
+ switch (rendererKey) {
947
+ case "brace":
948
+ return segmented("12px", 6, [
949
+ svgSegment("side-svg-cap", 12, 6, svgPath("M 1 0.75 C 3.5 0.75, 6 2.5, 6 5.25")),
950
+ stretchLineDiv(),
951
+ svgSegment("side-svg-beak", 12, 8, svgPath("M 6 0 C 6 2.5, 11 4, 11 4 C 11 4, 6 5.5, 6 8")),
952
+ stretchLineDiv(),
953
+ svgSegment("side-svg-cap", 12, 6, svgPath("M 6 0.75 C 6 3.5, 3.5 5.25, 1 5.25"))
954
+ ]);
955
+ case "left-brace":
956
+ return segmented("12px", 6, [
957
+ svgSegment("side-svg-cap", 12, 6, svgPath("M 11 0.75 C 8.5 0.75, 6 2.5, 6 5.25")),
958
+ stretchLineDiv(),
959
+ svgSegment("side-svg-beak", 12, 8, svgPath("M 6 0 C 6 2.5, 1 4, 1 4 C 1 4, 6 5.5, 6 8")),
960
+ stretchLineDiv(),
961
+ svgSegment("side-svg-cap", 12, 6, svgPath("M 6 0.75 C 6 3.5, 8.5 5.25, 11 5.25"))
962
+ ]);
963
+ case "bracket":
964
+ return segmented("8px", 4, [
965
+ svgSegment("side-svg-cap", 8, 6, svgPolyline("1,0.75 4,0.75 4,5.25")),
966
+ stretchLineDiv(),
967
+ svgSegment("side-svg-cap", 8, 6, svgPolyline("4,0.75 4,5.25 1,5.25"))
968
+ ], "side-svg-bracket");
969
+ case "left-bracket":
970
+ return segmented("8px", 4, [
971
+ svgSegment("side-svg-cap", 8, 6, svgPolyline("7,0.75 4,0.75 4,5.25")),
972
+ stretchLineDiv(),
973
+ svgSegment("side-svg-cap", 8, 6, svgPolyline("4,0.75 4,5.25 7,5.25"))
974
+ ], "side-svg-left-bracket");
975
+ case "line":
976
+ case "warning":
977
+ case "question":
978
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="4" viewBox="0 0 4 100" preserveAspectRatio="none" class="side-annotation-svg side-svg-vline">${svgLine(2, "2", 2, "98", ' vector-effect="non-scaling-stroke"')}</svg>`;
979
+ case "dash":
980
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="4" viewBox="0 0 20 4" class="side-annotation-svg side-svg-dash">${svgLine(1, "2", 19, "2")}</svg>`;
981
+ case "arrow":
982
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="16" viewBox="0 0 24 16" class="side-annotation-svg side-svg-arrow">${svgPath("M 1 8 L 20 8 M 16 3 L 21 8 L 16 13")}</svg>`;
983
+ case "fat-arrow":
984
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="16" viewBox="0 0 24 16" class="side-annotation-svg side-svg-fat-arrow">${svgPath("M 1 5 L 16 5 M 1 11 L 16 11 M 15 2 L 22 8 L 15 14")}</svg>`;
985
+ case "wave-arrow":
986
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="28" height="16" viewBox="0 0 28 16" class="side-annotation-svg side-svg-wave-arrow">${svgPath("M 1 8 C 4 3, 8 13, 12 8 C 16 3, 20 8, 20 8 M 18 3 L 23 8 L 18 13")}</svg>`;
987
+ case "brace-arrow":
988
+ return segmented("12px", 6, [
989
+ svgSegment("side-svg-cap", 12, 6, svgPath("M 1 0.75 C 3.5 0.75, 6 2.5, 6 5.25")),
990
+ stretchLineDiv(),
991
+ svgSegment("side-svg-beak", 12, 8, `${svgLine(6, "0", 6, "8", ' stroke-linecap="square" shape-rendering="geometricPrecision"')}${svgPath("M 6 4 L 19 4 M 16 1.5 L 20 4 L 16 6.5")}`, ' overflow="visible"'),
992
+ stretchLineDiv(),
993
+ svgSegment("side-svg-cap", 12, 6, svgPath("M 6 0.75 C 6 3.5, 3.5 5.25, 1 5.25"))
994
+ ], "side-svg-compound-arrow");
995
+ case "brace-fat-arrow":
996
+ return segmented("12px", 6, [
997
+ svgSegment("side-svg-cap", 12, 6, svgPath("M 1 0.75 C 3.5 0.75, 6 2.5, 6 5.25")),
998
+ stretchLineDiv(),
999
+ svgSegment("side-svg-beak", 12, 8, `${svgLine(6, "0", 6, "8", ' stroke-linecap="square" shape-rendering="geometricPrecision"')}${svgPath("M 6 2.5 L 14 2.5 M 6 5.5 L 14 5.5 M 13 0.5 L 18 4 L 13 7.5")}`, ' overflow="visible"'),
1000
+ stretchLineDiv(),
1001
+ svgSegment("side-svg-cap", 12, 6, svgPath("M 6 0.75 C 6 3.5, 3.5 5.25, 1 5.25"))
1002
+ ], "side-svg-compound-arrow");
1003
+ case "bracket-arrow":
1004
+ return segmented("8px", 4, [
1005
+ svgSegment("side-svg-cap", 8, 6, svgPolyline("1,0.75 4,0.75 4,5.25")),
1006
+ stretchLineDiv(),
1007
+ svgSegment("side-svg-beak", 8, 8, `${svgLine(4, "0", 4, "8", ' stroke-linecap="square" shape-rendering="geometricPrecision"')}${svgPath("M 4 4 L 17 4 M 14 1.5 L 18 4 L 14 6.5")}`, ' overflow="visible"'),
1008
+ stretchLineDiv(),
1009
+ svgSegment("side-svg-cap", 8, 6, svgPolyline("4,0.75 4,5.25 1,5.25"))
1010
+ ], "side-svg-bracket side-svg-compound-arrow");
1011
+ case "bracket-fat-arrow":
1012
+ return segmented("8px", 4, [
1013
+ svgSegment("side-svg-cap", 8, 6, svgPolyline("1,0.75 4,0.75 4,5.25")),
1014
+ stretchLineDiv(),
1015
+ svgSegment("side-svg-beak", 8, 8, `${svgLine(4, "0", 4, "8", ' stroke-linecap="square" shape-rendering="geometricPrecision"')}${svgPath("M 4 2.5 L 13 2.5 M 4 5.5 L 13 5.5 M 12 0.5 L 17 4 L 12 7.5")}`, ' overflow="visible"'),
1016
+ stretchLineDiv(),
1017
+ svgSegment("side-svg-cap", 8, 6, svgPolyline("4,0.75 4,5.25 1,5.25"))
1018
+ ], "side-svg-bracket side-svg-compound-arrow");
1019
+ case "line-arrow":
1020
+ return segmented("4px", 2, [
1021
+ stretchLineDiv(),
1022
+ svgSegment("side-svg-beak", 4, 8, `${svgLine(2, "0", 2, "8", ' stroke-linecap="square" shape-rendering="geometricPrecision"')}${svgPath("M 2 4 L 15 4 M 12 1.5 L 16 4 L 12 6.5")}`, ' overflow="visible"'),
1023
+ stretchLineDiv()
1024
+ ], "side-svg-compound-arrow");
1025
+ case "line-fat-arrow":
1026
+ return segmented("4px", 2, [
1027
+ stretchLineDiv(),
1028
+ svgSegment("side-svg-beak", 4, 8, `${svgLine(2, "0", 2, "8", ' stroke-linecap="square" shape-rendering="geometricPrecision"')}${svgPath("M 2 2.5 L 11 2.5 M 2 5.5 L 11 5.5 M 10 0.5 L 15 4 L 10 7.5")}`, ' overflow="visible"'),
1029
+ stretchLineDiv()
1030
+ ], "side-svg-compound-arrow");
1031
+ case "brace-warning":
1032
+ case "brace-question":
1033
+ return makeSvgForSideType("brace", false);
1034
+ default:
1035
+ return "";
1036
+ }
1037
+ }
1038
+ function toPreviewKind(type) {
1039
+ switch (type) {
1040
+ case "heading":
1041
+ return "heading";
1042
+ case "unordered-list":
1043
+ return "unordered-list";
1044
+ case "ordered-list":
1045
+ return "ordered-list";
1046
+ case "blockquote":
1047
+ return "blockquote";
1048
+ case "code-fence":
1049
+ return "code-fence";
1050
+ case "thematic-break":
1051
+ return "thematic-break";
1052
+ case "math-block":
1053
+ return "math-block";
1054
+ case "table":
1055
+ return "table";
1056
+ case "html-block":
1057
+ return "html-block";
1058
+ default:
1059
+ return "paragraph";
1060
+ }
1061
+ }
1062
+ function renderMainBlocks(block) {
1063
+ const rawMembers = Array.isArray(block.attributes?.mainBlocks) ? block.attributes?.mainBlocks : [];
1064
+ return rawMembers.map((entry, index) => {
1065
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
1066
+ return "";
1067
+ }
1068
+ const type = typeof entry.type === "string" ? entry.type : "paragraph";
1069
+ const raw = typeof entry.raw === "string" ? entry.raw : "";
1070
+ const memberBlock = {
1071
+ blockId: `${block.blockId}:member:${index}`,
1072
+ kind: toPreviewKind(type),
1073
+ raw,
1074
+ startLine: block.startLine,
1075
+ endLine: block.endLine,
1076
+ renderKey: `${block.renderKey}:member:${index}`
1077
+ };
1078
+ if (type === "heading" && typeof entry.headingLevel === "number") {
1079
+ memberBlock.headingLevel = entry.headingLevel;
1080
+ }
1081
+ if (type === "code-fence" && typeof entry.language === "string") {
1082
+ memberBlock.language = entry.language;
1083
+ }
1084
+ return renderBlockDefault(memberBlock);
1085
+ }).join("");
1086
+ }
1087
+ function isSideAnnotationPreviewBlock(block) {
1088
+ return block.kind === "custom" && block.attributes?.customBlockKey === "side-annotation";
1089
+ }
1090
+ function renderSideAnnotationPreviewBlock(block) {
1091
+ if (!isSideAnnotationPreviewBlock(block)) {
1092
+ return `<div>${escapeHtml2(block.raw)}</div>`;
1093
+ }
1094
+ const sideType = typeof block.attributes?.sideType === "string" ? block.attributes.sideType : "plain";
1095
+ const annotationText = typeof block.attributes?.annotationText === "string" ? block.attributes.annotationText : "";
1096
+ const orphan = Boolean(block.attributes?.orphan);
1097
+ const className = ["side-annotation", `side-type-${sideType}`, orphan ? "side-orphan" : ""].filter(Boolean).join(" ");
1098
+ const asideClass = ["side-annotation-aside", orphan ? "side-annotation-orphan" : ""].filter(Boolean).join(" ");
1099
+ const decoration = makeSvgForSideType(sideType, orphan);
1100
+ const mainHtml = renderMainBlocks(block);
1101
+ const textHtml = annotationText ? `<span class="side-annotation-text">${escapeHtml2(annotationText)}</span>` : "";
1102
+ return [
1103
+ `<div class="${className}" data-side-type="${escapeAttribute(sideType)}"${orphan ? ' data-side-orphan="true"' : ""}${annotationText ? ` data-side-text="${escapeAttribute(annotationText)}"` : ""}>`,
1104
+ `<div class="side-annotation-main">${mainHtml}</div>`,
1105
+ `<div class="${asideClass}">${decoration}${textHtml}</div>`,
1106
+ "</div>"
1107
+ ].join("");
1108
+ }
1109
+
1110
+ // src/renderer/cards-renderer.ts
1111
+ import {
1112
+ CARDS_FAMILY_CUSTOM_BLOCK_KEY,
1113
+ isCardsFamilyAttributes,
1114
+ parseMarkdownToDocument
1115
+ } from "@owomark/core";
1116
+ function escapeHtml3(text) {
1117
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1118
+ }
1119
+ function isCardsFamilyPreviewBlock(block) {
1120
+ return block.kind === "custom" && block.attributes?.customBlockKey === CARDS_FAMILY_CUSTOM_BLOCK_KEY && isCardsFamilyAttributes(block.attributes);
1121
+ }
1122
+ function renderCardBody(markdown) {
1123
+ if (!markdown.trim()) {
1124
+ return "";
1125
+ }
1126
+ const nestedBlocks = parseMarkdownToDocument(markdown).previewBlocks;
1127
+ return nestedBlocks.map((nestedBlock) => {
1128
+ if (isCardsFamilyPreviewBlock(nestedBlock)) {
1129
+ return renderCardsFamilyPreviewBlock(nestedBlock);
1130
+ }
1131
+ if (nestedBlock.kind === "custom" && nestedBlock.attributes?.customBlockKey === "side-annotation") {
1132
+ return renderSideAnnotationPreviewBlock(nestedBlock);
1133
+ }
1134
+ return renderBlockDefault(nestedBlock);
1135
+ }).join("");
1136
+ }
1137
+ function renderCardsFamilyPreviewBlock(block) {
1138
+ if (!isCardsFamilyPreviewBlock(block)) {
1139
+ throw new Error("[owomark/view] cards renderer requires a cards-family preview block");
1140
+ }
1141
+ const attributes = block.attributes;
1142
+ const cardsHtml = attributes.cards.map((card) => {
1143
+ const toneAttr = card.tone ? ` data-owo-card-tone="${card.tone}"` : "";
1144
+ const toneClass = card.tone ? ` owo-card-tone-${card.tone}` : "";
1145
+ const titleHtml = card.title ? `<header class="owo-card-title">${escapeHtml3(card.title)}</header>` : "";
1146
+ const bodyHtml = renderCardBody(card.bodyMarkdown);
1147
+ return [
1148
+ `<article class="owo-card${toneClass}" data-owo-card${toneAttr}>`,
1149
+ titleHtml,
1150
+ `<div class="owo-card-body">${bodyHtml}</div>`,
1151
+ "</article>"
1152
+ ].join("");
1153
+ }).join("");
1154
+ return [
1155
+ `<section class="owo-cards" data-owo-cards data-owo-cards-cols="${attributes.layout.cols}">`,
1156
+ cardsHtml,
1157
+ "</section>"
1158
+ ].join("");
1159
+ }
1160
+
1161
+ // src/renderer/registry.ts
1162
+ function normalizeTarget(target) {
1163
+ const normalized = {
1164
+ kind: target.kind,
1165
+ customBlockKey: target.customBlockKey ?? null
1166
+ };
1167
+ if (normalized.kind === "custom" && !normalized.customBlockKey) {
1168
+ throw new Error("[owomark/view] custom renderer targets must declare customBlockKey");
1169
+ }
1170
+ return normalized;
1171
+ }
1172
+ function resolveTargetFromLookup(lookup) {
1173
+ if ("blockId" in lookup) {
1174
+ const block = lookup;
1175
+ if (block.kind === "custom") {
1176
+ const customBlockKey = typeof block.attributes?.customBlockKey === "string" ? block.attributes.customBlockKey : null;
1177
+ if (!customBlockKey) {
1178
+ return {
1179
+ kind: "custom",
1180
+ customBlockKey: null
1181
+ };
1182
+ }
1183
+ return {
1184
+ kind: "custom",
1185
+ customBlockKey
1186
+ };
1187
+ }
1188
+ return {
1189
+ kind: block.kind
1190
+ };
1191
+ }
1192
+ return normalizeTarget(lookup);
1193
+ }
1194
+ function toSpecificKey(target) {
1195
+ return target.kind === "custom" ? `custom:${target.customBlockKey}` : `kind:${target.kind}`;
1196
+ }
1197
+ function createRendererRegistry() {
1198
+ const renderers = /* @__PURE__ */ new Map();
1199
+ const metadata = /* @__PURE__ */ new Map();
1200
+ const targets = /* @__PURE__ */ new Map();
1201
+ for (const descriptor of runtimeBlockRegistry.list()) {
1202
+ const target = {
1203
+ kind: descriptor.previewKind,
1204
+ customBlockKey: descriptor.customBlockKey ?? null
1205
+ };
1206
+ const key = toSpecificKey(target);
1207
+ metadata.set(key, {
1208
+ mode: descriptor.rendererMode,
1209
+ priority: descriptor.rendererPriority,
1210
+ heavy: descriptor.heavy
1211
+ });
1212
+ targets.set(key, target);
1213
+ }
1214
+ renderers.set("custom:side-annotation", {
1215
+ mode: metadata.get("custom:side-annotation")?.mode ?? "html-worker-safe",
1216
+ priority: metadata.get("custom:side-annotation")?.priority ?? "realtime",
1217
+ version: "builtin-side-annotation-v1",
1218
+ workerRendererId: "builtin:side-annotation",
1219
+ render(block) {
1220
+ return {
1221
+ kind: "html",
1222
+ html: renderSideAnnotationPreviewBlock(block)
1223
+ };
1224
+ }
1225
+ });
1226
+ targets.set("custom:side-annotation", {
1227
+ kind: "custom",
1228
+ customBlockKey: "side-annotation"
1229
+ });
1230
+ renderers.set(`custom:${CARDS_FAMILY_CUSTOM_BLOCK_KEY2}`, {
1231
+ mode: "html-worker-safe",
1232
+ priority: "realtime",
1233
+ version: "builtin-cards-family-v1",
1234
+ render(block) {
1235
+ return {
1236
+ kind: "html",
1237
+ html: renderCardsFamilyPreviewBlock(block)
1238
+ };
1239
+ }
1240
+ });
1241
+ metadata.set(`custom:${CARDS_FAMILY_CUSTOM_BLOCK_KEY2}`, {
1242
+ mode: "html-worker-safe",
1243
+ priority: "realtime"
1244
+ });
1245
+ targets.set(`custom:${CARDS_FAMILY_CUSTOM_BLOCK_KEY2}`, {
1246
+ kind: "custom",
1247
+ customBlockKey: CARDS_FAMILY_CUSTOM_BLOCK_KEY2
1248
+ });
1249
+ function getMetadata(target) {
1250
+ if (target.kind === "custom" && !target.customBlockKey) {
1251
+ return null;
1252
+ }
1253
+ const specificKey = toSpecificKey(target);
1254
+ return metadata.get(specificKey) ?? null;
1255
+ }
1256
+ function getRenderer(target) {
1257
+ if (target.kind === "custom" && !target.customBlockKey) {
1258
+ return null;
1259
+ }
1260
+ const specificKey = toSpecificKey(target);
1261
+ return renderers.get(specificKey) ?? null;
1262
+ }
1263
+ return {
1264
+ get(lookup) {
1265
+ return getRenderer(resolveTargetFromLookup(lookup));
1266
+ },
1267
+ register(targetLike, renderer) {
1268
+ const target = normalizeTarget(targetLike);
1269
+ const key = toSpecificKey(target);
1270
+ renderers.set(key, renderer);
1271
+ targets.set(key, target);
1272
+ if (!metadata.has(key)) {
1273
+ metadata.set(key, {
1274
+ mode: renderer.mode,
1275
+ priority: renderer.priority
1276
+ });
1277
+ }
1278
+ },
1279
+ unregister(targetLike) {
1280
+ const key = toSpecificKey(normalizeTarget(targetLike));
1281
+ renderers.delete(key);
1282
+ },
1283
+ registerMetadata(targetLike, meta) {
1284
+ const target = normalizeTarget(targetLike);
1285
+ const key = toSpecificKey(target);
1286
+ metadata.set(key, meta);
1287
+ targets.set(key, target);
1288
+ },
1289
+ isHeavy(lookup) {
1290
+ const target = resolveTargetFromLookup(lookup);
1291
+ const meta = getMetadata(target);
1292
+ if (meta?.heavy) return true;
1293
+ const def = getRenderer(target);
1294
+ return def?.priority === "deferred" || false;
1295
+ },
1296
+ getMode(lookup) {
1297
+ const target = resolveTargetFromLookup(lookup);
1298
+ const def = getRenderer(target);
1299
+ if (def) return def.mode;
1300
+ return getMetadata(target)?.mode ?? null;
1301
+ },
1302
+ getPriority(lookup) {
1303
+ const target = resolveTargetFromLookup(lookup);
1304
+ const def = getRenderer(target);
1305
+ if (def) return def.priority;
1306
+ return getMetadata(target)?.priority ?? null;
1307
+ },
1308
+ listRegistered() {
1309
+ const keys = /* @__PURE__ */ new Set([
1310
+ ...renderers.keys(),
1311
+ ...metadata.keys()
1312
+ ]);
1313
+ return Array.from(keys).map((key) => targets.get(key)).filter((target) => target != null);
1314
+ }
1315
+ };
1316
+ }
1317
+
898
1318
  // src/strategies/shared.ts
899
1319
  var scheduleIdle = typeof globalThis.requestIdleCallback === "function" ? (fn) => globalThis.requestIdleCallback(fn) : (fn) => setTimeout(fn, 1);
900
1320
  var cancelIdle = typeof globalThis.cancelIdleCallback === "function" ? (id) => globalThis.cancelIdleCallback(id) : (id) => clearTimeout(id);
@@ -925,17 +1345,30 @@ function cancelAllIdle(pendingIdleIds) {
925
1345
  pendingIdleIds.length = 0;
926
1346
  }
927
1347
  function isDeferred(block, registry) {
928
- const def = registry.get(block.kind);
1348
+ const def = registry.get(block);
929
1349
  return def?.priority === "deferred";
930
1350
  }
931
- function createRenderBlockFull(registry, renderCache, externalRenderBlock) {
1351
+ function createRenderBlockFull(registry, renderCache, externalRenderBlock, scheduler) {
932
1352
  return async function renderBlockFull(block, baseContext) {
933
1353
  const context = {
934
1354
  ...baseContext,
935
1355
  sourceLineOffset: block.startLine - 1
936
1356
  };
937
- const customRenderer = registry.get(block.kind);
1357
+ if (externalRenderBlock && block.kind === "custom") {
1358
+ const html2 = await externalRenderBlock(block, context);
1359
+ renderCache.set(block.renderKey, html2);
1360
+ return { kind: "html", html: html2 };
1361
+ }
1362
+ const customRenderer = registry.get(block);
938
1363
  if (customRenderer) {
1364
+ if (customRenderer.mode === "html-worker-safe" && customRenderer.workerRendererId && scheduler) {
1365
+ try {
1366
+ const html2 = await scheduler.submitWorkerTask(block, customRenderer, context);
1367
+ renderCache.set(block.renderKey, html2);
1368
+ return { kind: "html", html: html2 };
1369
+ } catch {
1370
+ }
1371
+ }
939
1372
  const result = await customRenderer.render(block, context);
940
1373
  if (result.kind === "html") {
941
1374
  renderCache.set(block.renderKey, result.html);
@@ -962,6 +1395,7 @@ function createIncrementalEngine(options) {
962
1395
  const themeKey = options?.themeKey ?? "";
963
1396
  const viewportFirst = options?.viewportFirst ?? false;
964
1397
  const registry = options?.registry ?? createRendererRegistry();
1398
+ const scheduler = options?.scheduler;
965
1399
  const externalRenderBlock = options?.renderBlock;
966
1400
  const onContentUpdate = options?.onContentUpdate;
967
1401
  const patcher = new PreviewDomPatcher();
@@ -972,7 +1406,7 @@ function createIncrementalEngine(options) {
972
1406
  let pendingAbort = null;
973
1407
  let pendingIdleIds = [];
974
1408
  let pendingBackfill = /* @__PURE__ */ new Set();
975
- const renderBlockFull = createRenderBlockFull(registry, renderCache, externalRenderBlock);
1409
+ const renderBlockFull = createRenderBlockFull(registry, renderCache, externalRenderBlock, scheduler);
976
1410
  function applyResult(blockId, result) {
977
1411
  if (result.kind === "html") {
978
1412
  patcher.patchBlockHtml(blockId, result.html);
@@ -1018,12 +1452,14 @@ function createIncrementalEngine(options) {
1018
1452
  return {
1019
1453
  mount(root) {
1020
1454
  patcher.mount(root);
1455
+ ensureSkeletonStyles(root.ownerDocument);
1021
1456
  mounted = true;
1022
1457
  },
1023
1458
  destroy() {
1024
1459
  pendingAbort?.abort();
1025
1460
  pendingAbort = null;
1026
1461
  cancelAllIdle(pendingIdleIds);
1462
+ scheduler?.release();
1027
1463
  patcher.destroy();
1028
1464
  renderCache.clear();
1029
1465
  pendingBackfill.clear();
@@ -1101,18 +1537,26 @@ function createIncrementalEngine(options) {
1101
1537
  }
1102
1538
  }
1103
1539
  if (signal.aborted) return;
1540
+ pendingBackfill = /* @__PURE__ */ new Set();
1541
+ for (const block of realtimeOffscreen) {
1542
+ const height = estimateBlockHeight(block, registry);
1543
+ htmlMap.set(block.blockId, createSkeletonHtml({ height }));
1544
+ pendingBackfill.add(block.blockId);
1545
+ }
1546
+ for (const block of deferredAll) {
1547
+ const height = estimateBlockHeight(block, registry);
1548
+ htmlMap.set(block.blockId, createSkeletonHtml({ height }));
1549
+ pendingBackfill.add(block.blockId);
1550
+ }
1104
1551
  patcher.fullRender(blocks, htmlMap);
1105
1552
  for (const { blockId, result } of domMounts) {
1106
1553
  if (signal.aborted) return;
1107
1554
  applyResult(blockId, result);
1108
1555
  }
1109
- pendingBackfill = /* @__PURE__ */ new Set();
1110
1556
  for (const block of realtimeOffscreen) {
1111
- pendingBackfill.add(block.blockId);
1112
1557
  scheduleBlockRender(block, baseContext, signal, state.version);
1113
1558
  }
1114
1559
  for (const block of deferredAll) {
1115
- pendingBackfill.add(block.blockId);
1116
1560
  scheduleBlockRender(block, baseContext, signal, state.version);
1117
1561
  }
1118
1562
  }
@@ -1229,6 +1673,7 @@ var OffscreenMeasurer = class {
1229
1673
  function createVirtualEngine(options) {
1230
1674
  const themeKey = options?.themeKey ?? "";
1231
1675
  const registry = options?.registry ?? createRendererRegistry();
1676
+ const scheduler = options?.scheduler;
1232
1677
  const externalRenderBlock = options?.renderBlock;
1233
1678
  const onContentUpdate = options?.onContentUpdate;
1234
1679
  const layoutMap = new BlockLayoutMap();
@@ -1236,7 +1681,7 @@ function createVirtualEngine(options) {
1236
1681
  const measurer = new OffscreenMeasurer();
1237
1682
  const viewport = new VirtualViewportManager();
1238
1683
  const renderCache = /* @__PURE__ */ new Map();
1239
- const renderBlockFull = createRenderBlockFull(registry, renderCache, externalRenderBlock);
1684
+ const renderBlockFull = createRenderBlockFull(registry, renderCache, externalRenderBlock, scheduler);
1240
1685
  let renderedVersion = 0;
1241
1686
  let lastBlocks = [];
1242
1687
  let blockMap = /* @__PURE__ */ new Map();
@@ -1253,7 +1698,7 @@ function createVirtualEngine(options) {
1253
1698
  function getInitialHeight(block) {
1254
1699
  const cached = heightCache.get(block.renderKey);
1255
1700
  if (cached !== void 0) return cached;
1256
- return estimateBlockHeight(block);
1701
+ return estimateBlockHeight(block, registry);
1257
1702
  }
1258
1703
  async function renderAndMountBlock(block, baseContext, signal) {
1259
1704
  if (signal.aborted) return;
@@ -1365,7 +1810,7 @@ function createVirtualEngine(options) {
1365
1810
  heightCache.clear();
1366
1811
  layoutMap.invalidateAll((blockId) => {
1367
1812
  const block = blockMap.get(blockId);
1368
- return block ? estimateBlockHeight(block) : FALLBACK_BLOCK_HEIGHT;
1813
+ return block ? estimateBlockHeight(block, registry) : FALLBACK_BLOCK_HEIGHT;
1369
1814
  });
1370
1815
  return true;
1371
1816
  }
@@ -1400,6 +1845,7 @@ function createVirtualEngine(options) {
1400
1845
  blockResizeObserver?.disconnect();
1401
1846
  blockResizeObserver = null;
1402
1847
  observedWrappers.clear();
1848
+ scheduler?.release();
1403
1849
  viewport.destroy();
1404
1850
  measurer.destroy();
1405
1851
  renderCache.clear();
@@ -1525,11 +1971,186 @@ function createOwoMarkPreviewEngine(options) {
1525
1971
  return createIncrementalEngine(options);
1526
1972
  case "virtual":
1527
1973
  return createVirtualEngine(options);
1974
+ case "mdx":
1975
+ throw new Error(
1976
+ 'The "mdx" strategy is implemented by @owomark/react OwoMarkPreview, not by @owomark/view createOwoMarkPreviewEngine().'
1977
+ );
1528
1978
  default:
1529
1979
  throw new Error(`Unknown preview strategy: ${strategy}`);
1530
1980
  }
1531
1981
  }
1532
1982
 
1983
+ // src/worker/preview-task-scheduler.ts
1984
+ var MAX_CONSECUTIVE_CRASHES = 3;
1985
+ var CRASH_WINDOW_MS = 3e4;
1986
+ function serializeBlock(block) {
1987
+ return {
1988
+ kind: block.kind,
1989
+ raw: block.raw,
1990
+ blockId: block.blockId,
1991
+ renderKey: block.renderKey,
1992
+ startLine: block.startLine,
1993
+ endLine: block.endLine
1994
+ };
1995
+ }
1996
+ function createPreviewTaskScheduler(options) {
1997
+ const poolSize = options?.poolSize ?? Math.max(1, (typeof navigator !== "undefined" ? navigator.hardwareConcurrency ?? 4 : 4) - 1);
1998
+ let refCount = 1;
1999
+ let taskCounter = 0;
2000
+ let permanentlyFailed = false;
2001
+ const crashTimestamps = [];
2002
+ const pendingTasks = /* @__PURE__ */ new Map();
2003
+ const workers = [];
2004
+ let nextWorkerIndex = 0;
2005
+ function createWorkerSlot() {
2006
+ try {
2007
+ if (typeof Worker === "undefined") return null;
2008
+ const worker = new Worker(
2009
+ new URL("./preview-render.worker.js", import.meta.url),
2010
+ { type: "module" }
2011
+ );
2012
+ const slot = { worker, pendingCount: 0, dead: false };
2013
+ worker.onmessage = (e) => {
2014
+ const resp = e.data;
2015
+ slot.pendingCount--;
2016
+ const pending = pendingTasks.get(resp.taskId);
2017
+ if (!pending) return;
2018
+ pendingTasks.delete(resp.taskId);
2019
+ if (resp.ok) {
2020
+ pending.resolve(resp.html);
2021
+ } else {
2022
+ pending.reject(new Error(resp.error));
2023
+ }
2024
+ };
2025
+ worker.onerror = () => {
2026
+ slot.dead = true;
2027
+ slot.pendingCount = 0;
2028
+ worker.terminate();
2029
+ recordCrash();
2030
+ options?.onCrash?.();
2031
+ };
2032
+ return slot;
2033
+ } catch {
2034
+ return null;
2035
+ }
2036
+ }
2037
+ function recordCrash() {
2038
+ const now = Date.now();
2039
+ crashTimestamps.push(now);
2040
+ while (crashTimestamps.length > 0 && now - crashTimestamps[0] > CRASH_WINDOW_MS) {
2041
+ crashTimestamps.shift();
2042
+ }
2043
+ if (crashTimestamps.length >= MAX_CONSECUTIVE_CRASHES) {
2044
+ permanentlyFailed = true;
2045
+ terminateAll();
2046
+ }
2047
+ }
2048
+ function getLeastBusySlot() {
2049
+ if (permanentlyFailed) return null;
2050
+ let best = null;
2051
+ for (const slot of workers) {
2052
+ if (slot.dead) continue;
2053
+ if (!best || slot.pendingCount < best.pendingCount) {
2054
+ best = slot;
2055
+ }
2056
+ }
2057
+ if (best) return best;
2058
+ if (workers.length < poolSize) {
2059
+ const slot = createWorkerSlot();
2060
+ if (slot) {
2061
+ workers.push(slot);
2062
+ return slot;
2063
+ }
2064
+ }
2065
+ for (let i = 0; i < workers.length; i++) {
2066
+ if (workers[i].dead) {
2067
+ const slot = createWorkerSlot();
2068
+ if (slot) {
2069
+ workers[i] = slot;
2070
+ return slot;
2071
+ }
2072
+ }
2073
+ }
2074
+ return null;
2075
+ }
2076
+ function terminateAll() {
2077
+ for (const slot of workers) {
2078
+ if (!slot.dead) {
2079
+ slot.dead = true;
2080
+ slot.worker.terminate();
2081
+ }
2082
+ }
2083
+ workers.length = 0;
2084
+ for (const [, pending] of pendingTasks) {
2085
+ pending.reject(new Error("Scheduler terminated"));
2086
+ }
2087
+ pendingTasks.clear();
2088
+ }
2089
+ const scheduler = {
2090
+ submitWorkerTask(block, rendererDef, context) {
2091
+ const rendererId = rendererDef.workerRendererId;
2092
+ if (permanentlyFailed || !rendererId) {
2093
+ return Promise.reject(new Error("Worker unavailable"));
2094
+ }
2095
+ const slot = getLeastBusySlot();
2096
+ if (!slot) {
2097
+ return Promise.reject(new Error("No worker available"));
2098
+ }
2099
+ const taskId = ++taskCounter;
2100
+ return new Promise((resolve, reject) => {
2101
+ pendingTasks.set(taskId, { resolve, reject });
2102
+ slot.pendingCount++;
2103
+ slot.worker.postMessage({
2104
+ type: "render",
2105
+ taskId,
2106
+ block: serializeBlock(block),
2107
+ rendererId,
2108
+ context: {
2109
+ version: context.version,
2110
+ themeKey: context.themeKey,
2111
+ sourceLineOffset: context.sourceLineOffset
2112
+ }
2113
+ });
2114
+ });
2115
+ },
2116
+ cancel(taskId) {
2117
+ const pending = pendingTasks.get(taskId);
2118
+ if (pending) {
2119
+ pendingTasks.delete(taskId);
2120
+ pending.reject(new Error("Task cancelled"));
2121
+ }
2122
+ const msg = { type: "cancel", taskId };
2123
+ for (const slot of workers) {
2124
+ if (!slot.dead) slot.worker.postMessage(msg);
2125
+ }
2126
+ },
2127
+ cancelAll() {
2128
+ for (const [, pending] of pendingTasks) {
2129
+ pending.reject(new Error("All tasks cancelled"));
2130
+ }
2131
+ pendingTasks.clear();
2132
+ const msg = { type: "cancel-all" };
2133
+ for (const slot of workers) {
2134
+ if (!slot.dead) {
2135
+ slot.pendingCount = 0;
2136
+ slot.worker.postMessage(msg);
2137
+ }
2138
+ }
2139
+ },
2140
+ acquire() {
2141
+ refCount++;
2142
+ return scheduler;
2143
+ },
2144
+ release() {
2145
+ if (--refCount <= 0) {
2146
+ refCount = 0;
2147
+ terminateAll();
2148
+ }
2149
+ }
2150
+ };
2151
+ return scheduler;
2152
+ }
2153
+
1533
2154
  // src/dom/side-annotation-positioner.ts
1534
2155
  var SideAnnotationPositioner = class {
1535
2156
  container;
@@ -1639,10 +2260,16 @@ export {
1639
2260
  THEME_DARK_CLASS,
1640
2261
  THEME_LIGHT_CLASS,
1641
2262
  createOwoMarkPreviewEngine,
1642
- createOwoMarkVanillaEditor,
1643
2263
  createOwoMarkView,
2264
+ createPreviewTaskScheduler,
1644
2265
  createRendererRegistry,
2266
+ createSkeletonHtml,
1645
2267
  createViewEngine,
2268
+ ensureSkeletonStyles,
1646
2269
  getThemeClassName,
1647
- renderBlockDefault
2270
+ isCardsFamilyPreviewBlock,
2271
+ isSideAnnotationPreviewBlock,
2272
+ renderBlockDefault,
2273
+ renderCardsFamilyPreviewBlock,
2274
+ renderSideAnnotationPreviewBlock
1648
2275
  };