@rse/specbook 0.9.5 → 0.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -328
- package/dst/specbook-api.d.ts +13 -20
- package/dst/specbook-api.js +43 -39
- package/dst/specbook-cli.js +42 -55
- package/dst/specbook-cmd-describe.d.ts +5 -2
- package/dst/specbook-cmd-describe.js +30 -91
- package/dst/specbook-cmd-export.d.ts +3 -3
- package/dst/specbook-cmd-export.js +23 -19
- package/dst/specbook-cmd-init.d.ts +3 -3
- package/dst/specbook-cmd-init.js +38 -15
- package/dst/specbook-cmd-lint.d.ts +5 -6
- package/dst/specbook-cmd-lint.js +61 -24
- package/dst/specbook-config.d.ts +2 -2
- package/dst/specbook-config.js +82 -73
- package/dst/specbook-diagnostic.js +4 -1
- package/dst/specbook-diagram.d.ts +6 -5
- package/dst/specbook-diagram.js +94 -104
- package/dst/specbook-export-ast.d.ts +3 -3
- package/dst/specbook-export-ast.js +1 -1
- package/dst/specbook-export-common.d.ts +11 -9
- package/dst/specbook-export-common.js +34 -22
- package/dst/specbook-export-html-search.js +10 -14
- package/dst/specbook-export-html.css +37 -32
- package/dst/specbook-export-html.d.ts +4 -4
- package/dst/specbook-export-html.js +153 -107
- package/dst/specbook-export-logo-dark.svg +35 -0
- package/dst/specbook-export-logo-light.svg +35 -0
- package/dst/specbook-export-md.d.ts +3 -3
- package/dst/specbook-export-md.js +50 -24
- package/dst/specbook-export-pdf.d.ts +4 -2
- package/dst/specbook-export-pdf.js +48 -38
- package/dst/{specbook-struct-schema.d.ts → specbook-format-schema.d.ts} +3 -2
- package/dst/{specbook-struct-schema.js → specbook-format-schema.js} +8 -4
- package/dst/specbook-format-spec.d.ts +31 -0
- package/dst/{specbook-struct-spec.js → specbook-format-spec.js} +10 -10
- package/dst/specbook-format.md +750 -0
- package/dst/specbook-link.d.ts +3 -2
- package/dst/specbook-link.js +7 -4
- package/dst/specbook-mcp.d.ts +2 -1
- package/dst/specbook-mcp.js +30 -62
- package/dst/specbook-parse-common.d.ts +5 -3
- package/dst/specbook-parse-common.js +8 -0
- package/dst/specbook-parse-semantic.d.ts +6 -4
- package/dst/specbook-parse-semantic.js +98 -106
- package/dst/specbook-parse-syntax.d.ts +2 -2
- package/dst/specbook-parse-syntax.js +175 -144
- package/dst/specbook-parse-value.d.ts +1 -1
- package/dst/specbook-parse-value.js +6 -3
- package/dst/specbook-parse.d.ts +4 -7
- package/dst/specbook-parse.js +28 -35
- package/dst/specbook-verbose.d.ts +2 -0
- package/dst/specbook-verbose.js +17 -0
- package/package.json +3 -7
- package/dst/specbook-cmd-edit.d.ts +0 -9
- package/dst/specbook-cmd-edit.js +0 -74
- package/dst/specbook-cmd-import.d.ts +0 -9
- package/dst/specbook-cmd-import.js +0 -78
- package/dst/specbook-export-logo.svg +0 -35
- package/dst/specbook-llm.d.ts +0 -13
- package/dst/specbook-llm.js +0 -51
- package/dst/specbook-struct-spec.d.ts +0 -31
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { Spec } from "./specbook-format-spec.js";
|
|
2
|
+
import type { Schema } from "./specbook-format-schema.js";
|
|
3
3
|
export type OutlineEntry = {
|
|
4
4
|
title: string;
|
|
5
5
|
anchor: string;
|
|
6
6
|
childs: OutlineEntry[];
|
|
7
7
|
};
|
|
8
|
-
export declare const htmlOutline: (specification:
|
|
9
|
-
export declare const renderHtml: (specification:
|
|
8
|
+
export declare const htmlOutline: (specification: Spec, config?: Schema) => OutlineEntry[];
|
|
9
|
+
export declare const renderHtml: (specification: Spec, config?: Schema, tocPages?: Map<string, number>, css?: string) => Promise<string>;
|
|
@@ -9,10 +9,11 @@ import nunjucks from "nunjucks";
|
|
|
9
9
|
import nunjucksAddons from "@rse/nunjucks-addons";
|
|
10
10
|
import textframe from "textframe";
|
|
11
11
|
import { Gradia } from "@rse/gradia";
|
|
12
|
-
import { buildLinkIndex, resolveUnique, expandReferences, anchorPaths } from "./specbook-link.js";
|
|
12
|
+
import { buildLinkIndex, resolveUnique, expandReferences, anchorPaths, plainText } from "./specbook-link.js";
|
|
13
13
|
import { compileValueExpr, splitItems } from "./specbook-parse-value.js";
|
|
14
|
-
import { embeddingRegex, embeddingMimeType } from "./specbook-parse-common.js";
|
|
15
|
-
import { escapeHtml, stylesheet, searchScript, fallbackLogo, isTitleObject, documentTitle, documentLang, documentThemeStyle } from "./specbook-export-common.js";
|
|
14
|
+
import { embeddingRegex, embeddingMimeType, embeddingThemes, embeddingVariants } from "./specbook-parse-common.js";
|
|
15
|
+
import { escapeHtml, stylesheet, searchScript, fallbackLogo, isTitleObject, titleObject, documentTitle, documentLang, documentThemeStyle } from "./specbook-export-common.js";
|
|
16
|
+
import { collectSchemas } from "./specbook-parse-semantic.js";
|
|
16
17
|
import { specDiagrams } from "./specbook-diagram.js";
|
|
17
18
|
/* ==== Templates ==== */
|
|
18
19
|
/* the built-in Nunjucks templates for the HTML export */
|
|
@@ -51,7 +52,6 @@ const templates = {
|
|
|
51
52
|
<body>
|
|
52
53
|
<div class="theme-switch" onclick="themeSwitch()" title="switch color theme">◐</div>
|
|
53
54
|
{{ Document.titlepage }}
|
|
54
|
-
{% if not Document.titlepage %}<div class="meta">created: {{ Document.created }}, modified: {{ Document.modified }}</div>{% endif %}
|
|
55
55
|
{{ Document.toc }}
|
|
56
56
|
{{ Document.artifacts }}
|
|
57
57
|
{% if Document.search %}<script>{{ Document.search }}</script>{% endif %}
|
|
@@ -118,7 +118,7 @@ const templates = {
|
|
|
118
118
|
{%- if Description.block %}
|
|
119
119
|
<div class="description">{{ Description.description }}{% if Description.rationale %}
|
|
120
120
|
<p class="rationale">— <span class="keyword">BECAUSE</span> {{ Description.rationale }}</p>{% endif %}</div>
|
|
121
|
-
{%-
|
|
121
|
+
{%- elif Description.description or Description.rationale %}
|
|
122
122
|
<p class="description">{{ Description.description }}{% if Description.rationale %}
|
|
123
123
|
<span class="rationale">— <span class="keyword">BECAUSE</span> {{ Description.rationale }}</span>{% endif %}</p>
|
|
124
124
|
{%- endif %}
|
|
@@ -210,6 +210,18 @@ const marked = new Marked();
|
|
|
210
210
|
marked.use({ hooks: { postprocess: (html) => html.replace(/“/g, quotes[0]).replace(/”/g, quotes[1])
|
|
211
211
|
.replace(/&(?![a-zA-Z][a-zA-Z0-9]*;|#\d+;|#x[0-9a-fA-F]+;)/g, "&") } });
|
|
212
212
|
marked.use(markedSmartypants({ config: 1 }));
|
|
213
|
+
/* the switched-off text escaping of marked also leaves the angle
|
|
214
|
+
brackets of the inline text unescaped, which a browser then takes
|
|
215
|
+
for tags ("Map<String, Object>"), so they are escaped upfront in
|
|
216
|
+
the inline text tokenizer (registered after smartypants, as the
|
|
217
|
+
later-registered tokenizer takes precedence) */
|
|
218
|
+
marked.use({ tokenizer: { inlineText(src) {
|
|
219
|
+
const cap = this.rules.inline.text.exec(src);
|
|
220
|
+
if (cap === null)
|
|
221
|
+
return undefined;
|
|
222
|
+
return { type: "text", raw: cap[0], escaped: true,
|
|
223
|
+
text: cap[0].replace(/</g, "<").replace(/>/g, ">") };
|
|
224
|
+
} } });
|
|
213
225
|
/* the Nunjucks environment with the @rse/nunjucks-addons extensions */
|
|
214
226
|
const env = new nunjucks.Environment(null, { autoescape: true });
|
|
215
227
|
nunjucksAddons(env);
|
|
@@ -232,38 +244,66 @@ const diagramCache = new WeakMap();
|
|
|
232
244
|
const anchorOf = (object) => anchors?.get(object) ?? object.id;
|
|
233
245
|
/* expand the inline Markdown of a text (code spans, emphasis, etc.),
|
|
234
246
|
with Wiki-style references expanded upfront */
|
|
235
|
-
const inline = (text) => safe(marked.parseInline(linker !== null ? linker(text) : text));
|
|
247
|
+
const inline = (text) => safe(marked.parseInline(linker !== null ? linker(text) : text, { async: false }));
|
|
236
248
|
/* expand the full Markdown of a text, keeping its block-level
|
|
237
249
|
constructs (lists, quotes, code blocks, tables) intact */
|
|
238
|
-
const block = (text) => safe(marked.parse(linker !== null ? linker(text) : text));
|
|
250
|
+
const block = (text) => safe(marked.parse(linker !== null ? linker(text) : text, { async: false }));
|
|
239
251
|
/* check whether a text carries any block-level Markdown, i.e. is
|
|
240
252
|
anything else than the single paragraph a description usually is */
|
|
241
253
|
const isBlock = (text) => {
|
|
242
254
|
const tokens = marked.lexer(text).filter((token) => token.type !== "space");
|
|
243
255
|
return tokens.length > 0 && !(tokens.length === 1 && tokens[0].type === "paragraph");
|
|
244
256
|
};
|
|
245
|
-
/* render
|
|
246
|
-
|
|
247
|
-
|
|
257
|
+
/* render a single embedded image file onto an <img> tag with a
|
|
258
|
+
self-contained data: URL (converting the SVG text into one, as an
|
|
259
|
+
SVG inlined as-is would leak its document-global <style> rules
|
|
260
|
+
into all other inlined SVGs sharing the same class names) */
|
|
261
|
+
const renderImage = (content, alt) => {
|
|
262
|
+
const url = content.startsWith("data:") ? content :
|
|
263
|
+
`data:image/svg+xml;base64,${Buffer.from(content, "utf8").toString("base64")}`;
|
|
264
|
+
return `<img src="${url}" alt="${escapeHtml(alt)}"/>`;
|
|
265
|
+
};
|
|
266
|
+
/* wrap the theme variants of an image into their layout-neutral theme
|
|
267
|
+
containers, of which the stylesheet shows just the one matching the
|
|
268
|
+
color theme currently active in the document */
|
|
269
|
+
const renderThemed = (images) => images.map((image, i) => `<span class="theme-${embeddingThemes[i]}-only">${image}</span>`).join("");
|
|
270
|
+
/* render the embedded image files of a text into HTML, taking the image
|
|
271
|
+
alternate texts from the corresponding "" markups and
|
|
272
|
+
pairing up the consecutive theme variants of a "{theme}" markup
|
|
273
|
+
(the empty entries of unreadable files are skipped) */
|
|
248
274
|
const renderEmbeddings = (text, embedding) => {
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
275
|
+
const result = new Array();
|
|
276
|
+
let i = 0;
|
|
277
|
+
for (const m of text.matchAll(embeddingRegex)) {
|
|
278
|
+
const reference = m[2].trim();
|
|
279
|
+
if (embeddingMimeType(reference) === undefined)
|
|
280
|
+
continue;
|
|
281
|
+
const variants = embeddingVariants(reference);
|
|
282
|
+
const contents = embedding.slice(i, i + variants.length);
|
|
283
|
+
const images = contents.filter((content) => content !== "")
|
|
284
|
+
.map((content) => renderImage(content, m[1].trim()));
|
|
285
|
+
i += variants.length;
|
|
286
|
+
if (variants.length > 1 && images.length === variants.length)
|
|
287
|
+
result.push(renderThemed(images));
|
|
288
|
+
else
|
|
289
|
+
result.push(...images);
|
|
290
|
+
}
|
|
291
|
+
return result;
|
|
255
292
|
};
|
|
293
|
+
/* the image embedding markup including the horizontal whitespace
|
|
294
|
+
before it, so that its removal leaves no double space behind */
|
|
295
|
+
const embeddingMarkup = new RegExp(`[ \\t]*${embeddingRegex.source}`, "g");
|
|
256
296
|
/* render a description into HTML, expanding its inline Markdown and
|
|
257
297
|
moving the file embeddings to the end of the description */
|
|
258
298
|
const renderDescription = (description) => {
|
|
259
299
|
const text = description.description
|
|
260
|
-
.replace(
|
|
261
|
-
.
|
|
300
|
+
.replace(embeddingMarkup, (markup, _alt, reference) => embeddingMimeType(reference.trim()) !== undefined ? "" : markup)
|
|
301
|
+
.trim();
|
|
262
302
|
const embeddings = renderEmbeddings(description.description, description.embedding ?? []).map((content) => safe(content));
|
|
263
303
|
const blocked = isBlock(text);
|
|
264
304
|
return render("Description", { Description: {
|
|
265
305
|
block: blocked,
|
|
266
|
-
description: blocked ? block(text) : inline(text),
|
|
306
|
+
description: text !== "" ? (blocked ? block(text) : inline(text)) : "",
|
|
267
307
|
rationale: description.rationale !== undefined ?
|
|
268
308
|
inline(description.rationale) : undefined,
|
|
269
309
|
embeddings
|
|
@@ -271,8 +311,8 @@ const renderDescription = (description) => {
|
|
|
271
311
|
};
|
|
272
312
|
/* collect the "enum(...)"/"tags(...)" constrained properties of the
|
|
273
313
|
schema configuration, keyed by object kind and property name */
|
|
274
|
-
const collectMembers = (
|
|
275
|
-
for (const schema of
|
|
314
|
+
const collectMembers = (nodes, result) => {
|
|
315
|
+
for (const schema of nodes) {
|
|
276
316
|
for (const property of schema.props ?? []) {
|
|
277
317
|
if (property.value === undefined)
|
|
278
318
|
continue;
|
|
@@ -289,44 +329,25 @@ const collectMembers = (schemas, result) => {
|
|
|
289
329
|
}
|
|
290
330
|
return result;
|
|
291
331
|
};
|
|
292
|
-
/* map the specification objects onto their schema configuration
|
|
293
|
-
nodes (the schema resolution mirrors the semantic validation) */
|
|
294
|
-
const collectSchemas = (specification, config) => {
|
|
295
|
-
const result = new Map();
|
|
296
|
-
const walk = (object, schema) => {
|
|
297
|
-
result.set(object, schema);
|
|
298
|
-
for (const child of object.childs) {
|
|
299
|
-
const childSchema = (schema.childs ?? []).find((c) => c.kind === child.kind);
|
|
300
|
-
if (childSchema !== undefined)
|
|
301
|
-
walk(child, childSchema);
|
|
302
|
-
}
|
|
303
|
-
};
|
|
304
|
-
for (const artifact of specification.artifacts) {
|
|
305
|
-
for (const object of artifact.objects) {
|
|
306
|
-
const schema = config.find((s) => (s.kind === object.kind && s.id === object.id) || `${s.kind}-${s.id}` === object.id) ??
|
|
307
|
-
config.find((s) => (s.name ?? "").toUpperCase() ===
|
|
308
|
-
object.name.replace(/`/g, "").toUpperCase());
|
|
309
|
-
if (schema !== undefined)
|
|
310
|
-
walk(object, schema);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
return result;
|
|
314
|
-
};
|
|
315
|
-
/* strip a trailing parenthesized annotation from a
|
|
316
|
-
property key (e.g. "WHEN (Context)") */
|
|
317
|
-
const plainKey = (key) => key.replace(/\s*\([^)]*\)\s*$/, "").trim();
|
|
318
332
|
/* render a property value, badging the individual members of an
|
|
319
|
-
"enum(...)" (a single member) or "tags(...)" (a member set) value
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
|
|
333
|
+
"enum(...)" (a single member) or "tags(...)" (a member set) value
|
|
334
|
+
and moving its image embeddings behind the value, rendered from the
|
|
335
|
+
embedded image contents (as for a description), not from the markup */
|
|
336
|
+
const inlineValue = (kind, { key, value, embedding }) => {
|
|
337
|
+
const member = members?.get(`${kind}${key}`);
|
|
338
|
+
const text = value
|
|
339
|
+
.replace(embeddingMarkup, (markup, _alt, reference) => embeddingMimeType(reference.trim()) !== undefined ? "" : markup)
|
|
340
|
+
.trim();
|
|
341
|
+
const embeddings = renderEmbeddings(value, embedding ?? [])
|
|
342
|
+
.map((content) => `<div class="embedding">${content}</div>`).join("");
|
|
343
|
+
if (member === undefined || text === "")
|
|
344
|
+
return safe(`${inline(text)}${embeddings}`);
|
|
345
|
+
const items = member === "tags" ? splitItems(text) : [text];
|
|
346
|
+
return safe(items.map((item) => `<span class="value-member">${inline(item)}</span>`).join(" ") + embeddings);
|
|
326
347
|
};
|
|
327
348
|
/* expand the inline Markdown of the property values */
|
|
328
349
|
const inlineProperties = (kind, properties) => properties.map((property) => ({ key: property.key,
|
|
329
|
-
value: inlineValue(kind, property
|
|
350
|
+
value: inlineValue(kind, property) }));
|
|
330
351
|
/* resolve the format configuration of an object */
|
|
331
352
|
const formatOf = (object) => schemas?.get(object)?.format;
|
|
332
353
|
/* determine the maximum table columns configured on an object (at
|
|
@@ -340,7 +361,7 @@ const effectiveProperties = (object) => {
|
|
|
340
361
|
if (schema?.format?.withUnusedProps !== true)
|
|
341
362
|
return object.properties;
|
|
342
363
|
const merged = (schema.props ?? []).flatMap((prop) => {
|
|
343
|
-
const present = object.properties.filter((property) =>
|
|
364
|
+
const present = object.properties.filter((property) => property.key === prop.name);
|
|
344
365
|
return present.length > 0 ? present : [{ key: prop.name, value: "" }];
|
|
345
366
|
});
|
|
346
367
|
return [...merged, ...object.properties.filter((property) => !merged.includes(property))];
|
|
@@ -349,32 +370,30 @@ const effectiveProperties = (object) => {
|
|
|
349
370
|
"withUnusedProps" injecting the defined but still unused schema
|
|
350
371
|
properties (in schema order) as additional columns */
|
|
351
372
|
const tableShape = (childs) => {
|
|
352
|
-
let keys = new
|
|
353
|
-
for (const child of childs)
|
|
354
|
-
for (const property of child.properties)
|
|
355
|
-
if (!keys.includes(property.key))
|
|
356
|
-
keys.push(property.key);
|
|
373
|
+
let keys = [...new Set(childs.flatMap((child) => child.properties.map((property) => property.key)))];
|
|
357
374
|
const schema = schemas?.get(childs[0]);
|
|
358
375
|
if (schema?.format?.withUnusedProps === true) {
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
return present.length > 0 ? present : [prop.name];
|
|
362
|
-
});
|
|
363
|
-
keys = [...merged, ...keys.filter((key) => !merged.includes(key))];
|
|
376
|
+
const names = (schema.props ?? []).map((prop) => prop.name);
|
|
377
|
+
keys = [...names, ...keys.filter((key) => !names.includes(key))];
|
|
364
378
|
}
|
|
365
379
|
return { keys, desc: childs.some((child) => child.description !== undefined || child.childs.length > 0) };
|
|
366
380
|
};
|
|
381
|
+
/* provide the childs of an object taking part in the regular document
|
|
382
|
+
flow: a nested title object leaves the flow, as it is rendered as the
|
|
383
|
+
title page instead (a top-level one instead removes its whole artifact) */
|
|
384
|
+
const flowChilds = (object) => object.childs.filter((child) => !isTitleObject(child));
|
|
367
385
|
/* decide whether the childs of an object collapse into the concise
|
|
368
386
|
(tabular) rendering: an explicit "format" type wins, "auto" collapses
|
|
369
387
|
the deepest level only, and inside an already concise rendering
|
|
370
388
|
context "auto" childs implicitly stay concise, too */
|
|
371
389
|
const conciseChilds = (object, schemaMap, concise) => {
|
|
372
|
-
|
|
390
|
+
const childs = flowChilds(object);
|
|
391
|
+
if (childs.length === 0)
|
|
373
392
|
return false;
|
|
374
393
|
const type = schemaMap?.get(object)?.format?.type ?? "auto";
|
|
375
394
|
if (type !== "auto")
|
|
376
395
|
return type === "concise";
|
|
377
|
-
return concise ||
|
|
396
|
+
return concise || childs.every((child) => child.childs.length === 0);
|
|
378
397
|
};
|
|
379
398
|
/* group the childs of an object by their kind, preserving order */
|
|
380
399
|
const groupChilds = (childs) => {
|
|
@@ -394,10 +413,11 @@ const groupChilds = (childs) => {
|
|
|
394
413
|
ones as regular nested object renderings pressed into the cell) */
|
|
395
414
|
const renderCell = (child) => {
|
|
396
415
|
let html = child.description !== undefined ? renderDescription(child.description) : "";
|
|
397
|
-
|
|
416
|
+
const childs = flowChilds(child);
|
|
417
|
+
if (childs.length > 0)
|
|
398
418
|
html += conciseChilds(child, schemas, true) ?
|
|
399
|
-
groupChilds(
|
|
400
|
-
|
|
419
|
+
groupChilds(childs).map((group) => renderTable(group, maxColumnsOf(child))).join("") :
|
|
420
|
+
childs.map((sub) => renderObject(sub, 6, true)).join("");
|
|
401
421
|
return html;
|
|
402
422
|
};
|
|
403
423
|
/* render a single-kind group of childs into one compact table:
|
|
@@ -420,8 +440,8 @@ const renderTable = (childs, maxColumns) => {
|
|
|
420
440
|
primary: child.primary,
|
|
421
441
|
name: inline(child.name),
|
|
422
442
|
values: keys.map((key) => {
|
|
423
|
-
const
|
|
424
|
-
return
|
|
443
|
+
const property = child.properties.find((property) => property.key === key);
|
|
444
|
+
return property !== undefined ? inlineValue(child.kind, property) : "";
|
|
425
445
|
}),
|
|
426
446
|
description: safe(renderCell(child))
|
|
427
447
|
}))
|
|
@@ -435,9 +455,9 @@ const renderTable = (childs, maxColumns) => {
|
|
|
435
455
|
width: Math.round(100 / maxColumns),
|
|
436
456
|
rows: childs.map((child) => {
|
|
437
457
|
const cells = keys.map((key) => {
|
|
438
|
-
const
|
|
458
|
+
const property = child.properties.find((property) => property.key === key);
|
|
439
459
|
return { key, desc: false, span: 1,
|
|
440
|
-
value:
|
|
460
|
+
value: property !== undefined ? inlineValue(child.kind, property) : "" };
|
|
441
461
|
});
|
|
442
462
|
if (desc)
|
|
443
463
|
cells.push({ key: "Description", desc: true, span: 1,
|
|
@@ -460,6 +480,7 @@ const renderTable = (childs, maxColumns) => {
|
|
|
460
480
|
/* recursively render an object into HTML */
|
|
461
481
|
const renderObject = (object, level, concise) => {
|
|
462
482
|
const properties = effectiveProperties(object);
|
|
483
|
+
const diagram = diagrams?.get(object);
|
|
463
484
|
return render("Object", { Object: {
|
|
464
485
|
level: Math.min(level, 6),
|
|
465
486
|
kind: object.kind,
|
|
@@ -467,17 +488,21 @@ const renderObject = (object, level, concise) => {
|
|
|
467
488
|
paren: object.paren,
|
|
468
489
|
primary: object.primary,
|
|
469
490
|
name: inline(object.name),
|
|
470
|
-
diagram:
|
|
471
|
-
safe(`<div class="diagram">${diagrams.get(object) ?? ""}</div>`) : "",
|
|
491
|
+
diagram: diagram !== undefined ? safe(`<div class="diagram">${diagram}</div>`) : "",
|
|
472
492
|
properties: properties.length > 0 ?
|
|
473
493
|
safe(render("Properties", { Properties: inlineProperties(object.kind, properties) })) : "",
|
|
474
494
|
description: object.description !== undefined ?
|
|
475
495
|
safe(renderDescription(object.description)) : "",
|
|
476
496
|
childs: conciseChilds(object, schemas, concise) ?
|
|
477
|
-
safe(groupChilds(object
|
|
478
|
-
safe(object.
|
|
497
|
+
safe(groupChilds(flowChilds(object)).map((group) => renderTable(group, maxColumnsOf(object))).join("")) :
|
|
498
|
+
safe(flowChilds(object).map((child) => renderObject(child, level + 1, concise)).join(""))
|
|
479
499
|
} });
|
|
480
500
|
};
|
|
501
|
+
/* format a timestamp as its calendar date in local time (the
|
|
502
|
+
frontmatter timestamps are parsed in local time, too, so the UTC
|
|
503
|
+
date of toISOString() could shift the day) */
|
|
504
|
+
const formatDate = (date) => `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-` +
|
|
505
|
+
String(date.getDate()).padStart(2, "0");
|
|
481
506
|
/* render the title object into a title page */
|
|
482
507
|
const renderTitlePage = (object, created, modified) => {
|
|
483
508
|
const prop = (name) => object.properties.find((property) => property.key === name)?.value;
|
|
@@ -489,11 +514,13 @@ const renderTitlePage = (object, created, modified) => {
|
|
|
489
514
|
"LANG", "CHARSET", "PAPER-SIZE", "THEME-STYLE", "THEME-TONE"].includes(property.key));
|
|
490
515
|
/* the logo is rendered above the title, from the embedded image content
|
|
491
516
|
of the LOGO property or, for a non-embeddable reference, as its inline
|
|
492
|
-
Markdown; without a LOGO property the built-in SpecBook logo is used
|
|
517
|
+
Markdown; without a LOGO property the built-in SpecBook logo is used,
|
|
518
|
+
in both its theme variants */
|
|
493
519
|
const logo = object.properties.find((property) => property.key === "LOGO");
|
|
494
520
|
const image = logo !== undefined ? renderEmbeddings(logo.value, logo.embedding ?? []) : [];
|
|
495
521
|
return render("TitlePage", { TitlePage: {
|
|
496
|
-
logo: logo === undefined ?
|
|
522
|
+
logo: logo === undefined ?
|
|
523
|
+
safe(renderThemed(embeddingThemes.map((theme) => `<img src="${fallbackLogo(theme)}" alt="SpecBook"/>`))) :
|
|
497
524
|
(image.length > 0 ? safe(image.join("")) : inline(logo.value)),
|
|
498
525
|
title: inline(prop("TITLE") ?? object.name),
|
|
499
526
|
subtitle: inlineProp("SUBTITLE"),
|
|
@@ -516,15 +543,24 @@ export const htmlOutline = (specification, config) => {
|
|
|
516
543
|
const paths = anchorPaths(buildLinkIndex(specification));
|
|
517
544
|
const schemaMap = config !== undefined ? collectSchemas(specification, config) : null;
|
|
518
545
|
const entry = (object) => ({
|
|
519
|
-
title: (object.kind !== "" ? `${object.kind}: ` : "") + object.name,
|
|
546
|
+
title: (object.kind !== "" ? `${object.kind}: ` : "") + plainText(object.name),
|
|
520
547
|
anchor: paths.get(object) ?? object.id,
|
|
521
|
-
childs: conciseChilds(object, schemaMap, false) ? [] : object.
|
|
548
|
+
childs: conciseChilds(object, schemaMap, false) ? [] : flowChilds(object).map(entry)
|
|
522
549
|
});
|
|
523
550
|
return specification.artifacts
|
|
524
551
|
.filter((artifact) => !artifact.objects.some(isTitleObject))
|
|
525
552
|
.flatMap((artifact) => artifact.objects)
|
|
526
553
|
.map(entry);
|
|
527
554
|
};
|
|
555
|
+
/* determine the object a title page is rendered from: the title object
|
|
556
|
+
has to carry a non-empty "TITLE" property, as without it there is
|
|
557
|
+
nothing to render a title page from (the object stays suppressed in
|
|
558
|
+
the regular flow nevertheless) */
|
|
559
|
+
const titlePageObject = (specification) => {
|
|
560
|
+
const object = titleObject(specification);
|
|
561
|
+
const title = object?.properties.find((property) => property.key === "TITLE");
|
|
562
|
+
return title !== undefined && title.value.trim() !== "" ? object : undefined;
|
|
563
|
+
};
|
|
528
564
|
/* render the entire specification into a self-contained HTML document,
|
|
529
565
|
with the build-time pre-assembled stylesheet embedded inline, the
|
|
530
566
|
artifact timestamps aggregated into min(Created)/max(Modified), and
|
|
@@ -538,23 +574,23 @@ export const renderHtml = async (specification, config, tocPages, css) => {
|
|
|
538
574
|
canvas and would dwarf the document text at 1:1 */
|
|
539
575
|
const scale = 0.75;
|
|
540
576
|
const cached = diagramCache.get(specification);
|
|
541
|
-
|
|
577
|
+
let rendered = null;
|
|
542
578
|
if (cached !== undefined && cached.config === config)
|
|
543
|
-
|
|
579
|
+
rendered = cached.diagrams;
|
|
544
580
|
else if (config !== undefined) {
|
|
545
|
-
|
|
581
|
+
rendered = new Map();
|
|
546
582
|
for (const [object, result] of specDiagrams(specification, config)) {
|
|
547
583
|
if (result.spec === undefined)
|
|
548
584
|
continue;
|
|
549
585
|
try {
|
|
550
|
-
const svg = await Gradia.render(result.spec, { format: "svg:embedded" });
|
|
551
|
-
|
|
586
|
+
const svg = await Gradia.render(result.spec, { format: "svg:embedded", config: result.config });
|
|
587
|
+
rendered.set(object, svg.replace(/(<svg[^>]*) width="([0-9.]+)" height="([0-9.]+)"/, (_, head, w, h) => `${head} width="${Number(w) * scale}" height="${Number(h) * scale}"`));
|
|
552
588
|
}
|
|
553
589
|
catch {
|
|
554
590
|
/* intentionally omitted */
|
|
555
591
|
}
|
|
556
592
|
}
|
|
557
|
-
diagramCache.set(specification, { config, diagrams });
|
|
593
|
+
diagramCache.set(specification, { config, diagrams: rendered });
|
|
558
594
|
}
|
|
559
595
|
/* the document language selects the smart typography quote style */
|
|
560
596
|
const lang = documentLang(specification);
|
|
@@ -566,6 +602,7 @@ export const renderHtml = async (specification, config, tocPages, css) => {
|
|
|
566
602
|
anchors = anchorPaths(index);
|
|
567
603
|
members = config !== undefined ? collectMembers(config, new Map()) : null;
|
|
568
604
|
schemas = config !== undefined ? collectSchemas(specification, config) : null;
|
|
605
|
+
diagrams = rendered;
|
|
569
606
|
linker = (text) => expandReferences(text, (reference) => {
|
|
570
607
|
const target = resolveUnique(index, reference).target;
|
|
571
608
|
if (target === undefined)
|
|
@@ -577,24 +614,33 @@ export const renderHtml = async (specification, config, tocPages, css) => {
|
|
|
577
614
|
const created = new Date(Math.min(...specification.artifacts.map((artifact) => artifact.created.getTime())));
|
|
578
615
|
const modified = new Date(Math.max(...specification.artifacts.map((artifact) => artifact.modified.getTime())));
|
|
579
616
|
/* a "META: Title" object becomes the title page and leaves the regular flow */
|
|
580
|
-
const title = specification
|
|
581
|
-
.flatMap((artifact) => artifact.objects).find(isTitleObject);
|
|
617
|
+
const title = titlePageObject(specification);
|
|
582
618
|
const artifacts = specification.artifacts
|
|
583
619
|
.filter((artifact) => !artifact.objects.some(isTitleObject));
|
|
584
|
-
|
|
585
|
-
.
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
620
|
+
try {
|
|
621
|
+
const entries = artifacts.flatMap((artifact) => artifact.objects)
|
|
622
|
+
.map((object) => ({ id: anchorOf(object), kind: object.kind, name: inline(object.name),
|
|
623
|
+
page: tocPages?.get(anchorOf(object)) }));
|
|
624
|
+
return render("Document", { Document: {
|
|
625
|
+
title: documentTitle(specification).title,
|
|
626
|
+
lang,
|
|
627
|
+
theme: documentThemeStyle(specification)?.toLowerCase(),
|
|
628
|
+
css: safe(css ?? stylesheet()),
|
|
629
|
+
titlepage: title !== undefined ?
|
|
630
|
+
safe(renderTitlePage(title, formatDate(created), formatDate(modified))) : "",
|
|
631
|
+
search: title !== undefined ? safe(searchScript()) : "",
|
|
632
|
+
toc: entries.length > 0 ? safe(render("Toc", { Toc: { entries } })) : "",
|
|
633
|
+
artifacts: safe(artifacts.map((artifact) => renderArtifact(artifact)).join(""))
|
|
634
|
+
} });
|
|
635
|
+
}
|
|
636
|
+
finally {
|
|
637
|
+
/* release the per-document state (also on a rendering failure),
|
|
638
|
+
as it would otherwise retain the specification (and its
|
|
639
|
+
embedded images) until the next rendering */
|
|
640
|
+
linker = null;
|
|
641
|
+
anchors = null;
|
|
642
|
+
members = null;
|
|
643
|
+
schemas = null;
|
|
644
|
+
diagrams = null;
|
|
645
|
+
}
|
|
600
646
|
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 260.8 110.5">
|
|
3
|
+
<!-- Generator: Adobe Illustrator 30.7.0, SVG Export Plug-In . SVG Version: 2.1.4 Build 114) -->
|
|
4
|
+
<defs>
|
|
5
|
+
<style>
|
|
6
|
+
.st0 {
|
|
7
|
+
fill: none;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.st1 {
|
|
11
|
+
fill: #69c;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.st2 {
|
|
15
|
+
fill: #9fc9eb;
|
|
16
|
+
}
|
|
17
|
+
</style>
|
|
18
|
+
</defs>
|
|
19
|
+
<g>
|
|
20
|
+
<rect class="st0" x="128" y="5.9" width="297.7" height="245.4"/>
|
|
21
|
+
<path class="st2" d="M132.6,41.3c2.6,1.3,6.7,2.7,10.8,2.7s6.8-1.9,6.8-4.7-2-4.2-7.2-6c-7.2-2.6-11.9-6.5-11.9-12.8s6.2-12.9,16.3-12.9,8.4,1,11,2.2l-2.2,7.8c-1.7-.8-4.8-2-9-2s-6.3,2-6.3,4.2,2.4,4,8,6.1c7.6,2.8,11.1,6.8,11.1,12.9s-5.5,13.4-17.3,13.4-9.8-1.3-12.2-2.7l2-8Z"/>
|
|
22
|
+
<path class="st2" d="M163.3,30.5c0-4.1-.1-7.6-.3-10.5h8.4l.4,4.4h.1c2.3-3.3,5.9-5.1,10.4-5.1,6.8,0,13,6,13,15.9s-7.2,16.8-14.2,16.8-6.7-1.5-8.1-3.6h-.1v15.6h-9.7V30.5ZM173,37.9c0,.8,0,1.4.2,2,.6,2.6,2.9,4.6,5.7,4.6,4.2,0,6.7-3.5,6.7-8.8s-2.2-8.8-6.6-8.8-5.2,2-5.8,4.9c-.1.5-.2,1.2-.2,1.7v4.3Z"/>
|
|
23
|
+
<path class="st2" d="M206,38.8c.3,4,4.3,6,8.8,6s6-.4,8.6-1.3l1.3,6.6c-3.2,1.3-7.1,2-11.3,2-10.6,0-16.7-6.1-16.7-15.9s4.9-16.7,15.8-16.7,14,7.9,14,15.6-.2,3.1-.3,3.8h-20.2ZM217.5,32.1c0-2.4-1-6.3-5.5-6.3s-5.8,3.8-6,6.3h11.5Z"/>
|
|
24
|
+
<path class="st2" d="M252.6,50.5c-1.7.8-5,1.5-8.7,1.5-10.1,0-16.6-6.2-16.6-16s6.3-16.6,17.9-16.6,5.4.4,7.4,1.2l-1.5,7.2c-1.2-.5-2.9-1-5.4-1-5.1,0-8.4,3.6-8.4,8.8,0,5.7,3.8,8.8,8.6,8.8s4.1-.4,5.6-1l1.2,7.2Z"/>
|
|
25
|
+
</g>
|
|
26
|
+
<path class="st2" d="M25.4,18.5h0c-1,0-2,.4-2.7,1.1-.7.7-1.1,1.7-1.1,2.8v58.4c0,2.1,1.7,3.9,3.9,3.9,9.1,0,24.3,1.9,34.7,12.9v-61.1c0-.7-.2-1.4-.5-2-8.6-13.9-25.1-16-34.2-16Z"/>
|
|
27
|
+
<path class="st2" d="M104.8,80.9V22.4c0-1-.4-2-1.1-2.8-.7-.7-1.7-1.1-2.7-1.1h0c-9.1,0-25.6,2.1-34.2,16-.4.6-.5,1.2-.5,2v61.1c10.5-11,25.7-12.9,34.7-12.9,2.2,0,3.9-1.8,3.9-3.9Z"/>
|
|
28
|
+
<path class="st1" d="M113.7,32h-2.8v48.8c0,5.5-4.5,9.9-9.9,9.9-7.7,0-20.4,1.5-29.4,10,15.5-3.8,31.9-1.3,41.2.8,1.2.3,2.4,0,3.3-.8.9-.7,1.5-1.9,1.5-3v-61.9c0-2.1-1.7-3.9-3.9-3.9Z"/>
|
|
29
|
+
<path class="st1" d="M15.4,80.9v-48.8h-2.8c-2.1,0-3.9,1.7-3.9,3.9v61.9c0,1.2.5,2.3,1.5,3,.9.7,2.1,1,3.3.8,9.3-2.1,25.7-4.6,41.2-.8-9-8.5-21.7-10-29.4-10-5.5,0-9.9-4.5-9.9-9.9Z"/>
|
|
30
|
+
<g>
|
|
31
|
+
<path class="st1" d="M131.7,55.6c2.6-.6,6.8-1,11-1,6,0,9.9,1,12.7,3.4,2.4,1.8,3.9,4.6,3.9,8.2s-3,8.5-7.9,10.2v.2c4.4,1,9.6,4.7,9.6,11.6s-1.6,7.1-4,9.3c-3.2,3-8.4,4.4-16,4.4s-7.3-.3-9.3-.6v-45.7ZM137.7,74.6h5.5c6.3,0,10-3.4,10-7.9s-4.1-7.6-10.2-7.6-4.4.2-5.3.4v15.1ZM137.7,96.9c1.2.2,2.9.3,5,.3,6.2,0,12-2.3,12-9.1s-5.5-9-12-9h-5v17.8Z"/>
|
|
32
|
+
<path class="st1" d="M206.1,66.9c-9.3,0-16.6,6.6-16.6,17.8s0,.2,0,.4h0c-.3,7.2-4.5,12.3-10.1,12.3s-10.2-5.5-10.2-12.8,3.1-13,10.3-13,7,2.6,8.5,5.8c.8-2.1,1.9-4,3.4-5.5-2.8-3.1-6.9-4.9-11.8-4.9-9.3,0-16.6,6.6-16.6,17.8s7,17.2,16,17.2,16.2-5.1,16.6-16.8h0c0-.2,0-.4,0-.6,0-6.4,3.1-13,10.3-13s10,7.2,10,12.8-4.4,13-10.2,13-7.3-2.6-8.9-6.5c-.7,2.3-1.9,4.2-3.3,5.8,2.9,3.4,7.1,5.3,12,5.3,8.1,0,16.6-5.4,16.6-17.8s-6.5-17.2-16.1-17.2Z"/>
|
|
33
|
+
<path class="st1" d="M230.6,83.8h.1c.8-1.2,1.9-2.6,2.9-3.8l9.9-11.5h7.3l-12.9,13.7,14.7,19.7h-7.5l-11.5-16-3.1,3.5v12.6h-6v-49.1h6v31Z"/>
|
|
34
|
+
</g>
|
|
35
|
+
</svg>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 260.8 110.5">
|
|
3
|
+
<!-- Generator: Adobe Illustrator 30.7.0, SVG Export Plug-In . SVG Version: 2.1.4 Build 114) -->
|
|
4
|
+
<defs>
|
|
5
|
+
<style>
|
|
6
|
+
.st0 {
|
|
7
|
+
fill: none;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.st1 {
|
|
11
|
+
fill: #69c;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.st2 {
|
|
15
|
+
fill: #33669a;
|
|
16
|
+
}
|
|
17
|
+
</style>
|
|
18
|
+
</defs>
|
|
19
|
+
<g>
|
|
20
|
+
<rect class="st0" x="128" y="5.9" width="297.7" height="245.4"/>
|
|
21
|
+
<path class="st2" d="M132.6,41.3c2.6,1.3,6.7,2.7,10.8,2.7s6.8-1.9,6.8-4.7-2-4.2-7.2-6c-7.2-2.6-11.9-6.5-11.9-12.8s6.2-12.9,16.3-12.9,8.4,1,11,2.2l-2.2,7.8c-1.7-.8-4.8-2-9-2s-6.3,2-6.3,4.2,2.4,4,8,6.1c7.6,2.8,11.1,6.8,11.1,12.9s-5.5,13.4-17.3,13.4-9.8-1.3-12.2-2.7l2-8Z"/>
|
|
22
|
+
<path class="st2" d="M163.3,30.5c0-4.1-.1-7.6-.3-10.5h8.4l.4,4.4h.1c2.3-3.3,5.9-5.1,10.4-5.1,6.8,0,13,6,13,15.9s-7.2,16.8-14.2,16.8-6.7-1.5-8.1-3.6h-.1v15.6h-9.7V30.5ZM173,37.9c0,.8,0,1.4.2,2,.6,2.6,2.9,4.6,5.7,4.6,4.2,0,6.7-3.5,6.7-8.8s-2.2-8.8-6.6-8.8-5.2,2-5.8,4.9c-.1.5-.2,1.2-.2,1.7v4.3Z"/>
|
|
23
|
+
<path class="st2" d="M206,38.8c.3,4,4.3,6,8.8,6s6-.4,8.6-1.3l1.3,6.6c-3.2,1.3-7.1,2-11.3,2-10.6,0-16.7-6.1-16.7-15.9s4.9-16.7,15.8-16.7,14,7.9,14,15.6-.2,3.1-.3,3.8h-20.2ZM217.5,32.1c0-2.4-1-6.3-5.5-6.3s-5.8,3.8-6,6.3h11.5Z"/>
|
|
24
|
+
<path class="st2" d="M252.6,50.5c-1.7.8-5,1.5-8.7,1.5-10.1,0-16.6-6.2-16.6-16s6.3-16.6,17.9-16.6,5.4.4,7.4,1.2l-1.5,7.2c-1.2-.5-2.9-1-5.4-1-5.1,0-8.4,3.6-8.4,8.8,0,5.7,3.8,8.8,8.6,8.8s4.1-.4,5.6-1l1.2,7.2Z"/>
|
|
25
|
+
</g>
|
|
26
|
+
<path class="st2" d="M25.4,18.5h0c-1,0-2,.4-2.7,1.1-.7.7-1.1,1.7-1.1,2.8v58.4c0,2.1,1.7,3.9,3.9,3.9,9.1,0,24.3,1.9,34.7,12.9v-61.1c0-.7-.2-1.4-.5-2-8.6-13.9-25.1-16-34.2-16Z"/>
|
|
27
|
+
<path class="st2" d="M104.8,80.9V22.4c0-1-.4-2-1.1-2.8-.7-.7-1.7-1.1-2.7-1.1h0c-9.1,0-25.6,2.1-34.2,16-.4.6-.5,1.2-.5,2v61.1c10.5-11,25.7-12.9,34.7-12.9,2.2,0,3.9-1.8,3.9-3.9Z"/>
|
|
28
|
+
<path class="st1" d="M113.7,32h-2.8v48.8c0,5.5-4.5,9.9-9.9,9.9-7.7,0-20.4,1.5-29.4,10,15.5-3.8,31.9-1.3,41.2.8,1.2.3,2.4,0,3.3-.8.9-.7,1.5-1.9,1.5-3v-61.9c0-2.1-1.7-3.9-3.9-3.9Z"/>
|
|
29
|
+
<path class="st1" d="M15.4,80.9v-48.8h-2.8c-2.1,0-3.9,1.7-3.9,3.9v61.9c0,1.2.5,2.3,1.5,3,.9.7,2.1,1,3.3.8,9.3-2.1,25.7-4.6,41.2-.8-9-8.5-21.7-10-29.4-10-5.5,0-9.9-4.5-9.9-9.9Z"/>
|
|
30
|
+
<g>
|
|
31
|
+
<path class="st1" d="M131.7,55.6c2.6-.6,6.8-1,11-1,6,0,9.9,1,12.7,3.4,2.4,1.8,3.9,4.6,3.9,8.2s-3,8.5-7.9,10.2v.2c4.4,1,9.6,4.7,9.6,11.6s-1.6,7.1-4,9.3c-3.2,3-8.4,4.4-16,4.4s-7.3-.3-9.3-.6v-45.7ZM137.7,74.6h5.5c6.3,0,10-3.4,10-7.9s-4.1-7.6-10.2-7.6-4.4.2-5.3.4v15.1ZM137.7,96.9c1.2.2,2.9.3,5,.3,6.2,0,12-2.3,12-9.1s-5.5-9-12-9h-5v17.8Z"/>
|
|
32
|
+
<path class="st1" d="M206.1,66.9c-9.3,0-16.6,6.6-16.6,17.8s0,.2,0,.4h0c-.3,7.2-4.5,12.3-10.1,12.3s-10.2-5.5-10.2-12.8,3.1-13,10.3-13,7,2.6,8.5,5.8c.8-2.1,1.9-4,3.4-5.5-2.8-3.1-6.9-4.9-11.8-4.9-9.3,0-16.6,6.6-16.6,17.8s7,17.2,16,17.2,16.2-5.1,16.6-16.8h0c0-.2,0-.4,0-.6,0-6.4,3.1-13,10.3-13s10,7.2,10,12.8-4.4,13-10.2,13-7.3-2.6-8.9-6.5c-.7,2.3-1.9,4.2-3.3,5.8,2.9,3.4,7.1,5.3,12,5.3,8.1,0,16.6-5.4,16.6-17.8s-6.5-17.2-16.1-17.2Z"/>
|
|
33
|
+
<path class="st1" d="M230.6,83.8h.1c.8-1.2,1.9-2.6,2.9-3.8l9.9-11.5h7.3l-12.9,13.7,14.7,19.7h-7.5l-11.5-16-3.1,3.5v12.6h-6v-49.1h6v31Z"/>
|
|
34
|
+
</g>
|
|
35
|
+
</svg>
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
export declare const renderMarkdown: (specification:
|
|
1
|
+
import type { Spec } from "./specbook-format-spec.js";
|
|
2
|
+
import type { Schema } from "./specbook-format-schema.js";
|
|
3
|
+
export declare const renderMarkdown: (specification: Spec, config?: Schema) => string;
|