@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.
@@ -1,13 +1,6 @@
1
1
  import { PreviewBlock } from '@owomark/core';
2
+ import { P as PreviewRendererRegistry } from '../../types-D4TVpyP7.js';
2
3
 
3
- /**
4
- * Heuristic height estimation for blocks before measurement.
5
- *
6
- * Uses block kind and source line count to produce a rough height estimate.
7
- * These estimates are used as initial placeholder heights in the coordinate
8
- * table until real measurement completes.
9
- */
10
-
11
- declare function estimateBlockHeight(block: PreviewBlock): number;
4
+ declare function estimateBlockHeight(block: PreviewBlock, registry?: PreviewRendererRegistry): number;
12
5
 
13
6
  export { estimateBlockHeight };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  estimateBlockHeight
3
- } from "../../chunk-Y72HQJQI.js";
3
+ } from "../../chunk-BPOZMVU7.js";
4
4
  export {
5
5
  estimateBlockHeight
6
6
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VirtualViewportManager
3
- } from "../../chunk-F3LG7AML.js";
3
+ } from "../../chunk-656BO747.js";
4
4
  export {
5
5
  VirtualViewportManager
6
6
  };
@@ -0,0 +1,369 @@
1
+ // src/worker/renderer-registry.ts
2
+ import { runtimeBlockRegistry } from "@owomark/core";
3
+
4
+ // src/renderer/side-annotation-renderer.ts
5
+ import { SIDE_ANNOTATION_RENDERER_KEY_BY_TYPE } from "@owomark/core/semantic/syntax";
6
+
7
+ // src/renderer/default-renderer.ts
8
+ function escapeHtml(text) {
9
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
10
+ }
11
+ var SAFE_URL_PATTERN = /^(?:https?:|mailto:|#|\/)/i;
12
+ function sanitizeUrl(url) {
13
+ return SAFE_URL_PATTERN.test(url) ? url : "";
14
+ }
15
+ function stripCommonBlockquotePrefixes(raw) {
16
+ let lines = raw.split("\n");
17
+ let depth = 0;
18
+ while (lines.length > 0 && lines.every((line) => line.trim() === "" || /^ {0,3}>\s?/.test(line))) {
19
+ lines = lines.map((line) => line.trim() === "" ? line : line.replace(/^ {0,3}>\s?/, ""));
20
+ depth += 1;
21
+ }
22
+ return {
23
+ depth,
24
+ content: lines.join("\n")
25
+ };
26
+ }
27
+ function wrapInBlockquotes(content, depth) {
28
+ let wrapped = content;
29
+ for (let index = 0; index < depth; index += 1) {
30
+ wrapped = `<blockquote>${wrapped}</blockquote>`;
31
+ }
32
+ return wrapped;
33
+ }
34
+ function extractHeadingText(raw) {
35
+ const { depth, content } = stripCommonBlockquotePrefixes(raw);
36
+ const setextMatch = content.match(/^([^\n]+)\n {0,3}(?:=+|-+)\s*$/);
37
+ if (setextMatch) {
38
+ return {
39
+ depth,
40
+ text: setextMatch[1]
41
+ };
42
+ }
43
+ return {
44
+ depth,
45
+ text: content.replace(/^#{1,6}\s*/, "")
46
+ };
47
+ }
48
+ function renderInline(text) {
49
+ let html = escapeHtml(text);
50
+ html = html.replace(/`([^`]+)`/g, "<code>$1</code>");
51
+ html = html.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
52
+ html = html.replace(/__(.+?)__/g, "<strong>$1</strong>");
53
+ html = html.replace(/\*(.+?)\*/g, "<em>$1</em>");
54
+ html = html.replace(/_(.+?)_/g, "<em>$1</em>");
55
+ html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_, alt, src) => `<img src="${sanitizeUrl(src)}" alt="${alt}" />`);
56
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, label, href) => `<a href="${sanitizeUrl(href)}">${label}</a>`);
57
+ return html;
58
+ }
59
+ function renderBlockDefault(block) {
60
+ switch (block.kind) {
61
+ case "paragraph": {
62
+ const content = renderInline(block.raw);
63
+ if (block.raw.trim() === "") return "";
64
+ return `<p>${content}</p>`;
65
+ }
66
+ case "heading": {
67
+ const level = block.headingLevel ?? 1;
68
+ const { depth, text } = extractHeadingText(block.raw);
69
+ return wrapInBlockquotes(`<h${level}>${renderInline(text)}</h${level}>`, depth);
70
+ }
71
+ case "code-fence": {
72
+ const lines = block.raw.split("\n");
73
+ const openMatch = lines[0]?.match(/^[`~]{3,}(.*)/);
74
+ const lang = openMatch?.[1]?.trim().split(/\s+/)[0] || "";
75
+ const closeFenceIdx = lines.length - 1;
76
+ const hasCloseFence = closeFenceIdx > 0 && /^[`~]{3,}\s*$/.test(lines[closeFenceIdx]);
77
+ const codeLines = lines.slice(1, hasCloseFence ? closeFenceIdx : void 0);
78
+ const code = escapeHtml(codeLines.join("\n"));
79
+ const langAttr = lang ? ` class="language-${lang}"` : "";
80
+ return `<pre data-source-line-start="${block.startLine}" data-source-line-end="${block.endLine}" data-block-id="${block.blockId}"><code${langAttr}>${code}</code></pre>`;
81
+ }
82
+ case "unordered-list": {
83
+ const items = block.raw.split("\n").filter((line) => line.trim()).map((line) => {
84
+ const text = line.replace(/^\s*[-*+]\s+/, "");
85
+ return `<li>${renderInline(text)}</li>`;
86
+ });
87
+ return `<ul>${items.join("")}</ul>`;
88
+ }
89
+ case "ordered-list": {
90
+ const items = block.raw.split("\n").filter((line) => line.trim()).map((line) => {
91
+ const text = line.replace(/^\s*\d+\.\s+/, "");
92
+ return `<li>${renderInline(text)}</li>`;
93
+ });
94
+ return `<ol>${items.join("")}</ol>`;
95
+ }
96
+ case "blockquote": {
97
+ const lines = block.raw.split("\n").map((line) => line.replace(/^\s*>\s?/, ""));
98
+ const content = lines.map((line) => `<p>${renderInline(line)}</p>`).join("");
99
+ return `<blockquote>${content}</blockquote>`;
100
+ }
101
+ case "thematic-break":
102
+ return "<hr />";
103
+ case "table":
104
+ case "math-block":
105
+ case "html-block":
106
+ case "custom":
107
+ return `<div>${escapeHtml(block.raw)}</div>`;
108
+ }
109
+ }
110
+
111
+ // src/renderer/side-annotation-renderer.ts
112
+ function escapeHtml2(text) {
113
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
114
+ }
115
+ function escapeAttribute(text) {
116
+ return escapeHtml2(text).replace(/'/g, "&#39;");
117
+ }
118
+ function svgSegment(className, width, height, body, extraAttrs = "") {
119
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" class="${className}"${extraAttrs}>${body}</svg>`;
120
+ }
121
+ function stretchLineDiv() {
122
+ return '<div class="side-svg-line"></div>';
123
+ }
124
+ function segmented(width, axisPx, segments, extraClass = "") {
125
+ const className = ["side-annotation-svg", "side-svg-segmented", extraClass].filter(Boolean).join(" ");
126
+ return `<div class="${className}" style="width:${width};--side-svg-axis:${axisPx}px">${segments.join("")}</div>`;
127
+ }
128
+ function svgPath(d, extra = "") {
129
+ return `<path d="${d}" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"${extra}></path>`;
130
+ }
131
+ function svgLine(x1, y1, x2, y2, extra = "") {
132
+ return `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"${extra}></line>`;
133
+ }
134
+ function svgPolyline(points, extra = "") {
135
+ return `<polyline points="${points}" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square" stroke-linejoin="miter" shape-rendering="geometricPrecision"${extra}></polyline>`;
136
+ }
137
+ function makeSvgForSideType(sideType, orphan) {
138
+ const rendererKey = SIDE_ANNOTATION_RENDERER_KEY_BY_TYPE[sideType] ?? sideType;
139
+ if (orphan) {
140
+ 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>`;
141
+ }
142
+ switch (rendererKey) {
143
+ case "brace":
144
+ return segmented("12px", 6, [
145
+ svgSegment("side-svg-cap", 12, 6, svgPath("M 1 0.75 C 3.5 0.75, 6 2.5, 6 5.25")),
146
+ stretchLineDiv(),
147
+ 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")),
148
+ stretchLineDiv(),
149
+ svgSegment("side-svg-cap", 12, 6, svgPath("M 6 0.75 C 6 3.5, 3.5 5.25, 1 5.25"))
150
+ ]);
151
+ case "left-brace":
152
+ return segmented("12px", 6, [
153
+ svgSegment("side-svg-cap", 12, 6, svgPath("M 11 0.75 C 8.5 0.75, 6 2.5, 6 5.25")),
154
+ stretchLineDiv(),
155
+ 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")),
156
+ stretchLineDiv(),
157
+ svgSegment("side-svg-cap", 12, 6, svgPath("M 6 0.75 C 6 3.5, 8.5 5.25, 11 5.25"))
158
+ ]);
159
+ case "bracket":
160
+ return segmented("8px", 4, [
161
+ svgSegment("side-svg-cap", 8, 6, svgPolyline("1,0.75 4,0.75 4,5.25")),
162
+ stretchLineDiv(),
163
+ svgSegment("side-svg-cap", 8, 6, svgPolyline("4,0.75 4,5.25 1,5.25"))
164
+ ], "side-svg-bracket");
165
+ case "left-bracket":
166
+ return segmented("8px", 4, [
167
+ svgSegment("side-svg-cap", 8, 6, svgPolyline("7,0.75 4,0.75 4,5.25")),
168
+ stretchLineDiv(),
169
+ svgSegment("side-svg-cap", 8, 6, svgPolyline("4,0.75 4,5.25 7,5.25"))
170
+ ], "side-svg-left-bracket");
171
+ case "line":
172
+ case "warning":
173
+ case "question":
174
+ 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>`;
175
+ case "dash":
176
+ 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>`;
177
+ case "arrow":
178
+ 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>`;
179
+ case "fat-arrow":
180
+ 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>`;
181
+ case "wave-arrow":
182
+ 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>`;
183
+ case "brace-arrow":
184
+ return segmented("12px", 6, [
185
+ svgSegment("side-svg-cap", 12, 6, svgPath("M 1 0.75 C 3.5 0.75, 6 2.5, 6 5.25")),
186
+ stretchLineDiv(),
187
+ 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"'),
188
+ stretchLineDiv(),
189
+ svgSegment("side-svg-cap", 12, 6, svgPath("M 6 0.75 C 6 3.5, 3.5 5.25, 1 5.25"))
190
+ ], "side-svg-compound-arrow");
191
+ case "brace-fat-arrow":
192
+ return segmented("12px", 6, [
193
+ svgSegment("side-svg-cap", 12, 6, svgPath("M 1 0.75 C 3.5 0.75, 6 2.5, 6 5.25")),
194
+ stretchLineDiv(),
195
+ 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"'),
196
+ stretchLineDiv(),
197
+ svgSegment("side-svg-cap", 12, 6, svgPath("M 6 0.75 C 6 3.5, 3.5 5.25, 1 5.25"))
198
+ ], "side-svg-compound-arrow");
199
+ case "bracket-arrow":
200
+ return segmented("8px", 4, [
201
+ svgSegment("side-svg-cap", 8, 6, svgPolyline("1,0.75 4,0.75 4,5.25")),
202
+ stretchLineDiv(),
203
+ 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"'),
204
+ stretchLineDiv(),
205
+ svgSegment("side-svg-cap", 8, 6, svgPolyline("4,0.75 4,5.25 1,5.25"))
206
+ ], "side-svg-bracket side-svg-compound-arrow");
207
+ case "bracket-fat-arrow":
208
+ return segmented("8px", 4, [
209
+ svgSegment("side-svg-cap", 8, 6, svgPolyline("1,0.75 4,0.75 4,5.25")),
210
+ stretchLineDiv(),
211
+ 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"'),
212
+ stretchLineDiv(),
213
+ svgSegment("side-svg-cap", 8, 6, svgPolyline("4,0.75 4,5.25 1,5.25"))
214
+ ], "side-svg-bracket side-svg-compound-arrow");
215
+ case "line-arrow":
216
+ return segmented("4px", 2, [
217
+ stretchLineDiv(),
218
+ 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"'),
219
+ stretchLineDiv()
220
+ ], "side-svg-compound-arrow");
221
+ case "line-fat-arrow":
222
+ return segmented("4px", 2, [
223
+ stretchLineDiv(),
224
+ 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"'),
225
+ stretchLineDiv()
226
+ ], "side-svg-compound-arrow");
227
+ case "brace-warning":
228
+ case "brace-question":
229
+ return makeSvgForSideType("brace", false);
230
+ default:
231
+ return "";
232
+ }
233
+ }
234
+ function toPreviewKind(type) {
235
+ switch (type) {
236
+ case "heading":
237
+ return "heading";
238
+ case "unordered-list":
239
+ return "unordered-list";
240
+ case "ordered-list":
241
+ return "ordered-list";
242
+ case "blockquote":
243
+ return "blockquote";
244
+ case "code-fence":
245
+ return "code-fence";
246
+ case "thematic-break":
247
+ return "thematic-break";
248
+ case "math-block":
249
+ return "math-block";
250
+ case "table":
251
+ return "table";
252
+ case "html-block":
253
+ return "html-block";
254
+ default:
255
+ return "paragraph";
256
+ }
257
+ }
258
+ function renderMainBlocks(block) {
259
+ const rawMembers = Array.isArray(block.attributes?.mainBlocks) ? block.attributes?.mainBlocks : [];
260
+ return rawMembers.map((entry, index) => {
261
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
262
+ return "";
263
+ }
264
+ const type = typeof entry.type === "string" ? entry.type : "paragraph";
265
+ const raw = typeof entry.raw === "string" ? entry.raw : "";
266
+ const memberBlock = {
267
+ blockId: `${block.blockId}:member:${index}`,
268
+ kind: toPreviewKind(type),
269
+ raw,
270
+ startLine: block.startLine,
271
+ endLine: block.endLine,
272
+ renderKey: `${block.renderKey}:member:${index}`
273
+ };
274
+ if (type === "heading" && typeof entry.headingLevel === "number") {
275
+ memberBlock.headingLevel = entry.headingLevel;
276
+ }
277
+ if (type === "code-fence" && typeof entry.language === "string") {
278
+ memberBlock.language = entry.language;
279
+ }
280
+ return renderBlockDefault(memberBlock);
281
+ }).join("");
282
+ }
283
+ function isSideAnnotationPreviewBlock(block) {
284
+ return block.kind === "custom" && block.attributes?.customBlockKey === "side-annotation";
285
+ }
286
+ function renderSideAnnotationPreviewBlock(block) {
287
+ if (!isSideAnnotationPreviewBlock(block)) {
288
+ return `<div>${escapeHtml2(block.raw)}</div>`;
289
+ }
290
+ const sideType = typeof block.attributes?.sideType === "string" ? block.attributes.sideType : "plain";
291
+ const annotationText = typeof block.attributes?.annotationText === "string" ? block.attributes.annotationText : "";
292
+ const orphan = Boolean(block.attributes?.orphan);
293
+ const className = ["side-annotation", `side-type-${sideType}`, orphan ? "side-orphan" : ""].filter(Boolean).join(" ");
294
+ const asideClass = ["side-annotation-aside", orphan ? "side-annotation-orphan" : ""].filter(Boolean).join(" ");
295
+ const decoration = makeSvgForSideType(sideType, orphan);
296
+ const mainHtml = renderMainBlocks(block);
297
+ const textHtml = annotationText ? `<span class="side-annotation-text">${escapeHtml2(annotationText)}</span>` : "";
298
+ return [
299
+ `<div class="${className}" data-side-type="${escapeAttribute(sideType)}"${orphan ? ' data-side-orphan="true"' : ""}${annotationText ? ` data-side-text="${escapeAttribute(annotationText)}"` : ""}>`,
300
+ `<div class="side-annotation-main">${mainHtml}</div>`,
301
+ `<div class="${asideClass}">${decoration}${textHtml}</div>`,
302
+ "</div>"
303
+ ].join("");
304
+ }
305
+
306
+ // src/worker/renderer-registry.ts
307
+ var workerRenderers = /* @__PURE__ */ new Map();
308
+ for (const descriptor of runtimeBlockRegistry.list()) {
309
+ if (descriptor.workerRendererId !== "builtin:side-annotation") {
310
+ continue;
311
+ }
312
+ workerRenderers.set(descriptor.workerRendererId, (block) => ({
313
+ kind: "html",
314
+ html: renderSideAnnotationPreviewBlock(block)
315
+ }));
316
+ }
317
+ function getWorkerRenderer(rendererId) {
318
+ return workerRenderers.get(rendererId) ?? null;
319
+ }
320
+
321
+ // src/worker/preview-render.worker.ts
322
+ var cancelledIds = /* @__PURE__ */ new Set();
323
+ async function handleRender(req) {
324
+ if (cancelledIds.has(req.taskId)) {
325
+ cancelledIds.delete(req.taskId);
326
+ return;
327
+ }
328
+ try {
329
+ const renderer = getWorkerRenderer(req.rendererId);
330
+ if (!renderer) {
331
+ throw new Error(`Unknown worker renderer: ${req.rendererId}`);
332
+ }
333
+ if (cancelledIds.has(req.taskId)) {
334
+ cancelledIds.delete(req.taskId);
335
+ return;
336
+ }
337
+ const result = await renderer(req.block, req.context);
338
+ if (cancelledIds.has(req.taskId)) {
339
+ cancelledIds.delete(req.taskId);
340
+ return;
341
+ }
342
+ const response = result.kind === "html" && result.html != null ? { taskId: req.taskId, ok: true, html: result.html } : { taskId: req.taskId, ok: false, error: "Renderer returned non-html result in worker context" };
343
+ self.postMessage(response);
344
+ } catch (error) {
345
+ if (cancelledIds.has(req.taskId)) {
346
+ cancelledIds.delete(req.taskId);
347
+ return;
348
+ }
349
+ self.postMessage({
350
+ taskId: req.taskId,
351
+ ok: false,
352
+ error: error instanceof Error ? error.message : String(error)
353
+ });
354
+ }
355
+ }
356
+ self.onmessage = (e) => {
357
+ const msg = e.data;
358
+ switch (msg.type) {
359
+ case "render":
360
+ void handleRender(msg);
361
+ break;
362
+ case "cancel":
363
+ cancelledIds.add(msg.taskId);
364
+ break;
365
+ case "cancel-all":
366
+ cancelledIds.clear();
367
+ break;
368
+ }
369
+ };
@@ -0,0 +1,103 @@
1
+ import { OwoMarkSharedState, PreviewBlock, PreviewBlockKind } from '@owomark/core';
2
+
3
+ type PreviewRenderPhase = 'idle' | 'rendering' | 'highlighting' | 'ready' | 'error';
4
+ type PreviewCacheEntry = {
5
+ blockId: string;
6
+ renderKey: string;
7
+ html: string;
8
+ highlighted: boolean;
9
+ themeKey: string;
10
+ updatedAt: number;
11
+ };
12
+ type PreviewRenderContext = {
13
+ version: number;
14
+ themeKey: string;
15
+ abortSignal?: AbortSignal;
16
+ /**
17
+ * Line offset to add to source-line attributes in the rendered HTML.
18
+ * When a block starts at line N in the document, the renderer processes
19
+ * its raw content starting from line 1; this offset (N - 1) must be
20
+ * added to `data-source-line-start/end` so scroll sync anchors map
21
+ * to the correct document lines.
22
+ */
23
+ sourceLineOffset: number;
24
+ };
25
+ type PreviewRenderResult = {
26
+ kind: 'html';
27
+ html: string;
28
+ } | {
29
+ kind: 'dom';
30
+ mount: (container: HTMLElement) => void;
31
+ unmount?: () => void;
32
+ };
33
+ type PreviewRendererMode = 'html-worker-safe' | 'dom-main-thread';
34
+ type PreviewTaskPriority = 'realtime' | 'deferred';
35
+ type PreviewRendererDefinition = {
36
+ mode: PreviewRendererMode;
37
+ priority: PreviewTaskPriority;
38
+ render: PreviewBlockRenderer;
39
+ version: string;
40
+ workerRendererId?: string;
41
+ /**
42
+ * @deprecated Dynamic worker module URLs are incompatible with static
43
+ * bundlers such as Next.js Turbopack. Prefer `workerRendererId`.
44
+ */
45
+ workerModuleUrl?: string;
46
+ };
47
+ type PreviewBlockRenderer = (block: PreviewBlock, context: PreviewRenderContext) => Promise<PreviewRenderResult> | PreviewRenderResult;
48
+ type PreviewBlockMetadata = {
49
+ mode: PreviewRendererMode;
50
+ priority: PreviewTaskPriority;
51
+ heavy?: boolean;
52
+ };
53
+ type PreviewRendererTarget = {
54
+ kind: PreviewBlockKind;
55
+ customBlockKey?: string | null;
56
+ };
57
+ type PreviewRendererRegistryLookup = PreviewBlock | PreviewRendererTarget;
58
+ type PreviewRendererRegistry = {
59
+ get(target: PreviewRendererRegistryLookup): PreviewRendererDefinition | null;
60
+ register(target: PreviewRendererTarget, renderer: PreviewRendererDefinition): void;
61
+ unregister(target: PreviewRendererTarget): void;
62
+ registerMetadata(target: PreviewRendererTarget, meta: PreviewBlockMetadata): void;
63
+ isHeavy(target: PreviewRendererRegistryLookup): boolean;
64
+ getMode(target: PreviewRendererRegistryLookup): PreviewRendererMode | null;
65
+ getPriority(target: PreviewRendererRegistryLookup): PreviewTaskPriority | null;
66
+ listRegistered(): PreviewRendererTarget[];
67
+ };
68
+ type PreviewTaskScheduler = {
69
+ submitWorkerTask(block: PreviewBlock, rendererDef: PreviewRendererDefinition, context: PreviewRenderContext): Promise<string>;
70
+ cancel(taskId: number): void;
71
+ cancelAll(): void;
72
+ acquire(): PreviewTaskScheduler;
73
+ release(): void;
74
+ };
75
+ type PreviewStrategy = 'incremental' | 'virtual' | 'mdx';
76
+ type OwoMarkPreviewEngineOptions = {
77
+ strategy?: PreviewStrategy;
78
+ themeKey?: string;
79
+ registry?: PreviewRendererRegistry;
80
+ scheduler?: PreviewTaskScheduler;
81
+ viewportFirst?: boolean;
82
+ /**
83
+ * External block renderer function. When provided, the engine uses this
84
+ * instead of the built-in default renderer. This allows the host to supply
85
+ * its own Markdown pipeline (unified, remark, rehype, Shiki, etc.) without
86
+ * the preview package bundling those heavy dependencies.
87
+ */
88
+ renderBlock?: (block: PreviewBlock, context: PreviewRenderContext) => Promise<string>;
89
+ /**
90
+ * Called after every DOM mutation — including idle-backfilled and deferred
91
+ * block renders — not just after the synchronous update() return.
92
+ * Use for scroll sync or other post-DOM-update side effects.
93
+ */
94
+ onContentUpdate?: () => void;
95
+ };
96
+ type OwoMarkPreviewEngine = {
97
+ mount(root: HTMLElement): void;
98
+ destroy(): void;
99
+ update(state: OwoMarkSharedState): void;
100
+ getRenderedVersion(): number;
101
+ };
102
+
103
+ export type { OwoMarkPreviewEngineOptions as O, PreviewRendererRegistry as P, OwoMarkPreviewEngine as a, PreviewTaskScheduler as b, PreviewBlockMetadata as c, PreviewBlockRenderer as d, PreviewCacheEntry as e, PreviewRenderContext as f, PreviewRenderPhase as g, PreviewRenderResult as h, PreviewRendererDefinition as i, PreviewRendererMode as j, PreviewRendererRegistryLookup as k, PreviewRendererTarget as l, PreviewStrategy as m, PreviewTaskPriority as n };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owomark/view",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Rendering engine, preview engine, DOM view layer, and official base theme for OwoMark.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -62,7 +62,7 @@
62
62
  "access": "public"
