@signiphi/pdf-compose 0.1.0-beta.2 → 0.1.0-beta.3
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/dist/index.js +2657 -469
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2645 -472
- package/dist/index.mjs.map +1 -1
- package/dist/styles/index.css +346 -194
- package/package.json +5 -1
- package/src/styles/index.css +140 -68
package/dist/index.mjs
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import * as React12 from 'react';
|
|
2
2
|
import React12__default, { createContext, useCallback, useState, useRef, useEffect, useMemo, useContext } from 'react';
|
|
3
|
-
import { AlertTriangle, RefreshCw, FileText, ChevronDown, Circle, CheckSquare, Calendar, Hash, PenTool, Type, Braces, ChevronLeft, ChevronRight, ZoomOut, ZoomIn, Maximize2, Loader2, Plus,
|
|
3
|
+
import { AlertTriangle, RefreshCw, FileText, ChevronDown, Circle, CheckSquare, Calendar, Hash, PenTool, Type, Braces, Droplets, ChevronLeft, ChevronRight, ZoomOut, ZoomIn, Maximize2, Loader2, Plus, Save, FileDown, CheckCircle2, PanelLeftOpen, PanelLeftClose, Undo, Redo, Bold, Italic, Underline as Underline$1, Code, Heading1, Heading2, Heading3, List, ListOrdered, Quote, Minus, AlignLeft, AlignCenter, AlignRight, Trash2, X, PenLine, Upload, Search, WrapText, Download } from 'lucide-react';
|
|
4
4
|
import { ReactNodeViewRenderer, NodeViewWrapper, useEditor, EditorContent } from '@tiptap/react';
|
|
5
5
|
import StarterKit from '@tiptap/starter-kit';
|
|
6
6
|
import Underline from '@tiptap/extension-underline';
|
|
7
7
|
import TextAlign from '@tiptap/extension-text-align';
|
|
8
8
|
import Placeholder from '@tiptap/extension-placeholder';
|
|
9
|
+
import Table from '@tiptap/extension-table';
|
|
10
|
+
import TableRow from '@tiptap/extension-table-row';
|
|
11
|
+
import TableCell from '@tiptap/extension-table-cell';
|
|
12
|
+
import TableHeader from '@tiptap/extension-table-header';
|
|
9
13
|
import { Node, mergeAttributes } from '@tiptap/core';
|
|
10
14
|
import { clsx } from 'clsx';
|
|
11
15
|
import { twMerge } from 'tailwind-merge';
|
|
12
16
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
13
17
|
import { v4 } from 'uuid';
|
|
14
|
-
import { PDFDocument, StandardFonts, rgb, PDFName as PDFName$1, PDFString as PDFString$1 } from 'pdf-lib';
|
|
18
|
+
import { PDFDocument, StandardFonts, rgb, degrees, PDFName as PDFName$1, PDFString as PDFString$1 } from 'pdf-lib';
|
|
15
19
|
import { NodeSelection } from '@tiptap/pm/state';
|
|
16
20
|
import { Slot } from '@radix-ui/react-slot';
|
|
17
21
|
import { cva } from 'class-variance-authority';
|
|
@@ -21,7 +25,7 @@ import * as LabelPrimitive from '@radix-ui/react-label';
|
|
|
21
25
|
|
|
22
26
|
var __defProp = Object.defineProperty;
|
|
23
27
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
24
|
-
var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);
|
|
28
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
25
29
|
function cn(...inputs) {
|
|
26
30
|
return twMerge(clsx(inputs));
|
|
27
31
|
}
|
|
@@ -213,7 +217,9 @@ var VariableNode = Node.create({
|
|
|
213
217
|
return {
|
|
214
218
|
varName: { default: "" },
|
|
215
219
|
varLabel: { default: "" },
|
|
216
|
-
varDefault: { default: "" }
|
|
220
|
+
varDefault: { default: "" },
|
|
221
|
+
format: { default: "" },
|
|
222
|
+
suppressZero: { default: "" }
|
|
217
223
|
};
|
|
218
224
|
},
|
|
219
225
|
parseHTML() {
|
|
@@ -279,6 +285,169 @@ var VariableNode = Node.create({
|
|
|
279
285
|
};
|
|
280
286
|
}
|
|
281
287
|
});
|
|
288
|
+
var PanelNode = Node.create({
|
|
289
|
+
name: "panel",
|
|
290
|
+
group: "block",
|
|
291
|
+
content: "block+",
|
|
292
|
+
defining: true,
|
|
293
|
+
addAttributes() {
|
|
294
|
+
return {
|
|
295
|
+
title: { default: "" },
|
|
296
|
+
border: { default: "solid" },
|
|
297
|
+
headerStyle: { default: "" }
|
|
298
|
+
};
|
|
299
|
+
},
|
|
300
|
+
parseHTML() {
|
|
301
|
+
return [{
|
|
302
|
+
tag: "div[data-panel-node]",
|
|
303
|
+
contentElement: "div.panel-content"
|
|
304
|
+
}];
|
|
305
|
+
},
|
|
306
|
+
renderHTML({ node, HTMLAttributes }) {
|
|
307
|
+
const { title, border, headerStyle } = node.attrs;
|
|
308
|
+
const classes = ["panel-node"];
|
|
309
|
+
if (border === "dashed") classes.push("panel-border-dashed");
|
|
310
|
+
else if (border === "none") classes.push("panel-border-none");
|
|
311
|
+
else classes.push("panel-border-solid");
|
|
312
|
+
if (headerStyle === "dark") classes.push("panel-dark");
|
|
313
|
+
const attrs = mergeAttributes(
|
|
314
|
+
{ "data-panel-node": "", class: classes.join(" ") },
|
|
315
|
+
HTMLAttributes
|
|
316
|
+
);
|
|
317
|
+
if (title) {
|
|
318
|
+
return [
|
|
319
|
+
"div",
|
|
320
|
+
attrs,
|
|
321
|
+
["div", { class: "panel-title" }, title],
|
|
322
|
+
["div", { class: "panel-content" }, 0]
|
|
323
|
+
];
|
|
324
|
+
}
|
|
325
|
+
return ["div", attrs, ["div", { class: "panel-content" }, 0]];
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
var ColumnsNode = Node.create({
|
|
329
|
+
name: "columns",
|
|
330
|
+
group: "block",
|
|
331
|
+
content: "column+",
|
|
332
|
+
defining: true,
|
|
333
|
+
addAttributes() {
|
|
334
|
+
return {
|
|
335
|
+
split: { default: "50" },
|
|
336
|
+
padX: { default: "0" }
|
|
337
|
+
};
|
|
338
|
+
},
|
|
339
|
+
parseHTML() {
|
|
340
|
+
return [{ tag: "div[data-columns-node]" }];
|
|
341
|
+
},
|
|
342
|
+
renderHTML({ node, HTMLAttributes }) {
|
|
343
|
+
const split = node.attrs.split || "50";
|
|
344
|
+
return ["div", mergeAttributes(
|
|
345
|
+
{
|
|
346
|
+
"data-columns-node": "",
|
|
347
|
+
class: "columns-node",
|
|
348
|
+
style: `--xpc-col-split: ${split}`
|
|
349
|
+
},
|
|
350
|
+
HTMLAttributes
|
|
351
|
+
), 0];
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
var ColumnNode = Node.create({
|
|
355
|
+
name: "column",
|
|
356
|
+
group: "",
|
|
357
|
+
content: "block+",
|
|
358
|
+
defining: true,
|
|
359
|
+
addAttributes() {
|
|
360
|
+
return {
|
|
361
|
+
padTop: { default: 0 }
|
|
362
|
+
};
|
|
363
|
+
},
|
|
364
|
+
parseHTML() {
|
|
365
|
+
return [{ tag: "div[data-column-node]" }];
|
|
366
|
+
},
|
|
367
|
+
renderHTML({ HTMLAttributes }) {
|
|
368
|
+
return ["div", mergeAttributes(
|
|
369
|
+
{ "data-column-node": "", class: "column-node" },
|
|
370
|
+
HTMLAttributes
|
|
371
|
+
), 0];
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
function WatermarkNodeView({ node, selected }) {
|
|
375
|
+
const { text } = node.attrs;
|
|
376
|
+
return /* @__PURE__ */ jsx(NodeViewWrapper, { children: /* @__PURE__ */ jsxs(
|
|
377
|
+
"div",
|
|
378
|
+
{
|
|
379
|
+
"data-watermark-node": "",
|
|
380
|
+
className: cn(
|
|
381
|
+
"inline-flex items-center gap-2 px-3 py-1.5 rounded-md border text-sm my-1",
|
|
382
|
+
"bg-blue-50 text-blue-600 border-blue-200",
|
|
383
|
+
selected && "ring-2 ring-primary ring-offset-1"
|
|
384
|
+
),
|
|
385
|
+
children: [
|
|
386
|
+
/* @__PURE__ */ jsx(Droplets, { size: 14 }),
|
|
387
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
388
|
+
"Watermark: ",
|
|
389
|
+
text || "(empty)"
|
|
390
|
+
] })
|
|
391
|
+
]
|
|
392
|
+
}
|
|
393
|
+
) });
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// src/extensions/watermark-node.ts
|
|
397
|
+
var WatermarkNode = Node.create({
|
|
398
|
+
name: "watermark",
|
|
399
|
+
group: "block",
|
|
400
|
+
atom: true,
|
|
401
|
+
selectable: true,
|
|
402
|
+
draggable: false,
|
|
403
|
+
addAttributes() {
|
|
404
|
+
return {
|
|
405
|
+
text: { default: "" },
|
|
406
|
+
opacity: { default: "0.15" },
|
|
407
|
+
angle: { default: "-45" }
|
|
408
|
+
};
|
|
409
|
+
},
|
|
410
|
+
parseHTML() {
|
|
411
|
+
return [{ tag: "div[data-watermark-node]" }];
|
|
412
|
+
},
|
|
413
|
+
renderHTML({ HTMLAttributes }) {
|
|
414
|
+
return ["div", mergeAttributes({ "data-watermark-node": "" }, HTMLAttributes)];
|
|
415
|
+
},
|
|
416
|
+
addNodeView() {
|
|
417
|
+
return ReactNodeViewRenderer(WatermarkNodeView);
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
var RepeatNode = Node.create({
|
|
421
|
+
name: "repeatBlock",
|
|
422
|
+
group: "block",
|
|
423
|
+
content: "block+",
|
|
424
|
+
addAttributes() {
|
|
425
|
+
return {
|
|
426
|
+
data: { default: "" }
|
|
427
|
+
};
|
|
428
|
+
},
|
|
429
|
+
parseHTML() {
|
|
430
|
+
return [{ tag: "div[data-repeat-node]" }];
|
|
431
|
+
},
|
|
432
|
+
renderHTML({ HTMLAttributes }) {
|
|
433
|
+
return ["div", mergeAttributes({ "data-repeat-node": "" }, HTMLAttributes), 0];
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
var SubtotalsNode = Node.create({
|
|
437
|
+
name: "subtotalsBlock",
|
|
438
|
+
group: "block",
|
|
439
|
+
content: "block+",
|
|
440
|
+
defining: true,
|
|
441
|
+
parseHTML() {
|
|
442
|
+
return [{ tag: "div[data-subtotals-node]" }];
|
|
443
|
+
},
|
|
444
|
+
renderHTML({ HTMLAttributes }) {
|
|
445
|
+
return ["div", mergeAttributes(
|
|
446
|
+
{ "data-subtotals-node": "", class: "subtotals-block" },
|
|
447
|
+
HTMLAttributes
|
|
448
|
+
), 0];
|
|
449
|
+
}
|
|
450
|
+
});
|
|
282
451
|
|
|
283
452
|
// src/types/index.ts
|
|
284
453
|
var FormFieldType = /* @__PURE__ */ ((FormFieldType2) => {
|
|
@@ -381,11 +550,24 @@ function extractVariablesFromContent(content) {
|
|
|
381
550
|
}
|
|
382
551
|
return Array.from(seen.values());
|
|
383
552
|
}
|
|
553
|
+
function applyFormat(value, format) {
|
|
554
|
+
if (format === "phone") {
|
|
555
|
+
const digits = value.replace(/\D/g, "");
|
|
556
|
+
if (digits.length === 10) {
|
|
557
|
+
return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)} - ${digits.slice(6)}`;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return value;
|
|
561
|
+
}
|
|
384
562
|
function replaceVariablesInContent(content, values) {
|
|
385
563
|
function walkNode(node) {
|
|
386
564
|
if (node.type === "variableNode" && node.attrs) {
|
|
387
565
|
const varName = node.attrs.varName;
|
|
388
|
-
|
|
566
|
+
let replacement = values[varName] || node.attrs.varDefault || "";
|
|
567
|
+
const format = node.attrs.format;
|
|
568
|
+
if (format && replacement) {
|
|
569
|
+
replacement = applyFormat(replacement, format);
|
|
570
|
+
}
|
|
389
571
|
const textNode = { type: "text", text: replacement };
|
|
390
572
|
if (node.marks && node.marks.length > 0) {
|
|
391
573
|
textNode.marks = node.marks;
|
|
@@ -399,116 +581,143 @@ function replaceVariablesInContent(content, values) {
|
|
|
399
581
|
}
|
|
400
582
|
return walkNode(content);
|
|
401
583
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
function serializeFieldToken(attrs) {
|
|
405
|
-
const parts = ["field"];
|
|
406
|
-
if (attrs.fieldType) parts.push(`type:${attrs.fieldType}`);
|
|
407
|
-
if (attrs.fieldName) parts.push(`name:${attrs.fieldName}`);
|
|
408
|
-
if (attrs.fieldLabel) parts.push(`label:${attrs.fieldLabel}`);
|
|
409
|
-
if (attrs.fieldId) parts.push(`id:${attrs.fieldId}`);
|
|
410
|
-
if (attrs.required) parts.push(`required:true`);
|
|
411
|
-
if (attrs.options) parts.push(`options:${attrs.options}`);
|
|
412
|
-
if (attrs.fontSize) parts.push(`fontSize:${attrs.fontSize}`);
|
|
413
|
-
if (attrs.placeholder) parts.push(`placeholder:${attrs.placeholder}`);
|
|
414
|
-
if (attrs.defaultValue) parts.push(`defaultValue:${attrs.defaultValue}`);
|
|
415
|
-
if (attrs.multiline) parts.push(`multiline:true`);
|
|
416
|
-
if (attrs.maxLength) parts.push(`maxLength:${attrs.maxLength}`);
|
|
417
|
-
if (attrs.acknowledgements) parts.push(`acks:${btoa(attrs.acknowledgements)}`);
|
|
418
|
-
return `{{${parts.join("|")}}}`;
|
|
584
|
+
function labelToVarName(label) {
|
|
585
|
+
return label.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
419
586
|
}
|
|
420
|
-
function
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
if (attrs.varDefault) parts.push(`default:${attrs.varDefault}`);
|
|
425
|
-
return `{{${parts.join("|")}}}`;
|
|
587
|
+
function isZeroLike(value) {
|
|
588
|
+
if (!value || value.trim() === "") return true;
|
|
589
|
+
const cleaned = value.replace(/[$,\s]/g, "");
|
|
590
|
+
return /^-?0+(\.0+)?$/.test(cleaned);
|
|
426
591
|
}
|
|
427
|
-
function
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
continue;
|
|
592
|
+
function suppressZeroContent(content, values) {
|
|
593
|
+
function hasSuppressedVar(node) {
|
|
594
|
+
if (node.type === "variableNode" && node.attrs?.suppressZero === "true") {
|
|
595
|
+
const varName = node.attrs.varName;
|
|
596
|
+
const val = values[varName] || node.attrs.varDefault || "";
|
|
597
|
+
return isZeroLike(val);
|
|
434
598
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
599
|
+
return (node.content || []).some(hasSuppressedVar);
|
|
600
|
+
}
|
|
601
|
+
function walkNode(node) {
|
|
602
|
+
if (node.type === "table" && node.content) {
|
|
603
|
+
const filtered = node.content.filter((row) => {
|
|
604
|
+
if (row.type !== "tableRow") return true;
|
|
605
|
+
const isHeader = row.content?.[0]?.type === "tableHeader";
|
|
606
|
+
if (isHeader) return true;
|
|
607
|
+
return !hasSuppressedVar(row);
|
|
608
|
+
});
|
|
609
|
+
const bodyRows = filtered.filter(
|
|
610
|
+
(r) => r.type === "tableRow" && r.content?.[0]?.type !== "tableHeader"
|
|
611
|
+
);
|
|
612
|
+
if (bodyRows.length === 0) return null;
|
|
613
|
+
return { ...node, content: filtered };
|
|
447
614
|
}
|
|
448
|
-
if (node.type === "
|
|
449
|
-
|
|
450
|
-
const marks = node.marks || [];
|
|
451
|
-
for (const mark of marks) {
|
|
452
|
-
if (mark.type === "bold") text = `**${text}**`;
|
|
453
|
-
else if (mark.type === "italic") text = `*${text}*`;
|
|
454
|
-
else if (mark.type === "underline") text = `__${text}__`;
|
|
455
|
-
else if (mark.type === "code") text = `\`${text}\``;
|
|
456
|
-
}
|
|
457
|
-
result += text;
|
|
615
|
+
if (node.type === "paragraph" && hasSuppressedVar(node)) {
|
|
616
|
+
return null;
|
|
458
617
|
}
|
|
459
|
-
if (node.
|
|
460
|
-
|
|
618
|
+
if (node.content) {
|
|
619
|
+
const filtered = node.content.map((child) => walkNode(child)).filter((c) => c !== null);
|
|
620
|
+
return { ...node, content: filtered };
|
|
461
621
|
}
|
|
622
|
+
return node;
|
|
462
623
|
}
|
|
463
|
-
return
|
|
624
|
+
return walkNode(content) || content;
|
|
464
625
|
}
|
|
465
|
-
function
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
const inline = serializeInline(node.content);
|
|
474
|
-
return inline;
|
|
475
|
-
}
|
|
476
|
-
case "bulletList": {
|
|
477
|
-
return (node.content || []).map((item) => {
|
|
478
|
-
const inner = (item.content || []).map(serializeBlock).join("\n");
|
|
479
|
-
return `- ${inner}`;
|
|
480
|
-
}).join("\n");
|
|
481
|
-
}
|
|
482
|
-
case "orderedList": {
|
|
483
|
-
return (node.content || []).map((item, i) => {
|
|
484
|
-
const inner = (item.content || []).map(serializeBlock).join("\n");
|
|
485
|
-
return `${i + 1}. ${inner}`;
|
|
486
|
-
}).join("\n");
|
|
487
|
-
}
|
|
488
|
-
case "listItem": {
|
|
489
|
-
return (node.content || []).map(serializeBlock).join("\n");
|
|
626
|
+
function expandRepeatContent(content, values) {
|
|
627
|
+
const enrichedValues = { ...values };
|
|
628
|
+
function cloneNode(node, dataKey, index) {
|
|
629
|
+
if (node.type === "variableNode" && node.attrs?.varName) {
|
|
630
|
+
const oldName = node.attrs.varName;
|
|
631
|
+
const bareName = oldName.replace(/^operation_/, "");
|
|
632
|
+
const newName = `${dataKey}_${index}_${bareName}`;
|
|
633
|
+
return { ...node, attrs: { ...node.attrs, varName: newName } };
|
|
490
634
|
}
|
|
491
|
-
|
|
492
|
-
return
|
|
635
|
+
if (node.content) {
|
|
636
|
+
return { ...node, content: node.content.map((n) => cloneNode(n, dataKey, index)) };
|
|
493
637
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
638
|
+
return { ...node };
|
|
639
|
+
}
|
|
640
|
+
function walkNode(node) {
|
|
641
|
+
if (node.type === "repeatBlock" && node.attrs?.data) {
|
|
642
|
+
const dataKey = node.attrs.data;
|
|
643
|
+
const arrayJson = values[dataKey];
|
|
644
|
+
if (!arrayJson) return [];
|
|
645
|
+
let items;
|
|
646
|
+
try {
|
|
647
|
+
items = JSON.parse(arrayJson);
|
|
648
|
+
} catch {
|
|
649
|
+
return [];
|
|
650
|
+
}
|
|
651
|
+
const sums = /* @__PURE__ */ new Map();
|
|
652
|
+
for (const item of items) {
|
|
653
|
+
for (const [key, val] of Object.entries(item)) {
|
|
654
|
+
const num = parseFloat(val);
|
|
655
|
+
if (!isNaN(num)) {
|
|
656
|
+
sums.set(key, (sums.get(key) || 0) + num);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
const woKeysByNorm = /* @__PURE__ */ new Map();
|
|
661
|
+
for (const k of Object.keys(enrichedValues)) {
|
|
662
|
+
if (k.startsWith("workorder_")) {
|
|
663
|
+
const norm = k.replace(/_/g, "");
|
|
664
|
+
woKeysByNorm.set(norm, k);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
for (const [field, total] of sums) {
|
|
668
|
+
const formatted = total % 1 === 0 ? total.toFixed(2) : total.toFixed(2);
|
|
669
|
+
const directKey = `workorder_${field}`;
|
|
670
|
+
enrichedValues[directKey] = formatted;
|
|
671
|
+
const norm = `workorder${field}`.replace(/_/g, "");
|
|
672
|
+
const existing = woKeysByNorm.get(norm);
|
|
673
|
+
if (existing && existing !== directKey) {
|
|
674
|
+
enrichedValues[existing] = formatted;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
const expanded = [];
|
|
678
|
+
for (let i = 0; i < items.length; i++) {
|
|
679
|
+
const item = items[i];
|
|
680
|
+
for (const [key, val] of Object.entries(item)) {
|
|
681
|
+
enrichedValues[`${dataKey}_${i}_${key}`] = String(val);
|
|
682
|
+
}
|
|
683
|
+
const cloned = (node.content || []).map((n) => cloneNode(n, dataKey, i));
|
|
684
|
+
expanded.push(...cloned);
|
|
685
|
+
}
|
|
686
|
+
return expanded;
|
|
499
687
|
}
|
|
500
|
-
|
|
501
|
-
|
|
688
|
+
if (node.content) {
|
|
689
|
+
const newContent = [];
|
|
690
|
+
for (const child of node.content) {
|
|
691
|
+
const result2 = walkNode(child);
|
|
692
|
+
if (Array.isArray(result2)) newContent.push(...result2);
|
|
693
|
+
else newContent.push(result2);
|
|
694
|
+
}
|
|
695
|
+
return { ...node, content: newContent };
|
|
502
696
|
}
|
|
503
|
-
|
|
504
|
-
return serializeInline(node.content);
|
|
697
|
+
return node;
|
|
505
698
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
699
|
+
const result = walkNode(content);
|
|
700
|
+
return {
|
|
701
|
+
content: Array.isArray(result) ? { ...content, content: result } : result,
|
|
702
|
+
values: enrichedValues
|
|
703
|
+
};
|
|
510
704
|
}
|
|
511
705
|
var TOKEN_REGEX = /\{\{(field|var)\|([^}]+)\}\}/g;
|
|
706
|
+
var DIRECTIVE_OPEN = /^:::(panel|columns|col|watermark|repeat|subtotals)(?:\{([^}]*)\})?$/;
|
|
707
|
+
var DIRECTIVE_CLOSE = /^:::$/;
|
|
708
|
+
function parseDirectiveAttrs(str) {
|
|
709
|
+
const attrs = {};
|
|
710
|
+
if (!str) return attrs;
|
|
711
|
+
const pairs = str.split("|");
|
|
712
|
+
for (const pair of pairs) {
|
|
713
|
+
const colonIdx = pair.indexOf(":");
|
|
714
|
+
if (colonIdx === -1) continue;
|
|
715
|
+
const key = pair.slice(0, colonIdx).trim();
|
|
716
|
+
const value = pair.slice(colonIdx + 1).trim();
|
|
717
|
+
if (key) attrs[key] = value;
|
|
718
|
+
}
|
|
719
|
+
return attrs;
|
|
720
|
+
}
|
|
512
721
|
function injectMarks(token, marks) {
|
|
513
722
|
const existingMatch = token.match(/\|_marks:([^}|]+)/);
|
|
514
723
|
if (existingMatch) {
|
|
@@ -625,6 +834,12 @@ function parseVariableToken(tokenBody) {
|
|
|
625
834
|
case "default":
|
|
626
835
|
attrs.varDefault = value;
|
|
627
836
|
break;
|
|
837
|
+
case "suppress":
|
|
838
|
+
attrs.suppressZero = value === "zero" ? "true" : "";
|
|
839
|
+
break;
|
|
840
|
+
case "format":
|
|
841
|
+
attrs.format = value;
|
|
842
|
+
break;
|
|
628
843
|
case "_marks":
|
|
629
844
|
attrs._marks = value;
|
|
630
845
|
break;
|
|
@@ -666,45 +881,63 @@ function parseInline(text) {
|
|
|
666
881
|
function parseFormattedText(text) {
|
|
667
882
|
if (!text) return [];
|
|
668
883
|
const nodes = [];
|
|
669
|
-
const parts = text.split(/(
|
|
884
|
+
const parts = text.split(/(\*\*\*[^*]+\*\*\*|\*\*[^*]+\*\*|\*[^*]+\*|__[^_]+__|`[^`]+`)/);
|
|
670
885
|
for (const part of parts) {
|
|
671
886
|
if (!part) continue;
|
|
672
|
-
if (part.startsWith("
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
});
|
|
887
|
+
if (part.startsWith("***") && part.endsWith("***")) {
|
|
888
|
+
const inner = part.slice(3, -3);
|
|
889
|
+
if (inner) nodes.push({ type: "text", text: inner, marks: [{ type: "bold" }, { type: "italic" }] });
|
|
890
|
+
} else if (part.startsWith("**") && part.endsWith("**")) {
|
|
891
|
+
const inner = part.slice(2, -2);
|
|
892
|
+
if (inner) nodes.push({ type: "text", text: inner, marks: [{ type: "bold" }] });
|
|
678
893
|
} else if (part.startsWith("*") && part.endsWith("*")) {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
text: part.slice(1, -1),
|
|
682
|
-
marks: [{ type: "italic" }]
|
|
683
|
-
});
|
|
894
|
+
const inner = part.slice(1, -1);
|
|
895
|
+
if (inner) nodes.push({ type: "text", text: inner, marks: [{ type: "italic" }] });
|
|
684
896
|
} else if (part.startsWith("__") && part.endsWith("__")) {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
text: part.slice(2, -2),
|
|
688
|
-
marks: [{ type: "underline" }]
|
|
689
|
-
});
|
|
897
|
+
const inner = part.slice(2, -2);
|
|
898
|
+
if (inner) nodes.push({ type: "text", text: inner, marks: [{ type: "underline" }] });
|
|
690
899
|
} else if (part.startsWith("`") && part.endsWith("`")) {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
text: part.slice(1, -1),
|
|
694
|
-
marks: [{ type: "code" }]
|
|
695
|
-
});
|
|
900
|
+
const inner = part.slice(1, -1);
|
|
901
|
+
if (inner) nodes.push({ type: "text", text: inner, marks: [{ type: "code" }] });
|
|
696
902
|
} else {
|
|
697
903
|
nodes.push({ type: "text", text: part });
|
|
698
904
|
}
|
|
699
905
|
}
|
|
700
906
|
return nodes;
|
|
701
907
|
}
|
|
702
|
-
function
|
|
703
|
-
const
|
|
908
|
+
function splitTableCells(row) {
|
|
909
|
+
const cells = [];
|
|
910
|
+
let current = "";
|
|
911
|
+
let depth = 0;
|
|
912
|
+
for (let i = 0; i < row.length; i++) {
|
|
913
|
+
if (row[i] === "{" && row[i + 1] === "{") {
|
|
914
|
+
depth++;
|
|
915
|
+
current += "{{";
|
|
916
|
+
i++;
|
|
917
|
+
} else if (row[i] === "}" && row[i + 1] === "}") {
|
|
918
|
+
depth = Math.max(0, depth - 1);
|
|
919
|
+
current += "}}";
|
|
920
|
+
i++;
|
|
921
|
+
} else if (row[i] === "|" && depth === 0) {
|
|
922
|
+
cells.push(current.trim());
|
|
923
|
+
current = "";
|
|
924
|
+
} else {
|
|
925
|
+
current += row[i];
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
if (current.trim()) {
|
|
929
|
+
cells.push(current.trim());
|
|
930
|
+
}
|
|
931
|
+
return cells;
|
|
932
|
+
}
|
|
933
|
+
function parseBlocks(lines, startIdx, stopCondition) {
|
|
704
934
|
const content = [];
|
|
705
|
-
let i =
|
|
935
|
+
let i = startIdx;
|
|
706
936
|
while (i < lines.length) {
|
|
707
937
|
const line = lines[i];
|
|
938
|
+
if (stopCondition && stopCondition(line)) {
|
|
939
|
+
break;
|
|
940
|
+
}
|
|
708
941
|
if (line.trim() === "") {
|
|
709
942
|
i++;
|
|
710
943
|
continue;
|
|
@@ -778,41 +1011,218 @@ function markdownToTiptap(markdown) {
|
|
|
778
1011
|
});
|
|
779
1012
|
continue;
|
|
780
1013
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
|
|
1014
|
+
if (line.trimStart().startsWith("|") && line.trimEnd().endsWith("|")) {
|
|
1015
|
+
const tableLines = [];
|
|
1016
|
+
while (i < lines.length) {
|
|
1017
|
+
const tl = lines[i];
|
|
1018
|
+
if (tl.trimStart().startsWith("|") && tl.trimEnd().endsWith("|")) {
|
|
1019
|
+
tableLines.push(tl);
|
|
1020
|
+
i++;
|
|
1021
|
+
} else {
|
|
1022
|
+
break;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
const separatorRegex = /^[\s|]*-+[\s|:-]*$/;
|
|
1026
|
+
const dataLines = tableLines.filter(
|
|
1027
|
+
(tl) => !separatorRegex.test(tl.replace(/^\s*\|/, "").replace(/\|\s*$/, ""))
|
|
1028
|
+
);
|
|
1029
|
+
const firstDataCellsRaw = dataLines.length > 0 ? splitTableCells(dataLines[0]) : [];
|
|
1030
|
+
const isSubtotals = firstDataCellsRaw.length >= 2 && firstDataCellsRaw.every((c) => c.trim() === "_");
|
|
1031
|
+
const isBorderless = isSubtotals || firstDataCellsRaw.length >= 2 && firstDataCellsRaw.every((c) => c.trim() === "");
|
|
1032
|
+
const tableRows = [];
|
|
1033
|
+
let isFirstDataRow = true;
|
|
1034
|
+
for (const tl of tableLines) {
|
|
1035
|
+
if (separatorRegex.test(tl.replace(/^\s*\|/, "").replace(/\|\s*$/, ""))) continue;
|
|
1036
|
+
const cells = splitTableCells(tl).filter((c) => c !== "");
|
|
1037
|
+
if (isBorderless && isFirstDataRow) {
|
|
1038
|
+
isFirstDataRow = false;
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1041
|
+
const cellType = isFirstDataRow && !isBorderless ? "tableHeader" : "tableCell";
|
|
1042
|
+
const rowContent = cells.map((cellText) => ({
|
|
1043
|
+
type: cellType,
|
|
1044
|
+
content: [{ type: "paragraph", content: cellText ? parseInline(cellText) : [] }]
|
|
1045
|
+
}));
|
|
1046
|
+
tableRows.push({ type: "tableRow", content: rowContent });
|
|
1047
|
+
isFirstDataRow = false;
|
|
1048
|
+
}
|
|
1049
|
+
if (tableRows.length > 0) {
|
|
1050
|
+
const tableNode = { type: "table", content: tableRows };
|
|
1051
|
+
if (isBorderless) {
|
|
1052
|
+
tableNode.attrs = { borderless: true, ...isSubtotals ? { subtotals: true } : {} };
|
|
1053
|
+
}
|
|
1054
|
+
content.push(tableNode);
|
|
1055
|
+
}
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
1058
|
+
const directiveMatch = line.trim().match(DIRECTIVE_OPEN);
|
|
1059
|
+
if (directiveMatch) {
|
|
1060
|
+
const directiveType = directiveMatch[1];
|
|
1061
|
+
const rawAttrs = directiveMatch[2] || "";
|
|
1062
|
+
const attrs = parseDirectiveAttrs(rawAttrs);
|
|
1063
|
+
i++;
|
|
1064
|
+
if (directiveType === "watermark") {
|
|
1065
|
+
content.push({
|
|
1066
|
+
type: "watermark",
|
|
1067
|
+
attrs: {
|
|
1068
|
+
text: attrs.text || "",
|
|
1069
|
+
opacity: attrs.opacity || "0.15",
|
|
1070
|
+
angle: attrs.angle || "-45"
|
|
1071
|
+
}
|
|
1072
|
+
});
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
if (directiveType === "panel") {
|
|
1076
|
+
const inner = parseBlocks(lines, i, (l) => DIRECTIVE_CLOSE.test(l.trim()));
|
|
1077
|
+
i = inner.nextIdx;
|
|
1078
|
+
if (i < lines.length && DIRECTIVE_CLOSE.test(lines[i].trim())) {
|
|
1079
|
+
i++;
|
|
1080
|
+
}
|
|
1081
|
+
content.push({
|
|
1082
|
+
type: "panel",
|
|
1083
|
+
attrs: {
|
|
1084
|
+
title: attrs.title || "",
|
|
1085
|
+
border: attrs.border || "solid",
|
|
1086
|
+
headerStyle: attrs.headerStyle || ""
|
|
1087
|
+
},
|
|
1088
|
+
content: inner.blocks.length > 0 ? inner.blocks : [{ type: "paragraph" }]
|
|
1089
|
+
});
|
|
1090
|
+
continue;
|
|
1091
|
+
}
|
|
1092
|
+
if (directiveType === "repeat") {
|
|
1093
|
+
const inner = parseBlocks(lines, i, (l) => DIRECTIVE_CLOSE.test(l.trim()));
|
|
1094
|
+
i = inner.nextIdx;
|
|
1095
|
+
if (i < lines.length && DIRECTIVE_CLOSE.test(lines[i].trim())) {
|
|
1096
|
+
i++;
|
|
1097
|
+
}
|
|
1098
|
+
content.push({
|
|
1099
|
+
type: "repeatBlock",
|
|
1100
|
+
attrs: { data: attrs.data || "" },
|
|
1101
|
+
content: inner.blocks.length > 0 ? inner.blocks : [{ type: "paragraph" }]
|
|
1102
|
+
});
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1105
|
+
if (directiveType === "subtotals") {
|
|
1106
|
+
const inner = parseBlocks(lines, i, (l) => DIRECTIVE_CLOSE.test(l.trim()));
|
|
1107
|
+
i = inner.nextIdx;
|
|
1108
|
+
if (i < lines.length && DIRECTIVE_CLOSE.test(lines[i].trim())) {
|
|
1109
|
+
i++;
|
|
1110
|
+
}
|
|
1111
|
+
content.push({
|
|
1112
|
+
type: "subtotalsBlock",
|
|
1113
|
+
content: inner.blocks.length > 0 ? inner.blocks : [{ type: "paragraph" }]
|
|
1114
|
+
});
|
|
1115
|
+
continue;
|
|
1116
|
+
}
|
|
1117
|
+
if (directiveType === "columns") {
|
|
1118
|
+
const splitVal = attrs.split || "50";
|
|
1119
|
+
const columns = [];
|
|
1120
|
+
while (i < lines.length) {
|
|
1121
|
+
const colLine = lines[i].trim();
|
|
1122
|
+
if (DIRECTIVE_CLOSE.test(colLine)) {
|
|
1123
|
+
if (columns.length > 0) {
|
|
1124
|
+
i++;
|
|
1125
|
+
break;
|
|
1126
|
+
}
|
|
1127
|
+
i++;
|
|
1128
|
+
break;
|
|
1129
|
+
}
|
|
1130
|
+
const colMatch = colLine.match(/^:::col(?:\{([^}]*)\})?$/);
|
|
1131
|
+
if (colMatch) {
|
|
1132
|
+
i++;
|
|
1133
|
+
const colInner = parseBlocks(lines, i, (l) => {
|
|
1134
|
+
const trimmed = l.trim();
|
|
1135
|
+
return DIRECTIVE_CLOSE.test(trimmed) || /^:::col(?:\{[^}]*\})?$/.test(trimmed);
|
|
1136
|
+
});
|
|
1137
|
+
i = colInner.nextIdx;
|
|
1138
|
+
if (i < lines.length && DIRECTIVE_CLOSE.test(lines[i].trim())) {
|
|
1139
|
+
i++;
|
|
1140
|
+
}
|
|
1141
|
+
const colAttrsRaw = colMatch[1] || "";
|
|
1142
|
+
const colAttrs = parseDirectiveAttrs(colAttrsRaw);
|
|
1143
|
+
const padTop = parseFloat(colAttrs.padTop || "0") || 0;
|
|
1144
|
+
columns.push({
|
|
1145
|
+
type: "column",
|
|
1146
|
+
attrs: padTop ? { padTop } : void 0,
|
|
1147
|
+
content: colInner.blocks.length > 0 ? colInner.blocks : [{ type: "paragraph" }]
|
|
1148
|
+
});
|
|
1149
|
+
continue;
|
|
1150
|
+
}
|
|
1151
|
+
if (colLine === "") {
|
|
1152
|
+
i++;
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
1155
|
+
break;
|
|
1156
|
+
}
|
|
1157
|
+
const columnsAttrs = { split: splitVal };
|
|
1158
|
+
if (attrs.padX) columnsAttrs.padX = attrs.padX;
|
|
1159
|
+
content.push({
|
|
1160
|
+
type: "columns",
|
|
1161
|
+
attrs: columnsAttrs,
|
|
1162
|
+
content: columns.length > 0 ? columns : [
|
|
1163
|
+
{ type: "column", content: [{ type: "paragraph" }] },
|
|
1164
|
+
{ type: "column", content: [{ type: "paragraph" }] }
|
|
1165
|
+
]
|
|
1166
|
+
});
|
|
1167
|
+
continue;
|
|
1168
|
+
}
|
|
1169
|
+
if (directiveType === "col") {
|
|
1170
|
+
const inner = parseBlocks(lines, i, (l) => DIRECTIVE_CLOSE.test(l.trim()));
|
|
1171
|
+
i = inner.nextIdx;
|
|
1172
|
+
if (i < lines.length && DIRECTIVE_CLOSE.test(lines[i].trim())) {
|
|
1173
|
+
i++;
|
|
1174
|
+
}
|
|
1175
|
+
for (const block of inner.blocks) {
|
|
1176
|
+
content.push(block);
|
|
1177
|
+
}
|
|
1178
|
+
continue;
|
|
1179
|
+
}
|
|
1180
|
+
continue;
|
|
1181
|
+
}
|
|
1182
|
+
if (DIRECTIVE_CLOSE.test(line.trim())) {
|
|
1183
|
+
i++;
|
|
1184
|
+
continue;
|
|
1185
|
+
}
|
|
1186
|
+
content.push({
|
|
1187
|
+
type: "paragraph",
|
|
1188
|
+
content: parseInline(line)
|
|
1189
|
+
});
|
|
1190
|
+
i++;
|
|
1191
|
+
}
|
|
1192
|
+
return { blocks: content, nextIdx: i };
|
|
1193
|
+
}
|
|
1194
|
+
function markdownToTiptap(markdown) {
|
|
1195
|
+
const lines = markdown.split("\n");
|
|
1196
|
+
const { blocks } = parseBlocks(lines, 0);
|
|
1197
|
+
return { type: "doc", content: blocks };
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// src/utils/error-helpers.ts
|
|
1201
|
+
function getErrorMessage(error) {
|
|
1202
|
+
if (error instanceof Error) return error.message;
|
|
1203
|
+
if (typeof error === "string") return error;
|
|
1204
|
+
try {
|
|
1205
|
+
return JSON.stringify(error);
|
|
1206
|
+
} catch {
|
|
1207
|
+
return String(error);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
function formatError(context, error) {
|
|
1211
|
+
const message = getErrorMessage(error);
|
|
1212
|
+
return `${context}: ${message}`;
|
|
1213
|
+
}
|
|
1214
|
+
var CONTEXT_CHARS = 25;
|
|
1215
|
+
function extractPosition(error, inputLength) {
|
|
1216
|
+
const msg = error.message;
|
|
1217
|
+
const v8 = /position\s+(\d+)/i.exec(msg);
|
|
1218
|
+
if (v8) return Number(v8[1]);
|
|
1219
|
+
if (/unexpected end/i.test(msg) || /unterminated/i.test(msg)) {
|
|
1220
|
+
return inputLength > 0 ? inputLength - 1 : 0;
|
|
1221
|
+
}
|
|
1222
|
+
return null;
|
|
1223
|
+
}
|
|
1224
|
+
function stripNativePosition(msg) {
|
|
1225
|
+
const v8Stripped = msg.replace(/\s+in JSON at position\s+\d+(\s*\(line\s+\d+\s+column\s+\d+\))?/i, "");
|
|
816
1226
|
if (v8Stripped !== msg) return v8Stripped;
|
|
817
1227
|
const ffStripped = msg.replace(/\s+at line\s+\d+\s+column\s+\d+\s+of the JSON data/i, "");
|
|
818
1228
|
if (ffStripped !== msg) return ffStripped;
|
|
@@ -922,10 +1332,12 @@ function buildMetadataObject(fields, actualFieldNames) {
|
|
|
922
1332
|
// src/utils/pdf-generator.ts
|
|
923
1333
|
var PAGE_WIDTH = 595.28;
|
|
924
1334
|
var PAGE_HEIGHT = 841.89;
|
|
925
|
-
var MARGIN =
|
|
1335
|
+
var MARGIN = 40;
|
|
926
1336
|
var CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN;
|
|
927
1337
|
var CONTENT_HEIGHT = PAGE_HEIGHT - 2 * MARGIN;
|
|
928
1338
|
var LINE_HEIGHT_FACTOR = 1.4;
|
|
1339
|
+
var BODY_FONT_SIZE = 10;
|
|
1340
|
+
var DEFAULT_REGION = { leftX: MARGIN, width: CONTENT_WIDTH };
|
|
929
1341
|
var FIELD_DISPLAY = {
|
|
930
1342
|
["text" /* TEXT */]: { label: "Text", width: 120, height: 30 },
|
|
931
1343
|
["signature" /* SIGNATURE */]: { label: "Signature", width: 200, height: 60 },
|
|
@@ -962,11 +1374,12 @@ function ensureSpace(state, neededHeight) {
|
|
|
962
1374
|
state.currentPage = newPage;
|
|
963
1375
|
state.pageIndex++;
|
|
964
1376
|
state.y = 0;
|
|
1377
|
+
renderWatermarksOnPage(newPage, state.fonts, state.watermarkNodes);
|
|
965
1378
|
}
|
|
966
1379
|
}
|
|
967
|
-
function drawText(state, text, font, fontSize, indent = 0) {
|
|
1380
|
+
function drawText(state, text, font, fontSize, indent = 0, region = DEFAULT_REGION) {
|
|
968
1381
|
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
969
|
-
const maxWidth =
|
|
1382
|
+
const maxWidth = region.width - indent;
|
|
970
1383
|
const words = text.split(/\s+/);
|
|
971
1384
|
let line = "";
|
|
972
1385
|
let totalAdvance = 0;
|
|
@@ -977,7 +1390,7 @@ function drawText(state, text, font, fontSize, indent = 0) {
|
|
|
977
1390
|
ensureSpace(state, lineHeight);
|
|
978
1391
|
const pdfY = PAGE_HEIGHT - MARGIN - state.y - fontSize;
|
|
979
1392
|
state.currentPage.drawText(line, {
|
|
980
|
-
x:
|
|
1393
|
+
x: region.leftX + indent,
|
|
981
1394
|
y: pdfY,
|
|
982
1395
|
size: fontSize,
|
|
983
1396
|
font,
|
|
@@ -994,7 +1407,7 @@ function drawText(state, text, font, fontSize, indent = 0) {
|
|
|
994
1407
|
ensureSpace(state, lineHeight);
|
|
995
1408
|
const pdfY = PAGE_HEIGHT - MARGIN - state.y - fontSize;
|
|
996
1409
|
state.currentPage.drawText(line, {
|
|
997
|
-
x:
|
|
1410
|
+
x: region.leftX + indent,
|
|
998
1411
|
y: pdfY,
|
|
999
1412
|
size: fontSize,
|
|
1000
1413
|
font,
|
|
@@ -1038,10 +1451,10 @@ function collectInlineSegments(content, fonts, fontSize) {
|
|
|
1038
1451
|
}
|
|
1039
1452
|
return segments;
|
|
1040
1453
|
}
|
|
1041
|
-
function layoutInlineSegments(state, segments, fontSize, indent = 0) {
|
|
1454
|
+
function layoutInlineSegments(state, segments, fontSize, indent = 0, region = DEFAULT_REGION) {
|
|
1042
1455
|
const textLineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
1043
|
-
const maxX =
|
|
1044
|
-
const startX =
|
|
1456
|
+
const maxX = region.leftX + region.width;
|
|
1457
|
+
const startX = region.leftX + indent;
|
|
1045
1458
|
let currentX = startX;
|
|
1046
1459
|
let currentLineHeight = 0;
|
|
1047
1460
|
let lineHasContent = false;
|
|
@@ -1146,13 +1559,316 @@ function layoutInlineSegments(state, segments, fontSize, indent = 0) {
|
|
|
1146
1559
|
}
|
|
1147
1560
|
flushLine();
|
|
1148
1561
|
}
|
|
1149
|
-
function processInlineContent(state, content, fontSize, indent = 0) {
|
|
1562
|
+
function processInlineContent(state, content, fontSize, indent = 0, region = DEFAULT_REGION) {
|
|
1150
1563
|
if (!content) return;
|
|
1151
1564
|
const segments = collectInlineSegments(content, state.fonts, fontSize);
|
|
1152
1565
|
if (segments.length === 0) return;
|
|
1153
|
-
layoutInlineSegments(state, segments, fontSize, indent);
|
|
1566
|
+
layoutInlineSegments(state, segments, fontSize, indent, region);
|
|
1567
|
+
}
|
|
1568
|
+
function extractCellText(cell) {
|
|
1569
|
+
const parts = [];
|
|
1570
|
+
for (const child of cell.content || []) {
|
|
1571
|
+
if (child.type === "paragraph" || child.type === "heading") {
|
|
1572
|
+
for (const inline of child.content || []) {
|
|
1573
|
+
if (inline.type === "text") {
|
|
1574
|
+
parts.push(inline.text || "");
|
|
1575
|
+
} else if (inline.type === "variableNode") {
|
|
1576
|
+
const label = inline.attrs?.varLabel || inline.attrs?.varName || "Variable";
|
|
1577
|
+
parts.push(`[${label}]`);
|
|
1578
|
+
} else if (inline.type === "fieldNode") {
|
|
1579
|
+
const label = inline.attrs?.fieldLabel || inline.attrs?.fieldName || "Field";
|
|
1580
|
+
parts.push(`[ ${label} ]`);
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
return parts.join("");
|
|
1586
|
+
}
|
|
1587
|
+
function isNumericText(text) {
|
|
1588
|
+
return /^\s*\$?\s*[\d,.]+\s*$/.test(text);
|
|
1589
|
+
}
|
|
1590
|
+
function measureCellHeight(cell, maxWidth, fonts, fontSize, isHeader) {
|
|
1591
|
+
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
1592
|
+
const font = isHeader ? fonts.bold : fonts.regular;
|
|
1593
|
+
let totalHeight = 0;
|
|
1594
|
+
for (const child of cell.content || []) {
|
|
1595
|
+
if (child.type === "paragraph" || child.type === "heading") {
|
|
1596
|
+
const text = extractCellText({ content: [child] });
|
|
1597
|
+
if (!text) {
|
|
1598
|
+
totalHeight += lineHeight;
|
|
1599
|
+
continue;
|
|
1600
|
+
}
|
|
1601
|
+
const words = text.split(/\s+/);
|
|
1602
|
+
let line = "";
|
|
1603
|
+
let lineCount = 0;
|
|
1604
|
+
for (const word of words) {
|
|
1605
|
+
const testLine = line ? `${line} ${word}` : word;
|
|
1606
|
+
const testWidth = font.widthOfTextAtSize(testLine, fontSize);
|
|
1607
|
+
if (testWidth > maxWidth && line) {
|
|
1608
|
+
lineCount++;
|
|
1609
|
+
line = word;
|
|
1610
|
+
} else {
|
|
1611
|
+
line = testLine;
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
if (line) lineCount++;
|
|
1615
|
+
totalHeight += lineCount * lineHeight;
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
return Math.max(totalHeight, lineHeight);
|
|
1619
|
+
}
|
|
1620
|
+
function renderCellContent(state, cell, cellX, cellY, cellWidth, fonts, fontSize, isHeader, rightAlign, textColor = rgb(0, 0, 0)) {
|
|
1621
|
+
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
1622
|
+
const cellPadding = 4;
|
|
1623
|
+
const maxTextWidth = cellWidth - cellPadding * 2;
|
|
1624
|
+
let textX = cellX + cellPadding;
|
|
1625
|
+
let textY = cellY;
|
|
1626
|
+
for (const child of cell.content || []) {
|
|
1627
|
+
if (child.type === "paragraph" || child.type === "heading") {
|
|
1628
|
+
const segments = collectInlineSegments(
|
|
1629
|
+
child.content || [],
|
|
1630
|
+
isHeader ? { regular: fonts.bold, bold: fonts.bold, italic: fonts.boldItalic, boldItalic: fonts.boldItalic } : fonts,
|
|
1631
|
+
fontSize
|
|
1632
|
+
);
|
|
1633
|
+
if (rightAlign && segments.length > 0) {
|
|
1634
|
+
const fullText = segments.filter((s) => s.kind === "text").map((s) => s.text).join("");
|
|
1635
|
+
const textWidth = (isHeader ? fonts.bold : fonts.regular).widthOfTextAtSize(fullText, fontSize);
|
|
1636
|
+
if (textWidth <= maxTextWidth) {
|
|
1637
|
+
const pdfY = PAGE_HEIGHT - textY - fontSize;
|
|
1638
|
+
state.currentPage.drawText(fullText, {
|
|
1639
|
+
x: cellX + cellWidth - cellPadding - textWidth,
|
|
1640
|
+
y: pdfY,
|
|
1641
|
+
size: fontSize,
|
|
1642
|
+
font: isHeader ? fonts.bold : fonts.regular,
|
|
1643
|
+
color: textColor
|
|
1644
|
+
});
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
let currentX = textX;
|
|
1649
|
+
for (const seg of segments) {
|
|
1650
|
+
if (seg.kind === "break") {
|
|
1651
|
+
textY += lineHeight;
|
|
1652
|
+
currentX = textX;
|
|
1653
|
+
continue;
|
|
1654
|
+
}
|
|
1655
|
+
if (seg.kind === "text") {
|
|
1656
|
+
const words = seg.text.split(/(\s+)/);
|
|
1657
|
+
const spaceWidth = seg.font.widthOfTextAtSize(" ", fontSize);
|
|
1658
|
+
for (const token of words) {
|
|
1659
|
+
if (!token) continue;
|
|
1660
|
+
if (/^\s+$/.test(token)) {
|
|
1661
|
+
currentX += spaceWidth;
|
|
1662
|
+
continue;
|
|
1663
|
+
}
|
|
1664
|
+
const wordWidth = seg.font.widthOfTextAtSize(token, fontSize);
|
|
1665
|
+
if (currentX + wordWidth > textX + maxTextWidth && currentX > textX) {
|
|
1666
|
+
textY += lineHeight;
|
|
1667
|
+
currentX = textX;
|
|
1668
|
+
}
|
|
1669
|
+
const pdfY = PAGE_HEIGHT - textY - fontSize;
|
|
1670
|
+
state.currentPage.drawText(token, {
|
|
1671
|
+
x: currentX,
|
|
1672
|
+
y: pdfY,
|
|
1673
|
+
size: fontSize,
|
|
1674
|
+
font: seg.font,
|
|
1675
|
+
color: textColor
|
|
1676
|
+
});
|
|
1677
|
+
currentX += wordWidth;
|
|
1678
|
+
}
|
|
1679
|
+
} else if (seg.kind === "field") {
|
|
1680
|
+
const pdfY = PAGE_HEIGHT - textY - seg.height;
|
|
1681
|
+
if (state.drawFieldPlaceholders) {
|
|
1682
|
+
state.currentPage.drawRectangle({
|
|
1683
|
+
x: currentX,
|
|
1684
|
+
y: pdfY,
|
|
1685
|
+
width: seg.width,
|
|
1686
|
+
height: seg.height,
|
|
1687
|
+
borderColor: rgb(0.5, 0.5, 0.7),
|
|
1688
|
+
borderWidth: 0.5,
|
|
1689
|
+
color: rgb(0.95, 0.95, 1),
|
|
1690
|
+
borderDashArray: [4, 2]
|
|
1691
|
+
});
|
|
1692
|
+
}
|
|
1693
|
+
const fieldId = seg.attrs.fieldId;
|
|
1694
|
+
if (fieldId) {
|
|
1695
|
+
state.fieldPositions.set(fieldId, {
|
|
1696
|
+
x: currentX,
|
|
1697
|
+
y: pdfY,
|
|
1698
|
+
width: seg.width,
|
|
1699
|
+
height: seg.height,
|
|
1700
|
+
page: state.pageIndex + 1
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
currentX += seg.width + 4;
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
function renderTable(state, node, region = DEFAULT_REGION) {
|
|
1710
|
+
const tableRows = node.content || [];
|
|
1711
|
+
if (tableRows.length === 0) return;
|
|
1712
|
+
const borderless = node.attrs?.borderless === true;
|
|
1713
|
+
const subtotals = node.attrs?.subtotals === true;
|
|
1714
|
+
const fontSize = 10;
|
|
1715
|
+
const cellPadding = borderless ? 2 : 4;
|
|
1716
|
+
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
1717
|
+
const rowPadding = cellPadding * 2;
|
|
1718
|
+
const borderColor = rgb(0.75, 0.75, 0.75);
|
|
1719
|
+
const headerBg = rgb(0.3, 0.3, 0.3);
|
|
1720
|
+
const headerTextColor = rgb(1, 1, 1);
|
|
1721
|
+
const firstRow = tableRows[0];
|
|
1722
|
+
const colCount = firstRow.content?.length || 0;
|
|
1723
|
+
if (colCount === 0) return;
|
|
1724
|
+
const colMaxWidths = new Array(colCount).fill(0);
|
|
1725
|
+
for (const row of tableRows) {
|
|
1726
|
+
const cells = row.content || [];
|
|
1727
|
+
for (let c = 0; c < cells.length && c < colCount; c++) {
|
|
1728
|
+
const cell = cells[c];
|
|
1729
|
+
const isHeader = cell.type === "tableHeader";
|
|
1730
|
+
const text = extractCellText(cell);
|
|
1731
|
+
const font = isHeader ? state.fonts.bold : state.fonts.regular;
|
|
1732
|
+
const textWidth = font.widthOfTextAtSize(text, fontSize);
|
|
1733
|
+
colMaxWidths[c] = Math.max(colMaxWidths[c], textWidth + cellPadding * 2 + 4);
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
const totalMeasured = colMaxWidths.reduce((a, b) => a + b, 0);
|
|
1737
|
+
const minColWidth = 40;
|
|
1738
|
+
let colWidths;
|
|
1739
|
+
if (totalMeasured <= region.width) {
|
|
1740
|
+
const scale = region.width / totalMeasured;
|
|
1741
|
+
colWidths = colMaxWidths.map((w) => Math.max(w * scale, minColWidth));
|
|
1742
|
+
} else {
|
|
1743
|
+
colWidths = colMaxWidths.map(
|
|
1744
|
+
(w) => Math.max(w / totalMeasured * region.width, minColWidth)
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1747
|
+
const colSum = colWidths.reduce((a, b) => a + b, 0);
|
|
1748
|
+
if (colSum > 0) {
|
|
1749
|
+
const normFactor = region.width / colSum;
|
|
1750
|
+
colWidths = colWidths.map((w) => w * normFactor);
|
|
1751
|
+
}
|
|
1752
|
+
const headerRow = tableRows[0];
|
|
1753
|
+
const hasHeader = headerRow?.content?.[0]?.type === "tableHeader";
|
|
1754
|
+
function renderRow(row, isHeaderRow) {
|
|
1755
|
+
const cells = row.content || [];
|
|
1756
|
+
const isHeader = isHeaderRow && hasHeader;
|
|
1757
|
+
let rowHeight = lineHeight;
|
|
1758
|
+
for (let c = 0; c < cells.length && c < colCount; c++) {
|
|
1759
|
+
const cellContentWidth = colWidths[c] - cellPadding * 2;
|
|
1760
|
+
const cellH = measureCellHeight(cells[c], cellContentWidth, state.fonts, fontSize, isHeader);
|
|
1761
|
+
rowHeight = Math.max(rowHeight, cellH);
|
|
1762
|
+
}
|
|
1763
|
+
rowHeight += rowPadding;
|
|
1764
|
+
ensureSpace(state, rowHeight);
|
|
1765
|
+
const rowTopY = state.y;
|
|
1766
|
+
let cellX = region.leftX;
|
|
1767
|
+
for (let c = 0; c < cells.length && c < colCount; c++) {
|
|
1768
|
+
const cellW = colWidths[c];
|
|
1769
|
+
const cell = cells[c];
|
|
1770
|
+
const pdfCellTop = PAGE_HEIGHT - MARGIN - rowTopY;
|
|
1771
|
+
const pdfCellBottom = pdfCellTop - rowHeight;
|
|
1772
|
+
if (isHeader && !borderless) {
|
|
1773
|
+
state.currentPage.drawRectangle({
|
|
1774
|
+
x: cellX,
|
|
1775
|
+
y: pdfCellBottom,
|
|
1776
|
+
width: cellW,
|
|
1777
|
+
height: rowHeight,
|
|
1778
|
+
color: headerBg
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
if (!borderless) {
|
|
1782
|
+
state.currentPage.drawRectangle({
|
|
1783
|
+
x: cellX,
|
|
1784
|
+
y: pdfCellBottom,
|
|
1785
|
+
width: cellW,
|
|
1786
|
+
height: rowHeight,
|
|
1787
|
+
borderColor,
|
|
1788
|
+
borderWidth: 0.5
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1791
|
+
const cellText = extractCellText(cell);
|
|
1792
|
+
const rightAlign = !isHeader && isNumericText(cellText);
|
|
1793
|
+
const contentY = MARGIN + rowTopY + cellPadding;
|
|
1794
|
+
const effectiveHeader = isHeader && !borderless;
|
|
1795
|
+
renderCellContent(
|
|
1796
|
+
state,
|
|
1797
|
+
cell,
|
|
1798
|
+
cellX,
|
|
1799
|
+
contentY,
|
|
1800
|
+
cellW,
|
|
1801
|
+
state.fonts,
|
|
1802
|
+
fontSize,
|
|
1803
|
+
effectiveHeader,
|
|
1804
|
+
rightAlign,
|
|
1805
|
+
effectiveHeader ? headerTextColor : rgb(0, 0, 0)
|
|
1806
|
+
);
|
|
1807
|
+
cellX += cellW;
|
|
1808
|
+
}
|
|
1809
|
+
state.y += rowHeight;
|
|
1810
|
+
}
|
|
1811
|
+
if (hasHeader && tableRows.length > 0) {
|
|
1812
|
+
renderRow(tableRows[0], true);
|
|
1813
|
+
}
|
|
1814
|
+
let lastPageIndex = state.pageIndex;
|
|
1815
|
+
const startIdx = hasHeader ? 1 : 0;
|
|
1816
|
+
const bodyRowCount = tableRows.length - startIdx;
|
|
1817
|
+
for (let r = startIdx; r < tableRows.length; r++) {
|
|
1818
|
+
const cells = tableRows[r].content || [];
|
|
1819
|
+
let estRowHeight = lineHeight;
|
|
1820
|
+
for (let c = 0; c < cells.length && c < colCount; c++) {
|
|
1821
|
+
const cellContentWidth = colWidths[c] - cellPadding * 2;
|
|
1822
|
+
const cellH = measureCellHeight(cells[c], cellContentWidth, state.fonts, fontSize, false);
|
|
1823
|
+
estRowHeight = Math.max(estRowHeight, cellH);
|
|
1824
|
+
}
|
|
1825
|
+
estRowHeight += rowPadding;
|
|
1826
|
+
if (state.y + estRowHeight > CONTENT_HEIGHT) {
|
|
1827
|
+
ensureSpace(state, estRowHeight + (hasHeader ? lineHeight + rowPadding : 0));
|
|
1828
|
+
if (state.pageIndex !== lastPageIndex && hasHeader) {
|
|
1829
|
+
renderRow(headerRow, true);
|
|
1830
|
+
lastPageIndex = state.pageIndex;
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
const rowTopYBeforeRender = state.y;
|
|
1834
|
+
renderRow(tableRows[r], false);
|
|
1835
|
+
lastPageIndex = state.pageIndex;
|
|
1836
|
+
if (subtotals && colCount >= 2) {
|
|
1837
|
+
const bodyIdx = r - startIdx;
|
|
1838
|
+
const isFirstBody = bodyIdx === 0;
|
|
1839
|
+
const isLastBody = bodyIdx === bodyRowCount - 1;
|
|
1840
|
+
if (isFirstBody || isLastBody) {
|
|
1841
|
+
const valueStartX = region.leftX + colWidths[0];
|
|
1842
|
+
const valueEndX = region.leftX + colWidths.reduce((a, b) => a + b, 0);
|
|
1843
|
+
const pdfRowTopY = PAGE_HEIGHT - MARGIN - rowTopYBeforeRender;
|
|
1844
|
+
const overlineColor = rgb(0, 0, 0);
|
|
1845
|
+
if (isFirstBody) {
|
|
1846
|
+
state.currentPage.drawLine({
|
|
1847
|
+
start: { x: valueStartX, y: pdfRowTopY },
|
|
1848
|
+
end: { x: valueEndX, y: pdfRowTopY },
|
|
1849
|
+
thickness: 0.5,
|
|
1850
|
+
color: overlineColor
|
|
1851
|
+
});
|
|
1852
|
+
} else if (isLastBody) {
|
|
1853
|
+
state.currentPage.drawLine({
|
|
1854
|
+
start: { x: valueStartX, y: pdfRowTopY + 2 },
|
|
1855
|
+
end: { x: valueEndX, y: pdfRowTopY + 2 },
|
|
1856
|
+
thickness: 0.5,
|
|
1857
|
+
color: overlineColor
|
|
1858
|
+
});
|
|
1859
|
+
state.currentPage.drawLine({
|
|
1860
|
+
start: { x: valueStartX, y: pdfRowTopY },
|
|
1861
|
+
end: { x: valueEndX, y: pdfRowTopY },
|
|
1862
|
+
thickness: 0.5,
|
|
1863
|
+
color: overlineColor
|
|
1864
|
+
});
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
state.y += 3;
|
|
1154
1870
|
}
|
|
1155
|
-
function processBlock(state, node) {
|
|
1871
|
+
function processBlock(state, node, region = DEFAULT_REGION) {
|
|
1156
1872
|
switch (node.type) {
|
|
1157
1873
|
case "heading": {
|
|
1158
1874
|
const level = node.attrs?.level || 1;
|
|
@@ -1169,34 +1885,34 @@ function processBlock(state, node) {
|
|
|
1169
1885
|
boldItalic: state.fonts.boldItalic
|
|
1170
1886
|
};
|
|
1171
1887
|
const segments = collectInlineSegments(node.content, headingFonts, fontSize);
|
|
1172
|
-
layoutInlineSegments(state, segments, fontSize);
|
|
1888
|
+
layoutInlineSegments(state, segments, fontSize, 0, region);
|
|
1173
1889
|
state.y += spacing / 2;
|
|
1174
1890
|
break;
|
|
1175
1891
|
}
|
|
1176
1892
|
case "paragraph": {
|
|
1177
|
-
const fontSize =
|
|
1893
|
+
const fontSize = BODY_FONT_SIZE;
|
|
1178
1894
|
if (!node.content || node.content.length === 0) {
|
|
1179
|
-
state.y += fontSize * LINE_HEIGHT_FACTOR;
|
|
1895
|
+
state.y += fontSize * LINE_HEIGHT_FACTOR * 0.65;
|
|
1180
1896
|
break;
|
|
1181
1897
|
}
|
|
1182
|
-
processInlineContent(state, node.content, fontSize);
|
|
1183
|
-
state.y +=
|
|
1898
|
+
processInlineContent(state, node.content, fontSize, 0, region);
|
|
1899
|
+
state.y += 2;
|
|
1184
1900
|
break;
|
|
1185
1901
|
}
|
|
1186
1902
|
case "bulletList": {
|
|
1187
1903
|
for (const item of node.content || []) {
|
|
1188
1904
|
for (const child of item.content || []) {
|
|
1189
|
-
const fontSize =
|
|
1905
|
+
const fontSize = BODY_FONT_SIZE;
|
|
1190
1906
|
ensureSpace(state, fontSize * LINE_HEIGHT_FACTOR);
|
|
1191
1907
|
const bulletPdfY = PAGE_HEIGHT - MARGIN - state.y - fontSize;
|
|
1192
1908
|
state.currentPage.drawText("\u2022", {
|
|
1193
|
-
x:
|
|
1909
|
+
x: region.leftX + 8,
|
|
1194
1910
|
y: bulletPdfY,
|
|
1195
1911
|
size: fontSize,
|
|
1196
1912
|
font: state.fonts.regular,
|
|
1197
1913
|
color: rgb(0, 0, 0)
|
|
1198
1914
|
});
|
|
1199
|
-
processInlineContent(state, child.content, fontSize, 24);
|
|
1915
|
+
processInlineContent(state, child.content, fontSize, 24, region);
|
|
1200
1916
|
}
|
|
1201
1917
|
}
|
|
1202
1918
|
state.y += 4;
|
|
@@ -1206,17 +1922,17 @@ function processBlock(state, node) {
|
|
|
1206
1922
|
let num = 1;
|
|
1207
1923
|
for (const item of node.content || []) {
|
|
1208
1924
|
for (const child of item.content || []) {
|
|
1209
|
-
const fontSize =
|
|
1925
|
+
const fontSize = BODY_FONT_SIZE;
|
|
1210
1926
|
ensureSpace(state, fontSize * LINE_HEIGHT_FACTOR);
|
|
1211
1927
|
const numPdfY = PAGE_HEIGHT - MARGIN - state.y - fontSize;
|
|
1212
1928
|
state.currentPage.drawText(`${num}.`, {
|
|
1213
|
-
x:
|
|
1929
|
+
x: region.leftX + 4,
|
|
1214
1930
|
y: numPdfY,
|
|
1215
1931
|
size: fontSize,
|
|
1216
1932
|
font: state.fonts.regular,
|
|
1217
1933
|
color: rgb(0, 0, 0)
|
|
1218
1934
|
});
|
|
1219
|
-
processInlineContent(state, child.content, fontSize, 24);
|
|
1935
|
+
processInlineContent(state, child.content, fontSize, 24, region);
|
|
1220
1936
|
}
|
|
1221
1937
|
num++;
|
|
1222
1938
|
}
|
|
@@ -1226,10 +1942,10 @@ function processBlock(state, node) {
|
|
|
1226
1942
|
case "blockquote": {
|
|
1227
1943
|
const startY = state.y;
|
|
1228
1944
|
for (const child of node.content || []) {
|
|
1229
|
-
processInlineContent(state, child.content, 12, 16);
|
|
1945
|
+
processInlineContent(state, child.content, 12, 16, region);
|
|
1230
1946
|
}
|
|
1231
1947
|
const endY = state.y;
|
|
1232
|
-
const barX =
|
|
1948
|
+
const barX = region.leftX + 4;
|
|
1233
1949
|
const barTop = PAGE_HEIGHT - MARGIN - startY;
|
|
1234
1950
|
const barBottom = PAGE_HEIGHT - MARGIN - endY;
|
|
1235
1951
|
state.currentPage.drawLine({
|
|
@@ -1242,16 +1958,16 @@ function processBlock(state, node) {
|
|
|
1242
1958
|
break;
|
|
1243
1959
|
}
|
|
1244
1960
|
case "horizontalRule": {
|
|
1245
|
-
ensureSpace(state,
|
|
1246
|
-
state.y +=
|
|
1961
|
+
ensureSpace(state, 10);
|
|
1962
|
+
state.y += 4;
|
|
1247
1963
|
const ruleY = PAGE_HEIGHT - MARGIN - state.y;
|
|
1248
1964
|
state.currentPage.drawLine({
|
|
1249
|
-
start: { x:
|
|
1250
|
-
end: { x:
|
|
1251
|
-
thickness:
|
|
1252
|
-
color: rgb(0
|
|
1965
|
+
start: { x: region.leftX, y: ruleY },
|
|
1966
|
+
end: { x: region.leftX + region.width, y: ruleY },
|
|
1967
|
+
thickness: 2.5,
|
|
1968
|
+
color: rgb(0, 0, 0)
|
|
1253
1969
|
});
|
|
1254
|
-
state.y +=
|
|
1970
|
+
state.y += 4;
|
|
1255
1971
|
break;
|
|
1256
1972
|
}
|
|
1257
1973
|
case "codeBlock": {
|
|
@@ -1263,9 +1979,9 @@ function processBlock(state, node) {
|
|
|
1263
1979
|
ensureSpace(state, blockHeight);
|
|
1264
1980
|
const boxY = PAGE_HEIGHT - MARGIN - state.y - blockHeight;
|
|
1265
1981
|
state.currentPage.drawRectangle({
|
|
1266
|
-
x:
|
|
1982
|
+
x: region.leftX,
|
|
1267
1983
|
y: boxY,
|
|
1268
|
-
width:
|
|
1984
|
+
width: region.width,
|
|
1269
1985
|
height: blockHeight,
|
|
1270
1986
|
color: rgb(0.95, 0.95, 0.95),
|
|
1271
1987
|
borderColor: rgb(0.85, 0.85, 0.85),
|
|
@@ -1273,18 +1989,292 @@ function processBlock(state, node) {
|
|
|
1273
1989
|
});
|
|
1274
1990
|
state.y += 8;
|
|
1275
1991
|
for (const line of lines) {
|
|
1276
|
-
drawText(state, line || " ", state.fonts.regular, fontSize, 8);
|
|
1992
|
+
drawText(state, line || " ", state.fonts.regular, fontSize, 8, region);
|
|
1277
1993
|
}
|
|
1278
1994
|
state.y += 8;
|
|
1279
1995
|
break;
|
|
1280
1996
|
}
|
|
1997
|
+
case "table": {
|
|
1998
|
+
renderTable(state, node, region);
|
|
1999
|
+
break;
|
|
2000
|
+
}
|
|
2001
|
+
// ─── Layout directives ─────────────────────────────────────────
|
|
2002
|
+
case "panel": {
|
|
2003
|
+
const title = node.attrs?.title || "";
|
|
2004
|
+
const border = node.attrs?.border || "solid";
|
|
2005
|
+
const headerStyle = node.attrs?.headerStyle || "";
|
|
2006
|
+
const padding = 8;
|
|
2007
|
+
const titleFontSize = 10;
|
|
2008
|
+
const titleHeight = title ? titleFontSize * LINE_HEIGHT_FACTOR + 6 : 0;
|
|
2009
|
+
const panelStartY = state.y;
|
|
2010
|
+
const panelStartPage = state.pageIndex;
|
|
2011
|
+
if (title) {
|
|
2012
|
+
state.y += titleHeight;
|
|
2013
|
+
}
|
|
2014
|
+
const isHeaderOnly = border === "none";
|
|
2015
|
+
if (!isHeaderOnly) {
|
|
2016
|
+
state.y += padding;
|
|
2017
|
+
}
|
|
2018
|
+
const innerRegion = {
|
|
2019
|
+
leftX: region.leftX + padding,
|
|
2020
|
+
width: region.width - padding * 2
|
|
2021
|
+
};
|
|
2022
|
+
if (!isHeaderOnly) {
|
|
2023
|
+
for (const child of node.content || []) {
|
|
2024
|
+
processBlock(state, child, innerRegion);
|
|
2025
|
+
}
|
|
2026
|
+
state.y += padding;
|
|
2027
|
+
}
|
|
2028
|
+
const startPage = state.pdfDoc.getPages()[panelStartPage];
|
|
2029
|
+
if (title) {
|
|
2030
|
+
const titleBarY = PAGE_HEIGHT - MARGIN - panelStartY - titleHeight;
|
|
2031
|
+
const isDarkHeader = headerStyle === "dark";
|
|
2032
|
+
startPage.drawRectangle({
|
|
2033
|
+
x: region.leftX,
|
|
2034
|
+
y: titleBarY,
|
|
2035
|
+
width: region.width,
|
|
2036
|
+
height: titleHeight,
|
|
2037
|
+
color: isDarkHeader ? rgb(0.25, 0.25, 0.25) : rgb(0.93, 0.93, 0.93),
|
|
2038
|
+
borderColor: rgb(0.6, 0.6, 0.6),
|
|
2039
|
+
borderWidth: 0.75
|
|
2040
|
+
});
|
|
2041
|
+
startPage.drawText(title, {
|
|
2042
|
+
x: region.leftX + padding,
|
|
2043
|
+
y: titleBarY + 4,
|
|
2044
|
+
size: titleFontSize,
|
|
2045
|
+
font: state.fonts.bold,
|
|
2046
|
+
color: isDarkHeader ? rgb(1, 1, 1) : rgb(0, 0, 0)
|
|
2047
|
+
});
|
|
2048
|
+
}
|
|
2049
|
+
if (border !== "none") {
|
|
2050
|
+
const borderDashArray = border === "dashed" ? [4, 2] : void 0;
|
|
2051
|
+
if (panelStartPage === state.pageIndex) {
|
|
2052
|
+
const borderY = PAGE_HEIGHT - MARGIN - state.y;
|
|
2053
|
+
const panelHeight = state.y - panelStartY;
|
|
2054
|
+
startPage.drawRectangle({
|
|
2055
|
+
x: region.leftX,
|
|
2056
|
+
y: borderY,
|
|
2057
|
+
width: region.width,
|
|
2058
|
+
height: panelHeight,
|
|
2059
|
+
borderColor: rgb(0.6, 0.6, 0.6),
|
|
2060
|
+
borderWidth: 0.75,
|
|
2061
|
+
borderDashArray
|
|
2062
|
+
});
|
|
2063
|
+
} else {
|
|
2064
|
+
const startBorderBottom = MARGIN;
|
|
2065
|
+
const startPanelHeight = PAGE_HEIGHT - MARGIN - panelStartY - startBorderBottom;
|
|
2066
|
+
startPage.drawRectangle({
|
|
2067
|
+
x: region.leftX,
|
|
2068
|
+
y: startBorderBottom,
|
|
2069
|
+
width: region.width,
|
|
2070
|
+
height: startPanelHeight,
|
|
2071
|
+
borderColor: rgb(0.6, 0.6, 0.6),
|
|
2072
|
+
borderWidth: 0.75,
|
|
2073
|
+
borderDashArray
|
|
2074
|
+
});
|
|
2075
|
+
const contTop = PAGE_HEIGHT - MARGIN;
|
|
2076
|
+
const contBorderY = PAGE_HEIGHT - MARGIN - state.y;
|
|
2077
|
+
const contHeight = contTop - contBorderY;
|
|
2078
|
+
state.currentPage.drawRectangle({
|
|
2079
|
+
x: region.leftX,
|
|
2080
|
+
y: contBorderY,
|
|
2081
|
+
width: region.width,
|
|
2082
|
+
height: contHeight,
|
|
2083
|
+
borderColor: rgb(0.6, 0.6, 0.6),
|
|
2084
|
+
borderWidth: 0.75,
|
|
2085
|
+
borderDashArray
|
|
2086
|
+
});
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
state.y += 4;
|
|
2090
|
+
break;
|
|
2091
|
+
}
|
|
2092
|
+
case "columns": {
|
|
2093
|
+
const split = parseInt(node.attrs?.split || "50", 10);
|
|
2094
|
+
const padX = parseFloat(node.attrs?.padX || "0") || 0;
|
|
2095
|
+
const columns = (node.content || []).filter((c) => c.type === "column");
|
|
2096
|
+
if (columns.length === 0) break;
|
|
2097
|
+
const colRegionLeftX = region.leftX + padX;
|
|
2098
|
+
const colRegionWidth = region.width - 2 * padX;
|
|
2099
|
+
const gap = 12;
|
|
2100
|
+
const columnsStartY = state.y;
|
|
2101
|
+
const columnsStartPage = state.pageIndex;
|
|
2102
|
+
let colWidths;
|
|
2103
|
+
if (columns.length === 2) {
|
|
2104
|
+
const firstWidth = (colRegionWidth - gap) * (split / 100);
|
|
2105
|
+
const secondWidth = colRegionWidth - gap - firstWidth;
|
|
2106
|
+
colWidths = [firstWidth, secondWidth];
|
|
2107
|
+
} else {
|
|
2108
|
+
const eachWidth = (colRegionWidth - gap * (columns.length - 1)) / columns.length;
|
|
2109
|
+
colWidths = columns.map(() => eachWidth);
|
|
2110
|
+
}
|
|
2111
|
+
let maxEndY = columnsStartY;
|
|
2112
|
+
let colX = colRegionLeftX;
|
|
2113
|
+
for (let ci = 0; ci < columns.length; ci++) {
|
|
2114
|
+
const col = columns[ci];
|
|
2115
|
+
const colWidth = colWidths[ci];
|
|
2116
|
+
const colRegion = { leftX: colX, width: colWidth };
|
|
2117
|
+
state.y = columnsStartY;
|
|
2118
|
+
state.pageIndex = columnsStartPage;
|
|
2119
|
+
state.currentPage = state.pdfDoc.getPages()[state.pageIndex];
|
|
2120
|
+
const padTop = parseFloat(col.attrs?.padTop || "0") || 0;
|
|
2121
|
+
if (padTop > 0) {
|
|
2122
|
+
state.y += padTop * BODY_FONT_SIZE * LINE_HEIGHT_FACTOR;
|
|
2123
|
+
}
|
|
2124
|
+
for (const child of col.content || []) {
|
|
2125
|
+
processBlock(state, child, colRegion);
|
|
2126
|
+
}
|
|
2127
|
+
maxEndY = Math.max(maxEndY, state.y);
|
|
2128
|
+
colX += colWidth + gap;
|
|
2129
|
+
}
|
|
2130
|
+
state.y = maxEndY;
|
|
2131
|
+
state.y += 4;
|
|
2132
|
+
break;
|
|
2133
|
+
}
|
|
2134
|
+
case "column": {
|
|
2135
|
+
for (const child of node.content || []) {
|
|
2136
|
+
processBlock(state, child, region);
|
|
2137
|
+
}
|
|
2138
|
+
break;
|
|
2139
|
+
}
|
|
2140
|
+
case "watermark": {
|
|
2141
|
+
break;
|
|
2142
|
+
}
|
|
2143
|
+
case "subtotalsBlock": {
|
|
2144
|
+
const stFontSize = BODY_FONT_SIZE;
|
|
2145
|
+
const stLineHeight = stFontSize * LINE_HEIGHT_FACTOR;
|
|
2146
|
+
const valueRightX = region.leftX + region.width;
|
|
2147
|
+
const rows = (node.content || []).filter(
|
|
2148
|
+
(c) => c.type === "paragraph" && c.content?.length
|
|
2149
|
+
);
|
|
2150
|
+
const parsed = [];
|
|
2151
|
+
for (const row of rows) {
|
|
2152
|
+
let label = "";
|
|
2153
|
+
let value = "";
|
|
2154
|
+
for (const seg of row.content || []) {
|
|
2155
|
+
if (seg.type === "text") {
|
|
2156
|
+
const isBold = seg.marks?.some((m) => m.type === "bold");
|
|
2157
|
+
if (isBold) {
|
|
2158
|
+
label += seg.text || "";
|
|
2159
|
+
} else {
|
|
2160
|
+
const t = (seg.text || "").trim();
|
|
2161
|
+
if (t) value += (value ? " " : "") + t;
|
|
2162
|
+
}
|
|
2163
|
+
} else if (seg.type === "variableNode") {
|
|
2164
|
+
const varLabel = seg.attrs?.varLabel || seg.attrs?.varName || "";
|
|
2165
|
+
const isBoldVar = seg.marks?.some((m) => m.type === "bold");
|
|
2166
|
+
if (isBoldVar) {
|
|
2167
|
+
label += varLabel;
|
|
2168
|
+
} else {
|
|
2169
|
+
value += (value ? " " : "") + `[${varLabel}]`;
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
parsed.push({ label: label.trim(), value: value.trim() });
|
|
2174
|
+
}
|
|
2175
|
+
let maxLabelWidth = 0;
|
|
2176
|
+
for (const { label } of parsed) {
|
|
2177
|
+
if (label) {
|
|
2178
|
+
const w = state.fonts.bold.widthOfTextAtSize(label, stFontSize);
|
|
2179
|
+
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
const gap = 12;
|
|
2183
|
+
const labelX = region.width > 300 ? Math.max(region.leftX + region.width - maxLabelWidth - gap - 80, region.leftX + region.width * 0.4) : region.leftX + 4;
|
|
2184
|
+
const valueColStartX = labelX + maxLabelWidth + gap;
|
|
2185
|
+
const isLastRowTotal = parsed.length > 1;
|
|
2186
|
+
for (let ri = 0; ri < parsed.length; ri++) {
|
|
2187
|
+
const isTotal = isLastRowTotal && ri === parsed.length - 1;
|
|
2188
|
+
if (isTotal) {
|
|
2189
|
+
state.y += stLineHeight * 0.6;
|
|
2190
|
+
}
|
|
2191
|
+
ensureSpace(state, stLineHeight + (isTotal ? 8 : 4));
|
|
2192
|
+
const { label, value } = parsed[ri];
|
|
2193
|
+
const pdfY = PAGE_HEIGHT - MARGIN - state.y - stFontSize;
|
|
2194
|
+
if (isTotal) {
|
|
2195
|
+
state.currentPage.drawLine({
|
|
2196
|
+
start: { x: valueColStartX, y: pdfY + stFontSize + 6 },
|
|
2197
|
+
end: { x: valueRightX, y: pdfY + stFontSize + 6 },
|
|
2198
|
+
thickness: 0.75,
|
|
2199
|
+
color: rgb(0, 0, 0)
|
|
2200
|
+
});
|
|
2201
|
+
state.currentPage.drawLine({
|
|
2202
|
+
start: { x: valueColStartX, y: pdfY + stFontSize + 3 },
|
|
2203
|
+
end: { x: valueRightX, y: pdfY + stFontSize + 3 },
|
|
2204
|
+
thickness: 0.75,
|
|
2205
|
+
color: rgb(0, 0, 0)
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2208
|
+
if (label) {
|
|
2209
|
+
state.currentPage.drawText(label, {
|
|
2210
|
+
x: labelX,
|
|
2211
|
+
y: pdfY,
|
|
2212
|
+
size: stFontSize,
|
|
2213
|
+
font: state.fonts.bold,
|
|
2214
|
+
color: rgb(0, 0, 0)
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
if (value) {
|
|
2218
|
+
const valueFont = state.fonts.bold;
|
|
2219
|
+
const valueWidth = valueFont.widthOfTextAtSize(value, stFontSize);
|
|
2220
|
+
state.currentPage.drawText(value, {
|
|
2221
|
+
x: valueRightX - valueWidth,
|
|
2222
|
+
y: pdfY,
|
|
2223
|
+
size: stFontSize,
|
|
2224
|
+
font: valueFont,
|
|
2225
|
+
color: rgb(0, 0, 0)
|
|
2226
|
+
});
|
|
2227
|
+
}
|
|
2228
|
+
state.y += stLineHeight;
|
|
2229
|
+
}
|
|
2230
|
+
state.y += 4;
|
|
2231
|
+
break;
|
|
2232
|
+
}
|
|
2233
|
+
case "repeatBlock":
|
|
2234
|
+
if (node.content) {
|
|
2235
|
+
for (const child of node.content) {
|
|
2236
|
+
processBlock(state, child, region);
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
break;
|
|
1281
2240
|
default:
|
|
1282
2241
|
if (node.content) {
|
|
1283
|
-
processInlineContent(state, node.content, 12);
|
|
2242
|
+
processInlineContent(state, node.content, 12, 0, region);
|
|
1284
2243
|
}
|
|
1285
2244
|
break;
|
|
1286
2245
|
}
|
|
1287
2246
|
}
|
|
2247
|
+
function renderWatermarksOnPage(page, fonts, watermarkNodes) {
|
|
2248
|
+
for (const wmNode of watermarkNodes) {
|
|
2249
|
+
const text = wmNode.attrs?.text || "";
|
|
2250
|
+
if (!text) continue;
|
|
2251
|
+
const opacity = parseFloat(wmNode.attrs?.opacity || "0.15");
|
|
2252
|
+
const angle = parseFloat(wmNode.attrs?.angle || "-45");
|
|
2253
|
+
const fontSize = 100;
|
|
2254
|
+
const { width, height } = page.getSize();
|
|
2255
|
+
const centerX = width / 2;
|
|
2256
|
+
const centerY = height / 2;
|
|
2257
|
+
const textWidth = fonts.bold.widthOfTextAtSize(text, fontSize);
|
|
2258
|
+
page.drawText(text, {
|
|
2259
|
+
x: centerX - textWidth / 2 * Math.cos(angle * Math.PI / 180),
|
|
2260
|
+
y: centerY - textWidth / 2 * Math.sin(angle * Math.PI / 180),
|
|
2261
|
+
size: fontSize,
|
|
2262
|
+
font: fonts.bold,
|
|
2263
|
+
color: rgb(0.5, 0.5, 0.5),
|
|
2264
|
+
opacity,
|
|
2265
|
+
rotate: degrees(angle)
|
|
2266
|
+
});
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
function collectWatermarkNodes(content) {
|
|
2270
|
+
const nodes = [];
|
|
2271
|
+
for (const block of content.content || []) {
|
|
2272
|
+
if (block.type === "watermark") {
|
|
2273
|
+
nodes.push(block);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
return nodes;
|
|
2277
|
+
}
|
|
1288
2278
|
function ensureFieldSuffix(fieldName, suffix) {
|
|
1289
2279
|
const cleanName = fieldName.replace(/_signature$/i, "").replace(/_initials$/i, "").replace(/_date$/i, "");
|
|
1290
2280
|
return `${cleanName}${suffix}`;
|
|
@@ -1513,6 +2503,7 @@ async function generatePdfFromContent(content, options = {}) {
|
|
|
1513
2503
|
italic: await pdfDoc.embedFont(StandardFonts.HelveticaOblique),
|
|
1514
2504
|
boldItalic: await pdfDoc.embedFont(StandardFonts.HelveticaBoldOblique)
|
|
1515
2505
|
};
|
|
2506
|
+
const watermarkNodes = collectWatermarkNodes(content);
|
|
1516
2507
|
const state = {
|
|
1517
2508
|
currentPage: firstPage,
|
|
1518
2509
|
pageIndex: 0,
|
|
@@ -1520,13 +2511,30 @@ async function generatePdfFromContent(content, options = {}) {
|
|
|
1520
2511
|
fonts,
|
|
1521
2512
|
pdfDoc,
|
|
1522
2513
|
fieldPositions: /* @__PURE__ */ new Map(),
|
|
1523
|
-
drawFieldPlaceholders
|
|
2514
|
+
drawFieldPlaceholders,
|
|
2515
|
+
watermarkNodes
|
|
1524
2516
|
};
|
|
2517
|
+
renderWatermarksOnPage(firstPage, fonts, watermarkNodes);
|
|
1525
2518
|
if (content.content) {
|
|
1526
2519
|
for (const block of content.content) {
|
|
1527
2520
|
processBlock(state, block);
|
|
1528
2521
|
}
|
|
1529
2522
|
}
|
|
2523
|
+
const allPages = pdfDoc.getPages();
|
|
2524
|
+
const totalPages = allPages.length;
|
|
2525
|
+
const pageNumFontSize = 9;
|
|
2526
|
+
for (let i = 0; i < totalPages; i++) {
|
|
2527
|
+
const page = allPages[i];
|
|
2528
|
+
const label = `Page ${i + 1} of ${totalPages}`;
|
|
2529
|
+
const labelWidth = fonts.bold.widthOfTextAtSize(label, pageNumFontSize);
|
|
2530
|
+
page.drawText(label, {
|
|
2531
|
+
x: PAGE_WIDTH - MARGIN - labelWidth,
|
|
2532
|
+
y: MARGIN / 2 - pageNumFontSize / 2,
|
|
2533
|
+
size: pageNumFontSize,
|
|
2534
|
+
font: fonts.bold,
|
|
2535
|
+
color: rgb(0, 0, 0)
|
|
2536
|
+
});
|
|
2537
|
+
}
|
|
1530
2538
|
let fieldWarnings;
|
|
1531
2539
|
if (embedFormFields && fields.length > 0) {
|
|
1532
2540
|
const warnings = await addFormFields(pdfDoc, state.fieldPositions, fields);
|
|
@@ -1540,32 +2548,259 @@ async function generatePdfFromContent(content, options = {}) {
|
|
|
1540
2548
|
};
|
|
1541
2549
|
}
|
|
1542
2550
|
|
|
1543
|
-
// src/utils/
|
|
1544
|
-
async function
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
2551
|
+
// src/utils/template-pipeline.ts
|
|
2552
|
+
async function generatePdfFromTiptap(content, values) {
|
|
2553
|
+
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, values);
|
|
2554
|
+
const suppressed = suppressZeroContent(expanded, enrichedValues);
|
|
2555
|
+
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
2556
|
+
const result = await generatePdfFromContent(replaced);
|
|
2557
|
+
return { pdfBytes: result.pdfBytes };
|
|
2558
|
+
}
|
|
2559
|
+
async function generatePdfFromMarkdown(markdown, values) {
|
|
2560
|
+
const content = markdownToTiptap(markdown);
|
|
2561
|
+
return generatePdfFromTiptap(content, values);
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
// src/utils/markdown-writer.ts
|
|
2565
|
+
function serializeFieldToken(attrs) {
|
|
2566
|
+
const parts = ["field"];
|
|
2567
|
+
if (attrs.fieldType) parts.push(`type:${attrs.fieldType}`);
|
|
2568
|
+
if (attrs.fieldName) parts.push(`name:${attrs.fieldName}`);
|
|
2569
|
+
if (attrs.fieldLabel) parts.push(`label:${attrs.fieldLabel}`);
|
|
2570
|
+
if (attrs.fieldId) parts.push(`id:${attrs.fieldId}`);
|
|
2571
|
+
if (attrs.required) parts.push(`required:true`);
|
|
2572
|
+
if (attrs.options) parts.push(`options:${attrs.options}`);
|
|
2573
|
+
if (attrs.fontSize) parts.push(`fontSize:${attrs.fontSize}`);
|
|
2574
|
+
if (attrs.placeholder) parts.push(`placeholder:${attrs.placeholder}`);
|
|
2575
|
+
if (attrs.defaultValue) parts.push(`defaultValue:${attrs.defaultValue}`);
|
|
2576
|
+
if (attrs.multiline) parts.push(`multiline:true`);
|
|
2577
|
+
if (attrs.maxLength) parts.push(`maxLength:${attrs.maxLength}`);
|
|
2578
|
+
if (attrs.acknowledgements) parts.push(`acks:${btoa(attrs.acknowledgements)}`);
|
|
2579
|
+
return `{{${parts.join("|")}}}`;
|
|
2580
|
+
}
|
|
2581
|
+
function serializeVariableToken(attrs) {
|
|
2582
|
+
const parts = ["var"];
|
|
2583
|
+
if (attrs.varName) parts.push(`name:${attrs.varName}`);
|
|
2584
|
+
if (attrs.varLabel) parts.push(`label:${attrs.varLabel}`);
|
|
2585
|
+
if (attrs.varDefault) parts.push(`default:${attrs.varDefault}`);
|
|
2586
|
+
if (attrs.suppressZero === "true") parts.push(`suppress:zero`);
|
|
2587
|
+
if (attrs.format) parts.push(`format:${attrs.format}`);
|
|
2588
|
+
return `{{${parts.join("|")}}}`;
|
|
2589
|
+
}
|
|
2590
|
+
function serializeInline(content) {
|
|
2591
|
+
if (!content) return "";
|
|
2592
|
+
let result = "";
|
|
2593
|
+
for (const node of content) {
|
|
2594
|
+
if (node.type === "fieldNode") {
|
|
2595
|
+
result += serializeFieldToken(node.attrs || {});
|
|
2596
|
+
continue;
|
|
2597
|
+
}
|
|
2598
|
+
if (node.type === "variableNode") {
|
|
2599
|
+
let token = serializeVariableToken(node.attrs || {});
|
|
2600
|
+
const varMarks = node.marks || [];
|
|
2601
|
+
const isBold = varMarks.some((m) => m.type === "bold");
|
|
2602
|
+
const isItalic = varMarks.some((m) => m.type === "italic");
|
|
2603
|
+
const isUnderline = varMarks.some((m) => m.type === "underline");
|
|
2604
|
+
if (isBold && isItalic) token = `***${token}***`;
|
|
2605
|
+
else if (isBold) token = `**${token}**`;
|
|
2606
|
+
else if (isItalic) token = `*${token}*`;
|
|
2607
|
+
if (isUnderline) token = `__${token}__`;
|
|
2608
|
+
result += token;
|
|
2609
|
+
continue;
|
|
2610
|
+
}
|
|
2611
|
+
if (node.type === "text") {
|
|
2612
|
+
let text = node.text || "";
|
|
2613
|
+
const marks = node.marks || [];
|
|
2614
|
+
for (const mark of marks) {
|
|
2615
|
+
if (mark.type === "bold") text = `**${text}**`;
|
|
2616
|
+
else if (mark.type === "italic") text = `*${text}*`;
|
|
2617
|
+
else if (mark.type === "underline") text = `__${text}__`;
|
|
2618
|
+
else if (mark.type === "code") text = `\`${text}\``;
|
|
2619
|
+
}
|
|
2620
|
+
result += text;
|
|
2621
|
+
}
|
|
2622
|
+
if (node.type === "hardBreak") {
|
|
2623
|
+
result += " \n";
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
return result;
|
|
2627
|
+
}
|
|
2628
|
+
function serializeBlock(node) {
|
|
2629
|
+
switch (node.type) {
|
|
2630
|
+
case "heading": {
|
|
2631
|
+
const level = node.attrs?.level || 1;
|
|
2632
|
+
const prefix = "#".repeat(level);
|
|
2633
|
+
return `${prefix} ${serializeInline(node.content)}`;
|
|
2634
|
+
}
|
|
2635
|
+
case "paragraph": {
|
|
2636
|
+
const inline = serializeInline(node.content);
|
|
2637
|
+
return inline;
|
|
2638
|
+
}
|
|
2639
|
+
case "bulletList": {
|
|
2640
|
+
return (node.content || []).map((item) => {
|
|
2641
|
+
const inner = (item.content || []).map(serializeBlock).join("\n");
|
|
2642
|
+
return `- ${inner}`;
|
|
2643
|
+
}).join("\n");
|
|
2644
|
+
}
|
|
2645
|
+
case "orderedList": {
|
|
2646
|
+
return (node.content || []).map((item, i) => {
|
|
2647
|
+
const inner = (item.content || []).map(serializeBlock).join("\n");
|
|
2648
|
+
return `${i + 1}. ${inner}`;
|
|
2649
|
+
}).join("\n");
|
|
2650
|
+
}
|
|
2651
|
+
case "listItem": {
|
|
2652
|
+
return (node.content || []).map(serializeBlock).join("\n");
|
|
2653
|
+
}
|
|
2654
|
+
case "blockquote": {
|
|
2655
|
+
return (node.content || []).map(serializeBlock).map((line) => `> ${line}`).join("\n");
|
|
2656
|
+
}
|
|
2657
|
+
case "codeBlock": {
|
|
2658
|
+
const text = serializeInline(node.content);
|
|
2659
|
+
return `\`\`\`
|
|
2660
|
+
${text}
|
|
2661
|
+
\`\`\``;
|
|
2662
|
+
}
|
|
2663
|
+
case "horizontalRule": {
|
|
2664
|
+
return "---";
|
|
2665
|
+
}
|
|
2666
|
+
case "table": {
|
|
2667
|
+
const rows = node.content || [];
|
|
2668
|
+
const isBorderless = node.attrs?.borderless === true;
|
|
2669
|
+
const isSubtotals = node.attrs?.subtotals === true;
|
|
2670
|
+
const serializedRows = [];
|
|
2671
|
+
if (isBorderless && rows.length > 0) {
|
|
2672
|
+
const colCount = rows[0].content?.length || 0;
|
|
2673
|
+
const markerCell = isSubtotals ? "_" : "";
|
|
2674
|
+
const markerRow = "| " + new Array(colCount).fill(markerCell).join(" | ") + " |";
|
|
2675
|
+
const sep = "|" + new Array(colCount).fill("---").join("|") + "|";
|
|
2676
|
+
serializedRows.push(markerRow);
|
|
2677
|
+
serializedRows.push(sep);
|
|
2678
|
+
}
|
|
2679
|
+
for (let ri = 0; ri < rows.length; ri++) {
|
|
2680
|
+
const row = rows[ri];
|
|
2681
|
+
const cells = (row.content || []).map((cell) => {
|
|
2682
|
+
const inner = (cell.content || []).map(serializeBlock).join("");
|
|
2683
|
+
return inner;
|
|
2684
|
+
});
|
|
2685
|
+
serializedRows.push(`| ${cells.join(" | ")} |`);
|
|
2686
|
+
if (!isBorderless && ri === 0 && row.content?.[0]?.type === "tableHeader") {
|
|
2687
|
+
const sep = cells.map(() => "------").join("|");
|
|
2688
|
+
serializedRows.push(`|${sep}|`);
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
return serializedRows.join("\n");
|
|
2692
|
+
}
|
|
2693
|
+
case "tableRow": {
|
|
2694
|
+
const cells = (node.content || []).map((cell) => {
|
|
2695
|
+
const inner = (cell.content || []).map(serializeBlock).join("");
|
|
2696
|
+
return inner;
|
|
2697
|
+
});
|
|
2698
|
+
return `| ${cells.join(" | ")} |`;
|
|
2699
|
+
}
|
|
2700
|
+
case "tableHeader":
|
|
2701
|
+
case "tableCell": {
|
|
2702
|
+
return (node.content || []).map(serializeBlock).join("");
|
|
2703
|
+
}
|
|
2704
|
+
case "watermark": {
|
|
2705
|
+
const attrs = node.attrs || {};
|
|
2706
|
+
const parts = [];
|
|
2707
|
+
if (attrs.text) parts.push(`text:${attrs.text}`);
|
|
2708
|
+
if (attrs.opacity && attrs.opacity !== "0.15") parts.push(`opacity:${attrs.opacity}`);
|
|
2709
|
+
if (attrs.angle && attrs.angle !== "-45") parts.push(`angle:${attrs.angle}`);
|
|
2710
|
+
return `:::watermark{${parts.join("|")}}`;
|
|
2711
|
+
}
|
|
2712
|
+
case "panel": {
|
|
2713
|
+
const attrs = node.attrs || {};
|
|
2714
|
+
const parts = [];
|
|
2715
|
+
if (attrs.title) parts.push(`title:${attrs.title}`);
|
|
2716
|
+
if (attrs.border && attrs.border !== "solid") parts.push(`border:${attrs.border}`);
|
|
2717
|
+
else if (attrs.border === "solid") parts.push(`border:solid`);
|
|
2718
|
+
if (attrs.headerStyle) parts.push(`headerStyle:${attrs.headerStyle}`);
|
|
2719
|
+
const attrStr = parts.length > 0 ? `{${parts.join("|")}}` : "";
|
|
2720
|
+
const children = (node.content || []).map(serializeBlock).join("\n\n");
|
|
2721
|
+
return `:::panel${attrStr}
|
|
2722
|
+
${children}
|
|
2723
|
+
:::`;
|
|
2724
|
+
}
|
|
2725
|
+
case "repeatBlock": {
|
|
2726
|
+
const attrs = node.attrs || {};
|
|
2727
|
+
const parts = [];
|
|
2728
|
+
if (attrs.data) parts.push(`data:${attrs.data}`);
|
|
2729
|
+
const attrStr = parts.length > 0 ? `{${parts.join("|")}}` : "";
|
|
2730
|
+
const children = (node.content || []).map(serializeBlock).join("\n\n");
|
|
2731
|
+
return `:::repeat${attrStr}
|
|
2732
|
+
${children}
|
|
2733
|
+
:::`;
|
|
2734
|
+
}
|
|
2735
|
+
case "subtotalsBlock": {
|
|
2736
|
+
const children = (node.content || []).map(serializeBlock).join("\n");
|
|
2737
|
+
return `:::subtotals
|
|
2738
|
+
${children}
|
|
2739
|
+
:::`;
|
|
2740
|
+
}
|
|
2741
|
+
case "columns": {
|
|
2742
|
+
const attrs = node.attrs || {};
|
|
2743
|
+
const splitVal = attrs.split || "50";
|
|
2744
|
+
const cols = (node.content || []).filter((c) => c.type === "column");
|
|
2745
|
+
const colBlocks = cols.map((col) => {
|
|
2746
|
+
const children = (col.content || []).map(serializeBlock).join("\n\n");
|
|
2747
|
+
const colAttrs = col.attrs || {};
|
|
2748
|
+
const padTop = parseFloat(colAttrs.padTop || "0") || 0;
|
|
2749
|
+
const colTag = padTop ? `:::col{padTop:${padTop}}` : ":::col";
|
|
2750
|
+
return `${colTag}
|
|
2751
|
+
${children}
|
|
2752
|
+
:::`;
|
|
2753
|
+
});
|
|
2754
|
+
const padX = parseFloat(attrs.padX || "0") || 0;
|
|
2755
|
+
const colsAttrs = padX ? `split:${splitVal}|padX:${padX}` : `split:${splitVal}`;
|
|
2756
|
+
return `:::columns{${colsAttrs}}
|
|
2757
|
+
${colBlocks.join("\n")}
|
|
2758
|
+
:::`;
|
|
2759
|
+
}
|
|
2760
|
+
case "column": {
|
|
2761
|
+
const children = (node.content || []).map(serializeBlock).join("\n\n");
|
|
2762
|
+
const colAttrs = node.attrs || {};
|
|
2763
|
+
const padTop = parseFloat(colAttrs.padTop || "0") || 0;
|
|
2764
|
+
const colTag = padTop ? `:::col{padTop:${padTop}}` : ":::col";
|
|
2765
|
+
return `${colTag}
|
|
2766
|
+
${children}
|
|
2767
|
+
:::`;
|
|
2768
|
+
}
|
|
2769
|
+
default:
|
|
2770
|
+
return serializeInline(node.content);
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
function tiptapToMarkdown(doc) {
|
|
2774
|
+
if (!doc.content) return "";
|
|
2775
|
+
return doc.content.map(serializeBlock).join("\n\n");
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
// src/utils/pdf-preview.ts
|
|
2779
|
+
async function pdfToImages(pdfBytes, scale = 2) {
|
|
2780
|
+
let pdfjsLib;
|
|
2781
|
+
try {
|
|
2782
|
+
pdfjsLib = await import('pdfjs-dist');
|
|
2783
|
+
} catch (err) {
|
|
2784
|
+
throw new Error(formatError(
|
|
2785
|
+
"Failed to load the PDF preview library \u2014 ensure pdfjs-dist is installed and the worker is accessible",
|
|
2786
|
+
err
|
|
2787
|
+
));
|
|
2788
|
+
}
|
|
2789
|
+
if (!pdfjsLib.GlobalWorkerOptions.workerSrc) {
|
|
2790
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc = "/pdfjs/build/pdf.worker.mjs";
|
|
2791
|
+
}
|
|
2792
|
+
let pdf;
|
|
2793
|
+
try {
|
|
2794
|
+
const loadingTask = pdfjsLib.getDocument({ data: pdfBytes });
|
|
2795
|
+
pdf = await loadingTask.promise;
|
|
2796
|
+
} catch (err) {
|
|
2797
|
+
throw new Error(formatError("Failed to parse the generated PDF for preview", err));
|
|
2798
|
+
}
|
|
2799
|
+
const pages = [];
|
|
2800
|
+
const pageErrors = [];
|
|
2801
|
+
for (let i = 1; i <= pdf.numPages; i++) {
|
|
2802
|
+
try {
|
|
2803
|
+
const page = await pdf.getPage(i);
|
|
1569
2804
|
const viewport = page.getViewport({ scale });
|
|
1570
2805
|
const canvas = document.createElement("canvas");
|
|
1571
2806
|
canvas.width = viewport.width;
|
|
@@ -1626,8 +2861,26 @@ function useDocumentGenerator(options = {}) {
|
|
|
1626
2861
|
Placeholder.configure({
|
|
1627
2862
|
placeholder: options.placeholder || "Start writing your document..."
|
|
1628
2863
|
}),
|
|
2864
|
+
Table.extend({
|
|
2865
|
+
addAttributes() {
|
|
2866
|
+
return {
|
|
2867
|
+
...this.parent?.(),
|
|
2868
|
+
borderless: { default: false },
|
|
2869
|
+
subtotals: { default: false }
|
|
2870
|
+
};
|
|
2871
|
+
}
|
|
2872
|
+
}).configure({ resizable: false }),
|
|
2873
|
+
TableRow,
|
|
2874
|
+
TableCell,
|
|
2875
|
+
TableHeader,
|
|
1629
2876
|
FieldNode,
|
|
1630
|
-
VariableNode
|
|
2877
|
+
VariableNode,
|
|
2878
|
+
PanelNode,
|
|
2879
|
+
ColumnsNode,
|
|
2880
|
+
ColumnNode,
|
|
2881
|
+
WatermarkNode,
|
|
2882
|
+
RepeatNode,
|
|
2883
|
+
SubtotalsNode
|
|
1631
2884
|
],
|
|
1632
2885
|
content: initialContent || { type: "doc", content: [{ type: "paragraph" }] },
|
|
1633
2886
|
onUpdate: ({ editor: ed }) => {
|
|
@@ -1731,10 +2984,9 @@ function useDocumentGenerator(options = {}) {
|
|
|
1731
2984
|
async (values) => {
|
|
1732
2985
|
if (!editor) throw new Error("Editor is not initialized yet \u2014 wait for the editor to load before generating");
|
|
1733
2986
|
const content = editor.getJSON();
|
|
1734
|
-
const
|
|
1735
|
-
const
|
|
1736
|
-
|
|
1737
|
-
return { pdfBytes: result.pdfBytes, pdfPages: pages };
|
|
2987
|
+
const { pdfBytes: pdfBytes2 } = await generatePdfFromTiptap(content, values);
|
|
2988
|
+
const pdfPages2 = await pdfToImages(pdfBytes2);
|
|
2989
|
+
return { pdfBytes: pdfBytes2, pdfPages: pdfPages2 };
|
|
1738
2990
|
},
|
|
1739
2991
|
[editor]
|
|
1740
2992
|
);
|
|
@@ -2888,15 +4140,47 @@ function FieldEditPopover({
|
|
|
2888
4140
|
] }) })
|
|
2889
4141
|
] });
|
|
2890
4142
|
}
|
|
2891
|
-
|
|
4143
|
+
var sizeClasses = {
|
|
4144
|
+
md: "px-3 py-1.5 text-xs font-medium",
|
|
4145
|
+
sm: "px-2 py-0.5 text-[10px] font-medium"
|
|
4146
|
+
};
|
|
4147
|
+
function ToggleGroup({
|
|
4148
|
+
value,
|
|
4149
|
+
onChange,
|
|
4150
|
+
options,
|
|
4151
|
+
size = "md",
|
|
4152
|
+
className
|
|
4153
|
+
}) {
|
|
4154
|
+
return /* @__PURE__ */ jsx("div", { className: cn("inline-flex items-center gap-1 bg-muted/50 p-0.5 rounded-lg", className), children: options.map((opt) => /* @__PURE__ */ jsx(
|
|
4155
|
+
"button",
|
|
4156
|
+
{
|
|
4157
|
+
className: cn(
|
|
4158
|
+
"rounded-md transition-colors",
|
|
4159
|
+
sizeClasses[size],
|
|
4160
|
+
value === opt.value ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
|
|
4161
|
+
),
|
|
4162
|
+
onClick: () => onChange(opt.value),
|
|
4163
|
+
children: opt.label
|
|
4164
|
+
},
|
|
4165
|
+
opt.value
|
|
4166
|
+
)) });
|
|
4167
|
+
}
|
|
4168
|
+
function labelToVarName2(label) {
|
|
2892
4169
|
return label.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
2893
4170
|
}
|
|
4171
|
+
function varNameToLabel(name) {
|
|
4172
|
+
return name.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
4173
|
+
}
|
|
2894
4174
|
function VariableInsertPopover({
|
|
2895
4175
|
open,
|
|
2896
4176
|
onOpenChange,
|
|
2897
4177
|
onInsert,
|
|
4178
|
+
predefinedVariables,
|
|
2898
4179
|
children
|
|
2899
4180
|
}) {
|
|
4181
|
+
const hasPredefined = !!predefinedVariables?.length;
|
|
4182
|
+
const [mode, setMode] = useState(hasPredefined ? "existing" : "new");
|
|
4183
|
+
const [search, setSearch] = useState("");
|
|
2900
4184
|
const [varLabel, setVarLabel] = useState("");
|
|
2901
4185
|
const [varName, setVarName] = useState("");
|
|
2902
4186
|
const [varDefault, setVarDefault] = useState("");
|
|
@@ -2906,7 +4190,9 @@ function VariableInsertPopover({
|
|
|
2906
4190
|
setVarName("");
|
|
2907
4191
|
setVarDefault("");
|
|
2908
4192
|
setNameManuallyEdited(false);
|
|
2909
|
-
|
|
4193
|
+
setSearch("");
|
|
4194
|
+
setMode(hasPredefined ? "existing" : "new");
|
|
4195
|
+
}, [hasPredefined]);
|
|
2910
4196
|
const handleOpenChange = useCallback(
|
|
2911
4197
|
(nextOpen) => {
|
|
2912
4198
|
if (!nextOpen) reset();
|
|
@@ -2916,7 +4202,7 @@ function VariableInsertPopover({
|
|
|
2916
4202
|
);
|
|
2917
4203
|
useEffect(() => {
|
|
2918
4204
|
if (!nameManuallyEdited) {
|
|
2919
|
-
setVarName(
|
|
4205
|
+
setVarName(labelToVarName2(varLabel));
|
|
2920
4206
|
}
|
|
2921
4207
|
}, [varLabel, nameManuallyEdited]);
|
|
2922
4208
|
const handleInsert = useCallback(() => {
|
|
@@ -2928,6 +4214,26 @@ function VariableInsertPopover({
|
|
|
2928
4214
|
});
|
|
2929
4215
|
handleOpenChange(false);
|
|
2930
4216
|
}, [varLabel, varName, varDefault, onInsert, handleOpenChange]);
|
|
4217
|
+
const handlePickPredefined = useCallback(
|
|
4218
|
+
(pv) => {
|
|
4219
|
+
onInsert({
|
|
4220
|
+
varName: pv.varName,
|
|
4221
|
+
varLabel: pv.varLabel || varNameToLabel(pv.varName),
|
|
4222
|
+
varDefault: ""
|
|
4223
|
+
});
|
|
4224
|
+
handleOpenChange(false);
|
|
4225
|
+
},
|
|
4226
|
+
[onInsert, handleOpenChange]
|
|
4227
|
+
);
|
|
4228
|
+
const filteredVars = useMemo(() => {
|
|
4229
|
+
if (!predefinedVariables?.length) return [];
|
|
4230
|
+
const q = search.toLowerCase();
|
|
4231
|
+
if (!q) return predefinedVariables;
|
|
4232
|
+
return predefinedVariables.filter((pv) => {
|
|
4233
|
+
const label = (pv.varLabel || varNameToLabel(pv.varName)).toLowerCase();
|
|
4234
|
+
return pv.varName.toLowerCase().includes(q) || label.includes(q);
|
|
4235
|
+
});
|
|
4236
|
+
}, [predefinedVariables, search]);
|
|
2931
4237
|
return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: handleOpenChange, children: [
|
|
2932
4238
|
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children }),
|
|
2933
4239
|
/* @__PURE__ */ jsx(PopoverContent, { className: "w-72 p-0", align: "start", sideOffset: 8, children: /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-3", children: [
|
|
@@ -2935,72 +4241,114 @@ function VariableInsertPopover({
|
|
|
2935
4241
|
/* @__PURE__ */ jsx(Braces, { size: 14 }),
|
|
2936
4242
|
/* @__PURE__ */ jsx("span", { children: "Insert Variable" })
|
|
2937
4243
|
] }),
|
|
2938
|
-
/* @__PURE__ */
|
|
2939
|
-
|
|
2940
|
-
|
|
4244
|
+
hasPredefined && /* @__PURE__ */ jsx(
|
|
4245
|
+
ToggleGroup,
|
|
4246
|
+
{
|
|
4247
|
+
value: mode,
|
|
4248
|
+
onChange: setMode,
|
|
4249
|
+
options: [
|
|
4250
|
+
{ value: "existing", label: "Existing" },
|
|
4251
|
+
{ value: "new", label: "New" }
|
|
4252
|
+
],
|
|
4253
|
+
size: "sm"
|
|
4254
|
+
}
|
|
4255
|
+
),
|
|
4256
|
+
mode === "existing" && hasPredefined ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
4257
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
4258
|
+
/* @__PURE__ */ jsx(Search, { size: 14, className: "absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground" }),
|
|
2941
4259
|
/* @__PURE__ */ jsx(
|
|
2942
4260
|
Input,
|
|
2943
4261
|
{
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
className: "h-8 text-xs"
|
|
4262
|
+
value: search,
|
|
4263
|
+
onChange: (e) => setSearch(e.target.value),
|
|
4264
|
+
placeholder: "Search variables...",
|
|
4265
|
+
className: "h-8 text-xs pl-7"
|
|
2949
4266
|
}
|
|
2950
4267
|
)
|
|
2951
4268
|
] }),
|
|
2952
|
-
/* @__PURE__ */
|
|
2953
|
-
|
|
4269
|
+
/* @__PURE__ */ jsx("div", { className: "max-h-48 overflow-y-auto -mx-1", children: filteredVars.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground px-2 py-3 text-center", children: "No variables found" }) : filteredVars.map((pv) => {
|
|
4270
|
+
const label = pv.varLabel || varNameToLabel(pv.varName);
|
|
4271
|
+
return /* @__PURE__ */ jsxs(
|
|
4272
|
+
"button",
|
|
4273
|
+
{
|
|
4274
|
+
className: "w-full text-left px-2 py-1.5 rounded-md text-xs hover:bg-accent transition-colors flex flex-col gap-0.5",
|
|
4275
|
+
onClick: () => handlePickPredefined(pv),
|
|
4276
|
+
children: [
|
|
4277
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: label }),
|
|
4278
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground font-mono", children: pv.varName })
|
|
4279
|
+
]
|
|
4280
|
+
},
|
|
4281
|
+
pv.varName
|
|
4282
|
+
);
|
|
4283
|
+
}) })
|
|
4284
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4285
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
4286
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
4287
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "var-label", className: "text-xs", children: "Label" }),
|
|
4288
|
+
/* @__PURE__ */ jsx(
|
|
4289
|
+
Input,
|
|
4290
|
+
{
|
|
4291
|
+
id: "var-label",
|
|
4292
|
+
value: varLabel,
|
|
4293
|
+
onChange: (e) => setVarLabel(e.target.value),
|
|
4294
|
+
placeholder: "e.g. Company Name",
|
|
4295
|
+
className: "h-8 text-xs"
|
|
4296
|
+
}
|
|
4297
|
+
)
|
|
4298
|
+
] }),
|
|
4299
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
4300
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "var-name", className: "text-xs", children: "Name (identifier)" }),
|
|
4301
|
+
/* @__PURE__ */ jsx(
|
|
4302
|
+
Input,
|
|
4303
|
+
{
|
|
4304
|
+
id: "var-name",
|
|
4305
|
+
value: varName,
|
|
4306
|
+
onChange: (e) => {
|
|
4307
|
+
setVarName(e.target.value);
|
|
4308
|
+
setNameManuallyEdited(true);
|
|
4309
|
+
},
|
|
4310
|
+
placeholder: "e.g. company_name",
|
|
4311
|
+
className: "h-8 text-xs font-mono"
|
|
4312
|
+
}
|
|
4313
|
+
),
|
|
4314
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground mt-0.5", children: "Used as key in data objects" })
|
|
4315
|
+
] }),
|
|
4316
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
4317
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "var-default", className: "text-xs", children: "Default Value" }),
|
|
4318
|
+
/* @__PURE__ */ jsx(
|
|
4319
|
+
Input,
|
|
4320
|
+
{
|
|
4321
|
+
id: "var-default",
|
|
4322
|
+
value: varDefault,
|
|
4323
|
+
onChange: (e) => setVarDefault(e.target.value),
|
|
4324
|
+
placeholder: "Optional",
|
|
4325
|
+
className: "h-8 text-xs"
|
|
4326
|
+
}
|
|
4327
|
+
)
|
|
4328
|
+
] })
|
|
4329
|
+
] }),
|
|
4330
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2 pt-1", children: [
|
|
2954
4331
|
/* @__PURE__ */ jsx(
|
|
2955
|
-
|
|
4332
|
+
Button,
|
|
2956
4333
|
{
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
},
|
|
2963
|
-
placeholder: "e.g. company_name",
|
|
2964
|
-
className: "h-8 text-xs font-mono"
|
|
4334
|
+
size: "sm",
|
|
4335
|
+
className: "h-8 flex-1 text-xs",
|
|
4336
|
+
onClick: handleInsert,
|
|
4337
|
+
disabled: !varLabel.trim() || !varName.trim(),
|
|
4338
|
+
children: "Insert"
|
|
2965
4339
|
}
|
|
2966
4340
|
),
|
|
2967
|
-
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground mt-0.5", children: "Used as key in data objects" })
|
|
2968
|
-
] }),
|
|
2969
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
2970
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "var-default", className: "text-xs", children: "Default Value" }),
|
|
2971
4341
|
/* @__PURE__ */ jsx(
|
|
2972
|
-
|
|
4342
|
+
Button,
|
|
2973
4343
|
{
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
4344
|
+
variant: "outline",
|
|
4345
|
+
size: "sm",
|
|
4346
|
+
className: "h-8 text-xs",
|
|
4347
|
+
onClick: () => handleOpenChange(false),
|
|
4348
|
+
children: "Cancel"
|
|
2979
4349
|
}
|
|
2980
4350
|
)
|
|
2981
4351
|
] })
|
|
2982
|
-
] }),
|
|
2983
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-2 pt-1", children: [
|
|
2984
|
-
/* @__PURE__ */ jsx(
|
|
2985
|
-
Button,
|
|
2986
|
-
{
|
|
2987
|
-
size: "sm",
|
|
2988
|
-
className: "h-8 flex-1 text-xs",
|
|
2989
|
-
onClick: handleInsert,
|
|
2990
|
-
disabled: !varLabel.trim() || !varName.trim(),
|
|
2991
|
-
children: "Insert"
|
|
2992
|
-
}
|
|
2993
|
-
),
|
|
2994
|
-
/* @__PURE__ */ jsx(
|
|
2995
|
-
Button,
|
|
2996
|
-
{
|
|
2997
|
-
variant: "outline",
|
|
2998
|
-
size: "sm",
|
|
2999
|
-
className: "h-8 text-xs",
|
|
3000
|
-
onClick: () => handleOpenChange(false),
|
|
3001
|
-
children: "Cancel"
|
|
3002
|
-
}
|
|
3003
|
-
)
|
|
3004
4352
|
] })
|
|
3005
4353
|
] }) })
|
|
3006
4354
|
] });
|
|
@@ -3222,7 +4570,6 @@ function PreviewPanel({
|
|
|
3222
4570
|
(f) => f.position.page === currentPageIndex + 1 && f.position.width > 0
|
|
3223
4571
|
);
|
|
3224
4572
|
}, [positionedFields, currentPage, currentPageIndex]);
|
|
3225
|
-
const showScrollbars = zoomLevel > 1;
|
|
3226
4573
|
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col h-full border border-border rounded-lg overflow-hidden bg-muted/20", className), children: [
|
|
3227
4574
|
/* @__PURE__ */ jsx("div", { className: "border-b border-border px-2 py-1.5 flex-shrink-0", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [
|
|
3228
4575
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
@@ -3338,12 +4685,13 @@ function PreviewPanel({
|
|
|
3338
4685
|
"div",
|
|
3339
4686
|
{
|
|
3340
4687
|
className: cn(
|
|
3341
|
-
"border border-border rounded-lg bg-muted/30 shrink-0"
|
|
3342
|
-
showScrollbars ? "overflow-auto" : "overflow-hidden"
|
|
4688
|
+
"border border-border rounded-lg bg-muted/30 shrink-0 overflow-auto scrollbar-hidden"
|
|
3343
4689
|
),
|
|
3344
4690
|
style: {
|
|
3345
4691
|
width: pageDisplaySize.viewportWidth,
|
|
3346
|
-
height: pageDisplaySize.viewportHeight
|
|
4692
|
+
height: pageDisplaySize.viewportHeight,
|
|
4693
|
+
maxWidth: "100%",
|
|
4694
|
+
maxHeight: "100%"
|
|
3347
4695
|
},
|
|
3348
4696
|
children: /* @__PURE__ */ jsxs(
|
|
3349
4697
|
"div",
|
|
@@ -3435,6 +4783,37 @@ function validateMarkdown(markdown) {
|
|
|
3435
4783
|
searchFrom = closeIdx + 2;
|
|
3436
4784
|
}
|
|
3437
4785
|
}
|
|
4786
|
+
const DIRECTIVE_OPEN_RE = /^:::(panel|columns|col|watermark)(?:\{[^}]*\})?$/;
|
|
4787
|
+
const DIRECTIVE_CLOSE_RE = /^:::$/;
|
|
4788
|
+
const directiveStack = [];
|
|
4789
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4790
|
+
const trimmed = lines[i].trim();
|
|
4791
|
+
const lineNum = i + 1;
|
|
4792
|
+
const openMatch = trimmed.match(DIRECTIVE_OPEN_RE);
|
|
4793
|
+
if (openMatch) {
|
|
4794
|
+
const dtype = openMatch[1];
|
|
4795
|
+
if (dtype !== "watermark") {
|
|
4796
|
+
if (dtype === "col") {
|
|
4797
|
+
const parent = directiveStack[directiveStack.length - 1];
|
|
4798
|
+
if (!parent || parent.type !== "columns") {
|
|
4799
|
+
warnings.push(`:::col at line ${lineNum} appears outside :::columns`);
|
|
4800
|
+
}
|
|
4801
|
+
}
|
|
4802
|
+
directiveStack.push({ type: dtype, line: lineNum });
|
|
4803
|
+
}
|
|
4804
|
+
continue;
|
|
4805
|
+
}
|
|
4806
|
+
if (DIRECTIVE_CLOSE_RE.test(trimmed)) {
|
|
4807
|
+
if (directiveStack.length === 0) {
|
|
4808
|
+
warnings.push(`Stray ::: close at line ${lineNum} with no matching open directive`);
|
|
4809
|
+
} else {
|
|
4810
|
+
directiveStack.pop();
|
|
4811
|
+
}
|
|
4812
|
+
}
|
|
4813
|
+
}
|
|
4814
|
+
for (const open of directiveStack) {
|
|
4815
|
+
errors.push(`Unclosed :::${open.type} directive opened at line ${open.line}`);
|
|
4816
|
+
}
|
|
3438
4817
|
let fields = [];
|
|
3439
4818
|
let variables = [];
|
|
3440
4819
|
try {
|
|
@@ -3468,31 +4847,6 @@ function validateMarkdown(markdown) {
|
|
|
3468
4847
|
fields
|
|
3469
4848
|
};
|
|
3470
4849
|
}
|
|
3471
|
-
var sizeClasses = {
|
|
3472
|
-
md: "px-3 py-1.5 text-xs font-medium",
|
|
3473
|
-
sm: "px-2 py-0.5 text-[10px] font-medium"
|
|
3474
|
-
};
|
|
3475
|
-
function ToggleGroup({
|
|
3476
|
-
value,
|
|
3477
|
-
onChange,
|
|
3478
|
-
options,
|
|
3479
|
-
size = "md",
|
|
3480
|
-
className
|
|
3481
|
-
}) {
|
|
3482
|
-
return /* @__PURE__ */ jsx("div", { className: cn("inline-flex items-center gap-1 bg-muted/50 p-0.5 rounded-lg", className), children: options.map((opt) => /* @__PURE__ */ jsx(
|
|
3483
|
-
"button",
|
|
3484
|
-
{
|
|
3485
|
-
className: cn(
|
|
3486
|
-
"rounded-md transition-colors",
|
|
3487
|
-
sizeClasses[size],
|
|
3488
|
-
value === opt.value ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
|
|
3489
|
-
),
|
|
3490
|
-
onClick: () => onChange(opt.value),
|
|
3491
|
-
children: opt.label
|
|
3492
|
-
},
|
|
3493
|
-
opt.value
|
|
3494
|
-
)) });
|
|
3495
|
-
}
|
|
3496
4850
|
function parseCsv(text) {
|
|
3497
4851
|
const lines = text.split(/\r?\n/).filter((l) => l.trim() !== "");
|
|
3498
4852
|
if (lines.length < 2) return [];
|
|
@@ -3542,7 +4896,10 @@ function GeneratePanel({
|
|
|
3542
4896
|
editorContent,
|
|
3543
4897
|
editorVariables,
|
|
3544
4898
|
onGeneratePdf,
|
|
3545
|
-
initialBulkData
|
|
4899
|
+
initialBulkData,
|
|
4900
|
+
initialVariableValues,
|
|
4901
|
+
exportFileName,
|
|
4902
|
+
templateMode
|
|
3546
4903
|
}) {
|
|
3547
4904
|
const [templateSource, setTemplateSource] = useState("editor");
|
|
3548
4905
|
const [importedMarkdown, setImportedMarkdown] = useState(null);
|
|
@@ -3550,6 +4907,7 @@ function GeneratePanel({
|
|
|
3550
4907
|
const [validationResult, setValidationResult] = useState(null);
|
|
3551
4908
|
const [mode, setMode] = useState("single");
|
|
3552
4909
|
const [variableValues, setVariableValues] = useState({});
|
|
4910
|
+
const [variableSearch, setVariableSearch] = useState("");
|
|
3553
4911
|
const [bulkInputFormat, setBulkInputFormat] = useState("json");
|
|
3554
4912
|
const [bulkInput, setBulkInput] = useState("");
|
|
3555
4913
|
const [bulkData, setBulkData] = useState(null);
|
|
@@ -3576,11 +4934,22 @@ function GeneratePanel({
|
|
|
3576
4934
|
const col = pos - textBefore.lastIndexOf("\n");
|
|
3577
4935
|
setJsonCursor({ line, col });
|
|
3578
4936
|
}, []);
|
|
4937
|
+
const lockedVarNames = React12__default.useMemo(
|
|
4938
|
+
() => templateMode && initialVariableValues ? new Set(Object.keys(initialVariableValues)) : /* @__PURE__ */ new Set(),
|
|
4939
|
+
[templateMode, initialVariableValues]
|
|
4940
|
+
);
|
|
3579
4941
|
const activeVariables = templateSource === "imported" && validationResult?.valid ? validationResult.variables : editorVariables;
|
|
3580
4942
|
const hasVariables = activeVariables.length > 0;
|
|
3581
4943
|
const hasEditorContent = editorMarkdown.trim().length > 0;
|
|
3582
4944
|
const hasImportedContent = templateSource === "imported" && importedMarkdown != null;
|
|
3583
4945
|
const hasContent = hasImportedContent || hasEditorContent;
|
|
4946
|
+
const filteredVariables = React12__default.useMemo(() => {
|
|
4947
|
+
if (!variableSearch.trim()) return activeVariables;
|
|
4948
|
+
const q = variableSearch.toLowerCase();
|
|
4949
|
+
return activeVariables.filter(
|
|
4950
|
+
(v) => v.varName.toLowerCase().includes(q) || (v.varLabel || "").toLowerCase().includes(q)
|
|
4951
|
+
);
|
|
4952
|
+
}, [activeVariables, variableSearch]);
|
|
3584
4953
|
const getActiveContent = useCallback(() => {
|
|
3585
4954
|
if (templateSource === "imported" && importedMarkdown) {
|
|
3586
4955
|
return markdownToTiptap(importedMarkdown);
|
|
@@ -3748,7 +5117,9 @@ function GeneratePanel({
|
|
|
3748
5117
|
setPreviewError(null);
|
|
3749
5118
|
try {
|
|
3750
5119
|
const content = getActiveContent();
|
|
3751
|
-
const
|
|
5120
|
+
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, variableValues);
|
|
5121
|
+
const suppressed = suppressZeroContent(expanded, enrichedValues);
|
|
5122
|
+
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
3752
5123
|
const fields = extractFieldsFromContent(replaced);
|
|
3753
5124
|
const result = await generatePdfFromContent(replaced);
|
|
3754
5125
|
const pages = await pdfToImages(result.pdfBytes);
|
|
@@ -3769,7 +5140,9 @@ function GeneratePanel({
|
|
|
3769
5140
|
setExportSuccess(null);
|
|
3770
5141
|
try {
|
|
3771
5142
|
const content = getActiveContent();
|
|
3772
|
-
const
|
|
5143
|
+
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, variableValues);
|
|
5144
|
+
const suppressed = suppressZeroContent(expanded, enrichedValues);
|
|
5145
|
+
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
3773
5146
|
const fields = extractFieldsFromContent(replaced);
|
|
3774
5147
|
const result = await generatePdfFromContent(replaced, {
|
|
3775
5148
|
drawFieldPlaceholders: false,
|
|
@@ -3777,7 +5150,7 @@ function GeneratePanel({
|
|
|
3777
5150
|
fields
|
|
3778
5151
|
});
|
|
3779
5152
|
const blob = new Blob([result.pdfBytes], { type: "application/pdf" });
|
|
3780
|
-
const fileName = importedFileName ? importedFileName.replace(".md", ".pdf") : "document.pdf";
|
|
5153
|
+
const fileName = exportFileName ? exportFileName.endsWith(".pdf") ? exportFileName : `${exportFileName}.pdf` : importedFileName ? importedFileName.replace(".md", ".pdf") : "document.pdf";
|
|
3781
5154
|
if (onGeneratePdf) {
|
|
3782
5155
|
onGeneratePdf(blob, fileName);
|
|
3783
5156
|
} else {
|
|
@@ -3849,7 +5222,9 @@ function GeneratePanel({
|
|
|
3849
5222
|
const allWarnings = [];
|
|
3850
5223
|
for (let i = 0; i < bulkData.length; i++) {
|
|
3851
5224
|
const values = bulkData[i];
|
|
3852
|
-
const
|
|
5225
|
+
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, values);
|
|
5226
|
+
const suppressed = suppressZeroContent(expanded, enrichedValues);
|
|
5227
|
+
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
3853
5228
|
const fields = extractFieldsFromContent(replaced);
|
|
3854
5229
|
const result = await generatePdfFromContent(replaced, {
|
|
3855
5230
|
drawFieldPlaceholders: false,
|
|
@@ -3904,13 +5279,15 @@ ${allWarnings.join("\n")}`);
|
|
|
3904
5279
|
}, [mode, bulkInputFormat, bulkInput, initialBulkJson]);
|
|
3905
5280
|
React12__default.useEffect(() => {
|
|
3906
5281
|
if (templateSource === "editor") {
|
|
3907
|
-
const defaults = {};
|
|
5282
|
+
const defaults = initialVariableValues ? { ...initialVariableValues } : {};
|
|
3908
5283
|
for (const v of editorVariables) {
|
|
3909
|
-
defaults[v.varName]
|
|
5284
|
+
if (!defaults[v.varName]) {
|
|
5285
|
+
defaults[v.varName] = variableValues[v.varName] || v.varDefault || "";
|
|
5286
|
+
}
|
|
3910
5287
|
}
|
|
3911
5288
|
setVariableValues(defaults);
|
|
3912
5289
|
}
|
|
3913
|
-
}, [editorVariables, templateSource]);
|
|
5290
|
+
}, [editorVariables, templateSource, initialVariableValues]);
|
|
3914
5291
|
useEffect(() => {
|
|
3915
5292
|
setPreviewFresh(false);
|
|
3916
5293
|
}, [variableValues, templateSource, importedMarkdown, editorMarkdown, editorContent]);
|
|
@@ -3922,8 +5299,8 @@ ${allWarnings.join("\n")}`);
|
|
|
3922
5299
|
}, [exportSuccess]);
|
|
3923
5300
|
return /* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0 grid grid-cols-[1fr_1px_1fr]", children: [
|
|
3924
5301
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col min-h-0 min-w-0", children: [
|
|
3925
|
-
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto p-4 space-y-4", children: [
|
|
3926
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
5302
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto p-4 space-y-4 scrollbar-hidden", children: [
|
|
5303
|
+
!templateMode && /* @__PURE__ */ jsxs("div", { children: [
|
|
3927
5304
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
|
|
3928
5305
|
/* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: "Template" }),
|
|
3929
5306
|
templateSource === "imported" && /* @__PURE__ */ jsxs(
|
|
@@ -4016,10 +5393,10 @@ ${allWarnings.join("\n")}`);
|
|
|
4016
5393
|
!hasContent && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-8 text-muted-foreground", children: [
|
|
4017
5394
|
/* @__PURE__ */ jsx(Braces, { size: 32, className: "mb-2 opacity-50" }),
|
|
4018
5395
|
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "No template content" }),
|
|
4019
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs mt-1", children: "Import a .md template above or add content in the Editor tab." })
|
|
5396
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs mt-1", children: templateMode ? "No template content was provided." : "Import a .md template above or add content in the Editor tab." })
|
|
4020
5397
|
] }),
|
|
4021
5398
|
hasVariables && hasContent && /* @__PURE__ */ jsxs("div", { className: "border border-border rounded-lg overflow-hidden", children: [
|
|
4022
|
-
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 px-3 py-2 bg-muted/30 border-b border-border", children: /* @__PURE__ */ jsx(
|
|
5399
|
+
!templateMode && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 px-3 py-2 bg-muted/30 border-b border-border", children: /* @__PURE__ */ jsx(
|
|
4023
5400
|
ToggleGroup,
|
|
4024
5401
|
{
|
|
4025
5402
|
value: mode,
|
|
@@ -4031,20 +5408,53 @@ ${allWarnings.join("\n")}`);
|
|
|
4031
5408
|
}
|
|
4032
5409
|
) }),
|
|
4033
5410
|
mode === "single" && /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-3", children: [
|
|
4034
|
-
/* @__PURE__ */
|
|
4035
|
-
|
|
4036
|
-
/* @__PURE__ */ jsx(
|
|
5411
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
5412
|
+
/* @__PURE__ */ jsx("h3", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: "Variables" }),
|
|
5413
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: activeVariables.length })
|
|
5414
|
+
] }),
|
|
5415
|
+
activeVariables.length > 5 && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
5416
|
+
/* @__PURE__ */ jsx(Search, { size: 14, className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground" }),
|
|
4037
5417
|
/* @__PURE__ */ jsx(
|
|
4038
5418
|
Input,
|
|
4039
5419
|
{
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
5420
|
+
value: variableSearch,
|
|
5421
|
+
onChange: (e) => setVariableSearch(e.target.value),
|
|
5422
|
+
placeholder: "Search variables...",
|
|
5423
|
+
className: "h-8 text-xs pl-8"
|
|
5424
|
+
}
|
|
5425
|
+
),
|
|
5426
|
+
variableSearch && /* @__PURE__ */ jsx(
|
|
5427
|
+
"button",
|
|
5428
|
+
{
|
|
5429
|
+
type: "button",
|
|
5430
|
+
onClick: () => setVariableSearch(""),
|
|
5431
|
+
className: "absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground",
|
|
5432
|
+
children: /* @__PURE__ */ jsx(X, { size: 12 })
|
|
4045
5433
|
}
|
|
4046
5434
|
)
|
|
4047
|
-
] },
|
|
5435
|
+
] }),
|
|
5436
|
+
filteredVariables.length === 0 && variableSearch.trim() && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground py-2 text-center", children: [
|
|
5437
|
+
"No variables match \u201C",
|
|
5438
|
+
variableSearch,
|
|
5439
|
+
"\u201D"
|
|
5440
|
+
] }),
|
|
5441
|
+
filteredVariables.map((v) => {
|
|
5442
|
+
const isLocked = lockedVarNames.has(v.varName);
|
|
5443
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
5444
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: `gen-${v.varName}`, className: "text-xs", children: v.varLabel || v.varName }),
|
|
5445
|
+
/* @__PURE__ */ jsx(
|
|
5446
|
+
Input,
|
|
5447
|
+
{
|
|
5448
|
+
id: `gen-${v.varName}`,
|
|
5449
|
+
value: variableValues[v.varName] || "",
|
|
5450
|
+
onChange: (e) => updateVariableValue(v.varName, e.target.value),
|
|
5451
|
+
placeholder: v.varDefault || `Enter ${v.varLabel || v.varName}`,
|
|
5452
|
+
disabled: isLocked,
|
|
5453
|
+
className: cn("h-8 text-xs", isLocked && "opacity-60 cursor-not-allowed")
|
|
5454
|
+
}
|
|
5455
|
+
)
|
|
5456
|
+
] }, v.varName);
|
|
5457
|
+
})
|
|
4048
5458
|
] }),
|
|
4049
5459
|
mode === "bulk" && /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-3", children: [
|
|
4050
5460
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
@@ -4233,56 +5643,60 @@ ${allWarnings.join("\n")}`);
|
|
|
4233
5643
|
] }),
|
|
4234
5644
|
!hasVariables && hasContent && /* @__PURE__ */ jsx("div", { className: "p-3 rounded-md bg-muted/30 border border-border", children: /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: 'This template has no variables. You can still preview and export the PDF as-is, or add variables in the Editor tab using the "Insert Variable" button.' }) })
|
|
4235
5645
|
] }),
|
|
4236
|
-
|
|
4237
|
-
/* @__PURE__ */
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
5646
|
+
hasContent && /* @__PURE__ */ jsxs("div", { className: "border-t border-border px-4 py-3 flex items-center gap-2 bg-gradient-to-b from-background to-muted/20", children: [
|
|
5647
|
+
mode === "single" || !hasVariables ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5648
|
+
/* @__PURE__ */ jsxs(
|
|
5649
|
+
Button,
|
|
5650
|
+
{
|
|
5651
|
+
variant: "secondary",
|
|
5652
|
+
onClick: handlePreview,
|
|
5653
|
+
disabled: isGenerating,
|
|
5654
|
+
className: "h-10 px-4 font-semibold shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
5655
|
+
children: [
|
|
5656
|
+
isGenerating ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsx(RefreshCw, { size: 16 }),
|
|
5657
|
+
isGenerating ? "Generating..." : "Generate PDF"
|
|
5658
|
+
]
|
|
5659
|
+
}
|
|
5660
|
+
),
|
|
5661
|
+
/* @__PURE__ */ jsxs(
|
|
5662
|
+
Button,
|
|
5663
|
+
{
|
|
5664
|
+
onClick: handleExportSingle,
|
|
5665
|
+
disabled: !previewFresh || isExporting,
|
|
5666
|
+
className: "h-10 px-4 font-semibold bg-primary hover:bg-primary/90 text-primary-foreground shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
5667
|
+
children: [
|
|
5668
|
+
isExporting ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsx(Download, { size: 16 }),
|
|
5669
|
+
isExporting ? "Exporting..." : "Export PDF"
|
|
5670
|
+
]
|
|
5671
|
+
}
|
|
5672
|
+
)
|
|
5673
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
4263
5674
|
Button,
|
|
4264
5675
|
{
|
|
4265
|
-
onClick:
|
|
4266
|
-
disabled: !
|
|
4267
|
-
className: "h-10 px-4 font-semibold bg-primary hover:bg-primary/90 text-primary-foreground shadow-
|
|
5676
|
+
onClick: handleBulkGenerate,
|
|
5677
|
+
disabled: isExporting || !bulkData || bulkData.length === 0,
|
|
5678
|
+
className: "h-10 px-4 font-semibold bg-primary hover:bg-primary/90 text-primary-foreground shadow-lg hover:shadow-xl transition-all duration-200 text-sm",
|
|
4268
5679
|
children: [
|
|
4269
5680
|
isExporting ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsx(Download, { size: 16 }),
|
|
4270
|
-
isExporting ? "
|
|
5681
|
+
isExporting ? "Generating..." : `Generate All (${bulkData?.length || 0} PDFs)`
|
|
4271
5682
|
]
|
|
4272
5683
|
}
|
|
4273
|
-
)
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
]
|
|
4284
|
-
|
|
4285
|
-
|
|
5684
|
+
),
|
|
5685
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 ml-auto", children: [
|
|
5686
|
+
previewError && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
5687
|
+
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
5688
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: previewError })
|
|
5689
|
+
] }),
|
|
5690
|
+
exportError && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
5691
|
+
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
5692
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: exportError })
|
|
5693
|
+
] }),
|
|
5694
|
+
exportSuccess && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-emerald-600 max-w-[300px]", children: [
|
|
5695
|
+
/* @__PURE__ */ jsx(CheckCircle2, { size: 14, className: "shrink-0" }),
|
|
5696
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: exportSuccess })
|
|
5697
|
+
] })
|
|
5698
|
+
] })
|
|
5699
|
+
] })
|
|
4286
5700
|
] }),
|
|
4287
5701
|
/* @__PURE__ */ jsx("div", { className: "bg-border" }),
|
|
4288
5702
|
/* @__PURE__ */ jsx("div", { className: "flex flex-col min-h-0 min-w-0", children: /* @__PURE__ */ jsx(
|
|
@@ -4460,7 +5874,13 @@ function DocumentGeneratorInner({
|
|
|
4460
5874
|
showToolbar = true,
|
|
4461
5875
|
showGenerateTab = true,
|
|
4462
5876
|
onGeneratePdf,
|
|
4463
|
-
initialBulkData
|
|
5877
|
+
initialBulkData,
|
|
5878
|
+
defaultTab = "editor",
|
|
5879
|
+
initialVariableValues,
|
|
5880
|
+
predefinedVariables,
|
|
5881
|
+
exportFileName,
|
|
5882
|
+
templateMode,
|
|
5883
|
+
onSaveTemplate
|
|
4464
5884
|
}) {
|
|
4465
5885
|
const {
|
|
4466
5886
|
editor,
|
|
@@ -4487,7 +5907,7 @@ function DocumentGeneratorInner({
|
|
|
4487
5907
|
placeholder,
|
|
4488
5908
|
onChange
|
|
4489
5909
|
});
|
|
4490
|
-
const [activeTab, setActiveTab] = useState(
|
|
5910
|
+
const [activeTab, setActiveTab] = useState(defaultTab);
|
|
4491
5911
|
const [insertPopoverOpen, setInsertPopoverOpen] = useState(false);
|
|
4492
5912
|
const [insertVarPopoverOpen, setInsertVarPopoverOpen] = useState(false);
|
|
4493
5913
|
const [editFieldId, setEditFieldId] = useState(null);
|
|
@@ -4570,6 +5990,21 @@ function DocumentGeneratorInner({
|
|
|
4570
5990
|
setExportSuccess("Document exported successfully");
|
|
4571
5991
|
}
|
|
4572
5992
|
}, [exportDocument, onExport]);
|
|
5993
|
+
const handleSaveTemplate = useCallback(() => {
|
|
5994
|
+
const md = exportMarkdown();
|
|
5995
|
+
if (!md.trim()) {
|
|
5996
|
+
setMdExportError("Editor content is empty");
|
|
5997
|
+
return;
|
|
5998
|
+
}
|
|
5999
|
+
const validation = validateMarkdown(md);
|
|
6000
|
+
if (!validation.valid) {
|
|
6001
|
+
setMdExportError(validation.errors.join("; "));
|
|
6002
|
+
return;
|
|
6003
|
+
}
|
|
6004
|
+
setMdExportError(null);
|
|
6005
|
+
onSaveTemplate?.(md);
|
|
6006
|
+
setExportSuccess("Template saved");
|
|
6007
|
+
}, [exportMarkdown, onSaveTemplate]);
|
|
4573
6008
|
const handleExportMarkdown = useCallback(() => {
|
|
4574
6009
|
const md = exportMarkdown();
|
|
4575
6010
|
if (!md.trim()) {
|
|
@@ -4586,10 +6021,10 @@ function DocumentGeneratorInner({
|
|
|
4586
6021
|
const url = URL.createObjectURL(blob);
|
|
4587
6022
|
const a = document.createElement("a");
|
|
4588
6023
|
a.href = url;
|
|
4589
|
-
a.download = "template.md";
|
|
6024
|
+
a.download = exportFileName ? `${exportFileName}.md` : "template.md";
|
|
4590
6025
|
a.click();
|
|
4591
6026
|
URL.revokeObjectURL(url);
|
|
4592
|
-
}, [exportMarkdown]);
|
|
6027
|
+
}, [exportMarkdown, exportFileName]);
|
|
4593
6028
|
useEffect(() => {
|
|
4594
6029
|
if (mdExportError) setMdExportError(null);
|
|
4595
6030
|
}, [markdown]);
|
|
@@ -4625,6 +6060,7 @@ function DocumentGeneratorInner({
|
|
|
4625
6060
|
open: insertVarPopoverOpen,
|
|
4626
6061
|
onOpenChange: setInsertVarPopoverOpen,
|
|
4627
6062
|
onInsert: handleInsertVariable,
|
|
6063
|
+
predefinedVariables,
|
|
4628
6064
|
children: /* @__PURE__ */ jsxs(
|
|
4629
6065
|
Button,
|
|
4630
6066
|
{
|
|
@@ -4665,118 +6101,141 @@ function DocumentGeneratorInner({
|
|
|
4665
6101
|
] })
|
|
4666
6102
|
] })
|
|
4667
6103
|
] }),
|
|
4668
|
-
/* @__PURE__ */
|
|
6104
|
+
/* @__PURE__ */ jsx("div", { className: cn(
|
|
4669
6105
|
"flex flex-col flex-1 min-h-0",
|
|
4670
6106
|
activeTab !== "editor" && "hidden"
|
|
6107
|
+
), children: /* @__PURE__ */ jsxs("div", { className: cn(
|
|
6108
|
+
"flex-1 min-h-0",
|
|
6109
|
+
showPreview && !editorCollapsed ? "grid grid-cols-[1fr_1px_1fr]" : "flex flex-col"
|
|
4671
6110
|
), children: [
|
|
4672
|
-
/* @__PURE__ */ jsxs("div", { className:
|
|
4673
|
-
"
|
|
4674
|
-
|
|
4675
|
-
), children: [
|
|
4676
|
-
!editorCollapsed && /* @__PURE__ */ jsxs("div", { className: "flex flex-col min-h-0 min-w-0", children: [
|
|
4677
|
-
showToolbar && !readOnly && /* @__PURE__ */ jsx("div", { className: "border-b border-border", children: /* @__PURE__ */ jsx(
|
|
4678
|
-
EditorToolbar,
|
|
4679
|
-
{
|
|
4680
|
-
editor,
|
|
4681
|
-
insertFieldButton,
|
|
4682
|
-
insertVariableButton,
|
|
4683
|
-
onCollapse: showPreview ? () => setEditorCollapsed(true) : void 0
|
|
4684
|
-
}
|
|
4685
|
-
) }),
|
|
4686
|
-
/* @__PURE__ */ jsx("div", { ref: editorWrapperRef, className: "flex-1 overflow-y-auto min-h-0", children: /* @__PURE__ */ jsx(
|
|
4687
|
-
EditorContent,
|
|
4688
|
-
{
|
|
4689
|
-
editor,
|
|
4690
|
-
className: "prose prose-sm max-w-none p-4 focus-within:outline-none"
|
|
4691
|
-
}
|
|
4692
|
-
) })
|
|
4693
|
-
] }),
|
|
4694
|
-
showPreview && !editorCollapsed && /* @__PURE__ */ jsx("div", { className: "bg-border" }),
|
|
4695
|
-
showPreview && /* @__PURE__ */ jsx(
|
|
4696
|
-
PreviewPanel,
|
|
6111
|
+
!editorCollapsed && /* @__PURE__ */ jsxs("div", { className: "flex flex-col min-h-0 min-w-0", children: [
|
|
6112
|
+
showToolbar && !readOnly && /* @__PURE__ */ jsx("div", { className: "border-b border-border", children: /* @__PURE__ */ jsx(
|
|
6113
|
+
EditorToolbar,
|
|
4697
6114
|
{
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
Button,
|
|
4703
|
-
{
|
|
4704
|
-
variant: "ghost",
|
|
4705
|
-
size: "sm",
|
|
4706
|
-
className: "h-8 w-8 p-0",
|
|
4707
|
-
onClick: () => setEditorCollapsed(false),
|
|
4708
|
-
children: /* @__PURE__ */ jsx(PanelLeftOpen, { size: 14 })
|
|
4709
|
-
}
|
|
4710
|
-
) : void 0,
|
|
4711
|
-
className: "flex-1 border-0 rounded-none"
|
|
6115
|
+
editor,
|
|
6116
|
+
insertFieldButton,
|
|
6117
|
+
insertVariableButton,
|
|
6118
|
+
onCollapse: showPreview ? () => setEditorCollapsed(true) : void 0
|
|
4712
6119
|
}
|
|
4713
|
-
)
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
/* @__PURE__ */ jsxs(
|
|
4717
|
-
Button,
|
|
6120
|
+
) }),
|
|
6121
|
+
/* @__PURE__ */ jsx("div", { ref: editorWrapperRef, className: "flex-1 overflow-y-auto min-h-0 scrollbar-hidden", children: /* @__PURE__ */ jsx(
|
|
6122
|
+
EditorContent,
|
|
4718
6123
|
{
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
disabled: isGenerating || !editor,
|
|
4722
|
-
className: "h-10 px-4 font-semibold shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
4723
|
-
children: [
|
|
4724
|
-
isGenerating ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsx(RefreshCw, { size: 16 }),
|
|
4725
|
-
isGenerating ? "Generating..." : "Generate PDF"
|
|
4726
|
-
]
|
|
6124
|
+
editor,
|
|
6125
|
+
className: "prose prose-sm max-w-none p-4 focus-within:outline-none"
|
|
4727
6126
|
}
|
|
4728
|
-
),
|
|
4729
|
-
/* @__PURE__ */ jsxs("div", { className: "flex
|
|
4730
|
-
mdExportError && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
4731
|
-
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
4732
|
-
/* @__PURE__ */ jsx("span", { className: "truncate", children: mdExportError })
|
|
4733
|
-
] }),
|
|
4734
|
-
generationError && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
4735
|
-
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
4736
|
-
/* @__PURE__ */ jsx("span", { className: "truncate", children: generationError })
|
|
4737
|
-
] }),
|
|
4738
|
-
fieldWarnings.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-orange-600 max-w-[400px]", children: [
|
|
4739
|
-
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
4740
|
-
/* @__PURE__ */ jsxs("span", { className: "truncate", children: [
|
|
4741
|
-
fieldWarnings.length,
|
|
4742
|
-
" field",
|
|
4743
|
-
fieldWarnings.length !== 1 ? "s" : "",
|
|
4744
|
-
" had warnings during generation"
|
|
4745
|
-
] })
|
|
4746
|
-
] }),
|
|
4747
|
-
exportSuccess && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-emerald-600 max-w-[300px]", children: [
|
|
4748
|
-
/* @__PURE__ */ jsx(CheckCircle2, { size: 14, className: "shrink-0" }),
|
|
4749
|
-
/* @__PURE__ */ jsx("span", { children: exportSuccess })
|
|
4750
|
-
] })
|
|
4751
|
-
] }),
|
|
4752
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
6127
|
+
) }),
|
|
6128
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-4 py-3 border-t border-border bg-gradient-to-b from-background to-muted/20", children: [
|
|
4753
6129
|
/* @__PURE__ */ jsxs(
|
|
4754
6130
|
Button,
|
|
4755
6131
|
{
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
6132
|
+
variant: "secondary",
|
|
6133
|
+
onClick: generatePdf,
|
|
6134
|
+
disabled: isGenerating || !editor,
|
|
6135
|
+
className: "h-10 px-4 font-semibold shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
4759
6136
|
children: [
|
|
4760
|
-
/* @__PURE__ */ jsx(
|
|
4761
|
-
"
|
|
6137
|
+
isGenerating ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsx(RefreshCw, { size: 16 }),
|
|
6138
|
+
isGenerating ? "Generating..." : "Generate PDF"
|
|
4762
6139
|
]
|
|
4763
6140
|
}
|
|
4764
6141
|
),
|
|
4765
|
-
/* @__PURE__ */ jsxs(
|
|
6142
|
+
onSaveTemplate ? /* @__PURE__ */ jsxs(
|
|
4766
6143
|
Button,
|
|
4767
6144
|
{
|
|
4768
|
-
onClick:
|
|
4769
|
-
disabled:
|
|
6145
|
+
onClick: handleSaveTemplate,
|
|
6146
|
+
disabled: !editor || !markdown.trim(),
|
|
4770
6147
|
className: "h-10 px-4 font-semibold bg-primary hover:bg-primary/90 text-primary-foreground shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
4771
6148
|
children: [
|
|
4772
|
-
/* @__PURE__ */ jsx(
|
|
4773
|
-
exportButtonText
|
|
6149
|
+
/* @__PURE__ */ jsx(Save, { size: 16 }),
|
|
6150
|
+
exportButtonText || "Save Template"
|
|
4774
6151
|
]
|
|
4775
6152
|
}
|
|
4776
|
-
)
|
|
6153
|
+
) : /* @__PURE__ */ jsxs(Popover, { children: [
|
|
6154
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
6155
|
+
Button,
|
|
6156
|
+
{
|
|
6157
|
+
disabled: !editor || !markdown.trim() && pdfPages.length === 0,
|
|
6158
|
+
className: "h-10 px-4 font-semibold bg-primary hover:bg-primary/90 text-primary-foreground shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
6159
|
+
children: [
|
|
6160
|
+
/* @__PURE__ */ jsx(FileDown, { size: 16 }),
|
|
6161
|
+
"Export",
|
|
6162
|
+
/* @__PURE__ */ jsx(ChevronDown, { size: 14 })
|
|
6163
|
+
]
|
|
6164
|
+
}
|
|
6165
|
+
) }),
|
|
6166
|
+
/* @__PURE__ */ jsxs(PopoverContent, { align: "start", className: "w-48 p-1", children: [
|
|
6167
|
+
/* @__PURE__ */ jsxs(
|
|
6168
|
+
"button",
|
|
6169
|
+
{
|
|
6170
|
+
onClick: handleExportMarkdown,
|
|
6171
|
+
disabled: !editor || !markdown.trim(),
|
|
6172
|
+
className: "flex w-full items-center gap-2 rounded-sm px-3 py-2 text-sm hover:bg-accent hover:text-accent-foreground disabled:opacity-50 disabled:pointer-events-none transition-colors",
|
|
6173
|
+
children: [
|
|
6174
|
+
/* @__PURE__ */ jsx(FileText, { size: 16 }),
|
|
6175
|
+
"Export as MD"
|
|
6176
|
+
]
|
|
6177
|
+
}
|
|
6178
|
+
),
|
|
6179
|
+
/* @__PURE__ */ jsxs(
|
|
6180
|
+
"button",
|
|
6181
|
+
{
|
|
6182
|
+
onClick: handleExport,
|
|
6183
|
+
disabled: isGenerating || !editor || pdfPages.length === 0,
|
|
6184
|
+
className: "flex w-full items-center gap-2 rounded-sm px-3 py-2 text-sm hover:bg-accent hover:text-accent-foreground disabled:opacity-50 disabled:pointer-events-none transition-colors",
|
|
6185
|
+
children: [
|
|
6186
|
+
/* @__PURE__ */ jsx(FileDown, { size: 16 }),
|
|
6187
|
+
"Export as PDF"
|
|
6188
|
+
]
|
|
6189
|
+
}
|
|
6190
|
+
)
|
|
6191
|
+
] })
|
|
6192
|
+
] }),
|
|
6193
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 ml-auto", children: [
|
|
6194
|
+
mdExportError && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
6195
|
+
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
6196
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: mdExportError })
|
|
6197
|
+
] }),
|
|
6198
|
+
generationError && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
6199
|
+
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
6200
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: generationError })
|
|
6201
|
+
] }),
|
|
6202
|
+
fieldWarnings.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-orange-600 max-w-[400px]", children: [
|
|
6203
|
+
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
6204
|
+
/* @__PURE__ */ jsxs("span", { className: "truncate", children: [
|
|
6205
|
+
fieldWarnings.length,
|
|
6206
|
+
" field",
|
|
6207
|
+
fieldWarnings.length !== 1 ? "s" : "",
|
|
6208
|
+
" had warnings during generation"
|
|
6209
|
+
] })
|
|
6210
|
+
] }),
|
|
6211
|
+
exportSuccess && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-emerald-600 max-w-[300px]", children: [
|
|
6212
|
+
/* @__PURE__ */ jsx(CheckCircle2, { size: 14, className: "shrink-0" }),
|
|
6213
|
+
/* @__PURE__ */ jsx("span", { children: exportSuccess })
|
|
6214
|
+
] })
|
|
6215
|
+
] })
|
|
4777
6216
|
] })
|
|
4778
|
-
] })
|
|
4779
|
-
|
|
6217
|
+
] }),
|
|
6218
|
+
showPreview && !editorCollapsed && /* @__PURE__ */ jsx("div", { className: "bg-border" }),
|
|
6219
|
+
showPreview && /* @__PURE__ */ jsx(
|
|
6220
|
+
PreviewPanel,
|
|
6221
|
+
{
|
|
6222
|
+
pages: pdfPages,
|
|
6223
|
+
isGenerating,
|
|
6224
|
+
positionedFields,
|
|
6225
|
+
headerLeft: editorCollapsed ? /* @__PURE__ */ jsx(
|
|
6226
|
+
Button,
|
|
6227
|
+
{
|
|
6228
|
+
variant: "ghost",
|
|
6229
|
+
size: "sm",
|
|
6230
|
+
className: "h-8 w-8 p-0",
|
|
6231
|
+
onClick: () => setEditorCollapsed(false),
|
|
6232
|
+
children: /* @__PURE__ */ jsx(PanelLeftOpen, { size: 14 })
|
|
6233
|
+
}
|
|
6234
|
+
) : void 0,
|
|
6235
|
+
className: "flex-1 border-0 rounded-none"
|
|
6236
|
+
}
|
|
6237
|
+
)
|
|
6238
|
+
] }) }),
|
|
4780
6239
|
showGenerateTab && activeTab === "generate" && /* @__PURE__ */ jsx(
|
|
4781
6240
|
GeneratePanel,
|
|
4782
6241
|
{
|
|
@@ -4784,7 +6243,10 @@ function DocumentGeneratorInner({
|
|
|
4784
6243
|
editorContent: editor?.getJSON(),
|
|
4785
6244
|
editorVariables: variables,
|
|
4786
6245
|
onGeneratePdf,
|
|
4787
|
-
initialBulkData
|
|
6246
|
+
initialBulkData,
|
|
6247
|
+
initialVariableValues,
|
|
6248
|
+
exportFileName,
|
|
6249
|
+
templateMode
|
|
4788
6250
|
}
|
|
4789
6251
|
),
|
|
4790
6252
|
/* @__PURE__ */ jsx(
|
|
@@ -4925,6 +6387,717 @@ function EditorPanel({
|
|
|
4925
6387
|
] });
|
|
4926
6388
|
}
|
|
4927
6389
|
|
|
4928
|
-
|
|
6390
|
+
// src/utils/xml-template-parser.ts
|
|
6391
|
+
function extractFieldRefs(expr) {
|
|
6392
|
+
const refs = [];
|
|
6393
|
+
const re = /\[([^\]]+)\](?:\.\[([^\]]+)\])?/g;
|
|
6394
|
+
let m;
|
|
6395
|
+
while ((m = re.exec(expr)) !== null) {
|
|
6396
|
+
if (m[2]) {
|
|
6397
|
+
refs.push(`${m[1]}.${m[2]}`);
|
|
6398
|
+
} else {
|
|
6399
|
+
refs.push(m[1]);
|
|
6400
|
+
}
|
|
6401
|
+
}
|
|
6402
|
+
return refs;
|
|
6403
|
+
}
|
|
6404
|
+
function fieldRefToVarName(ref, bandContext) {
|
|
6405
|
+
if (ref.includes(".")) {
|
|
6406
|
+
return labelToVarName(ref.replace(/\./g, "_"));
|
|
6407
|
+
}
|
|
6408
|
+
if (bandContext) {
|
|
6409
|
+
return labelToVarName(bandContext + "_" + ref);
|
|
6410
|
+
}
|
|
6411
|
+
return labelToVarName(ref);
|
|
6412
|
+
}
|
|
6413
|
+
function fieldRefToLabel(ref) {
|
|
6414
|
+
const parts = ref.split(".");
|
|
6415
|
+
const raw = parts[parts.length - 1];
|
|
6416
|
+
return raw.replace(/_/g, " ");
|
|
6417
|
+
}
|
|
6418
|
+
function resolveExpression(expr, registry, siblingLabel, bandContext) {
|
|
6419
|
+
const trimmed = expr.trim();
|
|
6420
|
+
const suppressZero = /<>\s*0/.test(trimmed);
|
|
6421
|
+
const szOpts = suppressZero ? { suppressZero: true } : void 0;
|
|
6422
|
+
const aggMatch = trimmed.match(
|
|
6423
|
+
/^\[([^\]]+)\]\.sum\(\[([^\]]+)\]\)$/i
|
|
6424
|
+
);
|
|
6425
|
+
if (aggMatch) {
|
|
6426
|
+
const fieldName = aggMatch[2];
|
|
6427
|
+
const varName = labelToVarName("workorder_" + fieldName);
|
|
6428
|
+
const label = fieldName.replace(/_/g, " ");
|
|
6429
|
+
return registry.register(varName, label, szOpts);
|
|
6430
|
+
}
|
|
6431
|
+
const concatSingle = trimmed.match(
|
|
6432
|
+
/^concat\(\s*'[^']*'\s*,\s*\[([^\]]+)\](?:\.\[([^\]]+)\])?\s*\)$/i
|
|
6433
|
+
);
|
|
6434
|
+
if (concatSingle) {
|
|
6435
|
+
const ref = concatSingle[2] ? `${concatSingle[1]}.${concatSingle[2]}` : concatSingle[1];
|
|
6436
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6437
|
+
const label = fieldRefToLabel(ref);
|
|
6438
|
+
return registry.register(varName, label, szOpts);
|
|
6439
|
+
}
|
|
6440
|
+
const concatMulti = trimmed.match(
|
|
6441
|
+
/^concat\(/i
|
|
6442
|
+
);
|
|
6443
|
+
if (concatMulti) {
|
|
6444
|
+
const refs = extractFieldRefs(trimmed);
|
|
6445
|
+
if (refs.length > 0) {
|
|
6446
|
+
const uniqueRefs = [...new Set(refs)];
|
|
6447
|
+
const tokens = uniqueRefs.map((ref) => {
|
|
6448
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6449
|
+
const label = fieldRefToLabel(ref);
|
|
6450
|
+
return registry.register(varName, label, szOpts);
|
|
6451
|
+
});
|
|
6452
|
+
return tokens.join(" ");
|
|
6453
|
+
}
|
|
6454
|
+
}
|
|
6455
|
+
const toLongMatch = trimmed.match(
|
|
6456
|
+
/^ToLong\(\s*\[([^\]]+)\](?:\.\[([^\]]+)\])?\s*\)$/i
|
|
6457
|
+
);
|
|
6458
|
+
if (toLongMatch) {
|
|
6459
|
+
const ref = toLongMatch[2] ? `${toLongMatch[1]}.${toLongMatch[2]}` : toLongMatch[1];
|
|
6460
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6461
|
+
const label = fieldRefToLabel(ref);
|
|
6462
|
+
return registry.register(varName, label, szOpts);
|
|
6463
|
+
}
|
|
6464
|
+
const fmtMatch = trimmed.match(
|
|
6465
|
+
/^FormatString\(\s*'[^']*'\s*,\s*(.+)\s*\)$/i
|
|
6466
|
+
);
|
|
6467
|
+
if (fmtMatch) {
|
|
6468
|
+
return resolveExpression(fmtMatch[1], registry, siblingLabel, bandContext);
|
|
6469
|
+
}
|
|
6470
|
+
const replaceMatch = trimmed.match(
|
|
6471
|
+
/^Replace\(\s*\[([^\]]+)\]/i
|
|
6472
|
+
);
|
|
6473
|
+
if (replaceMatch) {
|
|
6474
|
+
const ref = replaceMatch[1];
|
|
6475
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6476
|
+
const label = fieldRefToLabel(ref);
|
|
6477
|
+
return registry.register(varName, label, szOpts);
|
|
6478
|
+
}
|
|
6479
|
+
const iifLabelMatch = trimmed.match(
|
|
6480
|
+
/^iif\s*\(.+?,\s*'([^']+)'\s*,\s*\?\s*\)$/i
|
|
6481
|
+
);
|
|
6482
|
+
if (iifLabelMatch) {
|
|
6483
|
+
return iifLabelMatch[1];
|
|
6484
|
+
}
|
|
6485
|
+
const iifLabelElse = trimmed.match(
|
|
6486
|
+
/^iif\s*\(.+?,\s*'([^']+)'\s*,\s*'([^']+)'\s*\)$/i
|
|
6487
|
+
);
|
|
6488
|
+
if (iifLabelElse) {
|
|
6489
|
+
return iifLabelElse[1];
|
|
6490
|
+
}
|
|
6491
|
+
const iifEmptyElse = trimmed.match(
|
|
6492
|
+
/^[Ii]if\s*\(.+?,\s*'([^']+)'\s*,\s*''\s*\)$/i
|
|
6493
|
+
);
|
|
6494
|
+
if (iifEmptyElse) {
|
|
6495
|
+
return iifEmptyElse[1];
|
|
6496
|
+
}
|
|
6497
|
+
const nestedIifStaticLabel = trimmed.match(
|
|
6498
|
+
/^iif\s*\(.*,\s*[Ii]if\s*\([^,]+,\s*'([^']+)'\s*,/i
|
|
6499
|
+
);
|
|
6500
|
+
if (nestedIifStaticLabel) {
|
|
6501
|
+
return nestedIifStaticLabel[1];
|
|
6502
|
+
}
|
|
6503
|
+
const iifValueMatch = trimmed.match(
|
|
6504
|
+
/^iif\s*\(/i
|
|
6505
|
+
);
|
|
6506
|
+
if (iifValueMatch) {
|
|
6507
|
+
const refs = extractFieldRefs(trimmed);
|
|
6508
|
+
if (refs.length > 0) {
|
|
6509
|
+
const uniqueRefs = [...new Set(refs)];
|
|
6510
|
+
const innerAggMatch = trimmed.match(
|
|
6511
|
+
/\[([^\]]+)\]\.sum\(\[([^\]]+)\]\)/i
|
|
6512
|
+
);
|
|
6513
|
+
if (innerAggMatch) {
|
|
6514
|
+
const fieldName = innerAggMatch[2];
|
|
6515
|
+
const varName2 = labelToVarName("workorder_" + fieldName);
|
|
6516
|
+
const label2 = fieldName.replace(/_/g, " ");
|
|
6517
|
+
return registry.register(varName2, label2, szOpts);
|
|
6518
|
+
}
|
|
6519
|
+
const ref = uniqueRefs[uniqueRefs.length - 1];
|
|
6520
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6521
|
+
const label = fieldRefToLabel(ref);
|
|
6522
|
+
return registry.register(varName, label, szOpts);
|
|
6523
|
+
}
|
|
6524
|
+
const staticMatch = trimmed.match(/'([^']+)'/);
|
|
6525
|
+
if (staticMatch) return staticMatch[1];
|
|
6526
|
+
return "";
|
|
6527
|
+
}
|
|
6528
|
+
const arithmeticRefs = extractFieldRefs(trimmed);
|
|
6529
|
+
if (arithmeticRefs.length > 1 && /[+\-*/]/.test(trimmed)) {
|
|
6530
|
+
if (siblingLabel) {
|
|
6531
|
+
const cleanLabel = siblingLabel.replace(/[:\s]+$/, "");
|
|
6532
|
+
const varName = labelToVarName(
|
|
6533
|
+
(bandContext ? bandContext + "_" : "") + cleanLabel.replace(/\s+/g, "_")
|
|
6534
|
+
);
|
|
6535
|
+
return registry.register(varName, cleanLabel, szOpts);
|
|
6536
|
+
}
|
|
6537
|
+
const uniqueRefs = [...new Set(arithmeticRefs)];
|
|
6538
|
+
const tokens = uniqueRefs.map((ref) => {
|
|
6539
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6540
|
+
const label = fieldRefToLabel(ref);
|
|
6541
|
+
return registry.register(varName, label, szOpts);
|
|
6542
|
+
});
|
|
6543
|
+
return tokens.join(" ");
|
|
6544
|
+
}
|
|
6545
|
+
const simpleMatch = trimmed.match(
|
|
6546
|
+
/^\[([^\]]+)\](?:\.\[([^\]]+)\])?$/
|
|
6547
|
+
);
|
|
6548
|
+
if (simpleMatch) {
|
|
6549
|
+
const ref = simpleMatch[2] ? `${simpleMatch[1]}.${simpleMatch[2]}` : simpleMatch[1];
|
|
6550
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6551
|
+
const label = fieldRefToLabel(ref);
|
|
6552
|
+
return registry.register(varName, label, szOpts);
|
|
6553
|
+
}
|
|
6554
|
+
if (arithmeticRefs.length >= 1) {
|
|
6555
|
+
const ref = arithmeticRefs[arithmeticRefs.length - 1];
|
|
6556
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6557
|
+
const label = fieldRefToLabel(ref);
|
|
6558
|
+
return registry.register(varName, label, szOpts);
|
|
6559
|
+
}
|
|
6560
|
+
return "";
|
|
6561
|
+
}
|
|
6562
|
+
var VariableRegistry = class {
|
|
6563
|
+
constructor() {
|
|
6564
|
+
__publicField(this, "map", /* @__PURE__ */ new Map());
|
|
6565
|
+
}
|
|
6566
|
+
register(varName, varLabel, options) {
|
|
6567
|
+
if (!this.map.has(varName)) {
|
|
6568
|
+
this.map.set(varName, { varName, varLabel, varDefault: "" });
|
|
6569
|
+
}
|
|
6570
|
+
const suppress = options?.suppressZero ? "|suppress:zero" : "";
|
|
6571
|
+
return `{{var|name:${varName}|label:${varLabel}${suppress}}}`;
|
|
6572
|
+
}
|
|
6573
|
+
getAll() {
|
|
6574
|
+
return Array.from(this.map.values());
|
|
6575
|
+
}
|
|
6576
|
+
};
|
|
6577
|
+
function parseLocationFloat(s) {
|
|
6578
|
+
if (!s) return { x: 0, y: 0 };
|
|
6579
|
+
const parts = s.split(",");
|
|
6580
|
+
return {
|
|
6581
|
+
x: parseFloat(parts[0]) || 0,
|
|
6582
|
+
y: parseFloat(parts[1]) || 0
|
|
6583
|
+
};
|
|
6584
|
+
}
|
|
6585
|
+
function parseSizeF(s) {
|
|
6586
|
+
if (!s) return { w: 0, h: 0 };
|
|
6587
|
+
const parts = s.split(",");
|
|
6588
|
+
return {
|
|
6589
|
+
w: parseFloat(parts[0]) || 0,
|
|
6590
|
+
h: parseFloat(parts[1]) || 0
|
|
6591
|
+
};
|
|
6592
|
+
}
|
|
6593
|
+
function extractControls(bandEl) {
|
|
6594
|
+
const controlsEl = bandEl.querySelector(":scope > Controls");
|
|
6595
|
+
if (!controlsEl) return [];
|
|
6596
|
+
const items = [];
|
|
6597
|
+
for (const child of Array.from(controlsEl.children)) {
|
|
6598
|
+
items.push(extractControl(child));
|
|
6599
|
+
}
|
|
6600
|
+
return items;
|
|
6601
|
+
}
|
|
6602
|
+
function extractControl(el) {
|
|
6603
|
+
const ctrlType = el.getAttribute("ControlType") || "";
|
|
6604
|
+
const name = el.getAttribute("Name") || "";
|
|
6605
|
+
const text = el.getAttribute("Text") || "";
|
|
6606
|
+
const loc = parseLocationFloat(el.getAttribute("LocationFloat"));
|
|
6607
|
+
const size = parseSizeF(el.getAttribute("SizeF"));
|
|
6608
|
+
const formatString = el.getAttribute("TextFormatString") || null;
|
|
6609
|
+
let expression = null;
|
|
6610
|
+
const bindingsEl = el.querySelector(":scope > ExpressionBindings");
|
|
6611
|
+
if (bindingsEl) {
|
|
6612
|
+
for (const item of Array.from(bindingsEl.children)) {
|
|
6613
|
+
if (item.getAttribute("PropertyName") === "Text" && item.getAttribute("EventName") === "BeforePrint") {
|
|
6614
|
+
expression = item.getAttribute("Expression");
|
|
6615
|
+
break;
|
|
6616
|
+
}
|
|
6617
|
+
}
|
|
6618
|
+
}
|
|
6619
|
+
const children = [];
|
|
6620
|
+
if (ctrlType === "XRPanel") {
|
|
6621
|
+
const panelControls = extractControls(el);
|
|
6622
|
+
children.push(...panelControls);
|
|
6623
|
+
}
|
|
6624
|
+
return {
|
|
6625
|
+
name,
|
|
6626
|
+
type: ctrlType,
|
|
6627
|
+
text,
|
|
6628
|
+
x: loc.x,
|
|
6629
|
+
y: loc.y,
|
|
6630
|
+
width: size.w,
|
|
6631
|
+
expression,
|
|
6632
|
+
formatString,
|
|
6633
|
+
children
|
|
6634
|
+
};
|
|
6635
|
+
}
|
|
6636
|
+
function groupByRow(controls, threshold = 8) {
|
|
6637
|
+
const sorted = [...controls].sort((a, b) => a.y - b.y);
|
|
6638
|
+
const rows = [];
|
|
6639
|
+
for (const ctrl of sorted) {
|
|
6640
|
+
const lastRow = rows[rows.length - 1];
|
|
6641
|
+
if (lastRow && Math.abs(ctrl.y - lastRow.y) < threshold) {
|
|
6642
|
+
lastRow.controls.push(ctrl);
|
|
6643
|
+
} else {
|
|
6644
|
+
rows.push({ y: ctrl.y, controls: [ctrl] });
|
|
6645
|
+
}
|
|
6646
|
+
}
|
|
6647
|
+
for (const row of rows) {
|
|
6648
|
+
row.controls.sort((a, b) => a.x - b.x);
|
|
6649
|
+
}
|
|
6650
|
+
return rows;
|
|
6651
|
+
}
|
|
6652
|
+
function isStaticLabel(ctrl) {
|
|
6653
|
+
return ctrl.type === "XRLabel" && !ctrl.expression;
|
|
6654
|
+
}
|
|
6655
|
+
function isBound(ctrl) {
|
|
6656
|
+
return ctrl.type === "XRLabel" && !!ctrl.expression;
|
|
6657
|
+
}
|
|
6658
|
+
function computeSplitPercent(leftControls, rightControls, contentWidth) {
|
|
6659
|
+
const leftExtent = leftControls.reduce(
|
|
6660
|
+
(max, c) => Math.max(max, c.x + c.width),
|
|
6661
|
+
0
|
|
6662
|
+
);
|
|
6663
|
+
const rightMinX = rightControls.reduce(
|
|
6664
|
+
(min, c) => Math.min(min, c.x),
|
|
6665
|
+
contentWidth
|
|
6666
|
+
);
|
|
6667
|
+
const rightMaxExtent = rightControls.reduce(
|
|
6668
|
+
(max, c) => Math.max(max, c.x + c.width),
|
|
6669
|
+
0
|
|
6670
|
+
);
|
|
6671
|
+
const rightExtent = rightMaxExtent - rightMinX;
|
|
6672
|
+
if (leftExtent + rightExtent === 0) return 50;
|
|
6673
|
+
return Math.round(leftExtent / (leftExtent + rightExtent) * 100);
|
|
6674
|
+
}
|
|
6675
|
+
function renderTopMarginBand(controls, registry, contentWidth, calculatedFieldNames) {
|
|
6676
|
+
const lines = [];
|
|
6677
|
+
const leftControls = controls.filter((c) => c.x < 437);
|
|
6678
|
+
const rightControls = controls.filter((c) => c.x >= 437);
|
|
6679
|
+
const leftEntries = [];
|
|
6680
|
+
if (leftControls.length > 0) {
|
|
6681
|
+
const leftRows = groupByRow(leftControls);
|
|
6682
|
+
for (const row of leftRows) {
|
|
6683
|
+
const parts = [];
|
|
6684
|
+
for (const ctrl of row.controls) {
|
|
6685
|
+
if (isBound(ctrl)) {
|
|
6686
|
+
let resolved = resolveExpression(ctrl.expression, registry, void 0, "workorder");
|
|
6687
|
+
if (ctrl.formatString && /\(###\)\s*###\s*-\s*####/.test(ctrl.formatString) && resolved.startsWith("{{var|")) {
|
|
6688
|
+
resolved = resolved.replace("}}", "|format:phone}}");
|
|
6689
|
+
}
|
|
6690
|
+
if (resolved) parts.push(resolved);
|
|
6691
|
+
} else if (isStaticLabel(ctrl)) {
|
|
6692
|
+
const text = ctrl.text.trim();
|
|
6693
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
6694
|
+
parts.push(text);
|
|
6695
|
+
}
|
|
6696
|
+
}
|
|
6697
|
+
}
|
|
6698
|
+
if (parts.length > 0) {
|
|
6699
|
+
leftEntries.push(parts.join(" "));
|
|
6700
|
+
}
|
|
6701
|
+
}
|
|
6702
|
+
}
|
|
6703
|
+
const rightEntries = [];
|
|
6704
|
+
if (rightControls.length > 0) {
|
|
6705
|
+
const rightRows = groupByRow(rightControls);
|
|
6706
|
+
for (const row of rightRows) {
|
|
6707
|
+
let label = "";
|
|
6708
|
+
let value = "";
|
|
6709
|
+
for (const ctrl of row.controls) {
|
|
6710
|
+
if (isStaticLabel(ctrl)) {
|
|
6711
|
+
const text = ctrl.text.trim();
|
|
6712
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
6713
|
+
label = text;
|
|
6714
|
+
}
|
|
6715
|
+
} else if (isBound(ctrl)) {
|
|
6716
|
+
value = resolveExpression(ctrl.expression, registry, label, "workorder");
|
|
6717
|
+
if (ctrl.expression) {
|
|
6718
|
+
const refs = extractFieldRefs(ctrl.expression);
|
|
6719
|
+
if (refs.some((ref) => calculatedFieldNames.has(ref)) && value.startsWith("{{var|")) {
|
|
6720
|
+
value = value.replace("}}", "|suppress:zero}}");
|
|
6721
|
+
}
|
|
6722
|
+
}
|
|
6723
|
+
if (ctrl.formatString && /\(###\)\s*###\s*-\s*####/.test(ctrl.formatString) && value.startsWith("{{var|")) {
|
|
6724
|
+
value = value.replace("}}", "|format:phone}}");
|
|
6725
|
+
}
|
|
6726
|
+
}
|
|
6727
|
+
}
|
|
6728
|
+
if (label || value) {
|
|
6729
|
+
rightEntries.push({ label, value, y: row.y });
|
|
6730
|
+
}
|
|
6731
|
+
}
|
|
6732
|
+
}
|
|
6733
|
+
if (leftEntries.length > 0 || rightEntries.length > 0) {
|
|
6734
|
+
const outerSplit = computeSplitPercent(leftControls, rightControls, contentWidth);
|
|
6735
|
+
lines.push(`:::columns{split:${outerSplit}}`);
|
|
6736
|
+
const leftMinY = leftControls.length > 0 ? Math.min(...leftControls.map((c) => c.y)) : 0;
|
|
6737
|
+
const rightMinY = rightControls.length > 0 ? Math.min(...rightControls.map((c) => c.y)) : 0;
|
|
6738
|
+
const xmlLineHeight = 17;
|
|
6739
|
+
const leftPadTop = leftMinY > rightMinY ? Math.round((leftMinY - rightMinY) / xmlLineHeight) : 0;
|
|
6740
|
+
const rightPadTop = rightMinY > leftMinY ? Math.round((rightMinY - leftMinY) / xmlLineHeight) : 0;
|
|
6741
|
+
const leftColTag = leftPadTop ? `:::col{padTop:${leftPadTop}}` : ":::col";
|
|
6742
|
+
lines.push(leftColTag);
|
|
6743
|
+
for (const entry of leftEntries) {
|
|
6744
|
+
lines.push(entry);
|
|
6745
|
+
}
|
|
6746
|
+
lines.push(":::");
|
|
6747
|
+
const rightColTag = rightPadTop ? `:::col{padTop:${rightPadTop}}` : ":::col";
|
|
6748
|
+
lines.push(rightColTag);
|
|
6749
|
+
if (rightEntries.length > 0) {
|
|
6750
|
+
lines.push("| | |");
|
|
6751
|
+
lines.push("|---|---|");
|
|
6752
|
+
let prevY = -Infinity;
|
|
6753
|
+
for (const entry of rightEntries) {
|
|
6754
|
+
if (prevY > -Infinity && entry.y - prevY > 25) {
|
|
6755
|
+
lines.push("| | |");
|
|
6756
|
+
}
|
|
6757
|
+
const label = entry.label || "";
|
|
6758
|
+
const colon = entry.label ? ":" : "";
|
|
6759
|
+
const value = entry.value ? ` ${entry.value}` : "";
|
|
6760
|
+
lines.push(`| ${label} | ${colon}${value} |`);
|
|
6761
|
+
prevY = entry.y;
|
|
6762
|
+
}
|
|
6763
|
+
}
|
|
6764
|
+
lines.push(":::");
|
|
6765
|
+
lines.push(":::");
|
|
6766
|
+
lines.push("");
|
|
6767
|
+
}
|
|
6768
|
+
return lines.join("\n");
|
|
6769
|
+
}
|
|
6770
|
+
function renderReportHeaderBand(controls, registry, contentWidth) {
|
|
6771
|
+
const lines = [];
|
|
6772
|
+
const leftControls = controls.filter((c) => c.x < 437);
|
|
6773
|
+
const rightControls = controls.filter((c) => c.x >= 437);
|
|
6774
|
+
const leftEntries = [];
|
|
6775
|
+
if (leftControls.length > 0) {
|
|
6776
|
+
const leftRows = groupByRow(leftControls);
|
|
6777
|
+
for (const row of leftRows) {
|
|
6778
|
+
const parts = [];
|
|
6779
|
+
for (const ctrl of row.controls) {
|
|
6780
|
+
if (isBound(ctrl)) {
|
|
6781
|
+
const resolved = resolveExpression(ctrl.expression, registry, void 0, "workorder");
|
|
6782
|
+
if (resolved) parts.push(resolved);
|
|
6783
|
+
} else if (isStaticLabel(ctrl)) {
|
|
6784
|
+
const text = ctrl.text.trim();
|
|
6785
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
6786
|
+
parts.push(text);
|
|
6787
|
+
}
|
|
6788
|
+
}
|
|
6789
|
+
}
|
|
6790
|
+
if (parts.length > 0) {
|
|
6791
|
+
leftEntries.push(parts.join(" "));
|
|
6792
|
+
}
|
|
6793
|
+
}
|
|
6794
|
+
}
|
|
6795
|
+
const rightEntries = [];
|
|
6796
|
+
if (rightControls.length > 0) {
|
|
6797
|
+
const rightRows = groupByRow(rightControls);
|
|
6798
|
+
for (const row of rightRows) {
|
|
6799
|
+
let label = "";
|
|
6800
|
+
let value = "";
|
|
6801
|
+
for (const ctrl of row.controls) {
|
|
6802
|
+
if (isStaticLabel(ctrl)) {
|
|
6803
|
+
const text = ctrl.text.trim();
|
|
6804
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
6805
|
+
label = text;
|
|
6806
|
+
}
|
|
6807
|
+
} else if (isBound(ctrl)) {
|
|
6808
|
+
value = resolveExpression(ctrl.expression, registry, void 0, "workorder");
|
|
6809
|
+
if (ctrl.formatString && /\(###\)\s*###\s*-\s*####/.test(ctrl.formatString) && value.startsWith("{{var|")) {
|
|
6810
|
+
value = value.replace("}}", "|format:phone}}");
|
|
6811
|
+
}
|
|
6812
|
+
}
|
|
6813
|
+
}
|
|
6814
|
+
if (label || value) {
|
|
6815
|
+
rightEntries.push({ label, value });
|
|
6816
|
+
}
|
|
6817
|
+
}
|
|
6818
|
+
}
|
|
6819
|
+
if (leftEntries.length > 0 || rightEntries.length > 0) {
|
|
6820
|
+
const outerSplit = computeSplitPercent(leftControls, rightControls, contentWidth);
|
|
6821
|
+
lines.push(`:::columns{split:${outerSplit}}`);
|
|
6822
|
+
lines.push(":::col");
|
|
6823
|
+
for (const entry of leftEntries) {
|
|
6824
|
+
lines.push(entry);
|
|
6825
|
+
}
|
|
6826
|
+
lines.push(":::");
|
|
6827
|
+
lines.push(":::col");
|
|
6828
|
+
lines.push("| | |");
|
|
6829
|
+
lines.push("|---|---|");
|
|
6830
|
+
for (const entry of rightEntries) {
|
|
6831
|
+
const label = entry.label || "";
|
|
6832
|
+
const value = entry.value ? ` ${entry.value}` : "";
|
|
6833
|
+
lines.push(`| ${label} |${value} |`);
|
|
6834
|
+
}
|
|
6835
|
+
lines.push(":::");
|
|
6836
|
+
lines.push(":::");
|
|
6837
|
+
lines.push("");
|
|
6838
|
+
}
|
|
6839
|
+
return lines.join("\n");
|
|
6840
|
+
}
|
|
6841
|
+
function renderOperationsSection(pageHeaderControls, detailControls, groupFooterControls, registry, contentWidth) {
|
|
6842
|
+
const lines = [];
|
|
6843
|
+
const headerTexts = [];
|
|
6844
|
+
for (const ctrl of [...pageHeaderControls].sort((a, b) => a.x - b.x)) {
|
|
6845
|
+
if (isStaticLabel(ctrl)) {
|
|
6846
|
+
const text = ctrl.text.trim();
|
|
6847
|
+
if (text) headerTexts.push(text);
|
|
6848
|
+
}
|
|
6849
|
+
}
|
|
6850
|
+
const titleText = headerTexts.join(" ");
|
|
6851
|
+
lines.push(`:::panel{title:${titleText}|headerStyle:dark|border:none}`);
|
|
6852
|
+
lines.push(":::");
|
|
6853
|
+
lines.push(":::repeat{data:operations}");
|
|
6854
|
+
const sortedDetail = [...detailControls].sort((a, b) => a.x - b.x);
|
|
6855
|
+
const detailParts = [];
|
|
6856
|
+
for (const ctrl of sortedDetail) {
|
|
6857
|
+
if (isBound(ctrl)) {
|
|
6858
|
+
const resolved = resolveExpression(ctrl.expression, registry, void 0, "operation");
|
|
6859
|
+
if (resolved) detailParts.push(resolved);
|
|
6860
|
+
}
|
|
6861
|
+
}
|
|
6862
|
+
if (detailParts.length > 0) {
|
|
6863
|
+
if (detailParts.length >= 2) {
|
|
6864
|
+
lines.push(":::columns{split:12}");
|
|
6865
|
+
lines.push(":::col");
|
|
6866
|
+
lines.push(`**${detailParts[0]}**`);
|
|
6867
|
+
lines.push(":::");
|
|
6868
|
+
lines.push(":::col");
|
|
6869
|
+
lines.push(`**${detailParts.slice(1).join(" ")}**`);
|
|
6870
|
+
lines.push(":::");
|
|
6871
|
+
lines.push(":::");
|
|
6872
|
+
} else {
|
|
6873
|
+
lines.push(`**${detailParts[0]}**`);
|
|
6874
|
+
}
|
|
6875
|
+
lines.push("");
|
|
6876
|
+
}
|
|
6877
|
+
if (groupFooterControls.length > 0) {
|
|
6878
|
+
const subtotalsMarkdown = renderGroupFooterBand(groupFooterControls, registry);
|
|
6879
|
+
if (subtotalsMarkdown) lines.push(subtotalsMarkdown);
|
|
6880
|
+
}
|
|
6881
|
+
lines.push("---");
|
|
6882
|
+
lines.push(":::");
|
|
6883
|
+
lines.push("");
|
|
6884
|
+
return lines.join("\n");
|
|
6885
|
+
}
|
|
6886
|
+
function renderGroupFooterBand(controls, registry, contentWidth) {
|
|
6887
|
+
const rows = groupByRow(controls);
|
|
6888
|
+
const entries = [];
|
|
6889
|
+
for (const row of rows) {
|
|
6890
|
+
const labels = row.controls.filter((c) => c.type === "XRLabel");
|
|
6891
|
+
if (labels.length === 0) continue;
|
|
6892
|
+
let label = "";
|
|
6893
|
+
let value = "";
|
|
6894
|
+
const sorted = [...labels].sort((a, b) => a.x - b.x);
|
|
6895
|
+
for (const ctrl of sorted) {
|
|
6896
|
+
if (isStaticLabel(ctrl)) {
|
|
6897
|
+
const text = ctrl.text.trim();
|
|
6898
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
6899
|
+
label = text;
|
|
6900
|
+
}
|
|
6901
|
+
} else if (isBound(ctrl)) {
|
|
6902
|
+
const resolved = resolveExpression(ctrl.expression, registry, label, "operation");
|
|
6903
|
+
if (resolved) {
|
|
6904
|
+
if (!resolved.startsWith("{{var|")) {
|
|
6905
|
+
if (!label) label = resolved;
|
|
6906
|
+
} else {
|
|
6907
|
+
value = resolved;
|
|
6908
|
+
}
|
|
6909
|
+
}
|
|
6910
|
+
}
|
|
6911
|
+
}
|
|
6912
|
+
if (label && value) {
|
|
6913
|
+
entries.push({ label, value });
|
|
6914
|
+
}
|
|
6915
|
+
}
|
|
6916
|
+
if (entries.length === 0) return "";
|
|
6917
|
+
const lines = [];
|
|
6918
|
+
lines.push(":::subtotals");
|
|
6919
|
+
for (const entry of entries) {
|
|
6920
|
+
lines.push(`**${entry.label}** ${entry.value}`);
|
|
6921
|
+
}
|
|
6922
|
+
lines.push(":::");
|
|
6923
|
+
lines.push("");
|
|
6924
|
+
return lines.join("\n");
|
|
6925
|
+
}
|
|
6926
|
+
function renderReportFooterBand(controls, registry) {
|
|
6927
|
+
const lines = [];
|
|
6928
|
+
const panel = controls.find((c) => c.type === "XRPanel");
|
|
6929
|
+
const thankYouLabel = controls.find(
|
|
6930
|
+
(c) => c.type === "XRLabel" && c.text.includes("Thank You")
|
|
6931
|
+
);
|
|
6932
|
+
const priceRows = [];
|
|
6933
|
+
if (panel && panel.children.length > 0) {
|
|
6934
|
+
const rows = groupByRow(panel.children, 15);
|
|
6935
|
+
for (const row of rows) {
|
|
6936
|
+
const labels = row.controls.filter((c) => c.type === "XRLabel");
|
|
6937
|
+
if (labels.length === 0) continue;
|
|
6938
|
+
let label = "";
|
|
6939
|
+
let value = "";
|
|
6940
|
+
const sorted = [...labels].sort((a, b) => a.x - b.x);
|
|
6941
|
+
for (const ctrl of sorted) {
|
|
6942
|
+
if (isStaticLabel(ctrl) && ctrl.text.includes("Estimate Price Summary")) {
|
|
6943
|
+
continue;
|
|
6944
|
+
}
|
|
6945
|
+
const isLabelPos = ctrl.x < 150;
|
|
6946
|
+
if (isStaticLabel(ctrl)) {
|
|
6947
|
+
const text = ctrl.text.trim();
|
|
6948
|
+
if (isLabelPos && text && !text.match(/^label\d+$/) && text !== "0.00") {
|
|
6949
|
+
label = text;
|
|
6950
|
+
}
|
|
6951
|
+
} else if (isBound(ctrl)) {
|
|
6952
|
+
const resolved = resolveExpression(ctrl.expression, registry, label || void 0, "workorder");
|
|
6953
|
+
if (resolved) {
|
|
6954
|
+
if (isLabelPos) {
|
|
6955
|
+
if (!label) label = resolved;
|
|
6956
|
+
} else {
|
|
6957
|
+
if (!value) value = resolved;
|
|
6958
|
+
}
|
|
6959
|
+
}
|
|
6960
|
+
}
|
|
6961
|
+
}
|
|
6962
|
+
if (label && value) {
|
|
6963
|
+
priceRows.push({ label, value });
|
|
6964
|
+
} else if (value) {
|
|
6965
|
+
priceRows.push({ label: "", value });
|
|
6966
|
+
}
|
|
6967
|
+
}
|
|
6968
|
+
}
|
|
6969
|
+
lines.push(":::columns{split:40|padX:20}");
|
|
6970
|
+
lines.push(":::col{padTop:1}");
|
|
6971
|
+
if (thankYouLabel) {
|
|
6972
|
+
lines.push(`## ***${thankYouLabel.text.trim()}***`);
|
|
6973
|
+
}
|
|
6974
|
+
lines.push(":::");
|
|
6975
|
+
lines.push(":::col{padTop:1}");
|
|
6976
|
+
lines.push(":::panel{title:Estimate Price Summary|border:solid|headerStyle:dark}");
|
|
6977
|
+
if (priceRows.length > 0) {
|
|
6978
|
+
lines.push(":::subtotals");
|
|
6979
|
+
for (const row of priceRows) {
|
|
6980
|
+
const label = row.label ? `**${row.label}**` : "";
|
|
6981
|
+
lines.push(`${label} ${row.value}`);
|
|
6982
|
+
}
|
|
6983
|
+
lines.push(":::");
|
|
6984
|
+
}
|
|
6985
|
+
lines.push(":::");
|
|
6986
|
+
lines.push(":::");
|
|
6987
|
+
lines.push(":::");
|
|
6988
|
+
lines.push("");
|
|
6989
|
+
return lines.join("\n");
|
|
6990
|
+
}
|
|
6991
|
+
function parseXmlTemplate(xmlString) {
|
|
6992
|
+
const warnings = [];
|
|
6993
|
+
const registry = new VariableRegistry();
|
|
6994
|
+
const parser = new DOMParser();
|
|
6995
|
+
const doc = parser.parseFromString(xmlString, "text/xml");
|
|
6996
|
+
const parseError = doc.querySelector("parsererror");
|
|
6997
|
+
if (parseError) {
|
|
6998
|
+
return {
|
|
6999
|
+
markdown: "",
|
|
7000
|
+
variables: [],
|
|
7001
|
+
reportName: "",
|
|
7002
|
+
warnings: ["XML parse error: " + (parseError.textContent || "Unknown error")]
|
|
7003
|
+
};
|
|
7004
|
+
}
|
|
7005
|
+
const root = doc.documentElement;
|
|
7006
|
+
const reportName = root.getAttribute("DisplayName") || root.getAttribute("Name") || "Report";
|
|
7007
|
+
const pageWidth = parseInt(root.getAttribute("PageWidth") || "850", 10);
|
|
7008
|
+
const marginsStr = root.getAttribute("Margins") || "50,50,250,50";
|
|
7009
|
+
const marginParts = marginsStr.split(",").map(Number);
|
|
7010
|
+
const leftMargin = marginParts[0] || 50;
|
|
7011
|
+
const rightMargin = marginParts[1] || 50;
|
|
7012
|
+
const contentWidth = pageWidth - leftMargin - rightMargin;
|
|
7013
|
+
const calculatedFieldNames = /* @__PURE__ */ new Set();
|
|
7014
|
+
const calcFieldsEl = root.querySelector(":scope > CalculatedFields");
|
|
7015
|
+
if (calcFieldsEl) {
|
|
7016
|
+
for (const item of Array.from(calcFieldsEl.children)) {
|
|
7017
|
+
const name = item.getAttribute("Name");
|
|
7018
|
+
if (name) calculatedFieldNames.add(name);
|
|
7019
|
+
}
|
|
7020
|
+
}
|
|
7021
|
+
const markdownSections = [];
|
|
7022
|
+
const watermarkEl = root.querySelector("Watermarks > Item1");
|
|
7023
|
+
if (watermarkEl) {
|
|
7024
|
+
const wmText = watermarkEl.getAttribute("Text");
|
|
7025
|
+
if (wmText) {
|
|
7026
|
+
markdownSections.push(`:::watermark{text:${wmText}|opacity:0.15|angle:45}`);
|
|
7027
|
+
markdownSections.push("");
|
|
7028
|
+
}
|
|
7029
|
+
}
|
|
7030
|
+
const bandsEl = root.querySelector(":scope > Bands");
|
|
7031
|
+
if (!bandsEl) {
|
|
7032
|
+
warnings.push("No <Bands> element found in the XML");
|
|
7033
|
+
return {
|
|
7034
|
+
markdown: "",
|
|
7035
|
+
variables: registry.getAll(),
|
|
7036
|
+
reportName,
|
|
7037
|
+
warnings
|
|
7038
|
+
};
|
|
7039
|
+
}
|
|
7040
|
+
let topMarginControls = [];
|
|
7041
|
+
let reportHeaderControls = [];
|
|
7042
|
+
let pageHeaderControls = [];
|
|
7043
|
+
let detailControls = [];
|
|
7044
|
+
let groupFooterControls = [];
|
|
7045
|
+
let reportFooterControls = [];
|
|
7046
|
+
for (const bandItem of Array.from(bandsEl.children)) {
|
|
7047
|
+
const ctrlType = bandItem.getAttribute("ControlType") || "";
|
|
7048
|
+
if (ctrlType === "TopMarginBand") {
|
|
7049
|
+
topMarginControls = extractControls(bandItem);
|
|
7050
|
+
} else if (ctrlType === "ReportHeaderBand") {
|
|
7051
|
+
reportHeaderControls = extractControls(bandItem);
|
|
7052
|
+
} else if (ctrlType === "PageHeaderBand") {
|
|
7053
|
+
pageHeaderControls = extractControls(bandItem);
|
|
7054
|
+
} else if (ctrlType === "DetailReportBand") {
|
|
7055
|
+
const nestedBands = bandItem.querySelector(":scope > Bands");
|
|
7056
|
+
if (nestedBands) {
|
|
7057
|
+
for (const nested of Array.from(nestedBands.children)) {
|
|
7058
|
+
const nestedType = nested.getAttribute("ControlType") || "";
|
|
7059
|
+
if (nestedType === "DetailBand") {
|
|
7060
|
+
detailControls = extractControls(nested);
|
|
7061
|
+
} else if (nestedType === "GroupFooterBand") {
|
|
7062
|
+
groupFooterControls = extractControls(nested);
|
|
7063
|
+
}
|
|
7064
|
+
}
|
|
7065
|
+
}
|
|
7066
|
+
} else if (ctrlType === "ReportFooterBand") {
|
|
7067
|
+
reportFooterControls = extractControls(bandItem);
|
|
7068
|
+
}
|
|
7069
|
+
}
|
|
7070
|
+
if (topMarginControls.length > 0) {
|
|
7071
|
+
markdownSections.push(renderTopMarginBand(topMarginControls, registry, contentWidth, calculatedFieldNames));
|
|
7072
|
+
}
|
|
7073
|
+
if (reportHeaderControls.length > 0) {
|
|
7074
|
+
markdownSections.push(
|
|
7075
|
+
renderReportHeaderBand(reportHeaderControls, registry, contentWidth)
|
|
7076
|
+
);
|
|
7077
|
+
}
|
|
7078
|
+
if (pageHeaderControls.length > 0 || detailControls.length > 0) {
|
|
7079
|
+
markdownSections.push(
|
|
7080
|
+
renderOperationsSection(
|
|
7081
|
+
pageHeaderControls,
|
|
7082
|
+
detailControls,
|
|
7083
|
+
groupFooterControls,
|
|
7084
|
+
registry)
|
|
7085
|
+
);
|
|
7086
|
+
}
|
|
7087
|
+
if (reportFooterControls.length > 0) {
|
|
7088
|
+
markdownSections.push(
|
|
7089
|
+
renderReportFooterBand(reportFooterControls, registry)
|
|
7090
|
+
);
|
|
7091
|
+
}
|
|
7092
|
+
const markdown = markdownSections.join("\n").replace(/\n{4,}/g, "\n\n\n");
|
|
7093
|
+
return {
|
|
7094
|
+
markdown,
|
|
7095
|
+
variables: registry.getAll(),
|
|
7096
|
+
reportName,
|
|
7097
|
+
warnings
|
|
7098
|
+
};
|
|
7099
|
+
}
|
|
7100
|
+
|
|
7101
|
+
export { DocumentGenerator, EditorPanel, FormFieldType, PreviewPanel, RepeatNode, SubtotalsNode, ThemeProvider, expandRepeatContent, extractVariablesFromContent, generatePdfFromMarkdown, generatePdfFromTiptap, isZeroLike, markdownToTiptap, parseXmlTemplate, suppressZeroContent, tiptapToMarkdown, useDocumentGenerator, useTheme };
|
|
4929
7102
|
//# sourceMappingURL=index.mjs.map
|
|
4930
7103
|
//# sourceMappingURL=index.mjs.map
|