@owomark/view 0.1.6 → 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.
@@ -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
+ };
@@ -1,4 +1,4 @@
1
- import { OwoMarkSharedState, PreviewBlockKind, PreviewBlock } from '@owomark/core';
1
+ import { OwoMarkSharedState, PreviewBlock, PreviewBlockKind } from '@owomark/core';
2
2
 
3
3
  type PreviewRenderPhase = 'idle' | 'rendering' | 'highlighting' | 'ready' | 'error';
4
4
  type PreviewCacheEntry = {
@@ -37,6 +37,11 @@ type PreviewRendererDefinition = {
37
37
  priority: PreviewTaskPriority;
38
38
  render: PreviewBlockRenderer;
39
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
+ */
40
45
  workerModuleUrl?: string;
41
46
  };
42
47
  type PreviewBlockRenderer = (block: PreviewBlock, context: PreviewRenderContext) => Promise<PreviewRenderResult> | PreviewRenderResult;
@@ -45,15 +50,20 @@ type PreviewBlockMetadata = {
45
50
  priority: PreviewTaskPriority;
46
51
  heavy?: boolean;
47
52
  };
53
+ type PreviewRendererTarget = {
54
+ kind: PreviewBlockKind;
55
+ customBlockKey?: string | null;
56
+ };
57
+ type PreviewRendererRegistryLookup = PreviewBlock | PreviewRendererTarget;
48
58
  type PreviewRendererRegistry = {
49
- get(kind: PreviewBlockKind): PreviewRendererDefinition | null;
50
- register(kind: PreviewBlockKind, renderer: PreviewRendererDefinition): void;
51
- unregister(kind: PreviewBlockKind): void;
52
- registerMetadata(kind: PreviewBlockKind, meta: PreviewBlockMetadata): void;
53
- isHeavy(kind: PreviewBlockKind): boolean;
54
- getMode(kind: PreviewBlockKind): PreviewRendererMode | null;
55
- getPriority(kind: PreviewBlockKind): PreviewTaskPriority | null;
56
- listRegistered(): PreviewBlockKind[];
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[];
57
67
  };
58
68
  type PreviewTaskScheduler = {
59
69
  submitWorkerTask(block: PreviewBlock, rendererDef: PreviewRendererDefinition, context: PreviewRenderContext): Promise<string>;
@@ -90,4 +100,4 @@ type OwoMarkPreviewEngine = {
90
100
  getRenderedVersion(): number;
91
101
  };
92
102
 
93
- 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, PreviewStrategy as k, PreviewTaskPriority as l };
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.6",
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.6"
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,6 +56,8 @@
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;
@@ -56,6 +56,8 @@
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;
@@ -101,6 +101,12 @@
101
101
  color: var(--owo-editor-text, #1f2937);
102
102
  }
103
103
 
104
+ .owo-syntax-custom-inline {
105
+ color: var(--owo-syntax-custom-inline, var(--owo-editor-text, currentColor));
106
+ background-color: var(--owo-syntax-custom-inline-bg, rgba(250, 204, 21, 0.18));
107
+ border-radius: 0.2em;
108
+ }
109
+
104
110
  .owo-syntax-inline-code {
105
111
  color: var(--owo-syntax-inline-code, #0f766e);
106
112
  background-color: var(--owo-syntax-inline-code-bg, rgba(148, 163, 184, 0.16));
@@ -149,7 +149,22 @@
149
149
  gap: 0;
150
150
  }
151
151
 
152
+ .owomark-prose figure[data-rehype-pretty-code-figure] {
153
+ margin-top: var(--owo-preview-code-block-margin-top, 0.5rem);
154
+ margin-bottom: var(--owo-preview-code-block-margin-bottom, 0.5rem);
155
+ }
156
+
157
+ .owomark-prose figure[data-rehype-pretty-code-figure] pre {
158
+ margin-top: 0;
159
+ margin-bottom: 0;
160
+ }
161
+
162
+ .owomark-prose .owo-async-code-block[data-owo-code-block-state="loading"] pre {
163
+ opacity: 0.82;
164
+ }
165
+
152
166
  .owomark-prose pre.shiki span[data-line] {
167
+ display: block;
153
168
  min-height: var(--owo-preview-code-line-min-height, 1.5rem);
154
169
  }
155
170
 
@@ -0,0 +1,104 @@
1
+ /* OwoMark Toolbar — Component quick-insert buttons */
2
+
3
+ .owo-toolbar__components-group {
4
+ display: flex;
5
+ flex-wrap: wrap;
6
+ align-items: center;
7
+ gap: 4px;
8
+ padding: 4px 6px;
9
+ background: var(--owo-toolbar-bg, #f8fafc);
10
+ border: 1px solid var(--owo-toolbar-border, #e2e8f0);
11
+ border-radius: 6px;
12
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
13
+ font-size: 13px;
14
+ color: var(--owo-toolbar-text, #334155);
15
+ }
16
+
17
+ .owo-toolbar__component-button {
18
+ display: inline-flex;
19
+ align-items: center;
20
+ gap: 4px;
21
+ padding: 3px 8px;
22
+ border: none;
23
+ border-radius: 4px;
24
+ background: var(--owo-toolbar-button-bg, transparent);
25
+ color: var(--owo-toolbar-text, #334155);
26
+ font-size: 13px;
27
+ font-family: inherit;
28
+ line-height: 1.4;
29
+ cursor: pointer;
30
+ white-space: nowrap;
31
+ transition: background-color 0.1s, box-shadow 0.1s;
32
+ user-select: none;
33
+ }
34
+
35
+ .owo-toolbar__component-button:hover {
36
+ background: var(--owo-toolbar-button-hover-bg, rgba(15, 23, 42, 0.06));
37
+ box-shadow: 0 0 0 1px var(--owo-toolbar-border, #e2e8f0);
38
+ }
39
+
40
+ .owo-toolbar__component-button:active {
41
+ background: var(--owo-toolbar-button-active-bg, rgba(37, 99, 235, 0.1));
42
+ }
43
+
44
+ .owo-toolbar__component-icon {
45
+ flex-shrink: 0;
46
+ width: 16px;
47
+ height: 16px;
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: center;
51
+ color: var(--owo-toolbar-icon, #475569);
52
+ }
53
+
54
+ .owo-toolbar__component-label {
55
+ font-weight: 500;
56
+ }
57
+
58
+ /* --- Collapsed popover trigger (responsive) --- */
59
+
60
+ .owo-toolbar__collapse-trigger {
61
+ display: inline-flex;
62
+ align-items: center;
63
+ gap: 4px;
64
+ padding: 3px 8px;
65
+ border: none;
66
+ border-radius: 4px;
67
+ background: var(--owo-toolbar-button-bg, transparent);
68
+ color: var(--owo-toolbar-text, #334155);
69
+ font-size: 13px;
70
+ font-family: inherit;
71
+ line-height: 1.4;
72
+ cursor: pointer;
73
+ white-space: nowrap;
74
+ transition: background-color 0.1s;
75
+ user-select: none;
76
+ }
77
+
78
+ .owo-toolbar__collapse-trigger:hover {
79
+ background: var(--owo-toolbar-button-hover-bg, rgba(15, 23, 42, 0.06));
80
+ }
81
+
82
+ /* --- Popover panel --- */
83
+
84
+ .owo-toolbar__popover {
85
+ position: absolute;
86
+ top: 100%;
87
+ left: 0;
88
+ z-index: 100;
89
+ display: flex;
90
+ flex-direction: column;
91
+ gap: 2px;
92
+ min-width: 140px;
93
+ padding: 4px;
94
+ margin-top: 4px;
95
+ background: var(--owo-toolbar-bg, #f8fafc);
96
+ border: 1px solid var(--owo-toolbar-border, #e2e8f0);
97
+ border-radius: 6px;
98
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
99
+ }
100
+
101
+ .owo-toolbar__popover .owo-toolbar__component-button {
102
+ width: 100%;
103
+ justify-content: flex-start;
104
+ }