hdoc-tools 0.61.0 → 0.62.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 -21
- package/README.md +89 -75
- package/editor/dist/assets/index-Blewr90z.css +1 -1
- package/editor/dist/assets/index-BtxvGZHW.js +111 -111
- package/editor/dist/assets/spell.worker-sryEKmOj.js +13 -13
- package/editor/dist/index.html +14 -14
- package/hdoc-build-db.js +275 -275
- package/hdoc-build-embeddings.js +202 -202
- package/hdoc-build-pdf.js +232 -232
- package/hdoc-bump.js +125 -125
- package/hdoc-create.js +110 -110
- package/hdoc-db.js +114 -114
- package/hdoc-help.js +60 -60
- package/hdoc-install-browser.js +145 -145
- package/hdoc-mermaid.js +204 -204
- package/hdoc-module.js +1102 -1102
- package/hdoc-validate-config.js +355 -355
- package/hdoc-validate-interbook.js +321 -321
- package/hdoc-validate.js +1231 -1231
- package/hdoc-ver.js +45 -45
- package/package.json +12 -1
- package/templates/doc-header-non-git.html +19 -19
- package/templates/doc-header.html +26 -26
- package/templates/init/.github/workflows/hdocbuild_onpull.yml +16 -16
- package/templates/init/.github/workflows/hdocbuild_onpush.yml +15 -15
- package/templates/init/LICENSE +21 -21
- package/templates/init/README.md +9 -9
- package/templates/init/_hdocbook/index.md +4 -4
- package/templates/init/gitignore +8 -8
- package/templates/init/resources/README.md +2 -2
- package/templates/pdf/css/custom-block.css +90 -90
- package/templates/pdf/css/fonts.css +221 -221
- package/templates/pdf/css/hdocs-pdf.css +495 -495
- package/templates/pdf/css/vars.css +404 -404
- package/templates/pdf/template-footer.html +19 -19
- package/templates/pdf/template-header.html +37 -37
- package/templates/pdf/template.html +20 -20
- package/templates/pdf-header-non-git.html +12 -12
- package/templates/pdf-header.html +16 -16
- package/ui/content/invalid-hdocbook-json.html +6 -6
- package/ui/content/invalid-hdocbook-json.md +7 -7
- package/ui/css/theme-default/styles/components/content.css +124 -124
- package/ui/css/theme-default/styles/components/sidebar.css +182 -182
- package/ui/css/theme-default/styles/htldoc.layouts.css +310 -310
- package/ui/index.html +419 -419
package/hdoc-mermaid.js
CHANGED
|
@@ -1,204 +1,204 @@
|
|
|
1
|
-
// In-process Mermaid -> SVG renderer.
|
|
2
|
-
//
|
|
3
|
-
// Replaces the previous `@mermaid-js/mermaid-cli` (mmdc) integration. Instead of
|
|
4
|
-
// shelling out to the CLI (which spawned a fresh Chromium per diagram and pinned
|
|
5
|
-
// puppeteer to <=24 via its peerDependency), this renders Mermaid using the
|
|
6
|
-
// `mermaid` library directly inside a puppeteer browser supplied by the caller —
|
|
7
|
-
// the very same browser hdoc-build already launches for PDF/link work.
|
|
8
|
-
//
|
|
9
|
-
// The render path (Interceptor + page evaluation) is ported from mermaid-cli
|
|
10
|
-
// (MIT licensed): https://github.com/mermaid-js/mermaid-cli
|
|
11
|
-
// Trimmed to the SVG-only case we need, plus ELK layout support (required by
|
|
12
|
-
// books that use `layout: elk`). zenuml / tidy-tree / icon-packs / png / pdf are
|
|
13
|
-
// intentionally dropped — no hdocbook content uses them.
|
|
14
|
-
|
|
15
|
-
const { readFile, realpath } = require("node:fs/promises");
|
|
16
|
-
const path = require("node:path");
|
|
17
|
-
const url = require("node:url");
|
|
18
|
-
|
|
19
|
-
// ESM bundle locations inside the installed packages. Both packages expose
|
|
20
|
-
// "./*": "./*" in their exports map, so the dist files resolve directly.
|
|
21
|
-
const mermaidESMPath = require.resolve("mermaid/dist/mermaid.esm.mjs");
|
|
22
|
-
const elkESMPath = require.resolve(
|
|
23
|
-
"@mermaid-js/layout-elk/dist/mermaid-layout-elk.esm.mjs",
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Guesses the MIME-type of a file based on its extension. Only the handful of
|
|
28
|
-
* types the Mermaid ESM bundles actually request are supported.
|
|
29
|
-
*/
|
|
30
|
-
function getContentTypeFromFileExtension(filePath) {
|
|
31
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
32
|
-
switch (ext) {
|
|
33
|
-
case ".css":
|
|
34
|
-
// Force UTF-8 so puppeteer doesn't mis-parse as Latin-1.
|
|
35
|
-
return "text/css;charset=UTF-8";
|
|
36
|
-
case ".js":
|
|
37
|
-
case ".mjs":
|
|
38
|
-
return "application/javascript";
|
|
39
|
-
case ".woff2":
|
|
40
|
-
return "font/woff2";
|
|
41
|
-
default:
|
|
42
|
-
throw new Error(`Unsupported file extension for intercept: ${ext}`);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Puppeteer can't `import` ESM modules from `file://` URLs. Rather than stand up
|
|
48
|
-
* a real HTTP server (ports/firewalls), we intercept requests to a dummy
|
|
49
|
-
* `https://hdoc-mermaid-intercept.invalid` origin and serve the corresponding
|
|
50
|
-
* local file. Ported verbatim from mermaid-cli's puppeteerIntercept.js.
|
|
51
|
-
*/
|
|
52
|
-
class Interceptor {
|
|
53
|
-
#INTERCEPT_ORIGIN = "https://hdoc-mermaid-intercept.invalid";
|
|
54
|
-
|
|
55
|
-
/** @type {Set<string>} resolved (realpath) dirs allowed to be served. */
|
|
56
|
-
#allowedDirs = new Set();
|
|
57
|
-
|
|
58
|
-
async fileUrlToInterceptUrl(fileUrl, { allowParentDirectoryLevel = 1 } = {}) {
|
|
59
|
-
fileUrl = new URL(fileUrl);
|
|
60
|
-
if (fileUrl.protocol !== "file:") {
|
|
61
|
-
throw new Error(`Invalid file URL: ${fileUrl}`);
|
|
62
|
-
}
|
|
63
|
-
let parentDirectory = await realpath(url.fileURLToPath(fileUrl));
|
|
64
|
-
while (allowParentDirectoryLevel-- >= 0) {
|
|
65
|
-
parentDirectory = path.dirname(parentDirectory);
|
|
66
|
-
}
|
|
67
|
-
this.#allowedDirs.add(parentDirectory);
|
|
68
|
-
return `${this.#INTERCEPT_ORIGIN}${fileUrl.pathname}`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async interceptUrlToFileUrl(interceptUrl) {
|
|
72
|
-
interceptUrl = new URL(interceptUrl);
|
|
73
|
-
if (interceptUrl.origin !== this.#INTERCEPT_ORIGIN) {
|
|
74
|
-
throw new Error(`Invalid intercept URL: ${interceptUrl}`);
|
|
75
|
-
}
|
|
76
|
-
const fileUrl = new URL(
|
|
77
|
-
interceptUrl.href.slice(this.#INTERCEPT_ORIGIN.length),
|
|
78
|
-
"file://",
|
|
79
|
-
);
|
|
80
|
-
const filePath = await realpath(url.fileURLToPath(fileUrl));
|
|
81
|
-
if (
|
|
82
|
-
![...this.#allowedDirs].some((dir) =>
|
|
83
|
-
path.relative(filePath, dir).startsWith(".."),
|
|
84
|
-
)
|
|
85
|
-
) {
|
|
86
|
-
throw new Error(
|
|
87
|
-
`Intercept URL is not in an allowed directory: ${interceptUrl}`,
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
return fileUrl;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async #interceptRequestHandler(request) {
|
|
94
|
-
try {
|
|
95
|
-
if (request.url().startsWith(this.#INTERCEPT_ORIGIN)) {
|
|
96
|
-
const fileUrl = await this.interceptUrlToFileUrl(request.url());
|
|
97
|
-
return request.respond({
|
|
98
|
-
status: 200,
|
|
99
|
-
headers: { "Access-Control-Allow-Origin": "*" },
|
|
100
|
-
contentType: getContentTypeFromFileExtension(
|
|
101
|
-
url.fileURLToPath(fileUrl),
|
|
102
|
-
),
|
|
103
|
-
body: await readFile(fileUrl),
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
} catch (error) {
|
|
107
|
-
console.error(
|
|
108
|
-
`Error handling intercept request for ${request.url()}:`,
|
|
109
|
-
error,
|
|
110
|
-
);
|
|
111
|
-
request.abort();
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
request.continue();
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
get interceptRequestHandler() {
|
|
118
|
-
return this.#interceptRequestHandler.bind(this);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Render a single Mermaid definition to an SVG string.
|
|
124
|
-
*
|
|
125
|
-
* @param {import('puppeteer').Browser} browser - An open puppeteer browser.
|
|
126
|
-
* @param {string} definition - Mermaid source (may include `--- config: ---` frontmatter).
|
|
127
|
-
* @param {{ backgroundColor?: string }} [opts]
|
|
128
|
-
* @returns {Promise<string>} the serialized `<svg>`.
|
|
129
|
-
*/
|
|
130
|
-
async function render_svg(browser, definition, { backgroundColor = "transparent" } = {}) {
|
|
131
|
-
const page = await browser.newPage();
|
|
132
|
-
// Surface in-page warnings/errors (e.g. mermaid parse errors) to the build log.
|
|
133
|
-
page.on("console", (msg) => {
|
|
134
|
-
if (msg.type() === "error" || msg.type() === "warning") {
|
|
135
|
-
console.warn(msg.text());
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
try {
|
|
139
|
-
await page.setContent(
|
|
140
|
-
"<!doctype html><html><body><div id=\"container\"></div></body></html>",
|
|
141
|
-
);
|
|
142
|
-
await page.$eval(
|
|
143
|
-
"body",
|
|
144
|
-
(body, bg) => {
|
|
145
|
-
body.style.background = bg;
|
|
146
|
-
},
|
|
147
|
-
backgroundColor,
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
const interceptor = new Interceptor();
|
|
151
|
-
const mermaidUrl = await interceptor.fileUrlToInterceptUrl(
|
|
152
|
-
url.pathToFileURL(mermaidESMPath),
|
|
153
|
-
);
|
|
154
|
-
const elkUrl = await interceptor.fileUrlToInterceptUrl(
|
|
155
|
-
url.pathToFileURL(elkESMPath),
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
page.on("request", interceptor.interceptRequestHandler);
|
|
159
|
-
await page.setRequestInterception(true);
|
|
160
|
-
|
|
161
|
-
await page.$eval(
|
|
162
|
-
"#container",
|
|
163
|
-
async (container, { definition, mermaidUrl, elkUrl, backgroundColor }) => {
|
|
164
|
-
const { default: mermaid } = await import(mermaidUrl);
|
|
165
|
-
const { default: elkLayouts } = await import(elkUrl);
|
|
166
|
-
|
|
167
|
-
await Promise.all(
|
|
168
|
-
Array.from(document.fonts, (font) => font.load()),
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
mermaid.registerLayoutLoaders(elkLayouts);
|
|
172
|
-
mermaid.initialize({ startOnLoad: false });
|
|
173
|
-
|
|
174
|
-
// Throws on invalid diagrams — propagates out of $eval as a rejection.
|
|
175
|
-
const { svg: svgText } = await mermaid.render(
|
|
176
|
-
"hdoc-svg",
|
|
177
|
-
definition,
|
|
178
|
-
container,
|
|
179
|
-
);
|
|
180
|
-
container.innerHTML = svgText;
|
|
181
|
-
|
|
182
|
-
const svg = container.getElementsByTagName?.("svg")?.[0];
|
|
183
|
-
if (svg?.style) {
|
|
184
|
-
svg.style.backgroundColor = backgroundColor;
|
|
185
|
-
}
|
|
186
|
-
},
|
|
187
|
-
{ definition, mermaidUrl, elkUrl, backgroundColor },
|
|
188
|
-
);
|
|
189
|
-
|
|
190
|
-
// Serialize via XMLSerializer so HTML <foreignObject> content (e.g. <br>)
|
|
191
|
-
// becomes valid XML in the saved .svg file.
|
|
192
|
-
const svgXML = await page.$eval("svg", (svg) => {
|
|
193
|
-
// eslint-disable-next-line no-undef
|
|
194
|
-
const xmlSerializer = new XMLSerializer();
|
|
195
|
-
return xmlSerializer.serializeToString(svg);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
return svgXML;
|
|
199
|
-
} finally {
|
|
200
|
-
await page.close();
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
module.exports = { render_svg, Interceptor };
|
|
1
|
+
// In-process Mermaid -> SVG renderer.
|
|
2
|
+
//
|
|
3
|
+
// Replaces the previous `@mermaid-js/mermaid-cli` (mmdc) integration. Instead of
|
|
4
|
+
// shelling out to the CLI (which spawned a fresh Chromium per diagram and pinned
|
|
5
|
+
// puppeteer to <=24 via its peerDependency), this renders Mermaid using the
|
|
6
|
+
// `mermaid` library directly inside a puppeteer browser supplied by the caller —
|
|
7
|
+
// the very same browser hdoc-build already launches for PDF/link work.
|
|
8
|
+
//
|
|
9
|
+
// The render path (Interceptor + page evaluation) is ported from mermaid-cli
|
|
10
|
+
// (MIT licensed): https://github.com/mermaid-js/mermaid-cli
|
|
11
|
+
// Trimmed to the SVG-only case we need, plus ELK layout support (required by
|
|
12
|
+
// books that use `layout: elk`). zenuml / tidy-tree / icon-packs / png / pdf are
|
|
13
|
+
// intentionally dropped — no hdocbook content uses them.
|
|
14
|
+
|
|
15
|
+
const { readFile, realpath } = require("node:fs/promises");
|
|
16
|
+
const path = require("node:path");
|
|
17
|
+
const url = require("node:url");
|
|
18
|
+
|
|
19
|
+
// ESM bundle locations inside the installed packages. Both packages expose
|
|
20
|
+
// "./*": "./*" in their exports map, so the dist files resolve directly.
|
|
21
|
+
const mermaidESMPath = require.resolve("mermaid/dist/mermaid.esm.mjs");
|
|
22
|
+
const elkESMPath = require.resolve(
|
|
23
|
+
"@mermaid-js/layout-elk/dist/mermaid-layout-elk.esm.mjs",
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Guesses the MIME-type of a file based on its extension. Only the handful of
|
|
28
|
+
* types the Mermaid ESM bundles actually request are supported.
|
|
29
|
+
*/
|
|
30
|
+
function getContentTypeFromFileExtension(filePath) {
|
|
31
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
32
|
+
switch (ext) {
|
|
33
|
+
case ".css":
|
|
34
|
+
// Force UTF-8 so puppeteer doesn't mis-parse as Latin-1.
|
|
35
|
+
return "text/css;charset=UTF-8";
|
|
36
|
+
case ".js":
|
|
37
|
+
case ".mjs":
|
|
38
|
+
return "application/javascript";
|
|
39
|
+
case ".woff2":
|
|
40
|
+
return "font/woff2";
|
|
41
|
+
default:
|
|
42
|
+
throw new Error(`Unsupported file extension for intercept: ${ext}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Puppeteer can't `import` ESM modules from `file://` URLs. Rather than stand up
|
|
48
|
+
* a real HTTP server (ports/firewalls), we intercept requests to a dummy
|
|
49
|
+
* `https://hdoc-mermaid-intercept.invalid` origin and serve the corresponding
|
|
50
|
+
* local file. Ported verbatim from mermaid-cli's puppeteerIntercept.js.
|
|
51
|
+
*/
|
|
52
|
+
class Interceptor {
|
|
53
|
+
#INTERCEPT_ORIGIN = "https://hdoc-mermaid-intercept.invalid";
|
|
54
|
+
|
|
55
|
+
/** @type {Set<string>} resolved (realpath) dirs allowed to be served. */
|
|
56
|
+
#allowedDirs = new Set();
|
|
57
|
+
|
|
58
|
+
async fileUrlToInterceptUrl(fileUrl, { allowParentDirectoryLevel = 1 } = {}) {
|
|
59
|
+
fileUrl = new URL(fileUrl);
|
|
60
|
+
if (fileUrl.protocol !== "file:") {
|
|
61
|
+
throw new Error(`Invalid file URL: ${fileUrl}`);
|
|
62
|
+
}
|
|
63
|
+
let parentDirectory = await realpath(url.fileURLToPath(fileUrl));
|
|
64
|
+
while (allowParentDirectoryLevel-- >= 0) {
|
|
65
|
+
parentDirectory = path.dirname(parentDirectory);
|
|
66
|
+
}
|
|
67
|
+
this.#allowedDirs.add(parentDirectory);
|
|
68
|
+
return `${this.#INTERCEPT_ORIGIN}${fileUrl.pathname}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async interceptUrlToFileUrl(interceptUrl) {
|
|
72
|
+
interceptUrl = new URL(interceptUrl);
|
|
73
|
+
if (interceptUrl.origin !== this.#INTERCEPT_ORIGIN) {
|
|
74
|
+
throw new Error(`Invalid intercept URL: ${interceptUrl}`);
|
|
75
|
+
}
|
|
76
|
+
const fileUrl = new URL(
|
|
77
|
+
interceptUrl.href.slice(this.#INTERCEPT_ORIGIN.length),
|
|
78
|
+
"file://",
|
|
79
|
+
);
|
|
80
|
+
const filePath = await realpath(url.fileURLToPath(fileUrl));
|
|
81
|
+
if (
|
|
82
|
+
![...this.#allowedDirs].some((dir) =>
|
|
83
|
+
path.relative(filePath, dir).startsWith(".."),
|
|
84
|
+
)
|
|
85
|
+
) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`Intercept URL is not in an allowed directory: ${interceptUrl}`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return fileUrl;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async #interceptRequestHandler(request) {
|
|
94
|
+
try {
|
|
95
|
+
if (request.url().startsWith(this.#INTERCEPT_ORIGIN)) {
|
|
96
|
+
const fileUrl = await this.interceptUrlToFileUrl(request.url());
|
|
97
|
+
return request.respond({
|
|
98
|
+
status: 200,
|
|
99
|
+
headers: { "Access-Control-Allow-Origin": "*" },
|
|
100
|
+
contentType: getContentTypeFromFileExtension(
|
|
101
|
+
url.fileURLToPath(fileUrl),
|
|
102
|
+
),
|
|
103
|
+
body: await readFile(fileUrl),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error(
|
|
108
|
+
`Error handling intercept request for ${request.url()}:`,
|
|
109
|
+
error,
|
|
110
|
+
);
|
|
111
|
+
request.abort();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
request.continue();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
get interceptRequestHandler() {
|
|
118
|
+
return this.#interceptRequestHandler.bind(this);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Render a single Mermaid definition to an SVG string.
|
|
124
|
+
*
|
|
125
|
+
* @param {import('puppeteer').Browser} browser - An open puppeteer browser.
|
|
126
|
+
* @param {string} definition - Mermaid source (may include `--- config: ---` frontmatter).
|
|
127
|
+
* @param {{ backgroundColor?: string }} [opts]
|
|
128
|
+
* @returns {Promise<string>} the serialized `<svg>`.
|
|
129
|
+
*/
|
|
130
|
+
async function render_svg(browser, definition, { backgroundColor = "transparent" } = {}) {
|
|
131
|
+
const page = await browser.newPage();
|
|
132
|
+
// Surface in-page warnings/errors (e.g. mermaid parse errors) to the build log.
|
|
133
|
+
page.on("console", (msg) => {
|
|
134
|
+
if (msg.type() === "error" || msg.type() === "warning") {
|
|
135
|
+
console.warn(msg.text());
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
try {
|
|
139
|
+
await page.setContent(
|
|
140
|
+
"<!doctype html><html><body><div id=\"container\"></div></body></html>",
|
|
141
|
+
);
|
|
142
|
+
await page.$eval(
|
|
143
|
+
"body",
|
|
144
|
+
(body, bg) => {
|
|
145
|
+
body.style.background = bg;
|
|
146
|
+
},
|
|
147
|
+
backgroundColor,
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const interceptor = new Interceptor();
|
|
151
|
+
const mermaidUrl = await interceptor.fileUrlToInterceptUrl(
|
|
152
|
+
url.pathToFileURL(mermaidESMPath),
|
|
153
|
+
);
|
|
154
|
+
const elkUrl = await interceptor.fileUrlToInterceptUrl(
|
|
155
|
+
url.pathToFileURL(elkESMPath),
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
page.on("request", interceptor.interceptRequestHandler);
|
|
159
|
+
await page.setRequestInterception(true);
|
|
160
|
+
|
|
161
|
+
await page.$eval(
|
|
162
|
+
"#container",
|
|
163
|
+
async (container, { definition, mermaidUrl, elkUrl, backgroundColor }) => {
|
|
164
|
+
const { default: mermaid } = await import(mermaidUrl);
|
|
165
|
+
const { default: elkLayouts } = await import(elkUrl);
|
|
166
|
+
|
|
167
|
+
await Promise.all(
|
|
168
|
+
Array.from(document.fonts, (font) => font.load()),
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
mermaid.registerLayoutLoaders(elkLayouts);
|
|
172
|
+
mermaid.initialize({ startOnLoad: false });
|
|
173
|
+
|
|
174
|
+
// Throws on invalid diagrams — propagates out of $eval as a rejection.
|
|
175
|
+
const { svg: svgText } = await mermaid.render(
|
|
176
|
+
"hdoc-svg",
|
|
177
|
+
definition,
|
|
178
|
+
container,
|
|
179
|
+
);
|
|
180
|
+
container.innerHTML = svgText;
|
|
181
|
+
|
|
182
|
+
const svg = container.getElementsByTagName?.("svg")?.[0];
|
|
183
|
+
if (svg?.style) {
|
|
184
|
+
svg.style.backgroundColor = backgroundColor;
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
{ definition, mermaidUrl, elkUrl, backgroundColor },
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// Serialize via XMLSerializer so HTML <foreignObject> content (e.g. <br>)
|
|
191
|
+
// becomes valid XML in the saved .svg file.
|
|
192
|
+
const svgXML = await page.$eval("svg", (svg) => {
|
|
193
|
+
// eslint-disable-next-line no-undef
|
|
194
|
+
const xmlSerializer = new XMLSerializer();
|
|
195
|
+
return xmlSerializer.serializeToString(svg);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
return svgXML;
|
|
199
|
+
} finally {
|
|
200
|
+
await page.close();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = { render_svg, Interceptor };
|