63
63
  },
64
64
  "dependencies": {
65
- "@owomark/core": "^0.1.5"
65
+ "@owomark/core": "^0.1.7"
66
66
  },
67
67
  "devDependencies": {
68
68
  "tsup": "^8.5.1"
package/src/style.css CHANGED
@@ -4,8 +4,10 @@
4
4
 
5
5
  /* Structural: editor base styles consuming --owo-editor-* tokens */
6
6
  @import './theme/owomark.css';
7
+ @import './theme/cards.css';
7
8
  @import './theme/side-annotation.css';
8
9
  @import './theme/slash-menu.css';
10
+ @import './theme/toolbar.css';
9
11
 
10
12
  /* Aesthetic: token value assignments for light and dark presets */
11
13
  @import './theme/light.css';
@@ -0,0 +1,50 @@
1
+ .owo-cards {
2
+ display: grid;
3
+ grid-template-columns: repeat(2, minmax(0, 1fr));
4
+ gap: 1rem;
5
+ margin: 1rem 0 1.5rem;
6
+ }
7
+
8
+ .owo-card {
9
+ display: flex;
10
+ flex-direction: column;
11
+ gap: 0.75rem;
12
+ min-width: 0;
13
+ padding: 1rem;
14
+ border: 1px solid var(--owo-preview-card-border, var(--owo-border, #e2e8f0));
15
+ border-radius: 0.875rem;
16
+ background: var(--owo-preview-card-bg, var(--owo-surface-raised, #f8fafc));
17
+ box-shadow: var(--owo-preview-card-shadow, 0 10px 30px rgba(15, 23, 42, 0.06));
18
+ }
19
+
20
+ .owo-card-title {
21
+ margin: 0;
22
+ color: var(--owo-preview-card-title, var(--owo-text, #111827));
23
+ font-size: 1rem;
24
+ font-weight: 700;
25
+ line-height: 1.35;
26
+ }
27
+
28
+ .owo-card-body > :first-child {
29
+ margin-top: 0;
30
+ }
31
+
32
+ .owo-card-body > :last-child {
33
+ margin-bottom: 0;
34
+ }
35
+
36
+ .owo-card[data-owo-card-tone="info"] {
37
+ background: color-mix(in srgb, var(--owo-brand, #2563eb) 7%, var(--owo-preview-card-bg, #f8fafc));
38
+ border-color: color-mix(in srgb, var(--owo-brand, #2563eb) 22%, var(--owo-preview-card-border, #e2e8f0));
39
+ }
40
+
41
+ .owo-card[data-owo-card-tone="warning"] {
42
+ background: color-mix(in srgb, var(--owo-warning, #f59e0b) 9%, var(--owo-preview-card-bg, #f8fafc));
43
+ border-color: color-mix(in srgb, var(--owo-warning, #f59e0b) 26%, var(--owo-preview-card-border, #e2e8f0));
44
+ }
45
+
46
+ @media (max-width: 768px) {
47
+ .owo-cards {
48
+ grid-template-columns: minmax(0, 1fr);
49
+ }
50
+ }
@@ -56,11 +56,19 @@
56
56
  --owo-syntax-link-url: #89b4fa;
57
57
  --owo-syntax-inline-code: #94e2d5;
58
58
  --owo-syntax-inline-code-bg: rgba(88, 91, 112, 0.35);
59
+ --owo-syntax-custom-inline: #f9e2af;
60
+ --owo-syntax-custom-inline-bg: rgba(250, 179, 135, 0.2);
59
61
  --owo-syntax-code-fence: #7f849c;
60
62
  --owo-syntax-code-lang: #fab387;
61
63
  --owo-syntax-list-marker: #fab387;
64
+ --owo-syntax-task-marker: #f9c74f;
65
+ --owo-syntax-task-marker-checked: #a6e3a1;
66
+ --owo-syntax-table-separator: #6c7086;
67
+ --owo-syntax-strikethrough: #cdd6f4;
62
68
  --owo-syntax-blockquote-marker: #585b70;
63
69
  --owo-syntax-hr: #45475a;
70
+ --owo-syntax-html: #cba6f7;
71
+ --owo-syntax-math: #94e2d5;
64
72
 
65
73
  /* Toolbar */
66
74
  --owo-toolbar-bg: #252536;
@@ -56,11 +56,19 @@
56
56
  --owo-syntax-link-url: #2563eb;
57
57
  --owo-syntax-inline-code: #0f766e;
58
58
  --owo-syntax-inline-code-bg: rgba(148, 163, 184, 0.16);
59
+ --owo-syntax-custom-inline: #92400e;
60
+ --owo-syntax-custom-inline-bg: rgba(250, 204, 21, 0.18);
59
61
  --owo-syntax-code-fence: #64748b;
60
62
  --owo-syntax-code-lang: #ea580c;
61
63
  --owo-syntax-list-marker: #ea580c;
64
+ --owo-syntax-task-marker: #d97706;
65
+ --owo-syntax-task-marker-checked: #16a34a;
66
+ --owo-syntax-table-separator: #94a3b8;
67
+ --owo-syntax-strikethrough: #1f2937;
62
68
  --owo-syntax-blockquote-marker: #9ca3af;
63
69
  --owo-syntax-hr: #d1d5db;
70
+ --owo-syntax-html: #7c3aed;
71
+ --owo-syntax-math: #0f766e;
64
72
 
65
73
  /* Toolbar */
66
74
  --owo-toolbar-bg: #f8fafc;