@signiphi/pdf-compose 0.1.0-beta.2 → 0.1.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2657 -469
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2645 -472
- package/dist/index.mjs.map +1 -1
- package/dist/styles/index.css +346 -194
- package/package.json +5 -1
- package/src/styles/index.css +140 -68
package/dist/index.js
CHANGED
|
@@ -7,6 +7,10 @@ var StarterKit = require('@tiptap/starter-kit');
|
|
|
7
7
|
var Underline = require('@tiptap/extension-underline');
|
|
8
8
|
var TextAlign = require('@tiptap/extension-text-align');
|
|
9
9
|
var Placeholder = require('@tiptap/extension-placeholder');
|
|
10
|
+
var Table = require('@tiptap/extension-table');
|
|
11
|
+
var TableRow = require('@tiptap/extension-table-row');
|
|
12
|
+
var TableCell = require('@tiptap/extension-table-cell');
|
|
13
|
+
var TableHeader = require('@tiptap/extension-table-header');
|
|
10
14
|
var core = require('@tiptap/core');
|
|
11
15
|
var clsx = require('clsx');
|
|
12
16
|
var tailwindMerge = require('tailwind-merge');
|
|
@@ -45,13 +49,17 @@ var StarterKit__default = /*#__PURE__*/_interopDefault(StarterKit);
|
|
|
45
49
|
var Underline__default = /*#__PURE__*/_interopDefault(Underline);
|
|
46
50
|
var TextAlign__default = /*#__PURE__*/_interopDefault(TextAlign);
|
|
47
51
|
var Placeholder__default = /*#__PURE__*/_interopDefault(Placeholder);
|
|
52
|
+
var Table__default = /*#__PURE__*/_interopDefault(Table);
|
|
53
|
+
var TableRow__default = /*#__PURE__*/_interopDefault(TableRow);
|
|
54
|
+
var TableCell__default = /*#__PURE__*/_interopDefault(TableCell);
|
|
55
|
+
var TableHeader__default = /*#__PURE__*/_interopDefault(TableHeader);
|
|
48
56
|
var TooltipPrimitive__namespace = /*#__PURE__*/_interopNamespace(TooltipPrimitive);
|
|
49
57
|
var PopoverPrimitive__namespace = /*#__PURE__*/_interopNamespace(PopoverPrimitive);
|
|
50
58
|
var LabelPrimitive__namespace = /*#__PURE__*/_interopNamespace(LabelPrimitive);
|
|
51
59
|
|
|
52
60
|
var __defProp = Object.defineProperty;
|
|
53
61
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
54
|
-
var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);
|
|
62
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
55
63
|
function cn(...inputs) {
|
|
56
64
|
return tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
57
65
|
}
|
|
@@ -243,7 +251,9 @@ var VariableNode = core.Node.create({
|
|
|
243
251
|
return {
|
|
244
252
|
varName: { default: "" },
|
|
245
253
|
varLabel: { default: "" },
|
|
246
|
-
varDefault: { default: "" }
|
|
254
|
+
varDefault: { default: "" },
|
|
255
|
+
format: { default: "" },
|
|
256
|
+
suppressZero: { default: "" }
|
|
247
257
|
};
|
|
248
258
|
},
|
|
249
259
|
parseHTML() {
|
|
@@ -309,6 +319,169 @@ var VariableNode = core.Node.create({
|
|
|
309
319
|
};
|
|
310
320
|
}
|
|
311
321
|
});
|
|
322
|
+
var PanelNode = core.Node.create({
|
|
323
|
+
name: "panel",
|
|
324
|
+
group: "block",
|
|
325
|
+
content: "block+",
|
|
326
|
+
defining: true,
|
|
327
|
+
addAttributes() {
|
|
328
|
+
return {
|
|
329
|
+
title: { default: "" },
|
|
330
|
+
border: { default: "solid" },
|
|
331
|
+
headerStyle: { default: "" }
|
|
332
|
+
};
|
|
333
|
+
},
|
|
334
|
+
parseHTML() {
|
|
335
|
+
return [{
|
|
336
|
+
tag: "div[data-panel-node]",
|
|
337
|
+
contentElement: "div.panel-content"
|
|
338
|
+
}];
|
|
339
|
+
},
|
|
340
|
+
renderHTML({ node, HTMLAttributes }) {
|
|
341
|
+
const { title, border, headerStyle } = node.attrs;
|
|
342
|
+
const classes = ["panel-node"];
|
|
343
|
+
if (border === "dashed") classes.push("panel-border-dashed");
|
|
344
|
+
else if (border === "none") classes.push("panel-border-none");
|
|
345
|
+
else classes.push("panel-border-solid");
|
|
346
|
+
if (headerStyle === "dark") classes.push("panel-dark");
|
|
347
|
+
const attrs = core.mergeAttributes(
|
|
348
|
+
{ "data-panel-node": "", class: classes.join(" ") },
|
|
349
|
+
HTMLAttributes
|
|
350
|
+
);
|
|
351
|
+
if (title) {
|
|
352
|
+
return [
|
|
353
|
+
"div",
|
|
354
|
+
attrs,
|
|
355
|
+
["div", { class: "panel-title" }, title],
|
|
356
|
+
["div", { class: "panel-content" }, 0]
|
|
357
|
+
];
|
|
358
|
+
}
|
|
359
|
+
return ["div", attrs, ["div", { class: "panel-content" }, 0]];
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
var ColumnsNode = core.Node.create({
|
|
363
|
+
name: "columns",
|
|
364
|
+
group: "block",
|
|
365
|
+
content: "column+",
|
|
366
|
+
defining: true,
|
|
367
|
+
addAttributes() {
|
|
368
|
+
return {
|
|
369
|
+
split: { default: "50" },
|
|
370
|
+
padX: { default: "0" }
|
|
371
|
+
};
|
|
372
|
+
},
|
|
373
|
+
parseHTML() {
|
|
374
|
+
return [{ tag: "div[data-columns-node]" }];
|
|
375
|
+
},
|
|
376
|
+
renderHTML({ node, HTMLAttributes }) {
|
|
377
|
+
const split = node.attrs.split || "50";
|
|
378
|
+
return ["div", core.mergeAttributes(
|
|
379
|
+
{
|
|
380
|
+
"data-columns-node": "",
|
|
381
|
+
class: "columns-node",
|
|
382
|
+
style: `--xpc-col-split: ${split}`
|
|
383
|
+
},
|
|
384
|
+
HTMLAttributes
|
|
385
|
+
), 0];
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
var ColumnNode = core.Node.create({
|
|
389
|
+
name: "column",
|
|
390
|
+
group: "",
|
|
391
|
+
content: "block+",
|
|
392
|
+
defining: true,
|
|
393
|
+
addAttributes() {
|
|
394
|
+
return {
|
|
395
|
+
padTop: { default: 0 }
|
|
396
|
+
};
|
|
397
|
+
},
|
|
398
|
+
parseHTML() {
|
|
399
|
+
return [{ tag: "div[data-column-node]" }];
|
|
400
|
+
},
|
|
401
|
+
renderHTML({ HTMLAttributes }) {
|
|
402
|
+
return ["div", core.mergeAttributes(
|
|
403
|
+
{ "data-column-node": "", class: "column-node" },
|
|
404
|
+
HTMLAttributes
|
|
405
|
+
), 0];
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
function WatermarkNodeView({ node, selected }) {
|
|
409
|
+
const { text } = node.attrs;
|
|
410
|
+
return /* @__PURE__ */ jsxRuntime.jsx(react.NodeViewWrapper, { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
411
|
+
"div",
|
|
412
|
+
{
|
|
413
|
+
"data-watermark-node": "",
|
|
414
|
+
className: cn(
|
|
415
|
+
"inline-flex items-center gap-2 px-3 py-1.5 rounded-md border text-sm my-1",
|
|
416
|
+
"bg-blue-50 text-blue-600 border-blue-200",
|
|
417
|
+
selected && "ring-2 ring-primary ring-offset-1"
|
|
418
|
+
),
|
|
419
|
+
children: [
|
|
420
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Droplets, { size: 14 }),
|
|
421
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
422
|
+
"Watermark: ",
|
|
423
|
+
text || "(empty)"
|
|
424
|
+
] })
|
|
425
|
+
]
|
|
426
|
+
}
|
|
427
|
+
) });
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// src/extensions/watermark-node.ts
|
|
431
|
+
var WatermarkNode = core.Node.create({
|
|
432
|
+
name: "watermark",
|
|
433
|
+
group: "block",
|
|
434
|
+
atom: true,
|
|
435
|
+
selectable: true,
|
|
436
|
+
draggable: false,
|
|
437
|
+
addAttributes() {
|
|
438
|
+
return {
|
|
439
|
+
text: { default: "" },
|
|
440
|
+
opacity: { default: "0.15" },
|
|
441
|
+
angle: { default: "-45" }
|
|
442
|
+
};
|
|
443
|
+
},
|
|
444
|
+
parseHTML() {
|
|
445
|
+
return [{ tag: "div[data-watermark-node]" }];
|
|
446
|
+
},
|
|
447
|
+
renderHTML({ HTMLAttributes }) {
|
|
448
|
+
return ["div", core.mergeAttributes({ "data-watermark-node": "" }, HTMLAttributes)];
|
|
449
|
+
},
|
|
450
|
+
addNodeView() {
|
|
451
|
+
return react.ReactNodeViewRenderer(WatermarkNodeView);
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
var RepeatNode = core.Node.create({
|
|
455
|
+
name: "repeatBlock",
|
|
456
|
+
group: "block",
|
|
457
|
+
content: "block+",
|
|
458
|
+
addAttributes() {
|
|
459
|
+
return {
|
|
460
|
+
data: { default: "" }
|
|
461
|
+
};
|
|
462
|
+
},
|
|
463
|
+
parseHTML() {
|
|
464
|
+
return [{ tag: "div[data-repeat-node]" }];
|
|
465
|
+
},
|
|
466
|
+
renderHTML({ HTMLAttributes }) {
|
|
467
|
+
return ["div", core.mergeAttributes({ "data-repeat-node": "" }, HTMLAttributes), 0];
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
var SubtotalsNode = core.Node.create({
|
|
471
|
+
name: "subtotalsBlock",
|
|
472
|
+
group: "block",
|
|
473
|
+
content: "block+",
|
|
474
|
+
defining: true,
|
|
475
|
+
parseHTML() {
|
|
476
|
+
return [{ tag: "div[data-subtotals-node]" }];
|
|
477
|
+
},
|
|
478
|
+
renderHTML({ HTMLAttributes }) {
|
|
479
|
+
return ["div", core.mergeAttributes(
|
|
480
|
+
{ "data-subtotals-node": "", class: "subtotals-block" },
|
|
481
|
+
HTMLAttributes
|
|
482
|
+
), 0];
|
|
483
|
+
}
|
|
484
|
+
});
|
|
312
485
|
|
|
313
486
|
// src/types/index.ts
|
|
314
487
|
var FormFieldType = /* @__PURE__ */ ((FormFieldType2) => {
|
|
@@ -411,11 +584,24 @@ function extractVariablesFromContent(content) {
|
|
|
411
584
|
}
|
|
412
585
|
return Array.from(seen.values());
|
|
413
586
|
}
|
|
587
|
+
function applyFormat(value, format) {
|
|
588
|
+
if (format === "phone") {
|
|
589
|
+
const digits = value.replace(/\D/g, "");
|
|
590
|
+
if (digits.length === 10) {
|
|
591
|
+
return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)} - ${digits.slice(6)}`;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return value;
|
|
595
|
+
}
|
|
414
596
|
function replaceVariablesInContent(content, values) {
|
|
415
597
|
function walkNode(node) {
|
|
416
598
|
if (node.type === "variableNode" && node.attrs) {
|
|
417
599
|
const varName = node.attrs.varName;
|
|
418
|
-
|
|
600
|
+
let replacement = values[varName] || node.attrs.varDefault || "";
|
|
601
|
+
const format = node.attrs.format;
|
|
602
|
+
if (format && replacement) {
|
|
603
|
+
replacement = applyFormat(replacement, format);
|
|
604
|
+
}
|
|
419
605
|
const textNode = { type: "text", text: replacement };
|
|
420
606
|
if (node.marks && node.marks.length > 0) {
|
|
421
607
|
textNode.marks = node.marks;
|
|
@@ -429,116 +615,143 @@ function replaceVariablesInContent(content, values) {
|
|
|
429
615
|
}
|
|
430
616
|
return walkNode(content);
|
|
431
617
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
function serializeFieldToken(attrs) {
|
|
435
|
-
const parts = ["field"];
|
|
436
|
-
if (attrs.fieldType) parts.push(`type:${attrs.fieldType}`);
|
|
437
|
-
if (attrs.fieldName) parts.push(`name:${attrs.fieldName}`);
|
|
438
|
-
if (attrs.fieldLabel) parts.push(`label:${attrs.fieldLabel}`);
|
|
439
|
-
if (attrs.fieldId) parts.push(`id:${attrs.fieldId}`);
|
|
440
|
-
if (attrs.required) parts.push(`required:true`);
|
|
441
|
-
if (attrs.options) parts.push(`options:${attrs.options}`);
|
|
442
|
-
if (attrs.fontSize) parts.push(`fontSize:${attrs.fontSize}`);
|
|
443
|
-
if (attrs.placeholder) parts.push(`placeholder:${attrs.placeholder}`);
|
|
444
|
-
if (attrs.defaultValue) parts.push(`defaultValue:${attrs.defaultValue}`);
|
|
445
|
-
if (attrs.multiline) parts.push(`multiline:true`);
|
|
446
|
-
if (attrs.maxLength) parts.push(`maxLength:${attrs.maxLength}`);
|
|
447
|
-
if (attrs.acknowledgements) parts.push(`acks:${btoa(attrs.acknowledgements)}`);
|
|
448
|
-
return `{{${parts.join("|")}}}`;
|
|
618
|
+
function labelToVarName(label) {
|
|
619
|
+
return label.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
449
620
|
}
|
|
450
|
-
function
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
if (attrs.varDefault) parts.push(`default:${attrs.varDefault}`);
|
|
455
|
-
return `{{${parts.join("|")}}}`;
|
|
621
|
+
function isZeroLike(value) {
|
|
622
|
+
if (!value || value.trim() === "") return true;
|
|
623
|
+
const cleaned = value.replace(/[$,\s]/g, "");
|
|
624
|
+
return /^-?0+(\.0+)?$/.test(cleaned);
|
|
456
625
|
}
|
|
457
|
-
function
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
continue;
|
|
626
|
+
function suppressZeroContent(content, values) {
|
|
627
|
+
function hasSuppressedVar(node) {
|
|
628
|
+
if (node.type === "variableNode" && node.attrs?.suppressZero === "true") {
|
|
629
|
+
const varName = node.attrs.varName;
|
|
630
|
+
const val = values[varName] || node.attrs.varDefault || "";
|
|
631
|
+
return isZeroLike(val);
|
|
464
632
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
633
|
+
return (node.content || []).some(hasSuppressedVar);
|
|
634
|
+
}
|
|
635
|
+
function walkNode(node) {
|
|
636
|
+
if (node.type === "table" && node.content) {
|
|
637
|
+
const filtered = node.content.filter((row) => {
|
|
638
|
+
if (row.type !== "tableRow") return true;
|
|
639
|
+
const isHeader = row.content?.[0]?.type === "tableHeader";
|
|
640
|
+
if (isHeader) return true;
|
|
641
|
+
return !hasSuppressedVar(row);
|
|
642
|
+
});
|
|
643
|
+
const bodyRows = filtered.filter(
|
|
644
|
+
(r) => r.type === "tableRow" && r.content?.[0]?.type !== "tableHeader"
|
|
645
|
+
);
|
|
646
|
+
if (bodyRows.length === 0) return null;
|
|
647
|
+
return { ...node, content: filtered };
|
|
477
648
|
}
|
|
478
|
-
if (node.type === "
|
|
479
|
-
|
|
480
|
-
const marks = node.marks || [];
|
|
481
|
-
for (const mark of marks) {
|
|
482
|
-
if (mark.type === "bold") text = `**${text}**`;
|
|
483
|
-
else if (mark.type === "italic") text = `*${text}*`;
|
|
484
|
-
else if (mark.type === "underline") text = `__${text}__`;
|
|
485
|
-
else if (mark.type === "code") text = `\`${text}\``;
|
|
486
|
-
}
|
|
487
|
-
result += text;
|
|
649
|
+
if (node.type === "paragraph" && hasSuppressedVar(node)) {
|
|
650
|
+
return null;
|
|
488
651
|
}
|
|
489
|
-
if (node.
|
|
490
|
-
|
|
652
|
+
if (node.content) {
|
|
653
|
+
const filtered = node.content.map((child) => walkNode(child)).filter((c) => c !== null);
|
|
654
|
+
return { ...node, content: filtered };
|
|
491
655
|
}
|
|
656
|
+
return node;
|
|
492
657
|
}
|
|
493
|
-
return
|
|
658
|
+
return walkNode(content) || content;
|
|
494
659
|
}
|
|
495
|
-
function
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const inline = serializeInline(node.content);
|
|
504
|
-
return inline;
|
|
505
|
-
}
|
|
506
|
-
case "bulletList": {
|
|
507
|
-
return (node.content || []).map((item) => {
|
|
508
|
-
const inner = (item.content || []).map(serializeBlock).join("\n");
|
|
509
|
-
return `- ${inner}`;
|
|
510
|
-
}).join("\n");
|
|
511
|
-
}
|
|
512
|
-
case "orderedList": {
|
|
513
|
-
return (node.content || []).map((item, i) => {
|
|
514
|
-
const inner = (item.content || []).map(serializeBlock).join("\n");
|
|
515
|
-
return `${i + 1}. ${inner}`;
|
|
516
|
-
}).join("\n");
|
|
517
|
-
}
|
|
518
|
-
case "listItem": {
|
|
519
|
-
return (node.content || []).map(serializeBlock).join("\n");
|
|
660
|
+
function expandRepeatContent(content, values) {
|
|
661
|
+
const enrichedValues = { ...values };
|
|
662
|
+
function cloneNode(node, dataKey, index) {
|
|
663
|
+
if (node.type === "variableNode" && node.attrs?.varName) {
|
|
664
|
+
const oldName = node.attrs.varName;
|
|
665
|
+
const bareName = oldName.replace(/^operation_/, "");
|
|
666
|
+
const newName = `${dataKey}_${index}_${bareName}`;
|
|
667
|
+
return { ...node, attrs: { ...node.attrs, varName: newName } };
|
|
520
668
|
}
|
|
521
|
-
|
|
522
|
-
return
|
|
669
|
+
if (node.content) {
|
|
670
|
+
return { ...node, content: node.content.map((n) => cloneNode(n, dataKey, index)) };
|
|
523
671
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
672
|
+
return { ...node };
|
|
673
|
+
}
|
|
674
|
+
function walkNode(node) {
|
|
675
|
+
if (node.type === "repeatBlock" && node.attrs?.data) {
|
|
676
|
+
const dataKey = node.attrs.data;
|
|
677
|
+
const arrayJson = values[dataKey];
|
|
678
|
+
if (!arrayJson) return [];
|
|
679
|
+
let items;
|
|
680
|
+
try {
|
|
681
|
+
items = JSON.parse(arrayJson);
|
|
682
|
+
} catch {
|
|
683
|
+
return [];
|
|
684
|
+
}
|
|
685
|
+
const sums = /* @__PURE__ */ new Map();
|
|
686
|
+
for (const item of items) {
|
|
687
|
+
for (const [key, val] of Object.entries(item)) {
|
|
688
|
+
const num = parseFloat(val);
|
|
689
|
+
if (!isNaN(num)) {
|
|
690
|
+
sums.set(key, (sums.get(key) || 0) + num);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
const woKeysByNorm = /* @__PURE__ */ new Map();
|
|
695
|
+
for (const k of Object.keys(enrichedValues)) {
|
|
696
|
+
if (k.startsWith("workorder_")) {
|
|
697
|
+
const norm = k.replace(/_/g, "");
|
|
698
|
+
woKeysByNorm.set(norm, k);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
for (const [field, total] of sums) {
|
|
702
|
+
const formatted = total % 1 === 0 ? total.toFixed(2) : total.toFixed(2);
|
|
703
|
+
const directKey = `workorder_${field}`;
|
|
704
|
+
enrichedValues[directKey] = formatted;
|
|
705
|
+
const norm = `workorder${field}`.replace(/_/g, "");
|
|
706
|
+
const existing = woKeysByNorm.get(norm);
|
|
707
|
+
if (existing && existing !== directKey) {
|
|
708
|
+
enrichedValues[existing] = formatted;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
const expanded = [];
|
|
712
|
+
for (let i = 0; i < items.length; i++) {
|
|
713
|
+
const item = items[i];
|
|
714
|
+
for (const [key, val] of Object.entries(item)) {
|
|
715
|
+
enrichedValues[`${dataKey}_${i}_${key}`] = String(val);
|
|
716
|
+
}
|
|
717
|
+
const cloned = (node.content || []).map((n) => cloneNode(n, dataKey, i));
|
|
718
|
+
expanded.push(...cloned);
|
|
719
|
+
}
|
|
720
|
+
return expanded;
|
|
529
721
|
}
|
|
530
|
-
|
|
531
|
-
|
|
722
|
+
if (node.content) {
|
|
723
|
+
const newContent = [];
|
|
724
|
+
for (const child of node.content) {
|
|
725
|
+
const result2 = walkNode(child);
|
|
726
|
+
if (Array.isArray(result2)) newContent.push(...result2);
|
|
727
|
+
else newContent.push(result2);
|
|
728
|
+
}
|
|
729
|
+
return { ...node, content: newContent };
|
|
532
730
|
}
|
|
533
|
-
|
|
534
|
-
return serializeInline(node.content);
|
|
731
|
+
return node;
|
|
535
732
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
733
|
+
const result = walkNode(content);
|
|
734
|
+
return {
|
|
735
|
+
content: Array.isArray(result) ? { ...content, content: result } : result,
|
|
736
|
+
values: enrichedValues
|
|
737
|
+
};
|
|
540
738
|
}
|
|
541
739
|
var TOKEN_REGEX = /\{\{(field|var)\|([^}]+)\}\}/g;
|
|
740
|
+
var DIRECTIVE_OPEN = /^:::(panel|columns|col|watermark|repeat|subtotals)(?:\{([^}]*)\})?$/;
|
|
741
|
+
var DIRECTIVE_CLOSE = /^:::$/;
|
|
742
|
+
function parseDirectiveAttrs(str) {
|
|
743
|
+
const attrs = {};
|
|
744
|
+
if (!str) return attrs;
|
|
745
|
+
const pairs = str.split("|");
|
|
746
|
+
for (const pair of pairs) {
|
|
747
|
+
const colonIdx = pair.indexOf(":");
|
|
748
|
+
if (colonIdx === -1) continue;
|
|
749
|
+
const key = pair.slice(0, colonIdx).trim();
|
|
750
|
+
const value = pair.slice(colonIdx + 1).trim();
|
|
751
|
+
if (key) attrs[key] = value;
|
|
752
|
+
}
|
|
753
|
+
return attrs;
|
|
754
|
+
}
|
|
542
755
|
function injectMarks(token, marks) {
|
|
543
756
|
const existingMatch = token.match(/\|_marks:([^}|]+)/);
|
|
544
757
|
if (existingMatch) {
|
|
@@ -655,6 +868,12 @@ function parseVariableToken(tokenBody) {
|
|
|
655
868
|
case "default":
|
|
656
869
|
attrs.varDefault = value;
|
|
657
870
|
break;
|
|
871
|
+
case "suppress":
|
|
872
|
+
attrs.suppressZero = value === "zero" ? "true" : "";
|
|
873
|
+
break;
|
|
874
|
+
case "format":
|
|
875
|
+
attrs.format = value;
|
|
876
|
+
break;
|
|
658
877
|
case "_marks":
|
|
659
878
|
attrs._marks = value;
|
|
660
879
|
break;
|
|
@@ -696,45 +915,63 @@ function parseInline(text) {
|
|
|
696
915
|
function parseFormattedText(text) {
|
|
697
916
|
if (!text) return [];
|
|
698
917
|
const nodes = [];
|
|
699
|
-
const parts = text.split(/(
|
|
918
|
+
const parts = text.split(/(\*\*\*[^*]+\*\*\*|\*\*[^*]+\*\*|\*[^*]+\*|__[^_]+__|`[^`]+`)/);
|
|
700
919
|
for (const part of parts) {
|
|
701
920
|
if (!part) continue;
|
|
702
|
-
if (part.startsWith("
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
});
|
|
921
|
+
if (part.startsWith("***") && part.endsWith("***")) {
|
|
922
|
+
const inner = part.slice(3, -3);
|
|
923
|
+
if (inner) nodes.push({ type: "text", text: inner, marks: [{ type: "bold" }, { type: "italic" }] });
|
|
924
|
+
} else if (part.startsWith("**") && part.endsWith("**")) {
|
|
925
|
+
const inner = part.slice(2, -2);
|
|
926
|
+
if (inner) nodes.push({ type: "text", text: inner, marks: [{ type: "bold" }] });
|
|
708
927
|
} else if (part.startsWith("*") && part.endsWith("*")) {
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
text: part.slice(1, -1),
|
|
712
|
-
marks: [{ type: "italic" }]
|
|
713
|
-
});
|
|
928
|
+
const inner = part.slice(1, -1);
|
|
929
|
+
if (inner) nodes.push({ type: "text", text: inner, marks: [{ type: "italic" }] });
|
|
714
930
|
} else if (part.startsWith("__") && part.endsWith("__")) {
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
text: part.slice(2, -2),
|
|
718
|
-
marks: [{ type: "underline" }]
|
|
719
|
-
});
|
|
931
|
+
const inner = part.slice(2, -2);
|
|
932
|
+
if (inner) nodes.push({ type: "text", text: inner, marks: [{ type: "underline" }] });
|
|
720
933
|
} else if (part.startsWith("`") && part.endsWith("`")) {
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
text: part.slice(1, -1),
|
|
724
|
-
marks: [{ type: "code" }]
|
|
725
|
-
});
|
|
934
|
+
const inner = part.slice(1, -1);
|
|
935
|
+
if (inner) nodes.push({ type: "text", text: inner, marks: [{ type: "code" }] });
|
|
726
936
|
} else {
|
|
727
937
|
nodes.push({ type: "text", text: part });
|
|
728
938
|
}
|
|
729
939
|
}
|
|
730
940
|
return nodes;
|
|
731
941
|
}
|
|
732
|
-
function
|
|
733
|
-
const
|
|
942
|
+
function splitTableCells(row) {
|
|
943
|
+
const cells = [];
|
|
944
|
+
let current = "";
|
|
945
|
+
let depth = 0;
|
|
946
|
+
for (let i = 0; i < row.length; i++) {
|
|
947
|
+
if (row[i] === "{" && row[i + 1] === "{") {
|
|
948
|
+
depth++;
|
|
949
|
+
current += "{{";
|
|
950
|
+
i++;
|
|
951
|
+
} else if (row[i] === "}" && row[i + 1] === "}") {
|
|
952
|
+
depth = Math.max(0, depth - 1);
|
|
953
|
+
current += "}}";
|
|
954
|
+
i++;
|
|
955
|
+
} else if (row[i] === "|" && depth === 0) {
|
|
956
|
+
cells.push(current.trim());
|
|
957
|
+
current = "";
|
|
958
|
+
} else {
|
|
959
|
+
current += row[i];
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
if (current.trim()) {
|
|
963
|
+
cells.push(current.trim());
|
|
964
|
+
}
|
|
965
|
+
return cells;
|
|
966
|
+
}
|
|
967
|
+
function parseBlocks(lines, startIdx, stopCondition) {
|
|
734
968
|
const content = [];
|
|
735
|
-
let i =
|
|
969
|
+
let i = startIdx;
|
|
736
970
|
while (i < lines.length) {
|
|
737
971
|
const line = lines[i];
|
|
972
|
+
if (stopCondition && stopCondition(line)) {
|
|
973
|
+
break;
|
|
974
|
+
}
|
|
738
975
|
if (line.trim() === "") {
|
|
739
976
|
i++;
|
|
740
977
|
continue;
|
|
@@ -808,41 +1045,218 @@ function markdownToTiptap(markdown) {
|
|
|
808
1045
|
});
|
|
809
1046
|
continue;
|
|
810
1047
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
|
|
1048
|
+
if (line.trimStart().startsWith("|") && line.trimEnd().endsWith("|")) {
|
|
1049
|
+
const tableLines = [];
|
|
1050
|
+
while (i < lines.length) {
|
|
1051
|
+
const tl = lines[i];
|
|
1052
|
+
if (tl.trimStart().startsWith("|") && tl.trimEnd().endsWith("|")) {
|
|
1053
|
+
tableLines.push(tl);
|
|
1054
|
+
i++;
|
|
1055
|
+
} else {
|
|
1056
|
+
break;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
const separatorRegex = /^[\s|]*-+[\s|:-]*$/;
|
|
1060
|
+
const dataLines = tableLines.filter(
|
|
1061
|
+
(tl) => !separatorRegex.test(tl.replace(/^\s*\|/, "").replace(/\|\s*$/, ""))
|
|
1062
|
+
);
|
|
1063
|
+
const firstDataCellsRaw = dataLines.length > 0 ? splitTableCells(dataLines[0]) : [];
|
|
1064
|
+
const isSubtotals = firstDataCellsRaw.length >= 2 && firstDataCellsRaw.every((c) => c.trim() === "_");
|
|
1065
|
+
const isBorderless = isSubtotals || firstDataCellsRaw.length >= 2 && firstDataCellsRaw.every((c) => c.trim() === "");
|
|
1066
|
+
const tableRows = [];
|
|
1067
|
+
let isFirstDataRow = true;
|
|
1068
|
+
for (const tl of tableLines) {
|
|
1069
|
+
if (separatorRegex.test(tl.replace(/^\s*\|/, "").replace(/\|\s*$/, ""))) continue;
|
|
1070
|
+
const cells = splitTableCells(tl).filter((c) => c !== "");
|
|
1071
|
+
if (isBorderless && isFirstDataRow) {
|
|
1072
|
+
isFirstDataRow = false;
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
const cellType = isFirstDataRow && !isBorderless ? "tableHeader" : "tableCell";
|
|
1076
|
+
const rowContent = cells.map((cellText) => ({
|
|
1077
|
+
type: cellType,
|
|
1078
|
+
content: [{ type: "paragraph", content: cellText ? parseInline(cellText) : [] }]
|
|
1079
|
+
}));
|
|
1080
|
+
tableRows.push({ type: "tableRow", content: rowContent });
|
|
1081
|
+
isFirstDataRow = false;
|
|
1082
|
+
}
|
|
1083
|
+
if (tableRows.length > 0) {
|
|
1084
|
+
const tableNode = { type: "table", content: tableRows };
|
|
1085
|
+
if (isBorderless) {
|
|
1086
|
+
tableNode.attrs = { borderless: true, ...isSubtotals ? { subtotals: true } : {} };
|
|
1087
|
+
}
|
|
1088
|
+
content.push(tableNode);
|
|
1089
|
+
}
|
|
1090
|
+
continue;
|
|
1091
|
+
}
|
|
1092
|
+
const directiveMatch = line.trim().match(DIRECTIVE_OPEN);
|
|
1093
|
+
if (directiveMatch) {
|
|
1094
|
+
const directiveType = directiveMatch[1];
|
|
1095
|
+
const rawAttrs = directiveMatch[2] || "";
|
|
1096
|
+
const attrs = parseDirectiveAttrs(rawAttrs);
|
|
1097
|
+
i++;
|
|
1098
|
+
if (directiveType === "watermark") {
|
|
1099
|
+
content.push({
|
|
1100
|
+
type: "watermark",
|
|
1101
|
+
attrs: {
|
|
1102
|
+
text: attrs.text || "",
|
|
1103
|
+
opacity: attrs.opacity || "0.15",
|
|
1104
|
+
angle: attrs.angle || "-45"
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1109
|
+
if (directiveType === "panel") {
|
|
1110
|
+
const inner = parseBlocks(lines, i, (l) => DIRECTIVE_CLOSE.test(l.trim()));
|
|
1111
|
+
i = inner.nextIdx;
|
|
1112
|
+
if (i < lines.length && DIRECTIVE_CLOSE.test(lines[i].trim())) {
|
|
1113
|
+
i++;
|
|
1114
|
+
}
|
|
1115
|
+
content.push({
|
|
1116
|
+
type: "panel",
|
|
1117
|
+
attrs: {
|
|
1118
|
+
title: attrs.title || "",
|
|
1119
|
+
border: attrs.border || "solid",
|
|
1120
|
+
headerStyle: attrs.headerStyle || ""
|
|
1121
|
+
},
|
|
1122
|
+
content: inner.blocks.length > 0 ? inner.blocks : [{ type: "paragraph" }]
|
|
1123
|
+
});
|
|
1124
|
+
continue;
|
|
1125
|
+
}
|
|
1126
|
+
if (directiveType === "repeat") {
|
|
1127
|
+
const inner = parseBlocks(lines, i, (l) => DIRECTIVE_CLOSE.test(l.trim()));
|
|
1128
|
+
i = inner.nextIdx;
|
|
1129
|
+
if (i < lines.length && DIRECTIVE_CLOSE.test(lines[i].trim())) {
|
|
1130
|
+
i++;
|
|
1131
|
+
}
|
|
1132
|
+
content.push({
|
|
1133
|
+
type: "repeatBlock",
|
|
1134
|
+
attrs: { data: attrs.data || "" },
|
|
1135
|
+
content: inner.blocks.length > 0 ? inner.blocks : [{ type: "paragraph" }]
|
|
1136
|
+
});
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
if (directiveType === "subtotals") {
|
|
1140
|
+
const inner = parseBlocks(lines, i, (l) => DIRECTIVE_CLOSE.test(l.trim()));
|
|
1141
|
+
i = inner.nextIdx;
|
|
1142
|
+
if (i < lines.length && DIRECTIVE_CLOSE.test(lines[i].trim())) {
|
|
1143
|
+
i++;
|
|
1144
|
+
}
|
|
1145
|
+
content.push({
|
|
1146
|
+
type: "subtotalsBlock",
|
|
1147
|
+
content: inner.blocks.length > 0 ? inner.blocks : [{ type: "paragraph" }]
|
|
1148
|
+
});
|
|
1149
|
+
continue;
|
|
1150
|
+
}
|
|
1151
|
+
if (directiveType === "columns") {
|
|
1152
|
+
const splitVal = attrs.split || "50";
|
|
1153
|
+
const columns = [];
|
|
1154
|
+
while (i < lines.length) {
|
|
1155
|
+
const colLine = lines[i].trim();
|
|
1156
|
+
if (DIRECTIVE_CLOSE.test(colLine)) {
|
|
1157
|
+
if (columns.length > 0) {
|
|
1158
|
+
i++;
|
|
1159
|
+
break;
|
|
1160
|
+
}
|
|
1161
|
+
i++;
|
|
1162
|
+
break;
|
|
1163
|
+
}
|
|
1164
|
+
const colMatch = colLine.match(/^:::col(?:\{([^}]*)\})?$/);
|
|
1165
|
+
if (colMatch) {
|
|
1166
|
+
i++;
|
|
1167
|
+
const colInner = parseBlocks(lines, i, (l) => {
|
|
1168
|
+
const trimmed = l.trim();
|
|
1169
|
+
return DIRECTIVE_CLOSE.test(trimmed) || /^:::col(?:\{[^}]*\})?$/.test(trimmed);
|
|
1170
|
+
});
|
|
1171
|
+
i = colInner.nextIdx;
|
|
1172
|
+
if (i < lines.length && DIRECTIVE_CLOSE.test(lines[i].trim())) {
|
|
1173
|
+
i++;
|
|
1174
|
+
}
|
|
1175
|
+
const colAttrsRaw = colMatch[1] || "";
|
|
1176
|
+
const colAttrs = parseDirectiveAttrs(colAttrsRaw);
|
|
1177
|
+
const padTop = parseFloat(colAttrs.padTop || "0") || 0;
|
|
1178
|
+
columns.push({
|
|
1179
|
+
type: "column",
|
|
1180
|
+
attrs: padTop ? { padTop } : void 0,
|
|
1181
|
+
content: colInner.blocks.length > 0 ? colInner.blocks : [{ type: "paragraph" }]
|
|
1182
|
+
});
|
|
1183
|
+
continue;
|
|
1184
|
+
}
|
|
1185
|
+
if (colLine === "") {
|
|
1186
|
+
i++;
|
|
1187
|
+
continue;
|
|
1188
|
+
}
|
|
1189
|
+
break;
|
|
1190
|
+
}
|
|
1191
|
+
const columnsAttrs = { split: splitVal };
|
|
1192
|
+
if (attrs.padX) columnsAttrs.padX = attrs.padX;
|
|
1193
|
+
content.push({
|
|
1194
|
+
type: "columns",
|
|
1195
|
+
attrs: columnsAttrs,
|
|
1196
|
+
content: columns.length > 0 ? columns : [
|
|
1197
|
+
{ type: "column", content: [{ type: "paragraph" }] },
|
|
1198
|
+
{ type: "column", content: [{ type: "paragraph" }] }
|
|
1199
|
+
]
|
|
1200
|
+
});
|
|
1201
|
+
continue;
|
|
1202
|
+
}
|
|
1203
|
+
if (directiveType === "col") {
|
|
1204
|
+
const inner = parseBlocks(lines, i, (l) => DIRECTIVE_CLOSE.test(l.trim()));
|
|
1205
|
+
i = inner.nextIdx;
|
|
1206
|
+
if (i < lines.length && DIRECTIVE_CLOSE.test(lines[i].trim())) {
|
|
1207
|
+
i++;
|
|
1208
|
+
}
|
|
1209
|
+
for (const block of inner.blocks) {
|
|
1210
|
+
content.push(block);
|
|
1211
|
+
}
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
continue;
|
|
1215
|
+
}
|
|
1216
|
+
if (DIRECTIVE_CLOSE.test(line.trim())) {
|
|
1217
|
+
i++;
|
|
1218
|
+
continue;
|
|
1219
|
+
}
|
|
1220
|
+
content.push({
|
|
1221
|
+
type: "paragraph",
|
|
1222
|
+
content: parseInline(line)
|
|
1223
|
+
});
|
|
1224
|
+
i++;
|
|
1225
|
+
}
|
|
1226
|
+
return { blocks: content, nextIdx: i };
|
|
1227
|
+
}
|
|
1228
|
+
function markdownToTiptap(markdown) {
|
|
1229
|
+
const lines = markdown.split("\n");
|
|
1230
|
+
const { blocks } = parseBlocks(lines, 0);
|
|
1231
|
+
return { type: "doc", content: blocks };
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// src/utils/error-helpers.ts
|
|
1235
|
+
function getErrorMessage(error) {
|
|
1236
|
+
if (error instanceof Error) return error.message;
|
|
1237
|
+
if (typeof error === "string") return error;
|
|
1238
|
+
try {
|
|
1239
|
+
return JSON.stringify(error);
|
|
1240
|
+
} catch {
|
|
1241
|
+
return String(error);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
function formatError(context, error) {
|
|
1245
|
+
const message = getErrorMessage(error);
|
|
1246
|
+
return `${context}: ${message}`;
|
|
1247
|
+
}
|
|
1248
|
+
var CONTEXT_CHARS = 25;
|
|
1249
|
+
function extractPosition(error, inputLength) {
|
|
1250
|
+
const msg = error.message;
|
|
1251
|
+
const v8 = /position\s+(\d+)/i.exec(msg);
|
|
1252
|
+
if (v8) return Number(v8[1]);
|
|
1253
|
+
if (/unexpected end/i.test(msg) || /unterminated/i.test(msg)) {
|
|
1254
|
+
return inputLength > 0 ? inputLength - 1 : 0;
|
|
1255
|
+
}
|
|
1256
|
+
return null;
|
|
1257
|
+
}
|
|
1258
|
+
function stripNativePosition(msg) {
|
|
1259
|
+
const v8Stripped = msg.replace(/\s+in JSON at position\s+\d+(\s*\(line\s+\d+\s+column\s+\d+\))?/i, "");
|
|
846
1260
|
if (v8Stripped !== msg) return v8Stripped;
|
|
847
1261
|
const ffStripped = msg.replace(/\s+at line\s+\d+\s+column\s+\d+\s+of the JSON data/i, "");
|
|
848
1262
|
if (ffStripped !== msg) return ffStripped;
|
|
@@ -952,10 +1366,12 @@ function buildMetadataObject(fields, actualFieldNames) {
|
|
|
952
1366
|
// src/utils/pdf-generator.ts
|
|
953
1367
|
var PAGE_WIDTH = 595.28;
|
|
954
1368
|
var PAGE_HEIGHT = 841.89;
|
|
955
|
-
var MARGIN =
|
|
1369
|
+
var MARGIN = 40;
|
|
956
1370
|
var CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN;
|
|
957
1371
|
var CONTENT_HEIGHT = PAGE_HEIGHT - 2 * MARGIN;
|
|
958
1372
|
var LINE_HEIGHT_FACTOR = 1.4;
|
|
1373
|
+
var BODY_FONT_SIZE = 10;
|
|
1374
|
+
var DEFAULT_REGION = { leftX: MARGIN, width: CONTENT_WIDTH };
|
|
959
1375
|
var FIELD_DISPLAY = {
|
|
960
1376
|
["text" /* TEXT */]: { label: "Text", width: 120, height: 30 },
|
|
961
1377
|
["signature" /* SIGNATURE */]: { label: "Signature", width: 200, height: 60 },
|
|
@@ -992,11 +1408,12 @@ function ensureSpace(state, neededHeight) {
|
|
|
992
1408
|
state.currentPage = newPage;
|
|
993
1409
|
state.pageIndex++;
|
|
994
1410
|
state.y = 0;
|
|
1411
|
+
renderWatermarksOnPage(newPage, state.fonts, state.watermarkNodes);
|
|
995
1412
|
}
|
|
996
1413
|
}
|
|
997
|
-
function drawText(state, text, font, fontSize, indent = 0) {
|
|
1414
|
+
function drawText(state, text, font, fontSize, indent = 0, region = DEFAULT_REGION) {
|
|
998
1415
|
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
999
|
-
const maxWidth =
|
|
1416
|
+
const maxWidth = region.width - indent;
|
|
1000
1417
|
const words = text.split(/\s+/);
|
|
1001
1418
|
let line = "";
|
|
1002
1419
|
let totalAdvance = 0;
|
|
@@ -1007,7 +1424,7 @@ function drawText(state, text, font, fontSize, indent = 0) {
|
|
|
1007
1424
|
ensureSpace(state, lineHeight);
|
|
1008
1425
|
const pdfY = PAGE_HEIGHT - MARGIN - state.y - fontSize;
|
|
1009
1426
|
state.currentPage.drawText(line, {
|
|
1010
|
-
x:
|
|
1427
|
+
x: region.leftX + indent,
|
|
1011
1428
|
y: pdfY,
|
|
1012
1429
|
size: fontSize,
|
|
1013
1430
|
font,
|
|
@@ -1024,7 +1441,7 @@ function drawText(state, text, font, fontSize, indent = 0) {
|
|
|
1024
1441
|
ensureSpace(state, lineHeight);
|
|
1025
1442
|
const pdfY = PAGE_HEIGHT - MARGIN - state.y - fontSize;
|
|
1026
1443
|
state.currentPage.drawText(line, {
|
|
1027
|
-
x:
|
|
1444
|
+
x: region.leftX + indent,
|
|
1028
1445
|
y: pdfY,
|
|
1029
1446
|
size: fontSize,
|
|
1030
1447
|
font,
|
|
@@ -1068,10 +1485,10 @@ function collectInlineSegments(content, fonts, fontSize) {
|
|
|
1068
1485
|
}
|
|
1069
1486
|
return segments;
|
|
1070
1487
|
}
|
|
1071
|
-
function layoutInlineSegments(state, segments, fontSize, indent = 0) {
|
|
1488
|
+
function layoutInlineSegments(state, segments, fontSize, indent = 0, region = DEFAULT_REGION) {
|
|
1072
1489
|
const textLineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
1073
|
-
const maxX =
|
|
1074
|
-
const startX =
|
|
1490
|
+
const maxX = region.leftX + region.width;
|
|
1491
|
+
const startX = region.leftX + indent;
|
|
1075
1492
|
let currentX = startX;
|
|
1076
1493
|
let currentLineHeight = 0;
|
|
1077
1494
|
let lineHasContent = false;
|
|
@@ -1176,13 +1593,316 @@ function layoutInlineSegments(state, segments, fontSize, indent = 0) {
|
|
|
1176
1593
|
}
|
|
1177
1594
|
flushLine();
|
|
1178
1595
|
}
|
|
1179
|
-
function processInlineContent(state, content, fontSize, indent = 0) {
|
|
1596
|
+
function processInlineContent(state, content, fontSize, indent = 0, region = DEFAULT_REGION) {
|
|
1180
1597
|
if (!content) return;
|
|
1181
1598
|
const segments = collectInlineSegments(content, state.fonts, fontSize);
|
|
1182
1599
|
if (segments.length === 0) return;
|
|
1183
|
-
layoutInlineSegments(state, segments, fontSize, indent);
|
|
1600
|
+
layoutInlineSegments(state, segments, fontSize, indent, region);
|
|
1601
|
+
}
|
|
1602
|
+
function extractCellText(cell) {
|
|
1603
|
+
const parts = [];
|
|
1604
|
+
for (const child of cell.content || []) {
|
|
1605
|
+
if (child.type === "paragraph" || child.type === "heading") {
|
|
1606
|
+
for (const inline of child.content || []) {
|
|
1607
|
+
if (inline.type === "text") {
|
|
1608
|
+
parts.push(inline.text || "");
|
|
1609
|
+
} else if (inline.type === "variableNode") {
|
|
1610
|
+
const label = inline.attrs?.varLabel || inline.attrs?.varName || "Variable";
|
|
1611
|
+
parts.push(`[${label}]`);
|
|
1612
|
+
} else if (inline.type === "fieldNode") {
|
|
1613
|
+
const label = inline.attrs?.fieldLabel || inline.attrs?.fieldName || "Field";
|
|
1614
|
+
parts.push(`[ ${label} ]`);
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
return parts.join("");
|
|
1620
|
+
}
|
|
1621
|
+
function isNumericText(text) {
|
|
1622
|
+
return /^\s*\$?\s*[\d,.]+\s*$/.test(text);
|
|
1623
|
+
}
|
|
1624
|
+
function measureCellHeight(cell, maxWidth, fonts, fontSize, isHeader) {
|
|
1625
|
+
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
1626
|
+
const font = isHeader ? fonts.bold : fonts.regular;
|
|
1627
|
+
let totalHeight = 0;
|
|
1628
|
+
for (const child of cell.content || []) {
|
|
1629
|
+
if (child.type === "paragraph" || child.type === "heading") {
|
|
1630
|
+
const text = extractCellText({ content: [child] });
|
|
1631
|
+
if (!text) {
|
|
1632
|
+
totalHeight += lineHeight;
|
|
1633
|
+
continue;
|
|
1634
|
+
}
|
|
1635
|
+
const words = text.split(/\s+/);
|
|
1636
|
+
let line = "";
|
|
1637
|
+
let lineCount = 0;
|
|
1638
|
+
for (const word of words) {
|
|
1639
|
+
const testLine = line ? `${line} ${word}` : word;
|
|
1640
|
+
const testWidth = font.widthOfTextAtSize(testLine, fontSize);
|
|
1641
|
+
if (testWidth > maxWidth && line) {
|
|
1642
|
+
lineCount++;
|
|
1643
|
+
line = word;
|
|
1644
|
+
} else {
|
|
1645
|
+
line = testLine;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
if (line) lineCount++;
|
|
1649
|
+
totalHeight += lineCount * lineHeight;
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
return Math.max(totalHeight, lineHeight);
|
|
1653
|
+
}
|
|
1654
|
+
function renderCellContent(state, cell, cellX, cellY, cellWidth, fonts, fontSize, isHeader, rightAlign, textColor = pdfLib.rgb(0, 0, 0)) {
|
|
1655
|
+
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
1656
|
+
const cellPadding = 4;
|
|
1657
|
+
const maxTextWidth = cellWidth - cellPadding * 2;
|
|
1658
|
+
let textX = cellX + cellPadding;
|
|
1659
|
+
let textY = cellY;
|
|
1660
|
+
for (const child of cell.content || []) {
|
|
1661
|
+
if (child.type === "paragraph" || child.type === "heading") {
|
|
1662
|
+
const segments = collectInlineSegments(
|
|
1663
|
+
child.content || [],
|
|
1664
|
+
isHeader ? { regular: fonts.bold, bold: fonts.bold, italic: fonts.boldItalic, boldItalic: fonts.boldItalic } : fonts,
|
|
1665
|
+
fontSize
|
|
1666
|
+
);
|
|
1667
|
+
if (rightAlign && segments.length > 0) {
|
|
1668
|
+
const fullText = segments.filter((s) => s.kind === "text").map((s) => s.text).join("");
|
|
1669
|
+
const textWidth = (isHeader ? fonts.bold : fonts.regular).widthOfTextAtSize(fullText, fontSize);
|
|
1670
|
+
if (textWidth <= maxTextWidth) {
|
|
1671
|
+
const pdfY = PAGE_HEIGHT - textY - fontSize;
|
|
1672
|
+
state.currentPage.drawText(fullText, {
|
|
1673
|
+
x: cellX + cellWidth - cellPadding - textWidth,
|
|
1674
|
+
y: pdfY,
|
|
1675
|
+
size: fontSize,
|
|
1676
|
+
font: isHeader ? fonts.bold : fonts.regular,
|
|
1677
|
+
color: textColor
|
|
1678
|
+
});
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
let currentX = textX;
|
|
1683
|
+
for (const seg of segments) {
|
|
1684
|
+
if (seg.kind === "break") {
|
|
1685
|
+
textY += lineHeight;
|
|
1686
|
+
currentX = textX;
|
|
1687
|
+
continue;
|
|
1688
|
+
}
|
|
1689
|
+
if (seg.kind === "text") {
|
|
1690
|
+
const words = seg.text.split(/(\s+)/);
|
|
1691
|
+
const spaceWidth = seg.font.widthOfTextAtSize(" ", fontSize);
|
|
1692
|
+
for (const token of words) {
|
|
1693
|
+
if (!token) continue;
|
|
1694
|
+
if (/^\s+$/.test(token)) {
|
|
1695
|
+
currentX += spaceWidth;
|
|
1696
|
+
continue;
|
|
1697
|
+
}
|
|
1698
|
+
const wordWidth = seg.font.widthOfTextAtSize(token, fontSize);
|
|
1699
|
+
if (currentX + wordWidth > textX + maxTextWidth && currentX > textX) {
|
|
1700
|
+
textY += lineHeight;
|
|
1701
|
+
currentX = textX;
|
|
1702
|
+
}
|
|
1703
|
+
const pdfY = PAGE_HEIGHT - textY - fontSize;
|
|
1704
|
+
state.currentPage.drawText(token, {
|
|
1705
|
+
x: currentX,
|
|
1706
|
+
y: pdfY,
|
|
1707
|
+
size: fontSize,
|
|
1708
|
+
font: seg.font,
|
|
1709
|
+
color: textColor
|
|
1710
|
+
});
|
|
1711
|
+
currentX += wordWidth;
|
|
1712
|
+
}
|
|
1713
|
+
} else if (seg.kind === "field") {
|
|
1714
|
+
const pdfY = PAGE_HEIGHT - textY - seg.height;
|
|
1715
|
+
if (state.drawFieldPlaceholders) {
|
|
1716
|
+
state.currentPage.drawRectangle({
|
|
1717
|
+
x: currentX,
|
|
1718
|
+
y: pdfY,
|
|
1719
|
+
width: seg.width,
|
|
1720
|
+
height: seg.height,
|
|
1721
|
+
borderColor: pdfLib.rgb(0.5, 0.5, 0.7),
|
|
1722
|
+
borderWidth: 0.5,
|
|
1723
|
+
color: pdfLib.rgb(0.95, 0.95, 1),
|
|
1724
|
+
borderDashArray: [4, 2]
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
const fieldId = seg.attrs.fieldId;
|
|
1728
|
+
if (fieldId) {
|
|
1729
|
+
state.fieldPositions.set(fieldId, {
|
|
1730
|
+
x: currentX,
|
|
1731
|
+
y: pdfY,
|
|
1732
|
+
width: seg.width,
|
|
1733
|
+
height: seg.height,
|
|
1734
|
+
page: state.pageIndex + 1
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
currentX += seg.width + 4;
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
function renderTable(state, node, region = DEFAULT_REGION) {
|
|
1744
|
+
const tableRows = node.content || [];
|
|
1745
|
+
if (tableRows.length === 0) return;
|
|
1746
|
+
const borderless = node.attrs?.borderless === true;
|
|
1747
|
+
const subtotals = node.attrs?.subtotals === true;
|
|
1748
|
+
const fontSize = 10;
|
|
1749
|
+
const cellPadding = borderless ? 2 : 4;
|
|
1750
|
+
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
1751
|
+
const rowPadding = cellPadding * 2;
|
|
1752
|
+
const borderColor = pdfLib.rgb(0.75, 0.75, 0.75);
|
|
1753
|
+
const headerBg = pdfLib.rgb(0.3, 0.3, 0.3);
|
|
1754
|
+
const headerTextColor = pdfLib.rgb(1, 1, 1);
|
|
1755
|
+
const firstRow = tableRows[0];
|
|
1756
|
+
const colCount = firstRow.content?.length || 0;
|
|
1757
|
+
if (colCount === 0) return;
|
|
1758
|
+
const colMaxWidths = new Array(colCount).fill(0);
|
|
1759
|
+
for (const row of tableRows) {
|
|
1760
|
+
const cells = row.content || [];
|
|
1761
|
+
for (let c = 0; c < cells.length && c < colCount; c++) {
|
|
1762
|
+
const cell = cells[c];
|
|
1763
|
+
const isHeader = cell.type === "tableHeader";
|
|
1764
|
+
const text = extractCellText(cell);
|
|
1765
|
+
const font = isHeader ? state.fonts.bold : state.fonts.regular;
|
|
1766
|
+
const textWidth = font.widthOfTextAtSize(text, fontSize);
|
|
1767
|
+
colMaxWidths[c] = Math.max(colMaxWidths[c], textWidth + cellPadding * 2 + 4);
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
const totalMeasured = colMaxWidths.reduce((a, b) => a + b, 0);
|
|
1771
|
+
const minColWidth = 40;
|
|
1772
|
+
let colWidths;
|
|
1773
|
+
if (totalMeasured <= region.width) {
|
|
1774
|
+
const scale = region.width / totalMeasured;
|
|
1775
|
+
colWidths = colMaxWidths.map((w) => Math.max(w * scale, minColWidth));
|
|
1776
|
+
} else {
|
|
1777
|
+
colWidths = colMaxWidths.map(
|
|
1778
|
+
(w) => Math.max(w / totalMeasured * region.width, minColWidth)
|
|
1779
|
+
);
|
|
1780
|
+
}
|
|
1781
|
+
const colSum = colWidths.reduce((a, b) => a + b, 0);
|
|
1782
|
+
if (colSum > 0) {
|
|
1783
|
+
const normFactor = region.width / colSum;
|
|
1784
|
+
colWidths = colWidths.map((w) => w * normFactor);
|
|
1785
|
+
}
|
|
1786
|
+
const headerRow = tableRows[0];
|
|
1787
|
+
const hasHeader = headerRow?.content?.[0]?.type === "tableHeader";
|
|
1788
|
+
function renderRow(row, isHeaderRow) {
|
|
1789
|
+
const cells = row.content || [];
|
|
1790
|
+
const isHeader = isHeaderRow && hasHeader;
|
|
1791
|
+
let rowHeight = lineHeight;
|
|
1792
|
+
for (let c = 0; c < cells.length && c < colCount; c++) {
|
|
1793
|
+
const cellContentWidth = colWidths[c] - cellPadding * 2;
|
|
1794
|
+
const cellH = measureCellHeight(cells[c], cellContentWidth, state.fonts, fontSize, isHeader);
|
|
1795
|
+
rowHeight = Math.max(rowHeight, cellH);
|
|
1796
|
+
}
|
|
1797
|
+
rowHeight += rowPadding;
|
|
1798
|
+
ensureSpace(state, rowHeight);
|
|
1799
|
+
const rowTopY = state.y;
|
|
1800
|
+
let cellX = region.leftX;
|
|
1801
|
+
for (let c = 0; c < cells.length && c < colCount; c++) {
|
|
1802
|
+
const cellW = colWidths[c];
|
|
1803
|
+
const cell = cells[c];
|
|
1804
|
+
const pdfCellTop = PAGE_HEIGHT - MARGIN - rowTopY;
|
|
1805
|
+
const pdfCellBottom = pdfCellTop - rowHeight;
|
|
1806
|
+
if (isHeader && !borderless) {
|
|
1807
|
+
state.currentPage.drawRectangle({
|
|
1808
|
+
x: cellX,
|
|
1809
|
+
y: pdfCellBottom,
|
|
1810
|
+
width: cellW,
|
|
1811
|
+
height: rowHeight,
|
|
1812
|
+
color: headerBg
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1815
|
+
if (!borderless) {
|
|
1816
|
+
state.currentPage.drawRectangle({
|
|
1817
|
+
x: cellX,
|
|
1818
|
+
y: pdfCellBottom,
|
|
1819
|
+
width: cellW,
|
|
1820
|
+
height: rowHeight,
|
|
1821
|
+
borderColor,
|
|
1822
|
+
borderWidth: 0.5
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1825
|
+
const cellText = extractCellText(cell);
|
|
1826
|
+
const rightAlign = !isHeader && isNumericText(cellText);
|
|
1827
|
+
const contentY = MARGIN + rowTopY + cellPadding;
|
|
1828
|
+
const effectiveHeader = isHeader && !borderless;
|
|
1829
|
+
renderCellContent(
|
|
1830
|
+
state,
|
|
1831
|
+
cell,
|
|
1832
|
+
cellX,
|
|
1833
|
+
contentY,
|
|
1834
|
+
cellW,
|
|
1835
|
+
state.fonts,
|
|
1836
|
+
fontSize,
|
|
1837
|
+
effectiveHeader,
|
|
1838
|
+
rightAlign,
|
|
1839
|
+
effectiveHeader ? headerTextColor : pdfLib.rgb(0, 0, 0)
|
|
1840
|
+
);
|
|
1841
|
+
cellX += cellW;
|
|
1842
|
+
}
|
|
1843
|
+
state.y += rowHeight;
|
|
1844
|
+
}
|
|
1845
|
+
if (hasHeader && tableRows.length > 0) {
|
|
1846
|
+
renderRow(tableRows[0], true);
|
|
1847
|
+
}
|
|
1848
|
+
let lastPageIndex = state.pageIndex;
|
|
1849
|
+
const startIdx = hasHeader ? 1 : 0;
|
|
1850
|
+
const bodyRowCount = tableRows.length - startIdx;
|
|
1851
|
+
for (let r = startIdx; r < tableRows.length; r++) {
|
|
1852
|
+
const cells = tableRows[r].content || [];
|
|
1853
|
+
let estRowHeight = lineHeight;
|
|
1854
|
+
for (let c = 0; c < cells.length && c < colCount; c++) {
|
|
1855
|
+
const cellContentWidth = colWidths[c] - cellPadding * 2;
|
|
1856
|
+
const cellH = measureCellHeight(cells[c], cellContentWidth, state.fonts, fontSize, false);
|
|
1857
|
+
estRowHeight = Math.max(estRowHeight, cellH);
|
|
1858
|
+
}
|
|
1859
|
+
estRowHeight += rowPadding;
|
|
1860
|
+
if (state.y + estRowHeight > CONTENT_HEIGHT) {
|
|
1861
|
+
ensureSpace(state, estRowHeight + (hasHeader ? lineHeight + rowPadding : 0));
|
|
1862
|
+
if (state.pageIndex !== lastPageIndex && hasHeader) {
|
|
1863
|
+
renderRow(headerRow, true);
|
|
1864
|
+
lastPageIndex = state.pageIndex;
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
const rowTopYBeforeRender = state.y;
|
|
1868
|
+
renderRow(tableRows[r], false);
|
|
1869
|
+
lastPageIndex = state.pageIndex;
|
|
1870
|
+
if (subtotals && colCount >= 2) {
|
|
1871
|
+
const bodyIdx = r - startIdx;
|
|
1872
|
+
const isFirstBody = bodyIdx === 0;
|
|
1873
|
+
const isLastBody = bodyIdx === bodyRowCount - 1;
|
|
1874
|
+
if (isFirstBody || isLastBody) {
|
|
1875
|
+
const valueStartX = region.leftX + colWidths[0];
|
|
1876
|
+
const valueEndX = region.leftX + colWidths.reduce((a, b) => a + b, 0);
|
|
1877
|
+
const pdfRowTopY = PAGE_HEIGHT - MARGIN - rowTopYBeforeRender;
|
|
1878
|
+
const overlineColor = pdfLib.rgb(0, 0, 0);
|
|
1879
|
+
if (isFirstBody) {
|
|
1880
|
+
state.currentPage.drawLine({
|
|
1881
|
+
start: { x: valueStartX, y: pdfRowTopY },
|
|
1882
|
+
end: { x: valueEndX, y: pdfRowTopY },
|
|
1883
|
+
thickness: 0.5,
|
|
1884
|
+
color: overlineColor
|
|
1885
|
+
});
|
|
1886
|
+
} else if (isLastBody) {
|
|
1887
|
+
state.currentPage.drawLine({
|
|
1888
|
+
start: { x: valueStartX, y: pdfRowTopY + 2 },
|
|
1889
|
+
end: { x: valueEndX, y: pdfRowTopY + 2 },
|
|
1890
|
+
thickness: 0.5,
|
|
1891
|
+
color: overlineColor
|
|
1892
|
+
});
|
|
1893
|
+
state.currentPage.drawLine({
|
|
1894
|
+
start: { x: valueStartX, y: pdfRowTopY },
|
|
1895
|
+
end: { x: valueEndX, y: pdfRowTopY },
|
|
1896
|
+
thickness: 0.5,
|
|
1897
|
+
color: overlineColor
|
|
1898
|
+
});
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
state.y += 3;
|
|
1184
1904
|
}
|
|
1185
|
-
function processBlock(state, node) {
|
|
1905
|
+
function processBlock(state, node, region = DEFAULT_REGION) {
|
|
1186
1906
|
switch (node.type) {
|
|
1187
1907
|
case "heading": {
|
|
1188
1908
|
const level = node.attrs?.level || 1;
|
|
@@ -1199,34 +1919,34 @@ function processBlock(state, node) {
|
|
|
1199
1919
|
boldItalic: state.fonts.boldItalic
|
|
1200
1920
|
};
|
|
1201
1921
|
const segments = collectInlineSegments(node.content, headingFonts, fontSize);
|
|
1202
|
-
layoutInlineSegments(state, segments, fontSize);
|
|
1922
|
+
layoutInlineSegments(state, segments, fontSize, 0, region);
|
|
1203
1923
|
state.y += spacing / 2;
|
|
1204
1924
|
break;
|
|
1205
1925
|
}
|
|
1206
1926
|
case "paragraph": {
|
|
1207
|
-
const fontSize =
|
|
1927
|
+
const fontSize = BODY_FONT_SIZE;
|
|
1208
1928
|
if (!node.content || node.content.length === 0) {
|
|
1209
|
-
state.y += fontSize * LINE_HEIGHT_FACTOR;
|
|
1929
|
+
state.y += fontSize * LINE_HEIGHT_FACTOR * 0.65;
|
|
1210
1930
|
break;
|
|
1211
1931
|
}
|
|
1212
|
-
processInlineContent(state, node.content, fontSize);
|
|
1213
|
-
state.y +=
|
|
1932
|
+
processInlineContent(state, node.content, fontSize, 0, region);
|
|
1933
|
+
state.y += 2;
|
|
1214
1934
|
break;
|
|
1215
1935
|
}
|
|
1216
1936
|
case "bulletList": {
|
|
1217
1937
|
for (const item of node.content || []) {
|
|
1218
1938
|
for (const child of item.content || []) {
|
|
1219
|
-
const fontSize =
|
|
1939
|
+
const fontSize = BODY_FONT_SIZE;
|
|
1220
1940
|
ensureSpace(state, fontSize * LINE_HEIGHT_FACTOR);
|
|
1221
1941
|
const bulletPdfY = PAGE_HEIGHT - MARGIN - state.y - fontSize;
|
|
1222
1942
|
state.currentPage.drawText("\u2022", {
|
|
1223
|
-
x:
|
|
1943
|
+
x: region.leftX + 8,
|
|
1224
1944
|
y: bulletPdfY,
|
|
1225
1945
|
size: fontSize,
|
|
1226
1946
|
font: state.fonts.regular,
|
|
1227
1947
|
color: pdfLib.rgb(0, 0, 0)
|
|
1228
1948
|
});
|
|
1229
|
-
processInlineContent(state, child.content, fontSize, 24);
|
|
1949
|
+
processInlineContent(state, child.content, fontSize, 24, region);
|
|
1230
1950
|
}
|
|
1231
1951
|
}
|
|
1232
1952
|
state.y += 4;
|
|
@@ -1236,17 +1956,17 @@ function processBlock(state, node) {
|
|
|
1236
1956
|
let num = 1;
|
|
1237
1957
|
for (const item of node.content || []) {
|
|
1238
1958
|
for (const child of item.content || []) {
|
|
1239
|
-
const fontSize =
|
|
1959
|
+
const fontSize = BODY_FONT_SIZE;
|
|
1240
1960
|
ensureSpace(state, fontSize * LINE_HEIGHT_FACTOR);
|
|
1241
1961
|
const numPdfY = PAGE_HEIGHT - MARGIN - state.y - fontSize;
|
|
1242
1962
|
state.currentPage.drawText(`${num}.`, {
|
|
1243
|
-
x:
|
|
1963
|
+
x: region.leftX + 4,
|
|
1244
1964
|
y: numPdfY,
|
|
1245
1965
|
size: fontSize,
|
|
1246
1966
|
font: state.fonts.regular,
|
|
1247
1967
|
color: pdfLib.rgb(0, 0, 0)
|
|
1248
1968
|
});
|
|
1249
|
-
processInlineContent(state, child.content, fontSize, 24);
|
|
1969
|
+
processInlineContent(state, child.content, fontSize, 24, region);
|
|
1250
1970
|
}
|
|
1251
1971
|
num++;
|
|
1252
1972
|
}
|
|
@@ -1256,10 +1976,10 @@ function processBlock(state, node) {
|
|
|
1256
1976
|
case "blockquote": {
|
|
1257
1977
|
const startY = state.y;
|
|
1258
1978
|
for (const child of node.content || []) {
|
|
1259
|
-
processInlineContent(state, child.content, 12, 16);
|
|
1979
|
+
processInlineContent(state, child.content, 12, 16, region);
|
|
1260
1980
|
}
|
|
1261
1981
|
const endY = state.y;
|
|
1262
|
-
const barX =
|
|
1982
|
+
const barX = region.leftX + 4;
|
|
1263
1983
|
const barTop = PAGE_HEIGHT - MARGIN - startY;
|
|
1264
1984
|
const barBottom = PAGE_HEIGHT - MARGIN - endY;
|
|
1265
1985
|
state.currentPage.drawLine({
|
|
@@ -1272,16 +1992,16 @@ function processBlock(state, node) {
|
|
|
1272
1992
|
break;
|
|
1273
1993
|
}
|
|
1274
1994
|
case "horizontalRule": {
|
|
1275
|
-
ensureSpace(state,
|
|
1276
|
-
state.y +=
|
|
1995
|
+
ensureSpace(state, 10);
|
|
1996
|
+
state.y += 4;
|
|
1277
1997
|
const ruleY = PAGE_HEIGHT - MARGIN - state.y;
|
|
1278
1998
|
state.currentPage.drawLine({
|
|
1279
|
-
start: { x:
|
|
1280
|
-
end: { x:
|
|
1281
|
-
thickness:
|
|
1282
|
-
color: pdfLib.rgb(0
|
|
1999
|
+
start: { x: region.leftX, y: ruleY },
|
|
2000
|
+
end: { x: region.leftX + region.width, y: ruleY },
|
|
2001
|
+
thickness: 2.5,
|
|
2002
|
+
color: pdfLib.rgb(0, 0, 0)
|
|
1283
2003
|
});
|
|
1284
|
-
state.y +=
|
|
2004
|
+
state.y += 4;
|
|
1285
2005
|
break;
|
|
1286
2006
|
}
|
|
1287
2007
|
case "codeBlock": {
|
|
@@ -1293,9 +2013,9 @@ function processBlock(state, node) {
|
|
|
1293
2013
|
ensureSpace(state, blockHeight);
|
|
1294
2014
|
const boxY = PAGE_HEIGHT - MARGIN - state.y - blockHeight;
|
|
1295
2015
|
state.currentPage.drawRectangle({
|
|
1296
|
-
x:
|
|
2016
|
+
x: region.leftX,
|
|
1297
2017
|
y: boxY,
|
|
1298
|
-
width:
|
|
2018
|
+
width: region.width,
|
|
1299
2019
|
height: blockHeight,
|
|
1300
2020
|
color: pdfLib.rgb(0.95, 0.95, 0.95),
|
|
1301
2021
|
borderColor: pdfLib.rgb(0.85, 0.85, 0.85),
|
|
@@ -1303,18 +2023,292 @@ function processBlock(state, node) {
|
|
|
1303
2023
|
});
|
|
1304
2024
|
state.y += 8;
|
|
1305
2025
|
for (const line of lines) {
|
|
1306
|
-
drawText(state, line || " ", state.fonts.regular, fontSize, 8);
|
|
2026
|
+
drawText(state, line || " ", state.fonts.regular, fontSize, 8, region);
|
|
1307
2027
|
}
|
|
1308
2028
|
state.y += 8;
|
|
1309
2029
|
break;
|
|
1310
2030
|
}
|
|
2031
|
+
case "table": {
|
|
2032
|
+
renderTable(state, node, region);
|
|
2033
|
+
break;
|
|
2034
|
+
}
|
|
2035
|
+
// ─── Layout directives ─────────────────────────────────────────
|
|
2036
|
+
case "panel": {
|
|
2037
|
+
const title = node.attrs?.title || "";
|
|
2038
|
+
const border = node.attrs?.border || "solid";
|
|
2039
|
+
const headerStyle = node.attrs?.headerStyle || "";
|
|
2040
|
+
const padding = 8;
|
|
2041
|
+
const titleFontSize = 10;
|
|
2042
|
+
const titleHeight = title ? titleFontSize * LINE_HEIGHT_FACTOR + 6 : 0;
|
|
2043
|
+
const panelStartY = state.y;
|
|
2044
|
+
const panelStartPage = state.pageIndex;
|
|
2045
|
+
if (title) {
|
|
2046
|
+
state.y += titleHeight;
|
|
2047
|
+
}
|
|
2048
|
+
const isHeaderOnly = border === "none";
|
|
2049
|
+
if (!isHeaderOnly) {
|
|
2050
|
+
state.y += padding;
|
|
2051
|
+
}
|
|
2052
|
+
const innerRegion = {
|
|
2053
|
+
leftX: region.leftX + padding,
|
|
2054
|
+
width: region.width - padding * 2
|
|
2055
|
+
};
|
|
2056
|
+
if (!isHeaderOnly) {
|
|
2057
|
+
for (const child of node.content || []) {
|
|
2058
|
+
processBlock(state, child, innerRegion);
|
|
2059
|
+
}
|
|
2060
|
+
state.y += padding;
|
|
2061
|
+
}
|
|
2062
|
+
const startPage = state.pdfDoc.getPages()[panelStartPage];
|
|
2063
|
+
if (title) {
|
|
2064
|
+
const titleBarY = PAGE_HEIGHT - MARGIN - panelStartY - titleHeight;
|
|
2065
|
+
const isDarkHeader = headerStyle === "dark";
|
|
2066
|
+
startPage.drawRectangle({
|
|
2067
|
+
x: region.leftX,
|
|
2068
|
+
y: titleBarY,
|
|
2069
|
+
width: region.width,
|
|
2070
|
+
height: titleHeight,
|
|
2071
|
+
color: isDarkHeader ? pdfLib.rgb(0.25, 0.25, 0.25) : pdfLib.rgb(0.93, 0.93, 0.93),
|
|
2072
|
+
borderColor: pdfLib.rgb(0.6, 0.6, 0.6),
|
|
2073
|
+
borderWidth: 0.75
|
|
2074
|
+
});
|
|
2075
|
+
startPage.drawText(title, {
|
|
2076
|
+
x: region.leftX + padding,
|
|
2077
|
+
y: titleBarY + 4,
|
|
2078
|
+
size: titleFontSize,
|
|
2079
|
+
font: state.fonts.bold,
|
|
2080
|
+
color: isDarkHeader ? pdfLib.rgb(1, 1, 1) : pdfLib.rgb(0, 0, 0)
|
|
2081
|
+
});
|
|
2082
|
+
}
|
|
2083
|
+
if (border !== "none") {
|
|
2084
|
+
const borderDashArray = border === "dashed" ? [4, 2] : void 0;
|
|
2085
|
+
if (panelStartPage === state.pageIndex) {
|
|
2086
|
+
const borderY = PAGE_HEIGHT - MARGIN - state.y;
|
|
2087
|
+
const panelHeight = state.y - panelStartY;
|
|
2088
|
+
startPage.drawRectangle({
|
|
2089
|
+
x: region.leftX,
|
|
2090
|
+
y: borderY,
|
|
2091
|
+
width: region.width,
|
|
2092
|
+
height: panelHeight,
|
|
2093
|
+
borderColor: pdfLib.rgb(0.6, 0.6, 0.6),
|
|
2094
|
+
borderWidth: 0.75,
|
|
2095
|
+
borderDashArray
|
|
2096
|
+
});
|
|
2097
|
+
} else {
|
|
2098
|
+
const startBorderBottom = MARGIN;
|
|
2099
|
+
const startPanelHeight = PAGE_HEIGHT - MARGIN - panelStartY - startBorderBottom;
|
|
2100
|
+
startPage.drawRectangle({
|
|
2101
|
+
x: region.leftX,
|
|
2102
|
+
y: startBorderBottom,
|
|
2103
|
+
width: region.width,
|
|
2104
|
+
height: startPanelHeight,
|
|
2105
|
+
borderColor: pdfLib.rgb(0.6, 0.6, 0.6),
|
|
2106
|
+
borderWidth: 0.75,
|
|
2107
|
+
borderDashArray
|
|
2108
|
+
});
|
|
2109
|
+
const contTop = PAGE_HEIGHT - MARGIN;
|
|
2110
|
+
const contBorderY = PAGE_HEIGHT - MARGIN - state.y;
|
|
2111
|
+
const contHeight = contTop - contBorderY;
|
|
2112
|
+
state.currentPage.drawRectangle({
|
|
2113
|
+
x: region.leftX,
|
|
2114
|
+
y: contBorderY,
|
|
2115
|
+
width: region.width,
|
|
2116
|
+
height: contHeight,
|
|
2117
|
+
borderColor: pdfLib.rgb(0.6, 0.6, 0.6),
|
|
2118
|
+
borderWidth: 0.75,
|
|
2119
|
+
borderDashArray
|
|
2120
|
+
});
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
state.y += 4;
|
|
2124
|
+
break;
|
|
2125
|
+
}
|
|
2126
|
+
case "columns": {
|
|
2127
|
+
const split = parseInt(node.attrs?.split || "50", 10);
|
|
2128
|
+
const padX = parseFloat(node.attrs?.padX || "0") || 0;
|
|
2129
|
+
const columns = (node.content || []).filter((c) => c.type === "column");
|
|
2130
|
+
if (columns.length === 0) break;
|
|
2131
|
+
const colRegionLeftX = region.leftX + padX;
|
|
2132
|
+
const colRegionWidth = region.width - 2 * padX;
|
|
2133
|
+
const gap = 12;
|
|
2134
|
+
const columnsStartY = state.y;
|
|
2135
|
+
const columnsStartPage = state.pageIndex;
|
|
2136
|
+
let colWidths;
|
|
2137
|
+
if (columns.length === 2) {
|
|
2138
|
+
const firstWidth = (colRegionWidth - gap) * (split / 100);
|
|
2139
|
+
const secondWidth = colRegionWidth - gap - firstWidth;
|
|
2140
|
+
colWidths = [firstWidth, secondWidth];
|
|
2141
|
+
} else {
|
|
2142
|
+
const eachWidth = (colRegionWidth - gap * (columns.length - 1)) / columns.length;
|
|
2143
|
+
colWidths = columns.map(() => eachWidth);
|
|
2144
|
+
}
|
|
2145
|
+
let maxEndY = columnsStartY;
|
|
2146
|
+
let colX = colRegionLeftX;
|
|
2147
|
+
for (let ci = 0; ci < columns.length; ci++) {
|
|
2148
|
+
const col = columns[ci];
|
|
2149
|
+
const colWidth = colWidths[ci];
|
|
2150
|
+
const colRegion = { leftX: colX, width: colWidth };
|
|
2151
|
+
state.y = columnsStartY;
|
|
2152
|
+
state.pageIndex = columnsStartPage;
|
|
2153
|
+
state.currentPage = state.pdfDoc.getPages()[state.pageIndex];
|
|
2154
|
+
const padTop = parseFloat(col.attrs?.padTop || "0") || 0;
|
|
2155
|
+
if (padTop > 0) {
|
|
2156
|
+
state.y += padTop * BODY_FONT_SIZE * LINE_HEIGHT_FACTOR;
|
|
2157
|
+
}
|
|
2158
|
+
for (const child of col.content || []) {
|
|
2159
|
+
processBlock(state, child, colRegion);
|
|
2160
|
+
}
|
|
2161
|
+
maxEndY = Math.max(maxEndY, state.y);
|
|
2162
|
+
colX += colWidth + gap;
|
|
2163
|
+
}
|
|
2164
|
+
state.y = maxEndY;
|
|
2165
|
+
state.y += 4;
|
|
2166
|
+
break;
|
|
2167
|
+
}
|
|
2168
|
+
case "column": {
|
|
2169
|
+
for (const child of node.content || []) {
|
|
2170
|
+
processBlock(state, child, region);
|
|
2171
|
+
}
|
|
2172
|
+
break;
|
|
2173
|
+
}
|
|
2174
|
+
case "watermark": {
|
|
2175
|
+
break;
|
|
2176
|
+
}
|
|
2177
|
+
case "subtotalsBlock": {
|
|
2178
|
+
const stFontSize = BODY_FONT_SIZE;
|
|
2179
|
+
const stLineHeight = stFontSize * LINE_HEIGHT_FACTOR;
|
|
2180
|
+
const valueRightX = region.leftX + region.width;
|
|
2181
|
+
const rows = (node.content || []).filter(
|
|
2182
|
+
(c) => c.type === "paragraph" && c.content?.length
|
|
2183
|
+
);
|
|
2184
|
+
const parsed = [];
|
|
2185
|
+
for (const row of rows) {
|
|
2186
|
+
let label = "";
|
|
2187
|
+
let value = "";
|
|
2188
|
+
for (const seg of row.content || []) {
|
|
2189
|
+
if (seg.type === "text") {
|
|
2190
|
+
const isBold = seg.marks?.some((m) => m.type === "bold");
|
|
2191
|
+
if (isBold) {
|
|
2192
|
+
label += seg.text || "";
|
|
2193
|
+
} else {
|
|
2194
|
+
const t = (seg.text || "").trim();
|
|
2195
|
+
if (t) value += (value ? " " : "") + t;
|
|
2196
|
+
}
|
|
2197
|
+
} else if (seg.type === "variableNode") {
|
|
2198
|
+
const varLabel = seg.attrs?.varLabel || seg.attrs?.varName || "";
|
|
2199
|
+
const isBoldVar = seg.marks?.some((m) => m.type === "bold");
|
|
2200
|
+
if (isBoldVar) {
|
|
2201
|
+
label += varLabel;
|
|
2202
|
+
} else {
|
|
2203
|
+
value += (value ? " " : "") + `[${varLabel}]`;
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
parsed.push({ label: label.trim(), value: value.trim() });
|
|
2208
|
+
}
|
|
2209
|
+
let maxLabelWidth = 0;
|
|
2210
|
+
for (const { label } of parsed) {
|
|
2211
|
+
if (label) {
|
|
2212
|
+
const w = state.fonts.bold.widthOfTextAtSize(label, stFontSize);
|
|
2213
|
+
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
const gap = 12;
|
|
2217
|
+
const labelX = region.width > 300 ? Math.max(region.leftX + region.width - maxLabelWidth - gap - 80, region.leftX + region.width * 0.4) : region.leftX + 4;
|
|
2218
|
+
const valueColStartX = labelX + maxLabelWidth + gap;
|
|
2219
|
+
const isLastRowTotal = parsed.length > 1;
|
|
2220
|
+
for (let ri = 0; ri < parsed.length; ri++) {
|
|
2221
|
+
const isTotal = isLastRowTotal && ri === parsed.length - 1;
|
|
2222
|
+
if (isTotal) {
|
|
2223
|
+
state.y += stLineHeight * 0.6;
|
|
2224
|
+
}
|
|
2225
|
+
ensureSpace(state, stLineHeight + (isTotal ? 8 : 4));
|
|
2226
|
+
const { label, value } = parsed[ri];
|
|
2227
|
+
const pdfY = PAGE_HEIGHT - MARGIN - state.y - stFontSize;
|
|
2228
|
+
if (isTotal) {
|
|
2229
|
+
state.currentPage.drawLine({
|
|
2230
|
+
start: { x: valueColStartX, y: pdfY + stFontSize + 6 },
|
|
2231
|
+
end: { x: valueRightX, y: pdfY + stFontSize + 6 },
|
|
2232
|
+
thickness: 0.75,
|
|
2233
|
+
color: pdfLib.rgb(0, 0, 0)
|
|
2234
|
+
});
|
|
2235
|
+
state.currentPage.drawLine({
|
|
2236
|
+
start: { x: valueColStartX, y: pdfY + stFontSize + 3 },
|
|
2237
|
+
end: { x: valueRightX, y: pdfY + stFontSize + 3 },
|
|
2238
|
+
thickness: 0.75,
|
|
2239
|
+
color: pdfLib.rgb(0, 0, 0)
|
|
2240
|
+
});
|
|
2241
|
+
}
|
|
2242
|
+
if (label) {
|
|
2243
|
+
state.currentPage.drawText(label, {
|
|
2244
|
+
x: labelX,
|
|
2245
|
+
y: pdfY,
|
|
2246
|
+
size: stFontSize,
|
|
2247
|
+
font: state.fonts.bold,
|
|
2248
|
+
color: pdfLib.rgb(0, 0, 0)
|
|
2249
|
+
});
|
|
2250
|
+
}
|
|
2251
|
+
if (value) {
|
|
2252
|
+
const valueFont = state.fonts.bold;
|
|
2253
|
+
const valueWidth = valueFont.widthOfTextAtSize(value, stFontSize);
|
|
2254
|
+
state.currentPage.drawText(value, {
|
|
2255
|
+
x: valueRightX - valueWidth,
|
|
2256
|
+
y: pdfY,
|
|
2257
|
+
size: stFontSize,
|
|
2258
|
+
font: valueFont,
|
|
2259
|
+
color: pdfLib.rgb(0, 0, 0)
|
|
2260
|
+
});
|
|
2261
|
+
}
|
|
2262
|
+
state.y += stLineHeight;
|
|
2263
|
+
}
|
|
2264
|
+
state.y += 4;
|
|
2265
|
+
break;
|
|
2266
|
+
}
|
|
2267
|
+
case "repeatBlock":
|
|
2268
|
+
if (node.content) {
|
|
2269
|
+
for (const child of node.content) {
|
|
2270
|
+
processBlock(state, child, region);
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
break;
|
|
1311
2274
|
default:
|
|
1312
2275
|
if (node.content) {
|
|
1313
|
-
processInlineContent(state, node.content, 12);
|
|
2276
|
+
processInlineContent(state, node.content, 12, 0, region);
|
|
1314
2277
|
}
|
|
1315
2278
|
break;
|
|
1316
2279
|
}
|
|
1317
2280
|
}
|
|
2281
|
+
function renderWatermarksOnPage(page, fonts, watermarkNodes) {
|
|
2282
|
+
for (const wmNode of watermarkNodes) {
|
|
2283
|
+
const text = wmNode.attrs?.text || "";
|
|
2284
|
+
if (!text) continue;
|
|
2285
|
+
const opacity = parseFloat(wmNode.attrs?.opacity || "0.15");
|
|
2286
|
+
const angle = parseFloat(wmNode.attrs?.angle || "-45");
|
|
2287
|
+
const fontSize = 100;
|
|
2288
|
+
const { width, height } = page.getSize();
|
|
2289
|
+
const centerX = width / 2;
|
|
2290
|
+
const centerY = height / 2;
|
|
2291
|
+
const textWidth = fonts.bold.widthOfTextAtSize(text, fontSize);
|
|
2292
|
+
page.drawText(text, {
|
|
2293
|
+
x: centerX - textWidth / 2 * Math.cos(angle * Math.PI / 180),
|
|
2294
|
+
y: centerY - textWidth / 2 * Math.sin(angle * Math.PI / 180),
|
|
2295
|
+
size: fontSize,
|
|
2296
|
+
font: fonts.bold,
|
|
2297
|
+
color: pdfLib.rgb(0.5, 0.5, 0.5),
|
|
2298
|
+
opacity,
|
|
2299
|
+
rotate: pdfLib.degrees(angle)
|
|
2300
|
+
});
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
function collectWatermarkNodes(content) {
|
|
2304
|
+
const nodes = [];
|
|
2305
|
+
for (const block of content.content || []) {
|
|
2306
|
+
if (block.type === "watermark") {
|
|
2307
|
+
nodes.push(block);
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
return nodes;
|
|
2311
|
+
}
|
|
1318
2312
|
function ensureFieldSuffix(fieldName, suffix) {
|
|
1319
2313
|
const cleanName = fieldName.replace(/_signature$/i, "").replace(/_initials$/i, "").replace(/_date$/i, "");
|
|
1320
2314
|
return `${cleanName}${suffix}`;
|
|
@@ -1543,6 +2537,7 @@ async function generatePdfFromContent(content, options = {}) {
|
|
|
1543
2537
|
italic: await pdfDoc.embedFont(pdfLib.StandardFonts.HelveticaOblique),
|
|
1544
2538
|
boldItalic: await pdfDoc.embedFont(pdfLib.StandardFonts.HelveticaBoldOblique)
|
|
1545
2539
|
};
|
|
2540
|
+
const watermarkNodes = collectWatermarkNodes(content);
|
|
1546
2541
|
const state = {
|
|
1547
2542
|
currentPage: firstPage,
|
|
1548
2543
|
pageIndex: 0,
|
|
@@ -1550,13 +2545,30 @@ async function generatePdfFromContent(content, options = {}) {
|
|
|
1550
2545
|
fonts,
|
|
1551
2546
|
pdfDoc,
|
|
1552
2547
|
fieldPositions: /* @__PURE__ */ new Map(),
|
|
1553
|
-
drawFieldPlaceholders
|
|
2548
|
+
drawFieldPlaceholders,
|
|
2549
|
+
watermarkNodes
|
|
1554
2550
|
};
|
|
2551
|
+
renderWatermarksOnPage(firstPage, fonts, watermarkNodes);
|
|
1555
2552
|
if (content.content) {
|
|
1556
2553
|
for (const block of content.content) {
|
|
1557
2554
|
processBlock(state, block);
|
|
1558
2555
|
}
|
|
1559
2556
|
}
|
|
2557
|
+
const allPages = pdfDoc.getPages();
|
|
2558
|
+
const totalPages = allPages.length;
|
|
2559
|
+
const pageNumFontSize = 9;
|
|
2560
|
+
for (let i = 0; i < totalPages; i++) {
|
|
2561
|
+
const page = allPages[i];
|
|
2562
|
+
const label = `Page ${i + 1} of ${totalPages}`;
|
|
2563
|
+
const labelWidth = fonts.bold.widthOfTextAtSize(label, pageNumFontSize);
|
|
2564
|
+
page.drawText(label, {
|
|
2565
|
+
x: PAGE_WIDTH - MARGIN - labelWidth,
|
|
2566
|
+
y: MARGIN / 2 - pageNumFontSize / 2,
|
|
2567
|
+
size: pageNumFontSize,
|
|
2568
|
+
font: fonts.bold,
|
|
2569
|
+
color: pdfLib.rgb(0, 0, 0)
|
|
2570
|
+
});
|
|
2571
|
+
}
|
|
1560
2572
|
let fieldWarnings;
|
|
1561
2573
|
if (embedFormFields && fields.length > 0) {
|
|
1562
2574
|
const warnings = await addFormFields(pdfDoc, state.fieldPositions, fields);
|
|
@@ -1570,32 +2582,259 @@ async function generatePdfFromContent(content, options = {}) {
|
|
|
1570
2582
|
};
|
|
1571
2583
|
}
|
|
1572
2584
|
|
|
1573
|
-
// src/utils/
|
|
1574
|
-
async function
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
2585
|
+
// src/utils/template-pipeline.ts
|
|
2586
|
+
async function generatePdfFromTiptap(content, values) {
|
|
2587
|
+
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, values);
|
|
2588
|
+
const suppressed = suppressZeroContent(expanded, enrichedValues);
|
|
2589
|
+
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
2590
|
+
const result = await generatePdfFromContent(replaced);
|
|
2591
|
+
return { pdfBytes: result.pdfBytes };
|
|
2592
|
+
}
|
|
2593
|
+
async function generatePdfFromMarkdown(markdown, values) {
|
|
2594
|
+
const content = markdownToTiptap(markdown);
|
|
2595
|
+
return generatePdfFromTiptap(content, values);
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
// src/utils/markdown-writer.ts
|
|
2599
|
+
function serializeFieldToken(attrs) {
|
|
2600
|
+
const parts = ["field"];
|
|
2601
|
+
if (attrs.fieldType) parts.push(`type:${attrs.fieldType}`);
|
|
2602
|
+
if (attrs.fieldName) parts.push(`name:${attrs.fieldName}`);
|
|
2603
|
+
if (attrs.fieldLabel) parts.push(`label:${attrs.fieldLabel}`);
|
|
2604
|
+
if (attrs.fieldId) parts.push(`id:${attrs.fieldId}`);
|
|
2605
|
+
if (attrs.required) parts.push(`required:true`);
|
|
2606
|
+
if (attrs.options) parts.push(`options:${attrs.options}`);
|
|
2607
|
+
if (attrs.fontSize) parts.push(`fontSize:${attrs.fontSize}`);
|
|
2608
|
+
if (attrs.placeholder) parts.push(`placeholder:${attrs.placeholder}`);
|
|
2609
|
+
if (attrs.defaultValue) parts.push(`defaultValue:${attrs.defaultValue}`);
|
|
2610
|
+
if (attrs.multiline) parts.push(`multiline:true`);
|
|
2611
|
+
if (attrs.maxLength) parts.push(`maxLength:${attrs.maxLength}`);
|
|
2612
|
+
if (attrs.acknowledgements) parts.push(`acks:${btoa(attrs.acknowledgements)}`);
|
|
2613
|
+
return `{{${parts.join("|")}}}`;
|
|
2614
|
+
}
|
|
2615
|
+
function serializeVariableToken(attrs) {
|
|
2616
|
+
const parts = ["var"];
|
|
2617
|
+
if (attrs.varName) parts.push(`name:${attrs.varName}`);
|
|
2618
|
+
if (attrs.varLabel) parts.push(`label:${attrs.varLabel}`);
|
|
2619
|
+
if (attrs.varDefault) parts.push(`default:${attrs.varDefault}`);
|
|
2620
|
+
if (attrs.suppressZero === "true") parts.push(`suppress:zero`);
|
|
2621
|
+
if (attrs.format) parts.push(`format:${attrs.format}`);
|
|
2622
|
+
return `{{${parts.join("|")}}}`;
|
|
2623
|
+
}
|
|
2624
|
+
function serializeInline(content) {
|
|
2625
|
+
if (!content) return "";
|
|
2626
|
+
let result = "";
|
|
2627
|
+
for (const node of content) {
|
|
2628
|
+
if (node.type === "fieldNode") {
|
|
2629
|
+
result += serializeFieldToken(node.attrs || {});
|
|
2630
|
+
continue;
|
|
2631
|
+
}
|
|
2632
|
+
if (node.type === "variableNode") {
|
|
2633
|
+
let token = serializeVariableToken(node.attrs || {});
|
|
2634
|
+
const varMarks = node.marks || [];
|
|
2635
|
+
const isBold = varMarks.some((m) => m.type === "bold");
|
|
2636
|
+
const isItalic = varMarks.some((m) => m.type === "italic");
|
|
2637
|
+
const isUnderline = varMarks.some((m) => m.type === "underline");
|
|
2638
|
+
if (isBold && isItalic) token = `***${token}***`;
|
|
2639
|
+
else if (isBold) token = `**${token}**`;
|
|
2640
|
+
else if (isItalic) token = `*${token}*`;
|
|
2641
|
+
if (isUnderline) token = `__${token}__`;
|
|
2642
|
+
result += token;
|
|
2643
|
+
continue;
|
|
2644
|
+
}
|
|
2645
|
+
if (node.type === "text") {
|
|
2646
|
+
let text = node.text || "";
|
|
2647
|
+
const marks = node.marks || [];
|
|
2648
|
+
for (const mark of marks) {
|
|
2649
|
+
if (mark.type === "bold") text = `**${text}**`;
|
|
2650
|
+
else if (mark.type === "italic") text = `*${text}*`;
|
|
2651
|
+
else if (mark.type === "underline") text = `__${text}__`;
|
|
2652
|
+
else if (mark.type === "code") text = `\`${text}\``;
|
|
2653
|
+
}
|
|
2654
|
+
result += text;
|
|
2655
|
+
}
|
|
2656
|
+
if (node.type === "hardBreak") {
|
|
2657
|
+
result += " \n";
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
return result;
|
|
2661
|
+
}
|
|
2662
|
+
function serializeBlock(node) {
|
|
2663
|
+
switch (node.type) {
|
|
2664
|
+
case "heading": {
|
|
2665
|
+
const level = node.attrs?.level || 1;
|
|
2666
|
+
const prefix = "#".repeat(level);
|
|
2667
|
+
return `${prefix} ${serializeInline(node.content)}`;
|
|
2668
|
+
}
|
|
2669
|
+
case "paragraph": {
|
|
2670
|
+
const inline = serializeInline(node.content);
|
|
2671
|
+
return inline;
|
|
2672
|
+
}
|
|
2673
|
+
case "bulletList": {
|
|
2674
|
+
return (node.content || []).map((item) => {
|
|
2675
|
+
const inner = (item.content || []).map(serializeBlock).join("\n");
|
|
2676
|
+
return `- ${inner}`;
|
|
2677
|
+
}).join("\n");
|
|
2678
|
+
}
|
|
2679
|
+
case "orderedList": {
|
|
2680
|
+
return (node.content || []).map((item, i) => {
|
|
2681
|
+
const inner = (item.content || []).map(serializeBlock).join("\n");
|
|
2682
|
+
return `${i + 1}. ${inner}`;
|
|
2683
|
+
}).join("\n");
|
|
2684
|
+
}
|
|
2685
|
+
case "listItem": {
|
|
2686
|
+
return (node.content || []).map(serializeBlock).join("\n");
|
|
2687
|
+
}
|
|
2688
|
+
case "blockquote": {
|
|
2689
|
+
return (node.content || []).map(serializeBlock).map((line) => `> ${line}`).join("\n");
|
|
2690
|
+
}
|
|
2691
|
+
case "codeBlock": {
|
|
2692
|
+
const text = serializeInline(node.content);
|
|
2693
|
+
return `\`\`\`
|
|
2694
|
+
${text}
|
|
2695
|
+
\`\`\``;
|
|
2696
|
+
}
|
|
2697
|
+
case "horizontalRule": {
|
|
2698
|
+
return "---";
|
|
2699
|
+
}
|
|
2700
|
+
case "table": {
|
|
2701
|
+
const rows = node.content || [];
|
|
2702
|
+
const isBorderless = node.attrs?.borderless === true;
|
|
2703
|
+
const isSubtotals = node.attrs?.subtotals === true;
|
|
2704
|
+
const serializedRows = [];
|
|
2705
|
+
if (isBorderless && rows.length > 0) {
|
|
2706
|
+
const colCount = rows[0].content?.length || 0;
|
|
2707
|
+
const markerCell = isSubtotals ? "_" : "";
|
|
2708
|
+
const markerRow = "| " + new Array(colCount).fill(markerCell).join(" | ") + " |";
|
|
2709
|
+
const sep = "|" + new Array(colCount).fill("---").join("|") + "|";
|
|
2710
|
+
serializedRows.push(markerRow);
|
|
2711
|
+
serializedRows.push(sep);
|
|
2712
|
+
}
|
|
2713
|
+
for (let ri = 0; ri < rows.length; ri++) {
|
|
2714
|
+
const row = rows[ri];
|
|
2715
|
+
const cells = (row.content || []).map((cell) => {
|
|
2716
|
+
const inner = (cell.content || []).map(serializeBlock).join("");
|
|
2717
|
+
return inner;
|
|
2718
|
+
});
|
|
2719
|
+
serializedRows.push(`| ${cells.join(" | ")} |`);
|
|
2720
|
+
if (!isBorderless && ri === 0 && row.content?.[0]?.type === "tableHeader") {
|
|
2721
|
+
const sep = cells.map(() => "------").join("|");
|
|
2722
|
+
serializedRows.push(`|${sep}|`);
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
return serializedRows.join("\n");
|
|
2726
|
+
}
|
|
2727
|
+
case "tableRow": {
|
|
2728
|
+
const cells = (node.content || []).map((cell) => {
|
|
2729
|
+
const inner = (cell.content || []).map(serializeBlock).join("");
|
|
2730
|
+
return inner;
|
|
2731
|
+
});
|
|
2732
|
+
return `| ${cells.join(" | ")} |`;
|
|
2733
|
+
}
|
|
2734
|
+
case "tableHeader":
|
|
2735
|
+
case "tableCell": {
|
|
2736
|
+
return (node.content || []).map(serializeBlock).join("");
|
|
2737
|
+
}
|
|
2738
|
+
case "watermark": {
|
|
2739
|
+
const attrs = node.attrs || {};
|
|
2740
|
+
const parts = [];
|
|
2741
|
+
if (attrs.text) parts.push(`text:${attrs.text}`);
|
|
2742
|
+
if (attrs.opacity && attrs.opacity !== "0.15") parts.push(`opacity:${attrs.opacity}`);
|
|
2743
|
+
if (attrs.angle && attrs.angle !== "-45") parts.push(`angle:${attrs.angle}`);
|
|
2744
|
+
return `:::watermark{${parts.join("|")}}`;
|
|
2745
|
+
}
|
|
2746
|
+
case "panel": {
|
|
2747
|
+
const attrs = node.attrs || {};
|
|
2748
|
+
const parts = [];
|
|
2749
|
+
if (attrs.title) parts.push(`title:${attrs.title}`);
|
|
2750
|
+
if (attrs.border && attrs.border !== "solid") parts.push(`border:${attrs.border}`);
|
|
2751
|
+
else if (attrs.border === "solid") parts.push(`border:solid`);
|
|
2752
|
+
if (attrs.headerStyle) parts.push(`headerStyle:${attrs.headerStyle}`);
|
|
2753
|
+
const attrStr = parts.length > 0 ? `{${parts.join("|")}}` : "";
|
|
2754
|
+
const children = (node.content || []).map(serializeBlock).join("\n\n");
|
|
2755
|
+
return `:::panel${attrStr}
|
|
2756
|
+
${children}
|
|
2757
|
+
:::`;
|
|
2758
|
+
}
|
|
2759
|
+
case "repeatBlock": {
|
|
2760
|
+
const attrs = node.attrs || {};
|
|
2761
|
+
const parts = [];
|
|
2762
|
+
if (attrs.data) parts.push(`data:${attrs.data}`);
|
|
2763
|
+
const attrStr = parts.length > 0 ? `{${parts.join("|")}}` : "";
|
|
2764
|
+
const children = (node.content || []).map(serializeBlock).join("\n\n");
|
|
2765
|
+
return `:::repeat${attrStr}
|
|
2766
|
+
${children}
|
|
2767
|
+
:::`;
|
|
2768
|
+
}
|
|
2769
|
+
case "subtotalsBlock": {
|
|
2770
|
+
const children = (node.content || []).map(serializeBlock).join("\n");
|
|
2771
|
+
return `:::subtotals
|
|
2772
|
+
${children}
|
|
2773
|
+
:::`;
|
|
2774
|
+
}
|
|
2775
|
+
case "columns": {
|
|
2776
|
+
const attrs = node.attrs || {};
|
|
2777
|
+
const splitVal = attrs.split || "50";
|
|
2778
|
+
const cols = (node.content || []).filter((c) => c.type === "column");
|
|
2779
|
+
const colBlocks = cols.map((col) => {
|
|
2780
|
+
const children = (col.content || []).map(serializeBlock).join("\n\n");
|
|
2781
|
+
const colAttrs = col.attrs || {};
|
|
2782
|
+
const padTop = parseFloat(colAttrs.padTop || "0") || 0;
|
|
2783
|
+
const colTag = padTop ? `:::col{padTop:${padTop}}` : ":::col";
|
|
2784
|
+
return `${colTag}
|
|
2785
|
+
${children}
|
|
2786
|
+
:::`;
|
|
2787
|
+
});
|
|
2788
|
+
const padX = parseFloat(attrs.padX || "0") || 0;
|
|
2789
|
+
const colsAttrs = padX ? `split:${splitVal}|padX:${padX}` : `split:${splitVal}`;
|
|
2790
|
+
return `:::columns{${colsAttrs}}
|
|
2791
|
+
${colBlocks.join("\n")}
|
|
2792
|
+
:::`;
|
|
2793
|
+
}
|
|
2794
|
+
case "column": {
|
|
2795
|
+
const children = (node.content || []).map(serializeBlock).join("\n\n");
|
|
2796
|
+
const colAttrs = node.attrs || {};
|
|
2797
|
+
const padTop = parseFloat(colAttrs.padTop || "0") || 0;
|
|
2798
|
+
const colTag = padTop ? `:::col{padTop:${padTop}}` : ":::col";
|
|
2799
|
+
return `${colTag}
|
|
2800
|
+
${children}
|
|
2801
|
+
:::`;
|
|
2802
|
+
}
|
|
2803
|
+
default:
|
|
2804
|
+
return serializeInline(node.content);
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
function tiptapToMarkdown(doc) {
|
|
2808
|
+
if (!doc.content) return "";
|
|
2809
|
+
return doc.content.map(serializeBlock).join("\n\n");
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
// src/utils/pdf-preview.ts
|
|
2813
|
+
async function pdfToImages(pdfBytes, scale = 2) {
|
|
2814
|
+
let pdfjsLib;
|
|
2815
|
+
try {
|
|
2816
|
+
pdfjsLib = await import('pdfjs-dist');
|
|
2817
|
+
} catch (err) {
|
|
2818
|
+
throw new Error(formatError(
|
|
2819
|
+
"Failed to load the PDF preview library \u2014 ensure pdfjs-dist is installed and the worker is accessible",
|
|
2820
|
+
err
|
|
2821
|
+
));
|
|
2822
|
+
}
|
|
2823
|
+
if (!pdfjsLib.GlobalWorkerOptions.workerSrc) {
|
|
2824
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc = "/pdfjs/build/pdf.worker.mjs";
|
|
2825
|
+
}
|
|
2826
|
+
let pdf;
|
|
2827
|
+
try {
|
|
2828
|
+
const loadingTask = pdfjsLib.getDocument({ data: pdfBytes });
|
|
2829
|
+
pdf = await loadingTask.promise;
|
|
2830
|
+
} catch (err) {
|
|
2831
|
+
throw new Error(formatError("Failed to parse the generated PDF for preview", err));
|
|
2832
|
+
}
|
|
2833
|
+
const pages = [];
|
|
2834
|
+
const pageErrors = [];
|
|
2835
|
+
for (let i = 1; i <= pdf.numPages; i++) {
|
|
2836
|
+
try {
|
|
2837
|
+
const page = await pdf.getPage(i);
|
|
1599
2838
|
const viewport = page.getViewport({ scale });
|
|
1600
2839
|
const canvas = document.createElement("canvas");
|
|
1601
2840
|
canvas.width = viewport.width;
|
|
@@ -1656,8 +2895,26 @@ function useDocumentGenerator(options = {}) {
|
|
|
1656
2895
|
Placeholder__default.default.configure({
|
|
1657
2896
|
placeholder: options.placeholder || "Start writing your document..."
|
|
1658
2897
|
}),
|
|
2898
|
+
Table__default.default.extend({
|
|
2899
|
+
addAttributes() {
|
|
2900
|
+
return {
|
|
2901
|
+
...this.parent?.(),
|
|
2902
|
+
borderless: { default: false },
|
|
2903
|
+
subtotals: { default: false }
|
|
2904
|
+
};
|
|
2905
|
+
}
|
|
2906
|
+
}).configure({ resizable: false }),
|
|
2907
|
+
TableRow__default.default,
|
|
2908
|
+
TableCell__default.default,
|
|
2909
|
+
TableHeader__default.default,
|
|
1659
2910
|
FieldNode,
|
|
1660
|
-
VariableNode
|
|
2911
|
+
VariableNode,
|
|
2912
|
+
PanelNode,
|
|
2913
|
+
ColumnsNode,
|
|
2914
|
+
ColumnNode,
|
|
2915
|
+
WatermarkNode,
|
|
2916
|
+
RepeatNode,
|
|
2917
|
+
SubtotalsNode
|
|
1661
2918
|
],
|
|
1662
2919
|
content: initialContent || { type: "doc", content: [{ type: "paragraph" }] },
|
|
1663
2920
|
onUpdate: ({ editor: ed }) => {
|
|
@@ -1761,10 +3018,9 @@ function useDocumentGenerator(options = {}) {
|
|
|
1761
3018
|
async (values) => {
|
|
1762
3019
|
if (!editor) throw new Error("Editor is not initialized yet \u2014 wait for the editor to load before generating");
|
|
1763
3020
|
const content = editor.getJSON();
|
|
1764
|
-
const
|
|
1765
|
-
const
|
|
1766
|
-
|
|
1767
|
-
return { pdfBytes: result.pdfBytes, pdfPages: pages };
|
|
3021
|
+
const { pdfBytes: pdfBytes2 } = await generatePdfFromTiptap(content, values);
|
|
3022
|
+
const pdfPages2 = await pdfToImages(pdfBytes2);
|
|
3023
|
+
return { pdfBytes: pdfBytes2, pdfPages: pdfPages2 };
|
|
1768
3024
|
},
|
|
1769
3025
|
[editor]
|
|
1770
3026
|
);
|
|
@@ -2918,15 +4174,47 @@ function FieldEditPopover({
|
|
|
2918
4174
|
] }) })
|
|
2919
4175
|
] });
|
|
2920
4176
|
}
|
|
2921
|
-
|
|
4177
|
+
var sizeClasses = {
|
|
4178
|
+
md: "px-3 py-1.5 text-xs font-medium",
|
|
4179
|
+
sm: "px-2 py-0.5 text-[10px] font-medium"
|
|
4180
|
+
};
|
|
4181
|
+
function ToggleGroup({
|
|
4182
|
+
value,
|
|
4183
|
+
onChange,
|
|
4184
|
+
options,
|
|
4185
|
+
size = "md",
|
|
4186
|
+
className
|
|
4187
|
+
}) {
|
|
4188
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("inline-flex items-center gap-1 bg-muted/50 p-0.5 rounded-lg", className), children: options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
4189
|
+
"button",
|
|
4190
|
+
{
|
|
4191
|
+
className: cn(
|
|
4192
|
+
"rounded-md transition-colors",
|
|
4193
|
+
sizeClasses[size],
|
|
4194
|
+
value === opt.value ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
|
|
4195
|
+
),
|
|
4196
|
+
onClick: () => onChange(opt.value),
|
|
4197
|
+
children: opt.label
|
|
4198
|
+
},
|
|
4199
|
+
opt.value
|
|
4200
|
+
)) });
|
|
4201
|
+
}
|
|
4202
|
+
function labelToVarName2(label) {
|
|
2922
4203
|
return label.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
2923
4204
|
}
|
|
4205
|
+
function varNameToLabel(name) {
|
|
4206
|
+
return name.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
4207
|
+
}
|
|
2924
4208
|
function VariableInsertPopover({
|
|
2925
4209
|
open,
|
|
2926
4210
|
onOpenChange,
|
|
2927
4211
|
onInsert,
|
|
4212
|
+
predefinedVariables,
|
|
2928
4213
|
children
|
|
2929
4214
|
}) {
|
|
4215
|
+
const hasPredefined = !!predefinedVariables?.length;
|
|
4216
|
+
const [mode, setMode] = React12.useState(hasPredefined ? "existing" : "new");
|
|
4217
|
+
const [search, setSearch] = React12.useState("");
|
|
2930
4218
|
const [varLabel, setVarLabel] = React12.useState("");
|
|
2931
4219
|
const [varName, setVarName] = React12.useState("");
|
|
2932
4220
|
const [varDefault, setVarDefault] = React12.useState("");
|
|
@@ -2936,7 +4224,9 @@ function VariableInsertPopover({
|
|
|
2936
4224
|
setVarName("");
|
|
2937
4225
|
setVarDefault("");
|
|
2938
4226
|
setNameManuallyEdited(false);
|
|
2939
|
-
|
|
4227
|
+
setSearch("");
|
|
4228
|
+
setMode(hasPredefined ? "existing" : "new");
|
|
4229
|
+
}, [hasPredefined]);
|
|
2940
4230
|
const handleOpenChange = React12.useCallback(
|
|
2941
4231
|
(nextOpen) => {
|
|
2942
4232
|
if (!nextOpen) reset();
|
|
@@ -2946,7 +4236,7 @@ function VariableInsertPopover({
|
|
|
2946
4236
|
);
|
|
2947
4237
|
React12.useEffect(() => {
|
|
2948
4238
|
if (!nameManuallyEdited) {
|
|
2949
|
-
setVarName(
|
|
4239
|
+
setVarName(labelToVarName2(varLabel));
|
|
2950
4240
|
}
|
|
2951
4241
|
}, [varLabel, nameManuallyEdited]);
|
|
2952
4242
|
const handleInsert = React12.useCallback(() => {
|
|
@@ -2958,6 +4248,26 @@ function VariableInsertPopover({
|
|
|
2958
4248
|
});
|
|
2959
4249
|
handleOpenChange(false);
|
|
2960
4250
|
}, [varLabel, varName, varDefault, onInsert, handleOpenChange]);
|
|
4251
|
+
const handlePickPredefined = React12.useCallback(
|
|
4252
|
+
(pv) => {
|
|
4253
|
+
onInsert({
|
|
4254
|
+
varName: pv.varName,
|
|
4255
|
+
varLabel: pv.varLabel || varNameToLabel(pv.varName),
|
|
4256
|
+
varDefault: ""
|
|
4257
|
+
});
|
|
4258
|
+
handleOpenChange(false);
|
|
4259
|
+
},
|
|
4260
|
+
[onInsert, handleOpenChange]
|
|
4261
|
+
);
|
|
4262
|
+
const filteredVars = React12.useMemo(() => {
|
|
4263
|
+
if (!predefinedVariables?.length) return [];
|
|
4264
|
+
const q = search.toLowerCase();
|
|
4265
|
+
if (!q) return predefinedVariables;
|
|
4266
|
+
return predefinedVariables.filter((pv) => {
|
|
4267
|
+
const label = (pv.varLabel || varNameToLabel(pv.varName)).toLowerCase();
|
|
4268
|
+
return pv.varName.toLowerCase().includes(q) || label.includes(q);
|
|
4269
|
+
});
|
|
4270
|
+
}, [predefinedVariables, search]);
|
|
2961
4271
|
return /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open, onOpenChange: handleOpenChange, children: [
|
|
2962
4272
|
/* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children }),
|
|
2963
4273
|
/* @__PURE__ */ jsxRuntime.jsx(PopoverContent, { className: "w-72 p-0", align: "start", sideOffset: 8, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3 space-y-3", children: [
|
|
@@ -2965,72 +4275,114 @@ function VariableInsertPopover({
|
|
|
2965
4275
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Braces, { size: 14 }),
|
|
2966
4276
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Insert Variable" })
|
|
2967
4277
|
] }),
|
|
2968
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2969
|
-
|
|
2970
|
-
|
|
4278
|
+
hasPredefined && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4279
|
+
ToggleGroup,
|
|
4280
|
+
{
|
|
4281
|
+
value: mode,
|
|
4282
|
+
onChange: setMode,
|
|
4283
|
+
options: [
|
|
4284
|
+
{ value: "existing", label: "Existing" },
|
|
4285
|
+
{ value: "new", label: "New" }
|
|
4286
|
+
],
|
|
4287
|
+
size: "sm"
|
|
4288
|
+
}
|
|
4289
|
+
),
|
|
4290
|
+
mode === "existing" && hasPredefined ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
4291
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
4292
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { size: 14, className: "absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground" }),
|
|
2971
4293
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2972
4294
|
Input,
|
|
2973
4295
|
{
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
className: "h-8 text-xs"
|
|
4296
|
+
value: search,
|
|
4297
|
+
onChange: (e) => setSearch(e.target.value),
|
|
4298
|
+
placeholder: "Search variables...",
|
|
4299
|
+
className: "h-8 text-xs pl-7"
|
|
2979
4300
|
}
|
|
2980
4301
|
)
|
|
2981
4302
|
] }),
|
|
2982
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2983
|
-
|
|
4303
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-48 overflow-y-auto -mx-1", children: filteredVars.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground px-2 py-3 text-center", children: "No variables found" }) : filteredVars.map((pv) => {
|
|
4304
|
+
const label = pv.varLabel || varNameToLabel(pv.varName);
|
|
4305
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4306
|
+
"button",
|
|
4307
|
+
{
|
|
4308
|
+
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",
|
|
4309
|
+
onClick: () => handlePickPredefined(pv),
|
|
4310
|
+
children: [
|
|
4311
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: label }),
|
|
4312
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground font-mono", children: pv.varName })
|
|
4313
|
+
]
|
|
4314
|
+
},
|
|
4315
|
+
pv.varName
|
|
4316
|
+
);
|
|
4317
|
+
}) })
|
|
4318
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
4319
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
4320
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
4321
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "var-label", className: "text-xs", children: "Label" }),
|
|
4322
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4323
|
+
Input,
|
|
4324
|
+
{
|
|
4325
|
+
id: "var-label",
|
|
4326
|
+
value: varLabel,
|
|
4327
|
+
onChange: (e) => setVarLabel(e.target.value),
|
|
4328
|
+
placeholder: "e.g. Company Name",
|
|
4329
|
+
className: "h-8 text-xs"
|
|
4330
|
+
}
|
|
4331
|
+
)
|
|
4332
|
+
] }),
|
|
4333
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
4334
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "var-name", className: "text-xs", children: "Name (identifier)" }),
|
|
4335
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4336
|
+
Input,
|
|
4337
|
+
{
|
|
4338
|
+
id: "var-name",
|
|
4339
|
+
value: varName,
|
|
4340
|
+
onChange: (e) => {
|
|
4341
|
+
setVarName(e.target.value);
|
|
4342
|
+
setNameManuallyEdited(true);
|
|
4343
|
+
},
|
|
4344
|
+
placeholder: "e.g. company_name",
|
|
4345
|
+
className: "h-8 text-xs font-mono"
|
|
4346
|
+
}
|
|
4347
|
+
),
|
|
4348
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] text-muted-foreground mt-0.5", children: "Used as key in data objects" })
|
|
4349
|
+
] }),
|
|
4350
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
4351
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "var-default", className: "text-xs", children: "Default Value" }),
|
|
4352
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4353
|
+
Input,
|
|
4354
|
+
{
|
|
4355
|
+
id: "var-default",
|
|
4356
|
+
value: varDefault,
|
|
4357
|
+
onChange: (e) => setVarDefault(e.target.value),
|
|
4358
|
+
placeholder: "Optional",
|
|
4359
|
+
className: "h-8 text-xs"
|
|
4360
|
+
}
|
|
4361
|
+
)
|
|
4362
|
+
] })
|
|
4363
|
+
] }),
|
|
4364
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 pt-1", children: [
|
|
2984
4365
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2985
|
-
|
|
4366
|
+
Button,
|
|
2986
4367
|
{
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
},
|
|
2993
|
-
placeholder: "e.g. company_name",
|
|
2994
|
-
className: "h-8 text-xs font-mono"
|
|
4368
|
+
size: "sm",
|
|
4369
|
+
className: "h-8 flex-1 text-xs",
|
|
4370
|
+
onClick: handleInsert,
|
|
4371
|
+
disabled: !varLabel.trim() || !varName.trim(),
|
|
4372
|
+
children: "Insert"
|
|
2995
4373
|
}
|
|
2996
4374
|
),
|
|
2997
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] text-muted-foreground mt-0.5", children: "Used as key in data objects" })
|
|
2998
|
-
] }),
|
|
2999
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3000
|
-
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "var-default", className: "text-xs", children: "Default Value" }),
|
|
3001
4375
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3002
|
-
|
|
4376
|
+
Button,
|
|
3003
4377
|
{
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
4378
|
+
variant: "outline",
|
|
4379
|
+
size: "sm",
|
|
4380
|
+
className: "h-8 text-xs",
|
|
4381
|
+
onClick: () => handleOpenChange(false),
|
|
4382
|
+
children: "Cancel"
|
|
3009
4383
|
}
|
|
3010
4384
|
)
|
|
3011
4385
|
] })
|
|
3012
|
-
] }),
|
|
3013
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 pt-1", children: [
|
|
3014
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3015
|
-
Button,
|
|
3016
|
-
{
|
|
3017
|
-
size: "sm",
|
|
3018
|
-
className: "h-8 flex-1 text-xs",
|
|
3019
|
-
onClick: handleInsert,
|
|
3020
|
-
disabled: !varLabel.trim() || !varName.trim(),
|
|
3021
|
-
children: "Insert"
|
|
3022
|
-
}
|
|
3023
|
-
),
|
|
3024
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3025
|
-
Button,
|
|
3026
|
-
{
|
|
3027
|
-
variant: "outline",
|
|
3028
|
-
size: "sm",
|
|
3029
|
-
className: "h-8 text-xs",
|
|
3030
|
-
onClick: () => handleOpenChange(false),
|
|
3031
|
-
children: "Cancel"
|
|
3032
|
-
}
|
|
3033
|
-
)
|
|
3034
4386
|
] })
|
|
3035
4387
|
] }) })
|
|
3036
4388
|
] });
|
|
@@ -3252,7 +4604,6 @@ function PreviewPanel({
|
|
|
3252
4604
|
(f) => f.position.page === currentPageIndex + 1 && f.position.width > 0
|
|
3253
4605
|
);
|
|
3254
4606
|
}, [positionedFields, currentPage, currentPageIndex]);
|
|
3255
|
-
const showScrollbars = zoomLevel > 1;
|
|
3256
4607
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col h-full border border-border rounded-lg overflow-hidden bg-muted/20", className), children: [
|
|
3257
4608
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-border px-2 py-1.5 flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [
|
|
3258
4609
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
@@ -3368,12 +4719,13 @@ function PreviewPanel({
|
|
|
3368
4719
|
"div",
|
|
3369
4720
|
{
|
|
3370
4721
|
className: cn(
|
|
3371
|
-
"border border-border rounded-lg bg-muted/30 shrink-0"
|
|
3372
|
-
showScrollbars ? "overflow-auto" : "overflow-hidden"
|
|
4722
|
+
"border border-border rounded-lg bg-muted/30 shrink-0 overflow-auto scrollbar-hidden"
|
|
3373
4723
|
),
|
|
3374
4724
|
style: {
|
|
3375
4725
|
width: pageDisplaySize.viewportWidth,
|
|
3376
|
-
height: pageDisplaySize.viewportHeight
|
|
4726
|
+
height: pageDisplaySize.viewportHeight,
|
|
4727
|
+
maxWidth: "100%",
|
|
4728
|
+
maxHeight: "100%"
|
|
3377
4729
|
},
|
|
3378
4730
|
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3379
4731
|
"div",
|
|
@@ -3465,6 +4817,37 @@ function validateMarkdown(markdown) {
|
|
|
3465
4817
|
searchFrom = closeIdx + 2;
|
|
3466
4818
|
}
|
|
3467
4819
|
}
|
|
4820
|
+
const DIRECTIVE_OPEN_RE = /^:::(panel|columns|col|watermark)(?:\{[^}]*\})?$/;
|
|
4821
|
+
const DIRECTIVE_CLOSE_RE = /^:::$/;
|
|
4822
|
+
const directiveStack = [];
|
|
4823
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4824
|
+
const trimmed = lines[i].trim();
|
|
4825
|
+
const lineNum = i + 1;
|
|
4826
|
+
const openMatch = trimmed.match(DIRECTIVE_OPEN_RE);
|
|
4827
|
+
if (openMatch) {
|
|
4828
|
+
const dtype = openMatch[1];
|
|
4829
|
+
if (dtype !== "watermark") {
|
|
4830
|
+
if (dtype === "col") {
|
|
4831
|
+
const parent = directiveStack[directiveStack.length - 1];
|
|
4832
|
+
if (!parent || parent.type !== "columns") {
|
|
4833
|
+
warnings.push(`:::col at line ${lineNum} appears outside :::columns`);
|
|
4834
|
+
}
|
|
4835
|
+
}
|
|
4836
|
+
directiveStack.push({ type: dtype, line: lineNum });
|
|
4837
|
+
}
|
|
4838
|
+
continue;
|
|
4839
|
+
}
|
|
4840
|
+
if (DIRECTIVE_CLOSE_RE.test(trimmed)) {
|
|
4841
|
+
if (directiveStack.length === 0) {
|
|
4842
|
+
warnings.push(`Stray ::: close at line ${lineNum} with no matching open directive`);
|
|
4843
|
+
} else {
|
|
4844
|
+
directiveStack.pop();
|
|
4845
|
+
}
|
|
4846
|
+
}
|
|
4847
|
+
}
|
|
4848
|
+
for (const open of directiveStack) {
|
|
4849
|
+
errors.push(`Unclosed :::${open.type} directive opened at line ${open.line}`);
|
|
4850
|
+
}
|
|
3468
4851
|
let fields = [];
|
|
3469
4852
|
let variables = [];
|
|
3470
4853
|
try {
|
|
@@ -3498,31 +4881,6 @@ function validateMarkdown(markdown) {
|
|
|
3498
4881
|
fields
|
|
3499
4882
|
};
|
|
3500
4883
|
}
|
|
3501
|
-
var sizeClasses = {
|
|
3502
|
-
md: "px-3 py-1.5 text-xs font-medium",
|
|
3503
|
-
sm: "px-2 py-0.5 text-[10px] font-medium"
|
|
3504
|
-
};
|
|
3505
|
-
function ToggleGroup({
|
|
3506
|
-
value,
|
|
3507
|
-
onChange,
|
|
3508
|
-
options,
|
|
3509
|
-
size = "md",
|
|
3510
|
-
className
|
|
3511
|
-
}) {
|
|
3512
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("inline-flex items-center gap-1 bg-muted/50 p-0.5 rounded-lg", className), children: options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3513
|
-
"button",
|
|
3514
|
-
{
|
|
3515
|
-
className: cn(
|
|
3516
|
-
"rounded-md transition-colors",
|
|
3517
|
-
sizeClasses[size],
|
|
3518
|
-
value === opt.value ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
|
|
3519
|
-
),
|
|
3520
|
-
onClick: () => onChange(opt.value),
|
|
3521
|
-
children: opt.label
|
|
3522
|
-
},
|
|
3523
|
-
opt.value
|
|
3524
|
-
)) });
|
|
3525
|
-
}
|
|
3526
4884
|
function parseCsv(text) {
|
|
3527
4885
|
const lines = text.split(/\r?\n/).filter((l) => l.trim() !== "");
|
|
3528
4886
|
if (lines.length < 2) return [];
|
|
@@ -3572,7 +4930,10 @@ function GeneratePanel({
|
|
|
3572
4930
|
editorContent,
|
|
3573
4931
|
editorVariables,
|
|
3574
4932
|
onGeneratePdf,
|
|
3575
|
-
initialBulkData
|
|
4933
|
+
initialBulkData,
|
|
4934
|
+
initialVariableValues,
|
|
4935
|
+
exportFileName,
|
|
4936
|
+
templateMode
|
|
3576
4937
|
}) {
|
|
3577
4938
|
const [templateSource, setTemplateSource] = React12.useState("editor");
|
|
3578
4939
|
const [importedMarkdown, setImportedMarkdown] = React12.useState(null);
|
|
@@ -3580,6 +4941,7 @@ function GeneratePanel({
|
|
|
3580
4941
|
const [validationResult, setValidationResult] = React12.useState(null);
|
|
3581
4942
|
const [mode, setMode] = React12.useState("single");
|
|
3582
4943
|
const [variableValues, setVariableValues] = React12.useState({});
|
|
4944
|
+
const [variableSearch, setVariableSearch] = React12.useState("");
|
|
3583
4945
|
const [bulkInputFormat, setBulkInputFormat] = React12.useState("json");
|
|
3584
4946
|
const [bulkInput, setBulkInput] = React12.useState("");
|
|
3585
4947
|
const [bulkData, setBulkData] = React12.useState(null);
|
|
@@ -3606,11 +4968,22 @@ function GeneratePanel({
|
|
|
3606
4968
|
const col = pos - textBefore.lastIndexOf("\n");
|
|
3607
4969
|
setJsonCursor({ line, col });
|
|
3608
4970
|
}, []);
|
|
4971
|
+
const lockedVarNames = React12__namespace.default.useMemo(
|
|
4972
|
+
() => templateMode && initialVariableValues ? new Set(Object.keys(initialVariableValues)) : /* @__PURE__ */ new Set(),
|
|
4973
|
+
[templateMode, initialVariableValues]
|
|
4974
|
+
);
|
|
3609
4975
|
const activeVariables = templateSource === "imported" && validationResult?.valid ? validationResult.variables : editorVariables;
|
|
3610
4976
|
const hasVariables = activeVariables.length > 0;
|
|
3611
4977
|
const hasEditorContent = editorMarkdown.trim().length > 0;
|
|
3612
4978
|
const hasImportedContent = templateSource === "imported" && importedMarkdown != null;
|
|
3613
4979
|
const hasContent = hasImportedContent || hasEditorContent;
|
|
4980
|
+
const filteredVariables = React12__namespace.default.useMemo(() => {
|
|
4981
|
+
if (!variableSearch.trim()) return activeVariables;
|
|
4982
|
+
const q = variableSearch.toLowerCase();
|
|
4983
|
+
return activeVariables.filter(
|
|
4984
|
+
(v) => v.varName.toLowerCase().includes(q) || (v.varLabel || "").toLowerCase().includes(q)
|
|
4985
|
+
);
|
|
4986
|
+
}, [activeVariables, variableSearch]);
|
|
3614
4987
|
const getActiveContent = React12.useCallback(() => {
|
|
3615
4988
|
if (templateSource === "imported" && importedMarkdown) {
|
|
3616
4989
|
return markdownToTiptap(importedMarkdown);
|
|
@@ -3778,7 +5151,9 @@ function GeneratePanel({
|
|
|
3778
5151
|
setPreviewError(null);
|
|
3779
5152
|
try {
|
|
3780
5153
|
const content = getActiveContent();
|
|
3781
|
-
const
|
|
5154
|
+
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, variableValues);
|
|
5155
|
+
const suppressed = suppressZeroContent(expanded, enrichedValues);
|
|
5156
|
+
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
3782
5157
|
const fields = extractFieldsFromContent(replaced);
|
|
3783
5158
|
const result = await generatePdfFromContent(replaced);
|
|
3784
5159
|
const pages = await pdfToImages(result.pdfBytes);
|
|
@@ -3799,7 +5174,9 @@ function GeneratePanel({
|
|
|
3799
5174
|
setExportSuccess(null);
|
|
3800
5175
|
try {
|
|
3801
5176
|
const content = getActiveContent();
|
|
3802
|
-
const
|
|
5177
|
+
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, variableValues);
|
|
5178
|
+
const suppressed = suppressZeroContent(expanded, enrichedValues);
|
|
5179
|
+
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
3803
5180
|
const fields = extractFieldsFromContent(replaced);
|
|
3804
5181
|
const result = await generatePdfFromContent(replaced, {
|
|
3805
5182
|
drawFieldPlaceholders: false,
|
|
@@ -3807,7 +5184,7 @@ function GeneratePanel({
|
|
|
3807
5184
|
fields
|
|
3808
5185
|
});
|
|
3809
5186
|
const blob = new Blob([result.pdfBytes], { type: "application/pdf" });
|
|
3810
|
-
const fileName = importedFileName ? importedFileName.replace(".md", ".pdf") : "document.pdf";
|
|
5187
|
+
const fileName = exportFileName ? exportFileName.endsWith(".pdf") ? exportFileName : `${exportFileName}.pdf` : importedFileName ? importedFileName.replace(".md", ".pdf") : "document.pdf";
|
|
3811
5188
|
if (onGeneratePdf) {
|
|
3812
5189
|
onGeneratePdf(blob, fileName);
|
|
3813
5190
|
} else {
|
|
@@ -3879,7 +5256,9 @@ function GeneratePanel({
|
|
|
3879
5256
|
const allWarnings = [];
|
|
3880
5257
|
for (let i = 0; i < bulkData.length; i++) {
|
|
3881
5258
|
const values = bulkData[i];
|
|
3882
|
-
const
|
|
5259
|
+
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, values);
|
|
5260
|
+
const suppressed = suppressZeroContent(expanded, enrichedValues);
|
|
5261
|
+
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
3883
5262
|
const fields = extractFieldsFromContent(replaced);
|
|
3884
5263
|
const result = await generatePdfFromContent(replaced, {
|
|
3885
5264
|
drawFieldPlaceholders: false,
|
|
@@ -3934,13 +5313,15 @@ ${allWarnings.join("\n")}`);
|
|
|
3934
5313
|
}, [mode, bulkInputFormat, bulkInput, initialBulkJson]);
|
|
3935
5314
|
React12__namespace.default.useEffect(() => {
|
|
3936
5315
|
if (templateSource === "editor") {
|
|
3937
|
-
const defaults = {};
|
|
5316
|
+
const defaults = initialVariableValues ? { ...initialVariableValues } : {};
|
|
3938
5317
|
for (const v of editorVariables) {
|
|
3939
|
-
defaults[v.varName]
|
|
5318
|
+
if (!defaults[v.varName]) {
|
|
5319
|
+
defaults[v.varName] = variableValues[v.varName] || v.varDefault || "";
|
|
5320
|
+
}
|
|
3940
5321
|
}
|
|
3941
5322
|
setVariableValues(defaults);
|
|
3942
5323
|
}
|
|
3943
|
-
}, [editorVariables, templateSource]);
|
|
5324
|
+
}, [editorVariables, templateSource, initialVariableValues]);
|
|
3944
5325
|
React12.useEffect(() => {
|
|
3945
5326
|
setPreviewFresh(false);
|
|
3946
5327
|
}, [variableValues, templateSource, importedMarkdown, editorMarkdown, editorContent]);
|
|
@@ -3952,8 +5333,8 @@ ${allWarnings.join("\n")}`);
|
|
|
3952
5333
|
}, [exportSuccess]);
|
|
3953
5334
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-h-0 grid grid-cols-[1fr_1px_1fr]", children: [
|
|
3954
5335
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col min-h-0 min-w-0", children: [
|
|
3955
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto p-4 space-y-4", children: [
|
|
3956
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
5336
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto p-4 space-y-4 scrollbar-hidden", children: [
|
|
5337
|
+
!templateMode && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3957
5338
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
|
|
3958
5339
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-medium", children: "Template" }),
|
|
3959
5340
|
templateSource === "imported" && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -4046,10 +5427,10 @@ ${allWarnings.join("\n")}`);
|
|
|
4046
5427
|
!hasContent && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center py-8 text-muted-foreground", children: [
|
|
4047
5428
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Braces, { size: 32, className: "mb-2 opacity-50" }),
|
|
4048
5429
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium", children: "No template content" }),
|
|
4049
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs mt-1", children: "Import a .md template above or add content in the Editor tab." })
|
|
5430
|
+
/* @__PURE__ */ jsxRuntime.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." })
|
|
4050
5431
|
] }),
|
|
4051
5432
|
hasVariables && hasContent && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border border-border rounded-lg overflow-hidden", children: [
|
|
4052
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2 px-3 py-2 bg-muted/30 border-b border-border", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5433
|
+
!templateMode && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2 px-3 py-2 bg-muted/30 border-b border-border", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4053
5434
|
ToggleGroup,
|
|
4054
5435
|
{
|
|
4055
5436
|
value: mode,
|
|
@@ -4061,20 +5442,53 @@ ${allWarnings.join("\n")}`);
|
|
|
4061
5442
|
}
|
|
4062
5443
|
) }),
|
|
4063
5444
|
mode === "single" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3 space-y-3", children: [
|
|
4064
|
-
/* @__PURE__ */ jsxRuntime.
|
|
4065
|
-
|
|
4066
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5445
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
5446
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: "Variables" }),
|
|
5447
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground", children: activeVariables.length })
|
|
5448
|
+
] }),
|
|
5449
|
+
activeVariables.length > 5 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
5450
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { size: 14, className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground" }),
|
|
4067
5451
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4068
5452
|
Input,
|
|
4069
5453
|
{
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
5454
|
+
value: variableSearch,
|
|
5455
|
+
onChange: (e) => setVariableSearch(e.target.value),
|
|
5456
|
+
placeholder: "Search variables...",
|
|
5457
|
+
className: "h-8 text-xs pl-8"
|
|
5458
|
+
}
|
|
5459
|
+
),
|
|
5460
|
+
variableSearch && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5461
|
+
"button",
|
|
5462
|
+
{
|
|
5463
|
+
type: "button",
|
|
5464
|
+
onClick: () => setVariableSearch(""),
|
|
5465
|
+
className: "absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground",
|
|
5466
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 12 })
|
|
4075
5467
|
}
|
|
4076
5468
|
)
|
|
4077
|
-
] },
|
|
5469
|
+
] }),
|
|
5470
|
+
filteredVariables.length === 0 && variableSearch.trim() && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground py-2 text-center", children: [
|
|
5471
|
+
"No variables match \u201C",
|
|
5472
|
+
variableSearch,
|
|
5473
|
+
"\u201D"
|
|
5474
|
+
] }),
|
|
5475
|
+
filteredVariables.map((v) => {
|
|
5476
|
+
const isLocked = lockedVarNames.has(v.varName);
|
|
5477
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
5478
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: `gen-${v.varName}`, className: "text-xs", children: v.varLabel || v.varName }),
|
|
5479
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5480
|
+
Input,
|
|
5481
|
+
{
|
|
5482
|
+
id: `gen-${v.varName}`,
|
|
5483
|
+
value: variableValues[v.varName] || "",
|
|
5484
|
+
onChange: (e) => updateVariableValue(v.varName, e.target.value),
|
|
5485
|
+
placeholder: v.varDefault || `Enter ${v.varLabel || v.varName}`,
|
|
5486
|
+
disabled: isLocked,
|
|
5487
|
+
className: cn("h-8 text-xs", isLocked && "opacity-60 cursor-not-allowed")
|
|
5488
|
+
}
|
|
5489
|
+
)
|
|
5490
|
+
] }, v.varName);
|
|
5491
|
+
})
|
|
4078
5492
|
] }),
|
|
4079
5493
|
mode === "bulk" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3 space-y-3", children: [
|
|
4080
5494
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
@@ -4263,56 +5677,60 @@ ${allWarnings.join("\n")}`);
|
|
|
4263
5677
|
] }),
|
|
4264
5678
|
!hasVariables && hasContent && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-3 rounded-md bg-muted/30 border border-border", children: /* @__PURE__ */ jsxRuntime.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.' }) })
|
|
4265
5679
|
] }),
|
|
4266
|
-
|
|
4267
|
-
/* @__PURE__ */ jsxRuntime.
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
5680
|
+
hasContent && /* @__PURE__ */ jsxRuntime.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: [
|
|
5681
|
+
mode === "single" || !hasVariables ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
5682
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5683
|
+
Button,
|
|
5684
|
+
{
|
|
5685
|
+
variant: "secondary",
|
|
5686
|
+
onClick: handlePreview,
|
|
5687
|
+
disabled: isGenerating,
|
|
5688
|
+
className: "h-10 px-4 font-semibold shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
5689
|
+
children: [
|
|
5690
|
+
isGenerating ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: 16 }),
|
|
5691
|
+
isGenerating ? "Generating..." : "Generate PDF"
|
|
5692
|
+
]
|
|
5693
|
+
}
|
|
5694
|
+
),
|
|
5695
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5696
|
+
Button,
|
|
5697
|
+
{
|
|
5698
|
+
onClick: handleExportSingle,
|
|
5699
|
+
disabled: !previewFresh || isExporting,
|
|
5700
|
+
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",
|
|
5701
|
+
children: [
|
|
5702
|
+
isExporting ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { size: 16 }),
|
|
5703
|
+
isExporting ? "Exporting..." : "Export PDF"
|
|
5704
|
+
]
|
|
5705
|
+
}
|
|
5706
|
+
)
|
|
5707
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4293
5708
|
Button,
|
|
4294
5709
|
{
|
|
4295
|
-
onClick:
|
|
4296
|
-
disabled: !
|
|
4297
|
-
className: "h-10 px-4 font-semibold bg-primary hover:bg-primary/90 text-primary-foreground shadow-
|
|
5710
|
+
onClick: handleBulkGenerate,
|
|
5711
|
+
disabled: isExporting || !bulkData || bulkData.length === 0,
|
|
5712
|
+
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",
|
|
4298
5713
|
children: [
|
|
4299
5714
|
isExporting ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { size: 16 }),
|
|
4300
|
-
isExporting ? "
|
|
5715
|
+
isExporting ? "Generating..." : `Generate All (${bulkData?.length || 0} PDFs)`
|
|
4301
5716
|
]
|
|
4302
5717
|
}
|
|
4303
|
-
)
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
]
|
|
4314
|
-
|
|
4315
|
-
|
|
5718
|
+
),
|
|
5719
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-1 ml-auto", children: [
|
|
5720
|
+
previewError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
5721
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
5722
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: previewError })
|
|
5723
|
+
] }),
|
|
5724
|
+
exportError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
5725
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
5726
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: exportError })
|
|
5727
|
+
] }),
|
|
5728
|
+
exportSuccess && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-emerald-600 max-w-[300px]", children: [
|
|
5729
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCircle2, { size: 14, className: "shrink-0" }),
|
|
5730
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: exportSuccess })
|
|
5731
|
+
] })
|
|
5732
|
+
] })
|
|
5733
|
+
] })
|
|
4316
5734
|
] }),
|
|
4317
5735
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-border" }),
|
|
4318
5736
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col min-h-0 min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4490,7 +5908,13 @@ function DocumentGeneratorInner({
|
|
|
4490
5908
|
showToolbar = true,
|
|
4491
5909
|
showGenerateTab = true,
|
|
4492
5910
|
onGeneratePdf,
|
|
4493
|
-
initialBulkData
|
|
5911
|
+
initialBulkData,
|
|
5912
|
+
defaultTab = "editor",
|
|
5913
|
+
initialVariableValues,
|
|
5914
|
+
predefinedVariables,
|
|
5915
|
+
exportFileName,
|
|
5916
|
+
templateMode,
|
|
5917
|
+
onSaveTemplate
|
|
4494
5918
|
}) {
|
|
4495
5919
|
const {
|
|
4496
5920
|
editor,
|
|
@@ -4517,7 +5941,7 @@ function DocumentGeneratorInner({
|
|
|
4517
5941
|
placeholder,
|
|
4518
5942
|
onChange
|
|
4519
5943
|
});
|
|
4520
|
-
const [activeTab, setActiveTab] = React12.useState(
|
|
5944
|
+
const [activeTab, setActiveTab] = React12.useState(defaultTab);
|
|
4521
5945
|
const [insertPopoverOpen, setInsertPopoverOpen] = React12.useState(false);
|
|
4522
5946
|
const [insertVarPopoverOpen, setInsertVarPopoverOpen] = React12.useState(false);
|
|
4523
5947
|
const [editFieldId, setEditFieldId] = React12.useState(null);
|
|
@@ -4600,6 +6024,21 @@ function DocumentGeneratorInner({
|
|
|
4600
6024
|
setExportSuccess("Document exported successfully");
|
|
4601
6025
|
}
|
|
4602
6026
|
}, [exportDocument, onExport]);
|
|
6027
|
+
const handleSaveTemplate = React12.useCallback(() => {
|
|
6028
|
+
const md = exportMarkdown();
|
|
6029
|
+
if (!md.trim()) {
|
|
6030
|
+
setMdExportError("Editor content is empty");
|
|
6031
|
+
return;
|
|
6032
|
+
}
|
|
6033
|
+
const validation = validateMarkdown(md);
|
|
6034
|
+
if (!validation.valid) {
|
|
6035
|
+
setMdExportError(validation.errors.join("; "));
|
|
6036
|
+
return;
|
|
6037
|
+
}
|
|
6038
|
+
setMdExportError(null);
|
|
6039
|
+
onSaveTemplate?.(md);
|
|
6040
|
+
setExportSuccess("Template saved");
|
|
6041
|
+
}, [exportMarkdown, onSaveTemplate]);
|
|
4603
6042
|
const handleExportMarkdown = React12.useCallback(() => {
|
|
4604
6043
|
const md = exportMarkdown();
|
|
4605
6044
|
if (!md.trim()) {
|
|
@@ -4616,10 +6055,10 @@ function DocumentGeneratorInner({
|
|
|
4616
6055
|
const url = URL.createObjectURL(blob);
|
|
4617
6056
|
const a = document.createElement("a");
|
|
4618
6057
|
a.href = url;
|
|
4619
|
-
a.download = "template.md";
|
|
6058
|
+
a.download = exportFileName ? `${exportFileName}.md` : "template.md";
|
|
4620
6059
|
a.click();
|
|
4621
6060
|
URL.revokeObjectURL(url);
|
|
4622
|
-
}, [exportMarkdown]);
|
|
6061
|
+
}, [exportMarkdown, exportFileName]);
|
|
4623
6062
|
React12.useEffect(() => {
|
|
4624
6063
|
if (mdExportError) setMdExportError(null);
|
|
4625
6064
|
}, [markdown]);
|
|
@@ -4655,6 +6094,7 @@ function DocumentGeneratorInner({
|
|
|
4655
6094
|
open: insertVarPopoverOpen,
|
|
4656
6095
|
onOpenChange: setInsertVarPopoverOpen,
|
|
4657
6096
|
onInsert: handleInsertVariable,
|
|
6097
|
+
predefinedVariables,
|
|
4658
6098
|
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4659
6099
|
Button,
|
|
4660
6100
|
{
|
|
@@ -4695,118 +6135,141 @@ function DocumentGeneratorInner({
|
|
|
4695
6135
|
] })
|
|
4696
6136
|
] })
|
|
4697
6137
|
] }),
|
|
4698
|
-
/* @__PURE__ */ jsxRuntime.
|
|
6138
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(
|
|
4699
6139
|
"flex flex-col flex-1 min-h-0",
|
|
4700
6140
|
activeTab !== "editor" && "hidden"
|
|
6141
|
+
), children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(
|
|
6142
|
+
"flex-1 min-h-0",
|
|
6143
|
+
showPreview && !editorCollapsed ? "grid grid-cols-[1fr_1px_1fr]" : "flex flex-col"
|
|
4701
6144
|
), children: [
|
|
4702
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className:
|
|
4703
|
-
"
|
|
4704
|
-
|
|
4705
|
-
), children: [
|
|
4706
|
-
!editorCollapsed && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col min-h-0 min-w-0", children: [
|
|
4707
|
-
showToolbar && !readOnly && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-border", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4708
|
-
EditorToolbar,
|
|
4709
|
-
{
|
|
4710
|
-
editor,
|
|
4711
|
-
insertFieldButton,
|
|
4712
|
-
insertVariableButton,
|
|
4713
|
-
onCollapse: showPreview ? () => setEditorCollapsed(true) : void 0
|
|
4714
|
-
}
|
|
4715
|
-
) }),
|
|
4716
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: editorWrapperRef, className: "flex-1 overflow-y-auto min-h-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4717
|
-
react.EditorContent,
|
|
4718
|
-
{
|
|
4719
|
-
editor,
|
|
4720
|
-
className: "prose prose-sm max-w-none p-4 focus-within:outline-none"
|
|
4721
|
-
}
|
|
4722
|
-
) })
|
|
4723
|
-
] }),
|
|
4724
|
-
showPreview && !editorCollapsed && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-border" }),
|
|
4725
|
-
showPreview && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4726
|
-
PreviewPanel,
|
|
6145
|
+
!editorCollapsed && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col min-h-0 min-w-0", children: [
|
|
6146
|
+
showToolbar && !readOnly && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-border", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
6147
|
+
EditorToolbar,
|
|
4727
6148
|
{
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
Button,
|
|
4733
|
-
{
|
|
4734
|
-
variant: "ghost",
|
|
4735
|
-
size: "sm",
|
|
4736
|
-
className: "h-8 w-8 p-0",
|
|
4737
|
-
onClick: () => setEditorCollapsed(false),
|
|
4738
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PanelLeftOpen, { size: 14 })
|
|
4739
|
-
}
|
|
4740
|
-
) : void 0,
|
|
4741
|
-
className: "flex-1 border-0 rounded-none"
|
|
6149
|
+
editor,
|
|
6150
|
+
insertFieldButton,
|
|
6151
|
+
insertVariableButton,
|
|
6152
|
+
onCollapse: showPreview ? () => setEditorCollapsed(true) : void 0
|
|
4742
6153
|
}
|
|
4743
|
-
)
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4747
|
-
Button,
|
|
6154
|
+
) }),
|
|
6155
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: editorWrapperRef, className: "flex-1 overflow-y-auto min-h-0 scrollbar-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
6156
|
+
react.EditorContent,
|
|
4748
6157
|
{
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
disabled: isGenerating || !editor,
|
|
4752
|
-
className: "h-10 px-4 font-semibold shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
4753
|
-
children: [
|
|
4754
|
-
isGenerating ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: 16 }),
|
|
4755
|
-
isGenerating ? "Generating..." : "Generate PDF"
|
|
4756
|
-
]
|
|
6158
|
+
editor,
|
|
6159
|
+
className: "prose prose-sm max-w-none p-4 focus-within:outline-none"
|
|
4757
6160
|
}
|
|
4758
|
-
),
|
|
4759
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex
|
|
4760
|
-
mdExportError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
4761
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
4762
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: mdExportError })
|
|
4763
|
-
] }),
|
|
4764
|
-
generationError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
4765
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
4766
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: generationError })
|
|
4767
|
-
] }),
|
|
4768
|
-
fieldWarnings.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-orange-600 max-w-[400px]", children: [
|
|
4769
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
4770
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "truncate", children: [
|
|
4771
|
-
fieldWarnings.length,
|
|
4772
|
-
" field",
|
|
4773
|
-
fieldWarnings.length !== 1 ? "s" : "",
|
|
4774
|
-
" had warnings during generation"
|
|
4775
|
-
] })
|
|
4776
|
-
] }),
|
|
4777
|
-
exportSuccess && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-emerald-600 max-w-[300px]", children: [
|
|
4778
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCircle2, { size: 14, className: "shrink-0" }),
|
|
4779
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: exportSuccess })
|
|
4780
|
-
] })
|
|
4781
|
-
] }),
|
|
4782
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
6161
|
+
) }),
|
|
6162
|
+
/* @__PURE__ */ jsxRuntime.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: [
|
|
4783
6163
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4784
6164
|
Button,
|
|
4785
6165
|
{
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
6166
|
+
variant: "secondary",
|
|
6167
|
+
onClick: generatePdf,
|
|
6168
|
+
disabled: isGenerating || !editor,
|
|
6169
|
+
className: "h-10 px-4 font-semibold shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
4789
6170
|
children: [
|
|
4790
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.
|
|
4791
|
-
"
|
|
6171
|
+
isGenerating ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: 16 }),
|
|
6172
|
+
isGenerating ? "Generating..." : "Generate PDF"
|
|
4792
6173
|
]
|
|
4793
6174
|
}
|
|
4794
6175
|
),
|
|
4795
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6176
|
+
onSaveTemplate ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4796
6177
|
Button,
|
|
4797
6178
|
{
|
|
4798
|
-
onClick:
|
|
4799
|
-
disabled:
|
|
6179
|
+
onClick: handleSaveTemplate,
|
|
6180
|
+
disabled: !editor || !markdown.trim(),
|
|
4800
6181
|
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",
|
|
4801
6182
|
children: [
|
|
4802
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.
|
|
4803
|
-
exportButtonText
|
|
6183
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Save, { size: 16 }),
|
|
6184
|
+
exportButtonText || "Save Template"
|
|
4804
6185
|
]
|
|
4805
6186
|
}
|
|
4806
|
-
)
|
|
6187
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs(Popover, { children: [
|
|
6188
|
+
/* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6189
|
+
Button,
|
|
6190
|
+
{
|
|
6191
|
+
disabled: !editor || !markdown.trim() && pdfPages.length === 0,
|
|
6192
|
+
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",
|
|
6193
|
+
children: [
|
|
6194
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileDown, { size: 16 }),
|
|
6195
|
+
"Export",
|
|
6196
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { size: 14 })
|
|
6197
|
+
]
|
|
6198
|
+
}
|
|
6199
|
+
) }),
|
|
6200
|
+
/* @__PURE__ */ jsxRuntime.jsxs(PopoverContent, { align: "start", className: "w-48 p-1", children: [
|
|
6201
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6202
|
+
"button",
|
|
6203
|
+
{
|
|
6204
|
+
onClick: handleExportMarkdown,
|
|
6205
|
+
disabled: !editor || !markdown.trim(),
|
|
6206
|
+
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",
|
|
6207
|
+
children: [
|
|
6208
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileText, { size: 16 }),
|
|
6209
|
+
"Export as MD"
|
|
6210
|
+
]
|
|
6211
|
+
}
|
|
6212
|
+
),
|
|
6213
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6214
|
+
"button",
|
|
6215
|
+
{
|
|
6216
|
+
onClick: handleExport,
|
|
6217
|
+
disabled: isGenerating || !editor || pdfPages.length === 0,
|
|
6218
|
+
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",
|
|
6219
|
+
children: [
|
|
6220
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileDown, { size: 16 }),
|
|
6221
|
+
"Export as PDF"
|
|
6222
|
+
]
|
|
6223
|
+
}
|
|
6224
|
+
)
|
|
6225
|
+
] })
|
|
6226
|
+
] }),
|
|
6227
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-1 ml-auto", children: [
|
|
6228
|
+
mdExportError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
6229
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
6230
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: mdExportError })
|
|
6231
|
+
] }),
|
|
6232
|
+
generationError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
6233
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
6234
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: generationError })
|
|
6235
|
+
] }),
|
|
6236
|
+
fieldWarnings.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-orange-600 max-w-[400px]", children: [
|
|
6237
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
6238
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "truncate", children: [
|
|
6239
|
+
fieldWarnings.length,
|
|
6240
|
+
" field",
|
|
6241
|
+
fieldWarnings.length !== 1 ? "s" : "",
|
|
6242
|
+
" had warnings during generation"
|
|
6243
|
+
] })
|
|
6244
|
+
] }),
|
|
6245
|
+
exportSuccess && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-emerald-600 max-w-[300px]", children: [
|
|
6246
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCircle2, { size: 14, className: "shrink-0" }),
|
|
6247
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: exportSuccess })
|
|
6248
|
+
] })
|
|
6249
|
+
] })
|
|
4807
6250
|
] })
|
|
4808
|
-
] })
|
|
4809
|
-
|
|
6251
|
+
] }),
|
|
6252
|
+
showPreview && !editorCollapsed && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-border" }),
|
|
6253
|
+
showPreview && /* @__PURE__ */ jsxRuntime.jsx(
|
|
6254
|
+
PreviewPanel,
|
|
6255
|
+
{
|
|
6256
|
+
pages: pdfPages,
|
|
6257
|
+
isGenerating,
|
|
6258
|
+
positionedFields,
|
|
6259
|
+
headerLeft: editorCollapsed ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
6260
|
+
Button,
|
|
6261
|
+
{
|
|
6262
|
+
variant: "ghost",
|
|
6263
|
+
size: "sm",
|
|
6264
|
+
className: "h-8 w-8 p-0",
|
|
6265
|
+
onClick: () => setEditorCollapsed(false),
|
|
6266
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PanelLeftOpen, { size: 14 })
|
|
6267
|
+
}
|
|
6268
|
+
) : void 0,
|
|
6269
|
+
className: "flex-1 border-0 rounded-none"
|
|
6270
|
+
}
|
|
6271
|
+
)
|
|
6272
|
+
] }) }),
|
|
4810
6273
|
showGenerateTab && activeTab === "generate" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4811
6274
|
GeneratePanel,
|
|
4812
6275
|
{
|
|
@@ -4814,7 +6277,10 @@ function DocumentGeneratorInner({
|
|
|
4814
6277
|
editorContent: editor?.getJSON(),
|
|
4815
6278
|
editorVariables: variables,
|
|
4816
6279
|
onGeneratePdf,
|
|
4817
|
-
initialBulkData
|
|
6280
|
+
initialBulkData,
|
|
6281
|
+
initialVariableValues,
|
|
6282
|
+
exportFileName,
|
|
6283
|
+
templateMode
|
|
4818
6284
|
}
|
|
4819
6285
|
),
|
|
4820
6286
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4955,11 +6421,733 @@ function EditorPanel({
|
|
|
4955
6421
|
] });
|
|
4956
6422
|
}
|
|
4957
6423
|
|
|
6424
|
+
// src/utils/xml-template-parser.ts
|
|
6425
|
+
function extractFieldRefs(expr) {
|
|
6426
|
+
const refs = [];
|
|
6427
|
+
const re = /\[([^\]]+)\](?:\.\[([^\]]+)\])?/g;
|
|
6428
|
+
let m;
|
|
6429
|
+
while ((m = re.exec(expr)) !== null) {
|
|
6430
|
+
if (m[2]) {
|
|
6431
|
+
refs.push(`${m[1]}.${m[2]}`);
|
|
6432
|
+
} else {
|
|
6433
|
+
refs.push(m[1]);
|
|
6434
|
+
}
|
|
6435
|
+
}
|
|
6436
|
+
return refs;
|
|
6437
|
+
}
|
|
6438
|
+
function fieldRefToVarName(ref, bandContext) {
|
|
6439
|
+
if (ref.includes(".")) {
|
|
6440
|
+
return labelToVarName(ref.replace(/\./g, "_"));
|
|
6441
|
+
}
|
|
6442
|
+
if (bandContext) {
|
|
6443
|
+
return labelToVarName(bandContext + "_" + ref);
|
|
6444
|
+
}
|
|
6445
|
+
return labelToVarName(ref);
|
|
6446
|
+
}
|
|
6447
|
+
function fieldRefToLabel(ref) {
|
|
6448
|
+
const parts = ref.split(".");
|
|
6449
|
+
const raw = parts[parts.length - 1];
|
|
6450
|
+
return raw.replace(/_/g, " ");
|
|
6451
|
+
}
|
|
6452
|
+
function resolveExpression(expr, registry, siblingLabel, bandContext) {
|
|
6453
|
+
const trimmed = expr.trim();
|
|
6454
|
+
const suppressZero = /<>\s*0/.test(trimmed);
|
|
6455
|
+
const szOpts = suppressZero ? { suppressZero: true } : void 0;
|
|
6456
|
+
const aggMatch = trimmed.match(
|
|
6457
|
+
/^\[([^\]]+)\]\.sum\(\[([^\]]+)\]\)$/i
|
|
6458
|
+
);
|
|
6459
|
+
if (aggMatch) {
|
|
6460
|
+
const fieldName = aggMatch[2];
|
|
6461
|
+
const varName = labelToVarName("workorder_" + fieldName);
|
|
6462
|
+
const label = fieldName.replace(/_/g, " ");
|
|
6463
|
+
return registry.register(varName, label, szOpts);
|
|
6464
|
+
}
|
|
6465
|
+
const concatSingle = trimmed.match(
|
|
6466
|
+
/^concat\(\s*'[^']*'\s*,\s*\[([^\]]+)\](?:\.\[([^\]]+)\])?\s*\)$/i
|
|
6467
|
+
);
|
|
6468
|
+
if (concatSingle) {
|
|
6469
|
+
const ref = concatSingle[2] ? `${concatSingle[1]}.${concatSingle[2]}` : concatSingle[1];
|
|
6470
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6471
|
+
const label = fieldRefToLabel(ref);
|
|
6472
|
+
return registry.register(varName, label, szOpts);
|
|
6473
|
+
}
|
|
6474
|
+
const concatMulti = trimmed.match(
|
|
6475
|
+
/^concat\(/i
|
|
6476
|
+
);
|
|
6477
|
+
if (concatMulti) {
|
|
6478
|
+
const refs = extractFieldRefs(trimmed);
|
|
6479
|
+
if (refs.length > 0) {
|
|
6480
|
+
const uniqueRefs = [...new Set(refs)];
|
|
6481
|
+
const tokens = uniqueRefs.map((ref) => {
|
|
6482
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6483
|
+
const label = fieldRefToLabel(ref);
|
|
6484
|
+
return registry.register(varName, label, szOpts);
|
|
6485
|
+
});
|
|
6486
|
+
return tokens.join(" ");
|
|
6487
|
+
}
|
|
6488
|
+
}
|
|
6489
|
+
const toLongMatch = trimmed.match(
|
|
6490
|
+
/^ToLong\(\s*\[([^\]]+)\](?:\.\[([^\]]+)\])?\s*\)$/i
|
|
6491
|
+
);
|
|
6492
|
+
if (toLongMatch) {
|
|
6493
|
+
const ref = toLongMatch[2] ? `${toLongMatch[1]}.${toLongMatch[2]}` : toLongMatch[1];
|
|
6494
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6495
|
+
const label = fieldRefToLabel(ref);
|
|
6496
|
+
return registry.register(varName, label, szOpts);
|
|
6497
|
+
}
|
|
6498
|
+
const fmtMatch = trimmed.match(
|
|
6499
|
+
/^FormatString\(\s*'[^']*'\s*,\s*(.+)\s*\)$/i
|
|
6500
|
+
);
|
|
6501
|
+
if (fmtMatch) {
|
|
6502
|
+
return resolveExpression(fmtMatch[1], registry, siblingLabel, bandContext);
|
|
6503
|
+
}
|
|
6504
|
+
const replaceMatch = trimmed.match(
|
|
6505
|
+
/^Replace\(\s*\[([^\]]+)\]/i
|
|
6506
|
+
);
|
|
6507
|
+
if (replaceMatch) {
|
|
6508
|
+
const ref = replaceMatch[1];
|
|
6509
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6510
|
+
const label = fieldRefToLabel(ref);
|
|
6511
|
+
return registry.register(varName, label, szOpts);
|
|
6512
|
+
}
|
|
6513
|
+
const iifLabelMatch = trimmed.match(
|
|
6514
|
+
/^iif\s*\(.+?,\s*'([^']+)'\s*,\s*\?\s*\)$/i
|
|
6515
|
+
);
|
|
6516
|
+
if (iifLabelMatch) {
|
|
6517
|
+
return iifLabelMatch[1];
|
|
6518
|
+
}
|
|
6519
|
+
const iifLabelElse = trimmed.match(
|
|
6520
|
+
/^iif\s*\(.+?,\s*'([^']+)'\s*,\s*'([^']+)'\s*\)$/i
|
|
6521
|
+
);
|
|
6522
|
+
if (iifLabelElse) {
|
|
6523
|
+
return iifLabelElse[1];
|
|
6524
|
+
}
|
|
6525
|
+
const iifEmptyElse = trimmed.match(
|
|
6526
|
+
/^[Ii]if\s*\(.+?,\s*'([^']+)'\s*,\s*''\s*\)$/i
|
|
6527
|
+
);
|
|
6528
|
+
if (iifEmptyElse) {
|
|
6529
|
+
return iifEmptyElse[1];
|
|
6530
|
+
}
|
|
6531
|
+
const nestedIifStaticLabel = trimmed.match(
|
|
6532
|
+
/^iif\s*\(.*,\s*[Ii]if\s*\([^,]+,\s*'([^']+)'\s*,/i
|
|
6533
|
+
);
|
|
6534
|
+
if (nestedIifStaticLabel) {
|
|
6535
|
+
return nestedIifStaticLabel[1];
|
|
6536
|
+
}
|
|
6537
|
+
const iifValueMatch = trimmed.match(
|
|
6538
|
+
/^iif\s*\(/i
|
|
6539
|
+
);
|
|
6540
|
+
if (iifValueMatch) {
|
|
6541
|
+
const refs = extractFieldRefs(trimmed);
|
|
6542
|
+
if (refs.length > 0) {
|
|
6543
|
+
const uniqueRefs = [...new Set(refs)];
|
|
6544
|
+
const innerAggMatch = trimmed.match(
|
|
6545
|
+
/\[([^\]]+)\]\.sum\(\[([^\]]+)\]\)/i
|
|
6546
|
+
);
|
|
6547
|
+
if (innerAggMatch) {
|
|
6548
|
+
const fieldName = innerAggMatch[2];
|
|
6549
|
+
const varName2 = labelToVarName("workorder_" + fieldName);
|
|
6550
|
+
const label2 = fieldName.replace(/_/g, " ");
|
|
6551
|
+
return registry.register(varName2, label2, szOpts);
|
|
6552
|
+
}
|
|
6553
|
+
const ref = uniqueRefs[uniqueRefs.length - 1];
|
|
6554
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6555
|
+
const label = fieldRefToLabel(ref);
|
|
6556
|
+
return registry.register(varName, label, szOpts);
|
|
6557
|
+
}
|
|
6558
|
+
const staticMatch = trimmed.match(/'([^']+)'/);
|
|
6559
|
+
if (staticMatch) return staticMatch[1];
|
|
6560
|
+
return "";
|
|
6561
|
+
}
|
|
6562
|
+
const arithmeticRefs = extractFieldRefs(trimmed);
|
|
6563
|
+
if (arithmeticRefs.length > 1 && /[+\-*/]/.test(trimmed)) {
|
|
6564
|
+
if (siblingLabel) {
|
|
6565
|
+
const cleanLabel = siblingLabel.replace(/[:\s]+$/, "");
|
|
6566
|
+
const varName = labelToVarName(
|
|
6567
|
+
(bandContext ? bandContext + "_" : "") + cleanLabel.replace(/\s+/g, "_")
|
|
6568
|
+
);
|
|
6569
|
+
return registry.register(varName, cleanLabel, szOpts);
|
|
6570
|
+
}
|
|
6571
|
+
const uniqueRefs = [...new Set(arithmeticRefs)];
|
|
6572
|
+
const tokens = uniqueRefs.map((ref) => {
|
|
6573
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6574
|
+
const label = fieldRefToLabel(ref);
|
|
6575
|
+
return registry.register(varName, label, szOpts);
|
|
6576
|
+
});
|
|
6577
|
+
return tokens.join(" ");
|
|
6578
|
+
}
|
|
6579
|
+
const simpleMatch = trimmed.match(
|
|
6580
|
+
/^\[([^\]]+)\](?:\.\[([^\]]+)\])?$/
|
|
6581
|
+
);
|
|
6582
|
+
if (simpleMatch) {
|
|
6583
|
+
const ref = simpleMatch[2] ? `${simpleMatch[1]}.${simpleMatch[2]}` : simpleMatch[1];
|
|
6584
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6585
|
+
const label = fieldRefToLabel(ref);
|
|
6586
|
+
return registry.register(varName, label, szOpts);
|
|
6587
|
+
}
|
|
6588
|
+
if (arithmeticRefs.length >= 1) {
|
|
6589
|
+
const ref = arithmeticRefs[arithmeticRefs.length - 1];
|
|
6590
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6591
|
+
const label = fieldRefToLabel(ref);
|
|
6592
|
+
return registry.register(varName, label, szOpts);
|
|
6593
|
+
}
|
|
6594
|
+
return "";
|
|
6595
|
+
}
|
|
6596
|
+
var VariableRegistry = class {
|
|
6597
|
+
constructor() {
|
|
6598
|
+
__publicField(this, "map", /* @__PURE__ */ new Map());
|
|
6599
|
+
}
|
|
6600
|
+
register(varName, varLabel, options) {
|
|
6601
|
+
if (!this.map.has(varName)) {
|
|
6602
|
+
this.map.set(varName, { varName, varLabel, varDefault: "" });
|
|
6603
|
+
}
|
|
6604
|
+
const suppress = options?.suppressZero ? "|suppress:zero" : "";
|
|
6605
|
+
return `{{var|name:${varName}|label:${varLabel}${suppress}}}`;
|
|
6606
|
+
}
|
|
6607
|
+
getAll() {
|
|
6608
|
+
return Array.from(this.map.values());
|
|
6609
|
+
}
|
|
6610
|
+
};
|
|
6611
|
+
function parseLocationFloat(s) {
|
|
6612
|
+
if (!s) return { x: 0, y: 0 };
|
|
6613
|
+
const parts = s.split(",");
|
|
6614
|
+
return {
|
|
6615
|
+
x: parseFloat(parts[0]) || 0,
|
|
6616
|
+
y: parseFloat(parts[1]) || 0
|
|
6617
|
+
};
|
|
6618
|
+
}
|
|
6619
|
+
function parseSizeF(s) {
|
|
6620
|
+
if (!s) return { w: 0, h: 0 };
|
|
6621
|
+
const parts = s.split(",");
|
|
6622
|
+
return {
|
|
6623
|
+
w: parseFloat(parts[0]) || 0,
|
|
6624
|
+
h: parseFloat(parts[1]) || 0
|
|
6625
|
+
};
|
|
6626
|
+
}
|
|
6627
|
+
function extractControls(bandEl) {
|
|
6628
|
+
const controlsEl = bandEl.querySelector(":scope > Controls");
|
|
6629
|
+
if (!controlsEl) return [];
|
|
6630
|
+
const items = [];
|
|
6631
|
+
for (const child of Array.from(controlsEl.children)) {
|
|
6632
|
+
items.push(extractControl(child));
|
|
6633
|
+
}
|
|
6634
|
+
return items;
|
|
6635
|
+
}
|
|
6636
|
+
function extractControl(el) {
|
|
6637
|
+
const ctrlType = el.getAttribute("ControlType") || "";
|
|
6638
|
+
const name = el.getAttribute("Name") || "";
|
|
6639
|
+
const text = el.getAttribute("Text") || "";
|
|
6640
|
+
const loc = parseLocationFloat(el.getAttribute("LocationFloat"));
|
|
6641
|
+
const size = parseSizeF(el.getAttribute("SizeF"));
|
|
6642
|
+
const formatString = el.getAttribute("TextFormatString") || null;
|
|
6643
|
+
let expression = null;
|
|
6644
|
+
const bindingsEl = el.querySelector(":scope > ExpressionBindings");
|
|
6645
|
+
if (bindingsEl) {
|
|
6646
|
+
for (const item of Array.from(bindingsEl.children)) {
|
|
6647
|
+
if (item.getAttribute("PropertyName") === "Text" && item.getAttribute("EventName") === "BeforePrint") {
|
|
6648
|
+
expression = item.getAttribute("Expression");
|
|
6649
|
+
break;
|
|
6650
|
+
}
|
|
6651
|
+
}
|
|
6652
|
+
}
|
|
6653
|
+
const children = [];
|
|
6654
|
+
if (ctrlType === "XRPanel") {
|
|
6655
|
+
const panelControls = extractControls(el);
|
|
6656
|
+
children.push(...panelControls);
|
|
6657
|
+
}
|
|
6658
|
+
return {
|
|
6659
|
+
name,
|
|
6660
|
+
type: ctrlType,
|
|
6661
|
+
text,
|
|
6662
|
+
x: loc.x,
|
|
6663
|
+
y: loc.y,
|
|
6664
|
+
width: size.w,
|
|
6665
|
+
expression,
|
|
6666
|
+
formatString,
|
|
6667
|
+
children
|
|
6668
|
+
};
|
|
6669
|
+
}
|
|
6670
|
+
function groupByRow(controls, threshold = 8) {
|
|
6671
|
+
const sorted = [...controls].sort((a, b) => a.y - b.y);
|
|
6672
|
+
const rows = [];
|
|
6673
|
+
for (const ctrl of sorted) {
|
|
6674
|
+
const lastRow = rows[rows.length - 1];
|
|
6675
|
+
if (lastRow && Math.abs(ctrl.y - lastRow.y) < threshold) {
|
|
6676
|
+
lastRow.controls.push(ctrl);
|
|
6677
|
+
} else {
|
|
6678
|
+
rows.push({ y: ctrl.y, controls: [ctrl] });
|
|
6679
|
+
}
|
|
6680
|
+
}
|
|
6681
|
+
for (const row of rows) {
|
|
6682
|
+
row.controls.sort((a, b) => a.x - b.x);
|
|
6683
|
+
}
|
|
6684
|
+
return rows;
|
|
6685
|
+
}
|
|
6686
|
+
function isStaticLabel(ctrl) {
|
|
6687
|
+
return ctrl.type === "XRLabel" && !ctrl.expression;
|
|
6688
|
+
}
|
|
6689
|
+
function isBound(ctrl) {
|
|
6690
|
+
return ctrl.type === "XRLabel" && !!ctrl.expression;
|
|
6691
|
+
}
|
|
6692
|
+
function computeSplitPercent(leftControls, rightControls, contentWidth) {
|
|
6693
|
+
const leftExtent = leftControls.reduce(
|
|
6694
|
+
(max, c) => Math.max(max, c.x + c.width),
|
|
6695
|
+
0
|
|
6696
|
+
);
|
|
6697
|
+
const rightMinX = rightControls.reduce(
|
|
6698
|
+
(min, c) => Math.min(min, c.x),
|
|
6699
|
+
contentWidth
|
|
6700
|
+
);
|
|
6701
|
+
const rightMaxExtent = rightControls.reduce(
|
|
6702
|
+
(max, c) => Math.max(max, c.x + c.width),
|
|
6703
|
+
0
|
|
6704
|
+
);
|
|
6705
|
+
const rightExtent = rightMaxExtent - rightMinX;
|
|
6706
|
+
if (leftExtent + rightExtent === 0) return 50;
|
|
6707
|
+
return Math.round(leftExtent / (leftExtent + rightExtent) * 100);
|
|
6708
|
+
}
|
|
6709
|
+
function renderTopMarginBand(controls, registry, contentWidth, calculatedFieldNames) {
|
|
6710
|
+
const lines = [];
|
|
6711
|
+
const leftControls = controls.filter((c) => c.x < 437);
|
|
6712
|
+
const rightControls = controls.filter((c) => c.x >= 437);
|
|
6713
|
+
const leftEntries = [];
|
|
6714
|
+
if (leftControls.length > 0) {
|
|
6715
|
+
const leftRows = groupByRow(leftControls);
|
|
6716
|
+
for (const row of leftRows) {
|
|
6717
|
+
const parts = [];
|
|
6718
|
+
for (const ctrl of row.controls) {
|
|
6719
|
+
if (isBound(ctrl)) {
|
|
6720
|
+
let resolved = resolveExpression(ctrl.expression, registry, void 0, "workorder");
|
|
6721
|
+
if (ctrl.formatString && /\(###\)\s*###\s*-\s*####/.test(ctrl.formatString) && resolved.startsWith("{{var|")) {
|
|
6722
|
+
resolved = resolved.replace("}}", "|format:phone}}");
|
|
6723
|
+
}
|
|
6724
|
+
if (resolved) parts.push(resolved);
|
|
6725
|
+
} else if (isStaticLabel(ctrl)) {
|
|
6726
|
+
const text = ctrl.text.trim();
|
|
6727
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
6728
|
+
parts.push(text);
|
|
6729
|
+
}
|
|
6730
|
+
}
|
|
6731
|
+
}
|
|
6732
|
+
if (parts.length > 0) {
|
|
6733
|
+
leftEntries.push(parts.join(" "));
|
|
6734
|
+
}
|
|
6735
|
+
}
|
|
6736
|
+
}
|
|
6737
|
+
const rightEntries = [];
|
|
6738
|
+
if (rightControls.length > 0) {
|
|
6739
|
+
const rightRows = groupByRow(rightControls);
|
|
6740
|
+
for (const row of rightRows) {
|
|
6741
|
+
let label = "";
|
|
6742
|
+
let value = "";
|
|
6743
|
+
for (const ctrl of row.controls) {
|
|
6744
|
+
if (isStaticLabel(ctrl)) {
|
|
6745
|
+
const text = ctrl.text.trim();
|
|
6746
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
6747
|
+
label = text;
|
|
6748
|
+
}
|
|
6749
|
+
} else if (isBound(ctrl)) {
|
|
6750
|
+
value = resolveExpression(ctrl.expression, registry, label, "workorder");
|
|
6751
|
+
if (ctrl.expression) {
|
|
6752
|
+
const refs = extractFieldRefs(ctrl.expression);
|
|
6753
|
+
if (refs.some((ref) => calculatedFieldNames.has(ref)) && value.startsWith("{{var|")) {
|
|
6754
|
+
value = value.replace("}}", "|suppress:zero}}");
|
|
6755
|
+
}
|
|
6756
|
+
}
|
|
6757
|
+
if (ctrl.formatString && /\(###\)\s*###\s*-\s*####/.test(ctrl.formatString) && value.startsWith("{{var|")) {
|
|
6758
|
+
value = value.replace("}}", "|format:phone}}");
|
|
6759
|
+
}
|
|
6760
|
+
}
|
|
6761
|
+
}
|
|
6762
|
+
if (label || value) {
|
|
6763
|
+
rightEntries.push({ label, value, y: row.y });
|
|
6764
|
+
}
|
|
6765
|
+
}
|
|
6766
|
+
}
|
|
6767
|
+
if (leftEntries.length > 0 || rightEntries.length > 0) {
|
|
6768
|
+
const outerSplit = computeSplitPercent(leftControls, rightControls, contentWidth);
|
|
6769
|
+
lines.push(`:::columns{split:${outerSplit}}`);
|
|
6770
|
+
const leftMinY = leftControls.length > 0 ? Math.min(...leftControls.map((c) => c.y)) : 0;
|
|
6771
|
+
const rightMinY = rightControls.length > 0 ? Math.min(...rightControls.map((c) => c.y)) : 0;
|
|
6772
|
+
const xmlLineHeight = 17;
|
|
6773
|
+
const leftPadTop = leftMinY > rightMinY ? Math.round((leftMinY - rightMinY) / xmlLineHeight) : 0;
|
|
6774
|
+
const rightPadTop = rightMinY > leftMinY ? Math.round((rightMinY - leftMinY) / xmlLineHeight) : 0;
|
|
6775
|
+
const leftColTag = leftPadTop ? `:::col{padTop:${leftPadTop}}` : ":::col";
|
|
6776
|
+
lines.push(leftColTag);
|
|
6777
|
+
for (const entry of leftEntries) {
|
|
6778
|
+
lines.push(entry);
|
|
6779
|
+
}
|
|
6780
|
+
lines.push(":::");
|
|
6781
|
+
const rightColTag = rightPadTop ? `:::col{padTop:${rightPadTop}}` : ":::col";
|
|
6782
|
+
lines.push(rightColTag);
|
|
6783
|
+
if (rightEntries.length > 0) {
|
|
6784
|
+
lines.push("| | |");
|
|
6785
|
+
lines.push("|---|---|");
|
|
6786
|
+
let prevY = -Infinity;
|
|
6787
|
+
for (const entry of rightEntries) {
|
|
6788
|
+
if (prevY > -Infinity && entry.y - prevY > 25) {
|
|
6789
|
+
lines.push("| | |");
|
|
6790
|
+
}
|
|
6791
|
+
const label = entry.label || "";
|
|
6792
|
+
const colon = entry.label ? ":" : "";
|
|
6793
|
+
const value = entry.value ? ` ${entry.value}` : "";
|
|
6794
|
+
lines.push(`| ${label} | ${colon}${value} |`);
|
|
6795
|
+
prevY = entry.y;
|
|
6796
|
+
}
|
|
6797
|
+
}
|
|
6798
|
+
lines.push(":::");
|
|
6799
|
+
lines.push(":::");
|
|
6800
|
+
lines.push("");
|
|
6801
|
+
}
|
|
6802
|
+
return lines.join("\n");
|
|
6803
|
+
}
|
|
6804
|
+
function renderReportHeaderBand(controls, registry, contentWidth) {
|
|
6805
|
+
const lines = [];
|
|
6806
|
+
const leftControls = controls.filter((c) => c.x < 437);
|
|
6807
|
+
const rightControls = controls.filter((c) => c.x >= 437);
|
|
6808
|
+
const leftEntries = [];
|
|
6809
|
+
if (leftControls.length > 0) {
|
|
6810
|
+
const leftRows = groupByRow(leftControls);
|
|
6811
|
+
for (const row of leftRows) {
|
|
6812
|
+
const parts = [];
|
|
6813
|
+
for (const ctrl of row.controls) {
|
|
6814
|
+
if (isBound(ctrl)) {
|
|
6815
|
+
const resolved = resolveExpression(ctrl.expression, registry, void 0, "workorder");
|
|
6816
|
+
if (resolved) parts.push(resolved);
|
|
6817
|
+
} else if (isStaticLabel(ctrl)) {
|
|
6818
|
+
const text = ctrl.text.trim();
|
|
6819
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
6820
|
+
parts.push(text);
|
|
6821
|
+
}
|
|
6822
|
+
}
|
|
6823
|
+
}
|
|
6824
|
+
if (parts.length > 0) {
|
|
6825
|
+
leftEntries.push(parts.join(" "));
|
|
6826
|
+
}
|
|
6827
|
+
}
|
|
6828
|
+
}
|
|
6829
|
+
const rightEntries = [];
|
|
6830
|
+
if (rightControls.length > 0) {
|
|
6831
|
+
const rightRows = groupByRow(rightControls);
|
|
6832
|
+
for (const row of rightRows) {
|
|
6833
|
+
let label = "";
|
|
6834
|
+
let value = "";
|
|
6835
|
+
for (const ctrl of row.controls) {
|
|
6836
|
+
if (isStaticLabel(ctrl)) {
|
|
6837
|
+
const text = ctrl.text.trim();
|
|
6838
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
6839
|
+
label = text;
|
|
6840
|
+
}
|
|
6841
|
+
} else if (isBound(ctrl)) {
|
|
6842
|
+
value = resolveExpression(ctrl.expression, registry, void 0, "workorder");
|
|
6843
|
+
if (ctrl.formatString && /\(###\)\s*###\s*-\s*####/.test(ctrl.formatString) && value.startsWith("{{var|")) {
|
|
6844
|
+
value = value.replace("}}", "|format:phone}}");
|
|
6845
|
+
}
|
|
6846
|
+
}
|
|
6847
|
+
}
|
|
6848
|
+
if (label || value) {
|
|
6849
|
+
rightEntries.push({ label, value });
|
|
6850
|
+
}
|
|
6851
|
+
}
|
|
6852
|
+
}
|
|
6853
|
+
if (leftEntries.length > 0 || rightEntries.length > 0) {
|
|
6854
|
+
const outerSplit = computeSplitPercent(leftControls, rightControls, contentWidth);
|
|
6855
|
+
lines.push(`:::columns{split:${outerSplit}}`);
|
|
6856
|
+
lines.push(":::col");
|
|
6857
|
+
for (const entry of leftEntries) {
|
|
6858
|
+
lines.push(entry);
|
|
6859
|
+
}
|
|
6860
|
+
lines.push(":::");
|
|
6861
|
+
lines.push(":::col");
|
|
6862
|
+
lines.push("| | |");
|
|
6863
|
+
lines.push("|---|---|");
|
|
6864
|
+
for (const entry of rightEntries) {
|
|
6865
|
+
const label = entry.label || "";
|
|
6866
|
+
const value = entry.value ? ` ${entry.value}` : "";
|
|
6867
|
+
lines.push(`| ${label} |${value} |`);
|
|
6868
|
+
}
|
|
6869
|
+
lines.push(":::");
|
|
6870
|
+
lines.push(":::");
|
|
6871
|
+
lines.push("");
|
|
6872
|
+
}
|
|
6873
|
+
return lines.join("\n");
|
|
6874
|
+
}
|
|
6875
|
+
function renderOperationsSection(pageHeaderControls, detailControls, groupFooterControls, registry, contentWidth) {
|
|
6876
|
+
const lines = [];
|
|
6877
|
+
const headerTexts = [];
|
|
6878
|
+
for (const ctrl of [...pageHeaderControls].sort((a, b) => a.x - b.x)) {
|
|
6879
|
+
if (isStaticLabel(ctrl)) {
|
|
6880
|
+
const text = ctrl.text.trim();
|
|
6881
|
+
if (text) headerTexts.push(text);
|
|
6882
|
+
}
|
|
6883
|
+
}
|
|
6884
|
+
const titleText = headerTexts.join(" ");
|
|
6885
|
+
lines.push(`:::panel{title:${titleText}|headerStyle:dark|border:none}`);
|
|
6886
|
+
lines.push(":::");
|
|
6887
|
+
lines.push(":::repeat{data:operations}");
|
|
6888
|
+
const sortedDetail = [...detailControls].sort((a, b) => a.x - b.x);
|
|
6889
|
+
const detailParts = [];
|
|
6890
|
+
for (const ctrl of sortedDetail) {
|
|
6891
|
+
if (isBound(ctrl)) {
|
|
6892
|
+
const resolved = resolveExpression(ctrl.expression, registry, void 0, "operation");
|
|
6893
|
+
if (resolved) detailParts.push(resolved);
|
|
6894
|
+
}
|
|
6895
|
+
}
|
|
6896
|
+
if (detailParts.length > 0) {
|
|
6897
|
+
if (detailParts.length >= 2) {
|
|
6898
|
+
lines.push(":::columns{split:12}");
|
|
6899
|
+
lines.push(":::col");
|
|
6900
|
+
lines.push(`**${detailParts[0]}**`);
|
|
6901
|
+
lines.push(":::");
|
|
6902
|
+
lines.push(":::col");
|
|
6903
|
+
lines.push(`**${detailParts.slice(1).join(" ")}**`);
|
|
6904
|
+
lines.push(":::");
|
|
6905
|
+
lines.push(":::");
|
|
6906
|
+
} else {
|
|
6907
|
+
lines.push(`**${detailParts[0]}**`);
|
|
6908
|
+
}
|
|
6909
|
+
lines.push("");
|
|
6910
|
+
}
|
|
6911
|
+
if (groupFooterControls.length > 0) {
|
|
6912
|
+
const subtotalsMarkdown = renderGroupFooterBand(groupFooterControls, registry);
|
|
6913
|
+
if (subtotalsMarkdown) lines.push(subtotalsMarkdown);
|
|
6914
|
+
}
|
|
6915
|
+
lines.push("---");
|
|
6916
|
+
lines.push(":::");
|
|
6917
|
+
lines.push("");
|
|
6918
|
+
return lines.join("\n");
|
|
6919
|
+
}
|
|
6920
|
+
function renderGroupFooterBand(controls, registry, contentWidth) {
|
|
6921
|
+
const rows = groupByRow(controls);
|
|
6922
|
+
const entries = [];
|
|
6923
|
+
for (const row of rows) {
|
|
6924
|
+
const labels = row.controls.filter((c) => c.type === "XRLabel");
|
|
6925
|
+
if (labels.length === 0) continue;
|
|
6926
|
+
let label = "";
|
|
6927
|
+
let value = "";
|
|
6928
|
+
const sorted = [...labels].sort((a, b) => a.x - b.x);
|
|
6929
|
+
for (const ctrl of sorted) {
|
|
6930
|
+
if (isStaticLabel(ctrl)) {
|
|
6931
|
+
const text = ctrl.text.trim();
|
|
6932
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
6933
|
+
label = text;
|
|
6934
|
+
}
|
|
6935
|
+
} else if (isBound(ctrl)) {
|
|
6936
|
+
const resolved = resolveExpression(ctrl.expression, registry, label, "operation");
|
|
6937
|
+
if (resolved) {
|
|
6938
|
+
if (!resolved.startsWith("{{var|")) {
|
|
6939
|
+
if (!label) label = resolved;
|
|
6940
|
+
} else {
|
|
6941
|
+
value = resolved;
|
|
6942
|
+
}
|
|
6943
|
+
}
|
|
6944
|
+
}
|
|
6945
|
+
}
|
|
6946
|
+
if (label && value) {
|
|
6947
|
+
entries.push({ label, value });
|
|
6948
|
+
}
|
|
6949
|
+
}
|
|
6950
|
+
if (entries.length === 0) return "";
|
|
6951
|
+
const lines = [];
|
|
6952
|
+
lines.push(":::subtotals");
|
|
6953
|
+
for (const entry of entries) {
|
|
6954
|
+
lines.push(`**${entry.label}** ${entry.value}`);
|
|
6955
|
+
}
|
|
6956
|
+
lines.push(":::");
|
|
6957
|
+
lines.push("");
|
|
6958
|
+
return lines.join("\n");
|
|
6959
|
+
}
|
|
6960
|
+
function renderReportFooterBand(controls, registry) {
|
|
6961
|
+
const lines = [];
|
|
6962
|
+
const panel = controls.find((c) => c.type === "XRPanel");
|
|
6963
|
+
const thankYouLabel = controls.find(
|
|
6964
|
+
(c) => c.type === "XRLabel" && c.text.includes("Thank You")
|
|
6965
|
+
);
|
|
6966
|
+
const priceRows = [];
|
|
6967
|
+
if (panel && panel.children.length > 0) {
|
|
6968
|
+
const rows = groupByRow(panel.children, 15);
|
|
6969
|
+
for (const row of rows) {
|
|
6970
|
+
const labels = row.controls.filter((c) => c.type === "XRLabel");
|
|
6971
|
+
if (labels.length === 0) continue;
|
|
6972
|
+
let label = "";
|
|
6973
|
+
let value = "";
|
|
6974
|
+
const sorted = [...labels].sort((a, b) => a.x - b.x);
|
|
6975
|
+
for (const ctrl of sorted) {
|
|
6976
|
+
if (isStaticLabel(ctrl) && ctrl.text.includes("Estimate Price Summary")) {
|
|
6977
|
+
continue;
|
|
6978
|
+
}
|
|
6979
|
+
const isLabelPos = ctrl.x < 150;
|
|
6980
|
+
if (isStaticLabel(ctrl)) {
|
|
6981
|
+
const text = ctrl.text.trim();
|
|
6982
|
+
if (isLabelPos && text && !text.match(/^label\d+$/) && text !== "0.00") {
|
|
6983
|
+
label = text;
|
|
6984
|
+
}
|
|
6985
|
+
} else if (isBound(ctrl)) {
|
|
6986
|
+
const resolved = resolveExpression(ctrl.expression, registry, label || void 0, "workorder");
|
|
6987
|
+
if (resolved) {
|
|
6988
|
+
if (isLabelPos) {
|
|
6989
|
+
if (!label) label = resolved;
|
|
6990
|
+
} else {
|
|
6991
|
+
if (!value) value = resolved;
|
|
6992
|
+
}
|
|
6993
|
+
}
|
|
6994
|
+
}
|
|
6995
|
+
}
|
|
6996
|
+
if (label && value) {
|
|
6997
|
+
priceRows.push({ label, value });
|
|
6998
|
+
} else if (value) {
|
|
6999
|
+
priceRows.push({ label: "", value });
|
|
7000
|
+
}
|
|
7001
|
+
}
|
|
7002
|
+
}
|
|
7003
|
+
lines.push(":::columns{split:40|padX:20}");
|
|
7004
|
+
lines.push(":::col{padTop:1}");
|
|
7005
|
+
if (thankYouLabel) {
|
|
7006
|
+
lines.push(`## ***${thankYouLabel.text.trim()}***`);
|
|
7007
|
+
}
|
|
7008
|
+
lines.push(":::");
|
|
7009
|
+
lines.push(":::col{padTop:1}");
|
|
7010
|
+
lines.push(":::panel{title:Estimate Price Summary|border:solid|headerStyle:dark}");
|
|
7011
|
+
if (priceRows.length > 0) {
|
|
7012
|
+
lines.push(":::subtotals");
|
|
7013
|
+
for (const row of priceRows) {
|
|
7014
|
+
const label = row.label ? `**${row.label}**` : "";
|
|
7015
|
+
lines.push(`${label} ${row.value}`);
|
|
7016
|
+
}
|
|
7017
|
+
lines.push(":::");
|
|
7018
|
+
}
|
|
7019
|
+
lines.push(":::");
|
|
7020
|
+
lines.push(":::");
|
|
7021
|
+
lines.push(":::");
|
|
7022
|
+
lines.push("");
|
|
7023
|
+
return lines.join("\n");
|
|
7024
|
+
}
|
|
7025
|
+
function parseXmlTemplate(xmlString) {
|
|
7026
|
+
const warnings = [];
|
|
7027
|
+
const registry = new VariableRegistry();
|
|
7028
|
+
const parser = new DOMParser();
|
|
7029
|
+
const doc = parser.parseFromString(xmlString, "text/xml");
|
|
7030
|
+
const parseError = doc.querySelector("parsererror");
|
|
7031
|
+
if (parseError) {
|
|
7032
|
+
return {
|
|
7033
|
+
markdown: "",
|
|
7034
|
+
variables: [],
|
|
7035
|
+
reportName: "",
|
|
7036
|
+
warnings: ["XML parse error: " + (parseError.textContent || "Unknown error")]
|
|
7037
|
+
};
|
|
7038
|
+
}
|
|
7039
|
+
const root = doc.documentElement;
|
|
7040
|
+
const reportName = root.getAttribute("DisplayName") || root.getAttribute("Name") || "Report";
|
|
7041
|
+
const pageWidth = parseInt(root.getAttribute("PageWidth") || "850", 10);
|
|
7042
|
+
const marginsStr = root.getAttribute("Margins") || "50,50,250,50";
|
|
7043
|
+
const marginParts = marginsStr.split(",").map(Number);
|
|
7044
|
+
const leftMargin = marginParts[0] || 50;
|
|
7045
|
+
const rightMargin = marginParts[1] || 50;
|
|
7046
|
+
const contentWidth = pageWidth - leftMargin - rightMargin;
|
|
7047
|
+
const calculatedFieldNames = /* @__PURE__ */ new Set();
|
|
7048
|
+
const calcFieldsEl = root.querySelector(":scope > CalculatedFields");
|
|
7049
|
+
if (calcFieldsEl) {
|
|
7050
|
+
for (const item of Array.from(calcFieldsEl.children)) {
|
|
7051
|
+
const name = item.getAttribute("Name");
|
|
7052
|
+
if (name) calculatedFieldNames.add(name);
|
|
7053
|
+
}
|
|
7054
|
+
}
|
|
7055
|
+
const markdownSections = [];
|
|
7056
|
+
const watermarkEl = root.querySelector("Watermarks > Item1");
|
|
7057
|
+
if (watermarkEl) {
|
|
7058
|
+
const wmText = watermarkEl.getAttribute("Text");
|
|
7059
|
+
if (wmText) {
|
|
7060
|
+
markdownSections.push(`:::watermark{text:${wmText}|opacity:0.15|angle:45}`);
|
|
7061
|
+
markdownSections.push("");
|
|
7062
|
+
}
|
|
7063
|
+
}
|
|
7064
|
+
const bandsEl = root.querySelector(":scope > Bands");
|
|
7065
|
+
if (!bandsEl) {
|
|
7066
|
+
warnings.push("No <Bands> element found in the XML");
|
|
7067
|
+
return {
|
|
7068
|
+
markdown: "",
|
|
7069
|
+
variables: registry.getAll(),
|
|
7070
|
+
reportName,
|
|
7071
|
+
warnings
|
|
7072
|
+
};
|
|
7073
|
+
}
|
|
7074
|
+
let topMarginControls = [];
|
|
7075
|
+
let reportHeaderControls = [];
|
|
7076
|
+
let pageHeaderControls = [];
|
|
7077
|
+
let detailControls = [];
|
|
7078
|
+
let groupFooterControls = [];
|
|
7079
|
+
let reportFooterControls = [];
|
|
7080
|
+
for (const bandItem of Array.from(bandsEl.children)) {
|
|
7081
|
+
const ctrlType = bandItem.getAttribute("ControlType") || "";
|
|
7082
|
+
if (ctrlType === "TopMarginBand") {
|
|
7083
|
+
topMarginControls = extractControls(bandItem);
|
|
7084
|
+
} else if (ctrlType === "ReportHeaderBand") {
|
|
7085
|
+
reportHeaderControls = extractControls(bandItem);
|
|
7086
|
+
} else if (ctrlType === "PageHeaderBand") {
|
|
7087
|
+
pageHeaderControls = extractControls(bandItem);
|
|
7088
|
+
} else if (ctrlType === "DetailReportBand") {
|
|
7089
|
+
const nestedBands = bandItem.querySelector(":scope > Bands");
|
|
7090
|
+
if (nestedBands) {
|
|
7091
|
+
for (const nested of Array.from(nestedBands.children)) {
|
|
7092
|
+
const nestedType = nested.getAttribute("ControlType") || "";
|
|
7093
|
+
if (nestedType === "DetailBand") {
|
|
7094
|
+
detailControls = extractControls(nested);
|
|
7095
|
+
} else if (nestedType === "GroupFooterBand") {
|
|
7096
|
+
groupFooterControls = extractControls(nested);
|
|
7097
|
+
}
|
|
7098
|
+
}
|
|
7099
|
+
}
|
|
7100
|
+
} else if (ctrlType === "ReportFooterBand") {
|
|
7101
|
+
reportFooterControls = extractControls(bandItem);
|
|
7102
|
+
}
|
|
7103
|
+
}
|
|
7104
|
+
if (topMarginControls.length > 0) {
|
|
7105
|
+
markdownSections.push(renderTopMarginBand(topMarginControls, registry, contentWidth, calculatedFieldNames));
|
|
7106
|
+
}
|
|
7107
|
+
if (reportHeaderControls.length > 0) {
|
|
7108
|
+
markdownSections.push(
|
|
7109
|
+
renderReportHeaderBand(reportHeaderControls, registry, contentWidth)
|
|
7110
|
+
);
|
|
7111
|
+
}
|
|
7112
|
+
if (pageHeaderControls.length > 0 || detailControls.length > 0) {
|
|
7113
|
+
markdownSections.push(
|
|
7114
|
+
renderOperationsSection(
|
|
7115
|
+
pageHeaderControls,
|
|
7116
|
+
detailControls,
|
|
7117
|
+
groupFooterControls,
|
|
7118
|
+
registry)
|
|
7119
|
+
);
|
|
7120
|
+
}
|
|
7121
|
+
if (reportFooterControls.length > 0) {
|
|
7122
|
+
markdownSections.push(
|
|
7123
|
+
renderReportFooterBand(reportFooterControls, registry)
|
|
7124
|
+
);
|
|
7125
|
+
}
|
|
7126
|
+
const markdown = markdownSections.join("\n").replace(/\n{4,}/g, "\n\n\n");
|
|
7127
|
+
return {
|
|
7128
|
+
markdown,
|
|
7129
|
+
variables: registry.getAll(),
|
|
7130
|
+
reportName,
|
|
7131
|
+
warnings
|
|
7132
|
+
};
|
|
7133
|
+
}
|
|
7134
|
+
|
|
4958
7135
|
exports.DocumentGenerator = DocumentGenerator;
|
|
4959
7136
|
exports.EditorPanel = EditorPanel;
|
|
4960
7137
|
exports.FormFieldType = FormFieldType;
|
|
4961
7138
|
exports.PreviewPanel = PreviewPanel;
|
|
7139
|
+
exports.RepeatNode = RepeatNode;
|
|
7140
|
+
exports.SubtotalsNode = SubtotalsNode;
|
|
4962
7141
|
exports.ThemeProvider = ThemeProvider;
|
|
7142
|
+
exports.expandRepeatContent = expandRepeatContent;
|
|
7143
|
+
exports.extractVariablesFromContent = extractVariablesFromContent;
|
|
7144
|
+
exports.generatePdfFromMarkdown = generatePdfFromMarkdown;
|
|
7145
|
+
exports.generatePdfFromTiptap = generatePdfFromTiptap;
|
|
7146
|
+
exports.isZeroLike = isZeroLike;
|
|
7147
|
+
exports.markdownToTiptap = markdownToTiptap;
|
|
7148
|
+
exports.parseXmlTemplate = parseXmlTemplate;
|
|
7149
|
+
exports.suppressZeroContent = suppressZeroContent;
|
|
7150
|
+
exports.tiptapToMarkdown = tiptapToMarkdown;
|
|
4963
7151
|
exports.useDocumentGenerator = useDocumentGenerator;
|
|
4964
7152
|
exports.useTheme = useTheme;
|
|
4965
7153
|
//# sourceMappingURL=index.js.map
|