@signiphi/pdf-compose 0.1.0-beta.2 → 0.1.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2661 -469
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2649 -472
- package/dist/index.mjs.map +1 -1
- package/dist/styles/index.css +346 -194
- package/package.json +5 -1
- package/src/styles/index.css +140 -68
package/dist/index.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,263 @@ 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 fields = extractFieldsFromContent(replaced);
|
|
2591
|
+
const result = await generatePdfFromContent(replaced, {
|
|
2592
|
+
embedFormFields: fields.length > 0,
|
|
2593
|
+
fields
|
|
2594
|
+
});
|
|
2595
|
+
return { pdfBytes: result.pdfBytes };
|
|
2596
|
+
}
|
|
2597
|
+
async function generatePdfFromMarkdown(markdown, values) {
|
|
2598
|
+
const content = markdownToTiptap(markdown);
|
|
2599
|
+
return generatePdfFromTiptap(content, values);
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
// src/utils/markdown-writer.ts
|
|
2603
|
+
function serializeFieldToken(attrs) {
|
|
2604
|
+
const parts = ["field"];
|
|
2605
|
+
if (attrs.fieldType) parts.push(`type:${attrs.fieldType}`);
|
|
2606
|
+
if (attrs.fieldName) parts.push(`name:${attrs.fieldName}`);
|
|
2607
|
+
if (attrs.fieldLabel) parts.push(`label:${attrs.fieldLabel}`);
|
|
2608
|
+
if (attrs.fieldId) parts.push(`id:${attrs.fieldId}`);
|
|
2609
|
+
if (attrs.required) parts.push(`required:true`);
|
|
2610
|
+
if (attrs.options) parts.push(`options:${attrs.options}`);
|
|
2611
|
+
if (attrs.fontSize) parts.push(`fontSize:${attrs.fontSize}`);
|
|
2612
|
+
if (attrs.placeholder) parts.push(`placeholder:${attrs.placeholder}`);
|
|
2613
|
+
if (attrs.defaultValue) parts.push(`defaultValue:${attrs.defaultValue}`);
|
|
2614
|
+
if (attrs.multiline) parts.push(`multiline:true`);
|
|
2615
|
+
if (attrs.maxLength) parts.push(`maxLength:${attrs.maxLength}`);
|
|
2616
|
+
if (attrs.acknowledgements) parts.push(`acks:${btoa(attrs.acknowledgements)}`);
|
|
2617
|
+
return `{{${parts.join("|")}}}`;
|
|
2618
|
+
}
|
|
2619
|
+
function serializeVariableToken(attrs) {
|
|
2620
|
+
const parts = ["var"];
|
|
2621
|
+
if (attrs.varName) parts.push(`name:${attrs.varName}`);
|
|
2622
|
+
if (attrs.varLabel) parts.push(`label:${attrs.varLabel}`);
|
|
2623
|
+
if (attrs.varDefault) parts.push(`default:${attrs.varDefault}`);
|
|
2624
|
+
if (attrs.suppressZero === "true") parts.push(`suppress:zero`);
|
|
2625
|
+
if (attrs.format) parts.push(`format:${attrs.format}`);
|
|
2626
|
+
return `{{${parts.join("|")}}}`;
|
|
2627
|
+
}
|
|
2628
|
+
function serializeInline(content) {
|
|
2629
|
+
if (!content) return "";
|
|
2630
|
+
let result = "";
|
|
2631
|
+
for (const node of content) {
|
|
2632
|
+
if (node.type === "fieldNode") {
|
|
2633
|
+
result += serializeFieldToken(node.attrs || {});
|
|
2634
|
+
continue;
|
|
2635
|
+
}
|
|
2636
|
+
if (node.type === "variableNode") {
|
|
2637
|
+
let token = serializeVariableToken(node.attrs || {});
|
|
2638
|
+
const varMarks = node.marks || [];
|
|
2639
|
+
const isBold = varMarks.some((m) => m.type === "bold");
|
|
2640
|
+
const isItalic = varMarks.some((m) => m.type === "italic");
|
|
2641
|
+
const isUnderline = varMarks.some((m) => m.type === "underline");
|
|
2642
|
+
if (isBold && isItalic) token = `***${token}***`;
|
|
2643
|
+
else if (isBold) token = `**${token}**`;
|
|
2644
|
+
else if (isItalic) token = `*${token}*`;
|
|
2645
|
+
if (isUnderline) token = `__${token}__`;
|
|
2646
|
+
result += token;
|
|
2647
|
+
continue;
|
|
2648
|
+
}
|
|
2649
|
+
if (node.type === "text") {
|
|
2650
|
+
let text = node.text || "";
|
|
2651
|
+
const marks = node.marks || [];
|
|
2652
|
+
for (const mark of marks) {
|
|
2653
|
+
if (mark.type === "bold") text = `**${text}**`;
|
|
2654
|
+
else if (mark.type === "italic") text = `*${text}*`;
|
|
2655
|
+
else if (mark.type === "underline") text = `__${text}__`;
|
|
2656
|
+
else if (mark.type === "code") text = `\`${text}\``;
|
|
2657
|
+
}
|
|
2658
|
+
result += text;
|
|
2659
|
+
}
|
|
2660
|
+
if (node.type === "hardBreak") {
|
|
2661
|
+
result += " \n";
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
return result;
|
|
2665
|
+
}
|
|
2666
|
+
function serializeBlock(node) {
|
|
2667
|
+
switch (node.type) {
|
|
2668
|
+
case "heading": {
|
|
2669
|
+
const level = node.attrs?.level || 1;
|
|
2670
|
+
const prefix = "#".repeat(level);
|
|
2671
|
+
return `${prefix} ${serializeInline(node.content)}`;
|
|
2672
|
+
}
|
|
2673
|
+
case "paragraph": {
|
|
2674
|
+
const inline = serializeInline(node.content);
|
|
2675
|
+
return inline;
|
|
2676
|
+
}
|
|
2677
|
+
case "bulletList": {
|
|
2678
|
+
return (node.content || []).map((item) => {
|
|
2679
|
+
const inner = (item.content || []).map(serializeBlock).join("\n");
|
|
2680
|
+
return `- ${inner}`;
|
|
2681
|
+
}).join("\n");
|
|
2682
|
+
}
|
|
2683
|
+
case "orderedList": {
|
|
2684
|
+
return (node.content || []).map((item, i) => {
|
|
2685
|
+
const inner = (item.content || []).map(serializeBlock).join("\n");
|
|
2686
|
+
return `${i + 1}. ${inner}`;
|
|
2687
|
+
}).join("\n");
|
|
2688
|
+
}
|
|
2689
|
+
case "listItem": {
|
|
2690
|
+
return (node.content || []).map(serializeBlock).join("\n");
|
|
2691
|
+
}
|
|
2692
|
+
case "blockquote": {
|
|
2693
|
+
return (node.content || []).map(serializeBlock).map((line) => `> ${line}`).join("\n");
|
|
2694
|
+
}
|
|
2695
|
+
case "codeBlock": {
|
|
2696
|
+
const text = serializeInline(node.content);
|
|
2697
|
+
return `\`\`\`
|
|
2698
|
+
${text}
|
|
2699
|
+
\`\`\``;
|
|
2700
|
+
}
|
|
2701
|
+
case "horizontalRule": {
|
|
2702
|
+
return "---";
|
|
2703
|
+
}
|
|
2704
|
+
case "table": {
|
|
2705
|
+
const rows = node.content || [];
|
|
2706
|
+
const isBorderless = node.attrs?.borderless === true;
|
|
2707
|
+
const isSubtotals = node.attrs?.subtotals === true;
|
|
2708
|
+
const serializedRows = [];
|
|
2709
|
+
if (isBorderless && rows.length > 0) {
|
|
2710
|
+
const colCount = rows[0].content?.length || 0;
|
|
2711
|
+
const markerCell = isSubtotals ? "_" : "";
|
|
2712
|
+
const markerRow = "| " + new Array(colCount).fill(markerCell).join(" | ") + " |";
|
|
2713
|
+
const sep = "|" + new Array(colCount).fill("---").join("|") + "|";
|
|
2714
|
+
serializedRows.push(markerRow);
|
|
2715
|
+
serializedRows.push(sep);
|
|
2716
|
+
}
|
|
2717
|
+
for (let ri = 0; ri < rows.length; ri++) {
|
|
2718
|
+
const row = rows[ri];
|
|
2719
|
+
const cells = (row.content || []).map((cell) => {
|
|
2720
|
+
const inner = (cell.content || []).map(serializeBlock).join("");
|
|
2721
|
+
return inner;
|
|
2722
|
+
});
|
|
2723
|
+
serializedRows.push(`| ${cells.join(" | ")} |`);
|
|
2724
|
+
if (!isBorderless && ri === 0 && row.content?.[0]?.type === "tableHeader") {
|
|
2725
|
+
const sep = cells.map(() => "------").join("|");
|
|
2726
|
+
serializedRows.push(`|${sep}|`);
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
return serializedRows.join("\n");
|
|
2730
|
+
}
|
|
2731
|
+
case "tableRow": {
|
|
2732
|
+
const cells = (node.content || []).map((cell) => {
|
|
2733
|
+
const inner = (cell.content || []).map(serializeBlock).join("");
|
|
2734
|
+
return inner;
|
|
2735
|
+
});
|
|
2736
|
+
return `| ${cells.join(" | ")} |`;
|
|
2737
|
+
}
|
|
2738
|
+
case "tableHeader":
|
|
2739
|
+
case "tableCell": {
|
|
2740
|
+
return (node.content || []).map(serializeBlock).join("");
|
|
2741
|
+
}
|
|
2742
|
+
case "watermark": {
|
|
2743
|
+
const attrs = node.attrs || {};
|
|
2744
|
+
const parts = [];
|
|
2745
|
+
if (attrs.text) parts.push(`text:${attrs.text}`);
|
|
2746
|
+
if (attrs.opacity && attrs.opacity !== "0.15") parts.push(`opacity:${attrs.opacity}`);
|
|
2747
|
+
if (attrs.angle && attrs.angle !== "-45") parts.push(`angle:${attrs.angle}`);
|
|
2748
|
+
return `:::watermark{${parts.join("|")}}`;
|
|
2749
|
+
}
|
|
2750
|
+
case "panel": {
|
|
2751
|
+
const attrs = node.attrs || {};
|
|
2752
|
+
const parts = [];
|
|
2753
|
+
if (attrs.title) parts.push(`title:${attrs.title}`);
|
|
2754
|
+
if (attrs.border && attrs.border !== "solid") parts.push(`border:${attrs.border}`);
|
|
2755
|
+
else if (attrs.border === "solid") parts.push(`border:solid`);
|
|
2756
|
+
if (attrs.headerStyle) parts.push(`headerStyle:${attrs.headerStyle}`);
|
|
2757
|
+
const attrStr = parts.length > 0 ? `{${parts.join("|")}}` : "";
|
|
2758
|
+
const children = (node.content || []).map(serializeBlock).join("\n\n");
|
|
2759
|
+
return `:::panel${attrStr}
|
|
2760
|
+
${children}
|
|
2761
|
+
:::`;
|
|
2762
|
+
}
|
|
2763
|
+
case "repeatBlock": {
|
|
2764
|
+
const attrs = node.attrs || {};
|
|
2765
|
+
const parts = [];
|
|
2766
|
+
if (attrs.data) parts.push(`data:${attrs.data}`);
|
|
2767
|
+
const attrStr = parts.length > 0 ? `{${parts.join("|")}}` : "";
|
|
2768
|
+
const children = (node.content || []).map(serializeBlock).join("\n\n");
|
|
2769
|
+
return `:::repeat${attrStr}
|
|
2770
|
+
${children}
|
|
2771
|
+
:::`;
|
|
2772
|
+
}
|
|
2773
|
+
case "subtotalsBlock": {
|
|
2774
|
+
const children = (node.content || []).map(serializeBlock).join("\n");
|
|
2775
|
+
return `:::subtotals
|
|
2776
|
+
${children}
|
|
2777
|
+
:::`;
|
|
2778
|
+
}
|
|
2779
|
+
case "columns": {
|
|
2780
|
+
const attrs = node.attrs || {};
|
|
2781
|
+
const splitVal = attrs.split || "50";
|
|
2782
|
+
const cols = (node.content || []).filter((c) => c.type === "column");
|
|
2783
|
+
const colBlocks = cols.map((col) => {
|
|
2784
|
+
const children = (col.content || []).map(serializeBlock).join("\n\n");
|
|
2785
|
+
const colAttrs = col.attrs || {};
|
|
2786
|
+
const padTop = parseFloat(colAttrs.padTop || "0") || 0;
|
|
2787
|
+
const colTag = padTop ? `:::col{padTop:${padTop}}` : ":::col";
|
|
2788
|
+
return `${colTag}
|
|
2789
|
+
${children}
|
|
2790
|
+
:::`;
|
|
2791
|
+
});
|
|
2792
|
+
const padX = parseFloat(attrs.padX || "0") || 0;
|
|
2793
|
+
const colsAttrs = padX ? `split:${splitVal}|padX:${padX}` : `split:${splitVal}`;
|
|
2794
|
+
return `:::columns{${colsAttrs}}
|
|
2795
|
+
${colBlocks.join("\n")}
|
|
2796
|
+
:::`;
|
|
2797
|
+
}
|
|
2798
|
+
case "column": {
|
|
2799
|
+
const children = (node.content || []).map(serializeBlock).join("\n\n");
|
|
2800
|
+
const colAttrs = node.attrs || {};
|
|
2801
|
+
const padTop = parseFloat(colAttrs.padTop || "0") || 0;
|
|
2802
|
+
const colTag = padTop ? `:::col{padTop:${padTop}}` : ":::col";
|
|
2803
|
+
return `${colTag}
|
|
2804
|
+
${children}
|
|
2805
|
+
:::`;
|
|
2806
|
+
}
|
|
2807
|
+
default:
|
|
2808
|
+
return serializeInline(node.content);
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
function tiptapToMarkdown(doc) {
|
|
2812
|
+
if (!doc.content) return "";
|
|
2813
|
+
return doc.content.map(serializeBlock).join("\n\n");
|
|
2814
|
+
}
|
|
2815
|
+
|
|
2816
|
+
// src/utils/pdf-preview.ts
|
|
2817
|
+
async function pdfToImages(pdfBytes, scale = 2) {
|
|
2818
|
+
let pdfjsLib;
|
|
2819
|
+
try {
|
|
2820
|
+
pdfjsLib = await import('pdfjs-dist');
|
|
2821
|
+
} catch (err) {
|
|
2822
|
+
throw new Error(formatError(
|
|
2823
|
+
"Failed to load the PDF preview library \u2014 ensure pdfjs-dist is installed and the worker is accessible",
|
|
2824
|
+
err
|
|
2825
|
+
));
|
|
2826
|
+
}
|
|
2827
|
+
if (!pdfjsLib.GlobalWorkerOptions.workerSrc) {
|
|
2828
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc = "/pdfjs/build/pdf.worker.mjs";
|
|
2829
|
+
}
|
|
2830
|
+
let pdf;
|
|
2831
|
+
try {
|
|
2832
|
+
const loadingTask = pdfjsLib.getDocument({ data: pdfBytes });
|
|
2833
|
+
pdf = await loadingTask.promise;
|
|
2834
|
+
} catch (err) {
|
|
2835
|
+
throw new Error(formatError("Failed to parse the generated PDF for preview", err));
|
|
2836
|
+
}
|
|
2837
|
+
const pages = [];
|
|
2838
|
+
const pageErrors = [];
|
|
2839
|
+
for (let i = 1; i <= pdf.numPages; i++) {
|
|
2840
|
+
try {
|
|
2841
|
+
const page = await pdf.getPage(i);
|
|
1599
2842
|
const viewport = page.getViewport({ scale });
|
|
1600
2843
|
const canvas = document.createElement("canvas");
|
|
1601
2844
|
canvas.width = viewport.width;
|
|
@@ -1656,8 +2899,26 @@ function useDocumentGenerator(options = {}) {
|
|
|
1656
2899
|
Placeholder__default.default.configure({
|
|
1657
2900
|
placeholder: options.placeholder || "Start writing your document..."
|
|
1658
2901
|
}),
|
|
2902
|
+
Table__default.default.extend({
|
|
2903
|
+
addAttributes() {
|
|
2904
|
+
return {
|
|
2905
|
+
...this.parent?.(),
|
|
2906
|
+
borderless: { default: false },
|
|
2907
|
+
subtotals: { default: false }
|
|
2908
|
+
};
|
|
2909
|
+
}
|
|
2910
|
+
}).configure({ resizable: false }),
|
|
2911
|
+
TableRow__default.default,
|
|
2912
|
+
TableCell__default.default,
|
|
2913
|
+
TableHeader__default.default,
|
|
1659
2914
|
FieldNode,
|
|
1660
|
-
VariableNode
|
|
2915
|
+
VariableNode,
|
|
2916
|
+
PanelNode,
|
|
2917
|
+
ColumnsNode,
|
|
2918
|
+
ColumnNode,
|
|
2919
|
+
WatermarkNode,
|
|
2920
|
+
RepeatNode,
|
|
2921
|
+
SubtotalsNode
|
|
1661
2922
|
],
|
|
1662
2923
|
content: initialContent || { type: "doc", content: [{ type: "paragraph" }] },
|
|
1663
2924
|
onUpdate: ({ editor: ed }) => {
|
|
@@ -1761,10 +3022,9 @@ function useDocumentGenerator(options = {}) {
|
|
|
1761
3022
|
async (values) => {
|
|
1762
3023
|
if (!editor) throw new Error("Editor is not initialized yet \u2014 wait for the editor to load before generating");
|
|
1763
3024
|
const content = editor.getJSON();
|
|
1764
|
-
const
|
|
1765
|
-
const
|
|
1766
|
-
|
|
1767
|
-
return { pdfBytes: result.pdfBytes, pdfPages: pages };
|
|
3025
|
+
const { pdfBytes: pdfBytes2 } = await generatePdfFromTiptap(content, values);
|
|
3026
|
+
const pdfPages2 = await pdfToImages(pdfBytes2);
|
|
3027
|
+
return { pdfBytes: pdfBytes2, pdfPages: pdfPages2 };
|
|
1768
3028
|
},
|
|
1769
3029
|
[editor]
|
|
1770
3030
|
);
|
|
@@ -2918,15 +4178,47 @@ function FieldEditPopover({
|
|
|
2918
4178
|
] }) })
|
|
2919
4179
|
] });
|
|
2920
4180
|
}
|
|
2921
|
-
|
|
4181
|
+
var sizeClasses = {
|
|
4182
|
+
md: "px-3 py-1.5 text-xs font-medium",
|
|
4183
|
+
sm: "px-2 py-0.5 text-[10px] font-medium"
|
|
4184
|
+
};
|
|
4185
|
+
function ToggleGroup({
|
|
4186
|
+
value,
|
|
4187
|
+
onChange,
|
|
4188
|
+
options,
|
|
4189
|
+
size = "md",
|
|
4190
|
+
className
|
|
4191
|
+
}) {
|
|
4192
|
+
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(
|
|
4193
|
+
"button",
|
|
4194
|
+
{
|
|
4195
|
+
className: cn(
|
|
4196
|
+
"rounded-md transition-colors",
|
|
4197
|
+
sizeClasses[size],
|
|
4198
|
+
value === opt.value ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
|
|
4199
|
+
),
|
|
4200
|
+
onClick: () => onChange(opt.value),
|
|
4201
|
+
children: opt.label
|
|
4202
|
+
},
|
|
4203
|
+
opt.value
|
|
4204
|
+
)) });
|
|
4205
|
+
}
|
|
4206
|
+
function labelToVarName2(label) {
|
|
2922
4207
|
return label.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
2923
4208
|
}
|
|
4209
|
+
function varNameToLabel(name) {
|
|
4210
|
+
return name.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
4211
|
+
}
|
|
2924
4212
|
function VariableInsertPopover({
|
|
2925
4213
|
open,
|
|
2926
4214
|
onOpenChange,
|
|
2927
4215
|
onInsert,
|
|
4216
|
+
predefinedVariables,
|
|
2928
4217
|
children
|
|
2929
4218
|
}) {
|
|
4219
|
+
const hasPredefined = !!predefinedVariables?.length;
|
|
4220
|
+
const [mode, setMode] = React12.useState(hasPredefined ? "existing" : "new");
|
|
4221
|
+
const [search, setSearch] = React12.useState("");
|
|
2930
4222
|
const [varLabel, setVarLabel] = React12.useState("");
|
|
2931
4223
|
const [varName, setVarName] = React12.useState("");
|
|
2932
4224
|
const [varDefault, setVarDefault] = React12.useState("");
|
|
@@ -2936,7 +4228,9 @@ function VariableInsertPopover({
|
|
|
2936
4228
|
setVarName("");
|
|
2937
4229
|
setVarDefault("");
|
|
2938
4230
|
setNameManuallyEdited(false);
|
|
2939
|
-
|
|
4231
|
+
setSearch("");
|
|
4232
|
+
setMode(hasPredefined ? "existing" : "new");
|
|
4233
|
+
}, [hasPredefined]);
|
|
2940
4234
|
const handleOpenChange = React12.useCallback(
|
|
2941
4235
|
(nextOpen) => {
|
|
2942
4236
|
if (!nextOpen) reset();
|
|
@@ -2946,7 +4240,7 @@ function VariableInsertPopover({
|
|
|
2946
4240
|
);
|
|
2947
4241
|
React12.useEffect(() => {
|
|
2948
4242
|
if (!nameManuallyEdited) {
|
|
2949
|
-
setVarName(
|
|
4243
|
+
setVarName(labelToVarName2(varLabel));
|
|
2950
4244
|
}
|
|
2951
4245
|
}, [varLabel, nameManuallyEdited]);
|
|
2952
4246
|
const handleInsert = React12.useCallback(() => {
|
|
@@ -2958,6 +4252,26 @@ function VariableInsertPopover({
|
|
|
2958
4252
|
});
|
|
2959
4253
|
handleOpenChange(false);
|
|
2960
4254
|
}, [varLabel, varName, varDefault, onInsert, handleOpenChange]);
|
|
4255
|
+
const handlePickPredefined = React12.useCallback(
|
|
4256
|
+
(pv) => {
|
|
4257
|
+
onInsert({
|
|
4258
|
+
varName: pv.varName,
|
|
4259
|
+
varLabel: pv.varLabel || varNameToLabel(pv.varName),
|
|
4260
|
+
varDefault: ""
|
|
4261
|
+
});
|
|
4262
|
+
handleOpenChange(false);
|
|
4263
|
+
},
|
|
4264
|
+
[onInsert, handleOpenChange]
|
|
4265
|
+
);
|
|
4266
|
+
const filteredVars = React12.useMemo(() => {
|
|
4267
|
+
if (!predefinedVariables?.length) return [];
|
|
4268
|
+
const q = search.toLowerCase();
|
|
4269
|
+
if (!q) return predefinedVariables;
|
|
4270
|
+
return predefinedVariables.filter((pv) => {
|
|
4271
|
+
const label = (pv.varLabel || varNameToLabel(pv.varName)).toLowerCase();
|
|
4272
|
+
return pv.varName.toLowerCase().includes(q) || label.includes(q);
|
|
4273
|
+
});
|
|
4274
|
+
}, [predefinedVariables, search]);
|
|
2961
4275
|
return /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open, onOpenChange: handleOpenChange, children: [
|
|
2962
4276
|
/* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children }),
|
|
2963
4277
|
/* @__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 +4279,114 @@ function VariableInsertPopover({
|
|
|
2965
4279
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Braces, { size: 14 }),
|
|
2966
4280
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Insert Variable" })
|
|
2967
4281
|
] }),
|
|
2968
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2969
|
-
|
|
2970
|
-
|
|
4282
|
+
hasPredefined && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4283
|
+
ToggleGroup,
|
|
4284
|
+
{
|
|
4285
|
+
value: mode,
|
|
4286
|
+
onChange: setMode,
|
|
4287
|
+
options: [
|
|
4288
|
+
{ value: "existing", label: "Existing" },
|
|
4289
|
+
{ value: "new", label: "New" }
|
|
4290
|
+
],
|
|
4291
|
+
size: "sm"
|
|
4292
|
+
}
|
|
4293
|
+
),
|
|
4294
|
+
mode === "existing" && hasPredefined ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
4295
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
4296
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { size: 14, className: "absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground" }),
|
|
2971
4297
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2972
4298
|
Input,
|
|
2973
4299
|
{
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
className: "h-8 text-xs"
|
|
4300
|
+
value: search,
|
|
4301
|
+
onChange: (e) => setSearch(e.target.value),
|
|
4302
|
+
placeholder: "Search variables...",
|
|
4303
|
+
className: "h-8 text-xs pl-7"
|
|
2979
4304
|
}
|
|
2980
4305
|
)
|
|
2981
4306
|
] }),
|
|
2982
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2983
|
-
|
|
4307
|
+
/* @__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) => {
|
|
4308
|
+
const label = pv.varLabel || varNameToLabel(pv.varName);
|
|
4309
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4310
|
+
"button",
|
|
4311
|
+
{
|
|
4312
|
+
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",
|
|
4313
|
+
onClick: () => handlePickPredefined(pv),
|
|
4314
|
+
children: [
|
|
4315
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: label }),
|
|
4316
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground font-mono", children: pv.varName })
|
|
4317
|
+
]
|
|
4318
|
+
},
|
|
4319
|
+
pv.varName
|
|
4320
|
+
);
|
|
4321
|
+
}) })
|
|
4322
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
4323
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
4324
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
4325
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "var-label", className: "text-xs", children: "Label" }),
|
|
4326
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4327
|
+
Input,
|
|
4328
|
+
{
|
|
4329
|
+
id: "var-label",
|
|
4330
|
+
value: varLabel,
|
|
4331
|
+
onChange: (e) => setVarLabel(e.target.value),
|
|
4332
|
+
placeholder: "e.g. Company Name",
|
|
4333
|
+
className: "h-8 text-xs"
|
|
4334
|
+
}
|
|
4335
|
+
)
|
|
4336
|
+
] }),
|
|
4337
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
4338
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "var-name", className: "text-xs", children: "Name (identifier)" }),
|
|
4339
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4340
|
+
Input,
|
|
4341
|
+
{
|
|
4342
|
+
id: "var-name",
|
|
4343
|
+
value: varName,
|
|
4344
|
+
onChange: (e) => {
|
|
4345
|
+
setVarName(e.target.value);
|
|
4346
|
+
setNameManuallyEdited(true);
|
|
4347
|
+
},
|
|
4348
|
+
placeholder: "e.g. company_name",
|
|
4349
|
+
className: "h-8 text-xs font-mono"
|
|
4350
|
+
}
|
|
4351
|
+
),
|
|
4352
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] text-muted-foreground mt-0.5", children: "Used as key in data objects" })
|
|
4353
|
+
] }),
|
|
4354
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
4355
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "var-default", className: "text-xs", children: "Default Value" }),
|
|
4356
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4357
|
+
Input,
|
|
4358
|
+
{
|
|
4359
|
+
id: "var-default",
|
|
4360
|
+
value: varDefault,
|
|
4361
|
+
onChange: (e) => setVarDefault(e.target.value),
|
|
4362
|
+
placeholder: "Optional",
|
|
4363
|
+
className: "h-8 text-xs"
|
|
4364
|
+
}
|
|
4365
|
+
)
|
|
4366
|
+
] })
|
|
4367
|
+
] }),
|
|
4368
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 pt-1", children: [
|
|
2984
4369
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2985
|
-
|
|
4370
|
+
Button,
|
|
2986
4371
|
{
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
},
|
|
2993
|
-
placeholder: "e.g. company_name",
|
|
2994
|
-
className: "h-8 text-xs font-mono"
|
|
4372
|
+
size: "sm",
|
|
4373
|
+
className: "h-8 flex-1 text-xs",
|
|
4374
|
+
onClick: handleInsert,
|
|
4375
|
+
disabled: !varLabel.trim() || !varName.trim(),
|
|
4376
|
+
children: "Insert"
|
|
2995
4377
|
}
|
|
2996
4378
|
),
|
|
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
4379
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3002
|
-
|
|
4380
|
+
Button,
|
|
3003
4381
|
{
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
4382
|
+
variant: "outline",
|
|
4383
|
+
size: "sm",
|
|
4384
|
+
className: "h-8 text-xs",
|
|
4385
|
+
onClick: () => handleOpenChange(false),
|
|
4386
|
+
children: "Cancel"
|
|
3009
4387
|
}
|
|
3010
4388
|
)
|
|
3011
4389
|
] })
|
|
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
4390
|
] })
|
|
3035
4391
|
] }) })
|
|
3036
4392
|
] });
|
|
@@ -3252,7 +4608,6 @@ function PreviewPanel({
|
|
|
3252
4608
|
(f) => f.position.page === currentPageIndex + 1 && f.position.width > 0
|
|
3253
4609
|
);
|
|
3254
4610
|
}, [positionedFields, currentPage, currentPageIndex]);
|
|
3255
|
-
const showScrollbars = zoomLevel > 1;
|
|
3256
4611
|
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
4612
|
/* @__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
4613
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
@@ -3368,12 +4723,13 @@ function PreviewPanel({
|
|
|
3368
4723
|
"div",
|
|
3369
4724
|
{
|
|
3370
4725
|
className: cn(
|
|
3371
|
-
"border border-border rounded-lg bg-muted/30 shrink-0"
|
|
3372
|
-
showScrollbars ? "overflow-auto" : "overflow-hidden"
|
|
4726
|
+
"border border-border rounded-lg bg-muted/30 shrink-0 overflow-auto scrollbar-hidden"
|
|
3373
4727
|
),
|
|
3374
4728
|
style: {
|
|
3375
4729
|
width: pageDisplaySize.viewportWidth,
|
|
3376
|
-
height: pageDisplaySize.viewportHeight
|
|
4730
|
+
height: pageDisplaySize.viewportHeight,
|
|
4731
|
+
maxWidth: "100%",
|
|
4732
|
+
maxHeight: "100%"
|
|
3377
4733
|
},
|
|
3378
4734
|
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3379
4735
|
"div",
|
|
@@ -3465,6 +4821,37 @@ function validateMarkdown(markdown) {
|
|
|
3465
4821
|
searchFrom = closeIdx + 2;
|
|
3466
4822
|
}
|
|
3467
4823
|
}
|
|
4824
|
+
const DIRECTIVE_OPEN_RE = /^:::(panel|columns|col|watermark)(?:\{[^}]*\})?$/;
|
|
4825
|
+
const DIRECTIVE_CLOSE_RE = /^:::$/;
|
|
4826
|
+
const directiveStack = [];
|
|
4827
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4828
|
+
const trimmed = lines[i].trim();
|
|
4829
|
+
const lineNum = i + 1;
|
|
4830
|
+
const openMatch = trimmed.match(DIRECTIVE_OPEN_RE);
|
|
4831
|
+
if (openMatch) {
|
|
4832
|
+
const dtype = openMatch[1];
|
|
4833
|
+
if (dtype !== "watermark") {
|
|
4834
|
+
if (dtype === "col") {
|
|
4835
|
+
const parent = directiveStack[directiveStack.length - 1];
|
|
4836
|
+
if (!parent || parent.type !== "columns") {
|
|
4837
|
+
warnings.push(`:::col at line ${lineNum} appears outside :::columns`);
|
|
4838
|
+
}
|
|
4839
|
+
}
|
|
4840
|
+
directiveStack.push({ type: dtype, line: lineNum });
|
|
4841
|
+
}
|
|
4842
|
+
continue;
|
|
4843
|
+
}
|
|
4844
|
+
if (DIRECTIVE_CLOSE_RE.test(trimmed)) {
|
|
4845
|
+
if (directiveStack.length === 0) {
|
|
4846
|
+
warnings.push(`Stray ::: close at line ${lineNum} with no matching open directive`);
|
|
4847
|
+
} else {
|
|
4848
|
+
directiveStack.pop();
|
|
4849
|
+
}
|
|
4850
|
+
}
|
|
4851
|
+
}
|
|
4852
|
+
for (const open of directiveStack) {
|
|
4853
|
+
errors.push(`Unclosed :::${open.type} directive opened at line ${open.line}`);
|
|
4854
|
+
}
|
|
3468
4855
|
let fields = [];
|
|
3469
4856
|
let variables = [];
|
|
3470
4857
|
try {
|
|
@@ -3498,31 +4885,6 @@ function validateMarkdown(markdown) {
|
|
|
3498
4885
|
fields
|
|
3499
4886
|
};
|
|
3500
4887
|
}
|
|
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
4888
|
function parseCsv(text) {
|
|
3527
4889
|
const lines = text.split(/\r?\n/).filter((l) => l.trim() !== "");
|
|
3528
4890
|
if (lines.length < 2) return [];
|
|
@@ -3572,7 +4934,10 @@ function GeneratePanel({
|
|
|
3572
4934
|
editorContent,
|
|
3573
4935
|
editorVariables,
|
|
3574
4936
|
onGeneratePdf,
|
|
3575
|
-
initialBulkData
|
|
4937
|
+
initialBulkData,
|
|
4938
|
+
initialVariableValues,
|
|
4939
|
+
exportFileName,
|
|
4940
|
+
templateMode
|
|
3576
4941
|
}) {
|
|
3577
4942
|
const [templateSource, setTemplateSource] = React12.useState("editor");
|
|
3578
4943
|
const [importedMarkdown, setImportedMarkdown] = React12.useState(null);
|
|
@@ -3580,6 +4945,7 @@ function GeneratePanel({
|
|
|
3580
4945
|
const [validationResult, setValidationResult] = React12.useState(null);
|
|
3581
4946
|
const [mode, setMode] = React12.useState("single");
|
|
3582
4947
|
const [variableValues, setVariableValues] = React12.useState({});
|
|
4948
|
+
const [variableSearch, setVariableSearch] = React12.useState("");
|
|
3583
4949
|
const [bulkInputFormat, setBulkInputFormat] = React12.useState("json");
|
|
3584
4950
|
const [bulkInput, setBulkInput] = React12.useState("");
|
|
3585
4951
|
const [bulkData, setBulkData] = React12.useState(null);
|
|
@@ -3606,11 +4972,22 @@ function GeneratePanel({
|
|
|
3606
4972
|
const col = pos - textBefore.lastIndexOf("\n");
|
|
3607
4973
|
setJsonCursor({ line, col });
|
|
3608
4974
|
}, []);
|
|
4975
|
+
const lockedVarNames = React12__namespace.default.useMemo(
|
|
4976
|
+
() => templateMode && initialVariableValues ? new Set(Object.keys(initialVariableValues)) : /* @__PURE__ */ new Set(),
|
|
4977
|
+
[templateMode, initialVariableValues]
|
|
4978
|
+
);
|
|
3609
4979
|
const activeVariables = templateSource === "imported" && validationResult?.valid ? validationResult.variables : editorVariables;
|
|
3610
4980
|
const hasVariables = activeVariables.length > 0;
|
|
3611
4981
|
const hasEditorContent = editorMarkdown.trim().length > 0;
|
|
3612
4982
|
const hasImportedContent = templateSource === "imported" && importedMarkdown != null;
|
|
3613
4983
|
const hasContent = hasImportedContent || hasEditorContent;
|
|
4984
|
+
const filteredVariables = React12__namespace.default.useMemo(() => {
|
|
4985
|
+
if (!variableSearch.trim()) return activeVariables;
|
|
4986
|
+
const q = variableSearch.toLowerCase();
|
|
4987
|
+
return activeVariables.filter(
|
|
4988
|
+
(v) => v.varName.toLowerCase().includes(q) || (v.varLabel || "").toLowerCase().includes(q)
|
|
4989
|
+
);
|
|
4990
|
+
}, [activeVariables, variableSearch]);
|
|
3614
4991
|
const getActiveContent = React12.useCallback(() => {
|
|
3615
4992
|
if (templateSource === "imported" && importedMarkdown) {
|
|
3616
4993
|
return markdownToTiptap(importedMarkdown);
|
|
@@ -3778,7 +5155,9 @@ function GeneratePanel({
|
|
|
3778
5155
|
setPreviewError(null);
|
|
3779
5156
|
try {
|
|
3780
5157
|
const content = getActiveContent();
|
|
3781
|
-
const
|
|
5158
|
+
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, variableValues);
|
|
5159
|
+
const suppressed = suppressZeroContent(expanded, enrichedValues);
|
|
5160
|
+
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
3782
5161
|
const fields = extractFieldsFromContent(replaced);
|
|
3783
5162
|
const result = await generatePdfFromContent(replaced);
|
|
3784
5163
|
const pages = await pdfToImages(result.pdfBytes);
|
|
@@ -3799,7 +5178,9 @@ function GeneratePanel({
|
|
|
3799
5178
|
setExportSuccess(null);
|
|
3800
5179
|
try {
|
|
3801
5180
|
const content = getActiveContent();
|
|
3802
|
-
const
|
|
5181
|
+
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, variableValues);
|
|
5182
|
+
const suppressed = suppressZeroContent(expanded, enrichedValues);
|
|
5183
|
+
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
3803
5184
|
const fields = extractFieldsFromContent(replaced);
|
|
3804
5185
|
const result = await generatePdfFromContent(replaced, {
|
|
3805
5186
|
drawFieldPlaceholders: false,
|
|
@@ -3807,7 +5188,7 @@ function GeneratePanel({
|
|
|
3807
5188
|
fields
|
|
3808
5189
|
});
|
|
3809
5190
|
const blob = new Blob([result.pdfBytes], { type: "application/pdf" });
|
|
3810
|
-
const fileName = importedFileName ? importedFileName.replace(".md", ".pdf") : "document.pdf";
|
|
5191
|
+
const fileName = exportFileName ? exportFileName.endsWith(".pdf") ? exportFileName : `${exportFileName}.pdf` : importedFileName ? importedFileName.replace(".md", ".pdf") : "document.pdf";
|
|
3811
5192
|
if (onGeneratePdf) {
|
|
3812
5193
|
onGeneratePdf(blob, fileName);
|
|
3813
5194
|
} else {
|
|
@@ -3879,7 +5260,9 @@ function GeneratePanel({
|
|
|
3879
5260
|
const allWarnings = [];
|
|
3880
5261
|
for (let i = 0; i < bulkData.length; i++) {
|
|
3881
5262
|
const values = bulkData[i];
|
|
3882
|
-
const
|
|
5263
|
+
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, values);
|
|
5264
|
+
const suppressed = suppressZeroContent(expanded, enrichedValues);
|
|
5265
|
+
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
3883
5266
|
const fields = extractFieldsFromContent(replaced);
|
|
3884
5267
|
const result = await generatePdfFromContent(replaced, {
|
|
3885
5268
|
drawFieldPlaceholders: false,
|
|
@@ -3934,13 +5317,15 @@ ${allWarnings.join("\n")}`);
|
|
|
3934
5317
|
}, [mode, bulkInputFormat, bulkInput, initialBulkJson]);
|
|
3935
5318
|
React12__namespace.default.useEffect(() => {
|
|
3936
5319
|
if (templateSource === "editor") {
|
|
3937
|
-
const defaults = {};
|
|
5320
|
+
const defaults = initialVariableValues ? { ...initialVariableValues } : {};
|
|
3938
5321
|
for (const v of editorVariables) {
|
|
3939
|
-
defaults[v.varName]
|
|
5322
|
+
if (!defaults[v.varName]) {
|
|
5323
|
+
defaults[v.varName] = variableValues[v.varName] || v.varDefault || "";
|
|
5324
|
+
}
|
|
3940
5325
|
}
|
|
3941
5326
|
setVariableValues(defaults);
|
|
3942
5327
|
}
|
|
3943
|
-
}, [editorVariables, templateSource]);
|
|
5328
|
+
}, [editorVariables, templateSource, initialVariableValues]);
|
|
3944
5329
|
React12.useEffect(() => {
|
|
3945
5330
|
setPreviewFresh(false);
|
|
3946
5331
|
}, [variableValues, templateSource, importedMarkdown, editorMarkdown, editorContent]);
|
|
@@ -3952,8 +5337,8 @@ ${allWarnings.join("\n")}`);
|
|
|
3952
5337
|
}, [exportSuccess]);
|
|
3953
5338
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-h-0 grid grid-cols-[1fr_1px_1fr]", children: [
|
|
3954
5339
|
/* @__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: [
|
|
5340
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto p-4 space-y-4 scrollbar-hidden", children: [
|
|
5341
|
+
!templateMode && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3957
5342
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
|
|
3958
5343
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-medium", children: "Template" }),
|
|
3959
5344
|
templateSource === "imported" && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -4046,10 +5431,10 @@ ${allWarnings.join("\n")}`);
|
|
|
4046
5431
|
!hasContent && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center py-8 text-muted-foreground", children: [
|
|
4047
5432
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Braces, { size: 32, className: "mb-2 opacity-50" }),
|
|
4048
5433
|
/* @__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." })
|
|
5434
|
+
/* @__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
5435
|
] }),
|
|
4051
5436
|
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(
|
|
5437
|
+
!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
5438
|
ToggleGroup,
|
|
4054
5439
|
{
|
|
4055
5440
|
value: mode,
|
|
@@ -4061,20 +5446,53 @@ ${allWarnings.join("\n")}`);
|
|
|
4061
5446
|
}
|
|
4062
5447
|
) }),
|
|
4063
5448
|
mode === "single" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3 space-y-3", children: [
|
|
4064
|
-
/* @__PURE__ */ jsxRuntime.
|
|
4065
|
-
|
|
4066
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5449
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
5450
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: "Variables" }),
|
|
5451
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground", children: activeVariables.length })
|
|
5452
|
+
] }),
|
|
5453
|
+
activeVariables.length > 5 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
5454
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { size: 14, className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground" }),
|
|
4067
5455
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4068
5456
|
Input,
|
|
4069
5457
|
{
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
5458
|
+
value: variableSearch,
|
|
5459
|
+
onChange: (e) => setVariableSearch(e.target.value),
|
|
5460
|
+
placeholder: "Search variables...",
|
|
5461
|
+
className: "h-8 text-xs pl-8"
|
|
5462
|
+
}
|
|
5463
|
+
),
|
|
5464
|
+
variableSearch && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5465
|
+
"button",
|
|
5466
|
+
{
|
|
5467
|
+
type: "button",
|
|
5468
|
+
onClick: () => setVariableSearch(""),
|
|
5469
|
+
className: "absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground",
|
|
5470
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 12 })
|
|
4075
5471
|
}
|
|
4076
5472
|
)
|
|
4077
|
-
] },
|
|
5473
|
+
] }),
|
|
5474
|
+
filteredVariables.length === 0 && variableSearch.trim() && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground py-2 text-center", children: [
|
|
5475
|
+
"No variables match \u201C",
|
|
5476
|
+
variableSearch,
|
|
5477
|
+
"\u201D"
|
|
5478
|
+
] }),
|
|
5479
|
+
filteredVariables.map((v) => {
|
|
5480
|
+
const isLocked = lockedVarNames.has(v.varName);
|
|
5481
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
5482
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: `gen-${v.varName}`, className: "text-xs", children: v.varLabel || v.varName }),
|
|
5483
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5484
|
+
Input,
|
|
5485
|
+
{
|
|
5486
|
+
id: `gen-${v.varName}`,
|
|
5487
|
+
value: variableValues[v.varName] || "",
|
|
5488
|
+
onChange: (e) => updateVariableValue(v.varName, e.target.value),
|
|
5489
|
+
placeholder: v.varDefault || `Enter ${v.varLabel || v.varName}`,
|
|
5490
|
+
disabled: isLocked,
|
|
5491
|
+
className: cn("h-8 text-xs", isLocked && "opacity-60 cursor-not-allowed")
|
|
5492
|
+
}
|
|
5493
|
+
)
|
|
5494
|
+
] }, v.varName);
|
|
5495
|
+
})
|
|
4078
5496
|
] }),
|
|
4079
5497
|
mode === "bulk" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3 space-y-3", children: [
|
|
4080
5498
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
@@ -4263,56 +5681,60 @@ ${allWarnings.join("\n")}`);
|
|
|
4263
5681
|
] }),
|
|
4264
5682
|
!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
5683
|
] }),
|
|
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
|
-
|
|
5684
|
+
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: [
|
|
5685
|
+
mode === "single" || !hasVariables ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
5686
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5687
|
+
Button,
|
|
5688
|
+
{
|
|
5689
|
+
variant: "secondary",
|
|
5690
|
+
onClick: handlePreview,
|
|
5691
|
+
disabled: isGenerating,
|
|
5692
|
+
className: "h-10 px-4 font-semibold shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
5693
|
+
children: [
|
|
5694
|
+
isGenerating ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: 16 }),
|
|
5695
|
+
isGenerating ? "Generating..." : "Generate PDF"
|
|
5696
|
+
]
|
|
5697
|
+
}
|
|
5698
|
+
),
|
|
5699
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5700
|
+
Button,
|
|
5701
|
+
{
|
|
5702
|
+
onClick: handleExportSingle,
|
|
5703
|
+
disabled: !previewFresh || isExporting,
|
|
5704
|
+
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",
|
|
5705
|
+
children: [
|
|
5706
|
+
isExporting ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { size: 16 }),
|
|
5707
|
+
isExporting ? "Exporting..." : "Export PDF"
|
|
5708
|
+
]
|
|
5709
|
+
}
|
|
5710
|
+
)
|
|
5711
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4293
5712
|
Button,
|
|
4294
5713
|
{
|
|
4295
|
-
onClick:
|
|
4296
|
-
disabled: !
|
|
4297
|
-
className: "h-10 px-4 font-semibold bg-primary hover:bg-primary/90 text-primary-foreground shadow-
|
|
5714
|
+
onClick: handleBulkGenerate,
|
|
5715
|
+
disabled: isExporting || !bulkData || bulkData.length === 0,
|
|
5716
|
+
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
5717
|
children: [
|
|
4299
5718
|
isExporting ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { size: 16 }),
|
|
4300
|
-
isExporting ? "
|
|
5719
|
+
isExporting ? "Generating..." : `Generate All (${bulkData?.length || 0} PDFs)`
|
|
4301
5720
|
]
|
|
4302
5721
|
}
|
|
4303
|
-
)
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
]
|
|
4314
|
-
|
|
4315
|
-
|
|
5722
|
+
),
|
|
5723
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-1 ml-auto", children: [
|
|
5724
|
+
previewError && /* @__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: previewError })
|
|
5727
|
+
] }),
|
|
5728
|
+
exportError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
5729
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
5730
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: exportError })
|
|
5731
|
+
] }),
|
|
5732
|
+
exportSuccess && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-emerald-600 max-w-[300px]", children: [
|
|
5733
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCircle2, { size: 14, className: "shrink-0" }),
|
|
5734
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: exportSuccess })
|
|
5735
|
+
] })
|
|
5736
|
+
] })
|
|
5737
|
+
] })
|
|
4316
5738
|
] }),
|
|
4317
5739
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-border" }),
|
|
4318
5740
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col min-h-0 min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4490,7 +5912,13 @@ function DocumentGeneratorInner({
|
|
|
4490
5912
|
showToolbar = true,
|
|
4491
5913
|
showGenerateTab = true,
|
|
4492
5914
|
onGeneratePdf,
|
|
4493
|
-
initialBulkData
|
|
5915
|
+
initialBulkData,
|
|
5916
|
+
defaultTab = "editor",
|
|
5917
|
+
initialVariableValues,
|
|
5918
|
+
predefinedVariables,
|
|
5919
|
+
exportFileName,
|
|
5920
|
+
templateMode,
|
|
5921
|
+
onSaveTemplate
|
|
4494
5922
|
}) {
|
|
4495
5923
|
const {
|
|
4496
5924
|
editor,
|
|
@@ -4517,7 +5945,7 @@ function DocumentGeneratorInner({
|
|
|
4517
5945
|
placeholder,
|
|
4518
5946
|
onChange
|
|
4519
5947
|
});
|
|
4520
|
-
const [activeTab, setActiveTab] = React12.useState(
|
|
5948
|
+
const [activeTab, setActiveTab] = React12.useState(defaultTab);
|
|
4521
5949
|
const [insertPopoverOpen, setInsertPopoverOpen] = React12.useState(false);
|
|
4522
5950
|
const [insertVarPopoverOpen, setInsertVarPopoverOpen] = React12.useState(false);
|
|
4523
5951
|
const [editFieldId, setEditFieldId] = React12.useState(null);
|
|
@@ -4600,6 +6028,21 @@ function DocumentGeneratorInner({
|
|
|
4600
6028
|
setExportSuccess("Document exported successfully");
|
|
4601
6029
|
}
|
|
4602
6030
|
}, [exportDocument, onExport]);
|
|
6031
|
+
const handleSaveTemplate = React12.useCallback(() => {
|
|
6032
|
+
const md = exportMarkdown();
|
|
6033
|
+
if (!md.trim()) {
|
|
6034
|
+
setMdExportError("Editor content is empty");
|
|
6035
|
+
return;
|
|
6036
|
+
}
|
|
6037
|
+
const validation = validateMarkdown(md);
|
|
6038
|
+
if (!validation.valid) {
|
|
6039
|
+
setMdExportError(validation.errors.join("; "));
|
|
6040
|
+
return;
|
|
6041
|
+
}
|
|
6042
|
+
setMdExportError(null);
|
|
6043
|
+
onSaveTemplate?.(md);
|
|
6044
|
+
setExportSuccess("Template saved");
|
|
6045
|
+
}, [exportMarkdown, onSaveTemplate]);
|
|
4603
6046
|
const handleExportMarkdown = React12.useCallback(() => {
|
|
4604
6047
|
const md = exportMarkdown();
|
|
4605
6048
|
if (!md.trim()) {
|
|
@@ -4616,10 +6059,10 @@ function DocumentGeneratorInner({
|
|
|
4616
6059
|
const url = URL.createObjectURL(blob);
|
|
4617
6060
|
const a = document.createElement("a");
|
|
4618
6061
|
a.href = url;
|
|
4619
|
-
a.download = "template.md";
|
|
6062
|
+
a.download = exportFileName ? `${exportFileName}.md` : "template.md";
|
|
4620
6063
|
a.click();
|
|
4621
6064
|
URL.revokeObjectURL(url);
|
|
4622
|
-
}, [exportMarkdown]);
|
|
6065
|
+
}, [exportMarkdown, exportFileName]);
|
|
4623
6066
|
React12.useEffect(() => {
|
|
4624
6067
|
if (mdExportError) setMdExportError(null);
|
|
4625
6068
|
}, [markdown]);
|
|
@@ -4655,6 +6098,7 @@ function DocumentGeneratorInner({
|
|
|
4655
6098
|
open: insertVarPopoverOpen,
|
|
4656
6099
|
onOpenChange: setInsertVarPopoverOpen,
|
|
4657
6100
|
onInsert: handleInsertVariable,
|
|
6101
|
+
predefinedVariables,
|
|
4658
6102
|
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4659
6103
|
Button,
|
|
4660
6104
|
{
|
|
@@ -4695,118 +6139,141 @@ function DocumentGeneratorInner({
|
|
|
4695
6139
|
] })
|
|
4696
6140
|
] })
|
|
4697
6141
|
] }),
|
|
4698
|
-
/* @__PURE__ */ jsxRuntime.
|
|
6142
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(
|
|
4699
6143
|
"flex flex-col flex-1 min-h-0",
|
|
4700
6144
|
activeTab !== "editor" && "hidden"
|
|
6145
|
+
), children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(
|
|
6146
|
+
"flex-1 min-h-0",
|
|
6147
|
+
showPreview && !editorCollapsed ? "grid grid-cols-[1fr_1px_1fr]" : "flex flex-col"
|
|
4701
6148
|
), 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,
|
|
6149
|
+
!editorCollapsed && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col min-h-0 min-w-0", children: [
|
|
6150
|
+
showToolbar && !readOnly && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-border", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
6151
|
+
EditorToolbar,
|
|
4727
6152
|
{
|
|
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"
|
|
6153
|
+
editor,
|
|
6154
|
+
insertFieldButton,
|
|
6155
|
+
insertVariableButton,
|
|
6156
|
+
onCollapse: showPreview ? () => setEditorCollapsed(true) : void 0
|
|
4742
6157
|
}
|
|
4743
|
-
)
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4747
|
-
Button,
|
|
6158
|
+
) }),
|
|
6159
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: editorWrapperRef, className: "flex-1 overflow-y-auto min-h-0 scrollbar-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
6160
|
+
react.EditorContent,
|
|
4748
6161
|
{
|
|
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
|
-
]
|
|
6162
|
+
editor,
|
|
6163
|
+
className: "prose prose-sm max-w-none p-4 focus-within:outline-none"
|
|
4757
6164
|
}
|
|
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: [
|
|
6165
|
+
) }),
|
|
6166
|
+
/* @__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
6167
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4784
6168
|
Button,
|
|
4785
6169
|
{
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
6170
|
+
variant: "secondary",
|
|
6171
|
+
onClick: generatePdf,
|
|
6172
|
+
disabled: isGenerating || !editor,
|
|
6173
|
+
className: "h-10 px-4 font-semibold shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
4789
6174
|
children: [
|
|
4790
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.
|
|
4791
|
-
"
|
|
6175
|
+
isGenerating ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: 16 }),
|
|
6176
|
+
isGenerating ? "Generating..." : "Generate PDF"
|
|
4792
6177
|
]
|
|
4793
6178
|
}
|
|
4794
6179
|
),
|
|
4795
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6180
|
+
onSaveTemplate ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4796
6181
|
Button,
|
|
4797
6182
|
{
|
|
4798
|
-
onClick:
|
|
4799
|
-
disabled:
|
|
6183
|
+
onClick: handleSaveTemplate,
|
|
6184
|
+
disabled: !editor || !markdown.trim(),
|
|
4800
6185
|
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
6186
|
children: [
|
|
4802
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.
|
|
4803
|
-
exportButtonText
|
|
6187
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Save, { size: 16 }),
|
|
6188
|
+
exportButtonText || "Save Template"
|
|
4804
6189
|
]
|
|
4805
6190
|
}
|
|
4806
|
-
)
|
|
6191
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs(Popover, { children: [
|
|
6192
|
+
/* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6193
|
+
Button,
|
|
6194
|
+
{
|
|
6195
|
+
disabled: !editor || !markdown.trim() && pdfPages.length === 0,
|
|
6196
|
+
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",
|
|
6197
|
+
children: [
|
|
6198
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileDown, { size: 16 }),
|
|
6199
|
+
"Export",
|
|
6200
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { size: 14 })
|
|
6201
|
+
]
|
|
6202
|
+
}
|
|
6203
|
+
) }),
|
|
6204
|
+
/* @__PURE__ */ jsxRuntime.jsxs(PopoverContent, { align: "start", className: "w-48 p-1", children: [
|
|
6205
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6206
|
+
"button",
|
|
6207
|
+
{
|
|
6208
|
+
onClick: handleExportMarkdown,
|
|
6209
|
+
disabled: !editor || !markdown.trim(),
|
|
6210
|
+
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",
|
|
6211
|
+
children: [
|
|
6212
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileText, { size: 16 }),
|
|
6213
|
+
"Export as MD"
|
|
6214
|
+
]
|
|
6215
|
+
}
|
|
6216
|
+
),
|
|
6217
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6218
|
+
"button",
|
|
6219
|
+
{
|
|
6220
|
+
onClick: handleExport,
|
|
6221
|
+
disabled: isGenerating || !editor || pdfPages.length === 0,
|
|
6222
|
+
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",
|
|
6223
|
+
children: [
|
|
6224
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileDown, { size: 16 }),
|
|
6225
|
+
"Export as PDF"
|
|
6226
|
+
]
|
|
6227
|
+
}
|
|
6228
|
+
)
|
|
6229
|
+
] })
|
|
6230
|
+
] }),
|
|
6231
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-1 ml-auto", children: [
|
|
6232
|
+
mdExportError && /* @__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: mdExportError })
|
|
6235
|
+
] }),
|
|
6236
|
+
generationError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
6237
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
6238
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: generationError })
|
|
6239
|
+
] }),
|
|
6240
|
+
fieldWarnings.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-orange-600 max-w-[400px]", children: [
|
|
6241
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
6242
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "truncate", children: [
|
|
6243
|
+
fieldWarnings.length,
|
|
6244
|
+
" field",
|
|
6245
|
+
fieldWarnings.length !== 1 ? "s" : "",
|
|
6246
|
+
" had warnings during generation"
|
|
6247
|
+
] })
|
|
6248
|
+
] }),
|
|
6249
|
+
exportSuccess && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-emerald-600 max-w-[300px]", children: [
|
|
6250
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCircle2, { size: 14, className: "shrink-0" }),
|
|
6251
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: exportSuccess })
|
|
6252
|
+
] })
|
|
6253
|
+
] })
|
|
4807
6254
|
] })
|
|
4808
|
-
] })
|
|
4809
|
-
|
|
6255
|
+
] }),
|
|
6256
|
+
showPreview && !editorCollapsed && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-border" }),
|
|
6257
|
+
showPreview && /* @__PURE__ */ jsxRuntime.jsx(
|
|
6258
|
+
PreviewPanel,
|
|
6259
|
+
{
|
|
6260
|
+
pages: pdfPages,
|
|
6261
|
+
isGenerating,
|
|
6262
|
+
positionedFields,
|
|
6263
|
+
headerLeft: editorCollapsed ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
6264
|
+
Button,
|
|
6265
|
+
{
|
|
6266
|
+
variant: "ghost",
|
|
6267
|
+
size: "sm",
|
|
6268
|
+
className: "h-8 w-8 p-0",
|
|
6269
|
+
onClick: () => setEditorCollapsed(false),
|
|
6270
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PanelLeftOpen, { size: 14 })
|
|
6271
|
+
}
|
|
6272
|
+
) : void 0,
|
|
6273
|
+
className: "flex-1 border-0 rounded-none"
|
|
6274
|
+
}
|
|
6275
|
+
)
|
|
6276
|
+
] }) }),
|
|
4810
6277
|
showGenerateTab && activeTab === "generate" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4811
6278
|
GeneratePanel,
|
|
4812
6279
|
{
|
|
@@ -4814,7 +6281,10 @@ function DocumentGeneratorInner({
|
|
|
4814
6281
|
editorContent: editor?.getJSON(),
|
|
4815
6282
|
editorVariables: variables,
|
|
4816
6283
|
onGeneratePdf,
|
|
4817
|
-
initialBulkData
|
|
6284
|
+
initialBulkData,
|
|
6285
|
+
initialVariableValues,
|
|
6286
|
+
exportFileName,
|
|
6287
|
+
templateMode
|
|
4818
6288
|
}
|
|
4819
6289
|
),
|
|
4820
6290
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4955,11 +6425,733 @@ function EditorPanel({
|
|
|
4955
6425
|
] });
|
|
4956
6426
|
}
|
|
4957
6427
|
|
|
6428
|
+
// src/utils/xml-template-parser.ts
|
|
6429
|
+
function extractFieldRefs(expr) {
|
|
6430
|
+
const refs = [];
|
|
6431
|
+
const re = /\[([^\]]+)\](?:\.\[([^\]]+)\])?/g;
|
|
6432
|
+
let m;
|
|
6433
|
+
while ((m = re.exec(expr)) !== null) {
|
|
6434
|
+
if (m[2]) {
|
|
6435
|
+
refs.push(`${m[1]}.${m[2]}`);
|
|
6436
|
+
} else {
|
|
6437
|
+
refs.push(m[1]);
|
|
6438
|
+
}
|
|
6439
|
+
}
|
|
6440
|
+
return refs;
|
|
6441
|
+
}
|
|
6442
|
+
function fieldRefToVarName(ref, bandContext) {
|
|
6443
|
+
if (ref.includes(".")) {
|
|
6444
|
+
return labelToVarName(ref.replace(/\./g, "_"));
|
|
6445
|
+
}
|
|
6446
|
+
if (bandContext) {
|
|
6447
|
+
return labelToVarName(bandContext + "_" + ref);
|
|
6448
|
+
}
|
|
6449
|
+
return labelToVarName(ref);
|
|
6450
|
+
}
|
|
6451
|
+
function fieldRefToLabel(ref) {
|
|
6452
|
+
const parts = ref.split(".");
|
|
6453
|
+
const raw = parts[parts.length - 1];
|
|
6454
|
+
return raw.replace(/_/g, " ");
|
|
6455
|
+
}
|
|
6456
|
+
function resolveExpression(expr, registry, siblingLabel, bandContext) {
|
|
6457
|
+
const trimmed = expr.trim();
|
|
6458
|
+
const suppressZero = /<>\s*0/.test(trimmed);
|
|
6459
|
+
const szOpts = suppressZero ? { suppressZero: true } : void 0;
|
|
6460
|
+
const aggMatch = trimmed.match(
|
|
6461
|
+
/^\[([^\]]+)\]\.sum\(\[([^\]]+)\]\)$/i
|
|
6462
|
+
);
|
|
6463
|
+
if (aggMatch) {
|
|
6464
|
+
const fieldName = aggMatch[2];
|
|
6465
|
+
const varName = labelToVarName("workorder_" + fieldName);
|
|
6466
|
+
const label = fieldName.replace(/_/g, " ");
|
|
6467
|
+
return registry.register(varName, label, szOpts);
|
|
6468
|
+
}
|
|
6469
|
+
const concatSingle = trimmed.match(
|
|
6470
|
+
/^concat\(\s*'[^']*'\s*,\s*\[([^\]]+)\](?:\.\[([^\]]+)\])?\s*\)$/i
|
|
6471
|
+
);
|
|
6472
|
+
if (concatSingle) {
|
|
6473
|
+
const ref = concatSingle[2] ? `${concatSingle[1]}.${concatSingle[2]}` : concatSingle[1];
|
|
6474
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6475
|
+
const label = fieldRefToLabel(ref);
|
|
6476
|
+
return registry.register(varName, label, szOpts);
|
|
6477
|
+
}
|
|
6478
|
+
const concatMulti = trimmed.match(
|
|
6479
|
+
/^concat\(/i
|
|
6480
|
+
);
|
|
6481
|
+
if (concatMulti) {
|
|
6482
|
+
const refs = extractFieldRefs(trimmed);
|
|
6483
|
+
if (refs.length > 0) {
|
|
6484
|
+
const uniqueRefs = [...new Set(refs)];
|
|
6485
|
+
const tokens = uniqueRefs.map((ref) => {
|
|
6486
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6487
|
+
const label = fieldRefToLabel(ref);
|
|
6488
|
+
return registry.register(varName, label, szOpts);
|
|
6489
|
+
});
|
|
6490
|
+
return tokens.join(" ");
|
|
6491
|
+
}
|
|
6492
|
+
}
|
|
6493
|
+
const toLongMatch = trimmed.match(
|
|
6494
|
+
/^ToLong\(\s*\[([^\]]+)\](?:\.\[([^\]]+)\])?\s*\)$/i
|
|
6495
|
+
);
|
|
6496
|
+
if (toLongMatch) {
|
|
6497
|
+
const ref = toLongMatch[2] ? `${toLongMatch[1]}.${toLongMatch[2]}` : toLongMatch[1];
|
|
6498
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6499
|
+
const label = fieldRefToLabel(ref);
|
|
6500
|
+
return registry.register(varName, label, szOpts);
|
|
6501
|
+
}
|
|
6502
|
+
const fmtMatch = trimmed.match(
|
|
6503
|
+
/^FormatString\(\s*'[^']*'\s*,\s*(.+)\s*\)$/i
|
|
6504
|
+
);
|
|
6505
|
+
if (fmtMatch) {
|
|
6506
|
+
return resolveExpression(fmtMatch[1], registry, siblingLabel, bandContext);
|
|
6507
|
+
}
|
|
6508
|
+
const replaceMatch = trimmed.match(
|
|
6509
|
+
/^Replace\(\s*\[([^\]]+)\]/i
|
|
6510
|
+
);
|
|
6511
|
+
if (replaceMatch) {
|
|
6512
|
+
const ref = replaceMatch[1];
|
|
6513
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6514
|
+
const label = fieldRefToLabel(ref);
|
|
6515
|
+
return registry.register(varName, label, szOpts);
|
|
6516
|
+
}
|
|
6517
|
+
const iifLabelMatch = trimmed.match(
|
|
6518
|
+
/^iif\s*\(.+?,\s*'([^']+)'\s*,\s*\?\s*\)$/i
|
|
6519
|
+
);
|
|
6520
|
+
if (iifLabelMatch) {
|
|
6521
|
+
return iifLabelMatch[1];
|
|
6522
|
+
}
|
|
6523
|
+
const iifLabelElse = trimmed.match(
|
|
6524
|
+
/^iif\s*\(.+?,\s*'([^']+)'\s*,\s*'([^']+)'\s*\)$/i
|
|
6525
|
+
);
|
|
6526
|
+
if (iifLabelElse) {
|
|
6527
|
+
return iifLabelElse[1];
|
|
6528
|
+
}
|
|
6529
|
+
const iifEmptyElse = trimmed.match(
|
|
6530
|
+
/^[Ii]if\s*\(.+?,\s*'([^']+)'\s*,\s*''\s*\)$/i
|
|
6531
|
+
);
|
|
6532
|
+
if (iifEmptyElse) {
|
|
6533
|
+
return iifEmptyElse[1];
|
|
6534
|
+
}
|
|
6535
|
+
const nestedIifStaticLabel = trimmed.match(
|
|
6536
|
+
/^iif\s*\(.*,\s*[Ii]if\s*\([^,]+,\s*'([^']+)'\s*,/i
|
|
6537
|
+
);
|
|
6538
|
+
if (nestedIifStaticLabel) {
|
|
6539
|
+
return nestedIifStaticLabel[1];
|
|
6540
|
+
}
|
|
6541
|
+
const iifValueMatch = trimmed.match(
|
|
6542
|
+
/^iif\s*\(/i
|
|
6543
|
+
);
|
|
6544
|
+
if (iifValueMatch) {
|
|
6545
|
+
const refs = extractFieldRefs(trimmed);
|
|
6546
|
+
if (refs.length > 0) {
|
|
6547
|
+
const uniqueRefs = [...new Set(refs)];
|
|
6548
|
+
const innerAggMatch = trimmed.match(
|
|
6549
|
+
/\[([^\]]+)\]\.sum\(\[([^\]]+)\]\)/i
|
|
6550
|
+
);
|
|
6551
|
+
if (innerAggMatch) {
|
|
6552
|
+
const fieldName = innerAggMatch[2];
|
|
6553
|
+
const varName2 = labelToVarName("workorder_" + fieldName);
|
|
6554
|
+
const label2 = fieldName.replace(/_/g, " ");
|
|
6555
|
+
return registry.register(varName2, label2, szOpts);
|
|
6556
|
+
}
|
|
6557
|
+
const ref = uniqueRefs[uniqueRefs.length - 1];
|
|
6558
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6559
|
+
const label = fieldRefToLabel(ref);
|
|
6560
|
+
return registry.register(varName, label, szOpts);
|
|
6561
|
+
}
|
|
6562
|
+
const staticMatch = trimmed.match(/'([^']+)'/);
|
|
6563
|
+
if (staticMatch) return staticMatch[1];
|
|
6564
|
+
return "";
|
|
6565
|
+
}
|
|
6566
|
+
const arithmeticRefs = extractFieldRefs(trimmed);
|
|
6567
|
+
if (arithmeticRefs.length > 1 && /[+\-*/]/.test(trimmed)) {
|
|
6568
|
+
if (siblingLabel) {
|
|
6569
|
+
const cleanLabel = siblingLabel.replace(/[:\s]+$/, "");
|
|
6570
|
+
const varName = labelToVarName(
|
|
6571
|
+
(bandContext ? bandContext + "_" : "") + cleanLabel.replace(/\s+/g, "_")
|
|
6572
|
+
);
|
|
6573
|
+
return registry.register(varName, cleanLabel, szOpts);
|
|
6574
|
+
}
|
|
6575
|
+
const uniqueRefs = [...new Set(arithmeticRefs)];
|
|
6576
|
+
const tokens = uniqueRefs.map((ref) => {
|
|
6577
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6578
|
+
const label = fieldRefToLabel(ref);
|
|
6579
|
+
return registry.register(varName, label, szOpts);
|
|
6580
|
+
});
|
|
6581
|
+
return tokens.join(" ");
|
|
6582
|
+
}
|
|
6583
|
+
const simpleMatch = trimmed.match(
|
|
6584
|
+
/^\[([^\]]+)\](?:\.\[([^\]]+)\])?$/
|
|
6585
|
+
);
|
|
6586
|
+
if (simpleMatch) {
|
|
6587
|
+
const ref = simpleMatch[2] ? `${simpleMatch[1]}.${simpleMatch[2]}` : simpleMatch[1];
|
|
6588
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6589
|
+
const label = fieldRefToLabel(ref);
|
|
6590
|
+
return registry.register(varName, label, szOpts);
|
|
6591
|
+
}
|
|
6592
|
+
if (arithmeticRefs.length >= 1) {
|
|
6593
|
+
const ref = arithmeticRefs[arithmeticRefs.length - 1];
|
|
6594
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6595
|
+
const label = fieldRefToLabel(ref);
|
|
6596
|
+
return registry.register(varName, label, szOpts);
|
|
6597
|
+
}
|
|
6598
|
+
return "";
|
|
6599
|
+
}
|
|
6600
|
+
var VariableRegistry = class {
|
|
6601
|
+
constructor() {
|
|
6602
|
+
__publicField(this, "map", /* @__PURE__ */ new Map());
|
|
6603
|
+
}
|
|
6604
|
+
register(varName, varLabel, options) {
|
|
6605
|
+
if (!this.map.has(varName)) {
|
|
6606
|
+
this.map.set(varName, { varName, varLabel, varDefault: "" });
|
|
6607
|
+
}
|
|
6608
|
+
const suppress = options?.suppressZero ? "|suppress:zero" : "";
|
|
6609
|
+
return `{{var|name:${varName}|label:${varLabel}${suppress}}}`;
|
|
6610
|
+
}
|
|
6611
|
+
getAll() {
|
|
6612
|
+
return Array.from(this.map.values());
|
|
6613
|
+
}
|
|
6614
|
+
};
|
|
6615
|
+
function parseLocationFloat(s) {
|
|
6616
|
+
if (!s) return { x: 0, y: 0 };
|
|
6617
|
+
const parts = s.split(",");
|
|
6618
|
+
return {
|
|
6619
|
+
x: parseFloat(parts[0]) || 0,
|
|
6620
|
+
y: parseFloat(parts[1]) || 0
|
|
6621
|
+
};
|
|
6622
|
+
}
|
|
6623
|
+
function parseSizeF(s) {
|
|
6624
|
+
if (!s) return { w: 0, h: 0 };
|
|
6625
|
+
const parts = s.split(",");
|
|
6626
|
+
return {
|
|
6627
|
+
w: parseFloat(parts[0]) || 0,
|
|
6628
|
+
h: parseFloat(parts[1]) || 0
|
|
6629
|
+
};
|
|
6630
|
+
}
|
|
6631
|
+
function extractControls(bandEl) {
|
|
6632
|
+
const controlsEl = bandEl.querySelector(":scope > Controls");
|
|
6633
|
+
if (!controlsEl) return [];
|
|
6634
|
+
const items = [];
|
|
6635
|
+
for (const child of Array.from(controlsEl.children)) {
|
|
6636
|
+
items.push(extractControl(child));
|
|
6637
|
+
}
|
|
6638
|
+
return items;
|
|
6639
|
+
}
|
|
6640
|
+
function extractControl(el) {
|
|
6641
|
+
const ctrlType = el.getAttribute("ControlType") || "";
|
|
6642
|
+
const name = el.getAttribute("Name") || "";
|
|
6643
|
+
const text = el.getAttribute("Text") || "";
|
|
6644
|
+
const loc = parseLocationFloat(el.getAttribute("LocationFloat"));
|
|
6645
|
+
const size = parseSizeF(el.getAttribute("SizeF"));
|
|
6646
|
+
const formatString = el.getAttribute("TextFormatString") || null;
|
|
6647
|
+
let expression = null;
|
|
6648
|
+
const bindingsEl = el.querySelector(":scope > ExpressionBindings");
|
|
6649
|
+
if (bindingsEl) {
|
|
6650
|
+
for (const item of Array.from(bindingsEl.children)) {
|
|
6651
|
+
if (item.getAttribute("PropertyName") === "Text" && item.getAttribute("EventName") === "BeforePrint") {
|
|
6652
|
+
expression = item.getAttribute("Expression");
|
|
6653
|
+
break;
|
|
6654
|
+
}
|
|
6655
|
+
}
|
|
6656
|
+
}
|
|
6657
|
+
const children = [];
|
|
6658
|
+
if (ctrlType === "XRPanel") {
|
|
6659
|
+
const panelControls = extractControls(el);
|
|
6660
|
+
children.push(...panelControls);
|
|
6661
|
+
}
|
|
6662
|
+
return {
|
|
6663
|
+
name,
|
|
6664
|
+
type: ctrlType,
|
|
6665
|
+
text,
|
|
6666
|
+
x: loc.x,
|
|
6667
|
+
y: loc.y,
|
|
6668
|
+
width: size.w,
|
|
6669
|
+
expression,
|
|
6670
|
+
formatString,
|
|
6671
|
+
children
|
|
6672
|
+
};
|
|
6673
|
+
}
|
|
6674
|
+
function groupByRow(controls, threshold = 8) {
|
|
6675
|
+
const sorted = [...controls].sort((a, b) => a.y - b.y);
|
|
6676
|
+
const rows = [];
|
|
6677
|
+
for (const ctrl of sorted) {
|
|
6678
|
+
const lastRow = rows[rows.length - 1];
|
|
6679
|
+
if (lastRow && Math.abs(ctrl.y - lastRow.y) < threshold) {
|
|
6680
|
+
lastRow.controls.push(ctrl);
|
|
6681
|
+
} else {
|
|
6682
|
+
rows.push({ y: ctrl.y, controls: [ctrl] });
|
|
6683
|
+
}
|
|
6684
|
+
}
|
|
6685
|
+
for (const row of rows) {
|
|
6686
|
+
row.controls.sort((a, b) => a.x - b.x);
|
|
6687
|
+
}
|
|
6688
|
+
return rows;
|
|
6689
|
+
}
|
|
6690
|
+
function isStaticLabel(ctrl) {
|
|
6691
|
+
return ctrl.type === "XRLabel" && !ctrl.expression;
|
|
6692
|
+
}
|
|
6693
|
+
function isBound(ctrl) {
|
|
6694
|
+
return ctrl.type === "XRLabel" && !!ctrl.expression;
|
|
6695
|
+
}
|
|
6696
|
+
function computeSplitPercent(leftControls, rightControls, contentWidth) {
|
|
6697
|
+
const leftExtent = leftControls.reduce(
|
|
6698
|
+
(max, c) => Math.max(max, c.x + c.width),
|
|
6699
|
+
0
|
|
6700
|
+
);
|
|
6701
|
+
const rightMinX = rightControls.reduce(
|
|
6702
|
+
(min, c) => Math.min(min, c.x),
|
|
6703
|
+
contentWidth
|
|
6704
|
+
);
|
|
6705
|
+
const rightMaxExtent = rightControls.reduce(
|
|
6706
|
+
(max, c) => Math.max(max, c.x + c.width),
|
|
6707
|
+
0
|
|
6708
|
+
);
|
|
6709
|
+
const rightExtent = rightMaxExtent - rightMinX;
|
|
6710
|
+
if (leftExtent + rightExtent === 0) return 50;
|
|
6711
|
+
return Math.round(leftExtent / (leftExtent + rightExtent) * 100);
|
|
6712
|
+
}
|
|
6713
|
+
function renderTopMarginBand(controls, registry, contentWidth, calculatedFieldNames) {
|
|
6714
|
+
const lines = [];
|
|
6715
|
+
const leftControls = controls.filter((c) => c.x < 437);
|
|
6716
|
+
const rightControls = controls.filter((c) => c.x >= 437);
|
|
6717
|
+
const leftEntries = [];
|
|
6718
|
+
if (leftControls.length > 0) {
|
|
6719
|
+
const leftRows = groupByRow(leftControls);
|
|
6720
|
+
for (const row of leftRows) {
|
|
6721
|
+
const parts = [];
|
|
6722
|
+
for (const ctrl of row.controls) {
|
|
6723
|
+
if (isBound(ctrl)) {
|
|
6724
|
+
let resolved = resolveExpression(ctrl.expression, registry, void 0, "workorder");
|
|
6725
|
+
if (ctrl.formatString && /\(###\)\s*###\s*-\s*####/.test(ctrl.formatString) && resolved.startsWith("{{var|")) {
|
|
6726
|
+
resolved = resolved.replace("}}", "|format:phone}}");
|
|
6727
|
+
}
|
|
6728
|
+
if (resolved) parts.push(resolved);
|
|
6729
|
+
} else if (isStaticLabel(ctrl)) {
|
|
6730
|
+
const text = ctrl.text.trim();
|
|
6731
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
6732
|
+
parts.push(text);
|
|
6733
|
+
}
|
|
6734
|
+
}
|
|
6735
|
+
}
|
|
6736
|
+
if (parts.length > 0) {
|
|
6737
|
+
leftEntries.push(parts.join(" "));
|
|
6738
|
+
}
|
|
6739
|
+
}
|
|
6740
|
+
}
|
|
6741
|
+
const rightEntries = [];
|
|
6742
|
+
if (rightControls.length > 0) {
|
|
6743
|
+
const rightRows = groupByRow(rightControls);
|
|
6744
|
+
for (const row of rightRows) {
|
|
6745
|
+
let label = "";
|
|
6746
|
+
let value = "";
|
|
6747
|
+
for (const ctrl of row.controls) {
|
|
6748
|
+
if (isStaticLabel(ctrl)) {
|
|
6749
|
+
const text = ctrl.text.trim();
|
|
6750
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
6751
|
+
label = text;
|
|
6752
|
+
}
|
|
6753
|
+
} else if (isBound(ctrl)) {
|
|
6754
|
+
value = resolveExpression(ctrl.expression, registry, label, "workorder");
|
|
6755
|
+
if (ctrl.expression) {
|
|
6756
|
+
const refs = extractFieldRefs(ctrl.expression);
|
|
6757
|
+
if (refs.some((ref) => calculatedFieldNames.has(ref)) && value.startsWith("{{var|")) {
|
|
6758
|
+
value = value.replace("}}", "|suppress:zero}}");
|
|
6759
|
+
}
|
|
6760
|
+
}
|
|
6761
|
+
if (ctrl.formatString && /\(###\)\s*###\s*-\s*####/.test(ctrl.formatString) && value.startsWith("{{var|")) {
|
|
6762
|
+
value = value.replace("}}", "|format:phone}}");
|
|
6763
|
+
}
|
|
6764
|
+
}
|
|
6765
|
+
}
|
|
6766
|
+
if (label || value) {
|
|
6767
|
+
rightEntries.push({ label, value, y: row.y });
|
|
6768
|
+
}
|
|
6769
|
+
}
|
|
6770
|
+
}
|
|
6771
|
+
if (leftEntries.length > 0 || rightEntries.length > 0) {
|
|
6772
|
+
const outerSplit = computeSplitPercent(leftControls, rightControls, contentWidth);
|
|
6773
|
+
lines.push(`:::columns{split:${outerSplit}}`);
|
|
6774
|
+
const leftMinY = leftControls.length > 0 ? Math.min(...leftControls.map((c) => c.y)) : 0;
|
|
6775
|
+
const rightMinY = rightControls.length > 0 ? Math.min(...rightControls.map((c) => c.y)) : 0;
|
|
6776
|
+
const xmlLineHeight = 17;
|
|
6777
|
+
const leftPadTop = leftMinY > rightMinY ? Math.round((leftMinY - rightMinY) / xmlLineHeight) : 0;
|
|
6778
|
+
const rightPadTop = rightMinY > leftMinY ? Math.round((rightMinY - leftMinY) / xmlLineHeight) : 0;
|
|
6779
|
+
const leftColTag = leftPadTop ? `:::col{padTop:${leftPadTop}}` : ":::col";
|
|
6780
|
+
lines.push(leftColTag);
|
|
6781
|
+
for (const entry of leftEntries) {
|
|
6782
|
+
lines.push(entry);
|
|
6783
|
+
}
|
|
6784
|
+
lines.push(":::");
|
|
6785
|
+
const rightColTag = rightPadTop ? `:::col{padTop:${rightPadTop}}` : ":::col";
|
|
6786
|
+
lines.push(rightColTag);
|
|
6787
|
+
if (rightEntries.length > 0) {
|
|
6788
|
+
lines.push("| | |");
|
|
6789
|
+
lines.push("|---|---|");
|
|
6790
|
+
let prevY = -Infinity;
|
|
6791
|
+
for (const entry of rightEntries) {
|
|
6792
|
+
if (prevY > -Infinity && entry.y - prevY > 25) {
|
|
6793
|
+
lines.push("| | |");
|
|
6794
|
+
}
|
|
6795
|
+
const label = entry.label || "";
|
|
6796
|
+
const colon = entry.label ? ":" : "";
|
|
6797
|
+
const value = entry.value ? ` ${entry.value}` : "";
|
|
6798
|
+
lines.push(`| ${label} | ${colon}${value} |`);
|
|
6799
|
+
prevY = entry.y;
|
|
6800
|
+
}
|
|
6801
|
+
}
|
|
6802
|
+
lines.push(":::");
|
|
6803
|
+
lines.push(":::");
|
|
6804
|
+
lines.push("");
|
|
6805
|
+
}
|
|
6806
|
+
return lines.join("\n");
|
|
6807
|
+
}
|
|
6808
|
+
function renderReportHeaderBand(controls, registry, contentWidth) {
|
|
6809
|
+
const lines = [];
|
|
6810
|
+
const leftControls = controls.filter((c) => c.x < 437);
|
|
6811
|
+
const rightControls = controls.filter((c) => c.x >= 437);
|
|
6812
|
+
const leftEntries = [];
|
|
6813
|
+
if (leftControls.length > 0) {
|
|
6814
|
+
const leftRows = groupByRow(leftControls);
|
|
6815
|
+
for (const row of leftRows) {
|
|
6816
|
+
const parts = [];
|
|
6817
|
+
for (const ctrl of row.controls) {
|
|
6818
|
+
if (isBound(ctrl)) {
|
|
6819
|
+
const resolved = resolveExpression(ctrl.expression, registry, void 0, "workorder");
|
|
6820
|
+
if (resolved) parts.push(resolved);
|
|
6821
|
+
} else if (isStaticLabel(ctrl)) {
|
|
6822
|
+
const text = ctrl.text.trim();
|
|
6823
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
6824
|
+
parts.push(text);
|
|
6825
|
+
}
|
|
6826
|
+
}
|
|
6827
|
+
}
|
|
6828
|
+
if (parts.length > 0) {
|
|
6829
|
+
leftEntries.push(parts.join(" "));
|
|
6830
|
+
}
|
|
6831
|
+
}
|
|
6832
|
+
}
|
|
6833
|
+
const rightEntries = [];
|
|
6834
|
+
if (rightControls.length > 0) {
|
|
6835
|
+
const rightRows = groupByRow(rightControls);
|
|
6836
|
+
for (const row of rightRows) {
|
|
6837
|
+
let label = "";
|
|
6838
|
+
let value = "";
|
|
6839
|
+
for (const ctrl of row.controls) {
|
|
6840
|
+
if (isStaticLabel(ctrl)) {
|
|
6841
|
+
const text = ctrl.text.trim();
|
|
6842
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
6843
|
+
label = text;
|
|
6844
|
+
}
|
|
6845
|
+
} else if (isBound(ctrl)) {
|
|
6846
|
+
value = resolveExpression(ctrl.expression, registry, void 0, "workorder");
|
|
6847
|
+
if (ctrl.formatString && /\(###\)\s*###\s*-\s*####/.test(ctrl.formatString) && value.startsWith("{{var|")) {
|
|
6848
|
+
value = value.replace("}}", "|format:phone}}");
|
|
6849
|
+
}
|
|
6850
|
+
}
|
|
6851
|
+
}
|
|
6852
|
+
if (label || value) {
|
|
6853
|
+
rightEntries.push({ label, value });
|
|
6854
|
+
}
|
|
6855
|
+
}
|
|
6856
|
+
}
|
|
6857
|
+
if (leftEntries.length > 0 || rightEntries.length > 0) {
|
|
6858
|
+
const outerSplit = computeSplitPercent(leftControls, rightControls, contentWidth);
|
|
6859
|
+
lines.push(`:::columns{split:${outerSplit}}`);
|
|
6860
|
+
lines.push(":::col");
|
|
6861
|
+
for (const entry of leftEntries) {
|
|
6862
|
+
lines.push(entry);
|
|
6863
|
+
}
|
|
6864
|
+
lines.push(":::");
|
|
6865
|
+
lines.push(":::col");
|
|
6866
|
+
lines.push("| | |");
|
|
6867
|
+
lines.push("|---|---|");
|
|
6868
|
+
for (const entry of rightEntries) {
|
|
6869
|
+
const label = entry.label || "";
|
|
6870
|
+
const value = entry.value ? ` ${entry.value}` : "";
|
|
6871
|
+
lines.push(`| ${label} |${value} |`);
|
|
6872
|
+
}
|
|
6873
|
+
lines.push(":::");
|
|
6874
|
+
lines.push(":::");
|
|
6875
|
+
lines.push("");
|
|
6876
|
+
}
|
|
6877
|
+
return lines.join("\n");
|
|
6878
|
+
}
|
|
6879
|
+
function renderOperationsSection(pageHeaderControls, detailControls, groupFooterControls, registry, contentWidth) {
|
|
6880
|
+
const lines = [];
|
|
6881
|
+
const headerTexts = [];
|
|
6882
|
+
for (const ctrl of [...pageHeaderControls].sort((a, b) => a.x - b.x)) {
|
|
6883
|
+
if (isStaticLabel(ctrl)) {
|
|
6884
|
+
const text = ctrl.text.trim();
|
|
6885
|
+
if (text) headerTexts.push(text);
|
|
6886
|
+
}
|
|
6887
|
+
}
|
|
6888
|
+
const titleText = headerTexts.join(" ");
|
|
6889
|
+
lines.push(`:::panel{title:${titleText}|headerStyle:dark|border:none}`);
|
|
6890
|
+
lines.push(":::");
|
|
6891
|
+
lines.push(":::repeat{data:operations}");
|
|
6892
|
+
const sortedDetail = [...detailControls].sort((a, b) => a.x - b.x);
|
|
6893
|
+
const detailParts = [];
|
|
6894
|
+
for (const ctrl of sortedDetail) {
|
|
6895
|
+
if (isBound(ctrl)) {
|
|
6896
|
+
const resolved = resolveExpression(ctrl.expression, registry, void 0, "operation");
|
|
6897
|
+
if (resolved) detailParts.push(resolved);
|
|
6898
|
+
}
|
|
6899
|
+
}
|
|
6900
|
+
if (detailParts.length > 0) {
|
|
6901
|
+
if (detailParts.length >= 2) {
|
|
6902
|
+
lines.push(":::columns{split:12}");
|
|
6903
|
+
lines.push(":::col");
|
|
6904
|
+
lines.push(`**${detailParts[0]}**`);
|
|
6905
|
+
lines.push(":::");
|
|
6906
|
+
lines.push(":::col");
|
|
6907
|
+
lines.push(`**${detailParts.slice(1).join(" ")}**`);
|
|
6908
|
+
lines.push(":::");
|
|
6909
|
+
lines.push(":::");
|
|
6910
|
+
} else {
|
|
6911
|
+
lines.push(`**${detailParts[0]}**`);
|
|
6912
|
+
}
|
|
6913
|
+
lines.push("");
|
|
6914
|
+
}
|
|
6915
|
+
if (groupFooterControls.length > 0) {
|
|
6916
|
+
const subtotalsMarkdown = renderGroupFooterBand(groupFooterControls, registry);
|
|
6917
|
+
if (subtotalsMarkdown) lines.push(subtotalsMarkdown);
|
|
6918
|
+
}
|
|
6919
|
+
lines.push("---");
|
|
6920
|
+
lines.push(":::");
|
|
6921
|
+
lines.push("");
|
|
6922
|
+
return lines.join("\n");
|
|
6923
|
+
}
|
|
6924
|
+
function renderGroupFooterBand(controls, registry, contentWidth) {
|
|
6925
|
+
const rows = groupByRow(controls);
|
|
6926
|
+
const entries = [];
|
|
6927
|
+
for (const row of rows) {
|
|
6928
|
+
const labels = row.controls.filter((c) => c.type === "XRLabel");
|
|
6929
|
+
if (labels.length === 0) continue;
|
|
6930
|
+
let label = "";
|
|
6931
|
+
let value = "";
|
|
6932
|
+
const sorted = [...labels].sort((a, b) => a.x - b.x);
|
|
6933
|
+
for (const ctrl of sorted) {
|
|
6934
|
+
if (isStaticLabel(ctrl)) {
|
|
6935
|
+
const text = ctrl.text.trim();
|
|
6936
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
6937
|
+
label = text;
|
|
6938
|
+
}
|
|
6939
|
+
} else if (isBound(ctrl)) {
|
|
6940
|
+
const resolved = resolveExpression(ctrl.expression, registry, label, "operation");
|
|
6941
|
+
if (resolved) {
|
|
6942
|
+
if (!resolved.startsWith("{{var|")) {
|
|
6943
|
+
if (!label) label = resolved;
|
|
6944
|
+
} else {
|
|
6945
|
+
value = resolved;
|
|
6946
|
+
}
|
|
6947
|
+
}
|
|
6948
|
+
}
|
|
6949
|
+
}
|
|
6950
|
+
if (label && value) {
|
|
6951
|
+
entries.push({ label, value });
|
|
6952
|
+
}
|
|
6953
|
+
}
|
|
6954
|
+
if (entries.length === 0) return "";
|
|
6955
|
+
const lines = [];
|
|
6956
|
+
lines.push(":::subtotals");
|
|
6957
|
+
for (const entry of entries) {
|
|
6958
|
+
lines.push(`**${entry.label}** ${entry.value}`);
|
|
6959
|
+
}
|
|
6960
|
+
lines.push(":::");
|
|
6961
|
+
lines.push("");
|
|
6962
|
+
return lines.join("\n");
|
|
6963
|
+
}
|
|
6964
|
+
function renderReportFooterBand(controls, registry) {
|
|
6965
|
+
const lines = [];
|
|
6966
|
+
const panel = controls.find((c) => c.type === "XRPanel");
|
|
6967
|
+
const thankYouLabel = controls.find(
|
|
6968
|
+
(c) => c.type === "XRLabel" && c.text.includes("Thank You")
|
|
6969
|
+
);
|
|
6970
|
+
const priceRows = [];
|
|
6971
|
+
if (panel && panel.children.length > 0) {
|
|
6972
|
+
const rows = groupByRow(panel.children, 15);
|
|
6973
|
+
for (const row of rows) {
|
|
6974
|
+
const labels = row.controls.filter((c) => c.type === "XRLabel");
|
|
6975
|
+
if (labels.length === 0) continue;
|
|
6976
|
+
let label = "";
|
|
6977
|
+
let value = "";
|
|
6978
|
+
const sorted = [...labels].sort((a, b) => a.x - b.x);
|
|
6979
|
+
for (const ctrl of sorted) {
|
|
6980
|
+
if (isStaticLabel(ctrl) && ctrl.text.includes("Estimate Price Summary")) {
|
|
6981
|
+
continue;
|
|
6982
|
+
}
|
|
6983
|
+
const isLabelPos = ctrl.x < 150;
|
|
6984
|
+
if (isStaticLabel(ctrl)) {
|
|
6985
|
+
const text = ctrl.text.trim();
|
|
6986
|
+
if (isLabelPos && text && !text.match(/^label\d+$/) && text !== "0.00") {
|
|
6987
|
+
label = text;
|
|
6988
|
+
}
|
|
6989
|
+
} else if (isBound(ctrl)) {
|
|
6990
|
+
const resolved = resolveExpression(ctrl.expression, registry, label || void 0, "workorder");
|
|
6991
|
+
if (resolved) {
|
|
6992
|
+
if (isLabelPos) {
|
|
6993
|
+
if (!label) label = resolved;
|
|
6994
|
+
} else {
|
|
6995
|
+
if (!value) value = resolved;
|
|
6996
|
+
}
|
|
6997
|
+
}
|
|
6998
|
+
}
|
|
6999
|
+
}
|
|
7000
|
+
if (label && value) {
|
|
7001
|
+
priceRows.push({ label, value });
|
|
7002
|
+
} else if (value) {
|
|
7003
|
+
priceRows.push({ label: "", value });
|
|
7004
|
+
}
|
|
7005
|
+
}
|
|
7006
|
+
}
|
|
7007
|
+
lines.push(":::columns{split:40|padX:20}");
|
|
7008
|
+
lines.push(":::col{padTop:1}");
|
|
7009
|
+
if (thankYouLabel) {
|
|
7010
|
+
lines.push(`## ***${thankYouLabel.text.trim()}***`);
|
|
7011
|
+
}
|
|
7012
|
+
lines.push(":::");
|
|
7013
|
+
lines.push(":::col{padTop:1}");
|
|
7014
|
+
lines.push(":::panel{title:Estimate Price Summary|border:solid|headerStyle:dark}");
|
|
7015
|
+
if (priceRows.length > 0) {
|
|
7016
|
+
lines.push(":::subtotals");
|
|
7017
|
+
for (const row of priceRows) {
|
|
7018
|
+
const label = row.label ? `**${row.label}**` : "";
|
|
7019
|
+
lines.push(`${label} ${row.value}`);
|
|
7020
|
+
}
|
|
7021
|
+
lines.push(":::");
|
|
7022
|
+
}
|
|
7023
|
+
lines.push(":::");
|
|
7024
|
+
lines.push(":::");
|
|
7025
|
+
lines.push(":::");
|
|
7026
|
+
lines.push("");
|
|
7027
|
+
return lines.join("\n");
|
|
7028
|
+
}
|
|
7029
|
+
function parseXmlTemplate(xmlString) {
|
|
7030
|
+
const warnings = [];
|
|
7031
|
+
const registry = new VariableRegistry();
|
|
7032
|
+
const parser = new DOMParser();
|
|
7033
|
+
const doc = parser.parseFromString(xmlString, "text/xml");
|
|
7034
|
+
const parseError = doc.querySelector("parsererror");
|
|
7035
|
+
if (parseError) {
|
|
7036
|
+
return {
|
|
7037
|
+
markdown: "",
|
|
7038
|
+
variables: [],
|
|
7039
|
+
reportName: "",
|
|
7040
|
+
warnings: ["XML parse error: " + (parseError.textContent || "Unknown error")]
|
|
7041
|
+
};
|
|
7042
|
+
}
|
|
7043
|
+
const root = doc.documentElement;
|
|
7044
|
+
const reportName = root.getAttribute("DisplayName") || root.getAttribute("Name") || "Report";
|
|
7045
|
+
const pageWidth = parseInt(root.getAttribute("PageWidth") || "850", 10);
|
|
7046
|
+
const marginsStr = root.getAttribute("Margins") || "50,50,250,50";
|
|
7047
|
+
const marginParts = marginsStr.split(",").map(Number);
|
|
7048
|
+
const leftMargin = marginParts[0] || 50;
|
|
7049
|
+
const rightMargin = marginParts[1] || 50;
|
|
7050
|
+
const contentWidth = pageWidth - leftMargin - rightMargin;
|
|
7051
|
+
const calculatedFieldNames = /* @__PURE__ */ new Set();
|
|
7052
|
+
const calcFieldsEl = root.querySelector(":scope > CalculatedFields");
|
|
7053
|
+
if (calcFieldsEl) {
|
|
7054
|
+
for (const item of Array.from(calcFieldsEl.children)) {
|
|
7055
|
+
const name = item.getAttribute("Name");
|
|
7056
|
+
if (name) calculatedFieldNames.add(name);
|
|
7057
|
+
}
|
|
7058
|
+
}
|
|
7059
|
+
const markdownSections = [];
|
|
7060
|
+
const watermarkEl = root.querySelector("Watermarks > Item1");
|
|
7061
|
+
if (watermarkEl) {
|
|
7062
|
+
const wmText = watermarkEl.getAttribute("Text");
|
|
7063
|
+
if (wmText) {
|
|
7064
|
+
markdownSections.push(`:::watermark{text:${wmText}|opacity:0.15|angle:45}`);
|
|
7065
|
+
markdownSections.push("");
|
|
7066
|
+
}
|
|
7067
|
+
}
|
|
7068
|
+
const bandsEl = root.querySelector(":scope > Bands");
|
|
7069
|
+
if (!bandsEl) {
|
|
7070
|
+
warnings.push("No <Bands> element found in the XML");
|
|
7071
|
+
return {
|
|
7072
|
+
markdown: "",
|
|
7073
|
+
variables: registry.getAll(),
|
|
7074
|
+
reportName,
|
|
7075
|
+
warnings
|
|
7076
|
+
};
|
|
7077
|
+
}
|
|
7078
|
+
let topMarginControls = [];
|
|
7079
|
+
let reportHeaderControls = [];
|
|
7080
|
+
let pageHeaderControls = [];
|
|
7081
|
+
let detailControls = [];
|
|
7082
|
+
let groupFooterControls = [];
|
|
7083
|
+
let reportFooterControls = [];
|
|
7084
|
+
for (const bandItem of Array.from(bandsEl.children)) {
|
|
7085
|
+
const ctrlType = bandItem.getAttribute("ControlType") || "";
|
|
7086
|
+
if (ctrlType === "TopMarginBand") {
|
|
7087
|
+
topMarginControls = extractControls(bandItem);
|
|
7088
|
+
} else if (ctrlType === "ReportHeaderBand") {
|
|
7089
|
+
reportHeaderControls = extractControls(bandItem);
|
|
7090
|
+
} else if (ctrlType === "PageHeaderBand") {
|
|
7091
|
+
pageHeaderControls = extractControls(bandItem);
|
|
7092
|
+
} else if (ctrlType === "DetailReportBand") {
|
|
7093
|
+
const nestedBands = bandItem.querySelector(":scope > Bands");
|
|
7094
|
+
if (nestedBands) {
|
|
7095
|
+
for (const nested of Array.from(nestedBands.children)) {
|
|
7096
|
+
const nestedType = nested.getAttribute("ControlType") || "";
|
|
7097
|
+
if (nestedType === "DetailBand") {
|
|
7098
|
+
detailControls = extractControls(nested);
|
|
7099
|
+
} else if (nestedType === "GroupFooterBand") {
|
|
7100
|
+
groupFooterControls = extractControls(nested);
|
|
7101
|
+
}
|
|
7102
|
+
}
|
|
7103
|
+
}
|
|
7104
|
+
} else if (ctrlType === "ReportFooterBand") {
|
|
7105
|
+
reportFooterControls = extractControls(bandItem);
|
|
7106
|
+
}
|
|
7107
|
+
}
|
|
7108
|
+
if (topMarginControls.length > 0) {
|
|
7109
|
+
markdownSections.push(renderTopMarginBand(topMarginControls, registry, contentWidth, calculatedFieldNames));
|
|
7110
|
+
}
|
|
7111
|
+
if (reportHeaderControls.length > 0) {
|
|
7112
|
+
markdownSections.push(
|
|
7113
|
+
renderReportHeaderBand(reportHeaderControls, registry, contentWidth)
|
|
7114
|
+
);
|
|
7115
|
+
}
|
|
7116
|
+
if (pageHeaderControls.length > 0 || detailControls.length > 0) {
|
|
7117
|
+
markdownSections.push(
|
|
7118
|
+
renderOperationsSection(
|
|
7119
|
+
pageHeaderControls,
|
|
7120
|
+
detailControls,
|
|
7121
|
+
groupFooterControls,
|
|
7122
|
+
registry)
|
|
7123
|
+
);
|
|
7124
|
+
}
|
|
7125
|
+
if (reportFooterControls.length > 0) {
|
|
7126
|
+
markdownSections.push(
|
|
7127
|
+
renderReportFooterBand(reportFooterControls, registry)
|
|
7128
|
+
);
|
|
7129
|
+
}
|
|
7130
|
+
const markdown = markdownSections.join("\n").replace(/\n{4,}/g, "\n\n\n");
|
|
7131
|
+
return {
|
|
7132
|
+
markdown,
|
|
7133
|
+
variables: registry.getAll(),
|
|
7134
|
+
reportName,
|
|
7135
|
+
warnings
|
|
7136
|
+
};
|
|
7137
|
+
}
|
|
7138
|
+
|
|
4958
7139
|
exports.DocumentGenerator = DocumentGenerator;
|
|
4959
7140
|
exports.EditorPanel = EditorPanel;
|
|
4960
7141
|
exports.FormFieldType = FormFieldType;
|
|
4961
7142
|
exports.PreviewPanel = PreviewPanel;
|
|
7143
|
+
exports.RepeatNode = RepeatNode;
|
|
7144
|
+
exports.SubtotalsNode = SubtotalsNode;
|
|
4962
7145
|
exports.ThemeProvider = ThemeProvider;
|
|
7146
|
+
exports.expandRepeatContent = expandRepeatContent;
|
|
7147
|
+
exports.extractVariablesFromContent = extractVariablesFromContent;
|
|
7148
|
+
exports.generatePdfFromMarkdown = generatePdfFromMarkdown;
|
|
7149
|
+
exports.generatePdfFromTiptap = generatePdfFromTiptap;
|
|
7150
|
+
exports.isZeroLike = isZeroLike;
|
|
7151
|
+
exports.markdownToTiptap = markdownToTiptap;
|
|
7152
|
+
exports.parseXmlTemplate = parseXmlTemplate;
|
|
7153
|
+
exports.suppressZeroContent = suppressZeroContent;
|
|
7154
|
+
exports.tiptapToMarkdown = tiptapToMarkdown;
|
|
4963
7155
|
exports.useDocumentGenerator = useDocumentGenerator;
|
|
4964
7156
|
exports.useTheme = useTheme;
|
|
4965
7157
|
//# sourceMappingURL=index.js.map
|