@dotit/core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +229 -0
- package/dist/aliases.d.ts +1 -0
- package/dist/aliases.js +8 -0
- package/dist/ask.d.ts +7 -0
- package/dist/ask.js +55 -0
- package/dist/browser.d.ts +12 -0
- package/dist/browser.js +32 -0
- package/dist/diff.d.ts +17 -0
- package/dist/diff.js +179 -0
- package/dist/document-css.d.ts +1 -0
- package/dist/document-css.js +290 -0
- package/dist/executor.d.ts +40 -0
- package/dist/executor.js +501 -0
- package/dist/history.d.ts +10 -0
- package/dist/history.js +297 -0
- package/dist/html-to-it.d.ts +1 -0
- package/dist/html-to-it.js +288 -0
- package/dist/index-builder.d.ts +62 -0
- package/dist/index-builder.js +228 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +94 -0
- package/dist/language-registry.d.ts +39 -0
- package/dist/language-registry.js +530 -0
- package/dist/markdown.d.ts +1 -0
- package/dist/markdown.js +123 -0
- package/dist/merge.d.ts +6 -0
- package/dist/merge.js +255 -0
- package/dist/parser.d.ts +29 -0
- package/dist/parser.js +1562 -0
- package/dist/query.d.ts +32 -0
- package/dist/query.js +293 -0
- package/dist/renderer.d.ts +16 -0
- package/dist/renderer.js +1286 -0
- package/dist/schema.d.ts +47 -0
- package/dist/schema.js +574 -0
- package/dist/source.d.ts +3 -0
- package/dist/source.js +223 -0
- package/dist/theme.d.ts +49 -0
- package/dist/theme.js +113 -0
- package/dist/themes/corporate.json +86 -0
- package/dist/themes/dark.json +64 -0
- package/dist/themes/editorial.json +54 -0
- package/dist/themes/legal.json +57 -0
- package/dist/themes/minimal.json +50 -0
- package/dist/themes/print.json +54 -0
- package/dist/themes/technical.json +59 -0
- package/dist/themes/warm.json +53 -0
- package/dist/trust.d.ts +66 -0
- package/dist/trust.js +200 -0
- package/dist/types.d.ts +234 -0
- package/dist/types.js +19 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +13 -0
- package/dist/validate.d.ts +13 -0
- package/dist/validate.js +711 -0
- package/dist/workflow.d.ts +18 -0
- package/dist/workflow.js +160 -0
- package/package.json +51 -0
package/dist/renderer.js
ADDED
|
@@ -0,0 +1,1286 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DOC_STYLE_TARGETS = void 0;
|
|
4
|
+
exports.collectPrintLayout = collectPrintLayout;
|
|
5
|
+
exports.cssContentValue = cssContentValue;
|
|
6
|
+
exports.collectDocumentStyles = collectDocumentStyles;
|
|
7
|
+
exports.documentStyleCSS = documentStyleCSS;
|
|
8
|
+
exports.renderHTML = renderHTML;
|
|
9
|
+
exports.renderPrint = renderPrint;
|
|
10
|
+
const document_css_1 = require("./document-css");
|
|
11
|
+
const theme_1 = require("./theme");
|
|
12
|
+
function resolveThemeSync(ref) {
|
|
13
|
+
if (ref && typeof ref === "object")
|
|
14
|
+
return ref;
|
|
15
|
+
if (typeof ref === "string") {
|
|
16
|
+
const found = (0, theme_1.getBuiltinTheme)(ref);
|
|
17
|
+
if (found)
|
|
18
|
+
return found;
|
|
19
|
+
}
|
|
20
|
+
return (0, theme_1.getBuiltinTheme)("corporate");
|
|
21
|
+
}
|
|
22
|
+
const PAPER_SIZES = {
|
|
23
|
+
A4: "A4",
|
|
24
|
+
A5: "A5",
|
|
25
|
+
A3: "297mm 420mm",
|
|
26
|
+
Letter: "Letter",
|
|
27
|
+
Legal: "8.5in 14in",
|
|
28
|
+
};
|
|
29
|
+
function collectPrintLayout(doc) {
|
|
30
|
+
const allBlocks = doc.blocks.flatMap(function collect(b) {
|
|
31
|
+
return [b, ...(b.children ?? []).flatMap(collect)];
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
page: allBlocks.find((b) => b.type === "page"),
|
|
35
|
+
header: allBlocks.filter((b) => b.type === "header").pop(),
|
|
36
|
+
footer: allBlocks.filter((b) => b.type === "footer").pop(),
|
|
37
|
+
watermark: allBlocks.filter((b) => b.type === "watermark").pop(),
|
|
38
|
+
breaks: allBlocks.filter((b) => b.type === "break" && (b.properties?.before || b.properties?.keep)),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function escapeHtml(text) {
|
|
42
|
+
return text
|
|
43
|
+
.replace(/&/g, "&")
|
|
44
|
+
.replace(/</g, "<")
|
|
45
|
+
.replace(/>/g, ">")
|
|
46
|
+
.replace(/\"/g, """)
|
|
47
|
+
.replace(/'/g, "'");
|
|
48
|
+
}
|
|
49
|
+
function cssContentValue(text) {
|
|
50
|
+
if (!text)
|
|
51
|
+
return '""';
|
|
52
|
+
const parts = String(text)
|
|
53
|
+
.split(/(\{\{\s*pages?\s*\}\})/g)
|
|
54
|
+
.filter((p) => p !== "");
|
|
55
|
+
return (parts
|
|
56
|
+
.map((p) => {
|
|
57
|
+
if (/^\{\{\s*page\s*\}\}$/.test(p))
|
|
58
|
+
return "counter(page)";
|
|
59
|
+
if (/^\{\{\s*pages\s*\}\}$/.test(p))
|
|
60
|
+
return "counter(pages)";
|
|
61
|
+
return ('"' +
|
|
62
|
+
p
|
|
63
|
+
.replace(/\\/g, "\\\\")
|
|
64
|
+
.replace(/"/g, '\\"')
|
|
65
|
+
.replace(/[\r\n]+/g, " ") +
|
|
66
|
+
'"');
|
|
67
|
+
})
|
|
68
|
+
.join(" ") || '""');
|
|
69
|
+
}
|
|
70
|
+
function formatTrustDate(isoStr) {
|
|
71
|
+
try {
|
|
72
|
+
const d = new Date(isoStr);
|
|
73
|
+
if (isNaN(d.getTime()))
|
|
74
|
+
return escapeHtml(isoStr);
|
|
75
|
+
const months = [
|
|
76
|
+
"January",
|
|
77
|
+
"February",
|
|
78
|
+
"March",
|
|
79
|
+
"April",
|
|
80
|
+
"May",
|
|
81
|
+
"June",
|
|
82
|
+
"July",
|
|
83
|
+
"August",
|
|
84
|
+
"September",
|
|
85
|
+
"October",
|
|
86
|
+
"November",
|
|
87
|
+
"December",
|
|
88
|
+
];
|
|
89
|
+
const day = d.getUTCDate();
|
|
90
|
+
const month = months[d.getUTCMonth()];
|
|
91
|
+
const year = d.getUTCFullYear();
|
|
92
|
+
const hours = String(d.getUTCHours()).padStart(2, "0");
|
|
93
|
+
const minutes = String(d.getUTCMinutes()).padStart(2, "0");
|
|
94
|
+
return `${day} ${month} ${year}, ${hours}:${minutes} UTC`;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return escapeHtml(isoStr);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function sanitizeUrl(url) {
|
|
101
|
+
const trimmed = url.trim();
|
|
102
|
+
if (trimmed === "")
|
|
103
|
+
return "";
|
|
104
|
+
if (trimmed.startsWith("/") ||
|
|
105
|
+
trimmed.startsWith("./") ||
|
|
106
|
+
trimmed.startsWith("../") ||
|
|
107
|
+
trimmed.startsWith("#")) {
|
|
108
|
+
return trimmed;
|
|
109
|
+
}
|
|
110
|
+
const lower = trimmed.toLowerCase();
|
|
111
|
+
if (lower.startsWith("javascript:") ||
|
|
112
|
+
lower.startsWith("vbscript:") ||
|
|
113
|
+
lower.startsWith("data:")) {
|
|
114
|
+
return "#";
|
|
115
|
+
}
|
|
116
|
+
if (!lower.includes(":") && !lower.startsWith("//")) {
|
|
117
|
+
return trimmed;
|
|
118
|
+
}
|
|
119
|
+
if (lower.startsWith("http://") ||
|
|
120
|
+
lower.startsWith("https://") ||
|
|
121
|
+
lower.startsWith("mailto:") ||
|
|
122
|
+
lower.startsWith("tel:")) {
|
|
123
|
+
return trimmed;
|
|
124
|
+
}
|
|
125
|
+
return "#";
|
|
126
|
+
}
|
|
127
|
+
function applyInlineFormatting(content, inline, originalContent) {
|
|
128
|
+
if (inline && inline.length > 0) {
|
|
129
|
+
return inline
|
|
130
|
+
.map((node) => {
|
|
131
|
+
switch (node.type) {
|
|
132
|
+
case "text":
|
|
133
|
+
return escapeHtml(node.value);
|
|
134
|
+
case "bold":
|
|
135
|
+
return `<strong>${escapeHtml(node.value)}</strong>`;
|
|
136
|
+
case "italic":
|
|
137
|
+
return `<em>${escapeHtml(node.value)}</em>`;
|
|
138
|
+
case "strike":
|
|
139
|
+
return `<del>${escapeHtml(node.value)}</del>`;
|
|
140
|
+
case "inline-quote":
|
|
141
|
+
return `<q class="intent-inline-quote">${escapeHtml(node.value)}</q>`;
|
|
142
|
+
case "highlight":
|
|
143
|
+
return `<mark class="intent-inline-highlight">${escapeHtml(node.value)}</mark>`;
|
|
144
|
+
case "code":
|
|
145
|
+
return `<code>${escapeHtml(node.value)}</code>`;
|
|
146
|
+
case "styled": {
|
|
147
|
+
const css = extractInlineStyles(node.props);
|
|
148
|
+
return css
|
|
149
|
+
? `<span style="${css}">${escapeHtml(node.value)}</span>`
|
|
150
|
+
: escapeHtml(node.value);
|
|
151
|
+
}
|
|
152
|
+
case "inline-note":
|
|
153
|
+
return `<span class="intent-inline-note">${escapeHtml(node.value)}</span>`;
|
|
154
|
+
case "date":
|
|
155
|
+
return `<time class="intent-inline-date" datetime="${escapeHtml(node.iso)}">${escapeHtml(node.value)}</time>`;
|
|
156
|
+
case "mention":
|
|
157
|
+
return `<span class="intent-inline-mention">@${escapeHtml(node.value)}</span>`;
|
|
158
|
+
case "tag":
|
|
159
|
+
return `<span class="intent-inline-tag">#${escapeHtml(node.value)}</span>`;
|
|
160
|
+
case "link":
|
|
161
|
+
return `<a href="${escapeHtml(sanitizeUrl(node.href))}" class="intent-inline-link">${escapeHtml(node.value)}</a>`;
|
|
162
|
+
case "footnote-ref":
|
|
163
|
+
return `<sup class="it-fn-ref"><a href="#fn-${escapeHtml(node.value)}">${escapeHtml(node.value)}</a></sup>`;
|
|
164
|
+
case "label":
|
|
165
|
+
return `<span class="it-label">${escapeHtml(node.value)}</span>`;
|
|
166
|
+
default:
|
|
167
|
+
return escapeHtml(node.value);
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
.join("");
|
|
171
|
+
}
|
|
172
|
+
return escapeHtml(originalContent || content);
|
|
173
|
+
}
|
|
174
|
+
function getAlignmentClass(props) {
|
|
175
|
+
const raw = String(props.align || "")
|
|
176
|
+
.toLowerCase()
|
|
177
|
+
.trim();
|
|
178
|
+
if (raw === "center")
|
|
179
|
+
return " intent-align-center";
|
|
180
|
+
if (raw === "right")
|
|
181
|
+
return " intent-align-right";
|
|
182
|
+
if (raw === "justify")
|
|
183
|
+
return " intent-align-justify";
|
|
184
|
+
return "";
|
|
185
|
+
}
|
|
186
|
+
const STYLE_PROPERTIES = {
|
|
187
|
+
color: "color",
|
|
188
|
+
size: "font-size",
|
|
189
|
+
family: "font-family",
|
|
190
|
+
weight: "font-weight",
|
|
191
|
+
align: "text-align",
|
|
192
|
+
bg: "background-color",
|
|
193
|
+
indent: "padding-left",
|
|
194
|
+
opacity: "opacity",
|
|
195
|
+
italic: "font-style",
|
|
196
|
+
border: "border",
|
|
197
|
+
underline: "text-decoration",
|
|
198
|
+
strike: "text-decoration",
|
|
199
|
+
valign: "vertical-align",
|
|
200
|
+
};
|
|
201
|
+
function extractInlineStyles(properties, context = "attr") {
|
|
202
|
+
const styles = [];
|
|
203
|
+
const decorations = [];
|
|
204
|
+
for (const [prop, css] of Object.entries(STYLE_PROPERTIES)) {
|
|
205
|
+
const value = properties[prop];
|
|
206
|
+
if (value === undefined || value === "")
|
|
207
|
+
continue;
|
|
208
|
+
const strValue = context === "attr"
|
|
209
|
+
? escapeHtml(String(value).replace(/[;{}]/g, ""))
|
|
210
|
+
: String(value).replace(/[<>{};]/g, "");
|
|
211
|
+
if (prop === "border" && strValue === "true") {
|
|
212
|
+
styles.push("border: 1px solid currentColor");
|
|
213
|
+
}
|
|
214
|
+
else if (prop === "italic" && strValue === "true") {
|
|
215
|
+
styles.push("font-style: italic");
|
|
216
|
+
}
|
|
217
|
+
else if (prop === "underline" && strValue === "true") {
|
|
218
|
+
decorations.push("underline");
|
|
219
|
+
}
|
|
220
|
+
else if (prop === "strike" && strValue === "true") {
|
|
221
|
+
decorations.push("line-through");
|
|
222
|
+
}
|
|
223
|
+
else if (prop !== "underline" && prop !== "strike") {
|
|
224
|
+
styles.push(`${css}: ${strValue}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (decorations.length)
|
|
228
|
+
styles.push(`text-decoration: ${decorations.join(" ")}`);
|
|
229
|
+
return styles.join("; ");
|
|
230
|
+
}
|
|
231
|
+
exports.DOC_STYLE_TARGETS = {
|
|
232
|
+
title: [".intent-title"],
|
|
233
|
+
summary: [".intent-summary"],
|
|
234
|
+
section: [".intent-section"],
|
|
235
|
+
sub: [".intent-sub"],
|
|
236
|
+
text: [".intent-text"],
|
|
237
|
+
quote: [".intent-quote"],
|
|
238
|
+
callout: [".intent-callout"],
|
|
239
|
+
info: [".intent-callout"],
|
|
240
|
+
table: [".intent-table-th", ".intent-table-td"],
|
|
241
|
+
"table-header": [".intent-table-th"],
|
|
242
|
+
metric: [".it-metric-row"],
|
|
243
|
+
contact: [".it-contact"],
|
|
244
|
+
divider: [".intent-divider"],
|
|
245
|
+
};
|
|
246
|
+
function collectDocumentStyles(doc) {
|
|
247
|
+
const rules = [];
|
|
248
|
+
const walk = (blocks) => {
|
|
249
|
+
for (const b of blocks) {
|
|
250
|
+
if (b.type === "style" && b.content) {
|
|
251
|
+
const target = String(b.content).trim().toLowerCase();
|
|
252
|
+
if (exports.DOC_STYLE_TARGETS[target]) {
|
|
253
|
+
const declarations = extractInlineStyles(b.properties || {}, "stylesheet");
|
|
254
|
+
if (declarations)
|
|
255
|
+
rules.push({ target, declarations });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (b.children)
|
|
259
|
+
walk(b.children);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
walk(doc.blocks);
|
|
263
|
+
return rules;
|
|
264
|
+
}
|
|
265
|
+
function documentStyleCSS(doc, selectorMap = exports.DOC_STYLE_TARGETS, selectorPrefix = "") {
|
|
266
|
+
return collectDocumentStyles(doc)
|
|
267
|
+
.map((rule) => {
|
|
268
|
+
const sels = selectorMap[rule.target];
|
|
269
|
+
if (!sels || sels.length === 0)
|
|
270
|
+
return "";
|
|
271
|
+
const scoped = sels.map((s) => `${selectorPrefix}${s}`).join(",");
|
|
272
|
+
return `${scoped}{${rule.declarations};}`;
|
|
273
|
+
})
|
|
274
|
+
.filter(Boolean)
|
|
275
|
+
.join("\n");
|
|
276
|
+
}
|
|
277
|
+
function renderBlock(block) {
|
|
278
|
+
if (block.type === "agent" ||
|
|
279
|
+
block.type === "model" ||
|
|
280
|
+
block.type === "style") {
|
|
281
|
+
return "";
|
|
282
|
+
}
|
|
283
|
+
const content = applyInlineFormatting(block.content, block.inline, block.originalContent);
|
|
284
|
+
const props = block.properties || {};
|
|
285
|
+
const alignClass = getAlignmentClass(props);
|
|
286
|
+
const inlineStyle = extractInlineStyles(props);
|
|
287
|
+
const styleAttr = inlineStyle ? ` style="${inlineStyle}"` : "";
|
|
288
|
+
switch (block.type) {
|
|
289
|
+
case "title":
|
|
290
|
+
return `<h1 class="intent-title${alignClass}"${styleAttr}>${content}</h1>`;
|
|
291
|
+
case "summary":
|
|
292
|
+
return `<div class="intent-summary${alignClass}"${styleAttr}>${content}</div>`;
|
|
293
|
+
case "section":
|
|
294
|
+
return `<h2 id="${slugify(block.content)}" class="intent-section${alignClass}"${styleAttr}>${content}</h2>`;
|
|
295
|
+
case "sub":
|
|
296
|
+
return `<h3 id="${slugify(block.content)}" class="intent-sub${alignClass}"${styleAttr}>${content}</h3>`;
|
|
297
|
+
case "divider":
|
|
298
|
+
const dividerStyle = props.style
|
|
299
|
+
? escapeHtml(String(props.style).replace(/[;{}"]/g, ""))
|
|
300
|
+
: "solid";
|
|
301
|
+
const label = content
|
|
302
|
+
? `<span class="intent-divider-label">${content}</span>`
|
|
303
|
+
: "";
|
|
304
|
+
return `<div class="intent-divider">
|
|
305
|
+
<hr class="it-divider" style="border-style: ${dividerStyle}" />
|
|
306
|
+
${label}
|
|
307
|
+
</div>`;
|
|
308
|
+
case "text":
|
|
309
|
+
return `<p class="intent-text${alignClass}"${styleAttr}>${content}</p>`;
|
|
310
|
+
case "body-text":
|
|
311
|
+
return `<p class="intent-prose${alignClass}"${styleAttr}>${content}</p>`;
|
|
312
|
+
case "info": {
|
|
313
|
+
const CALLOUT_VARIANTS = {
|
|
314
|
+
info: "Note",
|
|
315
|
+
warning: "Caution",
|
|
316
|
+
danger: "Danger",
|
|
317
|
+
tip: "Tip",
|
|
318
|
+
success: "Done",
|
|
319
|
+
};
|
|
320
|
+
const subtype = props.type || "info";
|
|
321
|
+
const variant = subtype in CALLOUT_VARIANTS ? subtype : "info";
|
|
322
|
+
const label = CALLOUT_VARIANTS[variant];
|
|
323
|
+
if (variant === "danger") {
|
|
324
|
+
return `<div class="it-callout it-danger" role="alert"${styleAttr}><span class="it-callout-icon" aria-hidden="true">⛔</span><div class="it-callout-body">${content}</div></div>`;
|
|
325
|
+
}
|
|
326
|
+
return `<div class="intent-callout intent-${variant}"${styleAttr}><span class="intent-callout-label">${label}</span><div class="intent-callout-content">${content}</div></div>`;
|
|
327
|
+
}
|
|
328
|
+
case "task":
|
|
329
|
+
case "done": {
|
|
330
|
+
const isDone = props.status === "done" || block.type === "done";
|
|
331
|
+
return `<div class="intent-task${isDone ? " intent-task-done" : ""}">
|
|
332
|
+
<input class="intent-task-checkbox" type="checkbox"${isDone ? " checked" : ""} />
|
|
333
|
+
<span class="intent-task-text${isDone ? " intent-task-text-done" : ""}">${content}</span>
|
|
334
|
+
<span class="intent-task-meta">
|
|
335
|
+
${props.owner ? `<span class="intent-task-owner">${escapeHtml(String(props.owner))}</span>` : ""}
|
|
336
|
+
${props.due ? `<span class="intent-task-due">${escapeHtml(String(props.due))}</span>` : ""}
|
|
337
|
+
${props.time ? `<span class="intent-task-time">${escapeHtml(String(props.time))}</span>` : ""}
|
|
338
|
+
</span>
|
|
339
|
+
</div>`;
|
|
340
|
+
}
|
|
341
|
+
case "ask":
|
|
342
|
+
return `<div class="intent-ask"><span class="intent-ask-label">Query</span><div class="intent-ask-content">${content}</div></div>`;
|
|
343
|
+
case "quote": {
|
|
344
|
+
const attribution = props.by
|
|
345
|
+
? `<cite class="intent-quote-cite">— ${escapeHtml(String(props.by))}</cite>`
|
|
346
|
+
: "";
|
|
347
|
+
return `<blockquote class="intent-quote"><p>${content}</p>${attribution}</blockquote>`;
|
|
348
|
+
}
|
|
349
|
+
case "cite": {
|
|
350
|
+
const citeAuthor = props.author
|
|
351
|
+
? `<span class="it-cite-author">${escapeHtml(String(props.author))}</span>`
|
|
352
|
+
: "";
|
|
353
|
+
const citeDate = props.date
|
|
354
|
+
? `<span class="it-cite-date">${escapeHtml(String(props.date))}</span>`
|
|
355
|
+
: "";
|
|
356
|
+
const citeUrl = props.url
|
|
357
|
+
? ` href="${escapeHtml(sanitizeUrl(String(props.url)))}"`
|
|
358
|
+
: "";
|
|
359
|
+
const citeTitle = citeUrl
|
|
360
|
+
? `<a class="it-cite-title"${citeUrl} target="_blank" rel="noopener noreferrer">${content}</a>`
|
|
361
|
+
: `<span class="it-cite-title">${content}</span>`;
|
|
362
|
+
return `<div class="it-cite">${citeTitle}${citeAuthor ? ` — ${citeAuthor}` : ""}${citeDate ? `, ${citeDate}` : ""}</div>`;
|
|
363
|
+
}
|
|
364
|
+
case "image":
|
|
365
|
+
const imgSrc = escapeHtml(sanitizeUrl(String(props.src ?? props.at ?? "")) ||
|
|
366
|
+
String(props.src ?? props.at ?? content));
|
|
367
|
+
const imgAlt = content;
|
|
368
|
+
return `<figure class="intent-image">
|
|
369
|
+
<img class="intent-image-img" src="${imgSrc}" alt="${imgAlt}" />
|
|
370
|
+
${props.caption ? `<figcaption class="intent-image-caption">${escapeHtml(String(props.caption))}</figcaption>` : ""}
|
|
371
|
+
</figure>`;
|
|
372
|
+
case "link":
|
|
373
|
+
const href = escapeHtml(sanitizeUrl(String(props.to || content)));
|
|
374
|
+
const titleAttr = props.title
|
|
375
|
+
? `title="${escapeHtml(String(props.title))}"`
|
|
376
|
+
: "";
|
|
377
|
+
return `<p class="intent-link"><a href="${href}" ${titleAttr}>${content}</a></p>`;
|
|
378
|
+
case "ref": {
|
|
379
|
+
const refFile = props.file ? String(props.file) : "";
|
|
380
|
+
const refUrl = props.url ? String(props.url) : "";
|
|
381
|
+
const refRel = props.rel ? escapeHtml(String(props.rel)) : "";
|
|
382
|
+
const refHref = refFile
|
|
383
|
+
? escapeHtml(sanitizeUrl(refFile))
|
|
384
|
+
: refUrl
|
|
385
|
+
? escapeHtml(sanitizeUrl(refUrl))
|
|
386
|
+
: "";
|
|
387
|
+
const refName = escapeHtml(block.content || refFile || refUrl);
|
|
388
|
+
const relBadge = refRel
|
|
389
|
+
? `<span class="it-ref-rel">${refRel}</span>`
|
|
390
|
+
: "";
|
|
391
|
+
const linkEl = refHref
|
|
392
|
+
? `<a href="${refHref}" class="it-ref-link">${refName}</a>`
|
|
393
|
+
: `<span class="it-ref-name">${refName}</span>`;
|
|
394
|
+
return `<div class="it-ref-card">
|
|
395
|
+
<span class="it-ref-icon">📎</span>
|
|
396
|
+
${linkEl}
|
|
397
|
+
${relBadge}
|
|
398
|
+
</div>`;
|
|
399
|
+
}
|
|
400
|
+
case "embed": {
|
|
401
|
+
const embedType = props.type || "iframe";
|
|
402
|
+
const src = String(props.src || "");
|
|
403
|
+
const embedContent = String(props.content || "");
|
|
404
|
+
switch (embedType) {
|
|
405
|
+
case "iframe":
|
|
406
|
+
return `<div class="intent-embed"><iframe src="${escapeHtml(sanitizeUrl(src))}" frameborder="0" loading="lazy" style="width:100%;min-height:400px;border-radius:8px;"></iframe></div>`;
|
|
407
|
+
case "mermaid":
|
|
408
|
+
return `<div class="intent-embed mermaid">${embedContent}</div>`;
|
|
409
|
+
case "svg":
|
|
410
|
+
return `<div class="intent-embed svg">${embedContent}</div>`;
|
|
411
|
+
case "video":
|
|
412
|
+
return `<div class="intent-embed video"><video src="${escapeHtml(sanitizeUrl(src))}" controls style="max-width:100%;border-radius:8px;"></video></div>`;
|
|
413
|
+
case "audio":
|
|
414
|
+
return `<div class="intent-embed audio"><audio src="${escapeHtml(sanitizeUrl(src))}" controls style="width:100%;"></audio></div>`;
|
|
415
|
+
default:
|
|
416
|
+
return `<div class="intent-embed unknown">${escapeHtml(embedContent || src)}</div>`;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
case "code":
|
|
420
|
+
return `<pre class="intent-code"><code>${escapeHtml(block.content)}</code></pre>`;
|
|
421
|
+
case "table": {
|
|
422
|
+
const headers = block.table?.headers;
|
|
423
|
+
const rows = block.table?.rows || [];
|
|
424
|
+
const thead = headers
|
|
425
|
+
? `<thead><tr>${headers
|
|
426
|
+
.map((h) => `<th class="intent-table-th">${escapeHtml(h)}</th>`)
|
|
427
|
+
.join("")}</tr></thead>`
|
|
428
|
+
: "";
|
|
429
|
+
const tbody = `<tbody>${rows
|
|
430
|
+
.map((row) => `<tr class="intent-row">${row
|
|
431
|
+
.map((c) => `<td class="intent-table-td">${escapeHtml(c)}</td>`)
|
|
432
|
+
.join("")}</tr>`)
|
|
433
|
+
.join("")}</tbody>`;
|
|
434
|
+
return `<table class="intent-table">${thead}${tbody}</table>`;
|
|
435
|
+
}
|
|
436
|
+
case "list-item": {
|
|
437
|
+
const listItemProps = block.properties || {};
|
|
438
|
+
const listItemMeta = [
|
|
439
|
+
listItemProps.owner &&
|
|
440
|
+
`<span class="intent-task-owner">${escapeHtml(String(listItemProps.owner))}</span>`,
|
|
441
|
+
listItemProps.due &&
|
|
442
|
+
`<span class="intent-task-due">${escapeHtml(String(listItemProps.due))}</span>`,
|
|
443
|
+
]
|
|
444
|
+
.filter(Boolean)
|
|
445
|
+
.join(" ");
|
|
446
|
+
return `<li class="intent-list-item">${content}${listItemMeta ? ` <span class="intent-task-meta">${listItemMeta}</span>` : ""}</li>`;
|
|
447
|
+
}
|
|
448
|
+
case "step-item":
|
|
449
|
+
return `<li class="intent-step-item">${content}</li>`;
|
|
450
|
+
case "step": {
|
|
451
|
+
const statusVal = String(props.status || "pending");
|
|
452
|
+
const statusClass = `intent-status-${statusVal}`;
|
|
453
|
+
const toolBadge = props.tool
|
|
454
|
+
? `<span class="intent-badge intent-badge-tool">${escapeHtml(String(props.tool))}</span>`
|
|
455
|
+
: "";
|
|
456
|
+
const statusBadge = `<span class="intent-badge ${statusClass}">${escapeHtml(statusVal)}</span>`;
|
|
457
|
+
const dependsArrow = props.depends
|
|
458
|
+
? `<span class="intent-step-depends">⤷ ${escapeHtml(String(props.depends))}</span>`
|
|
459
|
+
: "";
|
|
460
|
+
const stepId = props.id
|
|
461
|
+
? `<span class="intent-step-id">${escapeHtml(String(props.id))}</span>`
|
|
462
|
+
: "";
|
|
463
|
+
return `<div class="intent-step">
|
|
464
|
+
<span class="intent-step-icon">▶</span>
|
|
465
|
+
<span class="intent-step-content">${content}</span>
|
|
466
|
+
<span class="intent-step-meta">${stepId}${toolBadge}${statusBadge}${dependsArrow}</span>
|
|
467
|
+
</div>`;
|
|
468
|
+
}
|
|
469
|
+
case "decision": {
|
|
470
|
+
const ifExpr = props.if ? escapeHtml(String(props.if)) : "";
|
|
471
|
+
const thenTarget = props.then ? escapeHtml(String(props.then)) : "";
|
|
472
|
+
const elseTarget = props.else ? escapeHtml(String(props.else)) : "";
|
|
473
|
+
return `<div class="intent-decision">
|
|
474
|
+
<div class="intent-decision-diamond"></div>
|
|
475
|
+
<div class="intent-decision-body">
|
|
476
|
+
<div class="intent-decision-label">${content}</div>
|
|
477
|
+
${ifExpr ? `<div class="intent-decision-condition"><strong>if</strong> ${ifExpr}</div>` : ""}
|
|
478
|
+
<div class="intent-decision-branches">
|
|
479
|
+
${thenTarget ? `<span class="intent-decision-then">✓ then → ${thenTarget}</span>` : ""}
|
|
480
|
+
${elseTarget ? `<span class="intent-decision-else">✗ else → ${elseTarget}</span>` : ""}
|
|
481
|
+
</div>
|
|
482
|
+
</div>
|
|
483
|
+
</div>`;
|
|
484
|
+
}
|
|
485
|
+
case "trigger": {
|
|
486
|
+
const eventBadge = props.event
|
|
487
|
+
? `<span class="intent-badge intent-badge-event">${escapeHtml(String(props.event))}</span>`
|
|
488
|
+
: "";
|
|
489
|
+
return `<div class="intent-trigger">
|
|
490
|
+
<span class="intent-trigger-icon">⚡</span>
|
|
491
|
+
<span class="intent-trigger-content">${content}</span>
|
|
492
|
+
${eventBadge}
|
|
493
|
+
</div>`;
|
|
494
|
+
}
|
|
495
|
+
case "loop": {
|
|
496
|
+
const overVal = props.over ? escapeHtml(String(props.over)) : "";
|
|
497
|
+
const doVal = props.do ? escapeHtml(String(props.do)) : "";
|
|
498
|
+
return `<div class="intent-loop">
|
|
499
|
+
<span class="intent-loop-icon">🔁</span>
|
|
500
|
+
<span class="intent-loop-content">${content}</span>
|
|
501
|
+
${overVal ? `<span class="intent-loop-over">over: ${overVal}</span>` : ""}
|
|
502
|
+
${doVal ? `<span class="intent-loop-do">do: ${doVal}</span>` : ""}
|
|
503
|
+
</div>`;
|
|
504
|
+
}
|
|
505
|
+
case "checkpoint":
|
|
506
|
+
return `<div class="intent-checkpoint">
|
|
507
|
+
<span class="intent-checkpoint-flag">🚩</span>
|
|
508
|
+
<span class="intent-checkpoint-label">${content}</span>
|
|
509
|
+
<hr class="intent-checkpoint-line" />
|
|
510
|
+
</div>`;
|
|
511
|
+
case "audit":
|
|
512
|
+
return `<div class="intent-audit"><span class="intent-audit-prefix">audit:</span> ${content}${props.by ? ` | by: ${escapeHtml(String(props.by))}` : ""}${props.at ? ` | at: ${escapeHtml(String(props.at))}` : ""}</div>`;
|
|
513
|
+
case "error": {
|
|
514
|
+
const fallbackInfo = props.fallback
|
|
515
|
+
? `<span class="intent-error-fallback">fallback → ${escapeHtml(String(props.fallback))}</span>`
|
|
516
|
+
: "";
|
|
517
|
+
const notifyInfo = props.notify
|
|
518
|
+
? `<span class="intent-error-notify">notify: ${escapeHtml(String(props.notify))}</span>`
|
|
519
|
+
: "";
|
|
520
|
+
return `<div class="intent-callout intent-error-block"><span class="intent-callout-label">Error</span><div class="intent-callout-content">${content} ${fallbackInfo} ${notifyInfo}</div></div>`;
|
|
521
|
+
}
|
|
522
|
+
case "context": {
|
|
523
|
+
const ctxEntries = Object.entries(props);
|
|
524
|
+
if (ctxEntries.length === 0) {
|
|
525
|
+
return `<div class="intent-context"><code>${content}</code></div>`;
|
|
526
|
+
}
|
|
527
|
+
const ctxRows = ctxEntries
|
|
528
|
+
.map(([k, v]) => `<tr><td class="intent-context-key">${escapeHtml(k)}</td><td class="intent-context-val">${escapeHtml(String(v))}</td></tr>`)
|
|
529
|
+
.join("");
|
|
530
|
+
return `<table class="intent-context-table"><tbody>${ctxRows}</tbody></table>`;
|
|
531
|
+
}
|
|
532
|
+
case "progress": {
|
|
533
|
+
let value = 0;
|
|
534
|
+
let total = 100;
|
|
535
|
+
const progressMatch = block.content.match(/(\d+)\s*\/\s*(\d+)/);
|
|
536
|
+
if (progressMatch) {
|
|
537
|
+
value = parseInt(progressMatch[1], 10);
|
|
538
|
+
total = parseInt(progressMatch[2], 10);
|
|
539
|
+
}
|
|
540
|
+
if (props.value)
|
|
541
|
+
value = Number(props.value);
|
|
542
|
+
if (props.total)
|
|
543
|
+
total = Number(props.total);
|
|
544
|
+
const pct = total > 0 ? Math.round((value / total) * 100) : 0;
|
|
545
|
+
return `<div class="intent-progress">
|
|
546
|
+
<span class="intent-progress-label">${content}</span>
|
|
547
|
+
<div class="intent-progress-bar"><div class="intent-progress-fill" style="width:${pct}%"></div></div>
|
|
548
|
+
<span class="intent-progress-pct">${pct}%</span>
|
|
549
|
+
</div>`;
|
|
550
|
+
}
|
|
551
|
+
case "import": {
|
|
552
|
+
const asAlias = props.as ? ` as ${escapeHtml(String(props.as))}` : "";
|
|
553
|
+
return `<div class="intent-file-ref intent-import"><span class="intent-file-ref-icon">📥</span> import: ${content}${asAlias}</div>`;
|
|
554
|
+
}
|
|
555
|
+
case "export": {
|
|
556
|
+
const fmt = props.format ? ` (${escapeHtml(String(props.format))})` : "";
|
|
557
|
+
return `<div class="intent-file-ref intent-export"><span class="intent-file-ref-icon">📤</span> export: ${content}${fmt}</div>`;
|
|
558
|
+
}
|
|
559
|
+
case "signal": {
|
|
560
|
+
const phase = props.phase
|
|
561
|
+
? `<span class="intent-signal-phase">${escapeHtml(String(props.phase))}</span>`
|
|
562
|
+
: "";
|
|
563
|
+
const levelBadge = props.level
|
|
564
|
+
? `<span class="intent-badge intent-badge-level">${escapeHtml(String(props.level))}</span>`
|
|
565
|
+
: "";
|
|
566
|
+
return `<div class="intent-signal-block">
|
|
567
|
+
<span class="intent-signal-icon">📡</span>
|
|
568
|
+
<span class="intent-signal-content">${content}</span>
|
|
569
|
+
<span class="intent-signal-meta">${phase}${levelBadge}</span>
|
|
570
|
+
</div>`;
|
|
571
|
+
}
|
|
572
|
+
case "result": {
|
|
573
|
+
const statusVal = String(props.status || "success");
|
|
574
|
+
const statusClass = `intent-result-${statusVal}`;
|
|
575
|
+
const codeBadge = props.code
|
|
576
|
+
? `<span class="intent-badge intent-badge-code">${escapeHtml(String(props.code))}</span>`
|
|
577
|
+
: "";
|
|
578
|
+
const dataInfo = props.data
|
|
579
|
+
? `<div class="intent-result-data"><code>${escapeHtml(String(props.data))}</code></div>`
|
|
580
|
+
: "";
|
|
581
|
+
return `<div class="intent-result ${statusClass}">
|
|
582
|
+
<span class="intent-result-icon">✅</span>
|
|
583
|
+
<span class="intent-result-content">${content}</span>
|
|
584
|
+
${codeBadge}
|
|
585
|
+
${dataInfo}
|
|
586
|
+
</div>`;
|
|
587
|
+
}
|
|
588
|
+
case "handoff": {
|
|
589
|
+
const fromAgent = props.from
|
|
590
|
+
? `<span class="intent-handoff-agent intent-handoff-from">${escapeHtml(String(props.from))}</span>`
|
|
591
|
+
: "";
|
|
592
|
+
const toAgent = props.to
|
|
593
|
+
? `<span class="intent-handoff-agent intent-handoff-to">${escapeHtml(String(props.to))}</span>`
|
|
594
|
+
: "";
|
|
595
|
+
const arrow = fromAgent || toAgent
|
|
596
|
+
? `<span class="intent-handoff-arrow">${fromAgent} → ${toAgent}</span>`
|
|
597
|
+
: "";
|
|
598
|
+
return `<div class="intent-handoff">
|
|
599
|
+
<span class="intent-handoff-icon">🤝</span>
|
|
600
|
+
<span class="intent-handoff-content">${content}</span>
|
|
601
|
+
${arrow}
|
|
602
|
+
</div>`;
|
|
603
|
+
}
|
|
604
|
+
case "wait": {
|
|
605
|
+
const onVal = props.on
|
|
606
|
+
? `<span class="intent-badge intent-badge-event">${escapeHtml(String(props.on))}</span>`
|
|
607
|
+
: "";
|
|
608
|
+
const timeoutVal = props.timeout
|
|
609
|
+
? `<span class="intent-badge intent-badge-timeout">${escapeHtml(String(props.timeout))}</span>`
|
|
610
|
+
: "";
|
|
611
|
+
const fallbackVal = props.fallback
|
|
612
|
+
? `<span class="intent-wait-fallback">fallback → ${escapeHtml(String(props.fallback))}</span>`
|
|
613
|
+
: "";
|
|
614
|
+
return `<div class="intent-wait">
|
|
615
|
+
<span class="intent-wait-icon">⏳</span>
|
|
616
|
+
<span class="intent-wait-content">${content}</span>
|
|
617
|
+
<span class="intent-wait-meta">${onVal}${timeoutVal}${fallbackVal}</span>
|
|
618
|
+
</div>`;
|
|
619
|
+
}
|
|
620
|
+
case "parallel": {
|
|
621
|
+
const stepsVal = props.steps
|
|
622
|
+
? String(props.steps)
|
|
623
|
+
.split(",")
|
|
624
|
+
.map((s) => s.trim())
|
|
625
|
+
: [];
|
|
626
|
+
const stepBadges = stepsVal.length > 0
|
|
627
|
+
? stepsVal
|
|
628
|
+
.map((s) => `<span class="intent-badge intent-badge-parallel-step">${escapeHtml(s)}</span>`)
|
|
629
|
+
.join("")
|
|
630
|
+
: "";
|
|
631
|
+
const joinBadge = props.join
|
|
632
|
+
? `<span class="intent-badge intent-badge-join">join: ${escapeHtml(String(props.join))}</span>`
|
|
633
|
+
: "";
|
|
634
|
+
return `<div class="intent-parallel">
|
|
635
|
+
<span class="intent-parallel-icon">⏩</span>
|
|
636
|
+
<span class="intent-parallel-content">${content}</span>
|
|
637
|
+
<span class="intent-parallel-steps">${stepBadges}${joinBadge}</span>
|
|
638
|
+
</div>`;
|
|
639
|
+
}
|
|
640
|
+
case "retry": {
|
|
641
|
+
const maxVal = props.max
|
|
642
|
+
? `<span class="intent-badge intent-badge-retry-max">max: ${escapeHtml(String(props.max))}</span>`
|
|
643
|
+
: "";
|
|
644
|
+
const delayVal = props.delay
|
|
645
|
+
? `<span class="intent-badge intent-badge-retry-delay">delay: ${escapeHtml(String(props.delay))}ms</span>`
|
|
646
|
+
: "";
|
|
647
|
+
const backoffVal = props.backoff
|
|
648
|
+
? `<span class="intent-badge intent-badge-retry-backoff">${escapeHtml(String(props.backoff))}</span>`
|
|
649
|
+
: "";
|
|
650
|
+
return `<div class="intent-retry">
|
|
651
|
+
<span class="intent-retry-icon">🔄</span>
|
|
652
|
+
<span class="intent-retry-content">${content}</span>
|
|
653
|
+
<span class="intent-retry-meta">${maxVal}${delayVal}${backoffVal}</span>
|
|
654
|
+
</div>`;
|
|
655
|
+
}
|
|
656
|
+
case "gate": {
|
|
657
|
+
const approverBadge = props.approver
|
|
658
|
+
? `<span class="intent-badge intent-badge-approver">${escapeHtml(String(props.approver))}</span>`
|
|
659
|
+
: "";
|
|
660
|
+
const timeoutBadge = props.timeout
|
|
661
|
+
? `<span class="intent-badge intent-badge-timeout">${escapeHtml(String(props.timeout))}</span>`
|
|
662
|
+
: "";
|
|
663
|
+
const fallbackInfo = props.fallback
|
|
664
|
+
? `<span class="intent-gate-fallback">fallback → ${escapeHtml(String(props.fallback))}</span>`
|
|
665
|
+
: "";
|
|
666
|
+
return `<div class="intent-gate">
|
|
667
|
+
<div class="intent-gate-icon">🛑</div>
|
|
668
|
+
<div class="intent-gate-body">
|
|
669
|
+
<div class="intent-gate-label">${content}</div>
|
|
670
|
+
<div class="intent-gate-meta">${approverBadge}${timeoutBadge}${fallbackInfo}</div>
|
|
671
|
+
</div>
|
|
672
|
+
</div>`;
|
|
673
|
+
}
|
|
674
|
+
case "call": {
|
|
675
|
+
const inputVal = props.input
|
|
676
|
+
? `<span class="intent-call-input">input: ${escapeHtml(String(props.input))}</span>`
|
|
677
|
+
: "";
|
|
678
|
+
const outputVal = props.output
|
|
679
|
+
? `<span class="intent-call-output">output: ${escapeHtml(String(props.output))}</span>`
|
|
680
|
+
: "";
|
|
681
|
+
return `<div class="intent-call">
|
|
682
|
+
<span class="intent-call-icon">📞</span>
|
|
683
|
+
<span class="intent-call-content">${content}</span>
|
|
684
|
+
<span class="intent-call-meta">${inputVal}${outputVal}</span>
|
|
685
|
+
</div>`;
|
|
686
|
+
}
|
|
687
|
+
case "policy": {
|
|
688
|
+
const conditions = [];
|
|
689
|
+
if (props.if)
|
|
690
|
+
conditions.push(`<span class="it-policy-condition">if ${escapeHtml(String(props.if))}</span>`);
|
|
691
|
+
if (props.always)
|
|
692
|
+
conditions.push(`<span class="it-policy-always">always: ${escapeHtml(String(props.always))}</span>`);
|
|
693
|
+
if (props.never)
|
|
694
|
+
conditions.push(`<span class="it-policy-never">never: ${escapeHtml(String(props.never))}</span>`);
|
|
695
|
+
if (props.action)
|
|
696
|
+
conditions.push(`<span class="it-policy-action">→ ${escapeHtml(String(props.action))}</span>`);
|
|
697
|
+
if (props.requires)
|
|
698
|
+
conditions.push(`<span class="it-policy-requires">requires: ${escapeHtml(String(props.requires))}</span>`);
|
|
699
|
+
if (props.notify)
|
|
700
|
+
conditions.push(`<span class="it-policy-notify">notify: ${escapeHtml(String(props.notify))}</span>`);
|
|
701
|
+
return `<div class="it-block it-policy">
|
|
702
|
+
<div class="it-policy-name">${content}</div>
|
|
703
|
+
<div class="it-policy-rules">${conditions.join(" ")}</div>
|
|
704
|
+
</div>`;
|
|
705
|
+
}
|
|
706
|
+
case "input": {
|
|
707
|
+
const inputType = props.type ? escapeHtml(String(props.type)) : "string";
|
|
708
|
+
const inputRequired = props.required === "true" || String(props.required) === "true";
|
|
709
|
+
const inputDef = props.default != null
|
|
710
|
+
? `<span class="it-input-default">= ${escapeHtml(String(props.default))}</span>`
|
|
711
|
+
: "";
|
|
712
|
+
return `<div class="it-input"><span class="it-input-name">${content}</span><span class="it-input-type">${inputType}</span>${inputRequired ? '<span class="it-input-required">required</span>' : ""}${inputDef}</div>`;
|
|
713
|
+
}
|
|
714
|
+
case "output": {
|
|
715
|
+
const outputType = props.type ? escapeHtml(String(props.type)) : "any";
|
|
716
|
+
const outputFormat = props.format
|
|
717
|
+
? `<span class="it-output-format">${escapeHtml(String(props.format))}</span>`
|
|
718
|
+
: "";
|
|
719
|
+
return `<div class="it-output"><span class="it-output-name">${content}</span><span class="it-output-type">${outputType}</span>${outputFormat}</div>`;
|
|
720
|
+
}
|
|
721
|
+
case "tool": {
|
|
722
|
+
const toolApi = props.api
|
|
723
|
+
? `<code class="it-tool-api">${escapeHtml(String(props.api))}</code>`
|
|
724
|
+
: "";
|
|
725
|
+
const toolMethod = props.method
|
|
726
|
+
? `<span class="it-tool-method">${escapeHtml(String(props.method))}</span>`
|
|
727
|
+
: "";
|
|
728
|
+
return `<div class="it-tool"><span class="it-tool-name">${content}</span>${toolApi}${toolMethod}</div>`;
|
|
729
|
+
}
|
|
730
|
+
case "prompt": {
|
|
731
|
+
const promptModel = props.model
|
|
732
|
+
? `<span class="it-prompt-model">${escapeHtml(String(props.model))}</span>`
|
|
733
|
+
: "";
|
|
734
|
+
return `<div class="it-prompt">${promptModel}<div class="it-prompt-content">${content}</div></div>`;
|
|
735
|
+
}
|
|
736
|
+
case "memory": {
|
|
737
|
+
const memoryScope = props.scope
|
|
738
|
+
? escapeHtml(String(props.scope))
|
|
739
|
+
: "session";
|
|
740
|
+
return `<div class="it-memory"><span class="it-memory-scope">${memoryScope}</span><span class="it-memory-content">${content}</span></div>`;
|
|
741
|
+
}
|
|
742
|
+
case "assert": {
|
|
743
|
+
const expectExpr = props.expect
|
|
744
|
+
? `<code class="it-assert-expect">${escapeHtml(String(props.expect))}</code>`
|
|
745
|
+
: "";
|
|
746
|
+
const severity = props.severity
|
|
747
|
+
? escapeHtml(String(props.severity))
|
|
748
|
+
: "error";
|
|
749
|
+
return `<div class="it-assert it-assert-${severity}"><span class="it-assert-label">ASSERT</span><span class="it-assert-content">${content}</span>${expectExpr}</div>`;
|
|
750
|
+
}
|
|
751
|
+
case "secret":
|
|
752
|
+
return `<div class="it-secret"><span class="it-secret-label">SECRET</span><span class="it-secret-value">${"\u2022".repeat(8)}</span></div>`;
|
|
753
|
+
case "font":
|
|
754
|
+
return "";
|
|
755
|
+
case "page":
|
|
756
|
+
return "";
|
|
757
|
+
case "break":
|
|
758
|
+
return `<div class="it-page-break" aria-hidden="true" style="display:none"></div>`;
|
|
759
|
+
case "history":
|
|
760
|
+
return "";
|
|
761
|
+
case "byline": {
|
|
762
|
+
const author = content;
|
|
763
|
+
const date = props.date ? escapeHtml(String(props.date)) : "";
|
|
764
|
+
const publication = props.publication
|
|
765
|
+
? escapeHtml(String(props.publication))
|
|
766
|
+
: "";
|
|
767
|
+
const role = props.role ? escapeHtml(String(props.role)) : "";
|
|
768
|
+
const metaParts = [role, date, publication].filter(Boolean);
|
|
769
|
+
return `<div class="it-byline"><span class="it-byline-author">${author}</span>${metaParts.length > 0 ? `<span class="it-byline-meta">${metaParts.join(" · ")}</span>` : ""}</div>`;
|
|
770
|
+
}
|
|
771
|
+
case "epigraph": {
|
|
772
|
+
const by = props.by
|
|
773
|
+
? `<span class="it-epigraph-by">— ${escapeHtml(String(props.by))}</span>`
|
|
774
|
+
: "";
|
|
775
|
+
return `<blockquote class="it-epigraph"><p>${content}</p>${by}</blockquote>`;
|
|
776
|
+
}
|
|
777
|
+
case "caption":
|
|
778
|
+
return `<figcaption class="it-caption">${content}</figcaption>`;
|
|
779
|
+
case "footnote":
|
|
780
|
+
return "";
|
|
781
|
+
case "toc":
|
|
782
|
+
return "";
|
|
783
|
+
case "dedication":
|
|
784
|
+
return `<div class="it-dedication">${content}</div>`;
|
|
785
|
+
case "track":
|
|
786
|
+
return "";
|
|
787
|
+
case "meta":
|
|
788
|
+
return "";
|
|
789
|
+
case "header":
|
|
790
|
+
return "";
|
|
791
|
+
case "footer":
|
|
792
|
+
return "";
|
|
793
|
+
case "watermark":
|
|
794
|
+
return "";
|
|
795
|
+
case "approve": {
|
|
796
|
+
const approveBy = props.by ? escapeHtml(String(props.by)) : "Unknown";
|
|
797
|
+
const approveRole = props.role ? escapeHtml(String(props.role)) : "";
|
|
798
|
+
const approveAt = props.at ? formatTrustDate(String(props.at)) : "";
|
|
799
|
+
return `<div class="it-approval">
|
|
800
|
+
<span class="it-approval__icon">✓</span>
|
|
801
|
+
<div class="it-approval__body">
|
|
802
|
+
<span class="it-approval__label">APPROVED</span>
|
|
803
|
+
<span class="it-approval__who">${approveBy}${approveRole ? ` — ${approveRole}` : ""}</span>
|
|
804
|
+
${approveAt ? `<span class="it-approval__date">${approveAt}</span>` : ""}
|
|
805
|
+
</div>
|
|
806
|
+
</div>`;
|
|
807
|
+
}
|
|
808
|
+
case "sign": {
|
|
809
|
+
const signerName = escapeHtml(block.content);
|
|
810
|
+
const signRole = props.role ? escapeHtml(String(props.role)) : "";
|
|
811
|
+
const signAt = props.at ? formatTrustDate(String(props.at)) : "";
|
|
812
|
+
const signValid = props.hash ? true : false;
|
|
813
|
+
return `<div class="it-signature${signValid ? " it-signature--valid" : " it-signature--invalid"}">
|
|
814
|
+
<span class="it-signature__name">${signerName}</span>
|
|
815
|
+
${signRole ? `<span class="it-signature__role">${signRole}</span>` : ""}
|
|
816
|
+
${signAt ? `<span class="it-signature__date">${signAt}</span>` : ""}
|
|
817
|
+
<span class="it-signature__status">${signValid ? "✅ Verified" : "❌ Invalid"}</span>
|
|
818
|
+
</div>`;
|
|
819
|
+
}
|
|
820
|
+
case "freeze": {
|
|
821
|
+
const freezeAt = props.at ? formatTrustDate(String(props.at)) : "";
|
|
822
|
+
const freezeHash = props.hash
|
|
823
|
+
? escapeHtml(String(props.hash)).slice(0, 20) + "..."
|
|
824
|
+
: "";
|
|
825
|
+
return `<div class="it-sealed-banner">
|
|
826
|
+
<span class="it-sealed-banner__icon">🔒</span>
|
|
827
|
+
<span class="it-sealed-banner__text">Sealed Document</span>
|
|
828
|
+
${freezeAt ? `<span class="it-sealed-banner__date">${freezeAt}</span>` : ""}
|
|
829
|
+
${freezeHash ? `<span class="it-sealed-banner__hash">${freezeHash}</span>` : ""}
|
|
830
|
+
</div>`;
|
|
831
|
+
}
|
|
832
|
+
case "revision":
|
|
833
|
+
return "";
|
|
834
|
+
case "def": {
|
|
835
|
+
const meaning = props.meaning ? escapeHtml(String(props.meaning)) : "";
|
|
836
|
+
const abbr = props.abbr
|
|
837
|
+
? ` <span class="it-def-abbr">(${escapeHtml(String(props.abbr))})</span>`
|
|
838
|
+
: "";
|
|
839
|
+
return `<div class="it-def">
|
|
840
|
+
<dt class="it-def-term">${content}${abbr}</dt>
|
|
841
|
+
<dd class="it-def-meaning">${meaning}</dd>
|
|
842
|
+
</div>`;
|
|
843
|
+
}
|
|
844
|
+
case "metric": {
|
|
845
|
+
const val = props.value != null ? escapeHtml(String(props.value)) : "";
|
|
846
|
+
const unit = props.unit ? escapeHtml(String(props.unit)) : "";
|
|
847
|
+
const target = props.target != null ? String(props.target) : "";
|
|
848
|
+
const trend = props.trend ? String(props.trend) : "";
|
|
849
|
+
const period = props.period ? escapeHtml(String(props.period)) : "";
|
|
850
|
+
if (!target && !trend && !period) {
|
|
851
|
+
const isTotal = /\b(total|balance due|amount due|grand)\b/i.test(String(block.content || ""));
|
|
852
|
+
const valueText = [val, unit].filter(Boolean).join(" ");
|
|
853
|
+
return `<div class="it-metric-row${isTotal ? " it-metric-row--total" : ""}">
|
|
854
|
+
<span class="it-metric-row__label">${content}</span>
|
|
855
|
+
<span class="it-metric-row__value">${valueText}</span>
|
|
856
|
+
</div>`;
|
|
857
|
+
}
|
|
858
|
+
const trendIcon = trend === "up"
|
|
859
|
+
? "↑"
|
|
860
|
+
: trend === "down"
|
|
861
|
+
? "↓"
|
|
862
|
+
: trend === "stable"
|
|
863
|
+
? "→"
|
|
864
|
+
: "";
|
|
865
|
+
let colorClass = "it-metric-neutral";
|
|
866
|
+
if (target && val) {
|
|
867
|
+
colorClass =
|
|
868
|
+
Number(val) >= Number(target) ? "it-metric-green" : "it-metric-red";
|
|
869
|
+
}
|
|
870
|
+
return `<div class="it-metric ${colorClass}">
|
|
871
|
+
<div class="it-metric-name">${content}</div>
|
|
872
|
+
<div class="it-metric-value">${val}<span class="it-metric-unit">${unit}</span></div>
|
|
873
|
+
${target ? `<div class="it-metric-target">Target: ${escapeHtml(target)}</div>` : ""}
|
|
874
|
+
${trendIcon ? `<div class="it-metric-trend">${trendIcon}</div>` : ""}
|
|
875
|
+
${period ? `<div class="it-metric-period">${period}</div>` : ""}
|
|
876
|
+
</div>`;
|
|
877
|
+
}
|
|
878
|
+
case "amendment": {
|
|
879
|
+
const amendRef = props.ref ? escapeHtml(String(props.ref)) : "";
|
|
880
|
+
const amendSection = props.section
|
|
881
|
+
? escapeHtml(String(props.section))
|
|
882
|
+
: "";
|
|
883
|
+
const amendWas = props.was ? escapeHtml(String(props.was)) : "";
|
|
884
|
+
const amendNow = props.now ? escapeHtml(String(props.now)) : "";
|
|
885
|
+
const amendBy = props.by ? escapeHtml(String(props.by)) : "";
|
|
886
|
+
const amendAt = props.at ? formatTrustDate(String(props.at)) : "";
|
|
887
|
+
return `<div class="it-amendment">
|
|
888
|
+
<div class="it-amendment-header">
|
|
889
|
+
<span class="it-amendment-icon">✏️</span>
|
|
890
|
+
<span class="it-amendment-ref">${amendRef}</span>
|
|
891
|
+
<span class="it-amendment-title">${content}</span>
|
|
892
|
+
</div>
|
|
893
|
+
${amendSection ? `<div class="it-amendment-section">Section: ${amendSection}</div>` : ""}
|
|
894
|
+
${amendWas ? `<div class="it-amendment-was">Was: ${amendWas}</div>` : ""}
|
|
895
|
+
${amendNow ? `<div class="it-amendment-now">Now: ${amendNow}</div>` : ""}
|
|
896
|
+
<div class="it-amendment-meta">
|
|
897
|
+
${amendBy ? `<span class="it-amendment-by">${amendBy}</span>` : ""}
|
|
898
|
+
${amendAt ? `<span class="it-amendment-at">${amendAt}</span>` : ""}
|
|
899
|
+
</div>
|
|
900
|
+
</div>`;
|
|
901
|
+
}
|
|
902
|
+
case "figure": {
|
|
903
|
+
const figSrc = props.src
|
|
904
|
+
? escapeHtml(sanitizeUrl(String(props.src)))
|
|
905
|
+
: "";
|
|
906
|
+
const figCaption = props.caption ? escapeHtml(String(props.caption)) : "";
|
|
907
|
+
const figNum = props.num ? escapeHtml(String(props.num)) : "";
|
|
908
|
+
const figWidth = props.width
|
|
909
|
+
? `width:${escapeHtml(String(props.width))};`
|
|
910
|
+
: "";
|
|
911
|
+
const figAlign = props.align ? String(props.align) : "center";
|
|
912
|
+
const figAlt = props.alt
|
|
913
|
+
? escapeHtml(String(props.alt))
|
|
914
|
+
: escapeHtml(block.content);
|
|
915
|
+
const numPrefix = figNum ? `Figure ${figNum}: ` : "";
|
|
916
|
+
return `<figure class="it-figure" style="text-align:${escapeHtml(figAlign)};">
|
|
917
|
+
${figSrc ? `<img src="${figSrc}" alt="${figAlt}" style="${figWidth}max-width:100%;" />` : ""}
|
|
918
|
+
<figcaption class="it-figure-caption">${numPrefix}${figCaption}</figcaption>
|
|
919
|
+
</figure>`;
|
|
920
|
+
}
|
|
921
|
+
case "signline": {
|
|
922
|
+
const sigLabel = props.label
|
|
923
|
+
? escapeHtml(String(props.label))
|
|
924
|
+
: "Signature";
|
|
925
|
+
const sigRole = props.role ? escapeHtml(String(props.role)) : "";
|
|
926
|
+
const sigDateLine = String(props["date-line"]) === "true";
|
|
927
|
+
const sigWidth = props.width ? escapeHtml(String(props.width)) : "60%";
|
|
928
|
+
return `<div class="it-signline" style="width:${sigWidth};">
|
|
929
|
+
<div class="it-signline-label">${sigLabel}</div>
|
|
930
|
+
<div class="it-signline-rule"></div>
|
|
931
|
+
<div class="it-signline-name">${content}</div>
|
|
932
|
+
${sigRole ? `<div class="it-signline-role">${sigRole}</div>` : ""}
|
|
933
|
+
${sigDateLine ? `<div class="it-signline-date">Date: _______________</div>` : ""}
|
|
934
|
+
</div>`;
|
|
935
|
+
}
|
|
936
|
+
case "contact": {
|
|
937
|
+
const cRole = props.role ? escapeHtml(String(props.role)) : "";
|
|
938
|
+
const cEmail = props.email ? String(props.email) : "";
|
|
939
|
+
const cPhone = props.phone ? String(props.phone) : "";
|
|
940
|
+
const cOrg = props.org ? escapeHtml(String(props.org)) : "";
|
|
941
|
+
const cUrl2 = props.url ? String(props.url) : "";
|
|
942
|
+
return `<div class="it-contact">
|
|
943
|
+
<div class="it-contact-name">${content}</div>
|
|
944
|
+
${cRole ? `<div class="it-contact-role">${cRole}</div>` : ""}
|
|
945
|
+
${cOrg ? `<div class="it-contact-org">${cOrg}</div>` : ""}
|
|
946
|
+
${cEmail ? `<div class="it-contact-email"><a href="mailto:${escapeHtml(cEmail)}">${escapeHtml(cEmail)}</a></div>` : ""}
|
|
947
|
+
${cPhone ? `<div class="it-contact-phone"><a href="tel:${escapeHtml(cPhone)}">${escapeHtml(cPhone)}</a></div>` : ""}
|
|
948
|
+
${cUrl2 ? `<div class="it-contact-url"><a href="${escapeHtml(sanitizeUrl(cUrl2))}">${escapeHtml(cUrl2)}</a></div>` : ""}
|
|
949
|
+
</div>`;
|
|
950
|
+
}
|
|
951
|
+
case "deadline": {
|
|
952
|
+
const dlDate = props.date ? String(props.date) : "";
|
|
953
|
+
const dlConsequence = props.consequence
|
|
954
|
+
? escapeHtml(String(props.consequence))
|
|
955
|
+
: "";
|
|
956
|
+
const dlAuthority = props.authority
|
|
957
|
+
? escapeHtml(String(props.authority))
|
|
958
|
+
: "";
|
|
959
|
+
const dlOwner = props.owner ? escapeHtml(String(props.owner)) : "";
|
|
960
|
+
let dlColorClass = "it-deadline-green";
|
|
961
|
+
if (dlDate) {
|
|
962
|
+
const dlDateObj = new Date(dlDate);
|
|
963
|
+
if (!isNaN(dlDateObj.getTime())) {
|
|
964
|
+
const daysUntil = (dlDateObj.getTime() - Date.now()) / (1000 * 60 * 60 * 24);
|
|
965
|
+
if (daysUntil < 7)
|
|
966
|
+
dlColorClass = "it-deadline-red";
|
|
967
|
+
else if (daysUntil < 30)
|
|
968
|
+
dlColorClass = "it-deadline-amber";
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
return `<div class="it-deadline ${dlColorClass}">
|
|
972
|
+
<div class="it-deadline-name">${content}</div>
|
|
973
|
+
${dlDate ? `<div class="it-deadline-date">${escapeHtml(dlDate)}</div>` : ""}
|
|
974
|
+
${dlConsequence ? `<div class="it-deadline-consequence">${dlConsequence}</div>` : ""}
|
|
975
|
+
${dlOwner ? `<div class="it-deadline-owner">${dlOwner}</div>` : ""}
|
|
976
|
+
${dlAuthority ? `<div class="it-deadline-authority">${dlAuthority}</div>` : ""}
|
|
977
|
+
</div>`;
|
|
978
|
+
}
|
|
979
|
+
case "extension": {
|
|
980
|
+
const xType = props["x-type"]
|
|
981
|
+
? escapeHtml(String(props["x-type"]))
|
|
982
|
+
: "unknown";
|
|
983
|
+
const xNs = props["x-ns"] ? escapeHtml(String(props["x-ns"])) : "ext";
|
|
984
|
+
return `<div class="it-extension it-ext-${xNs} it-ext-${xType}" data-x-type="${xType}" data-x-ns="${xNs}"${styleAttr}>${content}</div>`;
|
|
985
|
+
}
|
|
986
|
+
default:
|
|
987
|
+
return `<div class="intent-unknown">
|
|
988
|
+
<small class="intent-unknown-type">[${block.type}]</small> ${content}
|
|
989
|
+
</div>`;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
function slugify(text) {
|
|
993
|
+
return text
|
|
994
|
+
.toLowerCase()
|
|
995
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
996
|
+
.replace(/^-|-$/g, "");
|
|
997
|
+
}
|
|
998
|
+
function collectSections(blocks, depth) {
|
|
999
|
+
const entries = [];
|
|
1000
|
+
for (const block of blocks) {
|
|
1001
|
+
if (block.type === "section") {
|
|
1002
|
+
entries.push({
|
|
1003
|
+
level: 1,
|
|
1004
|
+
content: block.content,
|
|
1005
|
+
slug: slugify(block.content),
|
|
1006
|
+
});
|
|
1007
|
+
if (depth >= 2 && block.children) {
|
|
1008
|
+
for (const child of block.children) {
|
|
1009
|
+
if (child.type === "sub") {
|
|
1010
|
+
entries.push({
|
|
1011
|
+
level: 2,
|
|
1012
|
+
content: child.content,
|
|
1013
|
+
slug: slugify(child.content),
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
return entries;
|
|
1021
|
+
}
|
|
1022
|
+
function collectFootnotes(blocks) {
|
|
1023
|
+
const footnotes = [];
|
|
1024
|
+
for (const block of blocks) {
|
|
1025
|
+
if (block.type === "footnote")
|
|
1026
|
+
footnotes.push(block);
|
|
1027
|
+
if (block.children)
|
|
1028
|
+
footnotes.push(...collectFootnotes(block.children));
|
|
1029
|
+
}
|
|
1030
|
+
return footnotes;
|
|
1031
|
+
}
|
|
1032
|
+
function renderBlocks(blocks, allBlocks) {
|
|
1033
|
+
const topBlocks = allBlocks || blocks;
|
|
1034
|
+
let html = "";
|
|
1035
|
+
let i = 0;
|
|
1036
|
+
while (i < blocks.length) {
|
|
1037
|
+
const block = blocks[i];
|
|
1038
|
+
if (block.type === "toc") {
|
|
1039
|
+
const depth = Number(block.properties?.depth || 2);
|
|
1040
|
+
const title = String(block.properties?.title || "Contents");
|
|
1041
|
+
const entries = collectSections(topBlocks, depth);
|
|
1042
|
+
let tocHtml = `<nav class="it-toc"><h2 class="it-toc-title">${escapeHtml(title)}</h2><ol>`;
|
|
1043
|
+
for (const entry of entries) {
|
|
1044
|
+
const indent = entry.level === 2 ? ' class="it-toc-sub"' : "";
|
|
1045
|
+
tocHtml += `<li${indent}><a href="#${entry.slug}">${escapeHtml(entry.content)}</a></li>`;
|
|
1046
|
+
}
|
|
1047
|
+
tocHtml += `</ol></nav>`;
|
|
1048
|
+
html += tocHtml;
|
|
1049
|
+
i++;
|
|
1050
|
+
continue;
|
|
1051
|
+
}
|
|
1052
|
+
if (block.type === "list-item") {
|
|
1053
|
+
html += '<ul class="intent-list">';
|
|
1054
|
+
while (i < blocks.length && blocks[i].type === "list-item") {
|
|
1055
|
+
html += renderBlock(blocks[i]);
|
|
1056
|
+
i++;
|
|
1057
|
+
}
|
|
1058
|
+
html += "</ul>";
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
if (block.type === "step-item") {
|
|
1062
|
+
html += '<ol class="intent-list">';
|
|
1063
|
+
while (i < blocks.length && blocks[i].type === "step-item") {
|
|
1064
|
+
html += renderBlock(blocks[i]);
|
|
1065
|
+
i++;
|
|
1066
|
+
}
|
|
1067
|
+
html += "</ol>";
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
html += renderBlock(block);
|
|
1071
|
+
if ((block.type === "section" || block.type === "sub") &&
|
|
1072
|
+
block.children &&
|
|
1073
|
+
block.children.length > 0) {
|
|
1074
|
+
html += renderBlocks(block.children, topBlocks);
|
|
1075
|
+
}
|
|
1076
|
+
i++;
|
|
1077
|
+
}
|
|
1078
|
+
return html;
|
|
1079
|
+
}
|
|
1080
|
+
function renderHTML(document, options) {
|
|
1081
|
+
if (!document || !document.blocks)
|
|
1082
|
+
return "";
|
|
1083
|
+
const bodyHtml = renderBlocks(document.blocks);
|
|
1084
|
+
const footnotes = collectFootnotes(document.blocks);
|
|
1085
|
+
let footnotesHtml = "";
|
|
1086
|
+
if (footnotes.length > 0) {
|
|
1087
|
+
const items = footnotes
|
|
1088
|
+
.map((fn) => {
|
|
1089
|
+
const num = escapeHtml(fn.content);
|
|
1090
|
+
const text = fn.properties?.text
|
|
1091
|
+
? escapeHtml(String(fn.properties.text))
|
|
1092
|
+
: escapeHtml(fn.content);
|
|
1093
|
+
return `<li id="fn-${num}" value="${num}">${text}</li>`;
|
|
1094
|
+
})
|
|
1095
|
+
.join("");
|
|
1096
|
+
footnotesHtml = `<div class="it-footnotes"><ol>${items}</ol></div>`;
|
|
1097
|
+
}
|
|
1098
|
+
const html = bodyHtml + footnotesHtml;
|
|
1099
|
+
const themeRef = options?.theme ?? document.metadata?.meta?.theme ?? undefined;
|
|
1100
|
+
const theme = resolveThemeSync(themeRef);
|
|
1101
|
+
const themeCSS = (0, theme_1.generateThemeCSS)(theme, "web");
|
|
1102
|
+
const docStyleCSS = documentStyleCSS(document);
|
|
1103
|
+
const direction = document.metadata?.language === "rtl" ? 'dir="rtl"' : 'dir="ltr"';
|
|
1104
|
+
return `<div class="intent-document" ${direction}>
|
|
1105
|
+
<style>
|
|
1106
|
+
${document_css_1.DOCUMENT_CSS}${themeCSS}
|
|
1107
|
+
${docStyleCSS}
|
|
1108
|
+
</style>
|
|
1109
|
+
${html}
|
|
1110
|
+
</div>`;
|
|
1111
|
+
}
|
|
1112
|
+
function buildDynamicCSS(doc) {
|
|
1113
|
+
const fontBlock = doc.blocks.find((b) => b.type === "font");
|
|
1114
|
+
const pageBlock = doc.blocks.find((b) => b.type === "page");
|
|
1115
|
+
const fontFamily = String(fontBlock?.properties?.family || "Georgia, serif");
|
|
1116
|
+
const fontSize = String(fontBlock?.properties?.size || "12pt");
|
|
1117
|
+
const leading = String(fontBlock?.properties?.leading || "1.6");
|
|
1118
|
+
const rawSize = String(pageBlock?.properties?.size || "A4");
|
|
1119
|
+
let pageSize;
|
|
1120
|
+
let widthHint = rawSize;
|
|
1121
|
+
if (rawSize === "custom") {
|
|
1122
|
+
const w = String(pageBlock?.properties?.width || "210mm");
|
|
1123
|
+
const h = String(pageBlock?.properties?.height || "297mm");
|
|
1124
|
+
widthHint = w;
|
|
1125
|
+
pageSize = `${escapeHtml(w)} ${escapeHtml(h)}`;
|
|
1126
|
+
}
|
|
1127
|
+
else {
|
|
1128
|
+
pageSize = escapeHtml(PAPER_SIZES[rawSize] || rawSize);
|
|
1129
|
+
}
|
|
1130
|
+
const explicitMargin = pageBlock?.properties?.margin ?? pageBlock?.properties?.margins;
|
|
1131
|
+
const widthMatch = /(\d+(?:\.\d+)?)\s*mm/.exec(widthHint);
|
|
1132
|
+
const widthMm = widthMatch ? parseFloat(widthMatch[1]) : Infinity;
|
|
1133
|
+
const margins = String(explicitMargin ?? (widthMm <= 120 ? "4mm" : "20mm"));
|
|
1134
|
+
return `@page{size:${pageSize};margin:${escapeHtml(margins)};}body.it-print{font-family:${escapeHtml(fontFamily)};font-size:${escapeHtml(fontSize)};line-height:${escapeHtml(leading)};}`;
|
|
1135
|
+
}
|
|
1136
|
+
function renderPrint(doc, options) {
|
|
1137
|
+
if (!doc || !doc.blocks)
|
|
1138
|
+
return "";
|
|
1139
|
+
const bodyHtml = renderBlocks(doc.blocks);
|
|
1140
|
+
const footnotes = collectFootnotes(doc.blocks);
|
|
1141
|
+
let footnotesHtml = "";
|
|
1142
|
+
if (footnotes.length > 0) {
|
|
1143
|
+
const items = footnotes
|
|
1144
|
+
.map((fn) => {
|
|
1145
|
+
const num = escapeHtml(fn.content);
|
|
1146
|
+
const text = fn.properties?.text
|
|
1147
|
+
? escapeHtml(String(fn.properties.text))
|
|
1148
|
+
: escapeHtml(fn.content);
|
|
1149
|
+
return `<li id="fn-${num}" value="${num}">${text}</li>`;
|
|
1150
|
+
})
|
|
1151
|
+
.join("");
|
|
1152
|
+
footnotesHtml = `<div class="it-footnotes"><ol>${items}</ol></div>`;
|
|
1153
|
+
}
|
|
1154
|
+
const html = bodyHtml + footnotesHtml;
|
|
1155
|
+
const dynamicCSS = buildDynamicCSS(doc);
|
|
1156
|
+
const direction = doc.metadata?.language === "rtl" ? 'dir="rtl"' : 'dir="ltr"';
|
|
1157
|
+
const themeRef = options?.theme ?? doc.metadata?.meta?.theme ?? undefined;
|
|
1158
|
+
const theme = resolveThemeSync(themeRef);
|
|
1159
|
+
const themeCSS = (0, theme_1.generateThemeCSS)(theme, "print");
|
|
1160
|
+
const layout = collectPrintLayout(doc);
|
|
1161
|
+
let headerFooterCSS = "";
|
|
1162
|
+
if (layout.header) {
|
|
1163
|
+
const hp = layout.header.properties || {};
|
|
1164
|
+
const left = cssContentValue(String(hp.left ?? ""));
|
|
1165
|
+
const center = cssContentValue(String(hp.center ?? ""));
|
|
1166
|
+
const right = cssContentValue(String(hp.right ?? ""));
|
|
1167
|
+
headerFooterCSS += `@page{@top-left{content:${left};}@top-center{content:${center};}@top-right{content:${right};}}`;
|
|
1168
|
+
if (String(hp["skip-first"]) === "true") {
|
|
1169
|
+
headerFooterCSS += `@page:first{@top-left{content:"";}@top-center{content:"";}@top-right{content:"";}}`;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
if (layout.footer) {
|
|
1173
|
+
const fp = layout.footer.properties || {};
|
|
1174
|
+
const left = cssContentValue(String(fp.left ?? ""));
|
|
1175
|
+
const center = cssContentValue(String(fp.center ?? ""));
|
|
1176
|
+
const right = cssContentValue(String(fp.right ?? ""));
|
|
1177
|
+
headerFooterCSS += `@page{@bottom-left{content:${left};}@bottom-center{content:${center};}@bottom-right{content:${right};}}`;
|
|
1178
|
+
if (String(fp["skip-first"]) === "true") {
|
|
1179
|
+
headerFooterCSS += `@page:first{@bottom-left{content:"";}@bottom-center{content:"";}@bottom-right{content:"";}}`;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
let breakCSS = "";
|
|
1183
|
+
for (const br of layout.breaks) {
|
|
1184
|
+
const before = br.properties?.before ? String(br.properties.before) : "";
|
|
1185
|
+
const keep = br.properties?.keep ? String(br.properties.keep) : "";
|
|
1186
|
+
if (before) {
|
|
1187
|
+
breakCSS += `.it-${escapeHtml(before)}{page-break-before:always;}`;
|
|
1188
|
+
}
|
|
1189
|
+
if (keep) {
|
|
1190
|
+
breakCSS += `.it-${escapeHtml(keep)}{break-inside:avoid;}`;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
let watermarkHtml = "";
|
|
1194
|
+
if (layout.watermark && layout.watermark.content) {
|
|
1195
|
+
const wp = layout.watermark.properties || {};
|
|
1196
|
+
const color = wp.color ? String(wp.color) : "rgba(0,0,0,0.08)";
|
|
1197
|
+
const angle = wp.angle ? String(wp.angle) : "-45";
|
|
1198
|
+
const size = wp.size ? String(wp.size) : "80pt";
|
|
1199
|
+
watermarkHtml = `<div class="it-watermark" style="position:fixed;top:50%;left:50%;transform:translate(-50%,-50%) rotate(${escapeHtml(angle)}deg);font-size:${escapeHtml(size)};color:${escapeHtml(color)};z-index:-1;pointer-events:none;white-space:nowrap;">${escapeHtml(layout.watermark.content)}</div>`;
|
|
1200
|
+
}
|
|
1201
|
+
const pageBlock = layout.page;
|
|
1202
|
+
const printMode = pageBlock?.properties?.["print-mode"]
|
|
1203
|
+
? String(pageBlock.properties["print-mode"])
|
|
1204
|
+
: "full";
|
|
1205
|
+
const bodyClass = printMode === "minimal-ink" ? "it-print it-print-minimal" : "it-print";
|
|
1206
|
+
let backwardCompatCSS = "";
|
|
1207
|
+
if (!layout.header && pageBlock?.properties?.header) {
|
|
1208
|
+
const h = cssContentValue(String(pageBlock.properties.header));
|
|
1209
|
+
backwardCompatCSS += `@page{@top-center{content:${h};}}`;
|
|
1210
|
+
}
|
|
1211
|
+
if (!layout.footer && pageBlock?.properties?.footer) {
|
|
1212
|
+
const f = cssContentValue(String(pageBlock.properties.footer));
|
|
1213
|
+
backwardCompatCSS += `@page{@bottom-center{content:${f};}}`;
|
|
1214
|
+
}
|
|
1215
|
+
const minimalInkCSS = printMode === "minimal-ink"
|
|
1216
|
+
? `
|
|
1217
|
+
@media print{.it-print-minimal *{background-color:transparent !important;color:black !important;}.it-print-minimal strong,.it-print-minimal b{font-weight:bold;color:black !important;}.it-print-minimal em,.it-print-minimal i{font-style:italic;color:black !important;}.it-print-minimal .it-border{border:1px solid black !important;}}`
|
|
1218
|
+
: "";
|
|
1219
|
+
return `<!DOCTYPE html><html ${direction}><head><meta charset="utf-8"><style>
|
|
1220
|
+
${dynamicCSS}
|
|
1221
|
+
${document_css_1.DOCUMENT_CSS}
|
|
1222
|
+
${themeCSS}
|
|
1223
|
+
${documentStyleCSS(doc)}
|
|
1224
|
+
/* Print: the page box is handled by @page margins, so neutralise the screen
|
|
1225
|
+
document container's own max-width/centering/padding. */
|
|
1226
|
+
.intent-document{max-width:none;margin:0;padding:0;}
|
|
1227
|
+
${headerFooterCSS}
|
|
1228
|
+
${backwardCompatCSS}
|
|
1229
|
+
${breakCSS}
|
|
1230
|
+
${minimalInkCSS}
|
|
1231
|
+
@page{counter-increment:page;}
|
|
1232
|
+
@media print{body{margin:0;}.it-page-break{page-break-after:always;}.it-no-print{display:none;}a{text-decoration:none;color:inherit;}}
|
|
1233
|
+
body.it-print{color:#000;background:#fff;}
|
|
1234
|
+
body.it-print h1{font-size:1.8em;margin-bottom:0.3em;}
|
|
1235
|
+
body.it-print h2{font-size:1.3em;margin-top:1.5em;}
|
|
1236
|
+
body.it-print h3{font-size:1.1em;}
|
|
1237
|
+
body.it-print p{margin:0 0 0.8em 0;orphans:3;widows:3;}
|
|
1238
|
+
body.it-print table{width:100%;border-collapse:collapse;margin:1em 0;}
|
|
1239
|
+
body.it-print th{border-bottom:2px solid #000;padding:4pt 8pt;text-align:left;}
|
|
1240
|
+
body.it-print td{border-bottom:1px solid #ccc;padding:4pt 8pt;}
|
|
1241
|
+
/* Keep table rows whole across page breaks so they aren't clipped behind the
|
|
1242
|
+
running footer/header, and repeat the table header on every page. */
|
|
1243
|
+
body.it-print tr{break-inside:avoid;page-break-inside:avoid;}
|
|
1244
|
+
body.it-print thead{display:table-header-group;}
|
|
1245
|
+
body.it-print tfoot{display:table-footer-group;}
|
|
1246
|
+
/* Sections may legitimately span pages; only avoid splitting their headings. */
|
|
1247
|
+
body.it-print section{break-inside:auto;}
|
|
1248
|
+
body.it-print h1,body.it-print h2,body.it-print h3{break-after:avoid;page-break-after:avoid;}
|
|
1249
|
+
body.it-print .intent-callout{border-left:3pt solid #000;padding-left:10pt;margin:1em 0;}
|
|
1250
|
+
body.it-print .intent-quote{font-style:italic;margin:1em 2em;}
|
|
1251
|
+
body.it-print .it-byline{font-size:0.9em;color:#333;margin-bottom:1.5em;}
|
|
1252
|
+
body.it-print .it-byline .it-byline-author{font-weight:bold;display:block;}
|
|
1253
|
+
body.it-print .it-byline .it-byline-meta{font-size:0.85em;color:#666;}
|
|
1254
|
+
body.it-print .it-epigraph{font-style:italic;text-align:center;margin:2em 3em;border:none;padding:0;}
|
|
1255
|
+
body.it-print .it-epigraph .it-epigraph-by{display:block;text-align:right;font-size:0.9em;margin-top:0.5em;}
|
|
1256
|
+
body.it-print .it-caption{font-size:0.85em;font-style:italic;text-align:center;color:#444;margin-top:0.3em;margin-bottom:1em;}
|
|
1257
|
+
body.it-print .it-dedication{font-style:italic;text-align:center;margin:4em auto;page-break-after:always;}
|
|
1258
|
+
body.it-print .it-toc{margin:2em 0;}
|
|
1259
|
+
body.it-print .it-toc ol{list-style:none;padding:0;}
|
|
1260
|
+
body.it-print .it-toc li{margin:0.3em 0;}
|
|
1261
|
+
body.it-print .it-footnotes{border-top:1pt solid #ccc;margin-top:2em;padding-top:0.5em;font-size:0.85em;}
|
|
1262
|
+
body.it-print .it-footnotes ol{padding-left:1.5em;margin:0;}
|
|
1263
|
+
body.it-print .it-footnotes li{margin:0.3em 0;}
|
|
1264
|
+
body.it-print sup.it-fn-ref{font-size:0.7em;vertical-align:super;}
|
|
1265
|
+
body.it-print .it-page-break{page-break-after:always;break-after:page;height:0;}
|
|
1266
|
+
body.it-print .intent-task-checkbox{display:none;}
|
|
1267
|
+
body.it-print .intent-task::before{content:"\\2610 ";margin-right:4pt;}
|
|
1268
|
+
body.it-print .intent-task-done::before{content:"\\2611 ";}
|
|
1269
|
+
/* ── v2.11 Print ───────────────────────────────────────── */
|
|
1270
|
+
body.it-print .it-ref-card{border:none;padding:0;margin:0.5em 0;font-style:italic;}
|
|
1271
|
+
body.it-print .it-def{margin:0.3em 0;}
|
|
1272
|
+
body.it-print .it-def-term{font-weight:bold;}
|
|
1273
|
+
body.it-print .it-def-meaning{padding-left:1.5em;}
|
|
1274
|
+
body.it-print .it-metric{border:1pt solid #ccc;padding:6pt 10pt;display:inline-block;min-width:100pt;margin:4pt;vertical-align:top;}
|
|
1275
|
+
body.it-print .it-amendment{border:2pt solid #000;padding:8pt 12pt;margin:1em 0;}
|
|
1276
|
+
body.it-print .it-amendment-ref{border:1pt solid #000;color:#000;}
|
|
1277
|
+
body.it-print .it-figure{margin:1em 0;text-align:center;}
|
|
1278
|
+
body.it-print .it-figure img{max-width:100%;border:1pt solid #ccc;}
|
|
1279
|
+
body.it-print .it-figure-caption{font-size:0.85em;font-style:italic;text-align:center;margin-top:0.3em;}
|
|
1280
|
+
body.it-print .it-signline{display:inline-block;width:45%;margin:2em 2%;vertical-align:top;}
|
|
1281
|
+
body.it-print .it-signline-rule{border-bottom:1pt solid #000;margin-bottom:4pt;}
|
|
1282
|
+
body.it-print .it-contact{border:none;padding:0;margin:0.3em 0;}
|
|
1283
|
+
body.it-print .it-deadline{border-left:3pt solid #000;padding-left:8pt;margin:0.5em 0;}
|
|
1284
|
+
body.it-print .it-deadline-date{font-weight:bold;text-decoration:underline;}
|
|
1285
|
+
</style></head><body class="${bodyClass}"><div class="intent-document">${watermarkHtml}${html}</div></body></html>`;
|
|
1286
|
+
}
|