@bytechain.cn/colamd-themes 0.3.2 → 0.5.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/.claude/skills/colamd-themes-preview.skill.md +333 -0
- package/.claude/skills/colamd-themes.skill.md +10 -0
- package/.dsh/skills/colamd-themes/SKILL.md +14 -0
- package/.github/supported-topics.yml +17 -0
- package/.hermes/skills/tools/colamd-themes/SKILL.md +14 -0
- package/.openclaw/skills/colamd-themes/SKILL.md +1 -37
- package/.opencode/skills/colamd-themes/SKILL.md +17 -0
- package/.trae/rules/colamd-themes.md +0 -36
- package/CHANGELOG.md +318 -241
- package/DSH_SUPPORT_SUMMARY.md +87 -0
- package/README.md +414 -384
- package/README_CN.md +412 -384
- package/RELEASENOTE.md +541 -389
- package/bytechain.cn-colamd-themes-0.5.0.tgz +0 -0
- package/dist/cli.js +36 -55
- package/dist/cli.js.map +1 -1
- package/dist/color-utils.d.ts +1 -0
- package/dist/color-utils.js +25 -6
- package/dist/color-utils.js.map +1 -1
- package/dist/extractors/pdf-extractor.js +6 -1
- package/dist/extractors/pdf-extractor.js.map +1 -1
- package/dist/extractors/url-extractor.d.ts +0 -4
- package/dist/extractors/url-extractor.js +13 -126
- package/dist/extractors/url-extractor.js.map +1 -1
- package/dist/generator.d.ts +1 -1
- package/dist/generator.js +32 -11
- package/dist/generator.js.map +1 -1
- package/dist/models.d.ts +5 -4
- package/dist/models.js +2 -3
- package/dist/models.js.map +1 -1
- package/dist/renderer.d.ts +4 -2
- package/dist/renderer.js +64 -84
- package/dist/renderer.js.map +1 -1
- package/dist/templates/template.css +220 -498
- package/dist/templates/theme-paradigm.md +530 -658
- package/dist/templates/theme-template.hbs +256 -370
- package/dist/theme-store.js +19 -3
- package/dist/theme-store.js.map +1 -1
- package/dist/utils/document-paradigm.d.ts +12 -0
- package/dist/utils/document-paradigm.js +109 -0
- package/dist/utils/document-paradigm.js.map +1 -0
- package/dist/validators.d.ts +4 -1
- package/dist/validators.js +115 -1
- package/dist/validators.js.map +1 -1
- package/mcp-server/dist/index.d.ts +13 -0
- package/mcp-server/dist/index.d.ts.map +1 -0
- package/mcp-server/dist/index.js +0 -0
- package/mcp-server/dist/index.js.map +1 -0
- package/mcp-server/package-lock.json +1759 -0
- package/mcp-server/package.json +24 -0
- package/mcp-server/src/index.ts +0 -0
- package/mcp-server/tsconfig.json +19 -0
- package/package.json +54 -36
- package/skills/build-skills.sh +30 -5
- package/skills/colamd-themes/SKILL.md +180 -178
- package/src/cli.ts +474 -487
- package/src/color-systems.ts +289 -289
- package/src/color-utils.ts +137 -120
- package/src/contrast.ts +66 -66
- package/src/docx-style-paradigm.ts +811 -811
- package/src/export-cli.ts +356 -356
- package/src/extractors/base.ts +8 -8
- package/src/extractors/docx-extractor.ts +417 -417
- package/src/extractors/pdf-extractor.ts +533 -527
- package/src/extractors/url-extractor.ts +846 -981
- package/src/generator.ts +197 -169
- package/src/index.ts +29 -29
- package/src/models.ts +212 -212
- package/src/renderer.ts +332 -359
- package/src/templates/template.css +220 -498
- package/src/templates/theme-paradigm.md +530 -658
- package/src/templates/theme-template.hbs +256 -370
- package/src/theme-store.ts +185 -170
- package/src/types/pdf-parse.d.ts +12 -12
- package/src/utils/cli-utils.ts +84 -84
- package/src/utils/document-paradigm.ts +118 -0
- package/src/validators.ts +298 -176
- package/themes/academic-paper-pt.css +353 -1805
- package/themes/academic-paper.css +350 -1099
- package/themes/academic-style.css +342 -2430
- package/themes/colly.css +256 -370
- package/themes/elegant.css +288 -671
- package/themes/forest-ink.css +240 -664
- package/themes/guizang.css +250 -732
- package/themes/hermes-security.css +279 -0
- package/themes/meeting-minutes.css +256 -370
- package/themes/pixso-design.css +278 -1261
- package/themes/salvator-blog.css +256 -370
- package/themes/shimo-theme.css +256 -0
- package/themes/swiss-design.css +285 -746
- package/themes/template.css +220 -498
- package/themes/trae-blue.css +280 -737
- package/themes/word-contract.css +249 -0
- package/themes-preview/demo-content.md +187 -187
- package/tsconfig.json +18 -18
- package/src/mermaid-presets.ts +0 -142
- package/themes/swiss-design copy.css +0 -746
- package/themes-preview/colly-preview.html +0 -623
- package/themes-preview/meeting-minutes-preview.html +0 -625
- package/themes-preview/salvator-blog-preview.html +0 -623
package/src/renderer.ts
CHANGED
|
@@ -1,359 +1,332 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Puppeteer-based rendering engine for ColaMD HTML/PDF export.
|
|
3
|
-
*
|
|
4
|
-
* Uses @bytechain.cn/colamd/renderer to create a headless
|
|
5
|
-
* apply themes, render mermaid/math, and export standalone
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* →
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
//
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* One-shot render: init → render → close.
|
|
335
|
-
* Convenience for single-file export.
|
|
336
|
-
*/
|
|
337
|
-
export async function renderMarkdown(input: RenderInput): Promise<RenderResult> {
|
|
338
|
-
const renderer = new ColamdRenderer();
|
|
339
|
-
try {
|
|
340
|
-
return await renderer.render(input);
|
|
341
|
-
} finally {
|
|
342
|
-
await renderer.close();
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* One-shot PDF export: init → render PDF → close.
|
|
348
|
-
*/
|
|
349
|
-
export async function renderPDF(
|
|
350
|
-
input: RenderInput,
|
|
351
|
-
options?: PDFExportOptions
|
|
352
|
-
): Promise<Buffer> {
|
|
353
|
-
const renderer = new ColamdRenderer();
|
|
354
|
-
try {
|
|
355
|
-
return await renderer.exportPDF(input, options);
|
|
356
|
-
} finally {
|
|
357
|
-
await renderer.close();
|
|
358
|
-
}
|
|
359
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Puppeteer-based rendering engine for ColaMD HTML/PDF export.
|
|
3
|
+
*
|
|
4
|
+
* Uses @bytechain.cn/colamd/renderer (1.5.x library build) to create a headless
|
|
5
|
+
* Milkdown editor, apply themes, render mermaid/math, and export standalone
|
|
6
|
+
* HTML or PDF. Theme CSS targets the ColaMD 2.7.7 contract (verified against
|
|
7
|
+
* @bytechain.cn/colamd@2.7.7), so generated themes carry over unchanged.
|
|
8
|
+
*
|
|
9
|
+
* Architecture:
|
|
10
|
+
* Local HTTP server (serves renderer.js + page HTML)
|
|
11
|
+
* → Puppeteer page (same-origin, no CORS)
|
|
12
|
+
* → createColaMDEditor()
|
|
13
|
+
* → applyTheme() + setMarkdown() + ensureAllPluginsRendered()
|
|
14
|
+
* → buildExportHTML() / page.pdf()
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import puppeteer, { type Browser, type Page } from "puppeteer";
|
|
18
|
+
import { createServer, type Server } from "node:http";
|
|
19
|
+
import { readFileSync, existsSync, createReadStream } from "node:fs";
|
|
20
|
+
import { resolve, dirname, join } from "node:path";
|
|
21
|
+
|
|
22
|
+
// ── Types ──
|
|
23
|
+
|
|
24
|
+
/** Input for rendering a markdown document. */
|
|
25
|
+
export interface RenderInput {
|
|
26
|
+
/** Markdown content string */
|
|
27
|
+
markdown: string;
|
|
28
|
+
/** Theme name: built-in (light/dark/elegant/newsprint) or "custom:<name>" */
|
|
29
|
+
themeName?: string;
|
|
30
|
+
/** Custom CSS content (used with "custom:<name>" themes) */
|
|
31
|
+
customCSS?: string;
|
|
32
|
+
/** Document title for the HTML <title> */
|
|
33
|
+
title?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Result of a render operation. */
|
|
37
|
+
export interface RenderResult {
|
|
38
|
+
/** Complete standalone HTML document */
|
|
39
|
+
html: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** PDF export options. */
|
|
43
|
+
export interface PDFExportOptions {
|
|
44
|
+
/** Page format */
|
|
45
|
+
format?: "A4" | "Letter" | "A3";
|
|
46
|
+
/** Print background colors/images */
|
|
47
|
+
printBackground?: boolean;
|
|
48
|
+
/** Page margins */
|
|
49
|
+
margin?: { top?: string; right?: string; bottom?: string; left?: string };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ── File resolution ──
|
|
53
|
+
|
|
54
|
+
/** Locate the ColaMD renderer ESM bundle in node_modules. */
|
|
55
|
+
function findRendererPath(): string {
|
|
56
|
+
const candidates = [
|
|
57
|
+
resolve(dirname(new URL(import.meta.url).pathname), "../node_modules/@bytechain.cn/colamd/dist/lib/colamd-renderer.js"),
|
|
58
|
+
resolve(process.cwd(), "node_modules/@bytechain.cn/colamd/dist/lib/colamd-renderer.js"),
|
|
59
|
+
];
|
|
60
|
+
for (const p of candidates) {
|
|
61
|
+
if (existsSync(p)) return p;
|
|
62
|
+
}
|
|
63
|
+
// Detect the npm 2.x distribution: it is the Electron app package and no
|
|
64
|
+
// longer ships dist/lib/colamd-renderer.js. Give an actionable error.
|
|
65
|
+
for (const base of candidates.map((p) => resolve(p, "../../.."))) {
|
|
66
|
+
const pkgJson = join(base, "package.json");
|
|
67
|
+
if (existsSync(pkgJson)) {
|
|
68
|
+
try {
|
|
69
|
+
const version = JSON.parse(readFileSync(pkgJson, "utf-8")).version as string;
|
|
70
|
+
if (version.startsWith("2.")) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`@bytechain.cn/colamd ${version} 是 Electron 应用分发,不含 colamd-renderer.js 库入口,` +
|
|
73
|
+
"无法用于 HTML/PDF 导出。请降级: npm install @bytechain.cn/colamd@^1.5.2"
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
} catch (e) {
|
|
77
|
+
if (e instanceof Error && e.message.includes("Electron")) throw e;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
throw new Error(
|
|
82
|
+
"Cannot find @bytechain.cn/colamd renderer. " +
|
|
83
|
+
"Install it: npm install @bytechain.cn/colamd@^1.5.2"
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Local HTTP server ──
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Bridge for the 1.5.x renderer: its base CSS declares theme variables on
|
|
91
|
+
* `body` (element-level), which overrides `:root` inheritance regardless of
|
|
92
|
+
* style order. Mirror the theme's `:root` declarations onto `body` so exports
|
|
93
|
+
* carry the 2.7.7 palette. Theme files themselves stay `:root`-only (2.7.7
|
|
94
|
+
* contract); this shim lives only in the export pipeline.
|
|
95
|
+
*/
|
|
96
|
+
function bridgeLegacyBodyVars(css: string): string {
|
|
97
|
+
const rootMatch = css.match(/:root\s*\{([^}]*)\}/);
|
|
98
|
+
if (!rootMatch) return css;
|
|
99
|
+
return (
|
|
100
|
+
css +
|
|
101
|
+
"\n/* 1.5.x bridge: body-level variables override the base defaults */\nbody {\n" +
|
|
102
|
+
rootMatch[1] +
|
|
103
|
+
"}\n"
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const SHELL_HTML = `<!DOCTYPE html>
|
|
108
|
+
<html><head>
|
|
109
|
+
<meta charset="utf-8">
|
|
110
|
+
<link rel="stylesheet" href="/colamd.css">
|
|
111
|
+
<style>body{margin:0;padding:0}#editor{width:100%;min-height:100vh}</style>
|
|
112
|
+
</head><body><div id="editor"></div></body></html>`;
|
|
113
|
+
|
|
114
|
+
interface LocalServer {
|
|
115
|
+
port: number;
|
|
116
|
+
close(): Promise<void>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function startLocalServer(rendererPath: string): Promise<LocalServer> {
|
|
120
|
+
const cssPath = join(dirname(rendererPath), "colamd.css");
|
|
121
|
+
return new Promise((resolveStart, reject) => {
|
|
122
|
+
const server: Server = createServer((req, res) => {
|
|
123
|
+
if (req.url === "/" || req.url === "/index.html") {
|
|
124
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
125
|
+
res.end(SHELL_HTML);
|
|
126
|
+
} else if (req.url === "/colamd-renderer.js") {
|
|
127
|
+
res.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8" });
|
|
128
|
+
createReadStream(rendererPath).pipe(res);
|
|
129
|
+
} else if (req.url === "/colamd.css") {
|
|
130
|
+
res.writeHead(200, { "Content-Type": "text/css; charset=utf-8" });
|
|
131
|
+
createReadStream(cssPath).pipe(res);
|
|
132
|
+
} else {
|
|
133
|
+
res.writeHead(404);
|
|
134
|
+
res.end();
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
server.listen(0, "127.0.0.1", () => {
|
|
139
|
+
const addr = server.address();
|
|
140
|
+
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
141
|
+
resolveStart({
|
|
142
|
+
port,
|
|
143
|
+
close: () =>
|
|
144
|
+
new Promise((resolveClose) => server.close(() => resolveClose())),
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
server.on("error", reject);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ── Renderer ──
|
|
153
|
+
|
|
154
|
+
export class ColamdRenderer {
|
|
155
|
+
private browser: Browser | null = null;
|
|
156
|
+
private page: Page | null = null;
|
|
157
|
+
private server: LocalServer | null = null;
|
|
158
|
+
private ready = false;
|
|
159
|
+
|
|
160
|
+
/** Launch Puppeteer and prepare the editor page. */
|
|
161
|
+
async init(): Promise<void> {
|
|
162
|
+
if (this.browser) return;
|
|
163
|
+
|
|
164
|
+
// Start local HTTP server for the renderer bundle
|
|
165
|
+
const rendererPath = findRendererPath();
|
|
166
|
+
this.server = await startLocalServer(rendererPath);
|
|
167
|
+
const origin = `http://127.0.0.1:${this.server.port}`;
|
|
168
|
+
|
|
169
|
+
// Launch Puppeteer (use "shell" mode — chrome-headless-shell binary is more
|
|
170
|
+
// stable on this Windows environment than the full Chrome --headless=new mode)
|
|
171
|
+
this.browser = await puppeteer.launch({
|
|
172
|
+
headless: "shell",
|
|
173
|
+
args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage"],
|
|
174
|
+
});
|
|
175
|
+
this.page = await this.browser.newPage();
|
|
176
|
+
|
|
177
|
+
// Navigate to shell page (same origin as renderer script)
|
|
178
|
+
await this.page.goto(`${origin}/`, { waitUntil: "domcontentloaded" });
|
|
179
|
+
|
|
180
|
+
// Load the ColaMD renderer as ESM (same-origin, no CORS)
|
|
181
|
+
await this.page.addScriptTag({
|
|
182
|
+
type: "module",
|
|
183
|
+
content: `
|
|
184
|
+
import { createColaMDEditor } from '${origin}/colamd-renderer.js';
|
|
185
|
+
window.__colamdReady = createColaMDEditor({
|
|
186
|
+
rootId: 'editor',
|
|
187
|
+
editable: false
|
|
188
|
+
}).then(handle => {
|
|
189
|
+
window.__colamdHandle = handle;
|
|
190
|
+
return true;
|
|
191
|
+
}).catch(err => {
|
|
192
|
+
window.__colamdError = err.message;
|
|
193
|
+
return false;
|
|
194
|
+
});
|
|
195
|
+
`,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Wait for the editor to initialize
|
|
199
|
+
await this.page.waitForFunction(
|
|
200
|
+
() => (window as any).__colamdHandle || (window as any).__colamdError,
|
|
201
|
+
{ timeout: 30000 }
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const error = await this.page.evaluate(() => (window as any).__colamdError);
|
|
205
|
+
if (error) throw new Error(`Editor init failed: ${error}`);
|
|
206
|
+
|
|
207
|
+
this.ready = true;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Render markdown to a standalone HTML document.
|
|
212
|
+
*/
|
|
213
|
+
async render(input: RenderInput): Promise<RenderResult> {
|
|
214
|
+
if (!this.ready || !this.page) await this.init();
|
|
215
|
+
const page = this.page!;
|
|
216
|
+
|
|
217
|
+
// Apply theme
|
|
218
|
+
if (input.themeName) {
|
|
219
|
+
await page.evaluate(
|
|
220
|
+
(name: string, css: string | undefined) => {
|
|
221
|
+
(window as any).__colamdHandle.applyTheme(name, css || undefined);
|
|
222
|
+
},
|
|
223
|
+
input.themeName,
|
|
224
|
+
input.customCSS ? bridgeLegacyBodyVars(input.customCSS) : undefined
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Set markdown content
|
|
229
|
+
await page.evaluate((md: string) => {
|
|
230
|
+
(window as any).__colamdHandle.setMarkdown(md);
|
|
231
|
+
}, input.markdown);
|
|
232
|
+
|
|
233
|
+
// Wait for all plugins (mermaid, math) to finish rendering
|
|
234
|
+
await page.evaluate(async () => {
|
|
235
|
+
await (window as any).__colamdHandle.ensureAllPluginsRendered();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Additional settle time for complex diagrams
|
|
239
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
240
|
+
|
|
241
|
+
// Build export HTML
|
|
242
|
+
const html = await page.evaluate(() => {
|
|
243
|
+
return (window as any).__colamdHandle.buildExportHTML();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Inject custom title if provided
|
|
247
|
+
const finalHtml = input.title
|
|
248
|
+
? html.replace(/<title>[^<]*<\/title>/, `<title>${input.title}</title>`)
|
|
249
|
+
: html;
|
|
250
|
+
|
|
251
|
+
return { html: finalHtml };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Render markdown and export as PDF.
|
|
256
|
+
*/
|
|
257
|
+
async exportPDF(input: RenderInput, options: PDFExportOptions = {}): Promise<Buffer> {
|
|
258
|
+
if (!this.ready || !this.page) await this.init();
|
|
259
|
+
|
|
260
|
+
// First render to HTML
|
|
261
|
+
const { html } = await this.render(input);
|
|
262
|
+
|
|
263
|
+
// Load the HTML in a fresh page for PDF generation
|
|
264
|
+
const pdfPage = await this.browser!.newPage();
|
|
265
|
+
await pdfPage.setContent(html, { waitUntil: "load", timeout: 30000 });
|
|
266
|
+
|
|
267
|
+
// NOTE: Do NOT use emulateMediaType("screen") here.
|
|
268
|
+
// The CSS template includes comprehensive @media print rules (SECTION 6)
|
|
269
|
+
// with !important declarations and print-color-adjust: exact for proper styling.
|
|
270
|
+
// Using screen mode would prevent these print-specific styles from applying.
|
|
271
|
+
|
|
272
|
+
const pdfBuffer = await pdfPage.pdf({
|
|
273
|
+
format: options.format ?? "A4",
|
|
274
|
+
printBackground: options.printBackground ?? true,
|
|
275
|
+
margin: options.margin ?? { top: "15mm", right: "15mm", bottom: "15mm", left: "15mm" },
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
await pdfPage.close();
|
|
279
|
+
return Buffer.from(pdfBuffer);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Shut down the browser and local server with proper resource cleanup. */
|
|
283
|
+
async close(): Promise<void> {
|
|
284
|
+
try {
|
|
285
|
+
// Explicitly close page first to prevent zombie processes
|
|
286
|
+
if (this.page) {
|
|
287
|
+
await this.page.close().catch(() => {});
|
|
288
|
+
this.page = null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (this.browser) {
|
|
292
|
+
await this.browser.close();
|
|
293
|
+
this.browser = null;
|
|
294
|
+
}
|
|
295
|
+
} finally {
|
|
296
|
+
this.ready = false;
|
|
297
|
+
|
|
298
|
+
if (this.server) {
|
|
299
|
+
await this.server.close();
|
|
300
|
+
this.server = null;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* One-shot render: init → render → close.
|
|
308
|
+
* Convenience for single-file export.
|
|
309
|
+
*/
|
|
310
|
+
export async function renderMarkdown(input: RenderInput): Promise<RenderResult> {
|
|
311
|
+
const renderer = new ColamdRenderer();
|
|
312
|
+
try {
|
|
313
|
+
return await renderer.render(input);
|
|
314
|
+
} finally {
|
|
315
|
+
await renderer.close();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* One-shot PDF export: init → render PDF → close.
|
|
321
|
+
*/
|
|
322
|
+
export async function renderPDF(
|
|
323
|
+
input: RenderInput,
|
|
324
|
+
options?: PDFExportOptions
|
|
325
|
+
): Promise<Buffer> {
|
|
326
|
+
const renderer = new ColamdRenderer();
|
|
327
|
+
try {
|
|
328
|
+
return await renderer.exportPDF(input, options);
|
|
329
|
+
} finally {
|
|
330
|
+
await renderer.close();
|
|
331
|
+
}
|
|
332
|
+
}
|