@aruzone/aze-forge 0.1.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 +408 -0
- package/dist/acceptance.d.ts +48 -0
- package/dist/acceptance.js +180 -0
- package/dist/acceptance.js.map +1 -0
- package/dist/assets.d.ts +19 -0
- package/dist/assets.js +509 -0
- package/dist/assets.js.map +1 -0
- package/dist/atomic-write.d.ts +1 -0
- package/dist/atomic-write.js +23 -0
- package/dist/atomic-write.js.map +1 -0
- package/dist/callout.d.ts +27 -0
- package/dist/callout.js +78 -0
- package/dist/callout.js.map +1 -0
- package/dist/capabilities-json.d.ts +4 -0
- package/dist/capabilities-json.js +225 -0
- package/dist/capabilities-json.js.map +1 -0
- package/dist/capabilities.d.ts +110 -0
- package/dist/capabilities.js +270 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +1096 -0
- package/dist/cli.js.map +1 -0
- package/dist/compiler.d.ts +3 -0
- package/dist/compiler.js +1034 -0
- package/dist/compiler.js.map +1 -0
- package/dist/configuration-error.d.ts +4 -0
- package/dist/configuration-error.js +9 -0
- package/dist/configuration-error.js.map +1 -0
- package/dist/diagnostics-json.d.ts +23 -0
- package/dist/diagnostics-json.js +143 -0
- package/dist/diagnostics-json.js.map +1 -0
- package/dist/diagnostics.d.ts +16 -0
- package/dist/diagnostics.js +154 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/equation.d.ts +74 -0
- package/dist/equation.js +485 -0
- package/dist/equation.js.map +1 -0
- package/dist/font.d.ts +19 -0
- package/dist/font.js +75 -0
- package/dist/font.js.map +1 -0
- package/dist/format.d.ts +2 -0
- package/dist/format.js +308 -0
- package/dist/format.js.map +1 -0
- package/dist/hash.d.ts +5 -0
- package/dist/hash.js +189 -0
- package/dist/hash.js.map +1 -0
- package/dist/help.d.ts +9 -0
- package/dist/help.js +102 -0
- package/dist/help.js.map +1 -0
- package/dist/html-fragment.d.ts +7 -0
- package/dist/html-fragment.js +70 -0
- package/dist/html-fragment.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown.d.ts +14 -0
- package/dist/markdown.js +425 -0
- package/dist/markdown.js.map +1 -0
- package/dist/mermaid-browser.d.ts +43 -0
- package/dist/mermaid-browser.js +358 -0
- package/dist/mermaid-browser.js.map +1 -0
- package/dist/mermaid.d.ts +84 -0
- package/dist/mermaid.js +740 -0
- package/dist/mermaid.js.map +1 -0
- package/dist/model.d.ts +406 -0
- package/dist/model.js +2 -0
- package/dist/model.js.map +1 -0
- package/dist/parse.d.ts +3 -0
- package/dist/parse.js +1374 -0
- package/dist/parse.js.map +1 -0
- package/dist/preview-server.d.ts +39 -0
- package/dist/preview-server.js +156 -0
- package/dist/preview-server.js.map +1 -0
- package/dist/preview-shell.d.ts +29 -0
- package/dist/preview-shell.js +102 -0
- package/dist/preview-shell.js.map +1 -0
- package/dist/reference-validation.d.ts +7 -0
- package/dist/reference-validation.js +42 -0
- package/dist/reference-validation.js.map +1 -0
- package/dist/registry.d.ts +29 -0
- package/dist/registry.js +384 -0
- package/dist/registry.js.map +1 -0
- package/dist/render-html.d.ts +24 -0
- package/dist/render-html.js +149 -0
- package/dist/render-html.js.map +1 -0
- package/dist/render-pdf.d.ts +64 -0
- package/dist/render-pdf.js +503 -0
- package/dist/render-pdf.js.map +1 -0
- package/dist/render-png.d.ts +32 -0
- package/dist/render-png.js +427 -0
- package/dist/render-png.js.map +1 -0
- package/dist/render-svg.d.ts +23 -0
- package/dist/render-svg.js +243 -0
- package/dist/render-svg.js.map +1 -0
- package/dist/runtime-support.d.ts +26 -0
- package/dist/runtime-support.js +62 -0
- package/dist/runtime-support.js.map +1 -0
- package/dist/source-map.d.ts +12 -0
- package/dist/source-map.js +75 -0
- package/dist/source-map.js.map +1 -0
- package/dist/table.d.ts +22 -0
- package/dist/table.js +90 -0
- package/dist/table.js.map +1 -0
- package/dist/theme.d.ts +6 -0
- package/dist/theme.js +89 -0
- package/dist/theme.js.map +1 -0
- package/dist/tool-version.d.ts +3 -0
- package/dist/tool-version.js +3 -0
- package/dist/tool-version.js.map +1 -0
- package/dist/type-guards.d.ts +1 -0
- package/dist/type-guards.js +4 -0
- package/dist/type-guards.js.map +1 -0
- package/dist/validate-document.d.ts +2 -0
- package/dist/validate-document.js +248 -0
- package/dist/validate-document.js.map +1 -0
- package/dist/version.d.ts +37 -0
- package/dist/version.js +111 -0
- package/dist/version.js.map +1 -0
- package/dist/watch-dependencies.d.ts +15 -0
- package/dist/watch-dependencies.js +93 -0
- package/dist/watch-dependencies.js.map +1 -0
- package/dist/watch-events.d.ts +51 -0
- package/dist/watch-events.js +27 -0
- package/dist/watch-events.js.map +1 -0
- package/dist/watch-loop.d.ts +39 -0
- package/dist/watch-loop.js +117 -0
- package/dist/watch-loop.js.map +1 -0
- package/package.json +70 -0
- package/schemas/acceptance.json +104 -0
- package/schemas/capabilities.json +491 -0
- package/schemas/diagnostics.json +219 -0
- package/schemas/version.json +167 -0
package/dist/mermaid.js
ADDED
|
@@ -0,0 +1,740 @@
|
|
|
1
|
+
import { arch, platform } from "node:os";
|
|
2
|
+
import { createDiagnostic } from "./diagnostics.js";
|
|
3
|
+
import { sha256 } from "./hash.js";
|
|
4
|
+
import { CHROME_HEADLESS_SHELL_VERSION, MermaidBrowserParseError, MermaidBrowserUnavailableError, renderMermaidInBrowser, } from "./mermaid-browser.js";
|
|
5
|
+
import { SaxesParser } from "saxes";
|
|
6
|
+
import { defaultTheme } from "./theme.js";
|
|
7
|
+
export const MERMAID_PLUGIN_TYPE = "mermaid";
|
|
8
|
+
export const MERMAID_PLUGIN_VERSION = "1.0.0";
|
|
9
|
+
export const MERMAID_BODY_SYNTAX_ID = "azeforge.mermaid/v1";
|
|
10
|
+
export const MERMAID_BODY_SYNTAX_VERSION = "1.0.0";
|
|
11
|
+
/** Pinned Mermaid version per the HTML-first output decision (#10). */
|
|
12
|
+
export const MERMAID_VERSION = "11.17.2";
|
|
13
|
+
export const MERMAID_HTML_BLOCK_RENDERER_ID = "azeforge.mermaid.html/v1";
|
|
14
|
+
export const MERMAID_HTML_BLOCK_RENDERER_VERSION = "1.0.0";
|
|
15
|
+
export const MAX_MERMAID_SOURCE_LENGTH = 8000;
|
|
16
|
+
export const MAX_MERMAID_TEXT_LENGTH = 8000;
|
|
17
|
+
export { CHROME_HEADLESS_SHELL_VERSION, MermaidBrowserParseError, MermaidBrowserUnavailableError, };
|
|
18
|
+
export const mermaidSourceSchema = Object.freeze({
|
|
19
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
20
|
+
$id: "azeforge.mermaid/source/v1",
|
|
21
|
+
type: "object",
|
|
22
|
+
additionalProperties: false,
|
|
23
|
+
properties: {
|
|
24
|
+
id: {
|
|
25
|
+
type: "string",
|
|
26
|
+
pattern: "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$",
|
|
27
|
+
},
|
|
28
|
+
title: { type: "string", minLength: 1, maxLength: 200 },
|
|
29
|
+
description: { type: "string", minLength: 1, maxLength: 1000 },
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
export const mermaidDataSchema = Object.freeze({
|
|
33
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
34
|
+
$id: "azeforge.mermaid/data/v1",
|
|
35
|
+
type: "object",
|
|
36
|
+
additionalProperties: false,
|
|
37
|
+
required: ["diagramType", "source", "pluginVersion"],
|
|
38
|
+
properties: {
|
|
39
|
+
kind: { const: "mermaid" },
|
|
40
|
+
diagramType: { type: "string", minLength: 1 },
|
|
41
|
+
source: { type: "string", minLength: 1, maxLength: 8000 },
|
|
42
|
+
pluginVersion: { const: "1.0.0" },
|
|
43
|
+
title: { type: "string", minLength: 1, maxLength: 200 },
|
|
44
|
+
description: { type: "string", minLength: 1, maxLength: 1000 },
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
export class MermaidSanitizerError extends Error {
|
|
48
|
+
reason;
|
|
49
|
+
constructor(reason) {
|
|
50
|
+
super(reason);
|
|
51
|
+
this.reason = reason;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function headerDiagnostic(code, message, range, sourceName, extra = {}) {
|
|
55
|
+
return createDiagnostic(code, "error", message, {
|
|
56
|
+
location: sourceName === undefined ? { range } : { source: sourceName, range },
|
|
57
|
+
...(extra.suggestion === undefined
|
|
58
|
+
? {}
|
|
59
|
+
: { suggestion: extra.suggestion }),
|
|
60
|
+
...(extra.data === undefined ? {} : { data: extra.data }),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
export function parseMermaidHeader(entries, _blockRange, sourceName) {
|
|
64
|
+
const diagnostics = [];
|
|
65
|
+
let id;
|
|
66
|
+
let title;
|
|
67
|
+
let description;
|
|
68
|
+
for (const entry of entries) {
|
|
69
|
+
if (entry.key === "id") {
|
|
70
|
+
if (entry.value.length === 0) {
|
|
71
|
+
diagnostics.push(headerDiagnostic("azeforge.mermaid#invalid-attribute", "Mermaid attribute id must not be empty.", entry.range, sourceName, { data: { attribute: "id", value: entry.value } }));
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
id = entry.value;
|
|
75
|
+
}
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (entry.key === "title") {
|
|
79
|
+
if (entry.value.length === 0 || entry.value.length > 200) {
|
|
80
|
+
diagnostics.push(headerDiagnostic("azeforge.mermaid#invalid-attribute", "Mermaid attribute title must be 1..200 characters.", entry.range, sourceName, { data: { attribute: "title" } }));
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
title = entry.value;
|
|
84
|
+
}
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (entry.key === "description") {
|
|
88
|
+
if (entry.value.length === 0 || entry.value.length > 1000) {
|
|
89
|
+
diagnostics.push(headerDiagnostic("azeforge.mermaid#invalid-attribute", "Mermaid attribute description must be 1..1000 characters.", entry.range, sourceName, { data: { attribute: "description" } }));
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
description = entry.value;
|
|
93
|
+
}
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
diagnostics.push(headerDiagnostic("azeforge.mermaid#invalid-attribute", `Mermaid attribute "${entry.key}" is not a valid mermaid attribute.`, entry.range, sourceName, {
|
|
97
|
+
suggestion: "Valid mermaid attributes are id, title, and description.",
|
|
98
|
+
data: { attribute: entry.key },
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
...(id === undefined ? {} : { id }),
|
|
103
|
+
...(title === undefined ? {} : { title }),
|
|
104
|
+
...(description === undefined ? {} : { description }),
|
|
105
|
+
diagnostics,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const SUPPORTED_DIAGRAMS = {
|
|
109
|
+
flowchart: true,
|
|
110
|
+
graph: true,
|
|
111
|
+
sequencediagram: true,
|
|
112
|
+
classdiagram: true,
|
|
113
|
+
statediagram: true,
|
|
114
|
+
"statediagram-v2": true,
|
|
115
|
+
erdiagram: true,
|
|
116
|
+
pie: true,
|
|
117
|
+
gantt: true,
|
|
118
|
+
journey: true,
|
|
119
|
+
mindmap: true,
|
|
120
|
+
};
|
|
121
|
+
function diagramKey(firstLine) {
|
|
122
|
+
const head = firstLine.trim().split(/[\s(:]+/, 2)[0]?.toLowerCase() ?? "";
|
|
123
|
+
if (head === "")
|
|
124
|
+
return undefined;
|
|
125
|
+
// "flowchart TD" -> flowchart; "stateDiagram-v2" normalizes below.
|
|
126
|
+
const normalized = head.replace(/_/g, "");
|
|
127
|
+
if (SUPPORTED_DIAGRAMS[normalized] === true)
|
|
128
|
+
return normalized;
|
|
129
|
+
if (normalized === "statediagram-v2" || normalized === "statediagramv2") {
|
|
130
|
+
return "statediagram-v2";
|
|
131
|
+
}
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
function canonicalDiagramType(key, firstLine) {
|
|
135
|
+
if (key === "flowchart" || key === "graph") {
|
|
136
|
+
const direction = firstLine.trim().split(/\s+/)[1]?.toUpperCase();
|
|
137
|
+
if (direction === "TD" ||
|
|
138
|
+
direction === "TB" ||
|
|
139
|
+
direction === "BT" ||
|
|
140
|
+
direction === "LR" ||
|
|
141
|
+
direction === "RL") {
|
|
142
|
+
return key === "graph" ? `graph ${direction}` : `flowchart ${direction}`;
|
|
143
|
+
}
|
|
144
|
+
return key;
|
|
145
|
+
}
|
|
146
|
+
if (key === "sequencediagram")
|
|
147
|
+
return "sequenceDiagram";
|
|
148
|
+
if (key === "classdiagram")
|
|
149
|
+
return "classDiagram";
|
|
150
|
+
if (key === "statediagram" || key === "statediagram-v2") {
|
|
151
|
+
return firstLine.trim().startsWith("stateDiagram-v2")
|
|
152
|
+
? "stateDiagram-v2"
|
|
153
|
+
: "stateDiagram";
|
|
154
|
+
}
|
|
155
|
+
if (key === "erdiagram")
|
|
156
|
+
return "erDiagram";
|
|
157
|
+
return key;
|
|
158
|
+
}
|
|
159
|
+
const ACTIVE_CONTENT_PATTERNS = [
|
|
160
|
+
{ pattern: /<\s*script[\s>]/i, kind: "script element" },
|
|
161
|
+
{ pattern: /<\s*(iframe|object|embed|foreignobject|math)[\s>]/i, kind: "embedded element" },
|
|
162
|
+
{ pattern: /\bon\w+\s*=/i, kind: "event handler" },
|
|
163
|
+
{ pattern: /javascript\s*:/i, kind: "javascript URL" },
|
|
164
|
+
{ pattern: /data\s*:\s*text\/html/i, kind: "data URL" },
|
|
165
|
+
{ pattern: /<\s*style[\s>]/i, kind: "style element" },
|
|
166
|
+
{ pattern: /url\s*\(\s*["']?(https?:|data:)/i, kind: "external style URL" },
|
|
167
|
+
];
|
|
168
|
+
/**
|
|
169
|
+
* Synchronous Source preflight. Deep syntax is owned by pinned Mermaid in
|
|
170
|
+
* the isolated browser at compile time; this gate rejects empty,
|
|
171
|
+
* over-long, active-content, external-resource, and unknown-diagram input
|
|
172
|
+
* so obvious failures surface at parse with a stable diagnostic.
|
|
173
|
+
*/
|
|
174
|
+
export function validateMermaidBody(options) {
|
|
175
|
+
const { header, body, bodyRanges, blockRange, sourceName } = options;
|
|
176
|
+
const location = (range) => sourceName === undefined ? { range } : { source: sourceName, range };
|
|
177
|
+
const trimmed = body.trim();
|
|
178
|
+
if (trimmed.length === 0) {
|
|
179
|
+
return {
|
|
180
|
+
diagnostics: [
|
|
181
|
+
createDiagnostic("azeforge.mermaid#empty", "error", "The mermaid Block must contain diagram Source.", {
|
|
182
|
+
location: location(blockRange),
|
|
183
|
+
suggestion: "Add a flowchart such as flowchart TD.",
|
|
184
|
+
}),
|
|
185
|
+
],
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (trimmed.length > MAX_MERMAID_SOURCE_LENGTH) {
|
|
189
|
+
return {
|
|
190
|
+
diagnostics: [
|
|
191
|
+
createDiagnostic("azeforge.mermaid#invalid-syntax", "error", "The mermaid Block exceeds the maximum supported length.", {
|
|
192
|
+
location: location(bodyRanges[0] ?? blockRange),
|
|
193
|
+
data: { length: trimmed.length },
|
|
194
|
+
}),
|
|
195
|
+
],
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
for (const { pattern, kind } of ACTIVE_CONTENT_PATTERNS) {
|
|
199
|
+
if (pattern.test(trimmed)) {
|
|
200
|
+
return {
|
|
201
|
+
diagnostics: [
|
|
202
|
+
createDiagnostic("azeforge.mermaid#active-content", "error", `The mermaid Block contains forbidden active content (${kind}).`, {
|
|
203
|
+
location: location(bodyRanges[0] ?? blockRange),
|
|
204
|
+
suggestion: "Remove executable markup; labels must be plain text.",
|
|
205
|
+
data: { kind },
|
|
206
|
+
}),
|
|
207
|
+
],
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (/^\s*click\b/m.test(trimmed)) {
|
|
212
|
+
return {
|
|
213
|
+
diagnostics: [
|
|
214
|
+
createDiagnostic("azeforge.mermaid#external-resource", "error", "The mermaid Block links to an external resource.", {
|
|
215
|
+
location: location(bodyRanges[0] ?? blockRange),
|
|
216
|
+
suggestion: "Remove click directives; offline diagrams cannot dereference links.",
|
|
217
|
+
data: { construct: "click" },
|
|
218
|
+
}),
|
|
219
|
+
],
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
const lines = trimmed.split("\n").filter((line) => line.trim().length > 0);
|
|
223
|
+
const firstContent = lines.find((line) => !line.trim().startsWith("%%")) ?? "";
|
|
224
|
+
const key = diagramKey(firstContent);
|
|
225
|
+
if (key === undefined) {
|
|
226
|
+
return {
|
|
227
|
+
diagnostics: [
|
|
228
|
+
createDiagnostic("azeforge.mermaid#unsupported-diagram", "error", "The mermaid Block names an unsupported diagram type.", {
|
|
229
|
+
location: location(bodyRanges[0] ?? blockRange),
|
|
230
|
+
suggestion: "Use flowchart, sequenceDiagram, classDiagram, stateDiagram, erDiagram, pie, gantt, journey, or mindmap.",
|
|
231
|
+
}),
|
|
232
|
+
],
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
const diagramType = canonicalDiagramType(key, firstContent);
|
|
236
|
+
const block = {
|
|
237
|
+
kind: "mermaid",
|
|
238
|
+
range: blockRange,
|
|
239
|
+
...(header.id === undefined ? {} : { id: header.id }),
|
|
240
|
+
pluginVersion: MERMAID_PLUGIN_VERSION,
|
|
241
|
+
diagramType,
|
|
242
|
+
source: trimmed,
|
|
243
|
+
...(header.title === undefined ? {} : { title: header.title }),
|
|
244
|
+
...(header.description === undefined
|
|
245
|
+
? {}
|
|
246
|
+
: { description: header.description }),
|
|
247
|
+
};
|
|
248
|
+
return { block: Object.freeze(block), diagnostics: [] };
|
|
249
|
+
}
|
|
250
|
+
export function deriveMermaidSeed(options) {
|
|
251
|
+
return sha256(JSON.stringify({
|
|
252
|
+
source: options.source,
|
|
253
|
+
ordinal: options.ordinal,
|
|
254
|
+
pluginVersion: options.pluginVersion,
|
|
255
|
+
theme: `${options.theme.id}@${options.theme.version}`,
|
|
256
|
+
mermaid: options.mermaidVersion ?? MERMAID_VERSION,
|
|
257
|
+
})).slice("sha256:".length, "sha256:".length + 16);
|
|
258
|
+
}
|
|
259
|
+
function escapeXml(value) {
|
|
260
|
+
return value
|
|
261
|
+
.replace(/&/g, "&")
|
|
262
|
+
.replace(/</g, "<")
|
|
263
|
+
.replace(/>/g, ">")
|
|
264
|
+
.replace(/"/g, """)
|
|
265
|
+
.replace(/'/g, "'");
|
|
266
|
+
}
|
|
267
|
+
function defaultAccessibleName(block) {
|
|
268
|
+
const firstNode = [...block.source.split("\n")]
|
|
269
|
+
.map((line) => line.trim())
|
|
270
|
+
.find((line) => line !== "" &&
|
|
271
|
+
!line.startsWith("%%") &&
|
|
272
|
+
!/^(flowchart|graph|sequencediagram|classdiagram|statediagram|erdiagram|pie|gantt|journey|mindmap)\b/i.test(line));
|
|
273
|
+
if (firstNode !== undefined)
|
|
274
|
+
return firstNode.slice(0, 120);
|
|
275
|
+
return `${block.diagramType} diagram`;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Render pinned Mermaid in the isolated browser and return sanitized SVG.
|
|
279
|
+
* Every advertised diagram family executes through real Mermaid 11.17.2;
|
|
280
|
+
* deep syntax failures surface as MermaidBrowserParseError so the Compiler
|
|
281
|
+
* can report one scoped invalid-syntax diagnostic with no Artifact.
|
|
282
|
+
*/
|
|
283
|
+
export async function renderMermaidSvg(block, options) {
|
|
284
|
+
const seed = deriveMermaidSeed({
|
|
285
|
+
source: block.source,
|
|
286
|
+
ordinal: options.ordinal,
|
|
287
|
+
pluginVersion: block.pluginVersion,
|
|
288
|
+
theme: options.theme,
|
|
289
|
+
});
|
|
290
|
+
const rendered = await renderMermaidInBrowser({
|
|
291
|
+
source: block.source,
|
|
292
|
+
elementId: `aze-m-${options.ordinal}-render`,
|
|
293
|
+
seed,
|
|
294
|
+
theme: options.theme,
|
|
295
|
+
});
|
|
296
|
+
const namespaced = namespaceMermaidIds(rendered.svg, options.ordinal);
|
|
297
|
+
const accessible = injectAccessibleName(namespaced, {
|
|
298
|
+
ordinal: options.ordinal,
|
|
299
|
+
title: block.title ?? defaultAccessibleName(block),
|
|
300
|
+
...(block.description === undefined
|
|
301
|
+
? {}
|
|
302
|
+
: { description: block.description }),
|
|
303
|
+
seed,
|
|
304
|
+
diagramType: block.diagramType,
|
|
305
|
+
});
|
|
306
|
+
return sanitizeMermaidSvg(accessible, { ordinal: options.ordinal });
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Deterministically rewrite every surviving id into the
|
|
310
|
+
* aze-m-<ordinal> namespace and update local references. Mermaid can emit
|
|
311
|
+
* the same id more than once; each occurrence receives a unique name
|
|
312
|
+
* (`-r<k>` suffix from the second occurrence on) while references resolve
|
|
313
|
+
* to the first occurrence, matching document lookup behavior. Ordering is
|
|
314
|
+
* stable because Mermaid output is deterministic under a fixed seed.
|
|
315
|
+
*/
|
|
316
|
+
function namespaceMermaidIds(svg, ordinal) {
|
|
317
|
+
const prefix = `aze-m-${ordinal}`;
|
|
318
|
+
const renderId = `${prefix}-render`;
|
|
319
|
+
let counter = 0;
|
|
320
|
+
const firstNew = new Map();
|
|
321
|
+
const occurrences = new Map();
|
|
322
|
+
// Render-root id, when referenced, resolves to the namespaced root.
|
|
323
|
+
firstNew.set(renderId, prefix);
|
|
324
|
+
const out = svg.replace(/ id="([^"]+)"/g, (whole, old) => {
|
|
325
|
+
if (old === "" || old === renderId) {
|
|
326
|
+
return old === "" ? whole : ` id="${prefix}"`;
|
|
327
|
+
}
|
|
328
|
+
const seen = (occurrences.get(old) ?? 0) + 1;
|
|
329
|
+
occurrences.set(old, seen);
|
|
330
|
+
if (seen === 1) {
|
|
331
|
+
counter += 1;
|
|
332
|
+
const next = `${prefix}-n-${counter}`;
|
|
333
|
+
firstNew.set(old, next);
|
|
334
|
+
return ` id="${next}"`;
|
|
335
|
+
}
|
|
336
|
+
return ` id="${firstNew.get(old) ?? `${prefix}-n-0`}-r${seen}"`;
|
|
337
|
+
});
|
|
338
|
+
const resolveReference = (old) => firstNew.get(old) ?? old;
|
|
339
|
+
// url() and aria references are live (marker and accessibility hooks);
|
|
340
|
+
// href/xlink:href are rejected unconditionally by the sanitizer, so no
|
|
341
|
+
// rewriting for them exists here by design.
|
|
342
|
+
const withReferences = out
|
|
343
|
+
.replaceAll(/url\(#([^)]+)\)/g, (_whole, old) => `url(#${resolveReference(old)})`)
|
|
344
|
+
.replaceAll(/aria-(labelledby|describedby)="([^"]*)"/g, (_whole, kind, refs) => {
|
|
345
|
+
const rewritten = refs
|
|
346
|
+
.split(/[\s,]+/)
|
|
347
|
+
.filter((ref) => ref !== "")
|
|
348
|
+
.map((ref) => resolveReference(ref))
|
|
349
|
+
.join(" ");
|
|
350
|
+
return `aria-${kind}="${rewritten}"`;
|
|
351
|
+
});
|
|
352
|
+
// Defense in depth: the browser normalizer already strips these, but
|
|
353
|
+
// raw Mermaid output must never contribute executable styling or
|
|
354
|
+
// unnamespaced hooks to the Artifact.
|
|
355
|
+
return withReferences
|
|
356
|
+
.replaceAll(/ class="[^"]*"/g, "")
|
|
357
|
+
.replaceAll(/ data-[a-zA-Z0-9_-]+="[^"]*"/g, "")
|
|
358
|
+
.replaceAll(/ name="[^"]*"/g, "");
|
|
359
|
+
}
|
|
360
|
+
function injectAccessibleName(svg, options) {
|
|
361
|
+
const prefix = `aze-m-${options.ordinal}`;
|
|
362
|
+
const openMatch = /^<svg([^>]*)>/.exec(svg);
|
|
363
|
+
if (openMatch === null)
|
|
364
|
+
throw new MermaidSanitizerError("SVG misses its root element.");
|
|
365
|
+
const rawAttrs = openMatch[1] ?? "";
|
|
366
|
+
const kept = rawAttrs
|
|
367
|
+
.replace(/ id="[^"]*"/g, "")
|
|
368
|
+
.replace(/ class="[^"]*"/g, "")
|
|
369
|
+
.replace(/ style="[^"]*"/g, "")
|
|
370
|
+
.replace(/ width="[^"]*"/g, "")
|
|
371
|
+
.replace(/ height="[^"]*"/g, "")
|
|
372
|
+
.replace(/ role="[^"]*"/g, "")
|
|
373
|
+
.replace(/ aria-[a-z-]*="[^"]*"/g, "");
|
|
374
|
+
const describedby = options.description === undefined
|
|
375
|
+
? ""
|
|
376
|
+
: ` aria-describedby="${prefix}-desc"`;
|
|
377
|
+
const opening = `<svg${kept} id="${prefix}" role="img" aria-labelledby="${prefix}-title"${describedby}` +
|
|
378
|
+
` data-seed="${options.seed}" data-diagram="${escapeXml(options.diagramType)}">`;
|
|
379
|
+
const title = `<title id="${prefix}-title">${escapeXml(options.title)}</title>`;
|
|
380
|
+
const desc = options.description === undefined
|
|
381
|
+
? ""
|
|
382
|
+
: `<desc id="${prefix}-desc">${escapeXml(options.description)}</desc>`;
|
|
383
|
+
const rest = svg.slice(openMatch[0].length);
|
|
384
|
+
return `${opening}${title}${desc}${rest}`;
|
|
385
|
+
}
|
|
386
|
+
const ALLOWED_ELEMENTS = {
|
|
387
|
+
svg: true,
|
|
388
|
+
g: true,
|
|
389
|
+
defs: true,
|
|
390
|
+
marker: true,
|
|
391
|
+
filter: true,
|
|
392
|
+
fedropshadow: true,
|
|
393
|
+
fegaussianblur: true,
|
|
394
|
+
lineargradient: true,
|
|
395
|
+
radialgradient: true,
|
|
396
|
+
stop: true,
|
|
397
|
+
clippath: true,
|
|
398
|
+
mask: true,
|
|
399
|
+
symbol: true,
|
|
400
|
+
switch: true,
|
|
401
|
+
rect: true,
|
|
402
|
+
circle: true,
|
|
403
|
+
ellipse: true,
|
|
404
|
+
polygon: true,
|
|
405
|
+
polyline: true,
|
|
406
|
+
line: true,
|
|
407
|
+
path: true,
|
|
408
|
+
text: true,
|
|
409
|
+
tspan: true,
|
|
410
|
+
textpath: true,
|
|
411
|
+
title: true,
|
|
412
|
+
desc: true,
|
|
413
|
+
};
|
|
414
|
+
const ROOT_ONLY_ATTRIBUTES = {
|
|
415
|
+
xmlns: true,
|
|
416
|
+
role: true,
|
|
417
|
+
"aria-labelledby": true,
|
|
418
|
+
"aria-describedby": true,
|
|
419
|
+
"data-seed": true,
|
|
420
|
+
"data-diagram": true,
|
|
421
|
+
};
|
|
422
|
+
const GLOBAL_ATTRIBUTES = {
|
|
423
|
+
id: true,
|
|
424
|
+
x: true,
|
|
425
|
+
y: true,
|
|
426
|
+
x1: true,
|
|
427
|
+
y1: true,
|
|
428
|
+
x2: true,
|
|
429
|
+
y2: true,
|
|
430
|
+
cx: true,
|
|
431
|
+
cy: true,
|
|
432
|
+
r: true,
|
|
433
|
+
rx: true,
|
|
434
|
+
ry: true,
|
|
435
|
+
width: true,
|
|
436
|
+
height: true,
|
|
437
|
+
d: true,
|
|
438
|
+
points: true,
|
|
439
|
+
transform: true,
|
|
440
|
+
"transform-origin": true,
|
|
441
|
+
fill: true,
|
|
442
|
+
"fill-opacity": true,
|
|
443
|
+
viewbox: true,
|
|
444
|
+
preserveaspectratio: true,
|
|
445
|
+
"fill-rule": true,
|
|
446
|
+
stroke: true,
|
|
447
|
+
"stroke-width": true,
|
|
448
|
+
"stroke-opacity": true,
|
|
449
|
+
"stroke-dasharray": true,
|
|
450
|
+
"stroke-dashoffset": true,
|
|
451
|
+
"stroke-linecap": true,
|
|
452
|
+
"stroke-linejoin": true,
|
|
453
|
+
"stroke-miterlimit": true,
|
|
454
|
+
opacity: true,
|
|
455
|
+
color: true,
|
|
456
|
+
"flood-color": true,
|
|
457
|
+
"flood-opacity": true,
|
|
458
|
+
"font-family": true,
|
|
459
|
+
"font-size": true,
|
|
460
|
+
"font-style": true,
|
|
461
|
+
"font-weight": true,
|
|
462
|
+
"text-anchor": true,
|
|
463
|
+
"dominant-baseline": true,
|
|
464
|
+
"alignment-baseline": true,
|
|
465
|
+
"paint-order": true,
|
|
466
|
+
"shape-rendering": true,
|
|
467
|
+
"pointer-events": true,
|
|
468
|
+
visibility: true,
|
|
469
|
+
overflow: true,
|
|
470
|
+
"clip-path": true,
|
|
471
|
+
"clip-rule": true,
|
|
472
|
+
"marker-end": true,
|
|
473
|
+
"marker-start": true,
|
|
474
|
+
orient: true,
|
|
475
|
+
refx: true,
|
|
476
|
+
refy: true,
|
|
477
|
+
markerwidth: true,
|
|
478
|
+
markerheight: true,
|
|
479
|
+
markerunits: true,
|
|
480
|
+
dx: true,
|
|
481
|
+
dy: true,
|
|
482
|
+
stddeviation: true,
|
|
483
|
+
offset: true,
|
|
484
|
+
"stop-color": true,
|
|
485
|
+
"stop-opacity": true,
|
|
486
|
+
gradientunits: true,
|
|
487
|
+
gradienttransform: true,
|
|
488
|
+
"mask-type": true,
|
|
489
|
+
};
|
|
490
|
+
const ID_PATTERN = /^aze-m-\d+(?:-title|-desc|-n-\d+(?:-r\d+)?)?$/;
|
|
491
|
+
const ROOT_ID_PATTERN = /^aze-m-\d+$/;
|
|
492
|
+
/**
|
|
493
|
+
* Structural SVG sanitizer backed by a real XML parser. Strips only XML
|
|
494
|
+
* comments (inert); any other visible or security-relevant finding throws
|
|
495
|
+
* instead of silently rewriting meaning.
|
|
496
|
+
*/
|
|
497
|
+
export function sanitizeMermaidSvg(svg, options) {
|
|
498
|
+
if (/<!DOCTYPE/i.test(svg))
|
|
499
|
+
throw new MermaidSanitizerError("SVG must not declare a document type.");
|
|
500
|
+
if (/<\?/.test(svg))
|
|
501
|
+
throw new MermaidSanitizerError("SVG must not contain processing instructions.");
|
|
502
|
+
if (/<!\[CDATA\[/.test(svg))
|
|
503
|
+
throw new MermaidSanitizerError("SVG must not contain CDATA sections.");
|
|
504
|
+
const withoutComments = svg.replace(/<!--[\s\S]*?-->/g, "");
|
|
505
|
+
const text = withoutComments.trim();
|
|
506
|
+
if (!text.startsWith("<svg") || !text.endsWith("</svg>")) {
|
|
507
|
+
throw new MermaidSanitizerError("SVG must be one root element.");
|
|
508
|
+
}
|
|
509
|
+
const parser = new SaxesParser({ xmlns: false, position: false });
|
|
510
|
+
const ids = new Set();
|
|
511
|
+
const references = [];
|
|
512
|
+
const stack = [];
|
|
513
|
+
let rootSeen = false;
|
|
514
|
+
let rootViewBox;
|
|
515
|
+
let rootRole;
|
|
516
|
+
let rootLabelledBy;
|
|
517
|
+
let titleText = "";
|
|
518
|
+
let titleId;
|
|
519
|
+
let inTitle = false;
|
|
520
|
+
let failures = [];
|
|
521
|
+
let topLevelElements = 0;
|
|
522
|
+
let topLevelText = "";
|
|
523
|
+
parser.on("error", (error) => {
|
|
524
|
+
failures.push(error.message);
|
|
525
|
+
});
|
|
526
|
+
parser.on("opentag", (tag) => {
|
|
527
|
+
const name = tag.name.toLowerCase();
|
|
528
|
+
if (ALLOWED_ELEMENTS[name] !== true) {
|
|
529
|
+
failures.push(`Forbidden SVG element: ${name}.`);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
const depth = stack.length;
|
|
533
|
+
if (depth === 0) {
|
|
534
|
+
topLevelElements += 1;
|
|
535
|
+
if (name !== "svg")
|
|
536
|
+
failures.push("SVG must be one root element.");
|
|
537
|
+
if (rootSeen)
|
|
538
|
+
failures.push("SVG must contain exactly one root element.");
|
|
539
|
+
rootSeen = true;
|
|
540
|
+
}
|
|
541
|
+
stack.push(name);
|
|
542
|
+
if (name === "title") {
|
|
543
|
+
inTitle = titleId === undefined;
|
|
544
|
+
}
|
|
545
|
+
for (const [rawName, rawValue] of Object.entries(tag.attributes)) {
|
|
546
|
+
const attrName = rawName.toLowerCase();
|
|
547
|
+
const value = String(rawValue);
|
|
548
|
+
if (attrName === "xmlns:xlink") {
|
|
549
|
+
failures.push("Unused XML namespace declaration.");
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
const rootOnly = ROOT_ONLY_ATTRIBUTES[attrName] === true;
|
|
553
|
+
if (rootOnly && depth !== 0) {
|
|
554
|
+
failures.push(`Root-only attribute ${attrName} on ${name}.`);
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
if (GLOBAL_ATTRIBUTES[attrName] !== true && !rootOnly) {
|
|
558
|
+
failures.push(`Forbidden attribute ${attrName} on ${name}.`);
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
if (/^on/i.test(attrName) || attrName === "href" || attrName === "xlink:href") {
|
|
562
|
+
failures.push(`Executable attribute: ${attrName}.`);
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
if (attrName === "id") {
|
|
566
|
+
const pattern = depth === 0 ? ROOT_ID_PATTERN : ID_PATTERN;
|
|
567
|
+
if (!pattern.test(value) || !value.startsWith(`aze-m-${options.ordinal}`)) {
|
|
568
|
+
failures.push(`Non-deterministic id: ${value}.`);
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
if (ids.has(value)) {
|
|
572
|
+
failures.push(`Duplicate id: ${value}.`);
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
ids.add(value);
|
|
576
|
+
if (depth === 0 && name === "svg" && value !== `aze-m-${options.ordinal}`) {
|
|
577
|
+
failures.push(`Unexpected SVG root id: ${value}.`);
|
|
578
|
+
}
|
|
579
|
+
if (name === "title" && titleId === undefined)
|
|
580
|
+
titleId = value;
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
if (attrName === "xmlns") {
|
|
584
|
+
if (value !== "http://www.w3.org/2000/svg") {
|
|
585
|
+
failures.push(`Unexpected XML namespace: ${value}.`);
|
|
586
|
+
}
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
for (const ref of value.matchAll(/url\(#([^)]+)\)/g)) {
|
|
590
|
+
references.push(ref[1] ?? "");
|
|
591
|
+
}
|
|
592
|
+
if (/https?:|data:|javascript:|file:|ftp:|vbscript:/i.test(value)) {
|
|
593
|
+
failures.push(`External resource URL: ${value}.`);
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
if (depth === 0 && name === "svg") {
|
|
597
|
+
if (attrName === "viewbox")
|
|
598
|
+
rootViewBox = value;
|
|
599
|
+
if (attrName === "role")
|
|
600
|
+
rootRole = value;
|
|
601
|
+
if (attrName === "aria-labelledby")
|
|
602
|
+
rootLabelledBy = value;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
parser.on("closetag", () => {
|
|
607
|
+
const closed = stack.pop();
|
|
608
|
+
if (closed === "title")
|
|
609
|
+
inTitle = false;
|
|
610
|
+
});
|
|
611
|
+
parser.on("text", (value) => {
|
|
612
|
+
if (stack.length === 0) {
|
|
613
|
+
topLevelText += value;
|
|
614
|
+
}
|
|
615
|
+
else if (inTitle) {
|
|
616
|
+
titleText += value;
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
parser.on("cdata", () => {
|
|
620
|
+
failures.push("SVG must not contain CDATA sections.");
|
|
621
|
+
});
|
|
622
|
+
parser.on("comment", () => { });
|
|
623
|
+
parser.write(withoutComments).close();
|
|
624
|
+
if (failures.length > 0) {
|
|
625
|
+
throw new MermaidSanitizerError(failures[0] ?? "Invalid SVG.");
|
|
626
|
+
}
|
|
627
|
+
if (topLevelElements !== 1) {
|
|
628
|
+
throw new MermaidSanitizerError("SVG must contain exactly one root element.");
|
|
629
|
+
}
|
|
630
|
+
if (topLevelText.trim() !== "") {
|
|
631
|
+
throw new MermaidSanitizerError("SVG must not contain top-level text.");
|
|
632
|
+
}
|
|
633
|
+
const viewBox = /^([+-]?(?:\d+(?:\.\d*)?|\.\d+)) ([+-]?(?:\d+(?:\.\d*)?|\.\d+)) ([+-]?(?:\d+(?:\.\d*)?|\.\d+)) ([+-]?(?:\d+(?:\.\d*)?|\.\d+))$/.exec(rootViewBox ?? "");
|
|
634
|
+
const width = Number(viewBox?.[3]);
|
|
635
|
+
const height = Number(viewBox?.[4]);
|
|
636
|
+
if (viewBox === null ||
|
|
637
|
+
!Number.isFinite(width) ||
|
|
638
|
+
!Number.isFinite(height) ||
|
|
639
|
+
width <= 0 ||
|
|
640
|
+
height <= 0 ||
|
|
641
|
+
width > 10000 ||
|
|
642
|
+
height > 10000) {
|
|
643
|
+
throw new MermaidSanitizerError("SVG viewBox must be finite and nonzero.");
|
|
644
|
+
}
|
|
645
|
+
if (rootRole !== "img") {
|
|
646
|
+
throw new MermaidSanitizerError("SVG root must carry role img.");
|
|
647
|
+
}
|
|
648
|
+
const expectedTitle = `aze-m-${options.ordinal}-title`;
|
|
649
|
+
if (rootLabelledBy !== expectedTitle || titleId !== expectedTitle) {
|
|
650
|
+
throw new MermaidSanitizerError("SVG misses an accessible name.");
|
|
651
|
+
}
|
|
652
|
+
if (titleText.trim() === "") {
|
|
653
|
+
throw new MermaidSanitizerError("SVG misses an accessible name.");
|
|
654
|
+
}
|
|
655
|
+
for (const target of references) {
|
|
656
|
+
if (!ids.has(target)) {
|
|
657
|
+
throw new MermaidSanitizerError(`Dangling reference: ${target}.`);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return withoutComments;
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Fragment post-pass for the Compiler: the Block renderer returns a
|
|
664
|
+
* figure wrapping one sanitized SVG. The entire Fragment is validated —
|
|
665
|
+
* wrapper attributes and embedded SVG alike — and any failure throws
|
|
666
|
+
* instead of silently rewriting.
|
|
667
|
+
*/
|
|
668
|
+
export function sanitizeMermaidFragment(fragment, options) {
|
|
669
|
+
const text = fragment.trim();
|
|
670
|
+
const openEnd = text.indexOf(">");
|
|
671
|
+
if (!text.startsWith("<figure") || openEnd < 0) {
|
|
672
|
+
throw new MermaidSanitizerError("Fragment must be a figure wrapping one SVG.");
|
|
673
|
+
}
|
|
674
|
+
const opening = text.slice(0, openEnd + 1);
|
|
675
|
+
const allowedFigure = new RegExp(`^<figure class="aze-mermaid" data-ordinal="${options.ordinal}" data-diagram="[^"]*"( data-mermaid-id="[a-z][a-z0-9]*(?:-[a-z0-9]+)*")?>$`);
|
|
676
|
+
if (!allowedFigure.test(opening)) {
|
|
677
|
+
throw new MermaidSanitizerError("Fragment figure carries unexpected attributes.");
|
|
678
|
+
}
|
|
679
|
+
if (!text.endsWith("</figure>")) {
|
|
680
|
+
throw new MermaidSanitizerError("Fragment must close its figure element.");
|
|
681
|
+
}
|
|
682
|
+
const inner = text.slice(openEnd + 1, -"</figure>".length);
|
|
683
|
+
const innerTrimmed = inner.trim();
|
|
684
|
+
if (!innerTrimmed.startsWith("<svg") || !innerTrimmed.endsWith("</svg>")) {
|
|
685
|
+
throw new MermaidSanitizerError("Fragment misses embedded SVG.");
|
|
686
|
+
}
|
|
687
|
+
sanitizeMermaidSvg(innerTrimmed, options);
|
|
688
|
+
if (/<figure[\s>]/i.test(innerTrimmed) || /<\/figure>/i.test(innerTrimmed)) {
|
|
689
|
+
throw new MermaidSanitizerError("Fragment must wrap exactly one SVG.");
|
|
690
|
+
}
|
|
691
|
+
return fragment;
|
|
692
|
+
}
|
|
693
|
+
const pluginDescriptor = Object.freeze({
|
|
694
|
+
type: MERMAID_PLUGIN_TYPE,
|
|
695
|
+
version: MERMAID_PLUGIN_VERSION,
|
|
696
|
+
title: "Mermaid",
|
|
697
|
+
summary: "Offline deterministic diagrams rendered as sanitized SVG.",
|
|
698
|
+
diagnosticNamespace: "azeforge.mermaid",
|
|
699
|
+
sourceSchema: mermaidSourceSchema,
|
|
700
|
+
bodySyntax: Object.freeze({
|
|
701
|
+
id: MERMAID_BODY_SYNTAX_ID,
|
|
702
|
+
version: MERMAID_BODY_SYNTAX_VERSION,
|
|
703
|
+
}),
|
|
704
|
+
dataSchema: mermaidDataSchema,
|
|
705
|
+
});
|
|
706
|
+
export const mermaidPlugin = Object.freeze({
|
|
707
|
+
descriptor: pluginDescriptor,
|
|
708
|
+
});
|
|
709
|
+
const blockRendererDescriptor = Object.freeze({
|
|
710
|
+
id: MERMAID_HTML_BLOCK_RENDERER_ID,
|
|
711
|
+
version: MERMAID_HTML_BLOCK_RENDERER_VERSION,
|
|
712
|
+
blockType: MERMAID_PLUGIN_TYPE,
|
|
713
|
+
pluginVersionRange: "1.0.0",
|
|
714
|
+
rendererId: "html",
|
|
715
|
+
rendererVersionRange: "1.0.0",
|
|
716
|
+
});
|
|
717
|
+
async function renderMermaidFragment(block, context) {
|
|
718
|
+
const ordinal = context.ordinal ?? 0;
|
|
719
|
+
const theme = context.theme ?? defaultTheme;
|
|
720
|
+
const svg = await renderMermaidSvg(block, { ordinal, theme });
|
|
721
|
+
const label = block.id === undefined ? "" : ` data-mermaid-id="${escapeXml(block.id)}"`;
|
|
722
|
+
return `<figure class="aze-mermaid" data-ordinal="${ordinal}" data-diagram="${escapeXml(block.diagramType)}"${label}>${svg}</figure>`;
|
|
723
|
+
}
|
|
724
|
+
export const mermaidHtmlBlockRenderer = Object.freeze({
|
|
725
|
+
descriptor: blockRendererDescriptor,
|
|
726
|
+
render: renderMermaidFragment,
|
|
727
|
+
});
|
|
728
|
+
export function mermaidDependencyClosure() {
|
|
729
|
+
return {
|
|
730
|
+
mermaid: MERMAID_VERSION,
|
|
731
|
+
browser: `HeadlessChrome/${CHROME_HEADLESS_SHELL_VERSION}`,
|
|
732
|
+
platform: { os: platform(), architecture: arch() },
|
|
733
|
+
securityLevel: "strict",
|
|
734
|
+
deterministicIds: true,
|
|
735
|
+
suppressErrorRendering: true,
|
|
736
|
+
fonts: ["Inter"],
|
|
737
|
+
sanitizer: "azeforge-mermaid-svg/v2",
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
//# sourceMappingURL=mermaid.js.map
|