@masumdev/markforge 0.3.0 → 0.4.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/README.md +168 -141
- package/dist/App-3QDKBKHG.mjs +328 -0
- package/dist/{chunk-IYGPJIK5.mjs → chunk-BPHDY6VB.mjs} +6 -0
- package/dist/{App-AM2CWXHJ.mjs → chunk-CRV7R2BG.mjs} +1722 -657
- package/dist/{chunk-HKB4CSPZ.mjs → chunk-TNWZ4MFS.mjs} +1 -1
- package/dist/cli.mjs +29 -7
- package/dist/index.d.mts +506 -123
- package/dist/index.d.ts +506 -123
- package/dist/index.js +2481 -208
- package/dist/index.mjs +2470 -208
- package/dist/{loadConfig-PD6ENMAM.mjs → loadConfig-PGJKPE6G.mjs} +1 -1
- package/dist/previewServer-XAUOBNBZ.mjs +893 -0
- package/package.json +5 -1
- package/schema.json +142 -0
package/dist/index.mjs
CHANGED
|
@@ -49,6 +49,26 @@ function parseInlineSpans(text) {
|
|
|
49
49
|
remaining = remaining.slice(imgMatch[0].length);
|
|
50
50
|
continue;
|
|
51
51
|
}
|
|
52
|
+
const fnMatch = remaining.match(/^\[\^([\w-]+)\]/);
|
|
53
|
+
if (fnMatch) {
|
|
54
|
+
const fnId = fnMatch[1];
|
|
55
|
+
spans.push({
|
|
56
|
+
type: "footnoteRef",
|
|
57
|
+
content: fnId,
|
|
58
|
+
footnoteId: fnId
|
|
59
|
+
});
|
|
60
|
+
remaining = remaining.slice(fnMatch[0].length);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const mathMatch = remaining.match(/^\$([^$\n]+?)\$/);
|
|
64
|
+
if (mathMatch && !mathMatch[1].startsWith("$")) {
|
|
65
|
+
spans.push({
|
|
66
|
+
type: "mathInline",
|
|
67
|
+
content: mathMatch[1]
|
|
68
|
+
});
|
|
69
|
+
remaining = remaining.slice(mathMatch[0].length);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
52
72
|
const linkMatch = remaining.match(/^\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]+)")?\)/);
|
|
53
73
|
if (linkMatch) {
|
|
54
74
|
spans.push({
|
|
@@ -148,7 +168,7 @@ function parseInlineSpans(text) {
|
|
|
148
168
|
remaining = remaining.slice(fullTag.length);
|
|
149
169
|
continue;
|
|
150
170
|
}
|
|
151
|
-
const nextSpecial = remaining.search(/[\*\_\[
|
|
171
|
+
const nextSpecial = remaining.search(/[\*\_\[\!`~<\$]/);
|
|
152
172
|
if (nextSpecial === -1) {
|
|
153
173
|
spans.push({
|
|
154
174
|
type: "text",
|
|
@@ -174,6 +194,35 @@ function parseInlineSpans(text) {
|
|
|
174
194
|
function slugify(text) {
|
|
175
195
|
return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
176
196
|
}
|
|
197
|
+
function applyHeadingNumbering(nodes, tocEntries, options) {
|
|
198
|
+
const depth = options.depth ?? 3;
|
|
199
|
+
const skipH1 = options.skipH1 ?? false;
|
|
200
|
+
const prefix = options.prefix ?? "";
|
|
201
|
+
const counters = [0, 0, 0, 0, 0, 0];
|
|
202
|
+
for (const node of nodes) {
|
|
203
|
+
if (node.type === "heading" && node.level) {
|
|
204
|
+
const lvl = node.level;
|
|
205
|
+
if (lvl > depth) continue;
|
|
206
|
+
if (lvl === 1 && skipH1) continue;
|
|
207
|
+
const idx = lvl - 1;
|
|
208
|
+
counters[idx]++;
|
|
209
|
+
for (let c = idx + 1; c < counters.length; c++) {
|
|
210
|
+
counters[c] = 0;
|
|
211
|
+
}
|
|
212
|
+
const startIdx = skipH1 ? 1 : 0;
|
|
213
|
+
const parts = counters.slice(startIdx, idx + 1).filter((n) => n > 0);
|
|
214
|
+
const numberStr = parts.join(".") + ".";
|
|
215
|
+
const fullPrefix = prefix ? `${prefix} ${numberStr} ` : `${numberStr} `;
|
|
216
|
+
const originalText = node.text || "";
|
|
217
|
+
node.text = fullPrefix + originalText;
|
|
218
|
+
node.inlines = parseInlineSpans(node.text);
|
|
219
|
+
const toc = tocEntries.find((t) => t.id === node.id);
|
|
220
|
+
if (toc) {
|
|
221
|
+
toc.text = node.text;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
177
226
|
function parseMarkdownDocument(rawMarkdown) {
|
|
178
227
|
const { data: frontmatter, content } = matter(rawMarkdown);
|
|
179
228
|
const metadata = frontmatter || {};
|
|
@@ -185,6 +234,7 @@ function parseMarkdownDocument(rawMarkdown) {
|
|
|
185
234
|
const lines = cleanContent.split(/\r?\n/);
|
|
186
235
|
const nodes = [];
|
|
187
236
|
const tocEntries = [];
|
|
237
|
+
const footnoteDefs = [];
|
|
188
238
|
let i = 0;
|
|
189
239
|
while (i < lines.length) {
|
|
190
240
|
const line = lines[i];
|
|
@@ -213,6 +263,29 @@ function parseMarkdownDocument(rawMarkdown) {
|
|
|
213
263
|
i++;
|
|
214
264
|
continue;
|
|
215
265
|
}
|
|
266
|
+
if (line.trim().startsWith("$$")) {
|
|
267
|
+
const mathLines = [];
|
|
268
|
+
const singleLine = line.trim().match(/^\$\$(.+)\$\$$/);
|
|
269
|
+
if (singleLine) {
|
|
270
|
+
nodes.push({
|
|
271
|
+
type: "mathBlock",
|
|
272
|
+
text: singleLine[1].trim()
|
|
273
|
+
});
|
|
274
|
+
i++;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
i++;
|
|
278
|
+
while (i < lines.length && !lines[i].trim().startsWith("$$")) {
|
|
279
|
+
mathLines.push(lines[i]);
|
|
280
|
+
i++;
|
|
281
|
+
}
|
|
282
|
+
if (i < lines.length) i++;
|
|
283
|
+
nodes.push({
|
|
284
|
+
type: "mathBlock",
|
|
285
|
+
text: mathLines.join("\n").trim()
|
|
286
|
+
});
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
216
289
|
const codeBlockMatch = line.match(/^```(\w+)?/);
|
|
217
290
|
if (codeBlockMatch) {
|
|
218
291
|
const language = (codeBlockMatch[1] || "text").trim().toLowerCase();
|
|
@@ -239,6 +312,100 @@ function parseMarkdownDocument(rawMarkdown) {
|
|
|
239
312
|
}
|
|
240
313
|
continue;
|
|
241
314
|
}
|
|
315
|
+
const colsMatch = line.trim().match(/^:::columns(?:\s+\[?([\w\s=.-]+)\]?)?$/i);
|
|
316
|
+
if (colsMatch) {
|
|
317
|
+
const attrStr = colsMatch[1] || "";
|
|
318
|
+
let colsCount = 2;
|
|
319
|
+
let colGap = "1.5rem";
|
|
320
|
+
if (attrStr) {
|
|
321
|
+
const numMatch = attrStr.trim().match(/^(\d+)$/);
|
|
322
|
+
const cMatch = attrStr.match(/cols=(\d+)/i) || attrStr.match(/columns=(\d+)/i);
|
|
323
|
+
const gMatch = attrStr.match(/gap=([^\s]+)/i);
|
|
324
|
+
if (numMatch) colsCount = parseInt(numMatch[1], 10);
|
|
325
|
+
else if (cMatch) colsCount = parseInt(cMatch[1], 10);
|
|
326
|
+
if (gMatch) colGap = gMatch[1];
|
|
327
|
+
}
|
|
328
|
+
const columnNodes = [];
|
|
329
|
+
let currentColumnLines = [];
|
|
330
|
+
let inColBlock = false;
|
|
331
|
+
i++;
|
|
332
|
+
while (i < lines.length) {
|
|
333
|
+
const curLine = lines[i];
|
|
334
|
+
const trimmed = curLine.trim();
|
|
335
|
+
if (/^:::col(?:umn)?$/i.test(trimmed)) {
|
|
336
|
+
if (currentColumnLines.length > 0) {
|
|
337
|
+
const subDoc = parseMarkdownDocument(currentColumnLines.join("\n"));
|
|
338
|
+
columnNodes.push({
|
|
339
|
+
type: "column",
|
|
340
|
+
children: subDoc.nodes
|
|
341
|
+
});
|
|
342
|
+
currentColumnLines = [];
|
|
343
|
+
}
|
|
344
|
+
inColBlock = true;
|
|
345
|
+
i++;
|
|
346
|
+
} else if (trimmed === ":::") {
|
|
347
|
+
if (inColBlock) {
|
|
348
|
+
if (currentColumnLines.length > 0) {
|
|
349
|
+
const subDoc = parseMarkdownDocument(currentColumnLines.join("\n"));
|
|
350
|
+
columnNodes.push({
|
|
351
|
+
type: "column",
|
|
352
|
+
children: subDoc.nodes
|
|
353
|
+
});
|
|
354
|
+
currentColumnLines = [];
|
|
355
|
+
}
|
|
356
|
+
inColBlock = false;
|
|
357
|
+
i++;
|
|
358
|
+
} else {
|
|
359
|
+
if (currentColumnLines.length > 0) {
|
|
360
|
+
const subDoc = parseMarkdownDocument(currentColumnLines.join("\n"));
|
|
361
|
+
columnNodes.push({
|
|
362
|
+
type: "column",
|
|
363
|
+
children: subDoc.nodes
|
|
364
|
+
});
|
|
365
|
+
currentColumnLines = [];
|
|
366
|
+
}
|
|
367
|
+
i++;
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
currentColumnLines.push(curLine);
|
|
372
|
+
i++;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (currentColumnLines.length > 0) {
|
|
376
|
+
const subDoc = parseMarkdownDocument(currentColumnLines.join("\n"));
|
|
377
|
+
columnNodes.push({
|
|
378
|
+
type: "column",
|
|
379
|
+
children: subDoc.nodes
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
nodes.push({
|
|
383
|
+
type: "columns",
|
|
384
|
+
columnsCount: columnNodes.length > 0 ? columnNodes.length : colsCount,
|
|
385
|
+
columnGap: colGap,
|
|
386
|
+
children: columnNodes
|
|
387
|
+
});
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
const fnDefMatch = line.match(/^\[\^([\w-]+)\]:\s+(.+)$/);
|
|
391
|
+
if (fnDefMatch) {
|
|
392
|
+
const fnId = fnDefMatch[1];
|
|
393
|
+
const fnText = fnDefMatch[2].trim();
|
|
394
|
+
const inlines = parseInlineSpans(fnText);
|
|
395
|
+
footnoteDefs.push({
|
|
396
|
+
id: fnId,
|
|
397
|
+
text: fnText,
|
|
398
|
+
inlines
|
|
399
|
+
});
|
|
400
|
+
nodes.push({
|
|
401
|
+
type: "footnoteDef",
|
|
402
|
+
footnoteId: fnId,
|
|
403
|
+
text: fnText,
|
|
404
|
+
inlines
|
|
405
|
+
});
|
|
406
|
+
i++;
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
242
409
|
const calloutMatch = line.match(/^>\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*$/i);
|
|
243
410
|
if (calloutMatch) {
|
|
244
411
|
const calloutType = calloutMatch[1].toUpperCase();
|
|
@@ -362,7 +529,7 @@ function parseMarkdownDocument(rawMarkdown) {
|
|
|
362
529
|
continue;
|
|
363
530
|
}
|
|
364
531
|
const paraLines = [];
|
|
365
|
-
while (i < lines.length && lines[i].trim() && !lines[i].match(/^#{1,6}\s+/) && !lines[i].startsWith("```") && !lines[i].startsWith(">") && !lines[i].trim().startsWith("|") && !lines[i].match(/^(\s*)([-*+]|\d+\.)\s+/)) {
|
|
532
|
+
while (i < lines.length && lines[i].trim() && !lines[i].match(/^#{1,6}\s+/) && !lines[i].startsWith("```") && !lines[i].startsWith("$$") && !lines[i].startsWith(">") && !lines[i].trim().startsWith("|") && !lines[i].match(/^:::columns/) && !lines[i].match(/^\[\^[\w-]+\]:\s+/) && !lines[i].match(/^(\s*)([-*+]|\d+\.)\s+/)) {
|
|
366
533
|
paraLines.push(lines[i]);
|
|
367
534
|
i++;
|
|
368
535
|
}
|
|
@@ -373,12 +540,19 @@ function parseMarkdownDocument(rawMarkdown) {
|
|
|
373
540
|
inlines: parseInlineSpans(paraText)
|
|
374
541
|
});
|
|
375
542
|
}
|
|
543
|
+
if (metadata.numberHeadings) {
|
|
544
|
+
const opts = typeof metadata.numberHeadings === "boolean" ? { enabled: metadata.numberHeadings } : metadata.numberHeadings;
|
|
545
|
+
if (opts.enabled !== false) {
|
|
546
|
+
applyHeadingNumbering(nodes, tocEntries, opts);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
376
549
|
return {
|
|
377
550
|
metadata,
|
|
378
551
|
content: cleanContent,
|
|
379
552
|
nodes,
|
|
380
553
|
tocEntries,
|
|
381
|
-
inlinedStyles
|
|
554
|
+
inlinedStyles,
|
|
555
|
+
footnoteDefs
|
|
382
556
|
};
|
|
383
557
|
}
|
|
384
558
|
|
|
@@ -466,9 +640,14 @@ async function resolveImage(src, baseDir = process.cwd()) {
|
|
|
466
640
|
memoryImageCache.set(cacheKey, resolved2);
|
|
467
641
|
return resolved2;
|
|
468
642
|
}
|
|
469
|
-
|
|
643
|
+
let localPath = path.isAbsolute(src) ? src : path.resolve(baseDir, src);
|
|
470
644
|
if (!fs.existsSync(localPath)) {
|
|
471
|
-
|
|
645
|
+
const cwdPath = path.resolve(process.cwd(), src);
|
|
646
|
+
if (fs.existsSync(cwdPath)) {
|
|
647
|
+
localPath = cwdPath;
|
|
648
|
+
} else {
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
472
651
|
}
|
|
473
652
|
const buffer = fs.readFileSync(localPath);
|
|
474
653
|
const mimeType = getMimeType(localPath);
|
|
@@ -873,6 +1052,8 @@ import * as path4 from "path";
|
|
|
873
1052
|
import * as os from "os";
|
|
874
1053
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
875
1054
|
import { spawnSync } from "child_process";
|
|
1055
|
+
import { PDFDocument } from "pdf-lib";
|
|
1056
|
+
import { encryptPDF } from "@pdfsmaller/pdf-encrypt";
|
|
876
1057
|
|
|
877
1058
|
// src/core/html/htmlBuilder.ts
|
|
878
1059
|
import * as fs3 from "fs";
|
|
@@ -1115,6 +1296,12 @@ var DEFAULT_CONFIG = {
|
|
|
1115
1296
|
},
|
|
1116
1297
|
toc: false,
|
|
1117
1298
|
watermark: void 0,
|
|
1299
|
+
signatures: void 0,
|
|
1300
|
+
coverPage: void 0,
|
|
1301
|
+
backCover: void 0,
|
|
1302
|
+
numberHeadings: void 0,
|
|
1303
|
+
security: void 0,
|
|
1304
|
+
math: true,
|
|
1118
1305
|
embedImages: true,
|
|
1119
1306
|
metadata: void 0,
|
|
1120
1307
|
watch: false,
|
|
@@ -1271,7 +1458,8 @@ function formatMarginCss(margin, defaultCss = "2.5cm") {
|
|
|
1271
1458
|
return str;
|
|
1272
1459
|
}
|
|
1273
1460
|
function replaceDocumentTokens(template = "", meta) {
|
|
1274
|
-
|
|
1461
|
+
const currentYear = meta.year || (/* @__PURE__ */ new Date()).getFullYear().toString();
|
|
1462
|
+
return template.replace(/\{title\}/gi, meta.title || "").replace(/\{subtitle\}/gi, meta.subtitle || "").replace(/\{author\}/gi, meta.author || "").replace(/\{version\}/gi, meta.version || "").replace(/\{date\}/gi, meta.date || "").replace(/\{year\}/gi, currentYear).replace(/\{company\}/gi, meta.company || "");
|
|
1275
1463
|
}
|
|
1276
1464
|
function normalizeWatermark(rawWatermark) {
|
|
1277
1465
|
if (!rawWatermark) {
|
|
@@ -1408,6 +1596,129 @@ function normalizeSignatures(raw, meta = {}) {
|
|
|
1408
1596
|
spacingBeforeTwip
|
|
1409
1597
|
};
|
|
1410
1598
|
}
|
|
1599
|
+
function normalizeCoverPage(rawCover, tokenCtx = {}) {
|
|
1600
|
+
if (!rawCover) return void 0;
|
|
1601
|
+
const cfg = typeof rawCover === "object" ? rawCover : {};
|
|
1602
|
+
if (cfg.enabled === false) return void 0;
|
|
1603
|
+
const preset = cfg.preset || "modern";
|
|
1604
|
+
const title = cfg.title ? replaceDocumentTokens(String(cfg.title), tokenCtx) : tokenCtx.title || "Document Title";
|
|
1605
|
+
const subtitle = cfg.subtitle ? replaceDocumentTokens(String(cfg.subtitle), tokenCtx) : tokenCtx.subtitle;
|
|
1606
|
+
const author = Array.isArray(cfg.author) ? cfg.author.join(", ") : cfg.author ? replaceDocumentTokens(String(cfg.author), tokenCtx) : tokenCtx.author;
|
|
1607
|
+
const company = cfg.company ? replaceDocumentTokens(String(cfg.company), tokenCtx) : tokenCtx.company;
|
|
1608
|
+
const version = cfg.version ? replaceDocumentTokens(String(cfg.version), tokenCtx) : tokenCtx.version;
|
|
1609
|
+
let dateStr;
|
|
1610
|
+
if (typeof cfg.date === "string") {
|
|
1611
|
+
dateStr = replaceDocumentTokens(cfg.date, tokenCtx);
|
|
1612
|
+
} else if (cfg.date === true) {
|
|
1613
|
+
dateStr = tokenCtx.date || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1614
|
+
} else {
|
|
1615
|
+
dateStr = tokenCtx.date;
|
|
1616
|
+
}
|
|
1617
|
+
const badge = cfg.badge ? replaceDocumentTokens(String(cfg.badge), tokenCtx) : void 0;
|
|
1618
|
+
const badgeColor = typeof cfg.badgeColor === "string" ? cfg.badgeColor : void 0;
|
|
1619
|
+
const badgeTextColor = typeof cfg.badgeTextColor === "string" ? cfg.badgeTextColor : void 0;
|
|
1620
|
+
const logo = typeof cfg.logo === "string" ? cfg.logo : void 0;
|
|
1621
|
+
const logoWidth = cfg.logoWidth;
|
|
1622
|
+
const bgGradient = typeof cfg.bgGradient === "string" ? cfg.bgGradient : void 0;
|
|
1623
|
+
const textColor = typeof cfg.textColor === "string" ? cfg.textColor : void 0;
|
|
1624
|
+
const footerText = cfg.footerText ? replaceDocumentTokens(String(cfg.footerText), tokenCtx) : void 0;
|
|
1625
|
+
return {
|
|
1626
|
+
enabled: true,
|
|
1627
|
+
preset,
|
|
1628
|
+
title,
|
|
1629
|
+
subtitle,
|
|
1630
|
+
author,
|
|
1631
|
+
company,
|
|
1632
|
+
version,
|
|
1633
|
+
date: dateStr,
|
|
1634
|
+
badge,
|
|
1635
|
+
badgeColor,
|
|
1636
|
+
badgeTextColor,
|
|
1637
|
+
logo,
|
|
1638
|
+
logoWidth,
|
|
1639
|
+
bgGradient,
|
|
1640
|
+
textColor,
|
|
1641
|
+
footerText
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
function normalizeBackCover(rawBack, tokenCtx = {}) {
|
|
1645
|
+
if (!rawBack) return void 0;
|
|
1646
|
+
const cfg = typeof rawBack === "object" ? rawBack : {};
|
|
1647
|
+
if (cfg.enabled === false) return void 0;
|
|
1648
|
+
const preset = cfg.preset || "modern";
|
|
1649
|
+
const title = cfg.title ? replaceDocumentTokens(String(cfg.title), tokenCtx) : "Thank You";
|
|
1650
|
+
const subtitle = cfg.subtitle ? replaceDocumentTokens(String(cfg.subtitle), tokenCtx) : void 0;
|
|
1651
|
+
const company = cfg.company ? replaceDocumentTokens(String(cfg.company), tokenCtx) : tokenCtx.company;
|
|
1652
|
+
const address = cfg.address ? replaceDocumentTokens(String(cfg.address), tokenCtx) : void 0;
|
|
1653
|
+
const email = cfg.email ? replaceDocumentTokens(String(cfg.email), tokenCtx) : void 0;
|
|
1654
|
+
const phone = cfg.phone ? replaceDocumentTokens(String(cfg.phone), tokenCtx) : void 0;
|
|
1655
|
+
const website = cfg.website ? replaceDocumentTokens(String(cfg.website), tokenCtx) : void 0;
|
|
1656
|
+
let socialMap;
|
|
1657
|
+
if (cfg.social && typeof cfg.social === "object") {
|
|
1658
|
+
socialMap = {};
|
|
1659
|
+
for (const [k, v] of Object.entries(cfg.social)) {
|
|
1660
|
+
if (typeof v === "string") {
|
|
1661
|
+
socialMap[k] = replaceDocumentTokens(v, tokenCtx);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
const currentYear = (/* @__PURE__ */ new Date()).getFullYear().toString();
|
|
1666
|
+
const copyright = cfg.copyright ? replaceDocumentTokens(String(cfg.copyright), { ...tokenCtx, year: currentYear }) : company ? `Copyright (c) ${currentYear} ${company}. All Rights Reserved.` : void 0;
|
|
1667
|
+
const badge = cfg.badge ? replaceDocumentTokens(String(cfg.badge), tokenCtx) : void 0;
|
|
1668
|
+
const badgeColor = typeof cfg.badgeColor === "string" ? cfg.badgeColor : void 0;
|
|
1669
|
+
const badgeTextColor = typeof cfg.badgeTextColor === "string" ? cfg.badgeTextColor : void 0;
|
|
1670
|
+
const logo = typeof cfg.logo === "string" ? cfg.logo : void 0;
|
|
1671
|
+
const logoWidth = cfg.logoWidth;
|
|
1672
|
+
const bgGradient = typeof cfg.bgGradient === "string" ? cfg.bgGradient : void 0;
|
|
1673
|
+
const textColor = typeof cfg.textColor === "string" ? cfg.textColor : void 0;
|
|
1674
|
+
return {
|
|
1675
|
+
enabled: true,
|
|
1676
|
+
preset,
|
|
1677
|
+
title,
|
|
1678
|
+
subtitle,
|
|
1679
|
+
company,
|
|
1680
|
+
address,
|
|
1681
|
+
email,
|
|
1682
|
+
phone,
|
|
1683
|
+
website,
|
|
1684
|
+
social: socialMap,
|
|
1685
|
+
copyright,
|
|
1686
|
+
badge,
|
|
1687
|
+
badgeColor,
|
|
1688
|
+
badgeTextColor,
|
|
1689
|
+
logo,
|
|
1690
|
+
logoWidth,
|
|
1691
|
+
bgGradient,
|
|
1692
|
+
textColor
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
function normalizeNumberHeadings(raw) {
|
|
1696
|
+
if (raw === void 0 || raw === false) return void 0;
|
|
1697
|
+
if (raw === true) {
|
|
1698
|
+
return { enabled: true, depth: 3, skipH1: false, prefix: "" };
|
|
1699
|
+
}
|
|
1700
|
+
if (typeof raw === "object") {
|
|
1701
|
+
const obj = raw;
|
|
1702
|
+
if (obj.enabled === false) return void 0;
|
|
1703
|
+
return {
|
|
1704
|
+
enabled: true,
|
|
1705
|
+
depth: obj.depth ?? 3,
|
|
1706
|
+
skipH1: obj.skipH1 ?? false,
|
|
1707
|
+
prefix: obj.prefix ?? ""
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
return void 0;
|
|
1711
|
+
}
|
|
1712
|
+
function normalizeSecurity(raw) {
|
|
1713
|
+
if (!raw) return void 0;
|
|
1714
|
+
const sec = raw;
|
|
1715
|
+
if (!sec.userPassword && !sec.ownerPassword && !sec.permissions) return void 0;
|
|
1716
|
+
return {
|
|
1717
|
+
userPassword: sec.userPassword,
|
|
1718
|
+
ownerPassword: sec.ownerPassword,
|
|
1719
|
+
permissions: sec.permissions
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1411
1722
|
function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
|
|
1412
1723
|
const configMeta = userConfig.metadata || {};
|
|
1413
1724
|
const mergedMeta = { ...configMeta, ...frontmatter };
|
|
@@ -1450,6 +1761,15 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
|
|
|
1450
1761
|
const watermark = normalizeWatermark(rawWatermark);
|
|
1451
1762
|
const rawSignatures = mergedMeta.signatures || userConfig.signatures;
|
|
1452
1763
|
const signatures = normalizeSignatures(rawSignatures, tokenContext);
|
|
1764
|
+
const rawCover = mergedMeta.coverPage !== void 0 ? mergedMeta.coverPage : userConfig.coverPage;
|
|
1765
|
+
const coverPage = normalizeCoverPage(rawCover, tokenContext);
|
|
1766
|
+
const rawBack = mergedMeta.backCover !== void 0 ? mergedMeta.backCover : userConfig.backCover;
|
|
1767
|
+
const backCover = normalizeBackCover(rawBack, tokenContext);
|
|
1768
|
+
const rawNumberHeadings = mergedMeta.numberHeadings !== void 0 ? mergedMeta.numberHeadings : userConfig.numberHeadings;
|
|
1769
|
+
const numberHeadings = normalizeNumberHeadings(rawNumberHeadings);
|
|
1770
|
+
const rawSecurity = mergedMeta.security || userConfig.security;
|
|
1771
|
+
const security = normalizeSecurity(rawSecurity);
|
|
1772
|
+
const math = mergedMeta.math !== false && userConfig.math !== false;
|
|
1453
1773
|
const cssList = [];
|
|
1454
1774
|
const addCss = (item) => {
|
|
1455
1775
|
if (!item) return;
|
|
@@ -1479,6 +1799,11 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
|
|
|
1479
1799
|
toc,
|
|
1480
1800
|
signatures,
|
|
1481
1801
|
watermark,
|
|
1802
|
+
coverPage,
|
|
1803
|
+
backCover,
|
|
1804
|
+
numberHeadings,
|
|
1805
|
+
security,
|
|
1806
|
+
math,
|
|
1482
1807
|
css: cssList,
|
|
1483
1808
|
embedImages,
|
|
1484
1809
|
bundleHtml,
|
|
@@ -1486,6 +1811,45 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
|
|
|
1486
1811
|
};
|
|
1487
1812
|
}
|
|
1488
1813
|
|
|
1814
|
+
// src/core/math/mathRenderer.ts
|
|
1815
|
+
import katex from "katex";
|
|
1816
|
+
function renderMathToHtml(latex, displayMode = false) {
|
|
1817
|
+
try {
|
|
1818
|
+
return katex.renderToString(latex.trim(), {
|
|
1819
|
+
displayMode,
|
|
1820
|
+
throwOnError: false,
|
|
1821
|
+
output: "htmlAndMathml",
|
|
1822
|
+
strict: false
|
|
1823
|
+
});
|
|
1824
|
+
} catch {
|
|
1825
|
+
return `<span class="katex-fallback">${latex}</span>`;
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
var KATEX_INLINE_CSS = `
|
|
1829
|
+
.katex { font: normal 1.21em KaTeX_Main, Times New Roman, serif; line-height: 1.2; text-indent: 0; text-rendering: auto; border-color: currentColor; }
|
|
1830
|
+
.katex * { -ms-high-contrast-adjust: none !important; }
|
|
1831
|
+
.katex .katex-html { display: inline-block; }
|
|
1832
|
+
.katex .katex-mathml { clip: rect(1px, 1px, 1px, 1px); border: 0; height: 1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
|
|
1833
|
+
.katex-display { display: block; margin: 1em 0; text-align: center; }
|
|
1834
|
+
.katex-display > .katex { display: inline-block; text-align: initial; }
|
|
1835
|
+
.katex .base { position: relative; white-space: nowrap; width: min-content; }
|
|
1836
|
+
.katex .strut { display: inline-block; }
|
|
1837
|
+
.katex .mord { display: inline-block; }
|
|
1838
|
+
.katex .mbin { display: inline-block; }
|
|
1839
|
+
.katex .mrel { display: inline-block; }
|
|
1840
|
+
.katex .mopen { display: inline-block; }
|
|
1841
|
+
.katex .mclose { display: inline-block; }
|
|
1842
|
+
.katex .mpunct { display: inline-block; }
|
|
1843
|
+
.katex .minner { display: inline-block; }
|
|
1844
|
+
.katex .mop { display: inline-block; }
|
|
1845
|
+
.katex .frac-line { width: 100%; border-bottom-style: solid; }
|
|
1846
|
+
.katex .vlist-t { display: inline-table; table-layout: fixed; }
|
|
1847
|
+
.katex .vlist-r { display: table-row; }
|
|
1848
|
+
.katex .vlist { display: table-cell; vertical-align: bottom; position: relative; }
|
|
1849
|
+
.katex .msupsub { text-align: left; }
|
|
1850
|
+
.katex .sqrt > .root { margin-left: 0.27777778em; margin-right: -0.55555556em; }
|
|
1851
|
+
`;
|
|
1852
|
+
|
|
1489
1853
|
// src/core/html/htmlBuilder.ts
|
|
1490
1854
|
function escapeHtml(str) {
|
|
1491
1855
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -1528,6 +1892,15 @@ async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
|
|
|
1528
1892
|
result += `<code>${escapeHtml(span.content)}</code>`;
|
|
1529
1893
|
continue;
|
|
1530
1894
|
}
|
|
1895
|
+
if (span.type === "mathInline") {
|
|
1896
|
+
result += renderMathToHtml(span.content, false);
|
|
1897
|
+
continue;
|
|
1898
|
+
}
|
|
1899
|
+
if (span.type === "footnoteRef") {
|
|
1900
|
+
const id = escapeHtml(span.footnoteId || span.content);
|
|
1901
|
+
result += `<sup><a href="#fn-${id}" id="fnref-${id}" class="markforge-fnref">[${escapeHtml(span.content)}]</a></sup>`;
|
|
1902
|
+
continue;
|
|
1903
|
+
}
|
|
1531
1904
|
if (span.type === "htmlInline") {
|
|
1532
1905
|
result += span.content;
|
|
1533
1906
|
continue;
|
|
@@ -1536,66 +1909,9 @@ async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
|
|
|
1536
1909
|
}
|
|
1537
1910
|
return result;
|
|
1538
1911
|
}
|
|
1539
|
-
async function
|
|
1540
|
-
const resolved = resolveDocumentConfig(doc.metadata, config);
|
|
1541
|
-
const baseThemeCss = generateThemeCss(resolved.theme);
|
|
1542
|
-
let customCss = "";
|
|
1543
|
-
for (const cssPath of resolved.css) {
|
|
1544
|
-
const fullCssPath = path3.isAbsolute(cssPath) ? cssPath : path3.resolve(baseDir, cssPath);
|
|
1545
|
-
if (fs3.existsSync(fullCssPath)) {
|
|
1546
|
-
customCss += `
|
|
1547
|
-
/* Custom CSS: ${cssPath} */
|
|
1548
|
-
` + fs3.readFileSync(fullCssPath, "utf-8");
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
const inlinedCss = doc.inlinedStyles.join("\n");
|
|
1912
|
+
async function renderNodesToHtml(nodes, resolved, baseDir = process.cwd()) {
|
|
1552
1913
|
let bodyHtml = "";
|
|
1553
|
-
|
|
1554
|
-
bodyHtml += ` <header class="document-header">
|
|
1555
|
-
`;
|
|
1556
|
-
bodyHtml += ` <h1 class="document-title">${escapeHtml(resolved.title)}</h1>
|
|
1557
|
-
`;
|
|
1558
|
-
if (resolved.subtitle) {
|
|
1559
|
-
bodyHtml += ` <div class="document-subtitle">${escapeHtml(resolved.subtitle)}</div>
|
|
1560
|
-
`;
|
|
1561
|
-
}
|
|
1562
|
-
if (resolved.author || resolved.date || resolved.version) {
|
|
1563
|
-
bodyHtml += ` <div class="document-meta">
|
|
1564
|
-
`;
|
|
1565
|
-
if (resolved.author) {
|
|
1566
|
-
bodyHtml += ` <span>Author: ${escapeHtml(resolved.author)}</span>
|
|
1567
|
-
`;
|
|
1568
|
-
}
|
|
1569
|
-
if (resolved.version) {
|
|
1570
|
-
bodyHtml += ` <span>Version: ${escapeHtml(resolved.version)}</span>
|
|
1571
|
-
`;
|
|
1572
|
-
}
|
|
1573
|
-
if (resolved.date) {
|
|
1574
|
-
bodyHtml += ` <span>Date: ${escapeHtml(resolved.date)}</span>
|
|
1575
|
-
`;
|
|
1576
|
-
}
|
|
1577
|
-
bodyHtml += ` </div>
|
|
1578
|
-
`;
|
|
1579
|
-
}
|
|
1580
|
-
bodyHtml += ` </header>
|
|
1581
|
-
`;
|
|
1582
|
-
}
|
|
1583
|
-
if (resolved.toc && doc.tocEntries.length > 0) {
|
|
1584
|
-
bodyHtml += ` <nav class="table-of-contents">
|
|
1585
|
-
`;
|
|
1586
|
-
bodyHtml += ` <h2>Table of Contents</h2>
|
|
1587
|
-
<ul>
|
|
1588
|
-
`;
|
|
1589
|
-
for (const entry of doc.tocEntries) {
|
|
1590
|
-
const indent = " ".repeat(entry.level);
|
|
1591
|
-
bodyHtml += ` ${indent}<li><a href="#${entry.id}">${escapeHtml(entry.text)}</a></li>
|
|
1592
|
-
`;
|
|
1593
|
-
}
|
|
1594
|
-
bodyHtml += ` </ul>
|
|
1595
|
-
</nav>
|
|
1596
|
-
`;
|
|
1597
|
-
}
|
|
1598
|
-
for (const node of doc.nodes) {
|
|
1914
|
+
for (const node of nodes) {
|
|
1599
1915
|
if (node.type === "heading") {
|
|
1600
1916
|
const inner = await renderInlinesToHtml(node.inlines, baseDir);
|
|
1601
1917
|
bodyHtml += ` <h${node.level} id="${node.id}">${inner}</h${node.level}>
|
|
@@ -1605,6 +1921,26 @@ async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
|
|
|
1605
1921
|
if (node.type === "paragraph") {
|
|
1606
1922
|
const inner = await renderInlinesToHtml(node.inlines, baseDir);
|
|
1607
1923
|
bodyHtml += ` <p>${inner}</p>
|
|
1924
|
+
`;
|
|
1925
|
+
continue;
|
|
1926
|
+
}
|
|
1927
|
+
if (node.type === "mathBlock") {
|
|
1928
|
+
bodyHtml += ` <div class="math-block">${renderMathToHtml(node.text || "", true)}</div>
|
|
1929
|
+
`;
|
|
1930
|
+
continue;
|
|
1931
|
+
}
|
|
1932
|
+
if (node.type === "columns") {
|
|
1933
|
+
const cols = node.columnsCount || 2;
|
|
1934
|
+
const gap = node.columnGap || "1.5rem";
|
|
1935
|
+
let colChildrenHtml = "";
|
|
1936
|
+
for (const col of node.children || []) {
|
|
1937
|
+
const colInner = await renderNodesToHtml(col.children || [], resolved, baseDir);
|
|
1938
|
+
colChildrenHtml += ` <div class="markforge-col">
|
|
1939
|
+
${colInner} </div>
|
|
1940
|
+
`;
|
|
1941
|
+
}
|
|
1942
|
+
bodyHtml += ` <div class="markforge-columns" style="--cols: ${cols}; --col-gap: ${gap};">
|
|
1943
|
+
${colChildrenHtml} </div>
|
|
1608
1944
|
`;
|
|
1609
1945
|
continue;
|
|
1610
1946
|
}
|
|
@@ -1693,95 +2029,522 @@ ${escapeHtml(node.text || "")}
|
|
|
1693
2029
|
continue;
|
|
1694
2030
|
}
|
|
1695
2031
|
}
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
width: 100vw;
|
|
1706
|
-
height: 100vh;
|
|
1707
|
-
pointer-events: none;
|
|
1708
|
-
z-index: 0;
|
|
1709
|
-
user-select: none;
|
|
1710
|
-
-webkit-user-select: none;
|
|
2032
|
+
return bodyHtml;
|
|
2033
|
+
}
|
|
2034
|
+
async function renderCoverPageHtml(cover, baseDir = process.cwd()) {
|
|
2035
|
+
let logoHtml = "";
|
|
2036
|
+
if (cover.logo) {
|
|
2037
|
+
const resolvedLogo = await resolveImage(cover.logo, baseDir);
|
|
2038
|
+
const src = resolvedLogo ? resolvedLogo.dataUri : cover.logo;
|
|
2039
|
+
const widthStyle = cover.logoWidth ? `max-width: ${typeof cover.logoWidth === "number" ? cover.logoWidth + "px" : cover.logoWidth}; max-height: 80px; width: auto; height: auto;` : "max-height: 60px; max-width: 180px; width: auto; height: auto;";
|
|
2040
|
+
logoHtml = `<div class="cover-logo"><img src="${src}" alt="Logo" style="${widthStyle} object-fit: contain;" /></div>`;
|
|
1711
2041
|
}
|
|
1712
|
-
.
|
|
2042
|
+
const badgeHtml = cover.badge ? `<div class="cover-badge" style="${cover.badgeColor ? `background-color: ${cover.badgeColor};` : ""}${cover.badgeTextColor ? `color: ${cover.badgeTextColor};` : ""}">${escapeHtml(cover.badge)}</div>` : "";
|
|
2043
|
+
const titleHtml = `<h1 class="cover-title">${escapeHtml(cover.title)}</h1>`;
|
|
2044
|
+
const subtitleHtml = cover.subtitle ? `<div class="cover-subtitle">${escapeHtml(cover.subtitle)}</div>` : "";
|
|
2045
|
+
const metaItems = [];
|
|
2046
|
+
if (cover.company) metaItems.push(`<div class="cover-meta-item"><span class="cover-meta-label">Organization:</span> <span class="cover-meta-value">${escapeHtml(cover.company)}</span></div>`);
|
|
2047
|
+
if (cover.author) metaItems.push(`<div class="cover-meta-item"><span class="cover-meta-label">Author:</span> <span class="cover-meta-value">${escapeHtml(cover.author)}</span></div>`);
|
|
2048
|
+
if (cover.version) metaItems.push(`<div class="cover-meta-item"><span class="cover-meta-label">Version:</span> <span class="cover-meta-value">${escapeHtml(cover.version)}</span></div>`);
|
|
2049
|
+
if (cover.date) metaItems.push(`<div class="cover-meta-item"><span class="cover-meta-label">Date:</span> <span class="cover-meta-value">${escapeHtml(cover.date)}</span></div>`);
|
|
2050
|
+
const metaHtml = metaItems.length > 0 ? `<div class="cover-meta">${metaItems.join("\n")}</div>` : "";
|
|
2051
|
+
const footerHtml = cover.footerText ? `<div class="cover-footer-text">${escapeHtml(cover.footerText)}</div>` : "";
|
|
2052
|
+
const css = `
|
|
2053
|
+
.markforge-cover {
|
|
2054
|
+
min-height: 100vh;
|
|
2055
|
+
box-sizing: border-box;
|
|
2056
|
+
display: flex;
|
|
2057
|
+
flex-direction: column;
|
|
2058
|
+
justify-content: space-between;
|
|
2059
|
+
padding: 4rem 3.5rem;
|
|
2060
|
+
page-break-after: always;
|
|
2061
|
+
break-after: page;
|
|
1713
2062
|
position: relative;
|
|
1714
|
-
z-index:
|
|
2063
|
+
z-index: 2;
|
|
2064
|
+
background: ${cover.bgGradient || "#FFFFFF"};
|
|
2065
|
+
-webkit-print-color-adjust: exact;
|
|
2066
|
+
print-color-adjust: exact;
|
|
2067
|
+
color: ${cover.textColor || "#0F172A"};
|
|
2068
|
+
}
|
|
2069
|
+
.markforge-cover.cover-modern {
|
|
2070
|
+
border-top: 8px solid #0D998D;
|
|
2071
|
+
}
|
|
2072
|
+
.markforge-cover.cover-corporate-split {
|
|
2073
|
+
border-left: 12px solid #0D998D;
|
|
2074
|
+
}
|
|
2075
|
+
.markforge-cover.cover-card {
|
|
2076
|
+
background: #F8FAFC;
|
|
2077
|
+
}
|
|
2078
|
+
.cover-top {
|
|
2079
|
+
display: flex;
|
|
2080
|
+
justify-content: space-between;
|
|
2081
|
+
align-items: flex-start;
|
|
2082
|
+
width: 100%;
|
|
2083
|
+
}
|
|
2084
|
+
.cover-badge {
|
|
2085
|
+
display: inline-block;
|
|
2086
|
+
padding: 0.35rem 0.85rem;
|
|
2087
|
+
font-size: 0.78rem;
|
|
2088
|
+
font-weight: 700;
|
|
2089
|
+
letter-spacing: 0.08em;
|
|
2090
|
+
text-transform: uppercase;
|
|
2091
|
+
background-color: #ECFDFD;
|
|
2092
|
+
color: #0D998D;
|
|
2093
|
+
border-radius: 4px;
|
|
2094
|
+
border: 1px solid #33CDCF;
|
|
2095
|
+
}
|
|
2096
|
+
.cover-body {
|
|
2097
|
+
margin: auto 0;
|
|
2098
|
+
}
|
|
2099
|
+
.cover-title {
|
|
2100
|
+
font-size: 2.8rem;
|
|
2101
|
+
font-weight: 800;
|
|
2102
|
+
line-height: 1.15;
|
|
2103
|
+
margin: 0 0 1rem 0;
|
|
2104
|
+
color: inherit;
|
|
2105
|
+
}
|
|
2106
|
+
.cover-subtitle {
|
|
2107
|
+
font-size: 1.35rem;
|
|
2108
|
+
font-weight: 400;
|
|
2109
|
+
color: #64748B;
|
|
2110
|
+
margin: 0 0 2rem 0;
|
|
2111
|
+
line-height: 1.4;
|
|
2112
|
+
}
|
|
2113
|
+
.cover-meta {
|
|
2114
|
+
display: flex;
|
|
2115
|
+
flex-direction: column;
|
|
2116
|
+
gap: 0.5rem;
|
|
2117
|
+
border-top: 1.5px solid #E2E8F0;
|
|
2118
|
+
padding-top: 1.5rem;
|
|
2119
|
+
max-width: 480px;
|
|
2120
|
+
}
|
|
2121
|
+
.cover-meta-item {
|
|
2122
|
+
font-size: 0.92rem;
|
|
2123
|
+
display: flex;
|
|
2124
|
+
gap: 0.75rem;
|
|
2125
|
+
}
|
|
2126
|
+
.cover-meta-label {
|
|
2127
|
+
font-weight: 600;
|
|
2128
|
+
color: #64748B;
|
|
2129
|
+
min-width: 110px;
|
|
2130
|
+
}
|
|
2131
|
+
.cover-meta-value {
|
|
2132
|
+
font-weight: 500;
|
|
2133
|
+
color: #0F172A;
|
|
2134
|
+
}
|
|
2135
|
+
.cover-bottom {
|
|
2136
|
+
display: flex;
|
|
2137
|
+
justify-content: space-between;
|
|
2138
|
+
align-items: flex-end;
|
|
2139
|
+
font-size: 0.82rem;
|
|
2140
|
+
color: #94A3B8;
|
|
1715
2141
|
}
|
|
1716
2142
|
@media print {
|
|
1717
|
-
.
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
left: 0;
|
|
1721
|
-
width: 100vw;
|
|
2143
|
+
.markforge-cover {
|
|
2144
|
+
page-break-after: always;
|
|
2145
|
+
break-after: page;
|
|
1722
2146
|
height: 100vh;
|
|
2147
|
+
min-height: 100vh;
|
|
2148
|
+
max-height: 100vh;
|
|
2149
|
+
box-sizing: border-box;
|
|
2150
|
+
overflow: hidden;
|
|
2151
|
+
margin: 0;
|
|
1723
2152
|
-webkit-print-color-adjust: exact;
|
|
1724
2153
|
print-color-adjust: exact;
|
|
1725
2154
|
}
|
|
1726
2155
|
}
|
|
1727
2156
|
`;
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
ctx.rotate((${wm.rotate} * Math.PI) / 180);
|
|
1743
|
-
ctx.textAlign = 'center';
|
|
1744
|
-
ctx.textBaseline = 'middle';
|
|
1745
|
-
ctx.font = '900 ${wm.fontSize * 1.5}px system-ui, -apple-system, sans-serif';
|
|
1746
|
-
ctx.fillStyle = '${wm.color}';
|
|
1747
|
-
ctx.globalAlpha = ${wm.opacity};
|
|
1748
|
-
try { ctx.letterSpacing = '0.15em'; } catch(e) {}
|
|
1749
|
-
ctx.fillText(${JSON.stringify(wm.text.toUpperCase())}, 0, 0);
|
|
1750
|
-
var dataUrl = canvas.toDataURL('image/png');
|
|
1751
|
-
var wmEl = document.getElementById('markforge-watermark');
|
|
1752
|
-
if (wmEl) {
|
|
1753
|
-
wmEl.style.backgroundImage = 'url("' + dataUrl + '")';
|
|
1754
|
-
wmEl.style.backgroundRepeat = 'no-repeat';
|
|
1755
|
-
wmEl.style.backgroundPosition = 'center center';
|
|
1756
|
-
wmEl.style.backgroundSize = 'contain';
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
|
-
} catch(err) {}
|
|
1760
|
-
})();
|
|
1761
|
-
</script>
|
|
2157
|
+
const html = ` <section class="markforge-cover cover-${cover.preset}">
|
|
2158
|
+
<div class="cover-top">
|
|
2159
|
+
${logoHtml}
|
|
2160
|
+
${badgeHtml}
|
|
2161
|
+
</div>
|
|
2162
|
+
<div class="cover-body">
|
|
2163
|
+
${titleHtml}
|
|
2164
|
+
${subtitleHtml}
|
|
2165
|
+
${metaHtml}
|
|
2166
|
+
</div>
|
|
2167
|
+
<div class="cover-bottom">
|
|
2168
|
+
${footerHtml}
|
|
2169
|
+
</div>
|
|
2170
|
+
</section>
|
|
1762
2171
|
`;
|
|
2172
|
+
return { html, css };
|
|
2173
|
+
}
|
|
2174
|
+
async function renderBackCoverHtml(backCover, baseDir = process.cwd()) {
|
|
2175
|
+
let logoHtml = "";
|
|
2176
|
+
if (backCover.logo) {
|
|
2177
|
+
const resolved = await resolveImage(backCover.logo, baseDir);
|
|
2178
|
+
const src = resolved ? resolved.dataUri : backCover.logo;
|
|
2179
|
+
const widthStyle = backCover.logoWidth ? `style="width: ${typeof backCover.logoWidth === "number" ? `${backCover.logoWidth}px` : backCover.logoWidth}; max-width: 100%;"` : `style="max-width: 160px; height: auto;"`;
|
|
2180
|
+
logoHtml = `<div class="back-logo"><img src="${src}" alt="Brand Logo" ${widthStyle} /></div>`;
|
|
1763
2181
|
}
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
page-break-inside: avoid;
|
|
1780
|
-
break-inside: avoid;
|
|
2182
|
+
const badgeHtml = backCover.badge ? `<div class="back-badge" style="${backCover.badgeColor ? `background-color: ${backCover.badgeColor};` : ""}${backCover.badgeTextColor ? `color: ${backCover.badgeTextColor};` : ""}">${escapeHtml(backCover.badge)}</div>` : "";
|
|
2183
|
+
const titleHtml = `<h1 class="back-title">${escapeHtml(backCover.title)}</h1>`;
|
|
2184
|
+
const subtitleHtml = backCover.subtitle ? `<div class="back-subtitle">${escapeHtml(backCover.subtitle)}</div>` : "";
|
|
2185
|
+
const contactItems = [];
|
|
2186
|
+
if (backCover.company) contactItems.push(`<div class="back-contact-item"><span class="back-contact-label">Organization:</span> <span class="back-contact-value">${escapeHtml(backCover.company)}</span></div>`);
|
|
2187
|
+
if (backCover.address) contactItems.push(`<div class="back-contact-item"><span class="back-contact-label">Address:</span> <span class="back-contact-value">${escapeHtml(backCover.address)}</span></div>`);
|
|
2188
|
+
if (backCover.email) contactItems.push(`<div class="back-contact-item"><span class="back-contact-label">Email:</span> <a href="mailto:${escapeHtml(backCover.email)}" class="back-contact-link">${escapeHtml(backCover.email)}</a></div>`);
|
|
2189
|
+
if (backCover.phone) contactItems.push(`<div class="back-contact-item"><span class="back-contact-label">Phone:</span> <span class="back-contact-value">${escapeHtml(backCover.phone)}</span></div>`);
|
|
2190
|
+
if (backCover.website) contactItems.push(`<div class="back-contact-item"><span class="back-contact-label">Website:</span> <a href="${escapeHtml(backCover.website)}" target="_blank" class="back-contact-link">${escapeHtml(backCover.website)}</a></div>`);
|
|
2191
|
+
if (backCover.social) {
|
|
2192
|
+
for (const [network, url] of Object.entries(backCover.social)) {
|
|
2193
|
+
if (url) {
|
|
2194
|
+
contactItems.push(`<div class="back-contact-item"><span class="back-contact-label">${escapeHtml(network.toUpperCase())}:</span> <a href="${escapeHtml(url)}" target="_blank" class="back-contact-link">${escapeHtml(url)}</a></div>`);
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
1781
2197
|
}
|
|
1782
|
-
.
|
|
1783
|
-
|
|
1784
|
-
|
|
2198
|
+
const contactHtml = contactItems.length > 0 ? `<div class="back-contact-grid">${contactItems.join("\n")}</div>` : "";
|
|
2199
|
+
const copyrightHtml = backCover.copyright ? `<div class="back-copyright">${escapeHtml(backCover.copyright)}</div>` : "";
|
|
2200
|
+
const isDark = backCover.preset === "corporate";
|
|
2201
|
+
const css = `
|
|
2202
|
+
.markforge-back-cover {
|
|
2203
|
+
min-height: 100vh;
|
|
2204
|
+
box-sizing: border-box;
|
|
2205
|
+
display: flex;
|
|
2206
|
+
flex-direction: column;
|
|
2207
|
+
justify-content: space-between;
|
|
2208
|
+
padding: 4rem 3.5rem;
|
|
2209
|
+
page-break-before: always;
|
|
2210
|
+
break-before: page;
|
|
2211
|
+
position: relative;
|
|
2212
|
+
z-index: 2;
|
|
2213
|
+
background: ${backCover.bgGradient || (isDark ? "#0F172A" : "#FFFFFF")};
|
|
2214
|
+
color: ${backCover.textColor || (isDark ? "#F8FAFC" : "#0F172A")};
|
|
2215
|
+
}
|
|
2216
|
+
.markforge-back-cover.back-modern {
|
|
2217
|
+
border-bottom: 8px solid #0D998D;
|
|
2218
|
+
}
|
|
2219
|
+
.markforge-back-cover.back-corporate {
|
|
2220
|
+
border-left: 12px solid #33CDCF;
|
|
2221
|
+
}
|
|
2222
|
+
.markforge-back-cover.back-card {
|
|
2223
|
+
background: #F8FAFC;
|
|
2224
|
+
}
|
|
2225
|
+
.back-top {
|
|
2226
|
+
display: flex;
|
|
2227
|
+
justify-content: space-between;
|
|
2228
|
+
align-items: flex-start;
|
|
2229
|
+
width: 100%;
|
|
2230
|
+
}
|
|
2231
|
+
.back-badge {
|
|
2232
|
+
display: inline-block;
|
|
2233
|
+
padding: 0.35rem 0.85rem;
|
|
2234
|
+
font-size: 0.78rem;
|
|
2235
|
+
font-weight: 700;
|
|
2236
|
+
letter-spacing: 0.08em;
|
|
2237
|
+
text-transform: uppercase;
|
|
2238
|
+
background-color: #ECFDFD;
|
|
2239
|
+
color: #0D998D;
|
|
2240
|
+
border-radius: 4px;
|
|
2241
|
+
border: 1px solid #33CDCF;
|
|
2242
|
+
}
|
|
2243
|
+
.back-body {
|
|
2244
|
+
margin: auto 0;
|
|
2245
|
+
}
|
|
2246
|
+
.back-title {
|
|
2247
|
+
font-size: 2.6rem;
|
|
2248
|
+
font-weight: 800;
|
|
2249
|
+
line-height: 1.15;
|
|
2250
|
+
margin: 0 0 0.75rem 0;
|
|
2251
|
+
color: inherit;
|
|
2252
|
+
}
|
|
2253
|
+
.back-subtitle {
|
|
2254
|
+
font-size: 1.25rem;
|
|
2255
|
+
font-weight: 400;
|
|
2256
|
+
color: ${isDark ? "#94A3B8" : "#64748B"};
|
|
2257
|
+
margin: 0 0 2rem 0;
|
|
2258
|
+
line-height: 1.4;
|
|
2259
|
+
}
|
|
2260
|
+
.back-contact-grid {
|
|
2261
|
+
display: flex;
|
|
2262
|
+
flex-direction: column;
|
|
2263
|
+
gap: 0.6rem;
|
|
2264
|
+
border-top: 1.5px solid ${isDark ? "#334155" : "#E2E8F0"};
|
|
2265
|
+
padding-top: 1.5rem;
|
|
2266
|
+
max-width: 540px;
|
|
2267
|
+
}
|
|
2268
|
+
.back-contact-item {
|
|
2269
|
+
font-size: 0.92rem;
|
|
2270
|
+
display: flex;
|
|
2271
|
+
gap: 0.75rem;
|
|
2272
|
+
}
|
|
2273
|
+
.back-contact-label {
|
|
2274
|
+
font-weight: 600;
|
|
2275
|
+
color: ${isDark ? "#94A3B8" : "#64748B"};
|
|
2276
|
+
min-width: 110px;
|
|
2277
|
+
}
|
|
2278
|
+
.back-contact-link {
|
|
2279
|
+
color: #0D998D;
|
|
2280
|
+
text-decoration: none;
|
|
2281
|
+
font-weight: 600;
|
|
2282
|
+
}
|
|
2283
|
+
.back-contact-link:hover {
|
|
2284
|
+
text-decoration: underline;
|
|
2285
|
+
}
|
|
2286
|
+
.back-copyright {
|
|
2287
|
+
font-size: 0.82rem;
|
|
2288
|
+
color: ${isDark ? "#64748B" : "#94A3B8"};
|
|
2289
|
+
border-top: 1px solid ${isDark ? "#1E293B" : "#F1F5F9"};
|
|
2290
|
+
padding-top: 1rem;
|
|
2291
|
+
margin-top: 2rem;
|
|
2292
|
+
}
|
|
2293
|
+
@media print {
|
|
2294
|
+
.markforge-back-cover {
|
|
2295
|
+
page: back-cover-page;
|
|
2296
|
+
page-break-before: always;
|
|
2297
|
+
break-before: page;
|
|
2298
|
+
page-break-after: avoid;
|
|
2299
|
+
break-after: avoid;
|
|
2300
|
+
min-height: 100vh;
|
|
2301
|
+
height: 100vh;
|
|
2302
|
+
max-height: 100vh;
|
|
2303
|
+
margin: 0;
|
|
2304
|
+
box-sizing: border-box;
|
|
2305
|
+
overflow: hidden;
|
|
2306
|
+
-webkit-print-color-adjust: exact;
|
|
2307
|
+
print-color-adjust: exact;
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
`;
|
|
2311
|
+
const html = ` <section class="markforge-back-cover back-${backCover.preset}">
|
|
2312
|
+
<div class="back-top">
|
|
2313
|
+
${logoHtml}
|
|
2314
|
+
${badgeHtml}
|
|
2315
|
+
</div>
|
|
2316
|
+
<div class="back-body">
|
|
2317
|
+
${titleHtml}
|
|
2318
|
+
${subtitleHtml}
|
|
2319
|
+
${contactHtml}
|
|
2320
|
+
</div>
|
|
2321
|
+
${copyrightHtml}
|
|
2322
|
+
</section>
|
|
2323
|
+
`;
|
|
2324
|
+
return { html, css };
|
|
2325
|
+
}
|
|
2326
|
+
async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
|
|
2327
|
+
var _a;
|
|
2328
|
+
const resolved = resolveDocumentConfig(doc.metadata, config);
|
|
2329
|
+
const baseThemeCss = generateThemeCss(resolved.theme);
|
|
2330
|
+
let customCss = "";
|
|
2331
|
+
for (const cssPath of resolved.css) {
|
|
2332
|
+
const fullCssPath = path3.isAbsolute(cssPath) ? cssPath : path3.resolve(baseDir, cssPath);
|
|
2333
|
+
if (fs3.existsSync(fullCssPath)) {
|
|
2334
|
+
customCss += `
|
|
2335
|
+
/* Custom CSS: ${cssPath} */
|
|
2336
|
+
` + fs3.readFileSync(fullCssPath, "utf-8");
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
const inlinedCss = doc.inlinedStyles.join("\n");
|
|
2340
|
+
const extraCss = `
|
|
2341
|
+
.markforge-columns {
|
|
2342
|
+
display: grid;
|
|
2343
|
+
grid-template-columns: repeat(var(--cols, 2), minmax(0, 1fr));
|
|
2344
|
+
gap: var(--col-gap, 1.5rem);
|
|
2345
|
+
margin: 1.5rem 0;
|
|
2346
|
+
}
|
|
2347
|
+
.markforge-col {
|
|
2348
|
+
min-width: 0;
|
|
2349
|
+
}
|
|
2350
|
+
.markforge-fnref {
|
|
2351
|
+
text-decoration: none;
|
|
2352
|
+
font-size: 0.8em;
|
|
2353
|
+
vertical-align: super;
|
|
2354
|
+
color: #0D998D;
|
|
2355
|
+
font-weight: 700;
|
|
2356
|
+
}
|
|
2357
|
+
.markforge-footnotes {
|
|
2358
|
+
margin-top: 3rem;
|
|
2359
|
+
padding-top: 1rem;
|
|
2360
|
+
font-size: 0.88rem;
|
|
2361
|
+
color: #64748B;
|
|
2362
|
+
}
|
|
2363
|
+
.markforge-footnotes hr {
|
|
2364
|
+
border: 0;
|
|
2365
|
+
border-top: 1px solid #E2E8F0;
|
|
2366
|
+
margin-bottom: 1rem;
|
|
2367
|
+
}
|
|
2368
|
+
.markforge-fn-return {
|
|
2369
|
+
text-decoration: none;
|
|
2370
|
+
color: #0D998D;
|
|
2371
|
+
}
|
|
2372
|
+
.math-block {
|
|
2373
|
+
margin: 1.5rem 0;
|
|
2374
|
+
text-align: center;
|
|
2375
|
+
overflow-x: auto;
|
|
2376
|
+
}
|
|
2377
|
+
`;
|
|
2378
|
+
let coverHtml = "";
|
|
2379
|
+
let coverCss = "";
|
|
2380
|
+
if (resolved.coverPage && resolved.coverPage.enabled) {
|
|
2381
|
+
const coverRes = await renderCoverPageHtml(resolved.coverPage, baseDir);
|
|
2382
|
+
coverHtml = coverRes.html;
|
|
2383
|
+
coverCss = coverRes.css;
|
|
2384
|
+
}
|
|
2385
|
+
let backHtml = "";
|
|
2386
|
+
let backCss = "";
|
|
2387
|
+
if (resolved.backCover && resolved.backCover.enabled) {
|
|
2388
|
+
const backRes = await renderBackCoverHtml(resolved.backCover, baseDir);
|
|
2389
|
+
backHtml = backRes.html;
|
|
2390
|
+
backCss = backRes.css;
|
|
2391
|
+
}
|
|
2392
|
+
let bodyHtml = "";
|
|
2393
|
+
if (resolved.title && !((_a = resolved.coverPage) == null ? void 0 : _a.enabled)) {
|
|
2394
|
+
bodyHtml += ` <header class="document-header">
|
|
2395
|
+
`;
|
|
2396
|
+
bodyHtml += ` <h1 class="document-title">${escapeHtml(resolved.title)}</h1>
|
|
2397
|
+
`;
|
|
2398
|
+
if (resolved.subtitle) {
|
|
2399
|
+
bodyHtml += ` <div class="document-subtitle">${escapeHtml(resolved.subtitle)}</div>
|
|
2400
|
+
`;
|
|
2401
|
+
}
|
|
2402
|
+
if (resolved.author || resolved.date || resolved.version) {
|
|
2403
|
+
bodyHtml += ` <div class="document-meta">
|
|
2404
|
+
`;
|
|
2405
|
+
if (resolved.author) {
|
|
2406
|
+
bodyHtml += ` <span>Author: ${escapeHtml(resolved.author)}</span>
|
|
2407
|
+
`;
|
|
2408
|
+
}
|
|
2409
|
+
if (resolved.version) {
|
|
2410
|
+
bodyHtml += ` <span>Version: ${escapeHtml(resolved.version)}</span>
|
|
2411
|
+
`;
|
|
2412
|
+
}
|
|
2413
|
+
if (resolved.date) {
|
|
2414
|
+
bodyHtml += ` <span>Date: ${escapeHtml(resolved.date)}</span>
|
|
2415
|
+
`;
|
|
2416
|
+
}
|
|
2417
|
+
bodyHtml += ` </div>
|
|
2418
|
+
`;
|
|
2419
|
+
}
|
|
2420
|
+
bodyHtml += ` </header>
|
|
2421
|
+
`;
|
|
2422
|
+
}
|
|
2423
|
+
if (resolved.toc && doc.tocEntries.length > 0) {
|
|
2424
|
+
bodyHtml += ` <nav class="table-of-contents">
|
|
2425
|
+
`;
|
|
2426
|
+
bodyHtml += ` <h2>Table of Contents</h2>
|
|
2427
|
+
<ul>
|
|
2428
|
+
`;
|
|
2429
|
+
for (const entry of doc.tocEntries) {
|
|
2430
|
+
const indent = " ".repeat(entry.level);
|
|
2431
|
+
bodyHtml += ` ${indent}<li><a href="#${entry.id}">${escapeHtml(entry.text)}</a></li>
|
|
2432
|
+
`;
|
|
2433
|
+
}
|
|
2434
|
+
bodyHtml += ` </ul>
|
|
2435
|
+
</nav>
|
|
2436
|
+
`;
|
|
2437
|
+
}
|
|
2438
|
+
bodyHtml += await renderNodesToHtml(doc.nodes, resolved, baseDir);
|
|
2439
|
+
let footnotesHtml = "";
|
|
2440
|
+
if (doc.footnoteDefs && doc.footnoteDefs.length > 0) {
|
|
2441
|
+
let fnListHtml = "";
|
|
2442
|
+
for (const def of doc.footnoteDefs) {
|
|
2443
|
+
const defInner = await renderInlinesToHtml(def.inlines, baseDir);
|
|
2444
|
+
fnListHtml += ` <li id="fn-${escapeHtml(def.id)}">${defInner} <a href="#fnref-${escapeHtml(def.id)}" class="markforge-fn-return">↩</a></li>
|
|
2445
|
+
`;
|
|
2446
|
+
}
|
|
2447
|
+
footnotesHtml = `
|
|
2448
|
+
<footer class="markforge-footnotes">
|
|
2449
|
+
<hr />
|
|
2450
|
+
<ol>
|
|
2451
|
+
${fnListHtml} </ol>
|
|
2452
|
+
</footer>
|
|
2453
|
+
`;
|
|
2454
|
+
}
|
|
2455
|
+
let watermarkCss = "";
|
|
2456
|
+
let watermarkHtml = "";
|
|
2457
|
+
if (resolved.watermark) {
|
|
2458
|
+
const wm = resolved.watermark;
|
|
2459
|
+
watermarkCss = `
|
|
2460
|
+
.document-watermark {
|
|
2461
|
+
position: fixed;
|
|
2462
|
+
top: 0;
|
|
2463
|
+
left: 0;
|
|
2464
|
+
right: 0;
|
|
2465
|
+
bottom: 0;
|
|
2466
|
+
width: 100%;
|
|
2467
|
+
height: 100%;
|
|
2468
|
+
pointer-events: none;
|
|
2469
|
+
z-index: 0;
|
|
2470
|
+
user-select: none;
|
|
2471
|
+
-webkit-user-select: none;
|
|
2472
|
+
-webkit-print-color-adjust: exact;
|
|
2473
|
+
print-color-adjust: exact;
|
|
2474
|
+
}
|
|
2475
|
+
.document-container {
|
|
2476
|
+
position: relative;
|
|
2477
|
+
z-index: 1;
|
|
2478
|
+
}
|
|
2479
|
+
@media print {
|
|
2480
|
+
.document-watermark {
|
|
2481
|
+
position: fixed;
|
|
2482
|
+
top: 0;
|
|
2483
|
+
left: 0;
|
|
2484
|
+
right: 0;
|
|
2485
|
+
bottom: 0;
|
|
2486
|
+
width: 100vw;
|
|
2487
|
+
height: 100vh;
|
|
2488
|
+
pointer-events: none;
|
|
2489
|
+
z-index: 0;
|
|
2490
|
+
-webkit-print-color-adjust: exact;
|
|
2491
|
+
print-color-adjust: exact;
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
`;
|
|
2495
|
+
watermarkHtml = ` <div id="markforge-watermark" class="document-watermark" aria-hidden="true"></div>
|
|
2496
|
+
<script>
|
|
2497
|
+
(function() {
|
|
2498
|
+
try {
|
|
2499
|
+
var canvas = document.createElement('canvas');
|
|
2500
|
+
var dpr = 2;
|
|
2501
|
+
var width = 1200;
|
|
2502
|
+
var height = 1600;
|
|
2503
|
+
canvas.width = width * dpr;
|
|
2504
|
+
canvas.height = height * dpr;
|
|
2505
|
+
var ctx = canvas.getContext('2d');
|
|
2506
|
+
if (ctx) {
|
|
2507
|
+
ctx.scale(dpr, dpr);
|
|
2508
|
+
ctx.translate(width / 2, height / 2);
|
|
2509
|
+
ctx.rotate((-Math.abs(${wm.rotate || 45}) * Math.PI) / 180);
|
|
2510
|
+
ctx.textAlign = 'center';
|
|
2511
|
+
ctx.textBaseline = 'middle';
|
|
2512
|
+
ctx.font = '900 ${wm.fontSize * 1.5}px system-ui, -apple-system, sans-serif';
|
|
2513
|
+
ctx.fillStyle = '${wm.color}';
|
|
2514
|
+
ctx.globalAlpha = ${wm.opacity};
|
|
2515
|
+
try { ctx.letterSpacing = '0.15em'; } catch(e) {}
|
|
2516
|
+
ctx.fillText(${JSON.stringify(wm.text.toUpperCase())}, 0, 0);
|
|
2517
|
+
var dataUrl = canvas.toDataURL('image/png');
|
|
2518
|
+
var wmEl = document.getElementById('markforge-watermark');
|
|
2519
|
+
if (wmEl) {
|
|
2520
|
+
wmEl.style.backgroundImage = 'url("' + dataUrl + '")';
|
|
2521
|
+
wmEl.style.backgroundRepeat = 'no-repeat';
|
|
2522
|
+
wmEl.style.backgroundPosition = 'center center';
|
|
2523
|
+
wmEl.style.backgroundSize = 'contain';
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
} catch(err) {}
|
|
2527
|
+
})();
|
|
2528
|
+
</script>
|
|
2529
|
+
`;
|
|
2530
|
+
}
|
|
2531
|
+
let signaturesHtml = "";
|
|
2532
|
+
let signaturesCss = "";
|
|
2533
|
+
if (resolved.signatures && resolved.signatures.items.length > 0) {
|
|
2534
|
+
const sig = resolved.signatures;
|
|
2535
|
+
const numItems = sig.items.length;
|
|
2536
|
+
signaturesCss = `
|
|
2537
|
+
.markforge-signatures {
|
|
2538
|
+
margin-top: ${sig.spacingBefore};
|
|
2539
|
+
display: grid;
|
|
2540
|
+
grid-template-columns: ${numItems === 1 ? sig.align === "left" ? "minmax(200px, 280px) 1fr" : sig.align === "center" ? "1fr minmax(200px, 280px) 1fr" : "1fr minmax(200px, 280px)" : `repeat(${numItems}, minmax(0, 1fr))`};
|
|
2541
|
+
gap: 2rem;
|
|
2542
|
+
page-break-inside: avoid;
|
|
2543
|
+
break-inside: avoid;
|
|
2544
|
+
}
|
|
2545
|
+
.markforge-signature-card {
|
|
2546
|
+
${numItems === 1 && sig.align === "center" ? "grid-column: 2;" : ""}
|
|
2547
|
+
${numItems === 1 && sig.align === "right" ? "grid-column: 2;" : ""}
|
|
1785
2548
|
display: flex;
|
|
1786
2549
|
flex-direction: column;
|
|
1787
2550
|
${sig.style === "box" ? `border: 1px solid ${sig.borderColor}; border-radius: 6px; padding: 14px 18px; background-color: var(--mf-card-bg, #F8FAFC);` : ""}
|
|
@@ -1881,6 +2644,10 @@ ${itemCards}
|
|
|
1881
2644
|
<style>
|
|
1882
2645
|
${THEME_COMPONENTS}
|
|
1883
2646
|
${baseThemeCss}
|
|
2647
|
+
${KATEX_INLINE_CSS}
|
|
2648
|
+
${extraCss}
|
|
2649
|
+
${coverCss}
|
|
2650
|
+
${backCss}
|
|
1884
2651
|
${customCss}
|
|
1885
2652
|
${inlinedCss}
|
|
1886
2653
|
${watermarkCss}
|
|
@@ -1888,14 +2655,95 @@ ${signaturesCss}
|
|
|
1888
2655
|
</style>
|
|
1889
2656
|
</head>
|
|
1890
2657
|
<body>
|
|
1891
|
-
${watermarkHtml} <div class="document-container">
|
|
1892
|
-
${bodyHtml}${signaturesHtml} </div>
|
|
2658
|
+
${watermarkHtml}${coverHtml} <div class="document-container">
|
|
2659
|
+
${bodyHtml}${footnotesHtml}${signaturesHtml} </div>
|
|
1893
2660
|
${mermaidScript}
|
|
1894
|
-
</body>
|
|
2661
|
+
${backHtml}</body>
|
|
1895
2662
|
</html>`;
|
|
1896
2663
|
}
|
|
1897
2664
|
|
|
1898
2665
|
// src/core/pdf/pdfBuilder.ts
|
|
2666
|
+
function escapeXml(str) {
|
|
2667
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2668
|
+
}
|
|
2669
|
+
function generateWatermarkPngBuffer(chromePath, wm) {
|
|
2670
|
+
const tmpHtml = path4.join(os.tmpdir(), `markforge-wm-${Date.now()}-${Math.random().toString(36).slice(2)}.html`);
|
|
2671
|
+
const tmpPng = path4.join(os.tmpdir(), `markforge-wm-${Date.now()}-${Math.random().toString(36).slice(2)}.png`);
|
|
2672
|
+
try {
|
|
2673
|
+
const text = escapeXml(wm.text.toUpperCase());
|
|
2674
|
+
const fontSize = (wm.fontSize || 52) * 1.5;
|
|
2675
|
+
const color = wm.color || "#E11D48";
|
|
2676
|
+
const opacity = wm.opacity !== void 0 ? wm.opacity : 0.12;
|
|
2677
|
+
const rotate = wm.rotate !== void 0 ? wm.rotate : -45;
|
|
2678
|
+
const html = `<!DOCTYPE html>
|
|
2679
|
+
<html>
|
|
2680
|
+
<head>
|
|
2681
|
+
<meta charset="utf-8">
|
|
2682
|
+
<style>
|
|
2683
|
+
html, body {
|
|
2684
|
+
margin: 0;
|
|
2685
|
+
padding: 0;
|
|
2686
|
+
width: 1200px;
|
|
2687
|
+
height: 1600px;
|
|
2688
|
+
background: transparent;
|
|
2689
|
+
overflow: hidden;
|
|
2690
|
+
}
|
|
2691
|
+
.wm-box {
|
|
2692
|
+
width: 1200px;
|
|
2693
|
+
height: 1600px;
|
|
2694
|
+
display: flex;
|
|
2695
|
+
align-items: center;
|
|
2696
|
+
justify-content: center;
|
|
2697
|
+
transform: rotate(${rotate}deg);
|
|
2698
|
+
}
|
|
2699
|
+
.wm-text {
|
|
2700
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
2701
|
+
font-weight: 900;
|
|
2702
|
+
font-size: ${fontSize}px;
|
|
2703
|
+
color: ${color};
|
|
2704
|
+
opacity: ${opacity};
|
|
2705
|
+
letter-spacing: 0.15em;
|
|
2706
|
+
text-transform: uppercase;
|
|
2707
|
+
white-space: nowrap;
|
|
2708
|
+
}
|
|
2709
|
+
</style>
|
|
2710
|
+
</head>
|
|
2711
|
+
<body>
|
|
2712
|
+
<div class="wm-box"><span class="wm-text">${text}</span></div>
|
|
2713
|
+
</body>
|
|
2714
|
+
</html>`;
|
|
2715
|
+
fs4.writeFileSync(tmpHtml, html, "utf8");
|
|
2716
|
+
const fileUrl = pathToFileURL2(tmpHtml).href;
|
|
2717
|
+
const isWin = process.platform === "win32";
|
|
2718
|
+
spawnSync(
|
|
2719
|
+
chromePath,
|
|
2720
|
+
[
|
|
2721
|
+
"--headless=new",
|
|
2722
|
+
"--disable-gpu",
|
|
2723
|
+
"--disable-sync",
|
|
2724
|
+
"--disable-extensions",
|
|
2725
|
+
...isWin ? [] : ["--no-sandbox", "--disable-setuid-sandbox"],
|
|
2726
|
+
`--screenshot=${tmpPng}`,
|
|
2727
|
+
"--window-size=1200,1600",
|
|
2728
|
+
"--default-background-color=00000000",
|
|
2729
|
+
fileUrl
|
|
2730
|
+
],
|
|
2731
|
+
{ timeout: 15e3, windowsHide: true }
|
|
2732
|
+
);
|
|
2733
|
+
if (fs4.existsSync(tmpPng) && fs4.statSync(tmpPng).size > 0) {
|
|
2734
|
+
return fs4.readFileSync(tmpPng);
|
|
2735
|
+
}
|
|
2736
|
+
return null;
|
|
2737
|
+
} catch {
|
|
2738
|
+
return null;
|
|
2739
|
+
} finally {
|
|
2740
|
+
try {
|
|
2741
|
+
if (fs4.existsSync(tmpHtml)) fs4.unlinkSync(tmpHtml);
|
|
2742
|
+
if (fs4.existsSync(tmpPng)) fs4.unlinkSync(tmpPng);
|
|
2743
|
+
} catch {
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
1899
2747
|
function findChromeExecutable() {
|
|
1900
2748
|
if (process.env.CHROME_PATH && fs4.existsSync(process.env.CHROME_PATH)) {
|
|
1901
2749
|
return process.env.CHROME_PATH;
|
|
@@ -1960,7 +2808,7 @@ function findChromeExecutable() {
|
|
|
1960
2808
|
return null;
|
|
1961
2809
|
}
|
|
1962
2810
|
function injectPagedMediaStyles(html, config, metadata) {
|
|
1963
|
-
var _a, _b, _c, _d, _e, _f;
|
|
2811
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1964
2812
|
const resolved = resolveDocumentConfig(metadata || {}, config);
|
|
1965
2813
|
const size = resolved.paperSize;
|
|
1966
2814
|
const orientation = resolved.orientation;
|
|
@@ -2004,6 +2852,38 @@ function injectPagedMediaStyles(html, config, metadata) {
|
|
|
2004
2852
|
${fontStyle}
|
|
2005
2853
|
}`;
|
|
2006
2854
|
};
|
|
2855
|
+
const coverPageCss = ((_a = resolved.coverPage) == null ? void 0 : _a.enabled) ? `
|
|
2856
|
+
@page :first {
|
|
2857
|
+
margin-top: 0;
|
|
2858
|
+
margin-bottom: 0;
|
|
2859
|
+
margin-left: 0;
|
|
2860
|
+
margin-right: 0;
|
|
2861
|
+
background-image: none !important;
|
|
2862
|
+
@top-left { content: none; }
|
|
2863
|
+
@top-center { content: none; }
|
|
2864
|
+
@top-right { content: none; }
|
|
2865
|
+
@bottom-left { content: none; }
|
|
2866
|
+
@bottom-center { content: none; }
|
|
2867
|
+
@bottom-right { content: none; }
|
|
2868
|
+
}` : "";
|
|
2869
|
+
const backCoverCss = ((_b = resolved.backCover) == null ? void 0 : _b.enabled) ? `
|
|
2870
|
+
@page back-cover-page {
|
|
2871
|
+
size: ${size} ${orientation};
|
|
2872
|
+
margin: 0;
|
|
2873
|
+
background-image: none !important;
|
|
2874
|
+
@top-left { content: none; }
|
|
2875
|
+
@top-center { content: none; }
|
|
2876
|
+
@top-right { content: none; }
|
|
2877
|
+
@bottom-left { content: none; }
|
|
2878
|
+
@bottom-center { content: none; }
|
|
2879
|
+
@bottom-right { content: none; }
|
|
2880
|
+
}
|
|
2881
|
+
.markforge-back-cover {
|
|
2882
|
+
page: back-cover-page;
|
|
2883
|
+
min-height: 100vh;
|
|
2884
|
+
height: 100vh;
|
|
2885
|
+
box-sizing: border-box;
|
|
2886
|
+
}` : "";
|
|
2007
2887
|
const pagedCss = `
|
|
2008
2888
|
@page {
|
|
2009
2889
|
size: ${size} ${orientation};
|
|
@@ -2011,15 +2891,18 @@ function injectPagedMediaStyles(html, config, metadata) {
|
|
|
2011
2891
|
margin-bottom: ${bottom};
|
|
2012
2892
|
margin-left: ${left};
|
|
2013
2893
|
margin-right: ${right};
|
|
2014
|
-
${buildZoneCss("top-left", (
|
|
2015
|
-
${buildZoneCss("top-center", (
|
|
2016
|
-
${buildZoneCss("top-right", (
|
|
2017
|
-
${buildZoneCss("bottom-left", (
|
|
2018
|
-
${buildZoneCss("bottom-center", (
|
|
2019
|
-
${buildZoneCss("bottom-right", (
|
|
2894
|
+
${buildZoneCss("top-left", (_c = resolved.header) == null ? void 0 : _c.left)}
|
|
2895
|
+
${buildZoneCss("top-center", (_d = resolved.header) == null ? void 0 : _d.center)}
|
|
2896
|
+
${buildZoneCss("top-right", (_e = resolved.header) == null ? void 0 : _e.right)}
|
|
2897
|
+
${buildZoneCss("bottom-left", (_f = resolved.footer) == null ? void 0 : _f.left)}
|
|
2898
|
+
${buildZoneCss("bottom-center", (_g = resolved.footer) == null ? void 0 : _g.center)}
|
|
2899
|
+
${buildZoneCss("bottom-right", (_h = resolved.footer) == null ? void 0 : _h.right, true)}
|
|
2020
2900
|
}
|
|
2901
|
+
${coverPageCss}
|
|
2902
|
+
${backCoverCss}
|
|
2021
2903
|
@media print {
|
|
2022
2904
|
body { padding: 0; }
|
|
2905
|
+
.document-watermark { display: none !important; }
|
|
2023
2906
|
h1, h2, h3, pre, table, blockquote, .callout {
|
|
2024
2907
|
break-inside: avoid;
|
|
2025
2908
|
}
|
|
@@ -2067,6 +2950,7 @@ startxref
|
|
|
2067
2950
|
return Buffer.from(pdfBody, "utf-8");
|
|
2068
2951
|
}
|
|
2069
2952
|
async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
|
|
2953
|
+
var _a, _b, _c;
|
|
2070
2954
|
const baseHtml = await buildHtmlDocument(doc, config, baseDir);
|
|
2071
2955
|
const pagedHtml = injectPagedMediaStyles(baseHtml, config, doc.metadata);
|
|
2072
2956
|
const chromePath = findChromeExecutable();
|
|
@@ -2076,6 +2960,7 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
|
|
|
2076
2960
|
const tmpHtml = path4.join(tmpDir, `markforge_${tmpId}.html`);
|
|
2077
2961
|
const tmpPdf = path4.join(tmpDir, `markforge_${tmpId}.pdf`);
|
|
2078
2962
|
const tmpProfile = path4.join(tmpDir, `markforge_prof_${tmpId}`);
|
|
2963
|
+
const isWin = process.platform === "win32";
|
|
2079
2964
|
const isolatedFlags = [
|
|
2080
2965
|
`--user-data-dir=${tmpProfile}`,
|
|
2081
2966
|
"--no-first-run",
|
|
@@ -2086,7 +2971,6 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
|
|
|
2086
2971
|
"--disable-default-apps",
|
|
2087
2972
|
"--disable-extensions",
|
|
2088
2973
|
"--disable-domain-reliability",
|
|
2089
|
-
"--disable-client-side-phishing-detection",
|
|
2090
2974
|
"--disable-breakpad",
|
|
2091
2975
|
"--disable-component-extensions-with-background-pages",
|
|
2092
2976
|
"--disable-features=Translate,OptimizationHints,MediaRouter,DialMediaRouteProvider,CalculatedNewTabPage,ChromeWhatsNewUI,PrivacySandboxSettings4",
|
|
@@ -2095,12 +2979,10 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
|
|
|
2095
2979
|
"--mute-audio",
|
|
2096
2980
|
"--no-service-autorun",
|
|
2097
2981
|
"--disable-gpu",
|
|
2098
|
-
"--no-sandbox",
|
|
2099
|
-
"--disable-setuid-sandbox",
|
|
2100
|
-
"--allow-file-access-from-files",
|
|
2101
|
-
"--disable-web-security",
|
|
2982
|
+
...isWin ? [] : ["--no-sandbox", "--disable-setuid-sandbox"],
|
|
2102
2983
|
"--force-color-profile=srgb",
|
|
2103
|
-
"--no-pdf-header-footer"
|
|
2984
|
+
"--no-pdf-header-footer",
|
|
2985
|
+
"--window-size=1200,1600"
|
|
2104
2986
|
];
|
|
2105
2987
|
try {
|
|
2106
2988
|
fs4.writeFileSync(tmpHtml, pagedHtml, "utf-8");
|
|
@@ -2115,7 +2997,7 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
|
|
|
2115
2997
|
`--print-to-pdf=${tmpPdf}`,
|
|
2116
2998
|
fileUrl
|
|
2117
2999
|
],
|
|
2118
|
-
{ timeout: 3e4 }
|
|
3000
|
+
{ timeout: 3e4, windowsHide: true }
|
|
2119
3001
|
);
|
|
2120
3002
|
if ((res.status !== 0 || !fs4.existsSync(tmpPdf)) && chromePath) {
|
|
2121
3003
|
res = spawnSync(
|
|
@@ -2126,12 +3008,78 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
|
|
|
2126
3008
|
`--print-to-pdf=${tmpPdf}`,
|
|
2127
3009
|
fileUrl
|
|
2128
3010
|
],
|
|
2129
|
-
{ timeout: 3e4 }
|
|
3011
|
+
{ timeout: 3e4, windowsHide: true }
|
|
2130
3012
|
);
|
|
2131
3013
|
}
|
|
2132
3014
|
if (fs4.existsSync(tmpPdf) && fs4.statSync(tmpPdf).size > 0) {
|
|
2133
3015
|
const pdfBuffer = fs4.readFileSync(tmpPdf);
|
|
2134
|
-
|
|
3016
|
+
try {
|
|
3017
|
+
const pdfDoc = await PDFDocument.load(pdfBuffer);
|
|
3018
|
+
const resolved = resolveDocumentConfig(doc.metadata, config);
|
|
3019
|
+
if (resolved.title) pdfDoc.setTitle(resolved.title);
|
|
3020
|
+
if (resolved.author) pdfDoc.setAuthor(resolved.author);
|
|
3021
|
+
if (resolved.subtitle) pdfDoc.setSubject(resolved.subtitle);
|
|
3022
|
+
pdfDoc.setCreator("MarkForge Enterprise Document Generator");
|
|
3023
|
+
pdfDoc.setProducer("MarkForge (by Ma'sum)");
|
|
3024
|
+
pdfDoc.setModificationDate(/* @__PURE__ */ new Date());
|
|
3025
|
+
if (((_a = resolved.backCover) == null ? void 0 : _a.enabled) && pdfDoc.getPageCount() > 2) {
|
|
3026
|
+
pdfDoc.removePage(pdfDoc.getPageCount() - 1);
|
|
3027
|
+
}
|
|
3028
|
+
if (resolved.watermark) {
|
|
3029
|
+
const wmPng = generateWatermarkPngBuffer(chromePath, resolved.watermark);
|
|
3030
|
+
if (wmPng) {
|
|
3031
|
+
const embeddedPng = await pdfDoc.embedPng(wmPng);
|
|
3032
|
+
const pages = pdfDoc.getPages();
|
|
3033
|
+
const startPageIndex = ((_b = resolved.coverPage) == null ? void 0 : _b.enabled) ? 1 : 0;
|
|
3034
|
+
const endPageIndex = ((_c = resolved.backCover) == null ? void 0 : _c.enabled) ? pages.length - 1 : pages.length;
|
|
3035
|
+
for (let i = startPageIndex; i < endPageIndex; i++) {
|
|
3036
|
+
const page = pages[i];
|
|
3037
|
+
const { width, height } = page.getSize();
|
|
3038
|
+
page.drawImage(embeddedPng, {
|
|
3039
|
+
x: 0,
|
|
3040
|
+
y: 0,
|
|
3041
|
+
width,
|
|
3042
|
+
height
|
|
3043
|
+
});
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
const savedBytes = await pdfDoc.save();
|
|
3048
|
+
let finalBuffer = Buffer.from(savedBytes);
|
|
3049
|
+
if (resolved.security) {
|
|
3050
|
+
const sec = resolved.security;
|
|
3051
|
+
const hasUserPassword = typeof sec.userPassword === "string" && sec.userPassword.length > 0;
|
|
3052
|
+
const hasOwnerPassword = typeof sec.ownerPassword === "string" && sec.ownerPassword.length > 0;
|
|
3053
|
+
if (hasUserPassword || hasOwnerPassword) {
|
|
3054
|
+
try {
|
|
3055
|
+
const userPass = sec.userPassword ?? "";
|
|
3056
|
+
const ownerPass = sec.ownerPassword ?? userPass;
|
|
3057
|
+
const perms = sec.permissions;
|
|
3058
|
+
const encryptedBytes = await encryptPDF(
|
|
3059
|
+
new Uint8Array(finalBuffer),
|
|
3060
|
+
userPass,
|
|
3061
|
+
{
|
|
3062
|
+
ownerPassword: ownerPass,
|
|
3063
|
+
algorithm: "AES-256",
|
|
3064
|
+
allowPrinting: (perms == null ? void 0 : perms.printing) !== "none",
|
|
3065
|
+
allowHighQualityPrint: (perms == null ? void 0 : perms.printing) === "highResolution",
|
|
3066
|
+
allowModifying: (perms == null ? void 0 : perms.modifying) ?? true,
|
|
3067
|
+
allowCopying: (perms == null ? void 0 : perms.copying) ?? true,
|
|
3068
|
+
allowAnnotating: (perms == null ? void 0 : perms.annotating) ?? true,
|
|
3069
|
+
allowFillingForms: (perms == null ? void 0 : perms.fillingForms) ?? true,
|
|
3070
|
+
allowExtraction: (perms == null ? void 0 : perms.contentAccessibility) ?? true,
|
|
3071
|
+
allowAssembly: (perms == null ? void 0 : perms.documentAssembly) ?? true
|
|
3072
|
+
}
|
|
3073
|
+
);
|
|
3074
|
+
finalBuffer = Buffer.from(encryptedBytes);
|
|
3075
|
+
} catch {
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
return finalBuffer;
|
|
3080
|
+
} catch {
|
|
3081
|
+
return pdfBuffer;
|
|
3082
|
+
}
|
|
2135
3083
|
}
|
|
2136
3084
|
} catch {
|
|
2137
3085
|
} finally {
|
|
@@ -2386,6 +3334,29 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd(), opt
|
|
|
2386
3334
|
);
|
|
2387
3335
|
continue;
|
|
2388
3336
|
}
|
|
3337
|
+
if (span.type === "mathInline") {
|
|
3338
|
+
runs.push(
|
|
3339
|
+
new TextRun({
|
|
3340
|
+
text: span.content,
|
|
3341
|
+
font: "Cambria Math",
|
|
3342
|
+
italics: true,
|
|
3343
|
+
size: options.size,
|
|
3344
|
+
color: options.color || "0F172A"
|
|
3345
|
+
})
|
|
3346
|
+
);
|
|
3347
|
+
continue;
|
|
3348
|
+
}
|
|
3349
|
+
if (span.type === "footnoteRef") {
|
|
3350
|
+
runs.push(
|
|
3351
|
+
new TextRun({
|
|
3352
|
+
text: `[${span.content}]`,
|
|
3353
|
+
superScript: true,
|
|
3354
|
+
color: "009DA0",
|
|
3355
|
+
bold: true
|
|
3356
|
+
})
|
|
3357
|
+
);
|
|
3358
|
+
continue;
|
|
3359
|
+
}
|
|
2389
3360
|
runs.push(
|
|
2390
3361
|
new TextRun({
|
|
2391
3362
|
text: span.content,
|
|
@@ -2400,7 +3371,7 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd(), opt
|
|
|
2400
3371
|
return runs;
|
|
2401
3372
|
}
|
|
2402
3373
|
async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
|
|
2403
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
3374
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
2404
3375
|
const resolved = resolveDocumentConfig(doc.metadata, config);
|
|
2405
3376
|
const docElements = [];
|
|
2406
3377
|
const themeProps = typeof resolved.theme === "object" ? resolved.theme : {};
|
|
@@ -2411,7 +3382,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
|
|
|
2411
3382
|
const borderHex = (themeProps.borderColor || "#E2E8F0").replace("#", "");
|
|
2412
3383
|
const cardBgHex = (themeProps.cardBackground || "#F8FAFC").replace("#", "");
|
|
2413
3384
|
const defaultFont = themeProps.fontFamily ? themeProps.fontFamily.split(",")[0].replace(/['"]/g, "").trim() : "Segoe UI";
|
|
2414
|
-
if (resolved.title) {
|
|
3385
|
+
if (resolved.title && !((_a = resolved.coverPage) == null ? void 0 : _a.enabled)) {
|
|
2415
3386
|
docElements.push(
|
|
2416
3387
|
new Paragraph({
|
|
2417
3388
|
children: [
|
|
@@ -2827,7 +3798,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
|
|
|
2827
3798
|
}
|
|
2828
3799
|
if (node.type === "table" && node.children) {
|
|
2829
3800
|
const tableRows = [];
|
|
2830
|
-
const numCols = ((
|
|
3801
|
+
const numCols = ((_c = (_b = node.children[0]) == null ? void 0 : _b.children) == null ? void 0 : _c.length) || 1;
|
|
2831
3802
|
const colWidth = Math.floor(9e3 / numCols);
|
|
2832
3803
|
for (let rowIdx = 0; rowIdx < node.children.length; rowIdx++) {
|
|
2833
3804
|
const rowNode = node.children[rowIdx];
|
|
@@ -2837,7 +3808,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
|
|
|
2837
3808
|
if (rowNode.children) {
|
|
2838
3809
|
for (let colIdx = 0; colIdx < rowNode.children.length; colIdx++) {
|
|
2839
3810
|
const cellNode = rowNode.children[colIdx];
|
|
2840
|
-
const align = (
|
|
3811
|
+
const align = (_d = node.align) == null ? void 0 : _d[colIdx];
|
|
2841
3812
|
let alignment = AlignmentType.LEFT;
|
|
2842
3813
|
if (align === "center") alignment = AlignmentType.CENTER;
|
|
2843
3814
|
if (align === "right") alignment = AlignmentType.RIGHT;
|
|
@@ -2976,12 +3947,119 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
|
|
|
2976
3947
|
}
|
|
2977
3948
|
continue;
|
|
2978
3949
|
}
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
3950
|
+
if (node.type === "mathBlock") {
|
|
3951
|
+
docElements.push(
|
|
3952
|
+
new Paragraph({
|
|
3953
|
+
alignment: AlignmentType.CENTER,
|
|
3954
|
+
children: [
|
|
3955
|
+
new TextRun({
|
|
3956
|
+
text: node.text || "",
|
|
3957
|
+
font: "Cambria Math",
|
|
3958
|
+
italics: true,
|
|
3959
|
+
size: 24,
|
|
3960
|
+
// 12pt
|
|
3961
|
+
color: textHex
|
|
3962
|
+
})
|
|
3963
|
+
],
|
|
3964
|
+
spacing: { before: 180, after: 180 },
|
|
3965
|
+
shading: { fill: cardBgHex, type: ShadingType.CLEAR },
|
|
3966
|
+
border: {
|
|
3967
|
+
top: { style: BorderStyle.SINGLE, size: 4, color: borderHex },
|
|
3968
|
+
bottom: { style: BorderStyle.SINGLE, size: 4, color: borderHex },
|
|
3969
|
+
left: { style: BorderStyle.SINGLE, size: 4, color: borderHex },
|
|
3970
|
+
right: { style: BorderStyle.SINGLE, size: 4, color: borderHex }
|
|
3971
|
+
}
|
|
3972
|
+
})
|
|
3973
|
+
);
|
|
3974
|
+
continue;
|
|
3975
|
+
}
|
|
3976
|
+
if (node.type === "columns") {
|
|
3977
|
+
const cols = node.columnsCount || 2;
|
|
3978
|
+
const contentWidth = Math.max(
|
|
3979
|
+
1e3,
|
|
3980
|
+
resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
|
|
3981
|
+
);
|
|
3982
|
+
const cellWidthDxa = Math.floor(contentWidth / cols);
|
|
3983
|
+
const cells = [];
|
|
3984
|
+
for (const col of node.children || []) {
|
|
3985
|
+
const colParagraphs = [];
|
|
3986
|
+
for (const childNode of col.children || []) {
|
|
3987
|
+
if (childNode.type === "heading") {
|
|
3988
|
+
const runs = await convertInlinesToTextRuns(childNode.inlines, baseDir, { font: defaultFont, bold: true, size: 24, color: primaryDarkHex });
|
|
3989
|
+
colParagraphs.push(new Paragraph({ children: runs, spacing: { before: 120, after: 60 } }));
|
|
3990
|
+
} else if (childNode.type === "paragraph") {
|
|
3991
|
+
const runs = await convertInlinesToTextRuns(childNode.inlines, baseDir, { font: defaultFont, size: 21, color: textHex });
|
|
3992
|
+
colParagraphs.push(new Paragraph({ children: runs, spacing: { after: 100 } }));
|
|
3993
|
+
} else if (childNode.type === "list" && childNode.children) {
|
|
3994
|
+
for (const item of childNode.children) {
|
|
3995
|
+
const runs = await convertInlinesToTextRuns(item.inlines, baseDir, { font: defaultFont, size: 21, color: textHex });
|
|
3996
|
+
colParagraphs.push(new Paragraph({ children: [new TextRun({ text: "\u2022 ", font: defaultFont, color: primaryHex }), ...runs], spacing: { after: 40 } }));
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
}
|
|
4000
|
+
if (colParagraphs.length === 0) colParagraphs.push(new Paragraph({}));
|
|
4001
|
+
cells.push(
|
|
4002
|
+
new TableCell({
|
|
4003
|
+
width: { size: cellWidthDxa, type: WidthType.DXA },
|
|
4004
|
+
borders: {
|
|
4005
|
+
top: { style: BorderStyle.NONE, size: 0, color: "auto" },
|
|
4006
|
+
bottom: { style: BorderStyle.NONE, size: 0, color: "auto" },
|
|
4007
|
+
left: { style: BorderStyle.NONE, size: 0, color: "auto" },
|
|
4008
|
+
right: { style: BorderStyle.NONE, size: 0, color: "auto" }
|
|
4009
|
+
},
|
|
4010
|
+
margins: { top: 60, bottom: 60, left: 100, right: 100 },
|
|
4011
|
+
children: colParagraphs
|
|
4012
|
+
})
|
|
4013
|
+
);
|
|
4014
|
+
}
|
|
4015
|
+
docElements.push(
|
|
4016
|
+
new Table({
|
|
4017
|
+
width: { size: 100, type: WidthType.PERCENTAGE },
|
|
4018
|
+
rows: [new TableRow({ children: cells })]
|
|
4019
|
+
})
|
|
4020
|
+
);
|
|
4021
|
+
docElements.push(new Paragraph({ spacing: { after: 120 } }));
|
|
4022
|
+
continue;
|
|
4023
|
+
}
|
|
4024
|
+
}
|
|
4025
|
+
if (doc.footnoteDefs && doc.footnoteDefs.length > 0) {
|
|
4026
|
+
docElements.push(
|
|
4027
|
+
new Paragraph({
|
|
4028
|
+
border: {
|
|
4029
|
+
top: { style: BorderStyle.SINGLE, size: 4, color: borderHex, space: 8 }
|
|
4030
|
+
},
|
|
4031
|
+
spacing: { before: 360, after: 120 }
|
|
4032
|
+
})
|
|
4033
|
+
);
|
|
4034
|
+
for (const def of doc.footnoteDefs) {
|
|
4035
|
+
const defRuns = await convertInlinesToTextRuns(def.inlines, baseDir, {
|
|
4036
|
+
font: defaultFont,
|
|
4037
|
+
size: 18,
|
|
4038
|
+
// 9pt
|
|
4039
|
+
color: textMutedHex
|
|
4040
|
+
});
|
|
4041
|
+
docElements.push(
|
|
4042
|
+
new Paragraph({
|
|
4043
|
+
children: [
|
|
4044
|
+
new TextRun({
|
|
4045
|
+
text: `[${def.id}] `,
|
|
4046
|
+
bold: true,
|
|
4047
|
+
color: primaryDarkHex,
|
|
4048
|
+
font: defaultFont,
|
|
4049
|
+
size: 18
|
|
4050
|
+
}),
|
|
4051
|
+
...defRuns
|
|
4052
|
+
],
|
|
4053
|
+
spacing: { after: 60 }
|
|
4054
|
+
})
|
|
4055
|
+
);
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
4058
|
+
if (resolved.signatures && resolved.signatures.items.length > 0) {
|
|
4059
|
+
const sig = resolved.signatures;
|
|
4060
|
+
const numItems = sig.items.length;
|
|
4061
|
+
const contentWidth = Math.max(
|
|
4062
|
+
1e3,
|
|
2985
4063
|
resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
|
|
2986
4064
|
);
|
|
2987
4065
|
docElements.push(new Paragraph({ spacing: { before: sig.spacingBeforeTwip } }));
|
|
@@ -3037,7 +4115,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
|
|
|
3037
4115
|
const centerPos = Math.round(contentWidthTwip / 2);
|
|
3038
4116
|
const rightPos = contentWidthTwip;
|
|
3039
4117
|
const headerRuns = [];
|
|
3040
|
-
if ((
|
|
4118
|
+
if ((_e = resolved.header) == null ? void 0 : _e.left) {
|
|
3041
4119
|
headerRuns.push(
|
|
3042
4120
|
new TextRun({
|
|
3043
4121
|
text: resolved.header.left.text,
|
|
@@ -3050,7 +4128,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
|
|
|
3050
4128
|
);
|
|
3051
4129
|
}
|
|
3052
4130
|
headerRuns.push(new TextRun({ text: " " }));
|
|
3053
|
-
if ((
|
|
4131
|
+
if ((_f = resolved.header) == null ? void 0 : _f.center) {
|
|
3054
4132
|
headerRuns.push(
|
|
3055
4133
|
new TextRun({
|
|
3056
4134
|
text: resolved.header.center.text,
|
|
@@ -3063,7 +4141,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
|
|
|
3063
4141
|
);
|
|
3064
4142
|
}
|
|
3065
4143
|
headerRuns.push(new TextRun({ text: " " }));
|
|
3066
|
-
if ((
|
|
4144
|
+
if ((_g = resolved.header) == null ? void 0 : _g.right) {
|
|
3067
4145
|
headerRuns.push(
|
|
3068
4146
|
new TextRun({
|
|
3069
4147
|
text: resolved.header.right.text,
|
|
@@ -3102,7 +4180,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
|
|
|
3102
4180
|
]
|
|
3103
4181
|
}) : void 0;
|
|
3104
4182
|
const footerRuns = [];
|
|
3105
|
-
if ((
|
|
4183
|
+
if ((_h = resolved.footer) == null ? void 0 : _h.left) {
|
|
3106
4184
|
footerRuns.push(
|
|
3107
4185
|
new TextRun({
|
|
3108
4186
|
text: resolved.footer.left.text,
|
|
@@ -3115,7 +4193,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
|
|
|
3115
4193
|
);
|
|
3116
4194
|
}
|
|
3117
4195
|
footerRuns.push(new TextRun({ text: " " }));
|
|
3118
|
-
if ((
|
|
4196
|
+
if ((_i = resolved.footer) == null ? void 0 : _i.center) {
|
|
3119
4197
|
footerRuns.push(
|
|
3120
4198
|
new TextRun({
|
|
3121
4199
|
text: resolved.footer.center.text,
|
|
@@ -3128,7 +4206,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
|
|
|
3128
4206
|
);
|
|
3129
4207
|
}
|
|
3130
4208
|
footerRuns.push(new TextRun({ text: " " }));
|
|
3131
|
-
if ((
|
|
4209
|
+
if ((_j = resolved.footer) == null ? void 0 : _j.right) {
|
|
3132
4210
|
const rZone = resolved.footer.right;
|
|
3133
4211
|
const rColor = rZone.color.replace("#", "");
|
|
3134
4212
|
const rSize = (rZone.fontSize || 9) * 2;
|
|
@@ -3213,6 +4291,91 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
|
|
|
3213
4291
|
]
|
|
3214
4292
|
}) : void 0;
|
|
3215
4293
|
const isLandscape = resolved.orientation === "landscape";
|
|
4294
|
+
const docSections = [];
|
|
4295
|
+
if (resolved.coverPage && resolved.coverPage.enabled) {
|
|
4296
|
+
const coverElements = await buildDocxCoverPageElements(
|
|
4297
|
+
resolved.coverPage,
|
|
4298
|
+
defaultFont,
|
|
4299
|
+
textHex,
|
|
4300
|
+
primaryHex,
|
|
4301
|
+
primaryDarkHex,
|
|
4302
|
+
textMutedHex,
|
|
4303
|
+
baseDir
|
|
4304
|
+
);
|
|
4305
|
+
docSections.push({
|
|
4306
|
+
properties: {
|
|
4307
|
+
page: {
|
|
4308
|
+
size: {
|
|
4309
|
+
width: resolved.paperDimensions.widthTwip,
|
|
4310
|
+
height: resolved.paperDimensions.heightTwip,
|
|
4311
|
+
orientation: isLandscape ? PageOrientation.LANDSCAPE : PageOrientation.PORTRAIT
|
|
4312
|
+
},
|
|
4313
|
+
margin: {
|
|
4314
|
+
top: resolved.margins.topTwip,
|
|
4315
|
+
bottom: resolved.margins.bottomTwip,
|
|
4316
|
+
left: resolved.margins.leftTwip,
|
|
4317
|
+
right: resolved.margins.rightTwip
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
},
|
|
4321
|
+
headers: void 0,
|
|
4322
|
+
footers: void 0,
|
|
4323
|
+
children: coverElements
|
|
4324
|
+
});
|
|
4325
|
+
}
|
|
4326
|
+
docSections.push({
|
|
4327
|
+
properties: {
|
|
4328
|
+
page: {
|
|
4329
|
+
size: {
|
|
4330
|
+
width: resolved.paperDimensions.widthTwip,
|
|
4331
|
+
height: resolved.paperDimensions.heightTwip,
|
|
4332
|
+
orientation: isLandscape ? PageOrientation.LANDSCAPE : PageOrientation.PORTRAIT
|
|
4333
|
+
},
|
|
4334
|
+
margin: {
|
|
4335
|
+
top: resolved.margins.topTwip,
|
|
4336
|
+
bottom: resolved.margins.bottomTwip,
|
|
4337
|
+
left: resolved.margins.leftTwip,
|
|
4338
|
+
right: resolved.margins.rightTwip,
|
|
4339
|
+
header: 720,
|
|
4340
|
+
footer: 720
|
|
4341
|
+
}
|
|
4342
|
+
}
|
|
4343
|
+
},
|
|
4344
|
+
headers: docHeader ? { default: docHeader } : void 0,
|
|
4345
|
+
footers: docFooter ? { default: docFooter } : void 0,
|
|
4346
|
+
children: docElements
|
|
4347
|
+
});
|
|
4348
|
+
if (resolved.backCover && resolved.backCover.enabled) {
|
|
4349
|
+
const backElements = await buildDocxBackCoverElements(
|
|
4350
|
+
resolved.backCover,
|
|
4351
|
+
defaultFont,
|
|
4352
|
+
textHex,
|
|
4353
|
+
primaryHex,
|
|
4354
|
+
primaryDarkHex,
|
|
4355
|
+
textMutedHex,
|
|
4356
|
+
baseDir
|
|
4357
|
+
);
|
|
4358
|
+
docSections.push({
|
|
4359
|
+
properties: {
|
|
4360
|
+
page: {
|
|
4361
|
+
size: {
|
|
4362
|
+
width: resolved.paperDimensions.widthTwip,
|
|
4363
|
+
height: resolved.paperDimensions.heightTwip,
|
|
4364
|
+
orientation: isLandscape ? PageOrientation.LANDSCAPE : PageOrientation.PORTRAIT
|
|
4365
|
+
},
|
|
4366
|
+
margin: {
|
|
4367
|
+
top: resolved.margins.topTwip,
|
|
4368
|
+
bottom: resolved.margins.bottomTwip,
|
|
4369
|
+
left: resolved.margins.leftTwip,
|
|
4370
|
+
right: resolved.margins.rightTwip
|
|
4371
|
+
}
|
|
4372
|
+
}
|
|
4373
|
+
},
|
|
4374
|
+
headers: void 0,
|
|
4375
|
+
footers: void 0,
|
|
4376
|
+
children: backElements
|
|
4377
|
+
});
|
|
4378
|
+
}
|
|
3216
4379
|
const document = new Document({
|
|
3217
4380
|
styles: {
|
|
3218
4381
|
default: {
|
|
@@ -3233,33 +4396,250 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
|
|
|
3233
4396
|
}
|
|
3234
4397
|
}
|
|
3235
4398
|
},
|
|
3236
|
-
sections:
|
|
3237
|
-
{
|
|
3238
|
-
properties: {
|
|
3239
|
-
page: {
|
|
3240
|
-
size: {
|
|
3241
|
-
width: resolved.paperDimensions.widthTwip,
|
|
3242
|
-
height: resolved.paperDimensions.heightTwip,
|
|
3243
|
-
orientation: isLandscape ? PageOrientation.LANDSCAPE : PageOrientation.PORTRAIT
|
|
3244
|
-
},
|
|
3245
|
-
margin: {
|
|
3246
|
-
top: resolved.margins.topTwip,
|
|
3247
|
-
bottom: resolved.margins.bottomTwip,
|
|
3248
|
-
left: resolved.margins.leftTwip,
|
|
3249
|
-
right: resolved.margins.rightTwip,
|
|
3250
|
-
header: 720,
|
|
3251
|
-
footer: 720
|
|
3252
|
-
}
|
|
3253
|
-
}
|
|
3254
|
-
},
|
|
3255
|
-
headers: docHeader ? { default: docHeader } : void 0,
|
|
3256
|
-
footers: docFooter ? { default: docFooter } : void 0,
|
|
3257
|
-
children: docElements
|
|
3258
|
-
}
|
|
3259
|
-
]
|
|
4399
|
+
sections: docSections
|
|
3260
4400
|
});
|
|
3261
4401
|
return await Packer.toBuffer(document);
|
|
3262
4402
|
}
|
|
4403
|
+
async function buildDocxBackCoverElements(backCover, defaultFont, textHex, primaryHex, primaryDarkHex, textMutedHex, baseDir) {
|
|
4404
|
+
var _a;
|
|
4405
|
+
const elements = [];
|
|
4406
|
+
elements.push(new Paragraph({ spacing: { before: 1800 } }));
|
|
4407
|
+
if (backCover.logo) {
|
|
4408
|
+
const resolvedLogo = await resolveImage(backCover.logo, baseDir);
|
|
4409
|
+
if (resolvedLogo) {
|
|
4410
|
+
const logoW = typeof backCover.logoWidth === "number" ? backCover.logoWidth : 140;
|
|
4411
|
+
const logoType = ((_a = resolvedLogo.mimeType) == null ? void 0 : _a.includes("png")) ? "png" : "jpg";
|
|
4412
|
+
elements.push(
|
|
4413
|
+
new Paragraph({
|
|
4414
|
+
children: [
|
|
4415
|
+
new ImageRun({
|
|
4416
|
+
data: resolvedLogo.buffer,
|
|
4417
|
+
transformation: {
|
|
4418
|
+
width: logoW,
|
|
4419
|
+
height: Math.round(logoW * 0.75)
|
|
4420
|
+
},
|
|
4421
|
+
type: logoType
|
|
4422
|
+
})
|
|
4423
|
+
],
|
|
4424
|
+
spacing: { after: 240 }
|
|
4425
|
+
})
|
|
4426
|
+
);
|
|
4427
|
+
}
|
|
4428
|
+
}
|
|
4429
|
+
if (backCover.badge) {
|
|
4430
|
+
elements.push(
|
|
4431
|
+
new Paragraph({
|
|
4432
|
+
children: [
|
|
4433
|
+
new TextRun({
|
|
4434
|
+
text: `[ ${backCover.badge.toUpperCase()} ]`,
|
|
4435
|
+
font: defaultFont,
|
|
4436
|
+
size: 20,
|
|
4437
|
+
bold: true,
|
|
4438
|
+
color: primaryDarkHex
|
|
4439
|
+
})
|
|
4440
|
+
],
|
|
4441
|
+
spacing: { after: 240 }
|
|
4442
|
+
})
|
|
4443
|
+
);
|
|
4444
|
+
}
|
|
4445
|
+
elements.push(
|
|
4446
|
+
new Paragraph({
|
|
4447
|
+
children: [
|
|
4448
|
+
new TextRun({
|
|
4449
|
+
text: backCover.title,
|
|
4450
|
+
font: defaultFont,
|
|
4451
|
+
size: 52,
|
|
4452
|
+
// 26pt
|
|
4453
|
+
bold: true,
|
|
4454
|
+
color: textHex
|
|
4455
|
+
})
|
|
4456
|
+
],
|
|
4457
|
+
spacing: { after: 140 }
|
|
4458
|
+
})
|
|
4459
|
+
);
|
|
4460
|
+
if (backCover.subtitle) {
|
|
4461
|
+
elements.push(
|
|
4462
|
+
new Paragraph({
|
|
4463
|
+
children: [
|
|
4464
|
+
new TextRun({
|
|
4465
|
+
text: backCover.subtitle,
|
|
4466
|
+
font: defaultFont,
|
|
4467
|
+
size: 24,
|
|
4468
|
+
// 12pt
|
|
4469
|
+
color: textMutedHex
|
|
4470
|
+
})
|
|
4471
|
+
],
|
|
4472
|
+
spacing: { after: 480 }
|
|
4473
|
+
})
|
|
4474
|
+
);
|
|
4475
|
+
}
|
|
4476
|
+
elements.push(
|
|
4477
|
+
new Paragraph({
|
|
4478
|
+
border: {
|
|
4479
|
+
bottom: { style: BorderStyle.SINGLE, size: 16, color: primaryHex, space: 8 }
|
|
4480
|
+
},
|
|
4481
|
+
spacing: { after: 480 }
|
|
4482
|
+
})
|
|
4483
|
+
);
|
|
4484
|
+
const contactRuns = [];
|
|
4485
|
+
if (backCover.company) contactRuns.push(new TextRun({ text: `Organization: ${backCover.company}
|
|
4486
|
+
`, font: defaultFont, size: 21, color: textHex }));
|
|
4487
|
+
if (backCover.address) contactRuns.push(new TextRun({ text: `Address: ${backCover.address}
|
|
4488
|
+
`, font: defaultFont, size: 21, color: textHex }));
|
|
4489
|
+
if (backCover.email) contactRuns.push(new TextRun({ text: `Email: ${backCover.email}
|
|
4490
|
+
`, font: defaultFont, size: 21, color: textHex }));
|
|
4491
|
+
if (backCover.phone) contactRuns.push(new TextRun({ text: `Phone: ${backCover.phone}
|
|
4492
|
+
`, font: defaultFont, size: 21, color: textHex }));
|
|
4493
|
+
if (backCover.website) contactRuns.push(new TextRun({ text: `Website: ${backCover.website}
|
|
4494
|
+
`, font: defaultFont, size: 21, color: textHex }));
|
|
4495
|
+
if (backCover.social) {
|
|
4496
|
+
for (const [net, url] of Object.entries(backCover.social)) {
|
|
4497
|
+
if (url) {
|
|
4498
|
+
contactRuns.push(new TextRun({ text: `${net.toUpperCase()}: ${url}
|
|
4499
|
+
`, font: defaultFont, size: 21, color: textHex }));
|
|
4500
|
+
}
|
|
4501
|
+
}
|
|
4502
|
+
}
|
|
4503
|
+
if (contactRuns.length > 0) {
|
|
4504
|
+
elements.push(
|
|
4505
|
+
new Paragraph({
|
|
4506
|
+
children: contactRuns,
|
|
4507
|
+
spacing: { before: 360, after: 360 }
|
|
4508
|
+
})
|
|
4509
|
+
);
|
|
4510
|
+
}
|
|
4511
|
+
if (backCover.copyright) {
|
|
4512
|
+
elements.push(
|
|
4513
|
+
new Paragraph({
|
|
4514
|
+
children: [
|
|
4515
|
+
new TextRun({
|
|
4516
|
+
text: backCover.copyright,
|
|
4517
|
+
font: defaultFont,
|
|
4518
|
+
size: 18,
|
|
4519
|
+
color: "94A3B8"
|
|
4520
|
+
})
|
|
4521
|
+
],
|
|
4522
|
+
spacing: { before: 720 }
|
|
4523
|
+
})
|
|
4524
|
+
);
|
|
4525
|
+
}
|
|
4526
|
+
return elements;
|
|
4527
|
+
}
|
|
4528
|
+
async function buildDocxCoverPageElements(cover, defaultFont, textHex, primaryHex, primaryDarkHex, textMutedHex, baseDir) {
|
|
4529
|
+
var _a;
|
|
4530
|
+
const elements = [];
|
|
4531
|
+
elements.push(new Paragraph({ spacing: { before: 1800 } }));
|
|
4532
|
+
if (cover.logo) {
|
|
4533
|
+
const resolvedLogo = await resolveImage(cover.logo, baseDir);
|
|
4534
|
+
if (resolvedLogo) {
|
|
4535
|
+
const logoW = typeof cover.logoWidth === "number" ? cover.logoWidth : 140;
|
|
4536
|
+
const logoType = ((_a = resolvedLogo.mimeType) == null ? void 0 : _a.includes("png")) ? "png" : "jpg";
|
|
4537
|
+
elements.push(
|
|
4538
|
+
new Paragraph({
|
|
4539
|
+
children: [
|
|
4540
|
+
new ImageRun({
|
|
4541
|
+
data: resolvedLogo.buffer,
|
|
4542
|
+
transformation: {
|
|
4543
|
+
width: logoW,
|
|
4544
|
+
height: Math.round(logoW * 0.75)
|
|
4545
|
+
},
|
|
4546
|
+
type: logoType
|
|
4547
|
+
})
|
|
4548
|
+
],
|
|
4549
|
+
spacing: { after: 240 }
|
|
4550
|
+
})
|
|
4551
|
+
);
|
|
4552
|
+
}
|
|
4553
|
+
}
|
|
4554
|
+
if (cover.badge) {
|
|
4555
|
+
elements.push(
|
|
4556
|
+
new Paragraph({
|
|
4557
|
+
children: [
|
|
4558
|
+
new TextRun({
|
|
4559
|
+
text: `[ ${cover.badge.toUpperCase()} ]`,
|
|
4560
|
+
font: defaultFont,
|
|
4561
|
+
size: 20,
|
|
4562
|
+
bold: true,
|
|
4563
|
+
color: primaryDarkHex
|
|
4564
|
+
})
|
|
4565
|
+
],
|
|
4566
|
+
spacing: { after: 240 }
|
|
4567
|
+
})
|
|
4568
|
+
);
|
|
4569
|
+
}
|
|
4570
|
+
elements.push(
|
|
4571
|
+
new Paragraph({
|
|
4572
|
+
children: [
|
|
4573
|
+
new TextRun({
|
|
4574
|
+
text: cover.title,
|
|
4575
|
+
font: defaultFont,
|
|
4576
|
+
size: 56,
|
|
4577
|
+
// 28pt
|
|
4578
|
+
bold: true,
|
|
4579
|
+
color: textHex
|
|
4580
|
+
})
|
|
4581
|
+
],
|
|
4582
|
+
spacing: { after: 140 }
|
|
4583
|
+
})
|
|
4584
|
+
);
|
|
4585
|
+
if (cover.subtitle) {
|
|
4586
|
+
elements.push(
|
|
4587
|
+
new Paragraph({
|
|
4588
|
+
children: [
|
|
4589
|
+
new TextRun({
|
|
4590
|
+
text: cover.subtitle,
|
|
4591
|
+
font: defaultFont,
|
|
4592
|
+
size: 26,
|
|
4593
|
+
// 13pt
|
|
4594
|
+
color: textMutedHex
|
|
4595
|
+
})
|
|
4596
|
+
],
|
|
4597
|
+
spacing: { after: 480 }
|
|
4598
|
+
})
|
|
4599
|
+
);
|
|
4600
|
+
}
|
|
4601
|
+
elements.push(
|
|
4602
|
+
new Paragraph({
|
|
4603
|
+
border: {
|
|
4604
|
+
bottom: { style: BorderStyle.SINGLE, size: 16, color: primaryHex, space: 8 }
|
|
4605
|
+
},
|
|
4606
|
+
spacing: { after: 480 }
|
|
4607
|
+
})
|
|
4608
|
+
);
|
|
4609
|
+
if (cover.company || cover.author || cover.version || cover.date) {
|
|
4610
|
+
const metaRuns = [];
|
|
4611
|
+
if (cover.company) metaRuns.push(new TextRun({ text: `Organization: ${cover.company}
|
|
4612
|
+
`, font: defaultFont, size: 21, color: textHex }));
|
|
4613
|
+
if (cover.author) metaRuns.push(new TextRun({ text: `Author: ${cover.author}
|
|
4614
|
+
`, font: defaultFont, size: 21, color: textHex }));
|
|
4615
|
+
if (cover.version) metaRuns.push(new TextRun({ text: `Version: ${cover.version}
|
|
4616
|
+
`, font: defaultFont, size: 21, color: textHex }));
|
|
4617
|
+
if (cover.date) metaRuns.push(new TextRun({ text: `Date: ${cover.date}
|
|
4618
|
+
`, font: defaultFont, size: 21, color: textHex }));
|
|
4619
|
+
elements.push(
|
|
4620
|
+
new Paragraph({
|
|
4621
|
+
children: metaRuns,
|
|
4622
|
+
spacing: { before: 360, after: 360 }
|
|
4623
|
+
})
|
|
4624
|
+
);
|
|
4625
|
+
}
|
|
4626
|
+
if (cover.footerText) {
|
|
4627
|
+
elements.push(
|
|
4628
|
+
new Paragraph({
|
|
4629
|
+
children: [
|
|
4630
|
+
new TextRun({
|
|
4631
|
+
text: cover.footerText,
|
|
4632
|
+
font: defaultFont,
|
|
4633
|
+
size: 18,
|
|
4634
|
+
color: "94A3B8"
|
|
4635
|
+
})
|
|
4636
|
+
],
|
|
4637
|
+
spacing: { before: 720 }
|
|
4638
|
+
})
|
|
4639
|
+
);
|
|
4640
|
+
}
|
|
4641
|
+
return elements;
|
|
4642
|
+
}
|
|
3263
4643
|
async function buildDocxSignatureCell(item, sig, widthDxa, defaultFont, baseDir) {
|
|
3264
4644
|
const cellParagraphs = [];
|
|
3265
4645
|
if (item.title) {
|
|
@@ -3487,14 +4867,885 @@ async function compileMarkdown(inputFilePathOrContent, userConfig = {}, onProgre
|
|
|
3487
4867
|
};
|
|
3488
4868
|
}
|
|
3489
4869
|
|
|
4870
|
+
// src/server/previewServer.ts
|
|
4871
|
+
import * as http from "http";
|
|
4872
|
+
import * as fs7 from "fs";
|
|
4873
|
+
import * as path7 from "path";
|
|
4874
|
+
async function startPreviewServer(options) {
|
|
4875
|
+
const absoluteFilePath = path7.resolve(process.cwd(), options.filePath);
|
|
4876
|
+
if (!fs7.existsSync(absoluteFilePath)) {
|
|
4877
|
+
throw new Error(`MarkForge preview error: File not found at "${absoluteFilePath}"`);
|
|
4878
|
+
}
|
|
4879
|
+
const baseDir = path7.dirname(absoluteFilePath);
|
|
4880
|
+
const { config: fileConfig } = await loadConfig(void 0, baseDir);
|
|
4881
|
+
const baseConfig = options.config || fileConfig;
|
|
4882
|
+
const port = options.port || 3e3;
|
|
4883
|
+
const sseClients = /* @__PURE__ */ new Set();
|
|
4884
|
+
const broadcastReload = () => {
|
|
4885
|
+
sseClients.forEach((client) => {
|
|
4886
|
+
try {
|
|
4887
|
+
client.write(`event: reload
|
|
4888
|
+
data: ${Date.now()}
|
|
4889
|
+
|
|
4890
|
+
`);
|
|
4891
|
+
} catch {
|
|
4892
|
+
sseClients.delete(client);
|
|
4893
|
+
}
|
|
4894
|
+
});
|
|
4895
|
+
};
|
|
4896
|
+
let debounceTimer = null;
|
|
4897
|
+
const watcher = fs7.watch(baseDir, { recursive: false }, (_event, filename) => {
|
|
4898
|
+
if (!filename) return;
|
|
4899
|
+
const changedPath = path7.resolve(baseDir, filename);
|
|
4900
|
+
if (changedPath === absoluteFilePath || filename.includes("markforge") || filename.endsWith(".css")) {
|
|
4901
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
4902
|
+
debounceTimer = setTimeout(() => {
|
|
4903
|
+
broadcastReload();
|
|
4904
|
+
}, 150);
|
|
4905
|
+
}
|
|
4906
|
+
});
|
|
4907
|
+
const server = http.createServer(async (req, res) => {
|
|
4908
|
+
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
4909
|
+
if (url.pathname === "/events") {
|
|
4910
|
+
res.writeHead(200, {
|
|
4911
|
+
"Content-Type": "text/event-stream",
|
|
4912
|
+
"Cache-Control": "no-cache, no-transform",
|
|
4913
|
+
Connection: "keep-alive"
|
|
4914
|
+
});
|
|
4915
|
+
res.write(`data: connected
|
|
4916
|
+
|
|
4917
|
+
`);
|
|
4918
|
+
sseClients.add(res);
|
|
4919
|
+
req.on("close", () => {
|
|
4920
|
+
sseClients.delete(res);
|
|
4921
|
+
});
|
|
4922
|
+
return;
|
|
4923
|
+
}
|
|
4924
|
+
if (url.pathname === "/api/file-content" && req.method === "GET") {
|
|
4925
|
+
try {
|
|
4926
|
+
const content = fs7.readFileSync(absoluteFilePath, "utf-8");
|
|
4927
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
4928
|
+
res.end(
|
|
4929
|
+
JSON.stringify({
|
|
4930
|
+
content,
|
|
4931
|
+
fileName: path7.basename(absoluteFilePath),
|
|
4932
|
+
filePath: absoluteFilePath
|
|
4933
|
+
})
|
|
4934
|
+
);
|
|
4935
|
+
} catch (err) {
|
|
4936
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4937
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
4938
|
+
res.end(JSON.stringify({ error: msg }));
|
|
4939
|
+
}
|
|
4940
|
+
return;
|
|
4941
|
+
}
|
|
4942
|
+
if (url.pathname === "/api/save-content" && req.method === "POST") {
|
|
4943
|
+
let body = "";
|
|
4944
|
+
req.on("data", (chunk) => {
|
|
4945
|
+
body += chunk;
|
|
4946
|
+
});
|
|
4947
|
+
req.on("end", () => {
|
|
4948
|
+
try {
|
|
4949
|
+
const parsed = JSON.parse(body);
|
|
4950
|
+
if (typeof parsed.content === "string") {
|
|
4951
|
+
fs7.writeFileSync(absoluteFilePath, parsed.content, "utf-8");
|
|
4952
|
+
broadcastReload();
|
|
4953
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
4954
|
+
res.end(JSON.stringify({ success: true, savedAt: Date.now() }));
|
|
4955
|
+
} else {
|
|
4956
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
4957
|
+
res.end(JSON.stringify({ error: "Missing content field in request body" }));
|
|
4958
|
+
}
|
|
4959
|
+
} catch (err) {
|
|
4960
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4961
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
4962
|
+
res.end(JSON.stringify({ error: msg }));
|
|
4963
|
+
}
|
|
4964
|
+
});
|
|
4965
|
+
return;
|
|
4966
|
+
}
|
|
4967
|
+
if (url.pathname === "/api/export" && (req.method === "GET" || req.method === "POST")) {
|
|
4968
|
+
const format = url.searchParams.get("format") || "docx";
|
|
4969
|
+
try {
|
|
4970
|
+
const mdContent = fs7.readFileSync(absoluteFilePath, "utf-8");
|
|
4971
|
+
const doc = parseMarkdownDocument(mdContent);
|
|
4972
|
+
const { config: resolvedConfig } = await loadConfig(void 0, baseDir);
|
|
4973
|
+
const mergedConfig = { ...baseConfig, ...resolvedConfig };
|
|
4974
|
+
const fileBase = path7.basename(absoluteFilePath, path7.extname(absoluteFilePath));
|
|
4975
|
+
if (format === "docx") {
|
|
4976
|
+
const buffer = await buildDocxDocument(doc, mergedConfig, baseDir);
|
|
4977
|
+
res.writeHead(200, {
|
|
4978
|
+
"Content-Type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
4979
|
+
"Content-Disposition": `attachment; filename="${fileBase}.docx"`
|
|
4980
|
+
});
|
|
4981
|
+
res.end(buffer);
|
|
4982
|
+
return;
|
|
4983
|
+
} else if (format === "pdf") {
|
|
4984
|
+
const buffer = await buildPdfDocument(doc, mergedConfig, baseDir);
|
|
4985
|
+
res.writeHead(200, {
|
|
4986
|
+
"Content-Type": "application/pdf",
|
|
4987
|
+
"Content-Disposition": `attachment; filename="${fileBase}.pdf"`
|
|
4988
|
+
});
|
|
4989
|
+
res.end(buffer);
|
|
4990
|
+
return;
|
|
4991
|
+
} else {
|
|
4992
|
+
const html = await buildHtmlDocument(doc, mergedConfig, baseDir);
|
|
4993
|
+
res.writeHead(200, {
|
|
4994
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
4995
|
+
"Content-Disposition": `attachment; filename="${fileBase}.html"`
|
|
4996
|
+
});
|
|
4997
|
+
res.end(html);
|
|
4998
|
+
return;
|
|
4999
|
+
}
|
|
5000
|
+
} catch (err) {
|
|
5001
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5002
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
5003
|
+
res.end(`Export failed: ${msg}`);
|
|
5004
|
+
return;
|
|
5005
|
+
}
|
|
5006
|
+
}
|
|
5007
|
+
if (url.pathname === "/document-content") {
|
|
5008
|
+
try {
|
|
5009
|
+
const mdContent = fs7.readFileSync(absoluteFilePath, "utf-8");
|
|
5010
|
+
const doc = parseMarkdownDocument(mdContent);
|
|
5011
|
+
const { config: resolvedConfig } = await loadConfig(void 0, baseDir);
|
|
5012
|
+
const html = await buildHtmlDocument(doc, { ...baseConfig, ...resolvedConfig }, baseDir);
|
|
5013
|
+
const injectedScript = `
|
|
5014
|
+
<script>
|
|
5015
|
+
(function() {
|
|
5016
|
+
var evtSource = new EventSource('/events');
|
|
5017
|
+
evtSource.addEventListener('reload', function() {
|
|
5018
|
+
var scrollPos = window.scrollY;
|
|
5019
|
+
sessionStorage.setItem('markforge_scroll', scrollPos);
|
|
5020
|
+
window.location.reload();
|
|
5021
|
+
});
|
|
5022
|
+
window.addEventListener('load', function() {
|
|
5023
|
+
var saved = sessionStorage.getItem('markforge_scroll');
|
|
5024
|
+
if (saved) {
|
|
5025
|
+
window.scrollTo(0, parseInt(saved, 10));
|
|
5026
|
+
}
|
|
5027
|
+
});
|
|
5028
|
+
})();
|
|
5029
|
+
</script>
|
|
5030
|
+
`;
|
|
5031
|
+
const finalHtml = html.replace("</body>", `${injectedScript}</body>`);
|
|
5032
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
5033
|
+
res.end(finalHtml);
|
|
5034
|
+
} catch (err) {
|
|
5035
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5036
|
+
res.writeHead(500, { "Content-Type": "text/html; charset=utf-8" });
|
|
5037
|
+
res.end(`<div style="padding:2rem;font-family:sans-serif;color:#ef4444;background:#fef2f2;border:1px solid #f87171;border-radius:8px;"><h3>MarkForge Compilation Error</h3><pre>${escapeHtml2(msg)}</pre></div>`);
|
|
5038
|
+
}
|
|
5039
|
+
return;
|
|
5040
|
+
}
|
|
5041
|
+
if (url.pathname === "/" || url.pathname === "/index.html") {
|
|
5042
|
+
const fileName = path7.basename(absoluteFilePath);
|
|
5043
|
+
const initialContent = fs7.readFileSync(absoluteFilePath, "utf-8");
|
|
5044
|
+
const appHtml = `<!DOCTYPE html>
|
|
5045
|
+
<html lang="en">
|
|
5046
|
+
<head>
|
|
5047
|
+
<meta charset="UTF-8">
|
|
5048
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
5049
|
+
<title>MarkForge Live Studio - ${escapeHtml2(fileName)}</title>
|
|
5050
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
5051
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
5052
|
+
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
5053
|
+
<style>
|
|
5054
|
+
:root {
|
|
5055
|
+
--mf-primary: #0D998D;
|
|
5056
|
+
--mf-primary-dark: #008277;
|
|
5057
|
+
--mf-primary-light: #ECFDFD;
|
|
5058
|
+
--mf-primary-border: #33CDCF;
|
|
5059
|
+
--mf-dark: #0F172A;
|
|
5060
|
+
--mf-slate: #1E293B;
|
|
5061
|
+
--mf-editor-bg: #0F172A;
|
|
5062
|
+
--mf-editor-gutter: #1E293B;
|
|
5063
|
+
--mf-editor-text: #F8FAFC;
|
|
5064
|
+
--mf-muted: #64748B;
|
|
5065
|
+
--mf-light-border: #E2E8F0;
|
|
5066
|
+
--mf-bg: #F1F5F9;
|
|
5067
|
+
}
|
|
5068
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
5069
|
+
body {
|
|
5070
|
+
font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
5071
|
+
background: var(--mf-bg);
|
|
5072
|
+
color: var(--mf-dark);
|
|
5073
|
+
display: flex;
|
|
5074
|
+
flex-direction: column;
|
|
5075
|
+
height: 100vh;
|
|
5076
|
+
overflow: hidden;
|
|
5077
|
+
}
|
|
5078
|
+
header {
|
|
5079
|
+
background: #FFFFFF;
|
|
5080
|
+
border-bottom: 1px solid var(--mf-light-border);
|
|
5081
|
+
min-height: 56px;
|
|
5082
|
+
display: flex;
|
|
5083
|
+
flex-wrap: wrap;
|
|
5084
|
+
align-items: center;
|
|
5085
|
+
justify-content: space-between;
|
|
5086
|
+
padding: 0.4rem 1.2rem;
|
|
5087
|
+
z-index: 10;
|
|
5088
|
+
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
|
|
5089
|
+
gap: 0.75rem;
|
|
5090
|
+
}
|
|
5091
|
+
.brand-section {
|
|
5092
|
+
display: flex;
|
|
5093
|
+
align-items: center;
|
|
5094
|
+
gap: 0.75rem;
|
|
5095
|
+
}
|
|
5096
|
+
.brand-badge {
|
|
5097
|
+
font-size: 0.72rem;
|
|
5098
|
+
font-weight: 800;
|
|
5099
|
+
letter-spacing: 0.08em;
|
|
5100
|
+
background: var(--mf-dark);
|
|
5101
|
+
color: #FFFFFF;
|
|
5102
|
+
padding: 0.25rem 0.55rem;
|
|
5103
|
+
border-radius: 4px;
|
|
5104
|
+
text-transform: uppercase;
|
|
5105
|
+
}
|
|
5106
|
+
.file-name {
|
|
5107
|
+
font-size: 0.9rem;
|
|
5108
|
+
font-weight: 700;
|
|
5109
|
+
color: var(--mf-dark);
|
|
5110
|
+
}
|
|
5111
|
+
.sync-status {
|
|
5112
|
+
display: flex;
|
|
5113
|
+
align-items: center;
|
|
5114
|
+
gap: 0.35rem;
|
|
5115
|
+
font-size: 0.75rem;
|
|
5116
|
+
font-weight: 600;
|
|
5117
|
+
color: var(--mf-primary-dark);
|
|
5118
|
+
background: var(--mf-primary-light);
|
|
5119
|
+
padding: 0.2rem 0.55rem;
|
|
5120
|
+
border-radius: 9999px;
|
|
5121
|
+
border: 1px solid var(--mf-primary-border);
|
|
5122
|
+
}
|
|
5123
|
+
.sync-dot {
|
|
5124
|
+
width: 7px;
|
|
5125
|
+
height: 7px;
|
|
5126
|
+
background-color: var(--mf-primary);
|
|
5127
|
+
border-radius: 50%;
|
|
5128
|
+
box-shadow: 0 0 0 2px rgba(13, 153, 141, 0.2);
|
|
5129
|
+
}
|
|
5130
|
+
.toolbar-section {
|
|
5131
|
+
display: flex;
|
|
5132
|
+
align-items: center;
|
|
5133
|
+
gap: 0.3rem;
|
|
5134
|
+
background: #F8FAFC;
|
|
5135
|
+
padding: 0.25rem 0.4rem;
|
|
5136
|
+
border-radius: 6px;
|
|
5137
|
+
border: 1px solid var(--mf-light-border);
|
|
5138
|
+
}
|
|
5139
|
+
.tool-btn {
|
|
5140
|
+
font-family: 'JetBrains Mono', monospace;
|
|
5141
|
+
font-size: 0.75rem;
|
|
5142
|
+
font-weight: 600;
|
|
5143
|
+
padding: 0.25rem 0.45rem;
|
|
5144
|
+
background: transparent;
|
|
5145
|
+
border: 1px solid transparent;
|
|
5146
|
+
border-radius: 4px;
|
|
5147
|
+
cursor: pointer;
|
|
5148
|
+
color: var(--mf-slate);
|
|
5149
|
+
transition: all 0.1s ease;
|
|
5150
|
+
}
|
|
5151
|
+
.tool-btn:hover {
|
|
5152
|
+
background: #FFFFFF;
|
|
5153
|
+
border-color: var(--mf-light-border);
|
|
5154
|
+
color: var(--mf-primary-dark);
|
|
5155
|
+
}
|
|
5156
|
+
.tool-divider {
|
|
5157
|
+
width: 1px;
|
|
5158
|
+
height: 16px;
|
|
5159
|
+
background: var(--mf-light-border);
|
|
5160
|
+
margin: 0 0.15rem;
|
|
5161
|
+
}
|
|
5162
|
+
.controls {
|
|
5163
|
+
display: flex;
|
|
5164
|
+
align-items: center;
|
|
5165
|
+
gap: 0.5rem;
|
|
5166
|
+
}
|
|
5167
|
+
.view-toggles {
|
|
5168
|
+
display: flex;
|
|
5169
|
+
background: #F1F5F9;
|
|
5170
|
+
padding: 2px;
|
|
5171
|
+
border-radius: 6px;
|
|
5172
|
+
border: 1px solid var(--mf-light-border);
|
|
5173
|
+
}
|
|
5174
|
+
.toggle-btn {
|
|
5175
|
+
font-family: inherit;
|
|
5176
|
+
font-size: 0.74rem;
|
|
5177
|
+
font-weight: 600;
|
|
5178
|
+
padding: 0.25rem 0.55rem;
|
|
5179
|
+
border: none;
|
|
5180
|
+
background: transparent;
|
|
5181
|
+
border-radius: 4px;
|
|
5182
|
+
cursor: pointer;
|
|
5183
|
+
color: var(--mf-muted);
|
|
5184
|
+
transition: all 0.15s ease;
|
|
5185
|
+
}
|
|
5186
|
+
.toggle-btn.active {
|
|
5187
|
+
background: #FFFFFF;
|
|
5188
|
+
color: var(--mf-dark);
|
|
5189
|
+
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08);
|
|
5190
|
+
}
|
|
5191
|
+
.btn {
|
|
5192
|
+
font-family: inherit;
|
|
5193
|
+
font-size: 0.78rem;
|
|
5194
|
+
font-weight: 600;
|
|
5195
|
+
padding: 0.35rem 0.75rem;
|
|
5196
|
+
border-radius: 6px;
|
|
5197
|
+
cursor: pointer;
|
|
5198
|
+
text-decoration: none;
|
|
5199
|
+
transition: all 0.15s ease;
|
|
5200
|
+
display: inline-flex;
|
|
5201
|
+
align-items: center;
|
|
5202
|
+
gap: 0.3rem;
|
|
5203
|
+
border: 1px solid var(--mf-light-border);
|
|
5204
|
+
background: #FFFFFF;
|
|
5205
|
+
color: var(--mf-dark);
|
|
5206
|
+
}
|
|
5207
|
+
.btn:hover {
|
|
5208
|
+
background: #F8FAFC;
|
|
5209
|
+
border-color: #CBD5E1;
|
|
5210
|
+
}
|
|
5211
|
+
.btn-primary {
|
|
5212
|
+
background: var(--mf-primary);
|
|
5213
|
+
color: #FFFFFF;
|
|
5214
|
+
border-color: var(--mf-primary);
|
|
5215
|
+
}
|
|
5216
|
+
.btn-primary:hover {
|
|
5217
|
+
background: var(--mf-primary-dark);
|
|
5218
|
+
border-color: var(--mf-primary-dark);
|
|
5219
|
+
}
|
|
5220
|
+
.save-indicator {
|
|
5221
|
+
font-size: 0.75rem;
|
|
5222
|
+
font-weight: 600;
|
|
5223
|
+
color: var(--mf-muted);
|
|
5224
|
+
min-width: 65px;
|
|
5225
|
+
text-align: right;
|
|
5226
|
+
}
|
|
5227
|
+
.save-indicator.saved {
|
|
5228
|
+
color: var(--mf-primary-dark);
|
|
5229
|
+
}
|
|
5230
|
+
.save-indicator.saving {
|
|
5231
|
+
color: #D97706;
|
|
5232
|
+
}
|
|
5233
|
+
.save-indicator.unsaved {
|
|
5234
|
+
color: #E11D48;
|
|
5235
|
+
}
|
|
5236
|
+
|
|
5237
|
+
/* Main Workspace Splitter Layout */
|
|
5238
|
+
main.workspace {
|
|
5239
|
+
flex: 1;
|
|
5240
|
+
display: flex;
|
|
5241
|
+
height: calc(100vh - 56px);
|
|
5242
|
+
overflow: hidden;
|
|
5243
|
+
background: var(--mf-bg);
|
|
5244
|
+
position: relative;
|
|
5245
|
+
}
|
|
5246
|
+
.editor-pane {
|
|
5247
|
+
width: 50%;
|
|
5248
|
+
height: 100%;
|
|
5249
|
+
display: flex;
|
|
5250
|
+
flex-direction: column;
|
|
5251
|
+
background: var(--mf-editor-bg);
|
|
5252
|
+
border-right: 1px solid #334155;
|
|
5253
|
+
overflow: hidden;
|
|
5254
|
+
}
|
|
5255
|
+
.editor-header {
|
|
5256
|
+
background: #090D16;
|
|
5257
|
+
border-bottom: 1px solid #1E293B;
|
|
5258
|
+
padding: 0.4rem 0.8rem;
|
|
5259
|
+
display: flex;
|
|
5260
|
+
align-items: center;
|
|
5261
|
+
justify-content: space-between;
|
|
5262
|
+
color: #94A3B8;
|
|
5263
|
+
font-size: 0.74rem;
|
|
5264
|
+
font-weight: 500;
|
|
5265
|
+
}
|
|
5266
|
+
.editor-container {
|
|
5267
|
+
flex: 1;
|
|
5268
|
+
display: flex;
|
|
5269
|
+
position: relative;
|
|
5270
|
+
overflow: hidden;
|
|
5271
|
+
background: var(--mf-editor-bg);
|
|
5272
|
+
}
|
|
5273
|
+
.line-numbers {
|
|
5274
|
+
width: 44px;
|
|
5275
|
+
padding: 0.8rem 0.4rem;
|
|
5276
|
+
font-family: 'JetBrains Mono', monospace;
|
|
5277
|
+
font-size: 13px;
|
|
5278
|
+
line-height: 1.55;
|
|
5279
|
+
color: #475569;
|
|
5280
|
+
text-align: right;
|
|
5281
|
+
user-select: none;
|
|
5282
|
+
background: var(--mf-editor-gutter);
|
|
5283
|
+
overflow: hidden;
|
|
5284
|
+
border-right: 1px solid #1E293B;
|
|
5285
|
+
}
|
|
5286
|
+
.code-editor {
|
|
5287
|
+
flex: 1;
|
|
5288
|
+
padding: 0.8rem 1rem;
|
|
5289
|
+
font-family: 'JetBrains Mono', monospace;
|
|
5290
|
+
font-size: 13px;
|
|
5291
|
+
line-height: 1.55;
|
|
5292
|
+
color: var(--mf-editor-text);
|
|
5293
|
+
background: transparent;
|
|
5294
|
+
border: none;
|
|
5295
|
+
outline: none;
|
|
5296
|
+
resize: none;
|
|
5297
|
+
white-space: pre;
|
|
5298
|
+
overflow-wrap: normal;
|
|
5299
|
+
overflow: auto;
|
|
5300
|
+
tab-size: 2;
|
|
5301
|
+
}
|
|
5302
|
+
|
|
5303
|
+
/* Draggable Splitter Handle */
|
|
5304
|
+
.splitter {
|
|
5305
|
+
width: 8px;
|
|
5306
|
+
cursor: col-resize;
|
|
5307
|
+
background: #E2E8F0;
|
|
5308
|
+
transition: background 0.15s ease;
|
|
5309
|
+
position: relative;
|
|
5310
|
+
z-index: 5;
|
|
5311
|
+
}
|
|
5312
|
+
.splitter:hover, .splitter.active {
|
|
5313
|
+
background: var(--mf-primary);
|
|
5314
|
+
}
|
|
5315
|
+
|
|
5316
|
+
/* Right Preview Pane */
|
|
5317
|
+
.preview-pane {
|
|
5318
|
+
width: 50%;
|
|
5319
|
+
height: 100%;
|
|
5320
|
+
display: flex;
|
|
5321
|
+
flex-direction: column;
|
|
5322
|
+
background: #FFFFFF;
|
|
5323
|
+
overflow: hidden;
|
|
5324
|
+
}
|
|
5325
|
+
.preview-header {
|
|
5326
|
+
background: #FFFFFF;
|
|
5327
|
+
border-bottom: 1px solid var(--mf-light-border);
|
|
5328
|
+
padding: 0.35rem 0.8rem;
|
|
5329
|
+
display: flex;
|
|
5330
|
+
align-items: center;
|
|
5331
|
+
justify-content: space-between;
|
|
5332
|
+
color: var(--mf-muted);
|
|
5333
|
+
font-size: 0.74rem;
|
|
5334
|
+
font-weight: 600;
|
|
5335
|
+
}
|
|
5336
|
+
.viewport-selector {
|
|
5337
|
+
display: flex;
|
|
5338
|
+
gap: 0.25rem;
|
|
5339
|
+
}
|
|
5340
|
+
.vp-btn {
|
|
5341
|
+
font-size: 0.72rem;
|
|
5342
|
+
padding: 0.15rem 0.4rem;
|
|
5343
|
+
border: 1px solid var(--mf-light-border);
|
|
5344
|
+
background: #F8FAFC;
|
|
5345
|
+
border-radius: 4px;
|
|
5346
|
+
cursor: pointer;
|
|
5347
|
+
color: var(--mf-muted);
|
|
5348
|
+
}
|
|
5349
|
+
.vp-btn.active {
|
|
5350
|
+
background: var(--mf-primary-light);
|
|
5351
|
+
color: var(--mf-primary-dark);
|
|
5352
|
+
border-color: var(--mf-primary-border);
|
|
5353
|
+
}
|
|
5354
|
+
.preview-wrapper {
|
|
5355
|
+
flex: 1;
|
|
5356
|
+
display: flex;
|
|
5357
|
+
justify-content: center;
|
|
5358
|
+
align-items: stretch;
|
|
5359
|
+
background: #F1F5F9;
|
|
5360
|
+
overflow: hidden;
|
|
5361
|
+
}
|
|
5362
|
+
iframe {
|
|
5363
|
+
width: 100%;
|
|
5364
|
+
height: 100%;
|
|
5365
|
+
border: none;
|
|
5366
|
+
background: #FFFFFF;
|
|
5367
|
+
transition: max-width 0.2s ease;
|
|
5368
|
+
}
|
|
5369
|
+
.author-footer {
|
|
5370
|
+
font-size: 0.72rem;
|
|
5371
|
+
color: var(--mf-muted);
|
|
5372
|
+
padding-right: 0.5rem;
|
|
5373
|
+
}
|
|
5374
|
+
.author-footer a {
|
|
5375
|
+
color: var(--mf-primary-dark);
|
|
5376
|
+
text-decoration: none;
|
|
5377
|
+
font-weight: 600;
|
|
5378
|
+
}
|
|
5379
|
+
</style>
|
|
5380
|
+
</head>
|
|
5381
|
+
<body>
|
|
5382
|
+
<header>
|
|
5383
|
+
<div class="brand-section">
|
|
5384
|
+
<span class="brand-badge">MARKFORGE STUDIO</span>
|
|
5385
|
+
<span class="file-name" title="${escapeHtml2(absoluteFilePath)}">${escapeHtml2(fileName)}</span>
|
|
5386
|
+
<div class="sync-status">
|
|
5387
|
+
<div class="sync-dot"></div>
|
|
5388
|
+
<span>Live Sync Active</span>
|
|
5389
|
+
</div>
|
|
5390
|
+
</div>
|
|
5391
|
+
|
|
5392
|
+
<!-- Quick Formatting Toolbar -->
|
|
5393
|
+
<div class="toolbar-section">
|
|
5394
|
+
<button class="tool-btn" onclick="insertFormat('h1')" title="Heading 1">H1</button>
|
|
5395
|
+
<button class="tool-btn" onclick="insertFormat('h2')" title="Heading 2">H2</button>
|
|
5396
|
+
<button class="tool-btn" onclick="insertFormat('h3')" title="Heading 3">H3</button>
|
|
5397
|
+
<div class="tool-divider"></div>
|
|
5398
|
+
<button class="tool-btn" onclick="insertFormat('bold')" title="Bold">B</button>
|
|
5399
|
+
<button class="tool-btn" onclick="insertFormat('italic')" title="Italic">I</button>
|
|
5400
|
+
<button class="tool-btn" onclick="insertFormat('code')" title="Inline Code"><></button>
|
|
5401
|
+
<button class="tool-btn" onclick="insertFormat('quote')" title="Blockquote">></button>
|
|
5402
|
+
<div class="tool-divider"></div>
|
|
5403
|
+
<button class="tool-btn" onclick="insertFormat('table')" title="GFM Table">Table</button>
|
|
5404
|
+
<button class="tool-btn" onclick="insertFormat('list')" title="List">List</button>
|
|
5405
|
+
<button class="tool-btn" onclick="insertFormat('task')" title="Task Checklist">Task</button>
|
|
5406
|
+
<div class="tool-divider"></div>
|
|
5407
|
+
<button class="tool-btn" onclick="insertFormat('callout')" title="Callout Box">Callout</button>
|
|
5408
|
+
<button class="tool-btn" onclick="insertFormat('math')" title="LaTeX Math">Math</button>
|
|
5409
|
+
<button class="tool-btn" onclick="insertFormat('columns')" title="Multi-Columns">Columns</button>
|
|
5410
|
+
<button class="tool-btn" onclick="insertFormat('footnote')" title="Footnote">Footnote</button>
|
|
5411
|
+
<button class="tool-btn" onclick="insertFormat('mermaid')" title="Mermaid Diagram">Mermaid</button>
|
|
5412
|
+
</div>
|
|
5413
|
+
|
|
5414
|
+
<!-- Controls & View Mode -->
|
|
5415
|
+
<div class="controls">
|
|
5416
|
+
<div class="view-toggles">
|
|
5417
|
+
<button class="toggle-btn active" id="btn-split" onclick="setViewMode('split')">Split</button>
|
|
5418
|
+
<button class="toggle-btn" id="btn-edit" onclick="setViewMode('edit')">Editor</button>
|
|
5419
|
+
<button class="toggle-btn" id="btn-prev" onclick="setViewMode('prev')">Preview</button>
|
|
5420
|
+
</div>
|
|
5421
|
+
<span class="save-indicator saved" id="save-status">Saved</span>
|
|
5422
|
+
<button class="btn btn-primary" onclick="saveContentManual()" title="Save (Ctrl+S)">Save</button>
|
|
5423
|
+
<button class="btn" onclick="exportDoc('docx')" title="Download Word Document">DOCX</button>
|
|
5424
|
+
<button class="btn" onclick="exportDoc('pdf')" title="Download PDF Document">PDF</button>
|
|
5425
|
+
<button class="btn" onclick="printDoc()" title="Print / PDF dialog">Print</button>
|
|
5426
|
+
</div>
|
|
5427
|
+
</header>
|
|
5428
|
+
|
|
5429
|
+
<main class="workspace" id="workspace">
|
|
5430
|
+
<!-- Left: Code Editor Pane -->
|
|
5431
|
+
<div class="editor-pane" id="editor-pane">
|
|
5432
|
+
<div class="editor-header">
|
|
5433
|
+
<span>MARKDOWN SOURCE</span>
|
|
5434
|
+
<span id="editor-stats">Lines: 1 | Words: 0 | UTF-8</span>
|
|
5435
|
+
</div>
|
|
5436
|
+
<div class="editor-container">
|
|
5437
|
+
<div class="line-numbers" id="line-numbers">1</div>
|
|
5438
|
+
<textarea class="code-editor" id="code-editor" spellcheck="false" placeholder="Write markdown here...">${escapeHtml2(initialContent)}</textarea>
|
|
5439
|
+
</div>
|
|
5440
|
+
</div>
|
|
5441
|
+
|
|
5442
|
+
<!-- Middle: Draggable Splitter Handle -->
|
|
5443
|
+
<div class="splitter" id="splitter"></div>
|
|
5444
|
+
|
|
5445
|
+
<!-- Right: Rendered Preview Pane -->
|
|
5446
|
+
<div class="preview-pane" id="preview-pane">
|
|
5447
|
+
<div class="preview-header">
|
|
5448
|
+
<span>RENDERED PREVIEW</span>
|
|
5449
|
+
<div class="viewport-selector">
|
|
5450
|
+
<button class="vp-btn active" onclick="setViewport('100%')" id="vp-full">100% Full</button>
|
|
5451
|
+
<button class="vp-btn" onclick="setViewport('820px')" id="vp-a4">A4 (820px)</button>
|
|
5452
|
+
<button class="vp-btn" onclick="setViewport('440px')" id="vp-mob">Mobile</button>
|
|
5453
|
+
</div>
|
|
5454
|
+
<span class="author-footer">Created by <a href="https://github.com/masumrpg" target="_blank">Ma'sum (@masumrpg)</a></span>
|
|
5455
|
+
</div>
|
|
5456
|
+
<div class="preview-wrapper">
|
|
5457
|
+
<iframe id="preview-frame" src="/document-content"></iframe>
|
|
5458
|
+
</div>
|
|
5459
|
+
</div>
|
|
5460
|
+
</main>
|
|
5461
|
+
|
|
5462
|
+
<script>
|
|
5463
|
+
var editor = document.getElementById('code-editor');
|
|
5464
|
+
var lineNumbers = document.getElementById('line-numbers');
|
|
5465
|
+
var stats = document.getElementById('editor-stats');
|
|
5466
|
+
var saveStatus = document.getElementById('save-status');
|
|
5467
|
+
var previewFrame = document.getElementById('preview-frame');
|
|
5468
|
+
var editorPane = document.getElementById('editor-pane');
|
|
5469
|
+
var previewPane = document.getElementById('preview-pane');
|
|
5470
|
+
var splitter = document.getElementById('splitter');
|
|
5471
|
+
var isDirty = false;
|
|
5472
|
+
var autoSaveTimeout = null;
|
|
5473
|
+
|
|
5474
|
+
// Update Line Numbers & Stats
|
|
5475
|
+
function updateStatsAndLines() {
|
|
5476
|
+
var lines = editor.value.split('\\n');
|
|
5477
|
+
var lineCount = lines.length;
|
|
5478
|
+
var numHtml = '';
|
|
5479
|
+
for (var i = 1; i <= lineCount; i++) {
|
|
5480
|
+
numHtml += i + '<br>';
|
|
5481
|
+
}
|
|
5482
|
+
lineNumbers.innerHTML = numHtml;
|
|
5483
|
+
|
|
5484
|
+
var words = editor.value.trim().length > 0 ? editor.value.trim().split(/\\s+/).length : 0;
|
|
5485
|
+
var chars = editor.value.length;
|
|
5486
|
+
stats.textContent = 'Lines: ' + lineCount + ' | Words: ' + words + ' | Chars: ' + chars + ' | UTF-8';
|
|
5487
|
+
}
|
|
5488
|
+
|
|
5489
|
+
// Synchronize vertical scroll between Line Numbers and Textarea
|
|
5490
|
+
editor.addEventListener('scroll', function() {
|
|
5491
|
+
lineNumbers.scrollTop = editor.scrollTop;
|
|
5492
|
+
});
|
|
5493
|
+
|
|
5494
|
+
// Handle Input & Debounced Auto-Save
|
|
5495
|
+
editor.addEventListener('input', function() {
|
|
5496
|
+
updateStatsAndLines();
|
|
5497
|
+
setSaveState('unsaved');
|
|
5498
|
+
if (autoSaveTimeout) clearTimeout(autoSaveTimeout);
|
|
5499
|
+
autoSaveTimeout = setTimeout(function() {
|
|
5500
|
+
saveContent();
|
|
5501
|
+
}, 600);
|
|
5502
|
+
});
|
|
5503
|
+
|
|
5504
|
+
function setSaveState(state) {
|
|
5505
|
+
if (state === 'saved') {
|
|
5506
|
+
saveStatus.textContent = 'Saved';
|
|
5507
|
+
saveStatus.className = 'save-indicator saved';
|
|
5508
|
+
isDirty = false;
|
|
5509
|
+
} else if (state === 'saving') {
|
|
5510
|
+
saveStatus.textContent = 'Saving...';
|
|
5511
|
+
saveStatus.className = 'save-indicator saving';
|
|
5512
|
+
} else {
|
|
5513
|
+
saveStatus.textContent = 'Changes...';
|
|
5514
|
+
saveStatus.className = 'save-indicator unsaved';
|
|
5515
|
+
isDirty = true;
|
|
5516
|
+
}
|
|
5517
|
+
}
|
|
5518
|
+
|
|
5519
|
+
// Save Content via API
|
|
5520
|
+
function saveContent(callback) {
|
|
5521
|
+
setSaveState('saving');
|
|
5522
|
+
fetch('/api/save-content', {
|
|
5523
|
+
method: 'POST',
|
|
5524
|
+
headers: { 'Content-Type': 'application/json' },
|
|
5525
|
+
body: JSON.stringify({ content: editor.value }),
|
|
5526
|
+
})
|
|
5527
|
+
.then(function(res) { return res.json(); })
|
|
5528
|
+
.then(function(data) {
|
|
5529
|
+
if (data.success) {
|
|
5530
|
+
setSaveState('saved');
|
|
5531
|
+
if (callback) callback();
|
|
5532
|
+
} else {
|
|
5533
|
+
saveStatus.textContent = 'Save Error';
|
|
5534
|
+
}
|
|
5535
|
+
})
|
|
5536
|
+
.catch(function() {
|
|
5537
|
+
saveStatus.textContent = 'Save Error';
|
|
5538
|
+
});
|
|
5539
|
+
}
|
|
5540
|
+
|
|
5541
|
+
function saveContentManual() {
|
|
5542
|
+
saveContent();
|
|
5543
|
+
}
|
|
5544
|
+
|
|
5545
|
+
// Keyboard Shortcuts: Tab (2 spaces), Shift+Tab, Ctrl+S
|
|
5546
|
+
editor.addEventListener('keydown', function(e) {
|
|
5547
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
|
|
5548
|
+
e.preventDefault();
|
|
5549
|
+
saveContent();
|
|
5550
|
+
return;
|
|
5551
|
+
}
|
|
5552
|
+
|
|
5553
|
+
if (e.key === 'Tab') {
|
|
5554
|
+
e.preventDefault();
|
|
5555
|
+
var start = this.selectionStart;
|
|
5556
|
+
var end = this.selectionEnd;
|
|
5557
|
+
this.value = this.value.substring(0, start) + ' ' + this.value.substring(end);
|
|
5558
|
+
this.selectionStart = this.selectionEnd = start + 2;
|
|
5559
|
+
updateStatsAndLines();
|
|
5560
|
+
setSaveState('unsaved');
|
|
5561
|
+
if (autoSaveTimeout) clearTimeout(autoSaveTimeout);
|
|
5562
|
+
autoSaveTimeout = setTimeout(saveContent, 600);
|
|
5563
|
+
}
|
|
5564
|
+
});
|
|
5565
|
+
|
|
5566
|
+
// Formatting Snippet Injector
|
|
5567
|
+
function insertFormat(type) {
|
|
5568
|
+
var start = editor.selectionStart;
|
|
5569
|
+
var end = editor.selectionEnd;
|
|
5570
|
+
var selected = editor.value.substring(start, end);
|
|
5571
|
+
var replacement = '';
|
|
5572
|
+
|
|
5573
|
+
switch (type) {
|
|
5574
|
+
case 'h1': replacement = '# ' + (selected || 'Heading 1'); break;
|
|
5575
|
+
case 'h2': replacement = '## ' + (selected || 'Heading 2'); break;
|
|
5576
|
+
case 'h3': replacement = '### ' + (selected || 'Heading 3'); break;
|
|
5577
|
+
case 'bold': replacement = '**' + (selected || 'bold text') + '**'; break;
|
|
5578
|
+
case 'italic': replacement = '*' + (selected || 'italic text') + '*'; break;
|
|
5579
|
+
case 'code': replacement = '\`' + (selected || 'inline code') + '\`'; break;
|
|
5580
|
+
case 'quote': replacement = '> ' + (selected || 'Quote text'); break;
|
|
5581
|
+
case 'table':
|
|
5582
|
+
replacement = '\\n| Column 1 | Column 2 | Column 3 |\\n| :--- | :---: | ---: |\\n| Data A | Data B | Data C |\\n| Data D | Data E | Data F |\\n';
|
|
5583
|
+
break;
|
|
5584
|
+
case 'list': replacement = '- ' + (selected || 'List item'); break;
|
|
5585
|
+
case 'task': replacement = '- [ ] ' + (selected || 'Task item'); break;
|
|
5586
|
+
case 'callout':
|
|
5587
|
+
replacement = '> [!NOTE]\\n> ' + (selected || 'This is an important callout note.');
|
|
5588
|
+
break;
|
|
5589
|
+
case 'math':
|
|
5590
|
+
replacement = '$$\\n' + (selected || '\\\\int_{-\\\\infty}^{\\\\infty} e^{-x^2} dx = \\\\sqrt{\\\\pi}') + '\\n$$';
|
|
5591
|
+
break;
|
|
5592
|
+
case 'columns':
|
|
5593
|
+
replacement = ':::columns 2\\n:::col\\n### Left Column\\n' + (selected || 'Content on the left.') + '\\n:::\\n:::col\\n### Right Column\\nContent on the right.\\n:::\\n:::';
|
|
5594
|
+
break;
|
|
5595
|
+
case 'footnote':
|
|
5596
|
+
replacement = (selected || 'Statement with footnote') + '[^1]\\n\\n[^1]: Note description text.';
|
|
5597
|
+
break;
|
|
5598
|
+
case 'mermaid':
|
|
5599
|
+
replacement = '\\n\`\`\`mermaid\\ngraph TD\\n A[Start] --> B(Process)\\n B --> C{Decision}\\n C -->|Yes| D[Done]\\n C -->|No| B\\n\`\`\`\\n';
|
|
5600
|
+
break;
|
|
5601
|
+
}
|
|
5602
|
+
|
|
5603
|
+
editor.value = editor.value.substring(0, start) + replacement + editor.value.substring(end);
|
|
5604
|
+
editor.selectionStart = editor.selectionEnd = start + replacement.length;
|
|
5605
|
+
editor.focus();
|
|
5606
|
+
updateStatsAndLines();
|
|
5607
|
+
setSaveState('unsaved');
|
|
5608
|
+
if (autoSaveTimeout) clearTimeout(autoSaveTimeout);
|
|
5609
|
+
autoSaveTimeout = setTimeout(saveContent, 600);
|
|
5610
|
+
}
|
|
5611
|
+
|
|
5612
|
+
// View Mode Toggle (Split / Editor Only / Preview Only)
|
|
5613
|
+
function setViewMode(mode) {
|
|
5614
|
+
document.getElementById('btn-split').classList.remove('active');
|
|
5615
|
+
document.getElementById('btn-edit').classList.remove('active');
|
|
5616
|
+
document.getElementById('btn-prev').classList.remove('active');
|
|
5617
|
+
|
|
5618
|
+
if (mode === 'split') {
|
|
5619
|
+
document.getElementById('btn-split').classList.add('active');
|
|
5620
|
+
editorPane.style.display = 'flex';
|
|
5621
|
+
editorPane.style.width = '50%';
|
|
5622
|
+
previewPane.style.display = 'flex';
|
|
5623
|
+
previewPane.style.width = '50%';
|
|
5624
|
+
splitter.style.display = 'block';
|
|
5625
|
+
} else if (mode === 'edit') {
|
|
5626
|
+
document.getElementById('btn-edit').classList.add('active');
|
|
5627
|
+
editorPane.style.display = 'flex';
|
|
5628
|
+
editorPane.style.width = '100%';
|
|
5629
|
+
previewPane.style.display = 'none';
|
|
5630
|
+
splitter.style.display = 'none';
|
|
5631
|
+
} else if (mode === 'prev') {
|
|
5632
|
+
document.getElementById('btn-prev').classList.add('active');
|
|
5633
|
+
editorPane.style.display = 'none';
|
|
5634
|
+
previewPane.style.display = 'flex';
|
|
5635
|
+
previewPane.style.width = '100%';
|
|
5636
|
+
splitter.style.display = 'none';
|
|
5637
|
+
}
|
|
5638
|
+
}
|
|
5639
|
+
|
|
5640
|
+
// Viewport Width Resizer
|
|
5641
|
+
function setViewport(width) {
|
|
5642
|
+
document.getElementById('vp-full').classList.remove('active');
|
|
5643
|
+
document.getElementById('vp-a4').classList.remove('active');
|
|
5644
|
+
document.getElementById('vp-mob').classList.remove('active');
|
|
5645
|
+
|
|
5646
|
+
if (width === '100%') {
|
|
5647
|
+
document.getElementById('vp-full').classList.add('active');
|
|
5648
|
+
previewFrame.style.maxWidth = '100%';
|
|
5649
|
+
} else if (width === '820px') {
|
|
5650
|
+
document.getElementById('vp-a4').classList.add('active');
|
|
5651
|
+
previewFrame.style.maxWidth = '820px';
|
|
5652
|
+
} else if (width === '440px') {
|
|
5653
|
+
document.getElementById('vp-mob').classList.add('active');
|
|
5654
|
+
previewFrame.style.maxWidth = '440px';
|
|
5655
|
+
}
|
|
5656
|
+
}
|
|
5657
|
+
|
|
5658
|
+
// Draggable Splitter Handle Logic
|
|
5659
|
+
var isDragging = false;
|
|
5660
|
+
splitter.addEventListener('mousedown', function(e) {
|
|
5661
|
+
isDragging = true;
|
|
5662
|
+
splitter.classList.add('active');
|
|
5663
|
+
document.body.style.cursor = 'col-resize';
|
|
5664
|
+
document.body.style.userSelect = 'none';
|
|
5665
|
+
});
|
|
5666
|
+
|
|
5667
|
+
window.addEventListener('mousemove', function(e) {
|
|
5668
|
+
if (!isDragging) return;
|
|
5669
|
+
var totalWidth = document.getElementById('workspace').clientWidth;
|
|
5670
|
+
var newEditorWidth = (e.clientX / totalWidth) * 100;
|
|
5671
|
+
if (newEditorWidth > 15 && newEditorWidth < 85) {
|
|
5672
|
+
editorPane.style.width = newEditorWidth + '%';
|
|
5673
|
+
previewPane.style.width = (100 - newEditorWidth) + '%';
|
|
5674
|
+
}
|
|
5675
|
+
});
|
|
5676
|
+
|
|
5677
|
+
window.addEventListener('mouseup', function() {
|
|
5678
|
+
if (isDragging) {
|
|
5679
|
+
isDragging = false;
|
|
5680
|
+
splitter.classList.remove('active');
|
|
5681
|
+
document.body.style.cursor = '';
|
|
5682
|
+
document.body.style.userSelect = '';
|
|
5683
|
+
}
|
|
5684
|
+
});
|
|
5685
|
+
|
|
5686
|
+
// Document Print Action
|
|
5687
|
+
function printDoc() {
|
|
5688
|
+
previewFrame.contentWindow.print();
|
|
5689
|
+
}
|
|
5690
|
+
|
|
5691
|
+
// Document Export Action
|
|
5692
|
+
function exportDoc(format) {
|
|
5693
|
+
saveContent(function() {
|
|
5694
|
+
window.location.href = '/api/export?format=' + format;
|
|
5695
|
+
});
|
|
5696
|
+
}
|
|
5697
|
+
|
|
5698
|
+
// Initialize line stats
|
|
5699
|
+
updateStatsAndLines();
|
|
5700
|
+
</script>
|
|
5701
|
+
</body>
|
|
5702
|
+
</html>`;
|
|
5703
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
5704
|
+
res.end(appHtml);
|
|
5705
|
+
return;
|
|
5706
|
+
}
|
|
5707
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
5708
|
+
res.end("Not Found");
|
|
5709
|
+
});
|
|
5710
|
+
return new Promise((resolve6, reject) => {
|
|
5711
|
+
server.listen(port, () => {
|
|
5712
|
+
const url = `http://localhost:${port}`;
|
|
5713
|
+
resolve6({
|
|
5714
|
+
server,
|
|
5715
|
+
port,
|
|
5716
|
+
url,
|
|
5717
|
+
close: async () => {
|
|
5718
|
+
watcher.close();
|
|
5719
|
+
sseClients.forEach((client) => {
|
|
5720
|
+
try {
|
|
5721
|
+
client.end();
|
|
5722
|
+
} catch {
|
|
5723
|
+
}
|
|
5724
|
+
});
|
|
5725
|
+
sseClients.clear();
|
|
5726
|
+
return new Promise((res) => {
|
|
5727
|
+
server.close(() => res());
|
|
5728
|
+
});
|
|
5729
|
+
}
|
|
5730
|
+
});
|
|
5731
|
+
});
|
|
5732
|
+
server.on("error", (err) => {
|
|
5733
|
+
reject(err);
|
|
5734
|
+
});
|
|
5735
|
+
});
|
|
5736
|
+
}
|
|
5737
|
+
function escapeHtml2(str) {
|
|
5738
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
5739
|
+
}
|
|
5740
|
+
|
|
3490
5741
|
// src/config/defineConfig.ts
|
|
3491
5742
|
function defineConfig(config) {
|
|
3492
5743
|
return config;
|
|
3493
5744
|
}
|
|
3494
5745
|
|
|
3495
5746
|
// src/version.ts
|
|
3496
|
-
import * as
|
|
3497
|
-
import * as
|
|
5747
|
+
import * as fs8 from "fs";
|
|
5748
|
+
import * as path8 from "path";
|
|
3498
5749
|
import { fileURLToPath } from "url";
|
|
3499
5750
|
try {
|
|
3500
5751
|
if (typeof globalThis !== "undefined" && (!globalThis.localStorage || typeof globalThis.localStorage.getItem !== "function")) {
|
|
@@ -3516,21 +5767,21 @@ try {
|
|
|
3516
5767
|
}
|
|
3517
5768
|
} catch {
|
|
3518
5769
|
}
|
|
3519
|
-
var FALLBACK_VERSION = "0.
|
|
5770
|
+
var FALLBACK_VERSION = "0.4.0";
|
|
3520
5771
|
function readVersionFromPackageJson(fromDir) {
|
|
3521
5772
|
let currentDir = fromDir;
|
|
3522
5773
|
for (let i = 0; i < 6; i++) {
|
|
3523
5774
|
try {
|
|
3524
|
-
const pkgJsonPath =
|
|
3525
|
-
if (
|
|
3526
|
-
const pkg = JSON.parse(
|
|
5775
|
+
const pkgJsonPath = path8.join(currentDir, "package.json");
|
|
5776
|
+
if (fs8.existsSync(pkgJsonPath)) {
|
|
5777
|
+
const pkg = JSON.parse(fs8.readFileSync(pkgJsonPath, "utf-8"));
|
|
3527
5778
|
if (pkg.name === "@masumdev/markforge" && pkg.version) {
|
|
3528
5779
|
return pkg.version;
|
|
3529
5780
|
}
|
|
3530
5781
|
}
|
|
3531
5782
|
} catch {
|
|
3532
5783
|
}
|
|
3533
|
-
const parentDir =
|
|
5784
|
+
const parentDir = path8.dirname(currentDir);
|
|
3534
5785
|
if (parentDir === currentDir) break;
|
|
3535
5786
|
currentDir = parentDir;
|
|
3536
5787
|
}
|
|
@@ -3541,7 +5792,7 @@ function getPackageDir() {
|
|
|
3541
5792
|
return __dirname;
|
|
3542
5793
|
}
|
|
3543
5794
|
try {
|
|
3544
|
-
return
|
|
5795
|
+
return path8.dirname(fileURLToPath(import.meta.url));
|
|
3545
5796
|
} catch {
|
|
3546
5797
|
return process.cwd();
|
|
3547
5798
|
}
|
|
@@ -3552,6 +5803,7 @@ function getMarkforgeVersion(fromDir = getPackageDir()) {
|
|
|
3552
5803
|
}
|
|
3553
5804
|
export {
|
|
3554
5805
|
DEFAULT_CONFIG,
|
|
5806
|
+
KATEX_INLINE_CSS,
|
|
3555
5807
|
MARKFORGE_VERSION,
|
|
3556
5808
|
Orientation,
|
|
3557
5809
|
OutputFormat,
|
|
@@ -3580,18 +5832,28 @@ export {
|
|
|
3580
5832
|
inlineHtmlImages,
|
|
3581
5833
|
loadConfig,
|
|
3582
5834
|
compileMarkdown as markforge,
|
|
5835
|
+
normalizeBackCover,
|
|
5836
|
+
normalizeCoverPage,
|
|
3583
5837
|
normalizeHeaderFooter,
|
|
3584
5838
|
normalizeHeaderFooterSlot,
|
|
5839
|
+
normalizeNumberHeadings,
|
|
5840
|
+
normalizeSecurity,
|
|
3585
5841
|
normalizeSignatures,
|
|
3586
5842
|
normalizeWatermark,
|
|
3587
5843
|
parseInlineSpans,
|
|
3588
5844
|
parseMarginToTwip2 as parseMarginToTwip,
|
|
5845
|
+
parseMarkdownDocument as parseMarkdown,
|
|
3589
5846
|
parseMarkdownDocument,
|
|
5847
|
+
renderBackCoverHtml,
|
|
5848
|
+
renderCoverPageHtml,
|
|
3590
5849
|
renderInlinesToHtml,
|
|
5850
|
+
renderMathToHtml,
|
|
3591
5851
|
renderMermaidToPng,
|
|
5852
|
+
renderNodesToHtml,
|
|
3592
5853
|
replaceDocumentTokens,
|
|
3593
5854
|
resolveDocumentConfig,
|
|
3594
5855
|
resolveImage,
|
|
3595
5856
|
slugify,
|
|
5857
|
+
startPreviewServer,
|
|
3596
5858
|
tokenizeCodeLine
|
|
3597
5859
|
};
|