@rse/specbook 0.9.2 → 0.9.4
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 +195 -14
- package/dst/specbook-api.d.ts +1 -1
- package/dst/specbook-api.js +11 -2
- package/dst/specbook-cli.js +15 -16
- package/dst/specbook-cmd-describe.js +3 -1
- package/dst/specbook-cmd-edit.js +6 -1
- package/dst/specbook-cmd-export.d.ts +1 -1
- package/dst/specbook-cmd-export.js +6 -5
- package/dst/specbook-cmd-import.js +6 -1
- package/dst/specbook-cmd-init.js +4 -2
- package/dst/specbook-cmd-lint.js +4 -2
- package/dst/specbook-config.js +22 -7
- package/dst/specbook-diagram.js +67 -43
- package/dst/specbook-export-ast.js +5 -2
- package/dst/specbook-export-common.d.ts +0 -1
- package/dst/specbook-export-common.js +32 -19
- package/dst/specbook-export-html-search.js +6 -4
- package/dst/specbook-export-html.css +21 -0
- package/dst/specbook-export-html.d.ts +2 -2
- package/dst/specbook-export-html.js +218 -70
- package/dst/specbook-export-md.js +10 -6
- package/dst/specbook-export-pdf.js +4 -3
- package/dst/specbook-llm.js +18 -28
- package/dst/specbook-mcp.js +4 -6
- package/dst/specbook-parse-semantic.js +73 -58
- package/dst/specbook-parse-syntax.js +17 -16
- package/dst/specbook-parse-value.js +7 -1
- package/dst/specbook-parse.js +4 -0
- package/dst/specbook-struct-schema.d.ts +7 -0
- package/dst/specbook-struct-schema.js +10 -3
- package/dst/specbook-theme.js +1 -1
- package/package.json +4 -4
package/dst/specbook-diagram.js
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
** Licensed under Apache 2.0 <https://spdx.org/licenses/Apache-2.0>
|
|
5
5
|
*/
|
|
6
6
|
import { referenceRegex, buildLinkIndex, resolveUnique, resolveSet, anchorPaths } from "./specbook-link.js";
|
|
7
|
+
/* the single Wiki-style reference match (the non-global sibling of
|
|
8
|
+
the imported, global "referenceRegex") */
|
|
9
|
+
const referenceOnce = /\[\[([^[\]]+)\]\]/;
|
|
7
10
|
/* strip the inline code markup of a name or property value
|
|
8
11
|
(preserved in the AST for rendering) for labeling purposes */
|
|
9
12
|
const plainText = (text) => text.replace(/`/g, "");
|
|
@@ -27,7 +30,56 @@ const arityToken = (text) => /^[^\s[\]"]+$/.test(text) ? text : quoted(text);
|
|
|
27
30
|
collapsed, as a directive is a single line) */
|
|
28
31
|
const configValue = (value) => {
|
|
29
32
|
const text = String(value).replace(/\s+/g, " ").trim();
|
|
30
|
-
return
|
|
33
|
+
return /^[^\s"]+$/.test(text) ? text : quoted(text);
|
|
34
|
+
};
|
|
35
|
+
/* generate the Gradia spec text of a derived diagram, opened by a
|
|
36
|
+
"#type" directive and the configured "#config" directives so every
|
|
37
|
+
embedded spec is self-contained: one node statement per object (the
|
|
38
|
+
unique anchor path as the node id, the object name as the displayed
|
|
39
|
+
label) and one edge statement per derived edge */
|
|
40
|
+
const renderSpec = (diagram, type, center, nodes, edges, index, anchors) => {
|
|
41
|
+
const lines = [`#type ${type}`];
|
|
42
|
+
const config = diagram.config ?? {};
|
|
43
|
+
for (const key of Object.keys(config)) {
|
|
44
|
+
const value = config[key];
|
|
45
|
+
if (value !== undefined)
|
|
46
|
+
lines.push(`#config ${key} ${configValue(value)}`);
|
|
47
|
+
}
|
|
48
|
+
lines.push("");
|
|
49
|
+
for (const node of nodes) {
|
|
50
|
+
const anchor = anchors.get(node) ?? node.id;
|
|
51
|
+
const attrs = [`url: ${atom(`#${anchor}`)}`];
|
|
52
|
+
if (diagram.qualified === true && node.kind !== "")
|
|
53
|
+
attrs.push(`type: ${atom(node.kind)}`);
|
|
54
|
+
if (type === "hub" ? node === center : node.primary === true)
|
|
55
|
+
attrs.push("primary: true");
|
|
56
|
+
/* attach the values of the configured properties as Gradia
|
|
57
|
+
key/value attributes (a node lacking a property is fine, as
|
|
58
|
+
the node set can mix objects of different kinds), with every
|
|
59
|
+
"[[...]]" reference stripped to its target object name */
|
|
60
|
+
for (const key of diagram.properties ?? []) {
|
|
61
|
+
const value = node.properties.find((property) => plainKey(property.key) === key)?.value;
|
|
62
|
+
if (value !== undefined) {
|
|
63
|
+
const text = value.replace(referenceRegex, (_, reference) => {
|
|
64
|
+
const target = resolveUnique(index, reference.trim()).target;
|
|
65
|
+
return target !== undefined ? target.name : reference.trim();
|
|
66
|
+
});
|
|
67
|
+
attrs.push(`${atom(key)}: ${atom(plainText(text))}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
lines.push(`${atom(anchor)}: ${atom(plainText(node.name))} [ ${attrs.join(", ")} ]`);
|
|
71
|
+
}
|
|
72
|
+
if (edges.length > 0) {
|
|
73
|
+
lines.push("");
|
|
74
|
+
for (const edge of edges) {
|
|
75
|
+
const op = "--" +
|
|
76
|
+
(edge.name !== undefined && edge.name !== "" ? `(${nameToken(edge.name)})--` : "") + ">" +
|
|
77
|
+
(edge.arity !== undefined && edge.arity !== "" ? `[${arityToken(edge.arity)}]` : "");
|
|
78
|
+
lines.push(`${atom(anchors.get(edge.source) ?? edge.source.id)} ${op} ` +
|
|
79
|
+
atom(anchors.get(edge.target) ?? edge.target.id));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return lines.join("\n") + "\n";
|
|
31
83
|
};
|
|
32
84
|
/* derive the Gradia spec of a single object from its "diagram:"
|
|
33
85
|
schema configuration (the returned spec is absent whenever the
|
|
@@ -69,7 +121,8 @@ const deriveDiagram = (object, diagram, index, anchors, parents) => {
|
|
|
69
121
|
walk(object);
|
|
70
122
|
}
|
|
71
123
|
const edgeObjects = diagram.edges !== undefined ? resolvePatterns(diagram.edges, "edges") : [];
|
|
72
|
-
|
|
124
|
+
const edgeSet = new Set(edgeObjects);
|
|
125
|
+
nodes = nodes.filter((node) => !edgeSet.has(node));
|
|
73
126
|
const nodeSet = new Set(nodes);
|
|
74
127
|
/* derive the edges: from the "[[...]]" references of the node
|
|
75
128
|
objects (per the "links" selection) and from the edge objects by
|
|
@@ -98,8 +151,8 @@ const deriveDiagram = (object, diagram, index, anchors, parents) => {
|
|
|
98
151
|
const value = diagram.edgeTarget !== undefined ?
|
|
99
152
|
edgeObject.properties.find((property) => plainKey(property.key) === diagram.edgeTarget)?.value :
|
|
100
153
|
edgeObject.properties.map((property) => property.value)
|
|
101
|
-
.find((v) =>
|
|
102
|
-
const reference = value?.match(
|
|
154
|
+
.find((v) => referenceOnce.test(v));
|
|
155
|
+
const reference = value?.match(referenceOnce)?.[1].trim();
|
|
103
156
|
const target = reference !== undefined ? resolveUnique(index, reference).target : undefined;
|
|
104
157
|
if (source === undefined) {
|
|
105
158
|
errors.push(`diagram edge object "${edgeObject.name}" has no parent object as source`);
|
|
@@ -124,15 +177,18 @@ const deriveDiagram = (object, diagram, index, anchors, parents) => {
|
|
|
124
177
|
edges.push({ source: parent, target: node });
|
|
125
178
|
}
|
|
126
179
|
}
|
|
127
|
-
else
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
180
|
+
else {
|
|
181
|
+
if (diagram.edges !== undefined)
|
|
182
|
+
errors.push("\"grid\" diagram cannot carry an \"edges\" configuration");
|
|
183
|
+
if (diagram.hierarchy === true)
|
|
184
|
+
errors.push("\"grid\" diagram cannot carry a \"hierarchy\" configuration");
|
|
185
|
+
}
|
|
131
186
|
/* deduplicate the edges (the same reference can occur in
|
|
132
187
|
multiple texts of the same node object) */
|
|
133
188
|
const seenEdges = new Set();
|
|
134
189
|
edges = edges.filter((edge) => {
|
|
135
|
-
const key = `${anchors.get(edge.source)
|
|
190
|
+
const key = `${anchors.get(edge.source) ?? edge.source.id}` +
|
|
191
|
+
`\u0000${anchors.get(edge.target) ?? edge.target.id}` +
|
|
136
192
|
`\u0000${edge.name ?? ""}\u0000${edge.arity ?? ""}`;
|
|
137
193
|
if (seenEdges.has(key))
|
|
138
194
|
return false;
|
|
@@ -144,7 +200,7 @@ const deriveDiagram = (object, diagram, index, anchors, parents) => {
|
|
|
144
200
|
resolved object of an explicit "[[...]]" reference otherwise */
|
|
145
201
|
let center = object;
|
|
146
202
|
if (type === "hub" && diagram.center !== undefined && diagram.center !== "self") {
|
|
147
|
-
const reference = diagram.center.match(
|
|
203
|
+
const reference = diagram.center.match(referenceOnce)?.[1].trim();
|
|
148
204
|
const resolved = reference !== undefined ? resolveUnique(index, reference) : undefined;
|
|
149
205
|
if (resolved?.target === undefined) {
|
|
150
206
|
errors.push(`${resolved?.ambiguous === true ? "ambiguous" : "unresolvable"}` +
|
|
@@ -190,39 +246,7 @@ const deriveDiagram = (object, diagram, index, anchors, parents) => {
|
|
|
190
246
|
diagram carries no information beyond the object itself */
|
|
191
247
|
if (diagram.collapse !== false && nodes.length === 1 && edges.length === 0)
|
|
192
248
|
return { errors };
|
|
193
|
-
|
|
194
|
-
the configured "#config" directives so every embedded spec is
|
|
195
|
-
self-contained: one node statement per object (the unique anchor
|
|
196
|
-
path as the node id, the object name as the displayed label)
|
|
197
|
-
and one edge statement per derived edge */
|
|
198
|
-
const lines = [`#type ${type}`];
|
|
199
|
-
const config = diagram.config ?? {};
|
|
200
|
-
for (const key of Object.keys(config)) {
|
|
201
|
-
const value = config[key];
|
|
202
|
-
if (value !== undefined)
|
|
203
|
-
lines.push(`#config ${key} ${configValue(value)}`);
|
|
204
|
-
}
|
|
205
|
-
lines.push("");
|
|
206
|
-
for (const node of nodes) {
|
|
207
|
-
const anchor = anchors.get(node) ?? node.id;
|
|
208
|
-
const attrs = [`url: ${atom(`#${anchor}`)}`];
|
|
209
|
-
if (diagram.qualified === true && node.kind !== "")
|
|
210
|
-
attrs.push(`type: ${atom(node.kind)}`);
|
|
211
|
-
if (type === "hub" ? node === center : node.primary === true)
|
|
212
|
-
attrs.push("primary: true");
|
|
213
|
-
lines.push(`${atom(anchor)}: ${atom(plainText(node.name))} [ ${attrs.join(", ")} ]`);
|
|
214
|
-
}
|
|
215
|
-
if (edges.length > 0) {
|
|
216
|
-
lines.push("");
|
|
217
|
-
for (const edge of edges) {
|
|
218
|
-
const op = "--" +
|
|
219
|
-
(edge.name !== undefined && edge.name !== "" ? `(${nameToken(edge.name)})--` : "") + ">" +
|
|
220
|
-
(edge.arity !== undefined && edge.arity !== "" ? `[${arityToken(edge.arity)}]` : "");
|
|
221
|
-
lines.push(`${atom(anchors.get(edge.source) ?? edge.source.id)} ${op} ` +
|
|
222
|
-
atom(anchors.get(edge.target) ?? edge.target.id));
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
return { spec: lines.join("\n") + "\n", errors };
|
|
249
|
+
return { spec: renderSpec(diagram, type, center, nodes, edges, index, anchors), errors };
|
|
226
250
|
};
|
|
227
251
|
/* collect the objects whose schema configuration carries a "diagram:"
|
|
228
252
|
field (the schema resolution mirrors the semantic validation) */
|
|
@@ -25,10 +25,13 @@ export const renderAst = async (specification, format, config) => {
|
|
|
25
25
|
};
|
|
26
26
|
specification.artifacts.forEach((artifact, i) => artifact.objects.forEach((object, j) => walk(object, plain.artifacts[i].objects[j])));
|
|
27
27
|
}
|
|
28
|
+
/* serialize the plain specification into the target format
|
|
29
|
+
(JSON and JSON5 in compressed form, as they are intended
|
|
30
|
+
for machine consumption only) */
|
|
28
31
|
if (format === "json")
|
|
29
|
-
return Buffer.from(JSON.stringify(plain
|
|
32
|
+
return Buffer.from(JSON.stringify(plain) + "\n", "utf8");
|
|
30
33
|
else if (format === "json5")
|
|
31
|
-
return Buffer.from(JSON5.stringify(plain
|
|
34
|
+
return Buffer.from(JSON5.stringify(plain) + "\n", "utf8");
|
|
32
35
|
else if (format === "yaml")
|
|
33
36
|
return Buffer.from(stringifyYaml(plain), "utf8");
|
|
34
37
|
else
|
|
@@ -18,7 +18,6 @@ export type PaperSetup = {
|
|
|
18
18
|
right: number;
|
|
19
19
|
};
|
|
20
20
|
};
|
|
21
|
-
export declare const paperSizes: string[];
|
|
22
21
|
export declare const paperSizeDefault = "A4";
|
|
23
22
|
export declare const paperSetup: (paper: string) => PaperSetup;
|
|
24
23
|
export declare const paperLength: (setup: PaperSetup, value: number) => string;
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import fs from "node:fs";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
-
/* escape a text for embedding into template HTML */
|
|
10
|
-
export const escapeHtml = (text) => text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")
|
|
9
|
+
/* escape a text for embedding into template HTML (text and attributes) */
|
|
10
|
+
export const escapeHtml = (text) => text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")
|
|
11
|
+
.replace(/"/g, """).replace(/'/g, "'");
|
|
11
12
|
/* provide the build-time pre-assembled stylesheet (with the
|
|
12
13
|
font faces already inlined as base64 data: URIs) */
|
|
13
14
|
export const stylesheet = () => fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), "specbook-export-html.css"), "utf8");
|
|
@@ -18,9 +19,10 @@ export const searchScript = () => fs.readFileSync(path.join(path.dirname(fileURL
|
|
|
18
19
|
export const fallbackLogo = () => "data:image/svg+xml;base64," + fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), "specbook-export-logo.svg")).toString("base64");
|
|
19
20
|
/* check whether an object is the specification title object */
|
|
20
21
|
export const isTitleObject = (object) => object.kind === "META" && object.name.toUpperCase() === "TITLE";
|
|
22
|
+
/* determine the title object of the specification */
|
|
23
|
+
const titleObject = (specification) => specification.artifacts.flatMap((artifact) => artifact.objects).find(isTitleObject);
|
|
21
24
|
/* determine a property value of the title object */
|
|
22
|
-
const titleProperty = (specification, name) => specification.
|
|
23
|
-
?.properties.find((property) => property.key === name)?.value;
|
|
25
|
+
const titleProperty = (specification, name) => titleObject(specification)?.properties.find((property) => property.key === name)?.value;
|
|
24
26
|
/* determine the document language (LANG) from the title object */
|
|
25
27
|
export const documentLang = (specification) => titleProperty(specification, "LANG")?.trim();
|
|
26
28
|
/* determine the document character set (CHARSET) from the title object */
|
|
@@ -37,28 +39,39 @@ const papers = {
|
|
|
37
39
|
"Letter": { unit: "in", height: 11, margin: { top: 1, bottom: 1, left: 0.8, right: 0.8 } },
|
|
38
40
|
"Legal": { unit: "in", height: 14, margin: { top: 1, bottom: 1, left: 0.8, right: 0.8 } }
|
|
39
41
|
};
|
|
40
|
-
|
|
42
|
+
const paperSizes = Object.keys(papers);
|
|
41
43
|
export const paperSizeDefault = "A4";
|
|
42
44
|
/* provide the setup of a paper size, falling back onto the default */
|
|
43
45
|
export const paperSetup = (paper) => papers[paper] ?? papers[paperSizeDefault];
|
|
44
46
|
/* render a paper dimension as its CSS length */
|
|
45
47
|
export const paperLength = (setup, value) => `${value}${setup.unit}`;
|
|
46
48
|
/* determine the document paper size (PAPER-SIZE) from the title object,
|
|
47
|
-
matched case-insensitively
|
|
49
|
+
matched case-insensitively, falling back onto the default if unset
|
|
50
|
+
and rejecting an unknown size */
|
|
48
51
|
export const documentPaperSize = (specification) => {
|
|
49
|
-
const value = titleProperty(specification, "PAPER-SIZE")?.trim()
|
|
50
|
-
|
|
52
|
+
const value = titleProperty(specification, "PAPER-SIZE")?.trim();
|
|
53
|
+
if (value === undefined)
|
|
54
|
+
return paperSizeDefault;
|
|
55
|
+
const paper = paperSizes.find((name) => name.toLowerCase() === value.toLowerCase());
|
|
56
|
+
if (paper === undefined)
|
|
57
|
+
throw new Error(`unknown paper size "${value}" ` +
|
|
58
|
+
`(expected ${paperSizes.join(", ")})`);
|
|
59
|
+
return paper;
|
|
51
60
|
};
|
|
52
61
|
/* provide the paper-dependent print stylesheet: a diagram is scaled
|
|
53
62
|
down to still fit onto a single page (the paper height less the
|
|
54
|
-
print margins
|
|
55
|
-
|
|
63
|
+
print margins, the own vertical margins of the diagram, and the
|
|
64
|
+
room its introducing heading claims above it) and is never broken
|
|
65
|
+
across a page boundary; without the heading reserve a maximally
|
|
66
|
+
sized diagram could not share its page with the heading, which
|
|
67
|
+
would defeat the "break-after: avoid" bundling of the stylesheet */
|
|
68
|
+
const headingReserve = 6;
|
|
56
69
|
export const paperStylesheet = (paper) => {
|
|
57
70
|
const setup = paperSetup(paper);
|
|
58
71
|
const avail = setup.height - setup.margin.top - setup.margin.bottom;
|
|
59
72
|
return "@media print {\n" +
|
|
60
73
|
"div.diagram { break-inside: avoid; }\n" +
|
|
61
|
-
`div.diagram svg { max-height: calc(${paperLength(setup, avail)} -
|
|
74
|
+
`div.diagram svg { max-height: calc(${paperLength(setup, avail)} - ${headingReserve}rem);` +
|
|
62
75
|
" width: auto; height: auto; }\n" +
|
|
63
76
|
"}\n";
|
|
64
77
|
};
|
|
@@ -87,7 +100,7 @@ export const charsetCodepoints = (charset) => {
|
|
|
87
100
|
else if (name === "utf8" || name === "utf16" || name === "unicode")
|
|
88
101
|
return undefined;
|
|
89
102
|
throw new Error(`unknown charset "${charset}" ` +
|
|
90
|
-
"(expected US-ASCII, ISO-8859-1/ISO-Latin-1, ISO-8859-15/ISO-Latin-
|
|
103
|
+
"(expected US-ASCII, ISO-8859-1/ISO-Latin-1, ISO-8859-15/ISO-Latin-9, or UTF-8)");
|
|
91
104
|
};
|
|
92
105
|
/* the symbol glyphs used by the HTML/PDF rendering (kind and property
|
|
93
106
|
bullets, link symbol, primary marker, theme switch icon, anchor
|
|
@@ -123,18 +136,18 @@ export const subsetStylesheet = async (charset) => {
|
|
|
123
136
|
return result + css.slice(last);
|
|
124
137
|
};
|
|
125
138
|
/* determine the document title and subtitle from the title object */
|
|
126
|
-
export const documentTitle = (specification) => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
};
|
|
139
|
+
export const documentTitle = (specification) => ({
|
|
140
|
+
title: titleProperty(specification, "TITLE") ??
|
|
141
|
+
titleObject(specification)?.name ?? "Specification",
|
|
142
|
+
subtitle: titleProperty(specification, "SUBTITLE")
|
|
143
|
+
});
|
|
131
144
|
/* determine the document logo as a self-contained data: URL, taken from the
|
|
132
145
|
embedded image of the LOGO property of the title object, and falling back
|
|
133
146
|
onto the built-in SpecBook logo (for use in isolated rendering contexts,
|
|
134
147
|
like the print header/footer, which load no external resources) */
|
|
135
148
|
export const documentLogo = (specification) => {
|
|
136
|
-
const
|
|
137
|
-
|
|
149
|
+
const content = titleObject(specification)
|
|
150
|
+
?.properties.find((property) => property.key === "LOGO")?.embedding?.[0];
|
|
138
151
|
if (content === undefined)
|
|
139
152
|
return fallbackLogo();
|
|
140
153
|
return content.startsWith("data:") ? content :
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
return
|
|
46
46
|
parent = parent.parentElement
|
|
47
47
|
}
|
|
48
|
-
const text = (el.textContent
|
|
48
|
+
const text = (el.textContent ?? "").toLowerCase()
|
|
49
49
|
const words = new Set(text.split(/[^\p{L}\p{N}]+/u).filter((w) => w !== ""))
|
|
50
50
|
words.forEach((w) => { vocab.add(w) })
|
|
51
51
|
units.push({ el, text, words, keep: false })
|
|
@@ -141,7 +141,7 @@
|
|
|
141
141
|
keeps the tags and attributes untouched */
|
|
142
142
|
const highlight = (el, regex) => {
|
|
143
143
|
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT)
|
|
144
|
-
const nodes
|
|
144
|
+
const nodes = []
|
|
145
145
|
let node
|
|
146
146
|
while ((node = walker.nextNode()) !== null)
|
|
147
147
|
nodes.push(node)
|
|
@@ -231,13 +231,15 @@
|
|
|
231
231
|
})
|
|
232
232
|
|
|
233
233
|
/* hide the units not kept, plus the objects and tables which
|
|
234
|
-
ran entirely empty in the process
|
|
234
|
+
ran entirely empty in the process (a table nested inside a
|
|
235
|
+
kept unit, like a chunked property table, stays visible) */
|
|
235
236
|
units.forEach((unit) => {
|
|
236
237
|
if (!unit.el.classList.contains("search-keep"))
|
|
237
238
|
unit.el.classList.add("search-hide")
|
|
238
239
|
})
|
|
239
240
|
document.querySelectorAll("article section, article table").forEach((el) => {
|
|
240
|
-
if (el.querySelector(".search-keep") === null
|
|
241
|
+
if (el.querySelector(".search-keep") === null
|
|
242
|
+
&& el.closest(".search-keep") === null)
|
|
241
243
|
el.classList.add("search-hide")
|
|
242
244
|
})
|
|
243
245
|
|
|
@@ -137,6 +137,13 @@ section { margin: 0.5rem 0; }
|
|
|
137
137
|
h1 { margin: 3.0rem 0 2.0rem 0; border-bottom: 1px solid var(--theme-color-specbook-border); }
|
|
138
138
|
h2, h3, h4 { margin: 2.0rem 0 1.0rem 0; }
|
|
139
139
|
|
|
140
|
+
/* bundle a heading with the content it introduces (diagram, property
|
|
141
|
+
table, description), so no heading is left orphaned at the very
|
|
142
|
+
bottom of a page while its content starts on the next one */
|
|
143
|
+
@media print {
|
|
144
|
+
h1, h2, h3, h4, h5, h6 { page-break-after: avoid; break-after: avoid; }
|
|
145
|
+
}
|
|
146
|
+
|
|
140
147
|
body { counter-reset: sec1; }
|
|
141
148
|
article h1 { counter-increment: sec1; counter-reset: sec2; }
|
|
142
149
|
article h2 { counter-increment: sec2; counter-reset: sec3; }
|
|
@@ -192,6 +199,20 @@ table.objects div.description { margin: 0; }
|
|
|
192
199
|
table.objects div.description > :first-child { margin-top: 0; }
|
|
193
200
|
table.objects div.description > :last-child { margin-bottom: 0; }
|
|
194
201
|
|
|
202
|
+
/* the embedded per-object chunk tables of the over-wide compact tables */
|
|
203
|
+
table.objects td.chunks { padding: 0; }
|
|
204
|
+
table.chunks { border-collapse: collapse; table-layout: fixed;
|
|
205
|
+
width: 100%; }
|
|
206
|
+
table.objects table.chunks tr td { background-color: transparent; }
|
|
207
|
+
table.objects table.chunks td:first-child { font-weight: normal; }
|
|
208
|
+
|
|
209
|
+
/* the nested renderings inside the description cells of the compact
|
|
210
|
+
tables ("concise" sub-tables and pressed-in "complex" objects),
|
|
211
|
+
reduced in size to fit into the cell */
|
|
212
|
+
table.objects td table.objects { font-size: 85%; margin: 0.5rem 0; }
|
|
213
|
+
table.objects td section { font-size: 85%; margin: 0.5rem 0; }
|
|
214
|
+
table.objects td section h6 { margin: 0.5rem 0 0.25rem 0; }
|
|
215
|
+
|
|
195
216
|
p.description,
|
|
196
217
|
div.description { margin: 1.0rem 0 0.5rem 0; }
|
|
197
218
|
div.description p { margin: 0.5rem 0; }
|
|
@@ -5,5 +5,5 @@ export type OutlineEntry = {
|
|
|
5
5
|
anchor: string;
|
|
6
6
|
childs: OutlineEntry[];
|
|
7
7
|
};
|
|
8
|
-
export declare const htmlOutline: (specification: Specification,
|
|
9
|
-
export declare const renderHtml: (specification: Specification,
|
|
8
|
+
export declare const htmlOutline: (specification: Specification, config?: SchemaSpecification) => OutlineEntry[];
|
|
9
|
+
export declare const renderHtml: (specification: Specification, config?: SchemaSpecification, tocPages?: Map<string, number>, css?: string) => Promise<string>;
|