@signiphi/pdf-compose 0.1.0-beta.2 → 0.1.0-beta.4
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 +2661 -469
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2649 -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,263 @@ 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 fields = extractFieldsFromContent(replaced);
|
|
2557
|
+
const result = await generatePdfFromContent(replaced, {
|
|
2558
|
+
embedFormFields: fields.length > 0,
|
|
2559
|
+
fields
|
|
2560
|
+
});
|
|
2561
|
+
return { pdfBytes: result.pdfBytes };
|
|
2562
|
+
}
|
|
2563
|
+
async function generatePdfFromMarkdown(markdown, values) {
|
|
2564
|
+
const content = markdownToTiptap(markdown);
|
|
2565
|
+
return generatePdfFromTiptap(content, values);
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2568
|
+
// src/utils/markdown-writer.ts
|
|
2569
|
+
function serializeFieldToken(attrs) {
|
|
2570
|
+
const parts = ["field"];
|
|
2571
|
+
if (attrs.fieldType) parts.push(`type:${attrs.fieldType}`);
|
|
2572
|
+
if (attrs.fieldName) parts.push(`name:${attrs.fieldName}`);
|
|
2573
|
+
if (attrs.fieldLabel) parts.push(`label:${attrs.fieldLabel}`);
|
|
2574
|
+
if (attrs.fieldId) parts.push(`id:${attrs.fieldId}`);
|
|
2575
|
+
if (attrs.required) parts.push(`required:true`);
|
|
2576
|
+
if (attrs.options) parts.push(`options:${attrs.options}`);
|
|
2577
|
+
if (attrs.fontSize) parts.push(`fontSize:${attrs.fontSize}`);
|
|
2578
|
+
if (attrs.placeholder) parts.push(`placeholder:${attrs.placeholder}`);
|
|
2579
|
+
if (attrs.defaultValue) parts.push(`defaultValue:${attrs.defaultValue}`);
|
|
2580
|
+
if (attrs.multiline) parts.push(`multiline:true`);
|
|
2581
|
+
if (attrs.maxLength) parts.push(`maxLength:${attrs.maxLength}`);
|
|
2582
|
+
if (attrs.acknowledgements) parts.push(`acks:${btoa(attrs.acknowledgements)}`);
|
|
2583
|
+
return `{{${parts.join("|")}}}`;
|
|
2584
|
+
}
|
|
2585
|
+
function serializeVariableToken(attrs) {
|
|
2586
|
+
const parts = ["var"];
|
|
2587
|
+
if (attrs.varName) parts.push(`name:${attrs.varName}`);
|
|
2588
|
+
if (attrs.varLabel) parts.push(`label:${attrs.varLabel}`);
|
|
2589
|
+
if (attrs.varDefault) parts.push(`default:${attrs.varDefault}`);
|
|
2590
|
+
if (attrs.suppressZero === "true") parts.push(`suppress:zero`);
|
|
2591
|
+
if (attrs.format) parts.push(`format:${attrs.format}`);
|
|
2592
|
+
return `{{${parts.join("|")}}}`;
|
|
2593
|
+
}
|
|
2594
|
+
function serializeInline(content) {
|
|
2595
|
+
if (!content) return "";
|
|
2596
|
+
let result = "";
|
|
2597
|
+
for (const node of content) {
|
|
2598
|
+
if (node.type === "fieldNode") {
|
|
2599
|
+
result += serializeFieldToken(node.attrs || {});
|
|
2600
|
+
continue;
|
|
2601
|
+
}
|
|
2602
|
+
if (node.type === "variableNode") {
|
|
2603
|
+
let token = serializeVariableToken(node.attrs || {});
|
|
2604
|
+
const varMarks = node.marks || [];
|
|
2605
|
+
const isBold = varMarks.some((m) => m.type === "bold");
|
|
2606
|
+
const isItalic = varMarks.some((m) => m.type === "italic");
|
|
2607
|
+
const isUnderline = varMarks.some((m) => m.type === "underline");
|
|
2608
|
+
if (isBold && isItalic) token = `***${token}***`;
|
|
2609
|
+
else if (isBold) token = `**${token}**`;
|
|
2610
|
+
else if (isItalic) token = `*${token}*`;
|
|
2611
|
+
if (isUnderline) token = `__${token}__`;
|
|
2612
|
+
result += token;
|
|
2613
|
+
continue;
|
|
2614
|
+
}
|
|
2615
|
+
if (node.type === "text") {
|
|
2616
|
+
let text = node.text || "";
|
|
2617
|
+
const marks = node.marks || [];
|
|
2618
|
+
for (const mark of marks) {
|
|
2619
|
+
if (mark.type === "bold") text = `**${text}**`;
|
|
2620
|
+
else if (mark.type === "italic") text = `*${text}*`;
|
|
2621
|
+
else if (mark.type === "underline") text = `__${text}__`;
|
|
2622
|
+
else if (mark.type === "code") text = `\`${text}\``;
|
|
2623
|
+
}
|
|
2624
|
+
result += text;
|
|
2625
|
+
}
|
|
2626
|
+
if (node.type === "hardBreak") {
|
|
2627
|
+
result += " \n";
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
return result;
|
|
2631
|
+
}
|
|
2632
|
+
function serializeBlock(node) {
|
|
2633
|
+
switch (node.type) {
|
|
2634
|
+
case "heading": {
|
|
2635
|
+
const level = node.attrs?.level || 1;
|
|
2636
|
+
const prefix = "#".repeat(level);
|
|
2637
|
+
return `${prefix} ${serializeInline(node.content)}`;
|
|
2638
|
+
}
|
|
2639
|
+
case "paragraph": {
|
|
2640
|
+
const inline = serializeInline(node.content);
|
|
2641
|
+
return inline;
|
|
2642
|
+
}
|
|
2643
|
+
case "bulletList": {
|
|
2644
|
+
return (node.content || []).map((item) => {
|
|
2645
|
+
const inner = (item.content || []).map(serializeBlock).join("\n");
|
|
2646
|
+
return `- ${inner}`;
|
|
2647
|
+
}).join("\n");
|
|
2648
|
+
}
|
|
2649
|
+
case "orderedList": {
|
|
2650
|
+
return (node.content || []).map((item, i) => {
|
|
2651
|
+
const inner = (item.content || []).map(serializeBlock).join("\n");
|
|
2652
|
+
return `${i + 1}. ${inner}`;
|
|
2653
|
+
}).join("\n");
|
|
2654
|
+
}
|
|
2655
|
+
case "listItem": {
|
|
2656
|
+
return (node.content || []).map(serializeBlock).join("\n");
|
|
2657
|
+
}
|
|
2658
|
+
case "blockquote": {
|
|
2659
|
+
return (node.content || []).map(serializeBlock).map((line) => `> ${line}`).join("\n");
|
|
2660
|
+
}
|
|
2661
|
+
case "codeBlock": {
|
|
2662
|
+
const text = serializeInline(node.content);
|
|
2663
|
+
return `\`\`\`
|
|
2664
|
+
${text}
|
|
2665
|
+
\`\`\``;
|
|
2666
|
+
}
|
|
2667
|
+
case "horizontalRule": {
|
|
2668
|
+
return "---";
|
|
2669
|
+
}
|
|
2670
|
+
case "table": {
|
|
2671
|
+
const rows = node.content || [];
|
|
2672
|
+
const isBorderless = node.attrs?.borderless === true;
|
|
2673
|
+
const isSubtotals = node.attrs?.subtotals === true;
|
|
2674
|
+
const serializedRows = [];
|
|
2675
|
+
if (isBorderless && rows.length > 0) {
|
|
2676
|
+
const colCount = rows[0].content?.length || 0;
|
|
2677
|
+
const markerCell = isSubtotals ? "_" : "";
|
|
2678
|
+
const markerRow = "| " + new Array(colCount).fill(markerCell).join(" | ") + " |";
|
|
2679
|
+
const sep = "|" + new Array(colCount).fill("---").join("|") + "|";
|
|
2680
|
+
serializedRows.push(markerRow);
|
|
2681
|
+
serializedRows.push(sep);
|
|
2682
|
+
}
|
|
2683
|
+
for (let ri = 0; ri < rows.length; ri++) {
|
|
2684
|
+
const row = rows[ri];
|
|
2685
|
+
const cells = (row.content || []).map((cell) => {
|
|
2686
|
+
const inner = (cell.content || []).map(serializeBlock).join("");
|
|
2687
|
+
return inner;
|
|
2688
|
+
});
|
|
2689
|
+
serializedRows.push(`| ${cells.join(" | ")} |`);
|
|
2690
|
+
if (!isBorderless && ri === 0 && row.content?.[0]?.type === "tableHeader") {
|
|
2691
|
+
const sep = cells.map(() => "------").join("|");
|
|
2692
|
+
serializedRows.push(`|${sep}|`);
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
return serializedRows.join("\n");
|
|
2696
|
+
}
|
|
2697
|
+
case "tableRow": {
|
|
2698
|
+
const cells = (node.content || []).map((cell) => {
|
|
2699
|
+
const inner = (cell.content || []).map(serializeBlock).join("");
|
|
2700
|
+
return inner;
|
|
2701
|
+
});
|
|
2702
|
+
return `| ${cells.join(" | ")} |`;
|
|
2703
|
+
}
|
|
2704
|
+
case "tableHeader":
|
|
2705
|
+
case "tableCell": {
|
|
2706
|
+
return (node.content || []).map(serializeBlock).join("");
|
|
2707
|
+
}
|
|
2708
|
+
case "watermark": {
|
|
2709
|
+
const attrs = node.attrs || {};
|
|
2710
|
+
const parts = [];
|
|
2711
|
+
if (attrs.text) parts.push(`text:${attrs.text}`);
|
|
2712
|
+
if (attrs.opacity && attrs.opacity !== "0.15") parts.push(`opacity:${attrs.opacity}`);
|
|
2713
|
+
if (attrs.angle && attrs.angle !== "-45") parts.push(`angle:${attrs.angle}`);
|
|
2714
|
+
return `:::watermark{${parts.join("|")}}`;
|
|
2715
|
+
}
|
|
2716
|
+
case "panel": {
|
|
2717
|
+
const attrs = node.attrs || {};
|
|
2718
|
+
const parts = [];
|
|
2719
|
+
if (attrs.title) parts.push(`title:${attrs.title}`);
|
|
2720
|
+
if (attrs.border && attrs.border !== "solid") parts.push(`border:${attrs.border}`);
|
|
2721
|
+
else if (attrs.border === "solid") parts.push(`border:solid`);
|
|
2722
|
+
if (attrs.headerStyle) parts.push(`headerStyle:${attrs.headerStyle}`);
|
|
2723
|
+
const attrStr = parts.length > 0 ? `{${parts.join("|")}}` : "";
|
|
2724
|
+
const children = (node.content || []).map(serializeBlock).join("\n\n");
|
|
2725
|
+
return `:::panel${attrStr}
|
|
2726
|
+
${children}
|
|
2727
|
+
:::`;
|
|
2728
|
+
}
|
|
2729
|
+
case "repeatBlock": {
|
|
2730
|
+
const attrs = node.attrs || {};
|
|
2731
|
+
const parts = [];
|
|
2732
|
+
if (attrs.data) parts.push(`data:${attrs.data}`);
|
|
2733
|
+
const attrStr = parts.length > 0 ? `{${parts.join("|")}}` : "";
|
|
2734
|
+
const children = (node.content || []).map(serializeBlock).join("\n\n");
|
|
2735
|
+
return `:::repeat${attrStr}
|
|
2736
|
+
${children}
|
|
2737
|
+
:::`;
|
|
2738
|
+
}
|
|
2739
|
+
case "subtotalsBlock": {
|
|
2740
|
+
const children = (node.content || []).map(serializeBlock).join("\n");
|
|
2741
|
+
return `:::subtotals
|
|
2742
|
+
${children}
|
|
2743
|
+
:::`;
|
|
2744
|
+
}
|
|
2745
|
+
case "columns": {
|
|
2746
|
+
const attrs = node.attrs || {};
|
|
2747
|
+
const splitVal = attrs.split || "50";
|
|
2748
|
+
const cols = (node.content || []).filter((c) => c.type === "column");
|
|
2749
|
+
const colBlocks = cols.map((col) => {
|
|
2750
|
+
const children = (col.content || []).map(serializeBlock).join("\n\n");
|
|
2751
|
+
const colAttrs = col.attrs || {};
|
|
2752
|
+
const padTop = parseFloat(colAttrs.padTop || "0") || 0;
|
|
2753
|
+
const colTag = padTop ? `:::col{padTop:${padTop}}` : ":::col";
|
|
2754
|
+
return `${colTag}
|
|
2755
|
+
${children}
|
|
2756
|
+
:::`;
|
|
2757
|
+
});
|
|
2758
|
+
const padX = parseFloat(attrs.padX || "0") || 0;
|
|
2759
|
+
const colsAttrs = padX ? `split:${splitVal}|padX:${padX}` : `split:${splitVal}`;
|
|
2760
|
+
return `:::columns{${colsAttrs}}
|
|
2761
|
+
${colBlocks.join("\n")}
|
|
2762
|
+
:::`;
|
|
2763
|
+
}
|
|
2764
|
+
case "column": {
|
|
2765
|
+
const children = (node.content || []).map(serializeBlock).join("\n\n");
|
|
2766
|
+
const colAttrs = node.attrs || {};
|
|
2767
|
+
const padTop = parseFloat(colAttrs.padTop || "0") || 0;
|
|
2768
|
+
const colTag = padTop ? `:::col{padTop:${padTop}}` : ":::col";
|
|
2769
|
+
return `${colTag}
|
|
2770
|
+
${children}
|
|
2771
|
+
:::`;
|
|
2772
|
+
}
|
|
2773
|
+
default:
|
|
2774
|
+
return serializeInline(node.content);
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
function tiptapToMarkdown(doc) {
|
|
2778
|
+
if (!doc.content) return "";
|
|
2779
|
+
return doc.content.map(serializeBlock).join("\n\n");
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
// src/utils/pdf-preview.ts
|
|
2783
|
+
async function pdfToImages(pdfBytes, scale = 2) {
|
|
2784
|
+
let pdfjsLib;
|
|
2785
|
+
try {
|
|
2786
|
+
pdfjsLib = await import('pdfjs-dist');
|
|
2787
|
+
} catch (err) {
|
|
2788
|
+
throw new Error(formatError(
|
|
2789
|
+
"Failed to load the PDF preview library \u2014 ensure pdfjs-dist is installed and the worker is accessible",
|
|
2790
|
+
err
|
|
2791
|
+
));
|
|
2792
|
+
}
|
|
2793
|
+
if (!pdfjsLib.GlobalWorkerOptions.workerSrc) {
|
|
2794
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc = "/pdfjs/build/pdf.worker.mjs";
|
|
2795
|
+
}
|
|
2796
|
+
let pdf;
|
|
2797
|
+
try {
|
|
2798
|
+
const loadingTask = pdfjsLib.getDocument({ data: pdfBytes });
|
|
2799
|
+
pdf = await loadingTask.promise;
|
|
2800
|
+
} catch (err) {
|
|
2801
|
+
throw new Error(formatError("Failed to parse the generated PDF for preview", err));
|
|
2802
|
+
}
|
|
2803
|
+
const pages = [];
|
|
2804
|
+
const pageErrors = [];
|
|
2805
|
+
for (let i = 1; i <= pdf.numPages; i++) {
|
|
2806
|
+
try {
|
|
2807
|
+
const page = await pdf.getPage(i);
|
|
1569
2808
|
const viewport = page.getViewport({ scale });
|
|
1570
2809
|
const canvas = document.createElement("canvas");
|
|
1571
2810
|
canvas.width = viewport.width;
|
|
@@ -1626,8 +2865,26 @@ function useDocumentGenerator(options = {}) {
|
|
|
1626
2865
|
Placeholder.configure({
|
|
1627
2866
|
placeholder: options.placeholder || "Start writing your document..."
|
|
1628
2867
|
}),
|
|
2868
|
+
Table.extend({
|
|
2869
|
+
addAttributes() {
|
|
2870
|
+
return {
|
|
2871
|
+
...this.parent?.(),
|
|
2872
|
+
borderless: { default: false },
|
|
2873
|
+
subtotals: { default: false }
|
|
2874
|
+
};
|
|
2875
|
+
}
|
|
2876
|
+
}).configure({ resizable: false }),
|
|
2877
|
+
TableRow,
|
|
2878
|
+
TableCell,
|
|
2879
|
+
TableHeader,
|
|
1629
2880
|
FieldNode,
|
|
1630
|
-
VariableNode
|
|
2881
|
+
VariableNode,
|
|
2882
|
+
PanelNode,
|
|
2883
|
+
ColumnsNode,
|
|
2884
|
+
ColumnNode,
|
|
2885
|
+
WatermarkNode,
|
|
2886
|
+
RepeatNode,
|
|
2887
|
+
SubtotalsNode
|
|
1631
2888
|
],
|
|
1632
2889
|
content: initialContent || { type: "doc", content: [{ type: "paragraph" }] },
|
|
1633
2890
|
onUpdate: ({ editor: ed }) => {
|
|
@@ -1731,10 +2988,9 @@ function useDocumentGenerator(options = {}) {
|
|
|
1731
2988
|
async (values) => {
|
|
1732
2989
|
if (!editor) throw new Error("Editor is not initialized yet \u2014 wait for the editor to load before generating");
|
|
1733
2990
|
const content = editor.getJSON();
|
|
1734
|
-
const
|
|
1735
|
-
const
|
|
1736
|
-
|
|
1737
|
-
return { pdfBytes: result.pdfBytes, pdfPages: pages };
|
|
2991
|
+
const { pdfBytes: pdfBytes2 } = await generatePdfFromTiptap(content, values);
|
|
2992
|
+
const pdfPages2 = await pdfToImages(pdfBytes2);
|
|
2993
|
+
return { pdfBytes: pdfBytes2, pdfPages: pdfPages2 };
|
|
1738
2994
|
},
|
|
1739
2995
|
[editor]
|
|
1740
2996
|
);
|
|
@@ -2888,15 +4144,47 @@ function FieldEditPopover({
|
|
|
2888
4144
|
] }) })
|
|
2889
4145
|
] });
|
|
2890
4146
|
}
|
|
2891
|
-
|
|
4147
|
+
var sizeClasses = {
|
|
4148
|
+
md: "px-3 py-1.5 text-xs font-medium",
|
|
4149
|
+
sm: "px-2 py-0.5 text-[10px] font-medium"
|
|
4150
|
+
};
|
|
4151
|
+
function ToggleGroup({
|
|
4152
|
+
value,
|
|
4153
|
+
onChange,
|
|
4154
|
+
options,
|
|
4155
|
+
size = "md",
|
|
4156
|
+
className
|
|
4157
|
+
}) {
|
|
4158
|
+
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(
|
|
4159
|
+
"button",
|
|
4160
|
+
{
|
|
4161
|
+
className: cn(
|
|
4162
|
+
"rounded-md transition-colors",
|
|
4163
|
+
sizeClasses[size],
|
|
4164
|
+
value === opt.value ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
|
|
4165
|
+
),
|
|
4166
|
+
onClick: () => onChange(opt.value),
|
|
4167
|
+
children: opt.label
|
|
4168
|
+
},
|
|
4169
|
+
opt.value
|
|
4170
|
+
)) });
|
|
4171
|
+
}
|
|
4172
|
+
function labelToVarName2(label) {
|
|
2892
4173
|
return label.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
2893
4174
|
}
|
|
4175
|
+
function varNameToLabel(name) {
|
|
4176
|
+
return name.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
4177
|
+
}
|
|
2894
4178
|
function VariableInsertPopover({
|
|
2895
4179
|
open,
|
|
2896
4180
|
onOpenChange,
|
|
2897
4181
|
onInsert,
|
|
4182
|
+
predefinedVariables,
|
|
2898
4183
|
children
|
|
2899
4184
|
}) {
|
|
4185
|
+
const hasPredefined = !!predefinedVariables?.length;
|
|
4186
|
+
const [mode, setMode] = useState(hasPredefined ? "existing" : "new");
|
|
4187
|
+
const [search, setSearch] = useState("");
|
|
2900
4188
|
const [varLabel, setVarLabel] = useState("");
|
|
2901
4189
|
const [varName, setVarName] = useState("");
|
|
2902
4190
|
const [varDefault, setVarDefault] = useState("");
|
|
@@ -2906,7 +4194,9 @@ function VariableInsertPopover({
|
|
|
2906
4194
|
setVarName("");
|
|
2907
4195
|
setVarDefault("");
|
|
2908
4196
|
setNameManuallyEdited(false);
|
|
2909
|
-
|
|
4197
|
+
setSearch("");
|
|
4198
|
+
setMode(hasPredefined ? "existing" : "new");
|
|
4199
|
+
}, [hasPredefined]);
|
|
2910
4200
|
const handleOpenChange = useCallback(
|
|
2911
4201
|
(nextOpen) => {
|
|
2912
4202
|
if (!nextOpen) reset();
|
|
@@ -2916,7 +4206,7 @@ function VariableInsertPopover({
|
|
|
2916
4206
|
);
|
|
2917
4207
|
useEffect(() => {
|
|
2918
4208
|
if (!nameManuallyEdited) {
|
|
2919
|
-
setVarName(
|
|
4209
|
+
setVarName(labelToVarName2(varLabel));
|
|
2920
4210
|
}
|
|
2921
4211
|
}, [varLabel, nameManuallyEdited]);
|
|
2922
4212
|
const handleInsert = useCallback(() => {
|
|
@@ -2928,6 +4218,26 @@ function VariableInsertPopover({
|
|
|
2928
4218
|
});
|
|
2929
4219
|
handleOpenChange(false);
|
|
2930
4220
|
}, [varLabel, varName, varDefault, onInsert, handleOpenChange]);
|
|
4221
|
+
const handlePickPredefined = useCallback(
|
|
4222
|
+
(pv) => {
|
|
4223
|
+
onInsert({
|
|
4224
|
+
varName: pv.varName,
|
|
4225
|
+
varLabel: pv.varLabel || varNameToLabel(pv.varName),
|
|
4226
|
+
varDefault: ""
|
|
4227
|
+
});
|
|
4228
|
+
handleOpenChange(false);
|
|
4229
|
+
},
|
|
4230
|
+
[onInsert, handleOpenChange]
|
|
4231
|
+
);
|
|
4232
|
+
const filteredVars = useMemo(() => {
|
|
4233
|
+
if (!predefinedVariables?.length) return [];
|
|
4234
|
+
const q = search.toLowerCase();
|
|
4235
|
+
if (!q) return predefinedVariables;
|
|
4236
|
+
return predefinedVariables.filter((pv) => {
|
|
4237
|
+
const label = (pv.varLabel || varNameToLabel(pv.varName)).toLowerCase();
|
|
4238
|
+
return pv.varName.toLowerCase().includes(q) || label.includes(q);
|
|
4239
|
+
});
|
|
4240
|
+
}, [predefinedVariables, search]);
|
|
2931
4241
|
return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: handleOpenChange, children: [
|
|
2932
4242
|
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children }),
|
|
2933
4243
|
/* @__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 +4245,114 @@ function VariableInsertPopover({
|
|
|
2935
4245
|
/* @__PURE__ */ jsx(Braces, { size: 14 }),
|
|
2936
4246
|
/* @__PURE__ */ jsx("span", { children: "Insert Variable" })
|
|
2937
4247
|
] }),
|
|
2938
|
-
/* @__PURE__ */
|
|
2939
|
-
|
|
2940
|
-
|
|
4248
|
+
hasPredefined && /* @__PURE__ */ jsx(
|
|
4249
|
+
ToggleGroup,
|
|
4250
|
+
{
|
|
4251
|
+
value: mode,
|
|
4252
|
+
onChange: setMode,
|
|
4253
|
+
options: [
|
|
4254
|
+
{ value: "existing", label: "Existing" },
|
|
4255
|
+
{ value: "new", label: "New" }
|
|
4256
|
+
],
|
|
4257
|
+
size: "sm"
|
|
4258
|
+
}
|
|
4259
|
+
),
|
|
4260
|
+
mode === "existing" && hasPredefined ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
4261
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
4262
|
+
/* @__PURE__ */ jsx(Search, { size: 14, className: "absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground" }),
|
|
2941
4263
|
/* @__PURE__ */ jsx(
|
|
2942
4264
|
Input,
|
|
2943
4265
|
{
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
className: "h-8 text-xs"
|
|
4266
|
+
value: search,
|
|
4267
|
+
onChange: (e) => setSearch(e.target.value),
|
|
4268
|
+
placeholder: "Search variables...",
|
|
4269
|
+
className: "h-8 text-xs pl-7"
|
|
2949
4270
|
}
|
|
2950
4271
|
)
|
|
2951
4272
|
] }),
|
|
2952
|
-
/* @__PURE__ */
|
|
2953
|
-
|
|
4273
|
+
/* @__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) => {
|
|
4274
|
+
const label = pv.varLabel || varNameToLabel(pv.varName);
|
|
4275
|
+
return /* @__PURE__ */ jsxs(
|
|
4276
|
+
"button",
|
|
4277
|
+
{
|
|
4278
|
+
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",
|
|
4279
|
+
onClick: () => handlePickPredefined(pv),
|
|
4280
|
+
children: [
|
|
4281
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: label }),
|
|
4282
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground font-mono", children: pv.varName })
|
|
4283
|
+
]
|
|
4284
|
+
},
|
|
4285
|
+
pv.varName
|
|
4286
|
+
);
|
|
4287
|
+
}) })
|
|
4288
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4289
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
4290
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
4291
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "var-label", className: "text-xs", children: "Label" }),
|
|
4292
|
+
/* @__PURE__ */ jsx(
|
|
4293
|
+
Input,
|
|
4294
|
+
{
|
|
4295
|
+
id: "var-label",
|
|
4296
|
+
value: varLabel,
|
|
4297
|
+
onChange: (e) => setVarLabel(e.target.value),
|
|
4298
|
+
placeholder: "e.g. Company Name",
|
|
4299
|
+
className: "h-8 text-xs"
|
|
4300
|
+
}
|
|
4301
|
+
)
|
|
4302
|
+
] }),
|
|
4303
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
4304
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "var-name", className: "text-xs", children: "Name (identifier)" }),
|
|
4305
|
+
/* @__PURE__ */ jsx(
|
|
4306
|
+
Input,
|
|
4307
|
+
{
|
|
4308
|
+
id: "var-name",
|
|
4309
|
+
value: varName,
|
|
4310
|
+
onChange: (e) => {
|
|
4311
|
+
setVarName(e.target.value);
|
|
4312
|
+
setNameManuallyEdited(true);
|
|
4313
|
+
},
|
|
4314
|
+
placeholder: "e.g. company_name",
|
|
4315
|
+
className: "h-8 text-xs font-mono"
|
|
4316
|
+
}
|
|
4317
|
+
),
|
|
4318
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground mt-0.5", children: "Used as key in data objects" })
|
|
4319
|
+
] }),
|
|
4320
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
4321
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "var-default", className: "text-xs", children: "Default Value" }),
|
|
4322
|
+
/* @__PURE__ */ jsx(
|
|
4323
|
+
Input,
|
|
4324
|
+
{
|
|
4325
|
+
id: "var-default",
|
|
4326
|
+
value: varDefault,
|
|
4327
|
+
onChange: (e) => setVarDefault(e.target.value),
|
|
4328
|
+
placeholder: "Optional",
|
|
4329
|
+
className: "h-8 text-xs"
|
|
4330
|
+
}
|
|
4331
|
+
)
|
|
4332
|
+
] })
|
|
4333
|
+
] }),
|
|
4334
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2 pt-1", children: [
|
|
2954
4335
|
/* @__PURE__ */ jsx(
|
|
2955
|
-
|
|
4336
|
+
Button,
|
|
2956
4337
|
{
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
},
|
|
2963
|
-
placeholder: "e.g. company_name",
|
|
2964
|
-
className: "h-8 text-xs font-mono"
|
|
4338
|
+
size: "sm",
|
|
4339
|
+
className: "h-8 flex-1 text-xs",
|
|
4340
|
+
onClick: handleInsert,
|
|
4341
|
+
disabled: !varLabel.trim() || !varName.trim(),
|
|
4342
|
+
children: "Insert"
|
|
2965
4343
|
}
|
|
2966
4344
|
),
|
|
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
4345
|
/* @__PURE__ */ jsx(
|
|
2972
|
-
|
|
4346
|
+
Button,
|
|
2973
4347
|
{
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
4348
|
+
variant: "outline",
|
|
4349
|
+
size: "sm",
|
|
4350
|
+
className: "h-8 text-xs",
|
|
4351
|
+
onClick: () => handleOpenChange(false),
|
|
4352
|
+
children: "Cancel"
|
|
2979
4353
|
}
|
|
2980
4354
|
)
|
|
2981
4355
|
] })
|
|
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
4356
|
] })
|
|
3005
4357
|
] }) })
|
|
3006
4358
|
] });
|
|
@@ -3222,7 +4574,6 @@ function PreviewPanel({
|
|
|
3222
4574
|
(f) => f.position.page === currentPageIndex + 1 && f.position.width > 0
|
|
3223
4575
|
);
|
|
3224
4576
|
}, [positionedFields, currentPage, currentPageIndex]);
|
|
3225
|
-
const showScrollbars = zoomLevel > 1;
|
|
3226
4577
|
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col h-full border border-border rounded-lg overflow-hidden bg-muted/20", className), children: [
|
|
3227
4578
|
/* @__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
4579
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
@@ -3338,12 +4689,13 @@ function PreviewPanel({
|
|
|
3338
4689
|
"div",
|
|
3339
4690
|
{
|
|
3340
4691
|
className: cn(
|
|
3341
|
-
"border border-border rounded-lg bg-muted/30 shrink-0"
|
|
3342
|
-
showScrollbars ? "overflow-auto" : "overflow-hidden"
|
|
4692
|
+
"border border-border rounded-lg bg-muted/30 shrink-0 overflow-auto scrollbar-hidden"
|
|
3343
4693
|
),
|
|
3344
4694
|
style: {
|
|
3345
4695
|
width: pageDisplaySize.viewportWidth,
|
|
3346
|
-
height: pageDisplaySize.viewportHeight
|
|
4696
|
+
height: pageDisplaySize.viewportHeight,
|
|
4697
|
+
maxWidth: "100%",
|
|
4698
|
+
maxHeight: "100%"
|
|
3347
4699
|
},
|
|
3348
4700
|
children: /* @__PURE__ */ jsxs(
|
|
3349
4701
|
"div",
|
|
@@ -3435,6 +4787,37 @@ function validateMarkdown(markdown) {
|
|
|
3435
4787
|
searchFrom = closeIdx + 2;
|
|
3436
4788
|
}
|
|
3437
4789
|
}
|
|
4790
|
+
const DIRECTIVE_OPEN_RE = /^:::(panel|columns|col|watermark)(?:\{[^}]*\})?$/;
|
|
4791
|
+
const DIRECTIVE_CLOSE_RE = /^:::$/;
|
|
4792
|
+
const directiveStack = [];
|
|
4793
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4794
|
+
const trimmed = lines[i].trim();
|
|
4795
|
+
const lineNum = i + 1;
|
|
4796
|
+
const openMatch = trimmed.match(DIRECTIVE_OPEN_RE);
|
|
4797
|
+
if (openMatch) {
|
|
4798
|
+
const dtype = openMatch[1];
|
|
4799
|
+
if (dtype !== "watermark") {
|
|
4800
|
+
if (dtype === "col") {
|
|
4801
|
+
const parent = directiveStack[directiveStack.length - 1];
|
|
4802
|
+
if (!parent || parent.type !== "columns") {
|
|
4803
|
+
warnings.push(`:::col at line ${lineNum} appears outside :::columns`);
|
|
4804
|
+
}
|
|
4805
|
+
}
|
|
4806
|
+
directiveStack.push({ type: dtype, line: lineNum });
|
|
4807
|
+
}
|
|
4808
|
+
continue;
|
|
4809
|
+
}
|
|
4810
|
+
if (DIRECTIVE_CLOSE_RE.test(trimmed)) {
|
|
4811
|
+
if (directiveStack.length === 0) {
|
|
4812
|
+
warnings.push(`Stray ::: close at line ${lineNum} with no matching open directive`);
|
|
4813
|
+
} else {
|
|
4814
|
+
directiveStack.pop();
|
|
4815
|
+
}
|
|
4816
|
+
}
|
|
4817
|
+
}
|
|
4818
|
+
for (const open of directiveStack) {
|
|
4819
|
+
errors.push(`Unclosed :::${open.type} directive opened at line ${open.line}`);
|
|
4820
|
+
}
|
|
3438
4821
|
let fields = [];
|
|
3439
4822
|
let variables = [];
|
|
3440
4823
|
try {
|
|
@@ -3468,31 +4851,6 @@ function validateMarkdown(markdown) {
|
|
|
3468
4851
|
fields
|
|
3469
4852
|
};
|
|
3470
4853
|
}
|
|
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
4854
|
function parseCsv(text) {
|
|
3497
4855
|
const lines = text.split(/\r?\n/).filter((l) => l.trim() !== "");
|
|
3498
4856
|
if (lines.length < 2) return [];
|
|
@@ -3542,7 +4900,10 @@ function GeneratePanel({
|
|
|
3542
4900
|
editorContent,
|
|
3543
4901
|
editorVariables,
|
|
3544
4902
|
onGeneratePdf,
|
|
3545
|
-
initialBulkData
|
|
4903
|
+
initialBulkData,
|
|
4904
|
+
initialVariableValues,
|
|
4905
|
+
exportFileName,
|
|
4906
|
+
templateMode
|
|
3546
4907
|
}) {
|
|
3547
4908
|
const [templateSource, setTemplateSource] = useState("editor");
|
|
3548
4909
|
const [importedMarkdown, setImportedMarkdown] = useState(null);
|
|
@@ -3550,6 +4911,7 @@ function GeneratePanel({
|
|
|
3550
4911
|
const [validationResult, setValidationResult] = useState(null);
|
|
3551
4912
|
const [mode, setMode] = useState("single");
|
|
3552
4913
|
const [variableValues, setVariableValues] = useState({});
|
|
4914
|
+
const [variableSearch, setVariableSearch] = useState("");
|
|
3553
4915
|
const [bulkInputFormat, setBulkInputFormat] = useState("json");
|
|
3554
4916
|
const [bulkInput, setBulkInput] = useState("");
|
|
3555
4917
|
const [bulkData, setBulkData] = useState(null);
|
|
@@ -3576,11 +4938,22 @@ function GeneratePanel({
|
|
|
3576
4938
|
const col = pos - textBefore.lastIndexOf("\n");
|
|
3577
4939
|
setJsonCursor({ line, col });
|
|
3578
4940
|
}, []);
|
|
4941
|
+
const lockedVarNames = React12__default.useMemo(
|
|
4942
|
+
() => templateMode && initialVariableValues ? new Set(Object.keys(initialVariableValues)) : /* @__PURE__ */ new Set(),
|
|
4943
|
+
[templateMode, initialVariableValues]
|
|
4944
|
+
);
|
|
3579
4945
|
const activeVariables = templateSource === "imported" && validationResult?.valid ? validationResult.variables : editorVariables;
|
|
3580
4946
|
const hasVariables = activeVariables.length > 0;
|
|
3581
4947
|
const hasEditorContent = editorMarkdown.trim().length > 0;
|
|
3582
4948
|
const hasImportedContent = templateSource === "imported" && importedMarkdown != null;
|
|
3583
4949
|
const hasContent = hasImportedContent || hasEditorContent;
|
|
4950
|
+
const filteredVariables = React12__default.useMemo(() => {
|
|
4951
|
+
if (!variableSearch.trim()) return activeVariables;
|
|
4952
|
+
const q = variableSearch.toLowerCase();
|
|
4953
|
+
return activeVariables.filter(
|
|
4954
|
+
(v) => v.varName.toLowerCase().includes(q) || (v.varLabel || "").toLowerCase().includes(q)
|
|
4955
|
+
);
|
|
4956
|
+
}, [activeVariables, variableSearch]);
|
|
3584
4957
|
const getActiveContent = useCallback(() => {
|
|
3585
4958
|
if (templateSource === "imported" && importedMarkdown) {
|
|
3586
4959
|
return markdownToTiptap(importedMarkdown);
|
|
@@ -3748,7 +5121,9 @@ function GeneratePanel({
|
|
|
3748
5121
|
setPreviewError(null);
|
|
3749
5122
|
try {
|
|
3750
5123
|
const content = getActiveContent();
|
|
3751
|
-
const
|
|
5124
|
+
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, variableValues);
|
|
5125
|
+
const suppressed = suppressZeroContent(expanded, enrichedValues);
|
|
5126
|
+
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
3752
5127
|
const fields = extractFieldsFromContent(replaced);
|
|
3753
5128
|
const result = await generatePdfFromContent(replaced);
|
|
3754
5129
|
const pages = await pdfToImages(result.pdfBytes);
|
|
@@ -3769,7 +5144,9 @@ function GeneratePanel({
|
|
|
3769
5144
|
setExportSuccess(null);
|
|
3770
5145
|
try {
|
|
3771
5146
|
const content = getActiveContent();
|
|
3772
|
-
const
|
|
5147
|
+
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, variableValues);
|
|
5148
|
+
const suppressed = suppressZeroContent(expanded, enrichedValues);
|
|
5149
|
+
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
3773
5150
|
const fields = extractFieldsFromContent(replaced);
|
|
3774
5151
|
const result = await generatePdfFromContent(replaced, {
|
|
3775
5152
|
drawFieldPlaceholders: false,
|
|
@@ -3777,7 +5154,7 @@ function GeneratePanel({
|
|
|
3777
5154
|
fields
|
|
3778
5155
|
});
|
|
3779
5156
|
const blob = new Blob([result.pdfBytes], { type: "application/pdf" });
|
|
3780
|
-
const fileName = importedFileName ? importedFileName.replace(".md", ".pdf") : "document.pdf";
|
|
5157
|
+
const fileName = exportFileName ? exportFileName.endsWith(".pdf") ? exportFileName : `${exportFileName}.pdf` : importedFileName ? importedFileName.replace(".md", ".pdf") : "document.pdf";
|
|
3781
5158
|
if (onGeneratePdf) {
|
|
3782
5159
|
onGeneratePdf(blob, fileName);
|
|
3783
5160
|
} else {
|
|
@@ -3849,7 +5226,9 @@ function GeneratePanel({
|
|
|
3849
5226
|
const allWarnings = [];
|
|
3850
5227
|
for (let i = 0; i < bulkData.length; i++) {
|
|
3851
5228
|
const values = bulkData[i];
|
|
3852
|
-
const
|
|
5229
|
+
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, values);
|
|
5230
|
+
const suppressed = suppressZeroContent(expanded, enrichedValues);
|
|
5231
|
+
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
3853
5232
|
const fields = extractFieldsFromContent(replaced);
|
|
3854
5233
|
const result = await generatePdfFromContent(replaced, {
|
|
3855
5234
|
drawFieldPlaceholders: false,
|
|
@@ -3904,13 +5283,15 @@ ${allWarnings.join("\n")}`);
|
|
|
3904
5283
|
}, [mode, bulkInputFormat, bulkInput, initialBulkJson]);
|
|
3905
5284
|
React12__default.useEffect(() => {
|
|
3906
5285
|
if (templateSource === "editor") {
|
|
3907
|
-
const defaults = {};
|
|
5286
|
+
const defaults = initialVariableValues ? { ...initialVariableValues } : {};
|
|
3908
5287
|
for (const v of editorVariables) {
|
|
3909
|
-
defaults[v.varName]
|
|
5288
|
+
if (!defaults[v.varName]) {
|
|
5289
|
+
defaults[v.varName] = variableValues[v.varName] || v.varDefault || "";
|
|
5290
|
+
}
|
|
3910
5291
|
}
|
|
3911
5292
|
setVariableValues(defaults);
|
|
3912
5293
|
}
|
|
3913
|
-
}, [editorVariables, templateSource]);
|
|
5294
|
+
}, [editorVariables, templateSource, initialVariableValues]);
|
|
3914
5295
|
useEffect(() => {
|
|
3915
5296
|
setPreviewFresh(false);
|
|
3916
5297
|
}, [variableValues, templateSource, importedMarkdown, editorMarkdown, editorContent]);
|
|
@@ -3922,8 +5303,8 @@ ${allWarnings.join("\n")}`);
|
|
|
3922
5303
|
}, [exportSuccess]);
|
|
3923
5304
|
return /* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0 grid grid-cols-[1fr_1px_1fr]", children: [
|
|
3924
5305
|
/* @__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: [
|
|
5306
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto p-4 space-y-4 scrollbar-hidden", children: [
|
|
5307
|
+
!templateMode && /* @__PURE__ */ jsxs("div", { children: [
|
|
3927
5308
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
|
|
3928
5309
|
/* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: "Template" }),
|
|
3929
5310
|
templateSource === "imported" && /* @__PURE__ */ jsxs(
|
|
@@ -4016,10 +5397,10 @@ ${allWarnings.join("\n")}`);
|
|
|
4016
5397
|
!hasContent && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-8 text-muted-foreground", children: [
|
|
4017
5398
|
/* @__PURE__ */ jsx(Braces, { size: 32, className: "mb-2 opacity-50" }),
|
|
4018
5399
|
/* @__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." })
|
|
5400
|
+
/* @__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
5401
|
] }),
|
|
4021
5402
|
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(
|
|
5403
|
+
!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
5404
|
ToggleGroup,
|
|
4024
5405
|
{
|
|
4025
5406
|
value: mode,
|
|
@@ -4031,20 +5412,53 @@ ${allWarnings.join("\n")}`);
|
|
|
4031
5412
|
}
|
|
4032
5413
|
) }),
|
|
4033
5414
|
mode === "single" && /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-3", children: [
|
|
4034
|
-
/* @__PURE__ */
|
|
4035
|
-
|
|
4036
|
-
/* @__PURE__ */ jsx(
|
|
5415
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
5416
|
+
/* @__PURE__ */ jsx("h3", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: "Variables" }),
|
|
5417
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: activeVariables.length })
|
|
5418
|
+
] }),
|
|
5419
|
+
activeVariables.length > 5 && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
5420
|
+
/* @__PURE__ */ jsx(Search, { size: 14, className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground" }),
|
|
4037
5421
|
/* @__PURE__ */ jsx(
|
|
4038
5422
|
Input,
|
|
4039
5423
|
{
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
5424
|
+
value: variableSearch,
|
|
5425
|
+
onChange: (e) => setVariableSearch(e.target.value),
|
|
5426
|
+
placeholder: "Search variables...",
|
|
5427
|
+
className: "h-8 text-xs pl-8"
|
|
5428
|
+
}
|
|
5429
|
+
),
|
|
5430
|
+
variableSearch && /* @__PURE__ */ jsx(
|
|
5431
|
+
"button",
|
|
5432
|
+
{
|
|
5433
|
+
type: "button",
|
|
5434
|
+
onClick: () => setVariableSearch(""),
|
|
5435
|
+
className: "absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground",
|
|
5436
|
+
children: /* @__PURE__ */ jsx(X, { size: 12 })
|
|
4045
5437
|
}
|
|
4046
5438
|
)
|
|
4047
|
-
] },
|
|
5439
|
+
] }),
|
|
5440
|
+
filteredVariables.length === 0 && variableSearch.trim() && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground py-2 text-center", children: [
|
|
5441
|
+
"No variables match \u201C",
|
|
5442
|
+
variableSearch,
|
|
5443
|
+
"\u201D"
|
|
5444
|
+
] }),
|
|
5445
|
+
filteredVariables.map((v) => {
|
|
5446
|
+
const isLocked = lockedVarNames.has(v.varName);
|
|
5447
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
5448
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: `gen-${v.varName}`, className: "text-xs", children: v.varLabel || v.varName }),
|
|
5449
|
+
/* @__PURE__ */ jsx(
|
|
5450
|
+
Input,
|
|
5451
|
+
{
|
|
5452
|
+
id: `gen-${v.varName}`,
|
|
5453
|
+
value: variableValues[v.varName] || "",
|
|
5454
|
+
onChange: (e) => updateVariableValue(v.varName, e.target.value),
|
|
5455
|
+
placeholder: v.varDefault || `Enter ${v.varLabel || v.varName}`,
|
|
5456
|
+
disabled: isLocked,
|
|
5457
|
+
className: cn("h-8 text-xs", isLocked && "opacity-60 cursor-not-allowed")
|
|
5458
|
+
}
|
|
5459
|
+
)
|
|
5460
|
+
] }, v.varName);
|
|
5461
|
+
})
|
|
4048
5462
|
] }),
|
|
4049
5463
|
mode === "bulk" && /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-3", children: [
|
|
4050
5464
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
@@ -4233,56 +5647,60 @@ ${allWarnings.join("\n")}`);
|
|
|
4233
5647
|
] }),
|
|
4234
5648
|
!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
5649
|
] }),
|
|
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
|
-
|
|
5650
|
+
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: [
|
|
5651
|
+
mode === "single" || !hasVariables ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5652
|
+
/* @__PURE__ */ jsxs(
|
|
5653
|
+
Button,
|
|
5654
|
+
{
|
|
5655
|
+
variant: "secondary",
|
|
5656
|
+
onClick: handlePreview,
|
|
5657
|
+
disabled: isGenerating,
|
|
5658
|
+
className: "h-10 px-4 font-semibold shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
5659
|
+
children: [
|
|
5660
|
+
isGenerating ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsx(RefreshCw, { size: 16 }),
|
|
5661
|
+
isGenerating ? "Generating..." : "Generate PDF"
|
|
5662
|
+
]
|
|
5663
|
+
}
|
|
5664
|
+
),
|
|
5665
|
+
/* @__PURE__ */ jsxs(
|
|
5666
|
+
Button,
|
|
5667
|
+
{
|
|
5668
|
+
onClick: handleExportSingle,
|
|
5669
|
+
disabled: !previewFresh || isExporting,
|
|
5670
|
+
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",
|
|
5671
|
+
children: [
|
|
5672
|
+
isExporting ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsx(Download, { size: 16 }),
|
|
5673
|
+
isExporting ? "Exporting..." : "Export PDF"
|
|
5674
|
+
]
|
|
5675
|
+
}
|
|
5676
|
+
)
|
|
5677
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
4263
5678
|
Button,
|
|
4264
5679
|
{
|
|
4265
|
-
onClick:
|
|
4266
|
-
disabled: !
|
|
4267
|
-
className: "h-10 px-4 font-semibold bg-primary hover:bg-primary/90 text-primary-foreground shadow-
|
|
5680
|
+
onClick: handleBulkGenerate,
|
|
5681
|
+
disabled: isExporting || !bulkData || bulkData.length === 0,
|
|
5682
|
+
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
5683
|
children: [
|
|
4269
5684
|
isExporting ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsx(Download, { size: 16 }),
|
|
4270
|
-
isExporting ? "
|
|
5685
|
+
isExporting ? "Generating..." : `Generate All (${bulkData?.length || 0} PDFs)`
|
|
4271
5686
|
]
|
|
4272
5687
|
}
|
|
4273
|
-
)
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
]
|
|
4284
|
-
|
|
4285
|
-
|
|
5688
|
+
),
|
|
5689
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 ml-auto", children: [
|
|
5690
|
+
previewError && /* @__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: previewError })
|
|
5693
|
+
] }),
|
|
5694
|
+
exportError && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
5695
|
+
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
5696
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: exportError })
|
|
5697
|
+
] }),
|
|
5698
|
+
exportSuccess && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-emerald-600 max-w-[300px]", children: [
|
|
5699
|
+
/* @__PURE__ */ jsx(CheckCircle2, { size: 14, className: "shrink-0" }),
|
|
5700
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: exportSuccess })
|
|
5701
|
+
] })
|
|
5702
|
+
] })
|
|
5703
|
+
] })
|
|
4286
5704
|
] }),
|
|
4287
5705
|
/* @__PURE__ */ jsx("div", { className: "bg-border" }),
|
|
4288
5706
|
/* @__PURE__ */ jsx("div", { className: "flex flex-col min-h-0 min-w-0", children: /* @__PURE__ */ jsx(
|
|
@@ -4460,7 +5878,13 @@ function DocumentGeneratorInner({
|
|
|
4460
5878
|
showToolbar = true,
|
|
4461
5879
|
showGenerateTab = true,
|
|
4462
5880
|
onGeneratePdf,
|
|
4463
|
-
initialBulkData
|
|
5881
|
+
initialBulkData,
|
|
5882
|
+
defaultTab = "editor",
|
|
5883
|
+
initialVariableValues,
|
|
5884
|
+
predefinedVariables,
|
|
5885
|
+
exportFileName,
|
|
5886
|
+
templateMode,
|
|
5887
|
+
onSaveTemplate
|
|
4464
5888
|
}) {
|
|
4465
5889
|
const {
|
|
4466
5890
|
editor,
|
|
@@ -4487,7 +5911,7 @@ function DocumentGeneratorInner({
|
|
|
4487
5911
|
placeholder,
|
|
4488
5912
|
onChange
|
|
4489
5913
|
});
|
|
4490
|
-
const [activeTab, setActiveTab] = useState(
|
|
5914
|
+
const [activeTab, setActiveTab] = useState(defaultTab);
|
|
4491
5915
|
const [insertPopoverOpen, setInsertPopoverOpen] = useState(false);
|
|
4492
5916
|
const [insertVarPopoverOpen, setInsertVarPopoverOpen] = useState(false);
|
|
4493
5917
|
const [editFieldId, setEditFieldId] = useState(null);
|
|
@@ -4570,6 +5994,21 @@ function DocumentGeneratorInner({
|
|
|
4570
5994
|
setExportSuccess("Document exported successfully");
|
|
4571
5995
|
}
|
|
4572
5996
|
}, [exportDocument, onExport]);
|
|
5997
|
+
const handleSaveTemplate = useCallback(() => {
|
|
5998
|
+
const md = exportMarkdown();
|
|
5999
|
+
if (!md.trim()) {
|
|
6000
|
+
setMdExportError("Editor content is empty");
|
|
6001
|
+
return;
|
|
6002
|
+
}
|
|
6003
|
+
const validation = validateMarkdown(md);
|
|
6004
|
+
if (!validation.valid) {
|
|
6005
|
+
setMdExportError(validation.errors.join("; "));
|
|
6006
|
+
return;
|
|
6007
|
+
}
|
|
6008
|
+
setMdExportError(null);
|
|
6009
|
+
onSaveTemplate?.(md);
|
|
6010
|
+
setExportSuccess("Template saved");
|
|
6011
|
+
}, [exportMarkdown, onSaveTemplate]);
|
|
4573
6012
|
const handleExportMarkdown = useCallback(() => {
|
|
4574
6013
|
const md = exportMarkdown();
|
|
4575
6014
|
if (!md.trim()) {
|
|
@@ -4586,10 +6025,10 @@ function DocumentGeneratorInner({
|
|
|
4586
6025
|
const url = URL.createObjectURL(blob);
|
|
4587
6026
|
const a = document.createElement("a");
|
|
4588
6027
|
a.href = url;
|
|
4589
|
-
a.download = "template.md";
|
|
6028
|
+
a.download = exportFileName ? `${exportFileName}.md` : "template.md";
|
|
4590
6029
|
a.click();
|
|
4591
6030
|
URL.revokeObjectURL(url);
|
|
4592
|
-
}, [exportMarkdown]);
|
|
6031
|
+
}, [exportMarkdown, exportFileName]);
|
|
4593
6032
|
useEffect(() => {
|
|
4594
6033
|
if (mdExportError) setMdExportError(null);
|
|
4595
6034
|
}, [markdown]);
|
|
@@ -4625,6 +6064,7 @@ function DocumentGeneratorInner({
|
|
|
4625
6064
|
open: insertVarPopoverOpen,
|
|
4626
6065
|
onOpenChange: setInsertVarPopoverOpen,
|
|
4627
6066
|
onInsert: handleInsertVariable,
|
|
6067
|
+
predefinedVariables,
|
|
4628
6068
|
children: /* @__PURE__ */ jsxs(
|
|
4629
6069
|
Button,
|
|
4630
6070
|
{
|
|
@@ -4665,118 +6105,141 @@ function DocumentGeneratorInner({
|
|
|
4665
6105
|
] })
|
|
4666
6106
|
] })
|
|
4667
6107
|
] }),
|
|
4668
|
-
/* @__PURE__ */
|
|
6108
|
+
/* @__PURE__ */ jsx("div", { className: cn(
|
|
4669
6109
|
"flex flex-col flex-1 min-h-0",
|
|
4670
6110
|
activeTab !== "editor" && "hidden"
|
|
6111
|
+
), children: /* @__PURE__ */ jsxs("div", { className: cn(
|
|
6112
|
+
"flex-1 min-h-0",
|
|
6113
|
+
showPreview && !editorCollapsed ? "grid grid-cols-[1fr_1px_1fr]" : "flex flex-col"
|
|
4671
6114
|
), 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,
|
|
6115
|
+
!editorCollapsed && /* @__PURE__ */ jsxs("div", { className: "flex flex-col min-h-0 min-w-0", children: [
|
|
6116
|
+
showToolbar && !readOnly && /* @__PURE__ */ jsx("div", { className: "border-b border-border", children: /* @__PURE__ */ jsx(
|
|
6117
|
+
EditorToolbar,
|
|
4697
6118
|
{
|
|
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"
|
|
6119
|
+
editor,
|
|
6120
|
+
insertFieldButton,
|
|
6121
|
+
insertVariableButton,
|
|
6122
|
+
onCollapse: showPreview ? () => setEditorCollapsed(true) : void 0
|
|
4712
6123
|
}
|
|
4713
|
-
)
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
/* @__PURE__ */ jsxs(
|
|
4717
|
-
Button,
|
|
6124
|
+
) }),
|
|
6125
|
+
/* @__PURE__ */ jsx("div", { ref: editorWrapperRef, className: "flex-1 overflow-y-auto min-h-0 scrollbar-hidden", children: /* @__PURE__ */ jsx(
|
|
6126
|
+
EditorContent,
|
|
4718
6127
|
{
|
|
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
|
-
]
|
|
6128
|
+
editor,
|
|
6129
|
+
className: "prose prose-sm max-w-none p-4 focus-within:outline-none"
|
|
4727
6130
|
}
|
|
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: [
|
|
6131
|
+
) }),
|
|
6132
|
+
/* @__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
6133
|
/* @__PURE__ */ jsxs(
|
|
4754
6134
|
Button,
|
|
4755
6135
|
{
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
6136
|
+
variant: "secondary",
|
|
6137
|
+
onClick: generatePdf,
|
|
6138
|
+
disabled: isGenerating || !editor,
|
|
6139
|
+
className: "h-10 px-4 font-semibold shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
4759
6140
|
children: [
|
|
4760
|
-
/* @__PURE__ */ jsx(
|
|
4761
|
-
"
|
|
6141
|
+
isGenerating ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsx(RefreshCw, { size: 16 }),
|
|
6142
|
+
isGenerating ? "Generating..." : "Generate PDF"
|
|
4762
6143
|
]
|
|
4763
6144
|
}
|
|
4764
6145
|
),
|
|
4765
|
-
/* @__PURE__ */ jsxs(
|
|
6146
|
+
onSaveTemplate ? /* @__PURE__ */ jsxs(
|
|
4766
6147
|
Button,
|
|
4767
6148
|
{
|
|
4768
|
-
onClick:
|
|
4769
|
-
disabled:
|
|
6149
|
+
onClick: handleSaveTemplate,
|
|
6150
|
+
disabled: !editor || !markdown.trim(),
|
|
4770
6151
|
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
6152
|
children: [
|
|
4772
|
-
/* @__PURE__ */ jsx(
|
|
4773
|
-
exportButtonText
|
|
6153
|
+
/* @__PURE__ */ jsx(Save, { size: 16 }),
|
|
6154
|
+
exportButtonText || "Save Template"
|
|
4774
6155
|
]
|
|
4775
6156
|
}
|
|
4776
|
-
)
|
|
6157
|
+
) : /* @__PURE__ */ jsxs(Popover, { children: [
|
|
6158
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
6159
|
+
Button,
|
|
6160
|
+
{
|
|
6161
|
+
disabled: !editor || !markdown.trim() && pdfPages.length === 0,
|
|
6162
|
+
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",
|
|
6163
|
+
children: [
|
|
6164
|
+
/* @__PURE__ */ jsx(FileDown, { size: 16 }),
|
|
6165
|
+
"Export",
|
|
6166
|
+
/* @__PURE__ */ jsx(ChevronDown, { size: 14 })
|
|
6167
|
+
]
|
|
6168
|
+
}
|
|
6169
|
+
) }),
|
|
6170
|
+
/* @__PURE__ */ jsxs(PopoverContent, { align: "start", className: "w-48 p-1", children: [
|
|
6171
|
+
/* @__PURE__ */ jsxs(
|
|
6172
|
+
"button",
|
|
6173
|
+
{
|
|
6174
|
+
onClick: handleExportMarkdown,
|
|
6175
|
+
disabled: !editor || !markdown.trim(),
|
|
6176
|
+
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",
|
|
6177
|
+
children: [
|
|
6178
|
+
/* @__PURE__ */ jsx(FileText, { size: 16 }),
|
|
6179
|
+
"Export as MD"
|
|
6180
|
+
]
|
|
6181
|
+
}
|
|
6182
|
+
),
|
|
6183
|
+
/* @__PURE__ */ jsxs(
|
|
6184
|
+
"button",
|
|
6185
|
+
{
|
|
6186
|
+
onClick: handleExport,
|
|
6187
|
+
disabled: isGenerating || !editor || pdfPages.length === 0,
|
|
6188
|
+
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",
|
|
6189
|
+
children: [
|
|
6190
|
+
/* @__PURE__ */ jsx(FileDown, { size: 16 }),
|
|
6191
|
+
"Export as PDF"
|
|
6192
|
+
]
|
|
6193
|
+
}
|
|
6194
|
+
)
|
|
6195
|
+
] })
|
|
6196
|
+
] }),
|
|
6197
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 ml-auto", children: [
|
|
6198
|
+
mdExportError && /* @__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: mdExportError })
|
|
6201
|
+
] }),
|
|
6202
|
+
generationError && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
6203
|
+
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
6204
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: generationError })
|
|
6205
|
+
] }),
|
|
6206
|
+
fieldWarnings.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-orange-600 max-w-[400px]", children: [
|
|
6207
|
+
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
6208
|
+
/* @__PURE__ */ jsxs("span", { className: "truncate", children: [
|
|
6209
|
+
fieldWarnings.length,
|
|
6210
|
+
" field",
|
|
6211
|
+
fieldWarnings.length !== 1 ? "s" : "",
|
|
6212
|
+
" had warnings during generation"
|
|
6213
|
+
] })
|
|
6214
|
+
] }),
|
|
6215
|
+
exportSuccess && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-emerald-600 max-w-[300px]", children: [
|
|
6216
|
+
/* @__PURE__ */ jsx(CheckCircle2, { size: 14, className: "shrink-0" }),
|
|
6217
|
+
/* @__PURE__ */ jsx("span", { children: exportSuccess })
|
|
6218
|
+
] })
|
|
6219
|
+
] })
|
|
4777
6220
|
] })
|
|
4778
|
-
] })
|
|
4779
|
-
|
|
6221
|
+
] }),
|
|
6222
|
+
showPreview && !editorCollapsed && /* @__PURE__ */ jsx("div", { className: "bg-border" }),
|
|
6223
|
+
showPreview && /* @__PURE__ */ jsx(
|
|
6224
|
+
PreviewPanel,
|
|
6225
|
+
{
|
|
6226
|
+
pages: pdfPages,
|
|
6227
|
+
isGenerating,
|
|
6228
|
+
positionedFields,
|
|
6229
|
+
headerLeft: editorCollapsed ? /* @__PURE__ */ jsx(
|
|
6230
|
+
Button,
|
|
6231
|
+
{
|
|
6232
|
+
variant: "ghost",
|
|
6233
|
+
size: "sm",
|
|
6234
|
+
className: "h-8 w-8 p-0",
|
|
6235
|
+
onClick: () => setEditorCollapsed(false),
|
|
6236
|
+
children: /* @__PURE__ */ jsx(PanelLeftOpen, { size: 14 })
|
|
6237
|
+
}
|
|
6238
|
+
) : void 0,
|
|
6239
|
+
className: "flex-1 border-0 rounded-none"
|
|
6240
|
+
}
|
|
6241
|
+
)
|
|
6242
|
+
] }) }),
|
|
4780
6243
|
showGenerateTab && activeTab === "generate" && /* @__PURE__ */ jsx(
|
|
4781
6244
|
GeneratePanel,
|
|
4782
6245
|
{
|
|
@@ -4784,7 +6247,10 @@ function DocumentGeneratorInner({
|
|
|
4784
6247
|
editorContent: editor?.getJSON(),
|
|
4785
6248
|
editorVariables: variables,
|
|
4786
6249
|
onGeneratePdf,
|
|
4787
|
-
initialBulkData
|
|
6250
|
+
initialBulkData,
|
|
6251
|
+
initialVariableValues,
|
|
6252
|
+
exportFileName,
|
|
6253
|
+
templateMode
|
|
4788
6254
|
}
|
|
4789
6255
|
),
|
|
4790
6256
|
/* @__PURE__ */ jsx(
|
|
@@ -4925,6 +6391,717 @@ function EditorPanel({
|
|
|
4925
6391
|
] });
|
|
4926
6392
|
}
|
|
4927
6393
|
|
|
4928
|
-
|
|
6394
|
+
// src/utils/xml-template-parser.ts
|
|
6395
|
+
function extractFieldRefs(expr) {
|
|
6396
|
+
const refs = [];
|
|
6397
|
+
const re = /\[([^\]]+)\](?:\.\[([^\]]+)\])?/g;
|
|
6398
|
+
let m;
|
|
6399
|
+
while ((m = re.exec(expr)) !== null) {
|
|
6400
|
+
if (m[2]) {
|
|
6401
|
+
refs.push(`${m[1]}.${m[2]}`);
|
|
6402
|
+
} else {
|
|
6403
|
+
refs.push(m[1]);
|
|
6404
|
+
}
|
|
6405
|
+
}
|
|
6406
|
+
return refs;
|
|
6407
|
+
}
|
|
6408
|
+
function fieldRefToVarName(ref, bandContext) {
|
|
6409
|
+
if (ref.includes(".")) {
|
|
6410
|
+
return labelToVarName(ref.replace(/\./g, "_"));
|
|
6411
|
+
}
|
|
6412
|
+
if (bandContext) {
|
|
6413
|
+
return labelToVarName(bandContext + "_" + ref);
|
|
6414
|
+
}
|
|
6415
|
+
return labelToVarName(ref);
|
|
6416
|
+
}
|
|
6417
|
+
function fieldRefToLabel(ref) {
|
|
6418
|
+
const parts = ref.split(".");
|
|
6419
|
+
const raw = parts[parts.length - 1];
|
|
6420
|
+
return raw.replace(/_/g, " ");
|
|
6421
|
+
}
|
|
6422
|
+
function resolveExpression(expr, registry, siblingLabel, bandContext) {
|
|
6423
|
+
const trimmed = expr.trim();
|
|
6424
|
+
const suppressZero = /<>\s*0/.test(trimmed);
|
|
6425
|
+
const szOpts = suppressZero ? { suppressZero: true } : void 0;
|
|
6426
|
+
const aggMatch = trimmed.match(
|
|
6427
|
+
/^\[([^\]]+)\]\.sum\(\[([^\]]+)\]\)$/i
|
|
6428
|
+
);
|
|
6429
|
+
if (aggMatch) {
|
|
6430
|
+
const fieldName = aggMatch[2];
|
|
6431
|
+
const varName = labelToVarName("workorder_" + fieldName);
|
|
6432
|
+
const label = fieldName.replace(/_/g, " ");
|
|
6433
|
+
return registry.register(varName, label, szOpts);
|
|
6434
|
+
}
|
|
6435
|
+
const concatSingle = trimmed.match(
|
|
6436
|
+
/^concat\(\s*'[^']*'\s*,\s*\[([^\]]+)\](?:\.\[([^\]]+)\])?\s*\)$/i
|
|
6437
|
+
);
|
|
6438
|
+
if (concatSingle) {
|
|
6439
|
+
const ref = concatSingle[2] ? `${concatSingle[1]}.${concatSingle[2]}` : concatSingle[1];
|
|
6440
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6441
|
+
const label = fieldRefToLabel(ref);
|
|
6442
|
+
return registry.register(varName, label, szOpts);
|
|
6443
|
+
}
|
|
6444
|
+
const concatMulti = trimmed.match(
|
|
6445
|
+
/^concat\(/i
|
|
6446
|
+
);
|
|
6447
|
+
if (concatMulti) {
|
|
6448
|
+
const refs = extractFieldRefs(trimmed);
|
|
6449
|
+
if (refs.length > 0) {
|
|
6450
|
+
const uniqueRefs = [...new Set(refs)];
|
|
6451
|
+
const tokens = uniqueRefs.map((ref) => {
|
|
6452
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6453
|
+
const label = fieldRefToLabel(ref);
|
|
6454
|
+
return registry.register(varName, label, szOpts);
|
|
6455
|
+
});
|
|
6456
|
+
return tokens.join(" ");
|
|
6457
|
+
}
|
|
6458
|
+
}
|
|
6459
|
+
const toLongMatch = trimmed.match(
|
|
6460
|
+
/^ToLong\(\s*\[([^\]]+)\](?:\.\[([^\]]+)\])?\s*\)$/i
|
|
6461
|
+
);
|
|
6462
|
+
if (toLongMatch) {
|
|
6463
|
+
const ref = toLongMatch[2] ? `${toLongMatch[1]}.${toLongMatch[2]}` : toLongMatch[1];
|
|
6464
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6465
|
+
const label = fieldRefToLabel(ref);
|
|
6466
|
+
return registry.register(varName, label, szOpts);
|
|
6467
|
+
}
|
|
6468
|
+
const fmtMatch = trimmed.match(
|
|
6469
|
+
/^FormatString\(\s*'[^']*'\s*,\s*(.+)\s*\)$/i
|
|
6470
|
+
);
|
|
6471
|
+
if (fmtMatch) {
|
|
6472
|
+
return resolveExpression(fmtMatch[1], registry, siblingLabel, bandContext);
|
|
6473
|
+
}
|
|
6474
|
+
const replaceMatch = trimmed.match(
|
|
6475
|
+
/^Replace\(\s*\[([^\]]+)\]/i
|
|
6476
|
+
);
|
|
6477
|
+
if (replaceMatch) {
|
|
6478
|
+
const ref = replaceMatch[1];
|
|
6479
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6480
|
+
const label = fieldRefToLabel(ref);
|
|
6481
|
+
return registry.register(varName, label, szOpts);
|
|
6482
|
+
}
|
|
6483
|
+
const iifLabelMatch = trimmed.match(
|
|
6484
|
+
/^iif\s*\(.+?,\s*'([^']+)'\s*,\s*\?\s*\)$/i
|
|
6485
|
+
);
|
|
6486
|
+
if (iifLabelMatch) {
|
|
6487
|
+
return iifLabelMatch[1];
|
|
6488
|
+
}
|
|
6489
|
+
const iifLabelElse = trimmed.match(
|
|
6490
|
+
/^iif\s*\(.+?,\s*'([^']+)'\s*,\s*'([^']+)'\s*\)$/i
|
|
6491
|
+
);
|
|
6492
|
+
if (iifLabelElse) {
|
|
6493
|
+
return iifLabelElse[1];
|
|
6494
|
+
}
|
|
6495
|
+
const iifEmptyElse = trimmed.match(
|
|
6496
|
+
/^[Ii]if\s*\(.+?,\s*'([^']+)'\s*,\s*''\s*\)$/i
|
|
6497
|
+
);
|
|
6498
|
+
if (iifEmptyElse) {
|
|
6499
|
+
return iifEmptyElse[1];
|
|
6500
|
+
}
|
|
6501
|
+
const nestedIifStaticLabel = trimmed.match(
|
|
6502
|
+
/^iif\s*\(.*,\s*[Ii]if\s*\([^,]+,\s*'([^']+)'\s*,/i
|
|
6503
|
+
);
|
|
6504
|
+
if (nestedIifStaticLabel) {
|
|
6505
|
+
return nestedIifStaticLabel[1];
|
|
6506
|
+
}
|
|
6507
|
+
const iifValueMatch = trimmed.match(
|
|
6508
|
+
/^iif\s*\(/i
|
|
6509
|
+
);
|
|
6510
|
+
if (iifValueMatch) {
|
|
6511
|
+
const refs = extractFieldRefs(trimmed);
|
|
6512
|
+
if (refs.length > 0) {
|
|
6513
|
+
const uniqueRefs = [...new Set(refs)];
|
|
6514
|
+
const innerAggMatch = trimmed.match(
|
|
6515
|
+
/\[([^\]]+)\]\.sum\(\[([^\]]+)\]\)/i
|
|
6516
|
+
);
|
|
6517
|
+
if (innerAggMatch) {
|
|
6518
|
+
const fieldName = innerAggMatch[2];
|
|
6519
|
+
const varName2 = labelToVarName("workorder_" + fieldName);
|
|
6520
|
+
const label2 = fieldName.replace(/_/g, " ");
|
|
6521
|
+
return registry.register(varName2, label2, szOpts);
|
|
6522
|
+
}
|
|
6523
|
+
const ref = uniqueRefs[uniqueRefs.length - 1];
|
|
6524
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6525
|
+
const label = fieldRefToLabel(ref);
|
|
6526
|
+
return registry.register(varName, label, szOpts);
|
|
6527
|
+
}
|
|
6528
|
+
const staticMatch = trimmed.match(/'([^']+)'/);
|
|
6529
|
+
if (staticMatch) return staticMatch[1];
|
|
6530
|
+
return "";
|
|
6531
|
+
}
|
|
6532
|
+
const arithmeticRefs = extractFieldRefs(trimmed);
|
|
6533
|
+
if (arithmeticRefs.length > 1 && /[+\-*/]/.test(trimmed)) {
|
|
6534
|
+
if (siblingLabel) {
|
|
6535
|
+
const cleanLabel = siblingLabel.replace(/[:\s]+$/, "");
|
|
6536
|
+
const varName = labelToVarName(
|
|
6537
|
+
(bandContext ? bandContext + "_" : "") + cleanLabel.replace(/\s+/g, "_")
|
|
6538
|
+
);
|
|
6539
|
+
return registry.register(varName, cleanLabel, szOpts);
|
|
6540
|
+
}
|
|
6541
|
+
const uniqueRefs = [...new Set(arithmeticRefs)];
|
|
6542
|
+
const tokens = uniqueRefs.map((ref) => {
|
|
6543
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6544
|
+
const label = fieldRefToLabel(ref);
|
|
6545
|
+
return registry.register(varName, label, szOpts);
|
|
6546
|
+
});
|
|
6547
|
+
return tokens.join(" ");
|
|
6548
|
+
}
|
|
6549
|
+
const simpleMatch = trimmed.match(
|
|
6550
|
+
/^\[([^\]]+)\](?:\.\[([^\]]+)\])?$/
|
|
6551
|
+
);
|
|
6552
|
+
if (simpleMatch) {
|
|
6553
|
+
const ref = simpleMatch[2] ? `${simpleMatch[1]}.${simpleMatch[2]}` : simpleMatch[1];
|
|
6554
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6555
|
+
const label = fieldRefToLabel(ref);
|
|
6556
|
+
return registry.register(varName, label, szOpts);
|
|
6557
|
+
}
|
|
6558
|
+
if (arithmeticRefs.length >= 1) {
|
|
6559
|
+
const ref = arithmeticRefs[arithmeticRefs.length - 1];
|
|
6560
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6561
|
+
const label = fieldRefToLabel(ref);
|
|
6562
|
+
return registry.register(varName, label, szOpts);
|
|
6563
|
+
}
|
|
6564
|
+
return "";
|
|
6565
|
+
}
|
|
6566
|
+
var VariableRegistry = class {
|
|
6567
|
+
constructor() {
|
|
6568
|
+
__publicField(this, "map", /* @__PURE__ */ new Map());
|
|
6569
|
+
}
|
|
6570
|
+
register(varName, varLabel, options) {
|
|
6571
|
+
if (!this.map.has(varName)) {
|
|
6572
|
+
this.map.set(varName, { varName, varLabel, varDefault: "" });
|
|
6573
|
+
}
|
|
6574
|
+
const suppress = options?.suppressZero ? "|suppress:zero" : "";
|
|
6575
|
+
return `{{var|name:${varName}|label:${varLabel}${suppress}}}`;
|
|
6576
|
+
}
|
|
6577
|
+
getAll() {
|
|
6578
|
+
return Array.from(this.map.values());
|
|
6579
|
+
}
|
|
6580
|
+
};
|
|
6581
|
+
function parseLocationFloat(s) {
|
|
6582
|
+
if (!s) return { x: 0, y: 0 };
|
|
6583
|
+
const parts = s.split(",");
|
|
6584
|
+
return {
|
|
6585
|
+
x: parseFloat(parts[0]) || 0,
|
|
6586
|
+
y: parseFloat(parts[1]) || 0
|
|
6587
|
+
};
|
|
6588
|
+
}
|
|
6589
|
+
function parseSizeF(s) {
|
|
6590
|
+
if (!s) return { w: 0, h: 0 };
|
|
6591
|
+
const parts = s.split(",");
|
|
6592
|
+
return {
|
|
6593
|
+
w: parseFloat(parts[0]) || 0,
|
|
6594
|
+
h: parseFloat(parts[1]) || 0
|
|
6595
|
+
};
|
|
6596
|
+
}
|
|
6597
|
+
function extractControls(bandEl) {
|
|
6598
|
+
const controlsEl = bandEl.querySelector(":scope > Controls");
|
|
6599
|
+
if (!controlsEl) return [];
|
|
6600
|
+
const items = [];
|
|
6601
|
+
for (const child of Array.from(controlsEl.children)) {
|
|
6602
|
+
items.push(extractControl(child));
|
|
6603
|
+
}
|
|
6604
|
+
return items;
|
|
6605
|
+
}
|
|
6606
|
+
function extractControl(el) {
|
|
6607
|
+
const ctrlType = el.getAttribute("ControlType") || "";
|
|
6608
|
+
const name = el.getAttribute("Name") || "";
|
|
6609
|
+
const text = el.getAttribute("Text") || "";
|
|
6610
|
+
const loc = parseLocationFloat(el.getAttribute("LocationFloat"));
|
|
6611
|
+
const size = parseSizeF(el.getAttribute("SizeF"));
|
|
6612
|
+
const formatString = el.getAttribute("TextFormatString") || null;
|
|
6613
|
+
let expression = null;
|
|
6614
|
+
const bindingsEl = el.querySelector(":scope > ExpressionBindings");
|
|
6615
|
+
if (bindingsEl) {
|
|
6616
|
+
for (const item of Array.from(bindingsEl.children)) {
|
|
6617
|
+
if (item.getAttribute("PropertyName") === "Text" && item.getAttribute("EventName") === "BeforePrint") {
|
|
6618
|
+
expression = item.getAttribute("Expression");
|
|
6619
|
+
break;
|
|
6620
|
+
}
|
|
6621
|
+
}
|
|
6622
|
+
}
|
|
6623
|
+
const children = [];
|
|
6624
|
+
if (ctrlType === "XRPanel") {
|
|
6625
|
+
const panelControls = extractControls(el);
|
|
6626
|
+
children.push(...panelControls);
|
|
6627
|
+
}
|
|
6628
|
+
return {
|
|
6629
|
+
name,
|
|
6630
|
+
type: ctrlType,
|
|
6631
|
+
text,
|
|
6632
|
+
x: loc.x,
|
|
6633
|
+
y: loc.y,
|
|
6634
|
+
width: size.w,
|
|
6635
|
+
expression,
|
|
6636
|
+
formatString,
|
|
6637
|
+
children
|
|
6638
|
+
};
|
|
6639
|
+
}
|
|
6640
|
+
function groupByRow(controls, threshold = 8) {
|
|
6641
|
+
const sorted = [...controls].sort((a, b) => a.y - b.y);
|
|
6642
|
+
const rows = [];
|
|
6643
|
+
for (const ctrl of sorted) {
|
|
6644
|
+
const lastRow = rows[rows.length - 1];
|
|
6645
|
+
if (lastRow && Math.abs(ctrl.y - lastRow.y) < threshold) {
|
|
6646
|
+
lastRow.controls.push(ctrl);
|
|
6647
|
+
} else {
|
|
6648
|
+
rows.push({ y: ctrl.y, controls: [ctrl] });
|
|
6649
|
+
}
|
|
6650
|
+
}
|
|
6651
|
+
for (const row of rows) {
|
|
6652
|
+
row.controls.sort((a, b) => a.x - b.x);
|
|
6653
|
+
}
|
|
6654
|
+
return rows;
|
|
6655
|
+
}
|
|
6656
|
+
function isStaticLabel(ctrl) {
|
|
6657
|
+
return ctrl.type === "XRLabel" && !ctrl.expression;
|
|
6658
|
+
}
|
|
6659
|
+
function isBound(ctrl) {
|
|
6660
|
+
return ctrl.type === "XRLabel" && !!ctrl.expression;
|
|
6661
|
+
}
|
|
6662
|
+
function computeSplitPercent(leftControls, rightControls, contentWidth) {
|
|
6663
|
+
const leftExtent = leftControls.reduce(
|
|
6664
|
+
(max, c) => Math.max(max, c.x + c.width),
|
|
6665
|
+
0
|
|
6666
|
+
);
|
|
6667
|
+
const rightMinX = rightControls.reduce(
|
|
6668
|
+
(min, c) => Math.min(min, c.x),
|
|
6669
|
+
contentWidth
|
|
6670
|
+
);
|
|
6671
|
+
const rightMaxExtent = rightControls.reduce(
|
|
6672
|
+
(max, c) => Math.max(max, c.x + c.width),
|
|
6673
|
+
0
|
|
6674
|
+
);
|
|
6675
|
+
const rightExtent = rightMaxExtent - rightMinX;
|
|
6676
|
+
if (leftExtent + rightExtent === 0) return 50;
|
|
6677
|
+
return Math.round(leftExtent / (leftExtent + rightExtent) * 100);
|
|
6678
|
+
}
|
|
6679
|
+
function renderTopMarginBand(controls, registry, contentWidth, calculatedFieldNames) {
|
|
6680
|
+
const lines = [];
|
|
6681
|
+
const leftControls = controls.filter((c) => c.x < 437);
|
|
6682
|
+
const rightControls = controls.filter((c) => c.x >= 437);
|
|
6683
|
+
const leftEntries = [];
|
|
6684
|
+
if (leftControls.length > 0) {
|
|
6685
|
+
const leftRows = groupByRow(leftControls);
|
|
6686
|
+
for (const row of leftRows) {
|
|
6687
|
+
const parts = [];
|
|
6688
|
+
for (const ctrl of row.controls) {
|
|
6689
|
+
if (isBound(ctrl)) {
|
|
6690
|
+
let resolved = resolveExpression(ctrl.expression, registry, void 0, "workorder");
|
|
6691
|
+
if (ctrl.formatString && /\(###\)\s*###\s*-\s*####/.test(ctrl.formatString) && resolved.startsWith("{{var|")) {
|
|
6692
|
+
resolved = resolved.replace("}}", "|format:phone}}");
|
|
6693
|
+
}
|
|
6694
|
+
if (resolved) parts.push(resolved);
|
|
6695
|
+
} else if (isStaticLabel(ctrl)) {
|
|
6696
|
+
const text = ctrl.text.trim();
|
|
6697
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
6698
|
+
parts.push(text);
|
|
6699
|
+
}
|
|
6700
|
+
}
|
|
6701
|
+
}
|
|
6702
|
+
if (parts.length > 0) {
|
|
6703
|
+
leftEntries.push(parts.join(" "));
|
|
6704
|
+
}
|
|
6705
|
+
}
|
|
6706
|
+
}
|
|
6707
|
+
const rightEntries = [];
|
|
6708
|
+
if (rightControls.length > 0) {
|
|
6709
|
+
const rightRows = groupByRow(rightControls);
|
|
6710
|
+
for (const row of rightRows) {
|
|
6711
|
+
let label = "";
|
|
6712
|
+
let value = "";
|
|
6713
|
+
for (const ctrl of row.controls) {
|
|
6714
|
+
if (isStaticLabel(ctrl)) {
|
|
6715
|
+
const text = ctrl.text.trim();
|
|
6716
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
6717
|
+
label = text;
|
|
6718
|
+
}
|
|
6719
|
+
} else if (isBound(ctrl)) {
|
|
6720
|
+
value = resolveExpression(ctrl.expression, registry, label, "workorder");
|
|
6721
|
+
if (ctrl.expression) {
|
|
6722
|
+
const refs = extractFieldRefs(ctrl.expression);
|
|
6723
|
+
if (refs.some((ref) => calculatedFieldNames.has(ref)) && value.startsWith("{{var|")) {
|
|
6724
|
+
value = value.replace("}}", "|suppress:zero}}");
|
|
6725
|
+
}
|
|
6726
|
+
}
|
|
6727
|
+
if (ctrl.formatString && /\(###\)\s*###\s*-\s*####/.test(ctrl.formatString) && value.startsWith("{{var|")) {
|
|
6728
|
+
value = value.replace("}}", "|format:phone}}");
|
|
6729
|
+
}
|
|
6730
|
+
}
|
|
6731
|
+
}
|
|
6732
|
+
if (label || value) {
|
|
6733
|
+
rightEntries.push({ label, value, y: row.y });
|
|
6734
|
+
}
|
|
6735
|
+
}
|
|
6736
|
+
}
|
|
6737
|
+
if (leftEntries.length > 0 || rightEntries.length > 0) {
|
|
6738
|
+
const outerSplit = computeSplitPercent(leftControls, rightControls, contentWidth);
|
|
6739
|
+
lines.push(`:::columns{split:${outerSplit}}`);
|
|
6740
|
+
const leftMinY = leftControls.length > 0 ? Math.min(...leftControls.map((c) => c.y)) : 0;
|
|
6741
|
+
const rightMinY = rightControls.length > 0 ? Math.min(...rightControls.map((c) => c.y)) : 0;
|
|
6742
|
+
const xmlLineHeight = 17;
|
|
6743
|
+
const leftPadTop = leftMinY > rightMinY ? Math.round((leftMinY - rightMinY) / xmlLineHeight) : 0;
|
|
6744
|
+
const rightPadTop = rightMinY > leftMinY ? Math.round((rightMinY - leftMinY) / xmlLineHeight) : 0;
|
|
6745
|
+
const leftColTag = leftPadTop ? `:::col{padTop:${leftPadTop}}` : ":::col";
|
|
6746
|
+
lines.push(leftColTag);
|
|
6747
|
+
for (const entry of leftEntries) {
|
|
6748
|
+
lines.push(entry);
|
|
6749
|
+
}
|
|
6750
|
+
lines.push(":::");
|
|
6751
|
+
const rightColTag = rightPadTop ? `:::col{padTop:${rightPadTop}}` : ":::col";
|
|
6752
|
+
lines.push(rightColTag);
|
|
6753
|
+
if (rightEntries.length > 0) {
|
|
6754
|
+
lines.push("| | |");
|
|
6755
|
+
lines.push("|---|---|");
|
|
6756
|
+
let prevY = -Infinity;
|
|
6757
|
+
for (const entry of rightEntries) {
|
|
6758
|
+
if (prevY > -Infinity && entry.y - prevY > 25) {
|
|
6759
|
+
lines.push("| | |");
|
|
6760
|
+
}
|
|
6761
|
+
const label = entry.label || "";
|
|
6762
|
+
const colon = entry.label ? ":" : "";
|
|
6763
|
+
const value = entry.value ? ` ${entry.value}` : "";
|
|
6764
|
+
lines.push(`| ${label} | ${colon}${value} |`);
|
|
6765
|
+
prevY = entry.y;
|
|
6766
|
+
}
|
|
6767
|
+
}
|
|
6768
|
+
lines.push(":::");
|
|
6769
|
+
lines.push(":::");
|
|
6770
|
+
lines.push("");
|
|
6771
|
+
}
|
|
6772
|
+
return lines.join("\n");
|
|
6773
|
+
}
|
|
6774
|
+
function renderReportHeaderBand(controls, registry, contentWidth) {
|
|
6775
|
+
const lines = [];
|
|
6776
|
+
const leftControls = controls.filter((c) => c.x < 437);
|
|
6777
|
+
const rightControls = controls.filter((c) => c.x >= 437);
|
|
6778
|
+
const leftEntries = [];
|
|
6779
|
+
if (leftControls.length > 0) {
|
|
6780
|
+
const leftRows = groupByRow(leftControls);
|
|
6781
|
+
for (const row of leftRows) {
|
|
6782
|
+
const parts = [];
|
|
6783
|
+
for (const ctrl of row.controls) {
|
|
6784
|
+
if (isBound(ctrl)) {
|
|
6785
|
+
const resolved = resolveExpression(ctrl.expression, registry, void 0, "workorder");
|
|
6786
|
+
if (resolved) parts.push(resolved);
|
|
6787
|
+
} else if (isStaticLabel(ctrl)) {
|
|
6788
|
+
const text = ctrl.text.trim();
|
|
6789
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
6790
|
+
parts.push(text);
|
|
6791
|
+
}
|
|
6792
|
+
}
|
|
6793
|
+
}
|
|
6794
|
+
if (parts.length > 0) {
|
|
6795
|
+
leftEntries.push(parts.join(" "));
|
|
6796
|
+
}
|
|
6797
|
+
}
|
|
6798
|
+
}
|
|
6799
|
+
const rightEntries = [];
|
|
6800
|
+
if (rightControls.length > 0) {
|
|
6801
|
+
const rightRows = groupByRow(rightControls);
|
|
6802
|
+
for (const row of rightRows) {
|
|
6803
|
+
let label = "";
|
|
6804
|
+
let value = "";
|
|
6805
|
+
for (const ctrl of row.controls) {
|
|
6806
|
+
if (isStaticLabel(ctrl)) {
|
|
6807
|
+
const text = ctrl.text.trim();
|
|
6808
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
6809
|
+
label = text;
|
|
6810
|
+
}
|
|
6811
|
+
} else if (isBound(ctrl)) {
|
|
6812
|
+
value = resolveExpression(ctrl.expression, registry, void 0, "workorder");
|
|
6813
|
+
if (ctrl.formatString && /\(###\)\s*###\s*-\s*####/.test(ctrl.formatString) && value.startsWith("{{var|")) {
|
|
6814
|
+
value = value.replace("}}", "|format:phone}}");
|
|
6815
|
+
}
|
|
6816
|
+
}
|
|
6817
|
+
}
|
|
6818
|
+
if (label || value) {
|
|
6819
|
+
rightEntries.push({ label, value });
|
|
6820
|
+
}
|
|
6821
|
+
}
|
|
6822
|
+
}
|
|
6823
|
+
if (leftEntries.length > 0 || rightEntries.length > 0) {
|
|
6824
|
+
const outerSplit = computeSplitPercent(leftControls, rightControls, contentWidth);
|
|
6825
|
+
lines.push(`:::columns{split:${outerSplit}}`);
|
|
6826
|
+
lines.push(":::col");
|
|
6827
|
+
for (const entry of leftEntries) {
|
|
6828
|
+
lines.push(entry);
|
|
6829
|
+
}
|
|
6830
|
+
lines.push(":::");
|
|
6831
|
+
lines.push(":::col");
|
|
6832
|
+
lines.push("| | |");
|
|
6833
|
+
lines.push("|---|---|");
|
|
6834
|
+
for (const entry of rightEntries) {
|
|
6835
|
+
const label = entry.label || "";
|
|
6836
|
+
const value = entry.value ? ` ${entry.value}` : "";
|
|
6837
|
+
lines.push(`| ${label} |${value} |`);
|
|
6838
|
+
}
|
|
6839
|
+
lines.push(":::");
|
|
6840
|
+
lines.push(":::");
|
|
6841
|
+
lines.push("");
|
|
6842
|
+
}
|
|
6843
|
+
return lines.join("\n");
|
|
6844
|
+
}
|
|
6845
|
+
function renderOperationsSection(pageHeaderControls, detailControls, groupFooterControls, registry, contentWidth) {
|
|
6846
|
+
const lines = [];
|
|
6847
|
+
const headerTexts = [];
|
|
6848
|
+
for (const ctrl of [...pageHeaderControls].sort((a, b) => a.x - b.x)) {
|
|
6849
|
+
if (isStaticLabel(ctrl)) {
|
|
6850
|
+
const text = ctrl.text.trim();
|
|
6851
|
+
if (text) headerTexts.push(text);
|
|
6852
|
+
}
|
|
6853
|
+
}
|
|
6854
|
+
const titleText = headerTexts.join(" ");
|
|
6855
|
+
lines.push(`:::panel{title:${titleText}|headerStyle:dark|border:none}`);
|
|
6856
|
+
lines.push(":::");
|
|
6857
|
+
lines.push(":::repeat{data:operations}");
|
|
6858
|
+
const sortedDetail = [...detailControls].sort((a, b) => a.x - b.x);
|
|
6859
|
+
const detailParts = [];
|
|
6860
|
+
for (const ctrl of sortedDetail) {
|
|
6861
|
+
if (isBound(ctrl)) {
|
|
6862
|
+
const resolved = resolveExpression(ctrl.expression, registry, void 0, "operation");
|
|
6863
|
+
if (resolved) detailParts.push(resolved);
|
|
6864
|
+
}
|
|
6865
|
+
}
|
|
6866
|
+
if (detailParts.length > 0) {
|
|
6867
|
+
if (detailParts.length >= 2) {
|
|
6868
|
+
lines.push(":::columns{split:12}");
|
|
6869
|
+
lines.push(":::col");
|
|
6870
|
+
lines.push(`**${detailParts[0]}**`);
|
|
6871
|
+
lines.push(":::");
|
|
6872
|
+
lines.push(":::col");
|
|
6873
|
+
lines.push(`**${detailParts.slice(1).join(" ")}**`);
|
|
6874
|
+
lines.push(":::");
|
|
6875
|
+
lines.push(":::");
|
|
6876
|
+
} else {
|
|
6877
|
+
lines.push(`**${detailParts[0]}**`);
|
|
6878
|
+
}
|
|
6879
|
+
lines.push("");
|
|
6880
|
+
}
|
|
6881
|
+
if (groupFooterControls.length > 0) {
|
|
6882
|
+
const subtotalsMarkdown = renderGroupFooterBand(groupFooterControls, registry);
|
|
6883
|
+
if (subtotalsMarkdown) lines.push(subtotalsMarkdown);
|
|
6884
|
+
}
|
|
6885
|
+
lines.push("---");
|
|
6886
|
+
lines.push(":::");
|
|
6887
|
+
lines.push("");
|
|
6888
|
+
return lines.join("\n");
|
|
6889
|
+
}
|
|
6890
|
+
function renderGroupFooterBand(controls, registry, contentWidth) {
|
|
6891
|
+
const rows = groupByRow(controls);
|
|
6892
|
+
const entries = [];
|
|
6893
|
+
for (const row of rows) {
|
|
6894
|
+
const labels = row.controls.filter((c) => c.type === "XRLabel");
|
|
6895
|
+
if (labels.length === 0) continue;
|
|
6896
|
+
let label = "";
|
|
6897
|
+
let value = "";
|
|
6898
|
+
const sorted = [...labels].sort((a, b) => a.x - b.x);
|
|
6899
|
+
for (const ctrl of sorted) {
|
|
6900
|
+
if (isStaticLabel(ctrl)) {
|
|
6901
|
+
const text = ctrl.text.trim();
|
|
6902
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
6903
|
+
label = text;
|
|
6904
|
+
}
|
|
6905
|
+
} else if (isBound(ctrl)) {
|
|
6906
|
+
const resolved = resolveExpression(ctrl.expression, registry, label, "operation");
|
|
6907
|
+
if (resolved) {
|
|
6908
|
+
if (!resolved.startsWith("{{var|")) {
|
|
6909
|
+
if (!label) label = resolved;
|
|
6910
|
+
} else {
|
|
6911
|
+
value = resolved;
|
|
6912
|
+
}
|
|
6913
|
+
}
|
|
6914
|
+
}
|
|
6915
|
+
}
|
|
6916
|
+
if (label && value) {
|
|
6917
|
+
entries.push({ label, value });
|
|
6918
|
+
}
|
|
6919
|
+
}
|
|
6920
|
+
if (entries.length === 0) return "";
|
|
6921
|
+
const lines = [];
|
|
6922
|
+
lines.push(":::subtotals");
|
|
6923
|
+
for (const entry of entries) {
|
|
6924
|
+
lines.push(`**${entry.label}** ${entry.value}`);
|
|
6925
|
+
}
|
|
6926
|
+
lines.push(":::");
|
|
6927
|
+
lines.push("");
|
|
6928
|
+
return lines.join("\n");
|
|
6929
|
+
}
|
|
6930
|
+
function renderReportFooterBand(controls, registry) {
|
|
6931
|
+
const lines = [];
|
|
6932
|
+
const panel = controls.find((c) => c.type === "XRPanel");
|
|
6933
|
+
const thankYouLabel = controls.find(
|
|
6934
|
+
(c) => c.type === "XRLabel" && c.text.includes("Thank You")
|
|
6935
|
+
);
|
|
6936
|
+
const priceRows = [];
|
|
6937
|
+
if (panel && panel.children.length > 0) {
|
|
6938
|
+
const rows = groupByRow(panel.children, 15);
|
|
6939
|
+
for (const row of rows) {
|
|
6940
|
+
const labels = row.controls.filter((c) => c.type === "XRLabel");
|
|
6941
|
+
if (labels.length === 0) continue;
|
|
6942
|
+
let label = "";
|
|
6943
|
+
let value = "";
|
|
6944
|
+
const sorted = [...labels].sort((a, b) => a.x - b.x);
|
|
6945
|
+
for (const ctrl of sorted) {
|
|
6946
|
+
if (isStaticLabel(ctrl) && ctrl.text.includes("Estimate Price Summary")) {
|
|
6947
|
+
continue;
|
|
6948
|
+
}
|
|
6949
|
+
const isLabelPos = ctrl.x < 150;
|
|
6950
|
+
if (isStaticLabel(ctrl)) {
|
|
6951
|
+
const text = ctrl.text.trim();
|
|
6952
|
+
if (isLabelPos && text && !text.match(/^label\d+$/) && text !== "0.00") {
|
|
6953
|
+
label = text;
|
|
6954
|
+
}
|
|
6955
|
+
} else if (isBound(ctrl)) {
|
|
6956
|
+
const resolved = resolveExpression(ctrl.expression, registry, label || void 0, "workorder");
|
|
6957
|
+
if (resolved) {
|
|
6958
|
+
if (isLabelPos) {
|
|
6959
|
+
if (!label) label = resolved;
|
|
6960
|
+
} else {
|
|
6961
|
+
if (!value) value = resolved;
|
|
6962
|
+
}
|
|
6963
|
+
}
|
|
6964
|
+
}
|
|
6965
|
+
}
|
|
6966
|
+
if (label && value) {
|
|
6967
|
+
priceRows.push({ label, value });
|
|
6968
|
+
} else if (value) {
|
|
6969
|
+
priceRows.push({ label: "", value });
|
|
6970
|
+
}
|
|
6971
|
+
}
|
|
6972
|
+
}
|
|
6973
|
+
lines.push(":::columns{split:40|padX:20}");
|
|
6974
|
+
lines.push(":::col{padTop:1}");
|
|
6975
|
+
if (thankYouLabel) {
|
|
6976
|
+
lines.push(`## ***${thankYouLabel.text.trim()}***`);
|
|
6977
|
+
}
|
|
6978
|
+
lines.push(":::");
|
|
6979
|
+
lines.push(":::col{padTop:1}");
|
|
6980
|
+
lines.push(":::panel{title:Estimate Price Summary|border:solid|headerStyle:dark}");
|
|
6981
|
+
if (priceRows.length > 0) {
|
|
6982
|
+
lines.push(":::subtotals");
|
|
6983
|
+
for (const row of priceRows) {
|
|
6984
|
+
const label = row.label ? `**${row.label}**` : "";
|
|
6985
|
+
lines.push(`${label} ${row.value}`);
|
|
6986
|
+
}
|
|
6987
|
+
lines.push(":::");
|
|
6988
|
+
}
|
|
6989
|
+
lines.push(":::");
|
|
6990
|
+
lines.push(":::");
|
|
6991
|
+
lines.push(":::");
|
|
6992
|
+
lines.push("");
|
|
6993
|
+
return lines.join("\n");
|
|
6994
|
+
}
|
|
6995
|
+
function parseXmlTemplate(xmlString) {
|
|
6996
|
+
const warnings = [];
|
|
6997
|
+
const registry = new VariableRegistry();
|
|
6998
|
+
const parser = new DOMParser();
|
|
6999
|
+
const doc = parser.parseFromString(xmlString, "text/xml");
|
|
7000
|
+
const parseError = doc.querySelector("parsererror");
|
|
7001
|
+
if (parseError) {
|
|
7002
|
+
return {
|
|
7003
|
+
markdown: "",
|
|
7004
|
+
variables: [],
|
|
7005
|
+
reportName: "",
|
|
7006
|
+
warnings: ["XML parse error: " + (parseError.textContent || "Unknown error")]
|
|
7007
|
+
};
|
|
7008
|
+
}
|
|
7009
|
+
const root = doc.documentElement;
|
|
7010
|
+
const reportName = root.getAttribute("DisplayName") || root.getAttribute("Name") || "Report";
|
|
7011
|
+
const pageWidth = parseInt(root.getAttribute("PageWidth") || "850", 10);
|
|
7012
|
+
const marginsStr = root.getAttribute("Margins") || "50,50,250,50";
|
|
7013
|
+
const marginParts = marginsStr.split(",").map(Number);
|
|
7014
|
+
const leftMargin = marginParts[0] || 50;
|
|
7015
|
+
const rightMargin = marginParts[1] || 50;
|
|
7016
|
+
const contentWidth = pageWidth - leftMargin - rightMargin;
|
|
7017
|
+
const calculatedFieldNames = /* @__PURE__ */ new Set();
|
|
7018
|
+
const calcFieldsEl = root.querySelector(":scope > CalculatedFields");
|
|
7019
|
+
if (calcFieldsEl) {
|
|
7020
|
+
for (const item of Array.from(calcFieldsEl.children)) {
|
|
7021
|
+
const name = item.getAttribute("Name");
|
|
7022
|
+
if (name) calculatedFieldNames.add(name);
|
|
7023
|
+
}
|
|
7024
|
+
}
|
|
7025
|
+
const markdownSections = [];
|
|
7026
|
+
const watermarkEl = root.querySelector("Watermarks > Item1");
|
|
7027
|
+
if (watermarkEl) {
|
|
7028
|
+
const wmText = watermarkEl.getAttribute("Text");
|
|
7029
|
+
if (wmText) {
|
|
7030
|
+
markdownSections.push(`:::watermark{text:${wmText}|opacity:0.15|angle:45}`);
|
|
7031
|
+
markdownSections.push("");
|
|
7032
|
+
}
|
|
7033
|
+
}
|
|
7034
|
+
const bandsEl = root.querySelector(":scope > Bands");
|
|
7035
|
+
if (!bandsEl) {
|
|
7036
|
+
warnings.push("No <Bands> element found in the XML");
|
|
7037
|
+
return {
|
|
7038
|
+
markdown: "",
|
|
7039
|
+
variables: registry.getAll(),
|
|
7040
|
+
reportName,
|
|
7041
|
+
warnings
|
|
7042
|
+
};
|
|
7043
|
+
}
|
|
7044
|
+
let topMarginControls = [];
|
|
7045
|
+
let reportHeaderControls = [];
|
|
7046
|
+
let pageHeaderControls = [];
|
|
7047
|
+
let detailControls = [];
|
|
7048
|
+
let groupFooterControls = [];
|
|
7049
|
+
let reportFooterControls = [];
|
|
7050
|
+
for (const bandItem of Array.from(bandsEl.children)) {
|
|
7051
|
+
const ctrlType = bandItem.getAttribute("ControlType") || "";
|
|
7052
|
+
if (ctrlType === "TopMarginBand") {
|
|
7053
|
+
topMarginControls = extractControls(bandItem);
|
|
7054
|
+
} else if (ctrlType === "ReportHeaderBand") {
|
|
7055
|
+
reportHeaderControls = extractControls(bandItem);
|
|
7056
|
+
} else if (ctrlType === "PageHeaderBand") {
|
|
7057
|
+
pageHeaderControls = extractControls(bandItem);
|
|
7058
|
+
} else if (ctrlType === "DetailReportBand") {
|
|
7059
|
+
const nestedBands = bandItem.querySelector(":scope > Bands");
|
|
7060
|
+
if (nestedBands) {
|
|
7061
|
+
for (const nested of Array.from(nestedBands.children)) {
|
|
7062
|
+
const nestedType = nested.getAttribute("ControlType") || "";
|
|
7063
|
+
if (nestedType === "DetailBand") {
|
|
7064
|
+
detailControls = extractControls(nested);
|
|
7065
|
+
} else if (nestedType === "GroupFooterBand") {
|
|
7066
|
+
groupFooterControls = extractControls(nested);
|
|
7067
|
+
}
|
|
7068
|
+
}
|
|
7069
|
+
}
|
|
7070
|
+
} else if (ctrlType === "ReportFooterBand") {
|
|
7071
|
+
reportFooterControls = extractControls(bandItem);
|
|
7072
|
+
}
|
|
7073
|
+
}
|
|
7074
|
+
if (topMarginControls.length > 0) {
|
|
7075
|
+
markdownSections.push(renderTopMarginBand(topMarginControls, registry, contentWidth, calculatedFieldNames));
|
|
7076
|
+
}
|
|
7077
|
+
if (reportHeaderControls.length > 0) {
|
|
7078
|
+
markdownSections.push(
|
|
7079
|
+
renderReportHeaderBand(reportHeaderControls, registry, contentWidth)
|
|
7080
|
+
);
|
|
7081
|
+
}
|
|
7082
|
+
if (pageHeaderControls.length > 0 || detailControls.length > 0) {
|
|
7083
|
+
markdownSections.push(
|
|
7084
|
+
renderOperationsSection(
|
|
7085
|
+
pageHeaderControls,
|
|
7086
|
+
detailControls,
|
|
7087
|
+
groupFooterControls,
|
|
7088
|
+
registry)
|
|
7089
|
+
);
|
|
7090
|
+
}
|
|
7091
|
+
if (reportFooterControls.length > 0) {
|
|
7092
|
+
markdownSections.push(
|
|
7093
|
+
renderReportFooterBand(reportFooterControls, registry)
|
|
7094
|
+
);
|
|
7095
|
+
}
|
|
7096
|
+
const markdown = markdownSections.join("\n").replace(/\n{4,}/g, "\n\n\n");
|
|
7097
|
+
return {
|
|
7098
|
+
markdown,
|
|
7099
|
+
variables: registry.getAll(),
|
|
7100
|
+
reportName,
|
|
7101
|
+
warnings
|
|
7102
|
+
};
|
|
7103
|
+
}
|
|
7104
|
+
|
|
7105
|
+
export { DocumentGenerator, EditorPanel, FormFieldType, PreviewPanel, RepeatNode, SubtotalsNode, ThemeProvider, expandRepeatContent, extractVariablesFromContent, generatePdfFromMarkdown, generatePdfFromTiptap, isZeroLike, markdownToTiptap, parseXmlTemplate, suppressZeroContent, tiptapToMarkdown, useDocumentGenerator, useTheme };
|
|
4929
7106
|
//# sourceMappingURL=index.mjs.map
|
|
4930
7107
|
//# sourceMappingURL=index.mjs.map
|