@signiphi/pdf-compose 0.1.0-beta.1 → 0.1.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +265 -0
- package/dist/components/DocumentGenerator.d.ts +3 -0
- package/dist/components/DocumentGenerator.d.ts.map +1 -0
- package/dist/components/EditorPanel.d.ts +15 -0
- package/dist/components/EditorPanel.d.ts.map +1 -0
- package/dist/components/EditorToolbar.d.ts +11 -0
- package/dist/components/EditorToolbar.d.ts.map +1 -0
- package/dist/components/ErrorBoundary.d.ts +18 -0
- package/dist/components/ErrorBoundary.d.ts.map +1 -0
- package/dist/components/FieldEditPopover.d.ts +12 -0
- package/dist/components/FieldEditPopover.d.ts.map +1 -0
- package/dist/components/FieldInsertPopover.d.ts +12 -0
- package/dist/components/FieldInsertPopover.d.ts.map +1 -0
- package/dist/components/FieldNodeView.d.ts +3 -0
- package/dist/components/FieldNodeView.d.ts.map +1 -0
- package/dist/components/FieldOverlay.d.ts +9 -0
- package/dist/components/FieldOverlay.d.ts.map +1 -0
- package/dist/components/GeneratePanel.d.ts +15 -0
- package/dist/components/GeneratePanel.d.ts.map +1 -0
- package/dist/components/ImageNodeView.d.ts +11 -0
- package/dist/components/ImageNodeView.d.ts.map +1 -0
- package/dist/components/PreviewPanel.d.ts +12 -0
- package/dist/components/PreviewPanel.d.ts.map +1 -0
- package/dist/components/VariableEditPopover.d.ts +12 -0
- package/dist/components/VariableEditPopover.d.ts.map +1 -0
- package/dist/components/VariableInsertPopover.d.ts +13 -0
- package/dist/components/VariableInsertPopover.d.ts.map +1 -0
- package/dist/components/VariableNodeView.d.ts +3 -0
- package/dist/components/VariableNodeView.d.ts.map +1 -0
- package/dist/components/WatermarkNodeView.d.ts +3 -0
- package/dist/components/WatermarkNodeView.d.ts.map +1 -0
- package/dist/contexts/ThemeContext.d.ts +19 -0
- package/dist/contexts/ThemeContext.d.ts.map +1 -0
- package/dist/extensions/columns-node.d.ts +4 -0
- package/dist/extensions/columns-node.d.ts.map +1 -0
- package/dist/extensions/field-node.d.ts +31 -0
- package/dist/extensions/field-node.d.ts.map +1 -0
- package/dist/extensions/image-node.d.ts +10 -0
- package/dist/extensions/image-node.d.ts.map +1 -0
- package/dist/extensions/panel-node.d.ts +3 -0
- package/dist/extensions/panel-node.d.ts.map +1 -0
- package/dist/extensions/repeat-node.d.ts +3 -0
- package/dist/extensions/repeat-node.d.ts.map +1 -0
- package/dist/extensions/subtotals-node.d.ts +3 -0
- package/dist/extensions/subtotals-node.d.ts.map +1 -0
- package/dist/extensions/variable-node.d.ts +18 -0
- package/dist/extensions/variable-node.d.ts.map +1 -0
- package/dist/extensions/watermark-node.d.ts +3 -0
- package/dist/extensions/watermark-node.d.ts.map +1 -0
- package/dist/hooks/useDocumentGenerator.d.ts +46 -0
- package/dist/hooks/useDocumentGenerator.d.ts.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3108 -509
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3094 -512
- package/dist/index.mjs.map +1 -1
- package/dist/lib/ui/button.d.ts +12 -0
- package/dist/lib/ui/button.d.ts.map +1 -0
- package/dist/lib/ui/index.d.ts +10 -0
- package/dist/lib/ui/index.d.ts.map +1 -0
- package/dist/lib/ui/input.d.ts +6 -0
- package/dist/lib/ui/input.d.ts.map +1 -0
- package/dist/lib/ui/label.d.ts +8 -0
- package/dist/lib/ui/label.d.ts.map +1 -0
- package/dist/lib/ui/popover.d.ts +8 -0
- package/dist/lib/ui/popover.d.ts.map +1 -0
- package/dist/lib/ui/select.d.ts +14 -0
- package/dist/lib/ui/select.d.ts.map +1 -0
- package/dist/lib/ui/toggle-group.d.ts +13 -0
- package/dist/lib/ui/toggle-group.d.ts.map +1 -0
- package/dist/lib/ui/tooltip.d.ts +8 -0
- package/dist/lib/ui/tooltip.d.ts.map +1 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/styles/index.css +374 -194
- package/dist/styles/index.d.ts +1 -0
- package/dist/types/index.d.ts +204 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/utils/error-helpers.d.ts +27 -0
- package/dist/utils/error-helpers.d.ts.map +1 -0
- package/dist/utils/field-helpers.d.ts +31 -0
- package/dist/utils/field-helpers.d.ts.map +1 -0
- package/dist/utils/markdown-parser.d.ts +6 -0
- package/dist/utils/markdown-parser.d.ts.map +1 -0
- package/dist/utils/markdown-validator.d.ts +13 -0
- package/dist/utils/markdown-validator.d.ts.map +1 -0
- package/dist/utils/markdown-writer.d.ts +6 -0
- package/dist/utils/markdown-writer.d.ts.map +1 -0
- package/dist/utils/pdf-generator.d.ts +77 -0
- package/dist/utils/pdf-generator.d.ts.map +1 -0
- package/dist/utils/pdf-metadata.d.ts +58 -0
- package/dist/utils/pdf-metadata.d.ts.map +1 -0
- package/dist/utils/pdf-preview.d.ts +7 -0
- package/dist/utils/pdf-preview.d.ts.map +1 -0
- package/dist/utils/template-pipeline.d.ts +39 -0
- package/dist/utils/template-pipeline.d.ts.map +1 -0
- package/dist/utils/theme-helpers.d.ts +21 -0
- package/dist/utils/theme-helpers.d.ts.map +1 -0
- package/dist/utils/variable-helpers.d.ts +74 -0
- package/dist/utils/variable-helpers.d.ts.map +1 -0
- package/dist/utils/xml-template-parser.d.ts +9 -0
- package/dist/utils/xml-template-parser.d.ts.map +1 -0
- package/package.json +16 -6
- package/src/styles/index.css +140 -68
- package/src/styles/index.d.ts +1 -0
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,10 @@ 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: "" },
|
|
257
|
+
block: { default: "" }
|
|
247
258
|
};
|
|
248
259
|
},
|
|
249
260
|
parseHTML() {
|
|
@@ -309,6 +320,235 @@ var VariableNode = core.Node.create({
|
|
|
309
320
|
};
|
|
310
321
|
}
|
|
311
322
|
});
|
|
323
|
+
var PanelNode = core.Node.create({
|
|
324
|
+
name: "panel",
|
|
325
|
+
group: "block",
|
|
326
|
+
content: "block+",
|
|
327
|
+
defining: true,
|
|
328
|
+
addAttributes() {
|
|
329
|
+
return {
|
|
330
|
+
title: { default: "" },
|
|
331
|
+
border: { default: "solid" },
|
|
332
|
+
headerStyle: { default: "" }
|
|
333
|
+
};
|
|
334
|
+
},
|
|
335
|
+
parseHTML() {
|
|
336
|
+
return [{
|
|
337
|
+
tag: "div[data-panel-node]",
|
|
338
|
+
contentElement: "div.panel-content"
|
|
339
|
+
}];
|
|
340
|
+
},
|
|
341
|
+
renderHTML({ node, HTMLAttributes }) {
|
|
342
|
+
const { title, border, headerStyle } = node.attrs;
|
|
343
|
+
const classes = ["panel-node"];
|
|
344
|
+
if (border === "dashed") classes.push("panel-border-dashed");
|
|
345
|
+
else if (border === "none") classes.push("panel-border-none");
|
|
346
|
+
else classes.push("panel-border-solid");
|
|
347
|
+
if (headerStyle === "dark") classes.push("panel-dark");
|
|
348
|
+
const attrs = core.mergeAttributes(
|
|
349
|
+
{ "data-panel-node": "", class: classes.join(" ") },
|
|
350
|
+
HTMLAttributes
|
|
351
|
+
);
|
|
352
|
+
if (title) {
|
|
353
|
+
return [
|
|
354
|
+
"div",
|
|
355
|
+
attrs,
|
|
356
|
+
["div", { class: "panel-title" }, title],
|
|
357
|
+
["div", { class: "panel-content" }, 0]
|
|
358
|
+
];
|
|
359
|
+
}
|
|
360
|
+
return ["div", attrs, ["div", { class: "panel-content" }, 0]];
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
var ColumnsNode = core.Node.create({
|
|
364
|
+
name: "columns",
|
|
365
|
+
group: "block",
|
|
366
|
+
content: "column+",
|
|
367
|
+
defining: true,
|
|
368
|
+
addAttributes() {
|
|
369
|
+
return {
|
|
370
|
+
split: { default: "50" },
|
|
371
|
+
padX: { default: "0" }
|
|
372
|
+
};
|
|
373
|
+
},
|
|
374
|
+
parseHTML() {
|
|
375
|
+
return [{ tag: "div[data-columns-node]" }];
|
|
376
|
+
},
|
|
377
|
+
renderHTML({ node, HTMLAttributes }) {
|
|
378
|
+
const split = node.attrs.split || "50";
|
|
379
|
+
return ["div", core.mergeAttributes(
|
|
380
|
+
{
|
|
381
|
+
"data-columns-node": "",
|
|
382
|
+
class: "columns-node",
|
|
383
|
+
style: `--xpc-col-split: ${split}`
|
|
384
|
+
},
|
|
385
|
+
HTMLAttributes
|
|
386
|
+
), 0];
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
var ColumnNode = core.Node.create({
|
|
390
|
+
name: "column",
|
|
391
|
+
group: "",
|
|
392
|
+
content: "block+",
|
|
393
|
+
defining: true,
|
|
394
|
+
addAttributes() {
|
|
395
|
+
return {
|
|
396
|
+
padTop: { default: 0 }
|
|
397
|
+
};
|
|
398
|
+
},
|
|
399
|
+
parseHTML() {
|
|
400
|
+
return [{ tag: "div[data-column-node]" }];
|
|
401
|
+
},
|
|
402
|
+
renderHTML({ HTMLAttributes }) {
|
|
403
|
+
return ["div", core.mergeAttributes(
|
|
404
|
+
{ "data-column-node": "", class: "column-node" },
|
|
405
|
+
HTMLAttributes
|
|
406
|
+
), 0];
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
function WatermarkNodeView({ node, selected }) {
|
|
410
|
+
const { text } = node.attrs;
|
|
411
|
+
return /* @__PURE__ */ jsxRuntime.jsx(react.NodeViewWrapper, { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
412
|
+
"div",
|
|
413
|
+
{
|
|
414
|
+
"data-watermark-node": "",
|
|
415
|
+
className: cn(
|
|
416
|
+
"inline-flex items-center gap-2 px-3 py-1.5 rounded-md border text-sm my-1",
|
|
417
|
+
"bg-blue-50 text-blue-600 border-blue-200",
|
|
418
|
+
selected && "ring-2 ring-primary ring-offset-1"
|
|
419
|
+
),
|
|
420
|
+
children: [
|
|
421
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Droplets, { size: 14 }),
|
|
422
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
423
|
+
"Watermark: ",
|
|
424
|
+
text || "(empty)"
|
|
425
|
+
] })
|
|
426
|
+
]
|
|
427
|
+
}
|
|
428
|
+
) });
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/extensions/watermark-node.ts
|
|
432
|
+
var WatermarkNode = core.Node.create({
|
|
433
|
+
name: "watermark",
|
|
434
|
+
group: "block",
|
|
435
|
+
atom: true,
|
|
436
|
+
selectable: true,
|
|
437
|
+
draggable: false,
|
|
438
|
+
addAttributes() {
|
|
439
|
+
return {
|
|
440
|
+
text: { default: "" },
|
|
441
|
+
opacity: { default: "0.15" },
|
|
442
|
+
angle: { default: "-45" }
|
|
443
|
+
};
|
|
444
|
+
},
|
|
445
|
+
parseHTML() {
|
|
446
|
+
return [{ tag: "div[data-watermark-node]" }];
|
|
447
|
+
},
|
|
448
|
+
renderHTML({ HTMLAttributes }) {
|
|
449
|
+
return ["div", core.mergeAttributes({ "data-watermark-node": "" }, HTMLAttributes)];
|
|
450
|
+
},
|
|
451
|
+
addNodeView() {
|
|
452
|
+
return react.ReactNodeViewRenderer(WatermarkNodeView);
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
function ImageNodeView({ node, selected }) {
|
|
456
|
+
const { src, varName, alt, width, height, align } = node.attrs;
|
|
457
|
+
const hasRenderableSrc = !!src && /^(https?:|data:)/i.test(src);
|
|
458
|
+
const wrapperJustify = align === "center" ? "justify-center" : align === "right" ? "justify-end" : "justify-start";
|
|
459
|
+
const toCssSize = (v) => /^\d+(\.\d+)?$/.test(v) ? `${v}px` : v;
|
|
460
|
+
const sizeStyle = {};
|
|
461
|
+
if (width) sizeStyle.width = toCssSize(width);
|
|
462
|
+
if (height) sizeStyle.height = toCssSize(height);
|
|
463
|
+
return /* @__PURE__ */ jsxRuntime.jsx(react.NodeViewWrapper, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex my-1", wrapperJustify), "data-image-node": "", children: hasRenderableSrc ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
464
|
+
"img",
|
|
465
|
+
{
|
|
466
|
+
src,
|
|
467
|
+
alt: alt || varName || "",
|
|
468
|
+
style: sizeStyle,
|
|
469
|
+
className: cn(
|
|
470
|
+
"max-w-full rounded-sm object-contain",
|
|
471
|
+
selected && "ring-2 ring-primary ring-offset-1"
|
|
472
|
+
),
|
|
473
|
+
draggable: false
|
|
474
|
+
}
|
|
475
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
476
|
+
"div",
|
|
477
|
+
{
|
|
478
|
+
"data-image-placeholder": "",
|
|
479
|
+
className: cn(
|
|
480
|
+
"inline-flex items-center gap-2 px-3 py-2 rounded-md border border-dashed text-sm",
|
|
481
|
+
"bg-slate-50 text-slate-500 border-slate-300",
|
|
482
|
+
selected && "ring-2 ring-primary ring-offset-1"
|
|
483
|
+
),
|
|
484
|
+
style: height ? { minHeight: toCssSize(height) } : void 0,
|
|
485
|
+
title: varName ? `Image variable: ${varName}` : "Image",
|
|
486
|
+
children: [
|
|
487
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Image, { size: 16 }),
|
|
488
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: varName ? `Image: ${varName}` : alt || "Image" })
|
|
489
|
+
]
|
|
490
|
+
}
|
|
491
|
+
) }) });
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// src/extensions/image-node.ts
|
|
495
|
+
var ImageNode = core.Node.create({
|
|
496
|
+
name: "imageNode",
|
|
497
|
+
group: "block",
|
|
498
|
+
atom: true,
|
|
499
|
+
selectable: true,
|
|
500
|
+
draggable: false,
|
|
501
|
+
addAttributes() {
|
|
502
|
+
return {
|
|
503
|
+
src: { default: "" },
|
|
504
|
+
varName: { default: "" },
|
|
505
|
+
alt: { default: "" },
|
|
506
|
+
width: { default: "" },
|
|
507
|
+
height: { default: "" },
|
|
508
|
+
align: { default: "" }
|
|
509
|
+
};
|
|
510
|
+
},
|
|
511
|
+
parseHTML() {
|
|
512
|
+
return [{ tag: "div[data-image-node]" }];
|
|
513
|
+
},
|
|
514
|
+
renderHTML({ HTMLAttributes }) {
|
|
515
|
+
return ["div", core.mergeAttributes({ "data-image-node": "" }, HTMLAttributes)];
|
|
516
|
+
},
|
|
517
|
+
addNodeView() {
|
|
518
|
+
return react.ReactNodeViewRenderer(ImageNodeView);
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
var RepeatNode = core.Node.create({
|
|
522
|
+
name: "repeatBlock",
|
|
523
|
+
group: "block",
|
|
524
|
+
content: "block+",
|
|
525
|
+
addAttributes() {
|
|
526
|
+
return {
|
|
527
|
+
data: { default: "" }
|
|
528
|
+
};
|
|
529
|
+
},
|
|
530
|
+
parseHTML() {
|
|
531
|
+
return [{ tag: "div[data-repeat-node]" }];
|
|
532
|
+
},
|
|
533
|
+
renderHTML({ HTMLAttributes }) {
|
|
534
|
+
return ["div", core.mergeAttributes({ "data-repeat-node": "" }, HTMLAttributes), 0];
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
var SubtotalsNode = core.Node.create({
|
|
538
|
+
name: "subtotalsBlock",
|
|
539
|
+
group: "block",
|
|
540
|
+
content: "block+",
|
|
541
|
+
defining: true,
|
|
542
|
+
parseHTML() {
|
|
543
|
+
return [{ tag: "div[data-subtotals-node]" }];
|
|
544
|
+
},
|
|
545
|
+
renderHTML({ HTMLAttributes }) {
|
|
546
|
+
return ["div", core.mergeAttributes(
|
|
547
|
+
{ "data-subtotals-node": "", class: "subtotals-block" },
|
|
548
|
+
HTMLAttributes
|
|
549
|
+
), 0];
|
|
550
|
+
}
|
|
551
|
+
});
|
|
312
552
|
|
|
313
553
|
// src/types/index.ts
|
|
314
554
|
var FormFieldType = /* @__PURE__ */ ((FormFieldType2) => {
|
|
@@ -387,158 +627,23 @@ function extractFieldsFromContent(content) {
|
|
|
387
627
|
}
|
|
388
628
|
return fields;
|
|
389
629
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
}
|
|
405
|
-
if (node.content) {
|
|
406
|
-
node.content.forEach(walk);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
if (content.content) {
|
|
410
|
-
content.content.forEach(walk);
|
|
411
|
-
}
|
|
412
|
-
return Array.from(seen.values());
|
|
413
|
-
}
|
|
414
|
-
function replaceVariablesInContent(content, values) {
|
|
415
|
-
function walkNode(node) {
|
|
416
|
-
if (node.type === "variableNode" && node.attrs) {
|
|
417
|
-
const varName = node.attrs.varName;
|
|
418
|
-
const replacement = values[varName] || node.attrs.varDefault || `[${node.attrs.varLabel || varName}]`;
|
|
419
|
-
const textNode = { type: "text", text: replacement };
|
|
420
|
-
if (node.marks && node.marks.length > 0) {
|
|
421
|
-
textNode.marks = node.marks;
|
|
422
|
-
}
|
|
423
|
-
return textNode;
|
|
424
|
-
}
|
|
425
|
-
if (node.content) {
|
|
426
|
-
return { ...node, content: node.content.map(walkNode) };
|
|
427
|
-
}
|
|
428
|
-
return node;
|
|
429
|
-
}
|
|
430
|
-
return walkNode(content);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// src/utils/markdown-writer.ts
|
|
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("|")}}}`;
|
|
449
|
-
}
|
|
450
|
-
function serializeVariableToken(attrs) {
|
|
451
|
-
const parts = ["var"];
|
|
452
|
-
if (attrs.varName) parts.push(`name:${attrs.varName}`);
|
|
453
|
-
if (attrs.varLabel) parts.push(`label:${attrs.varLabel}`);
|
|
454
|
-
if (attrs.varDefault) parts.push(`default:${attrs.varDefault}`);
|
|
455
|
-
return `{{${parts.join("|")}}}`;
|
|
456
|
-
}
|
|
457
|
-
function serializeInline(content) {
|
|
458
|
-
if (!content) return "";
|
|
459
|
-
let result = "";
|
|
460
|
-
for (const node of content) {
|
|
461
|
-
if (node.type === "fieldNode") {
|
|
462
|
-
result += serializeFieldToken(node.attrs || {});
|
|
463
|
-
continue;
|
|
464
|
-
}
|
|
465
|
-
if (node.type === "variableNode") {
|
|
466
|
-
let token = serializeVariableToken(node.attrs || {});
|
|
467
|
-
const varMarks = node.marks || [];
|
|
468
|
-
const isBold = varMarks.some((m) => m.type === "bold");
|
|
469
|
-
const isItalic = varMarks.some((m) => m.type === "italic");
|
|
470
|
-
const isUnderline = varMarks.some((m) => m.type === "underline");
|
|
471
|
-
if (isBold && isItalic) token = `***${token}***`;
|
|
472
|
-
else if (isBold) token = `**${token}**`;
|
|
473
|
-
else if (isItalic) token = `*${token}*`;
|
|
474
|
-
if (isUnderline) token = `__${token}__`;
|
|
475
|
-
result += token;
|
|
476
|
-
continue;
|
|
477
|
-
}
|
|
478
|
-
if (node.type === "text") {
|
|
479
|
-
let text = node.text || "";
|
|
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;
|
|
488
|
-
}
|
|
489
|
-
if (node.type === "hardBreak") {
|
|
490
|
-
result += " \n";
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
return result;
|
|
494
|
-
}
|
|
495
|
-
function serializeBlock(node) {
|
|
496
|
-
switch (node.type) {
|
|
497
|
-
case "heading": {
|
|
498
|
-
const level = node.attrs?.level || 1;
|
|
499
|
-
const prefix = "#".repeat(level);
|
|
500
|
-
return `${prefix} ${serializeInline(node.content)}`;
|
|
501
|
-
}
|
|
502
|
-
case "paragraph": {
|
|
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");
|
|
520
|
-
}
|
|
521
|
-
case "blockquote": {
|
|
522
|
-
return (node.content || []).map(serializeBlock).map((line) => `> ${line}`).join("\n");
|
|
523
|
-
}
|
|
524
|
-
case "codeBlock": {
|
|
525
|
-
const text = serializeInline(node.content);
|
|
526
|
-
return `\`\`\`
|
|
527
|
-
${text}
|
|
528
|
-
\`\`\``;
|
|
529
|
-
}
|
|
530
|
-
case "horizontalRule": {
|
|
531
|
-
return "---";
|
|
532
|
-
}
|
|
533
|
-
default:
|
|
534
|
-
return serializeInline(node.content);
|
|
630
|
+
var TOKEN_REGEX = /\{\{(field|var|image)\|([^}]+)\}\}/g;
|
|
631
|
+
var BLOCK_IMAGE_REGEX = /^!\[([^\]]*)\]\(([^)\s]+)\)$/;
|
|
632
|
+
var DIRECTIVE_OPEN = /^:::(panel|columns|col|watermark|repeat|subtotals)(?:\{([^}]*)\})?$/;
|
|
633
|
+
var DIRECTIVE_CLOSE = /^:::$/;
|
|
634
|
+
function parseDirectiveAttrs(str) {
|
|
635
|
+
const attrs = {};
|
|
636
|
+
if (!str) return attrs;
|
|
637
|
+
const pairs = str.split("|");
|
|
638
|
+
for (const pair of pairs) {
|
|
639
|
+
const colonIdx = pair.indexOf(":");
|
|
640
|
+
if (colonIdx === -1) continue;
|
|
641
|
+
const key = pair.slice(0, colonIdx).trim();
|
|
642
|
+
const value = pair.slice(colonIdx + 1).trim();
|
|
643
|
+
if (key) attrs[key] = value;
|
|
535
644
|
}
|
|
645
|
+
return attrs;
|
|
536
646
|
}
|
|
537
|
-
function tiptapToMarkdown(doc) {
|
|
538
|
-
if (!doc.content) return "";
|
|
539
|
-
return doc.content.map(serializeBlock).join("\n\n");
|
|
540
|
-
}
|
|
541
|
-
var TOKEN_REGEX = /\{\{(field|var)\|([^}]+)\}\}/g;
|
|
542
647
|
function injectMarks(token, marks) {
|
|
543
648
|
const existingMatch = token.match(/\|_marks:([^}|]+)/);
|
|
544
649
|
if (existingMatch) {
|
|
@@ -655,6 +760,15 @@ function parseVariableToken(tokenBody) {
|
|
|
655
760
|
case "default":
|
|
656
761
|
attrs.varDefault = value;
|
|
657
762
|
break;
|
|
763
|
+
case "suppress":
|
|
764
|
+
attrs.suppressZero = value === "zero" ? "true" : "";
|
|
765
|
+
break;
|
|
766
|
+
case "format":
|
|
767
|
+
attrs.format = value;
|
|
768
|
+
break;
|
|
769
|
+
case "block":
|
|
770
|
+
attrs.block = value === "true" ? "true" : "";
|
|
771
|
+
break;
|
|
658
772
|
case "_marks":
|
|
659
773
|
attrs._marks = value;
|
|
660
774
|
break;
|
|
@@ -662,6 +776,44 @@ function parseVariableToken(tokenBody) {
|
|
|
662
776
|
}
|
|
663
777
|
return attrs;
|
|
664
778
|
}
|
|
779
|
+
function parseImageToken(tokenBody) {
|
|
780
|
+
const attrs = {
|
|
781
|
+
src: "",
|
|
782
|
+
varName: "",
|
|
783
|
+
alt: "",
|
|
784
|
+
width: "",
|
|
785
|
+
height: "",
|
|
786
|
+
align: ""
|
|
787
|
+
};
|
|
788
|
+
const pairs = tokenBody.split("|");
|
|
789
|
+
for (const pair of pairs) {
|
|
790
|
+
const colonIdx = pair.indexOf(":");
|
|
791
|
+
if (colonIdx === -1) continue;
|
|
792
|
+
const key = pair.slice(0, colonIdx).trim();
|
|
793
|
+
const value = pair.slice(colonIdx + 1).trim();
|
|
794
|
+
switch (key) {
|
|
795
|
+
case "src":
|
|
796
|
+
attrs.src = value;
|
|
797
|
+
break;
|
|
798
|
+
case "var":
|
|
799
|
+
attrs.varName = value;
|
|
800
|
+
break;
|
|
801
|
+
case "alt":
|
|
802
|
+
attrs.alt = value;
|
|
803
|
+
break;
|
|
804
|
+
case "width":
|
|
805
|
+
attrs.width = value;
|
|
806
|
+
break;
|
|
807
|
+
case "height":
|
|
808
|
+
attrs.height = value;
|
|
809
|
+
break;
|
|
810
|
+
case "align":
|
|
811
|
+
attrs.align = value;
|
|
812
|
+
break;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
return attrs;
|
|
816
|
+
}
|
|
665
817
|
function parseInline(text) {
|
|
666
818
|
text = preprocessMarkedTokens(text);
|
|
667
819
|
const nodes = [];
|
|
@@ -685,6 +837,8 @@ function parseInline(text) {
|
|
|
685
837
|
const node = { type: "variableNode", attrs };
|
|
686
838
|
if (marks.length > 0) node.marks = marks;
|
|
687
839
|
nodes.push(node);
|
|
840
|
+
} else if (match[1] === "image") {
|
|
841
|
+
nodes.push({ type: "imageNode", attrs: parseImageToken(match[2]) });
|
|
688
842
|
}
|
|
689
843
|
lastIndex = match.index + match[0].length;
|
|
690
844
|
}
|
|
@@ -696,45 +850,63 @@ function parseInline(text) {
|
|
|
696
850
|
function parseFormattedText(text) {
|
|
697
851
|
if (!text) return [];
|
|
698
852
|
const nodes = [];
|
|
699
|
-
const parts = text.split(/(
|
|
853
|
+
const parts = text.split(/(\*\*\*[^*]+\*\*\*|\*\*[^*]+\*\*|\*[^*]+\*|__[^_]+__|`[^`]+`)/);
|
|
700
854
|
for (const part of parts) {
|
|
701
855
|
if (!part) continue;
|
|
702
|
-
if (part.startsWith("
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
});
|
|
856
|
+
if (part.startsWith("***") && part.endsWith("***")) {
|
|
857
|
+
const inner = part.slice(3, -3);
|
|
858
|
+
if (inner) nodes.push({ type: "text", text: inner, marks: [{ type: "bold" }, { type: "italic" }] });
|
|
859
|
+
} else if (part.startsWith("**") && part.endsWith("**")) {
|
|
860
|
+
const inner = part.slice(2, -2);
|
|
861
|
+
if (inner) nodes.push({ type: "text", text: inner, marks: [{ type: "bold" }] });
|
|
708
862
|
} else if (part.startsWith("*") && part.endsWith("*")) {
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
text: part.slice(1, -1),
|
|
712
|
-
marks: [{ type: "italic" }]
|
|
713
|
-
});
|
|
863
|
+
const inner = part.slice(1, -1);
|
|
864
|
+
if (inner) nodes.push({ type: "text", text: inner, marks: [{ type: "italic" }] });
|
|
714
865
|
} else if (part.startsWith("__") && part.endsWith("__")) {
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
text: part.slice(2, -2),
|
|
718
|
-
marks: [{ type: "underline" }]
|
|
719
|
-
});
|
|
866
|
+
const inner = part.slice(2, -2);
|
|
867
|
+
if (inner) nodes.push({ type: "text", text: inner, marks: [{ type: "underline" }] });
|
|
720
868
|
} else if (part.startsWith("`") && part.endsWith("`")) {
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
text: part.slice(1, -1),
|
|
724
|
-
marks: [{ type: "code" }]
|
|
725
|
-
});
|
|
869
|
+
const inner = part.slice(1, -1);
|
|
870
|
+
if (inner) nodes.push({ type: "text", text: inner, marks: [{ type: "code" }] });
|
|
726
871
|
} else {
|
|
727
872
|
nodes.push({ type: "text", text: part });
|
|
728
873
|
}
|
|
729
874
|
}
|
|
730
875
|
return nodes;
|
|
731
876
|
}
|
|
732
|
-
function
|
|
733
|
-
const
|
|
877
|
+
function splitTableCells(row) {
|
|
878
|
+
const cells = [];
|
|
879
|
+
let current = "";
|
|
880
|
+
let depth = 0;
|
|
881
|
+
for (let i = 0; i < row.length; i++) {
|
|
882
|
+
if (row[i] === "{" && row[i + 1] === "{") {
|
|
883
|
+
depth++;
|
|
884
|
+
current += "{{";
|
|
885
|
+
i++;
|
|
886
|
+
} else if (row[i] === "}" && row[i + 1] === "}") {
|
|
887
|
+
depth = Math.max(0, depth - 1);
|
|
888
|
+
current += "}}";
|
|
889
|
+
i++;
|
|
890
|
+
} else if (row[i] === "|" && depth === 0) {
|
|
891
|
+
cells.push(current.trim());
|
|
892
|
+
current = "";
|
|
893
|
+
} else {
|
|
894
|
+
current += row[i];
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
if (current.trim()) {
|
|
898
|
+
cells.push(current.trim());
|
|
899
|
+
}
|
|
900
|
+
return cells;
|
|
901
|
+
}
|
|
902
|
+
function parseBlocks(lines, startIdx, stopCondition) {
|
|
734
903
|
const content = [];
|
|
735
|
-
let i =
|
|
904
|
+
let i = startIdx;
|
|
736
905
|
while (i < lines.length) {
|
|
737
906
|
const line = lines[i];
|
|
907
|
+
if (stopCondition && stopCondition(line)) {
|
|
908
|
+
break;
|
|
909
|
+
}
|
|
738
910
|
if (line.trim() === "") {
|
|
739
911
|
i++;
|
|
740
912
|
continue;
|
|
@@ -808,13 +980,443 @@ function markdownToTiptap(markdown) {
|
|
|
808
980
|
});
|
|
809
981
|
continue;
|
|
810
982
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
983
|
+
if (line.trimStart().startsWith("|") && line.trimEnd().endsWith("|")) {
|
|
984
|
+
const tableLines = [];
|
|
985
|
+
while (i < lines.length) {
|
|
986
|
+
const tl = lines[i];
|
|
987
|
+
if (tl.trimStart().startsWith("|") && tl.trimEnd().endsWith("|")) {
|
|
988
|
+
tableLines.push(tl);
|
|
989
|
+
i++;
|
|
990
|
+
} else {
|
|
991
|
+
break;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
const separatorRegex = /^[\s|]*-+[\s|:-]*$/;
|
|
995
|
+
const dataLines = tableLines.filter(
|
|
996
|
+
(tl) => !separatorRegex.test(tl.replace(/^\s*\|/, "").replace(/\|\s*$/, ""))
|
|
997
|
+
);
|
|
998
|
+
const firstDataCellsRaw = dataLines.length > 0 ? splitTableCells(dataLines[0]) : [];
|
|
999
|
+
const isSubtotals = firstDataCellsRaw.length >= 2 && firstDataCellsRaw.every((c) => c.trim() === "_");
|
|
1000
|
+
const isBorderless = isSubtotals || firstDataCellsRaw.length >= 2 && firstDataCellsRaw.every((c) => c.trim() === "");
|
|
1001
|
+
const tableRows = [];
|
|
1002
|
+
let isFirstDataRow = true;
|
|
1003
|
+
for (const tl of tableLines) {
|
|
1004
|
+
if (separatorRegex.test(tl.replace(/^\s*\|/, "").replace(/\|\s*$/, ""))) continue;
|
|
1005
|
+
const cells = splitTableCells(tl).filter((c) => c !== "");
|
|
1006
|
+
if (isBorderless && isFirstDataRow) {
|
|
1007
|
+
isFirstDataRow = false;
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
const cellType = isFirstDataRow && !isBorderless ? "tableHeader" : "tableCell";
|
|
1011
|
+
const rowContent = cells.map((cellText) => ({
|
|
1012
|
+
type: cellType,
|
|
1013
|
+
content: [{ type: "paragraph", content: cellText ? parseInline(cellText) : [] }]
|
|
1014
|
+
}));
|
|
1015
|
+
tableRows.push({ type: "tableRow", content: rowContent });
|
|
1016
|
+
isFirstDataRow = false;
|
|
1017
|
+
}
|
|
1018
|
+
if (tableRows.length > 0) {
|
|
1019
|
+
const tableNode = { type: "table", content: tableRows };
|
|
1020
|
+
if (isBorderless) {
|
|
1021
|
+
tableNode.attrs = { borderless: true, ...isSubtotals ? { subtotals: true } : {} };
|
|
1022
|
+
}
|
|
1023
|
+
content.push(tableNode);
|
|
1024
|
+
}
|
|
1025
|
+
continue;
|
|
1026
|
+
}
|
|
1027
|
+
const directiveMatch = line.trim().match(DIRECTIVE_OPEN);
|
|
1028
|
+
if (directiveMatch) {
|
|
1029
|
+
const directiveType = directiveMatch[1];
|
|
1030
|
+
const rawAttrs = directiveMatch[2] || "";
|
|
1031
|
+
const attrs = parseDirectiveAttrs(rawAttrs);
|
|
1032
|
+
i++;
|
|
1033
|
+
if (directiveType === "watermark") {
|
|
1034
|
+
content.push({
|
|
1035
|
+
type: "watermark",
|
|
1036
|
+
attrs: {
|
|
1037
|
+
text: attrs.text || "",
|
|
1038
|
+
opacity: attrs.opacity || "0.15",
|
|
1039
|
+
angle: attrs.angle || "-45"
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
continue;
|
|
1043
|
+
}
|
|
1044
|
+
if (directiveType === "panel") {
|
|
1045
|
+
const inner = parseBlocks(lines, i, (l) => DIRECTIVE_CLOSE.test(l.trim()));
|
|
1046
|
+
i = inner.nextIdx;
|
|
1047
|
+
if (i < lines.length && DIRECTIVE_CLOSE.test(lines[i].trim())) {
|
|
1048
|
+
i++;
|
|
1049
|
+
}
|
|
1050
|
+
content.push({
|
|
1051
|
+
type: "panel",
|
|
1052
|
+
attrs: {
|
|
1053
|
+
title: attrs.title || "",
|
|
1054
|
+
border: attrs.border || "solid",
|
|
1055
|
+
headerStyle: attrs.headerStyle || ""
|
|
1056
|
+
},
|
|
1057
|
+
content: inner.blocks.length > 0 ? inner.blocks : [{ type: "paragraph" }]
|
|
1058
|
+
});
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
if (directiveType === "repeat") {
|
|
1062
|
+
const inner = parseBlocks(lines, i, (l) => DIRECTIVE_CLOSE.test(l.trim()));
|
|
1063
|
+
i = inner.nextIdx;
|
|
1064
|
+
if (i < lines.length && DIRECTIVE_CLOSE.test(lines[i].trim())) {
|
|
1065
|
+
i++;
|
|
1066
|
+
}
|
|
1067
|
+
content.push({
|
|
1068
|
+
type: "repeatBlock",
|
|
1069
|
+
attrs: { data: attrs.data || "" },
|
|
1070
|
+
content: inner.blocks.length > 0 ? inner.blocks : [{ type: "paragraph" }]
|
|
1071
|
+
});
|
|
1072
|
+
continue;
|
|
1073
|
+
}
|
|
1074
|
+
if (directiveType === "subtotals") {
|
|
1075
|
+
const inner = parseBlocks(lines, i, (l) => DIRECTIVE_CLOSE.test(l.trim()));
|
|
1076
|
+
i = inner.nextIdx;
|
|
1077
|
+
if (i < lines.length && DIRECTIVE_CLOSE.test(lines[i].trim())) {
|
|
1078
|
+
i++;
|
|
1079
|
+
}
|
|
1080
|
+
content.push({
|
|
1081
|
+
type: "subtotalsBlock",
|
|
1082
|
+
content: inner.blocks.length > 0 ? inner.blocks : [{ type: "paragraph" }]
|
|
1083
|
+
});
|
|
1084
|
+
continue;
|
|
1085
|
+
}
|
|
1086
|
+
if (directiveType === "columns") {
|
|
1087
|
+
const splitVal = attrs.split || "50";
|
|
1088
|
+
const columns = [];
|
|
1089
|
+
while (i < lines.length) {
|
|
1090
|
+
const colLine = lines[i].trim();
|
|
1091
|
+
if (DIRECTIVE_CLOSE.test(colLine)) {
|
|
1092
|
+
if (columns.length > 0) {
|
|
1093
|
+
i++;
|
|
1094
|
+
break;
|
|
1095
|
+
}
|
|
1096
|
+
i++;
|
|
1097
|
+
break;
|
|
1098
|
+
}
|
|
1099
|
+
const colMatch = colLine.match(/^:::col(?:\{([^}]*)\})?$/);
|
|
1100
|
+
if (colMatch) {
|
|
1101
|
+
i++;
|
|
1102
|
+
const colInner = parseBlocks(lines, i, (l) => {
|
|
1103
|
+
const trimmed = l.trim();
|
|
1104
|
+
return DIRECTIVE_CLOSE.test(trimmed) || /^:::col(?:\{[^}]*\})?$/.test(trimmed);
|
|
1105
|
+
});
|
|
1106
|
+
i = colInner.nextIdx;
|
|
1107
|
+
if (i < lines.length && DIRECTIVE_CLOSE.test(lines[i].trim())) {
|
|
1108
|
+
i++;
|
|
1109
|
+
}
|
|
1110
|
+
const colAttrsRaw = colMatch[1] || "";
|
|
1111
|
+
const colAttrs = parseDirectiveAttrs(colAttrsRaw);
|
|
1112
|
+
const padTop = parseFloat(colAttrs.padTop || "0") || 0;
|
|
1113
|
+
columns.push({
|
|
1114
|
+
type: "column",
|
|
1115
|
+
attrs: padTop ? { padTop } : void 0,
|
|
1116
|
+
content: colInner.blocks.length > 0 ? colInner.blocks : [{ type: "paragraph" }]
|
|
1117
|
+
});
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
if (colLine === "") {
|
|
1121
|
+
i++;
|
|
1122
|
+
continue;
|
|
1123
|
+
}
|
|
1124
|
+
break;
|
|
1125
|
+
}
|
|
1126
|
+
const columnsAttrs = { split: splitVal };
|
|
1127
|
+
if (attrs.padX) columnsAttrs.padX = attrs.padX;
|
|
1128
|
+
content.push({
|
|
1129
|
+
type: "columns",
|
|
1130
|
+
attrs: columnsAttrs,
|
|
1131
|
+
content: columns.length > 0 ? columns : [
|
|
1132
|
+
{ type: "column", content: [{ type: "paragraph" }] },
|
|
1133
|
+
{ type: "column", content: [{ type: "paragraph" }] }
|
|
1134
|
+
]
|
|
1135
|
+
});
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1138
|
+
if (directiveType === "col") {
|
|
1139
|
+
const inner = parseBlocks(lines, i, (l) => DIRECTIVE_CLOSE.test(l.trim()));
|
|
1140
|
+
i = inner.nextIdx;
|
|
1141
|
+
if (i < lines.length && DIRECTIVE_CLOSE.test(lines[i].trim())) {
|
|
1142
|
+
i++;
|
|
1143
|
+
}
|
|
1144
|
+
for (const block of inner.blocks) {
|
|
1145
|
+
content.push(block);
|
|
1146
|
+
}
|
|
1147
|
+
continue;
|
|
1148
|
+
}
|
|
1149
|
+
continue;
|
|
1150
|
+
}
|
|
1151
|
+
if (DIRECTIVE_CLOSE.test(line.trim())) {
|
|
1152
|
+
i++;
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
1155
|
+
const imageMatch = line.trim().match(BLOCK_IMAGE_REGEX);
|
|
1156
|
+
if (imageMatch) {
|
|
1157
|
+
content.push({
|
|
1158
|
+
type: "imageNode",
|
|
1159
|
+
attrs: { src: imageMatch[2], varName: "", alt: imageMatch[1], width: "", height: "", align: "" }
|
|
1160
|
+
});
|
|
1161
|
+
i++;
|
|
1162
|
+
continue;
|
|
1163
|
+
}
|
|
1164
|
+
const inlineNodes = parseInline(line);
|
|
1165
|
+
if (inlineNodes.some((n) => n.type === "imageNode")) {
|
|
1166
|
+
let run = [];
|
|
1167
|
+
const flushRun = () => {
|
|
1168
|
+
if (run.some((n) => !(n.type === "text" && !(n.text || "").trim()))) {
|
|
1169
|
+
content.push({ type: "paragraph", content: run });
|
|
1170
|
+
}
|
|
1171
|
+
run = [];
|
|
1172
|
+
};
|
|
1173
|
+
for (const n of inlineNodes) {
|
|
1174
|
+
if (n.type === "imageNode") {
|
|
1175
|
+
flushRun();
|
|
1176
|
+
content.push(n);
|
|
1177
|
+
} else {
|
|
1178
|
+
run.push(n);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
flushRun();
|
|
1182
|
+
} else {
|
|
1183
|
+
content.push({ type: "paragraph", content: inlineNodes });
|
|
1184
|
+
}
|
|
815
1185
|
i++;
|
|
816
1186
|
}
|
|
817
|
-
return {
|
|
1187
|
+
return { blocks: content, nextIdx: i };
|
|
1188
|
+
}
|
|
1189
|
+
function markdownToTiptap(markdown) {
|
|
1190
|
+
const lines = markdown.split("\n");
|
|
1191
|
+
const { blocks } = parseBlocks(lines, 0);
|
|
1192
|
+
return { type: "doc", content: blocks };
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// src/utils/variable-helpers.ts
|
|
1196
|
+
function extractVariablesFromContent(content) {
|
|
1197
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1198
|
+
function walk(node) {
|
|
1199
|
+
if (node.type === "variableNode" && node.attrs) {
|
|
1200
|
+
const varName = node.attrs.varName;
|
|
1201
|
+
if (varName && !seen.has(varName)) {
|
|
1202
|
+
seen.set(varName, {
|
|
1203
|
+
varName,
|
|
1204
|
+
varLabel: node.attrs.varLabel || varName,
|
|
1205
|
+
varDefault: node.attrs.varDefault || ""
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
if (node.content) {
|
|
1210
|
+
node.content.forEach(walk);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
if (content.content) {
|
|
1214
|
+
content.content.forEach(walk);
|
|
1215
|
+
}
|
|
1216
|
+
return Array.from(seen.values());
|
|
1217
|
+
}
|
|
1218
|
+
function applyFormat(value, format) {
|
|
1219
|
+
if (format === "phone") {
|
|
1220
|
+
const digits = value.replace(/\D/g, "");
|
|
1221
|
+
if (digits.length === 10) {
|
|
1222
|
+
return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)} - ${digits.slice(6)}`;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
return value;
|
|
1226
|
+
}
|
|
1227
|
+
function replaceVariablesInContent(content, values) {
|
|
1228
|
+
function walkNode(node) {
|
|
1229
|
+
if (node.type === "variableNode" && node.attrs) {
|
|
1230
|
+
const varName = node.attrs.varName;
|
|
1231
|
+
let replacement = values[varName] || node.attrs.varDefault || "";
|
|
1232
|
+
const format = node.attrs.format;
|
|
1233
|
+
if (format && replacement) {
|
|
1234
|
+
replacement = applyFormat(replacement, format);
|
|
1235
|
+
}
|
|
1236
|
+
const textNode = { type: "text", text: replacement };
|
|
1237
|
+
if (node.marks && node.marks.length > 0) {
|
|
1238
|
+
textNode.marks = node.marks;
|
|
1239
|
+
}
|
|
1240
|
+
return textNode;
|
|
1241
|
+
}
|
|
1242
|
+
if (node.type === "imageNode" && node.attrs?.varName) {
|
|
1243
|
+
const varName = node.attrs.varName;
|
|
1244
|
+
const resolved = values[varName] || node.attrs.src || "";
|
|
1245
|
+
return { ...node, attrs: { ...node.attrs, src: resolved } };
|
|
1246
|
+
}
|
|
1247
|
+
if (node.content) {
|
|
1248
|
+
return { ...node, content: node.content.map(walkNode) };
|
|
1249
|
+
}
|
|
1250
|
+
return node;
|
|
1251
|
+
}
|
|
1252
|
+
return walkNode(content);
|
|
1253
|
+
}
|
|
1254
|
+
function expandBlockVariables(content, values) {
|
|
1255
|
+
function soleVariableOf(node) {
|
|
1256
|
+
if (node.type !== "paragraph" || !node.content) return null;
|
|
1257
|
+
const meaningful = node.content.filter(
|
|
1258
|
+
(c) => !(c.type === "text" && !(c.text || "").trim())
|
|
1259
|
+
);
|
|
1260
|
+
if (meaningful.length === 1 && meaningful[0].type === "variableNode") {
|
|
1261
|
+
return meaningful[0];
|
|
1262
|
+
}
|
|
1263
|
+
return null;
|
|
1264
|
+
}
|
|
1265
|
+
function stripUnsafeNodes(node) {
|
|
1266
|
+
if (!node.content) return node;
|
|
1267
|
+
return {
|
|
1268
|
+
...node,
|
|
1269
|
+
content: node.content.filter((c) => c.type !== "fieldNode" && c.type !== "watermark").map(stripUnsafeNodes)
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
function isCellSafe(blocks) {
|
|
1273
|
+
return blocks.every((b) => b.type === "paragraph" || b.type === "heading");
|
|
1274
|
+
}
|
|
1275
|
+
function walkNode(node, inCell) {
|
|
1276
|
+
if (!node.content) return node;
|
|
1277
|
+
const childInCell = inCell || node.type === "tableCell" || node.type === "tableHeader";
|
|
1278
|
+
const newContent = [];
|
|
1279
|
+
for (const child of node.content) {
|
|
1280
|
+
const varNode = soleVariableOf(child);
|
|
1281
|
+
if (varNode?.attrs) {
|
|
1282
|
+
const varName = varNode.attrs.varName;
|
|
1283
|
+
const value = values[varName] || varNode.attrs.varDefault || "";
|
|
1284
|
+
const isBlock = varNode.attrs.block === "true" || value.includes("\n");
|
|
1285
|
+
if (isBlock) {
|
|
1286
|
+
const parsed = stripUnsafeNodes(markdownToTiptap(value));
|
|
1287
|
+
const blocks = parsed.content || [];
|
|
1288
|
+
if (!childInCell || isCellSafe(blocks)) {
|
|
1289
|
+
newContent.push(...blocks);
|
|
1290
|
+
continue;
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
newContent.push(walkNode(child, childInCell));
|
|
1295
|
+
}
|
|
1296
|
+
return { ...node, content: newContent };
|
|
1297
|
+
}
|
|
1298
|
+
return walkNode(content, false);
|
|
1299
|
+
}
|
|
1300
|
+
function labelToVarName(label) {
|
|
1301
|
+
return label.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
1302
|
+
}
|
|
1303
|
+
function isZeroLike(value) {
|
|
1304
|
+
if (!value || value.trim() === "") return true;
|
|
1305
|
+
const cleaned = value.replace(/[$,\s]/g, "");
|
|
1306
|
+
return /^-?0+(\.0+)?$/.test(cleaned);
|
|
1307
|
+
}
|
|
1308
|
+
function suppressZeroContent(content, values) {
|
|
1309
|
+
function hasSuppressedVar(node) {
|
|
1310
|
+
if (node.type === "variableNode" && node.attrs?.suppressZero === "true") {
|
|
1311
|
+
const varName = node.attrs.varName;
|
|
1312
|
+
const val = values[varName] || node.attrs.varDefault || "";
|
|
1313
|
+
return isZeroLike(val);
|
|
1314
|
+
}
|
|
1315
|
+
return (node.content || []).some(hasSuppressedVar);
|
|
1316
|
+
}
|
|
1317
|
+
function walkNode(node) {
|
|
1318
|
+
if (node.type === "table" && node.content) {
|
|
1319
|
+
const filtered = node.content.filter((row) => {
|
|
1320
|
+
if (row.type !== "tableRow") return true;
|
|
1321
|
+
const isHeader = row.content?.[0]?.type === "tableHeader";
|
|
1322
|
+
if (isHeader) return true;
|
|
1323
|
+
return !hasSuppressedVar(row);
|
|
1324
|
+
});
|
|
1325
|
+
const bodyRows = filtered.filter(
|
|
1326
|
+
(r) => r.type === "tableRow" && r.content?.[0]?.type !== "tableHeader"
|
|
1327
|
+
);
|
|
1328
|
+
if (bodyRows.length === 0) return null;
|
|
1329
|
+
return { ...node, content: filtered };
|
|
1330
|
+
}
|
|
1331
|
+
if (node.type === "paragraph" && hasSuppressedVar(node)) {
|
|
1332
|
+
return null;
|
|
1333
|
+
}
|
|
1334
|
+
if (node.content) {
|
|
1335
|
+
const filtered = node.content.map((child) => walkNode(child)).filter((c) => c !== null);
|
|
1336
|
+
return { ...node, content: filtered };
|
|
1337
|
+
}
|
|
1338
|
+
return node;
|
|
1339
|
+
}
|
|
1340
|
+
return walkNode(content) || content;
|
|
1341
|
+
}
|
|
1342
|
+
function expandRepeatContent(content, values) {
|
|
1343
|
+
const enrichedValues = { ...values };
|
|
1344
|
+
function cloneNode(node, dataKey, index) {
|
|
1345
|
+
if (node.type === "variableNode" && node.attrs?.varName) {
|
|
1346
|
+
const oldName = node.attrs.varName;
|
|
1347
|
+
const bareName = oldName.replace(/^operation_/, "");
|
|
1348
|
+
const newName = `${dataKey}_${index}_${bareName}`;
|
|
1349
|
+
return { ...node, attrs: { ...node.attrs, varName: newName } };
|
|
1350
|
+
}
|
|
1351
|
+
if (node.content) {
|
|
1352
|
+
return { ...node, content: node.content.map((n) => cloneNode(n, dataKey, index)) };
|
|
1353
|
+
}
|
|
1354
|
+
return { ...node };
|
|
1355
|
+
}
|
|
1356
|
+
function walkNode(node) {
|
|
1357
|
+
if (node.type === "repeatBlock" && node.attrs?.data) {
|
|
1358
|
+
const dataKey = node.attrs.data;
|
|
1359
|
+
const arrayJson = values[dataKey];
|
|
1360
|
+
if (!arrayJson) return [];
|
|
1361
|
+
let items;
|
|
1362
|
+
try {
|
|
1363
|
+
items = JSON.parse(arrayJson);
|
|
1364
|
+
} catch {
|
|
1365
|
+
return [];
|
|
1366
|
+
}
|
|
1367
|
+
const sums = /* @__PURE__ */ new Map();
|
|
1368
|
+
for (const item of items) {
|
|
1369
|
+
for (const [key, val] of Object.entries(item)) {
|
|
1370
|
+
const num = parseFloat(val);
|
|
1371
|
+
if (!isNaN(num)) {
|
|
1372
|
+
sums.set(key, (sums.get(key) || 0) + num);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
const woKeysByNorm = /* @__PURE__ */ new Map();
|
|
1377
|
+
for (const k of Object.keys(enrichedValues)) {
|
|
1378
|
+
if (k.startsWith("workorder_")) {
|
|
1379
|
+
const norm = k.replace(/_/g, "");
|
|
1380
|
+
woKeysByNorm.set(norm, k);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
for (const [field, total] of sums) {
|
|
1384
|
+
const formatted = total % 1 === 0 ? total.toFixed(2) : total.toFixed(2);
|
|
1385
|
+
const directKey = `workorder_${field}`;
|
|
1386
|
+
enrichedValues[directKey] = formatted;
|
|
1387
|
+
const norm = `workorder${field}`.replace(/_/g, "");
|
|
1388
|
+
const existing = woKeysByNorm.get(norm);
|
|
1389
|
+
if (existing && existing !== directKey) {
|
|
1390
|
+
enrichedValues[existing] = formatted;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
const expanded = [];
|
|
1394
|
+
for (let i = 0; i < items.length; i++) {
|
|
1395
|
+
const item = items[i];
|
|
1396
|
+
for (const [key, val] of Object.entries(item)) {
|
|
1397
|
+
enrichedValues[`${dataKey}_${i}_${key}`] = String(val);
|
|
1398
|
+
}
|
|
1399
|
+
const cloned = (node.content || []).map((n) => cloneNode(n, dataKey, i));
|
|
1400
|
+
expanded.push(...cloned);
|
|
1401
|
+
}
|
|
1402
|
+
return expanded;
|
|
1403
|
+
}
|
|
1404
|
+
if (node.content) {
|
|
1405
|
+
const newContent = [];
|
|
1406
|
+
for (const child of node.content) {
|
|
1407
|
+
const result2 = walkNode(child);
|
|
1408
|
+
if (Array.isArray(result2)) newContent.push(...result2);
|
|
1409
|
+
else newContent.push(result2);
|
|
1410
|
+
}
|
|
1411
|
+
return { ...node, content: newContent };
|
|
1412
|
+
}
|
|
1413
|
+
return node;
|
|
1414
|
+
}
|
|
1415
|
+
const result = walkNode(content);
|
|
1416
|
+
return {
|
|
1417
|
+
content: Array.isArray(result) ? { ...content, content: result } : result,
|
|
1418
|
+
values: enrichedValues
|
|
1419
|
+
};
|
|
818
1420
|
}
|
|
819
1421
|
|
|
820
1422
|
// src/utils/error-helpers.ts
|
|
@@ -952,10 +1554,40 @@ function buildMetadataObject(fields, actualFieldNames) {
|
|
|
952
1554
|
// src/utils/pdf-generator.ts
|
|
953
1555
|
var PAGE_WIDTH = 595.28;
|
|
954
1556
|
var PAGE_HEIGHT = 841.89;
|
|
955
|
-
var MARGIN =
|
|
1557
|
+
var MARGIN = 40;
|
|
956
1558
|
var CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN;
|
|
957
1559
|
var CONTENT_HEIGHT = PAGE_HEIGHT - 2 * MARGIN;
|
|
958
1560
|
var LINE_HEIGHT_FACTOR = 1.4;
|
|
1561
|
+
var BODY_FONT_SIZE = 10;
|
|
1562
|
+
function hexToRgbColor(hex) {
|
|
1563
|
+
if (!hex) return null;
|
|
1564
|
+
const m = hex.trim().match(/^#?([0-9a-fA-F]{6})$/);
|
|
1565
|
+
if (!m) return null;
|
|
1566
|
+
const n = parseInt(m[1], 16);
|
|
1567
|
+
return pdfLib.rgb((n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255);
|
|
1568
|
+
}
|
|
1569
|
+
function contrastTextColor(fill) {
|
|
1570
|
+
const c = fill;
|
|
1571
|
+
const luminance = 0.299 * c.red + 0.587 * c.green + 0.114 * c.blue;
|
|
1572
|
+
return luminance > 0.6 ? pdfLib.rgb(0, 0, 0) : pdfLib.rgb(1, 1, 1);
|
|
1573
|
+
}
|
|
1574
|
+
function resolveTheme(theme) {
|
|
1575
|
+
const primary = hexToRgbColor(theme?.primaryColor);
|
|
1576
|
+
const accent = hexToRgbColor(theme?.accentColor);
|
|
1577
|
+
const border = hexToRgbColor(theme?.borderColor);
|
|
1578
|
+
return {
|
|
1579
|
+
tableHeaderBg: primary ?? pdfLib.rgb(0.3, 0.3, 0.3),
|
|
1580
|
+
tableHeaderText: primary ? contrastTextColor(primary) : pdfLib.rgb(1, 1, 1),
|
|
1581
|
+
tableBorder: border ?? pdfLib.rgb(0.75, 0.75, 0.75),
|
|
1582
|
+
panelDarkHeaderBg: primary ?? pdfLib.rgb(0.25, 0.25, 0.25),
|
|
1583
|
+
panelDarkHeaderText: primary ? contrastTextColor(primary) : pdfLib.rgb(1, 1, 1),
|
|
1584
|
+
panelBorder: border ?? pdfLib.rgb(0.6, 0.6, 0.6),
|
|
1585
|
+
rule: primary ?? pdfLib.rgb(0, 0, 0),
|
|
1586
|
+
pageHeaderText: primary ?? pdfLib.rgb(0, 0, 0),
|
|
1587
|
+
pageHeaderRule: accent ?? primary ?? pdfLib.rgb(0.75, 0.75, 0.75)
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
var DEFAULT_REGION = { leftX: MARGIN, width: CONTENT_WIDTH };
|
|
959
1591
|
var FIELD_DISPLAY = {
|
|
960
1592
|
["text" /* TEXT */]: { label: "Text", width: 120, height: 30 },
|
|
961
1593
|
["signature" /* SIGNATURE */]: { label: "Signature", width: 200, height: 60 },
|
|
@@ -986,17 +1618,65 @@ function getFieldDimensions(fieldType, attrs, font, fontSize) {
|
|
|
986
1618
|
const height = textLineHeight + 4;
|
|
987
1619
|
return { width, height };
|
|
988
1620
|
}
|
|
1621
|
+
async function loadImageBytes(src) {
|
|
1622
|
+
if (src.startsWith("data:")) {
|
|
1623
|
+
const comma = src.indexOf(",");
|
|
1624
|
+
if (comma === -1) throw new Error("malformed data URL");
|
|
1625
|
+
const meta = src.slice(0, comma);
|
|
1626
|
+
if (!/;base64$/i.test(meta)) throw new Error("data URL must be base64-encoded");
|
|
1627
|
+
const bin = atob(src.slice(comma + 1));
|
|
1628
|
+
const bytes = new Uint8Array(bin.length);
|
|
1629
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
1630
|
+
return bytes;
|
|
1631
|
+
}
|
|
1632
|
+
const res = await fetch(src);
|
|
1633
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1634
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
1635
|
+
}
|
|
1636
|
+
function describeImageSrc(src) {
|
|
1637
|
+
return src.length > 80 ? `${src.slice(0, 77)}...` : src;
|
|
1638
|
+
}
|
|
1639
|
+
async function preloadImages(pdfDoc, content) {
|
|
1640
|
+
const srcs = /* @__PURE__ */ new Set();
|
|
1641
|
+
(function walk(node) {
|
|
1642
|
+
if (node.type === "imageNode") {
|
|
1643
|
+
const src = node.attrs?.src || "";
|
|
1644
|
+
if (src) srcs.add(src);
|
|
1645
|
+
}
|
|
1646
|
+
for (const child of node.content || []) walk(child);
|
|
1647
|
+
})(content);
|
|
1648
|
+
const images = /* @__PURE__ */ new Map();
|
|
1649
|
+
const warnings = [];
|
|
1650
|
+
for (const src of srcs) {
|
|
1651
|
+
try {
|
|
1652
|
+
const bytes = await loadImageBytes(src);
|
|
1653
|
+
const isPng = bytes.length > 4 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71;
|
|
1654
|
+
const isJpeg = bytes.length > 2 && bytes[0] === 255 && bytes[1] === 216;
|
|
1655
|
+
if (isPng) {
|
|
1656
|
+
images.set(src, await pdfDoc.embedPng(bytes));
|
|
1657
|
+
} else if (isJpeg) {
|
|
1658
|
+
images.set(src, await pdfDoc.embedJpg(bytes));
|
|
1659
|
+
} else {
|
|
1660
|
+
warnings.push(`Image skipped (unsupported format \u2014 only PNG/JPEG embed): ${describeImageSrc(src)}`);
|
|
1661
|
+
}
|
|
1662
|
+
} catch (err) {
|
|
1663
|
+
warnings.push(`Image skipped (${getErrorMessage(err)}): ${describeImageSrc(src)}`);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
return { images, warnings };
|
|
1667
|
+
}
|
|
989
1668
|
function ensureSpace(state, neededHeight) {
|
|
990
1669
|
if (state.y + neededHeight > CONTENT_HEIGHT) {
|
|
991
1670
|
const newPage = state.pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
|
|
992
1671
|
state.currentPage = newPage;
|
|
993
1672
|
state.pageIndex++;
|
|
994
1673
|
state.y = 0;
|
|
1674
|
+
renderWatermarksOnPage(newPage, state.fonts, state.watermarkNodes);
|
|
995
1675
|
}
|
|
996
1676
|
}
|
|
997
|
-
function drawText(state, text, font, fontSize, indent = 0) {
|
|
1677
|
+
function drawText(state, text, font, fontSize, indent = 0, region = DEFAULT_REGION) {
|
|
998
1678
|
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
999
|
-
const maxWidth =
|
|
1679
|
+
const maxWidth = region.width - indent;
|
|
1000
1680
|
const words = text.split(/\s+/);
|
|
1001
1681
|
let line = "";
|
|
1002
1682
|
let totalAdvance = 0;
|
|
@@ -1007,7 +1687,7 @@ function drawText(state, text, font, fontSize, indent = 0) {
|
|
|
1007
1687
|
ensureSpace(state, lineHeight);
|
|
1008
1688
|
const pdfY = PAGE_HEIGHT - MARGIN - state.y - fontSize;
|
|
1009
1689
|
state.currentPage.drawText(line, {
|
|
1010
|
-
x:
|
|
1690
|
+
x: region.leftX + indent,
|
|
1011
1691
|
y: pdfY,
|
|
1012
1692
|
size: fontSize,
|
|
1013
1693
|
font,
|
|
@@ -1024,7 +1704,7 @@ function drawText(state, text, font, fontSize, indent = 0) {
|
|
|
1024
1704
|
ensureSpace(state, lineHeight);
|
|
1025
1705
|
const pdfY = PAGE_HEIGHT - MARGIN - state.y - fontSize;
|
|
1026
1706
|
state.currentPage.drawText(line, {
|
|
1027
|
-
x:
|
|
1707
|
+
x: region.leftX + indent,
|
|
1028
1708
|
y: pdfY,
|
|
1029
1709
|
size: fontSize,
|
|
1030
1710
|
font,
|
|
@@ -1068,10 +1748,10 @@ function collectInlineSegments(content, fonts, fontSize) {
|
|
|
1068
1748
|
}
|
|
1069
1749
|
return segments;
|
|
1070
1750
|
}
|
|
1071
|
-
function layoutInlineSegments(state, segments, fontSize, indent = 0) {
|
|
1751
|
+
function layoutInlineSegments(state, segments, fontSize, indent = 0, region = DEFAULT_REGION) {
|
|
1072
1752
|
const textLineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
1073
|
-
const maxX =
|
|
1074
|
-
const startX =
|
|
1753
|
+
const maxX = region.leftX + region.width;
|
|
1754
|
+
const startX = region.leftX + indent;
|
|
1075
1755
|
let currentX = startX;
|
|
1076
1756
|
let currentLineHeight = 0;
|
|
1077
1757
|
let lineHasContent = false;
|
|
@@ -1176,13 +1856,318 @@ function layoutInlineSegments(state, segments, fontSize, indent = 0) {
|
|
|
1176
1856
|
}
|
|
1177
1857
|
flushLine();
|
|
1178
1858
|
}
|
|
1179
|
-
function processInlineContent(state, content, fontSize, indent = 0) {
|
|
1859
|
+
function processInlineContent(state, content, fontSize, indent = 0, region = DEFAULT_REGION) {
|
|
1180
1860
|
if (!content) return;
|
|
1181
1861
|
const segments = collectInlineSegments(content, state.fonts, fontSize);
|
|
1182
1862
|
if (segments.length === 0) return;
|
|
1183
|
-
layoutInlineSegments(state, segments, fontSize, indent);
|
|
1863
|
+
layoutInlineSegments(state, segments, fontSize, indent, region);
|
|
1864
|
+
}
|
|
1865
|
+
function extractCellText(cell) {
|
|
1866
|
+
const parts = [];
|
|
1867
|
+
for (const child of cell.content || []) {
|
|
1868
|
+
if (child.type === "paragraph" || child.type === "heading") {
|
|
1869
|
+
for (const inline of child.content || []) {
|
|
1870
|
+
if (inline.type === "text") {
|
|
1871
|
+
parts.push(inline.text || "");
|
|
1872
|
+
} else if (inline.type === "variableNode") {
|
|
1873
|
+
const label = inline.attrs?.varLabel || inline.attrs?.varName || "Variable";
|
|
1874
|
+
parts.push(`[${label}]`);
|
|
1875
|
+
} else if (inline.type === "fieldNode") {
|
|
1876
|
+
const label = inline.attrs?.fieldLabel || inline.attrs?.fieldName || "Field";
|
|
1877
|
+
parts.push(`[ ${label} ]`);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
return parts.join("");
|
|
1184
1883
|
}
|
|
1185
|
-
function
|
|
1884
|
+
function isNumericText(text) {
|
|
1885
|
+
return /^\s*\$?\s*[\d,.]+\s*$/.test(text);
|
|
1886
|
+
}
|
|
1887
|
+
function measureCellHeight(cell, maxWidth, fonts, fontSize, isHeader) {
|
|
1888
|
+
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
1889
|
+
const font = isHeader ? fonts.bold : fonts.regular;
|
|
1890
|
+
let totalHeight = 0;
|
|
1891
|
+
for (const child of cell.content || []) {
|
|
1892
|
+
if (child.type === "paragraph" || child.type === "heading") {
|
|
1893
|
+
const text = extractCellText({ content: [child] });
|
|
1894
|
+
if (!text) {
|
|
1895
|
+
totalHeight += lineHeight;
|
|
1896
|
+
continue;
|
|
1897
|
+
}
|
|
1898
|
+
const words = text.split(/\s+/);
|
|
1899
|
+
let line = "";
|
|
1900
|
+
let lineCount = 0;
|
|
1901
|
+
for (const word of words) {
|
|
1902
|
+
const testLine = line ? `${line} ${word}` : word;
|
|
1903
|
+
const testWidth = font.widthOfTextAtSize(testLine, fontSize);
|
|
1904
|
+
if (testWidth > maxWidth && line) {
|
|
1905
|
+
lineCount++;
|
|
1906
|
+
line = word;
|
|
1907
|
+
} else {
|
|
1908
|
+
line = testLine;
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
if (line) lineCount++;
|
|
1912
|
+
totalHeight += lineCount * lineHeight;
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
return Math.max(totalHeight, lineHeight);
|
|
1916
|
+
}
|
|
1917
|
+
function renderCellContent(state, cell, cellX, cellY, cellWidth, fonts, fontSize, isHeader, rightAlign, textColor = pdfLib.rgb(0, 0, 0)) {
|
|
1918
|
+
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
1919
|
+
const cellPadding = 4;
|
|
1920
|
+
const maxTextWidth = cellWidth - cellPadding * 2;
|
|
1921
|
+
let textX = cellX + cellPadding;
|
|
1922
|
+
let textY = cellY;
|
|
1923
|
+
for (const child of cell.content || []) {
|
|
1924
|
+
if (child.type === "paragraph" || child.type === "heading") {
|
|
1925
|
+
const segments = collectInlineSegments(
|
|
1926
|
+
child.content || [],
|
|
1927
|
+
isHeader ? { regular: fonts.bold, bold: fonts.bold, italic: fonts.boldItalic, boldItalic: fonts.boldItalic } : fonts,
|
|
1928
|
+
fontSize
|
|
1929
|
+
);
|
|
1930
|
+
if (rightAlign && segments.length > 0) {
|
|
1931
|
+
const fullText = segments.filter((s) => s.kind === "text").map((s) => s.text).join("");
|
|
1932
|
+
const textWidth = (isHeader ? fonts.bold : fonts.regular).widthOfTextAtSize(fullText, fontSize);
|
|
1933
|
+
if (textWidth <= maxTextWidth) {
|
|
1934
|
+
const pdfY = PAGE_HEIGHT - textY - fontSize;
|
|
1935
|
+
state.currentPage.drawText(fullText, {
|
|
1936
|
+
x: cellX + cellWidth - cellPadding - textWidth,
|
|
1937
|
+
y: pdfY,
|
|
1938
|
+
size: fontSize,
|
|
1939
|
+
font: isHeader ? fonts.bold : fonts.regular,
|
|
1940
|
+
color: textColor
|
|
1941
|
+
});
|
|
1942
|
+
return;
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
let currentX = textX;
|
|
1946
|
+
for (const seg of segments) {
|
|
1947
|
+
if (seg.kind === "break") {
|
|
1948
|
+
textY += lineHeight;
|
|
1949
|
+
currentX = textX;
|
|
1950
|
+
continue;
|
|
1951
|
+
}
|
|
1952
|
+
if (seg.kind === "text") {
|
|
1953
|
+
const words = seg.text.split(/(\s+)/);
|
|
1954
|
+
const spaceWidth = seg.font.widthOfTextAtSize(" ", fontSize);
|
|
1955
|
+
for (const token of words) {
|
|
1956
|
+
if (!token) continue;
|
|
1957
|
+
if (/^\s+$/.test(token)) {
|
|
1958
|
+
currentX += spaceWidth;
|
|
1959
|
+
continue;
|
|
1960
|
+
}
|
|
1961
|
+
const wordWidth = seg.font.widthOfTextAtSize(token, fontSize);
|
|
1962
|
+
if (currentX + wordWidth > textX + maxTextWidth && currentX > textX) {
|
|
1963
|
+
textY += lineHeight;
|
|
1964
|
+
currentX = textX;
|
|
1965
|
+
}
|
|
1966
|
+
const pdfY = PAGE_HEIGHT - textY - fontSize;
|
|
1967
|
+
state.currentPage.drawText(token, {
|
|
1968
|
+
x: currentX,
|
|
1969
|
+
y: pdfY,
|
|
1970
|
+
size: fontSize,
|
|
1971
|
+
font: seg.font,
|
|
1972
|
+
color: textColor
|
|
1973
|
+
});
|
|
1974
|
+
currentX += wordWidth;
|
|
1975
|
+
}
|
|
1976
|
+
} else if (seg.kind === "field") {
|
|
1977
|
+
const pdfY = PAGE_HEIGHT - textY - seg.height;
|
|
1978
|
+
if (state.drawFieldPlaceholders) {
|
|
1979
|
+
state.currentPage.drawRectangle({
|
|
1980
|
+
x: currentX,
|
|
1981
|
+
y: pdfY,
|
|
1982
|
+
width: seg.width,
|
|
1983
|
+
height: seg.height,
|
|
1984
|
+
borderColor: pdfLib.rgb(0.5, 0.5, 0.7),
|
|
1985
|
+
borderWidth: 0.5,
|
|
1986
|
+
color: pdfLib.rgb(0.95, 0.95, 1),
|
|
1987
|
+
borderDashArray: [4, 2]
|
|
1988
|
+
});
|
|
1989
|
+
}
|
|
1990
|
+
const fieldId = seg.attrs.fieldId;
|
|
1991
|
+
if (fieldId) {
|
|
1992
|
+
state.fieldPositions.set(fieldId, {
|
|
1993
|
+
x: currentX,
|
|
1994
|
+
y: pdfY,
|
|
1995
|
+
width: seg.width,
|
|
1996
|
+
height: seg.height,
|
|
1997
|
+
page: state.pageIndex + 1
|
|
1998
|
+
});
|
|
1999
|
+
}
|
|
2000
|
+
currentX += seg.width + 4;
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
function renderTable(state, node, region = DEFAULT_REGION) {
|
|
2007
|
+
const tableRows = node.content || [];
|
|
2008
|
+
if (tableRows.length === 0) return;
|
|
2009
|
+
const borderless = node.attrs?.borderless === true;
|
|
2010
|
+
const subtotals = node.attrs?.subtotals === true;
|
|
2011
|
+
const fontSize = 10;
|
|
2012
|
+
const cellPadding = borderless ? 2 : 4;
|
|
2013
|
+
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
2014
|
+
const rowPadding = cellPadding * 2;
|
|
2015
|
+
const borderColor = state.theme.tableBorder;
|
|
2016
|
+
const headerBg = state.theme.tableHeaderBg;
|
|
2017
|
+
const headerTextColor = state.theme.tableHeaderText;
|
|
2018
|
+
let colCount = 0;
|
|
2019
|
+
for (const row of tableRows) {
|
|
2020
|
+
colCount = Math.max(colCount, row.content?.length || 0);
|
|
2021
|
+
}
|
|
2022
|
+
if (colCount === 0) return;
|
|
2023
|
+
const colMaxWidths = new Array(colCount).fill(0);
|
|
2024
|
+
for (const row of tableRows) {
|
|
2025
|
+
const cells = row.content || [];
|
|
2026
|
+
for (let c = 0; c < cells.length && c < colCount; c++) {
|
|
2027
|
+
const cell = cells[c];
|
|
2028
|
+
const isHeader = cell.type === "tableHeader";
|
|
2029
|
+
const text = extractCellText(cell);
|
|
2030
|
+
const font = isHeader ? state.fonts.bold : state.fonts.regular;
|
|
2031
|
+
const textWidth = font.widthOfTextAtSize(text, fontSize);
|
|
2032
|
+
colMaxWidths[c] = Math.max(colMaxWidths[c], textWidth + cellPadding * 2 + 4);
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
const totalMeasured = colMaxWidths.reduce((a, b) => a + b, 0);
|
|
2036
|
+
const minColWidth = 40;
|
|
2037
|
+
let colWidths;
|
|
2038
|
+
if (totalMeasured <= region.width) {
|
|
2039
|
+
const scale = region.width / totalMeasured;
|
|
2040
|
+
colWidths = colMaxWidths.map((w) => Math.max(w * scale, minColWidth));
|
|
2041
|
+
} else {
|
|
2042
|
+
colWidths = colMaxWidths.map(
|
|
2043
|
+
(w) => Math.max(w / totalMeasured * region.width, minColWidth)
|
|
2044
|
+
);
|
|
2045
|
+
}
|
|
2046
|
+
const colSum = colWidths.reduce((a, b) => a + b, 0);
|
|
2047
|
+
if (colSum > 0) {
|
|
2048
|
+
const normFactor = region.width / colSum;
|
|
2049
|
+
colWidths = colWidths.map((w) => w * normFactor);
|
|
2050
|
+
}
|
|
2051
|
+
const headerRow = tableRows[0];
|
|
2052
|
+
const hasHeader = headerRow?.content?.[0]?.type === "tableHeader";
|
|
2053
|
+
function renderRow(row, isHeaderRow) {
|
|
2054
|
+
const cells = row.content || [];
|
|
2055
|
+
const isHeader = isHeaderRow && hasHeader;
|
|
2056
|
+
let rowHeight = lineHeight;
|
|
2057
|
+
for (let c = 0; c < cells.length && c < colCount; c++) {
|
|
2058
|
+
const cellContentWidth = colWidths[c] - cellPadding * 2;
|
|
2059
|
+
const cellH = measureCellHeight(cells[c], cellContentWidth, state.fonts, fontSize, isHeader);
|
|
2060
|
+
rowHeight = Math.max(rowHeight, cellH);
|
|
2061
|
+
}
|
|
2062
|
+
rowHeight += rowPadding;
|
|
2063
|
+
ensureSpace(state, rowHeight);
|
|
2064
|
+
const rowTopY = state.y;
|
|
2065
|
+
let cellX = region.leftX;
|
|
2066
|
+
for (let c = 0; c < cells.length && c < colCount; c++) {
|
|
2067
|
+
const cellW = colWidths[c];
|
|
2068
|
+
const cell = cells[c];
|
|
2069
|
+
const pdfCellTop = PAGE_HEIGHT - MARGIN - rowTopY;
|
|
2070
|
+
const pdfCellBottom = pdfCellTop - rowHeight;
|
|
2071
|
+
if (isHeader && !borderless) {
|
|
2072
|
+
state.currentPage.drawRectangle({
|
|
2073
|
+
x: cellX,
|
|
2074
|
+
y: pdfCellBottom,
|
|
2075
|
+
width: cellW,
|
|
2076
|
+
height: rowHeight,
|
|
2077
|
+
color: headerBg
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
if (!borderless) {
|
|
2081
|
+
state.currentPage.drawRectangle({
|
|
2082
|
+
x: cellX,
|
|
2083
|
+
y: pdfCellBottom,
|
|
2084
|
+
width: cellW,
|
|
2085
|
+
height: rowHeight,
|
|
2086
|
+
borderColor,
|
|
2087
|
+
borderWidth: 0.5
|
|
2088
|
+
});
|
|
2089
|
+
}
|
|
2090
|
+
const cellText = extractCellText(cell);
|
|
2091
|
+
const rightAlign = !isHeader && isNumericText(cellText);
|
|
2092
|
+
const contentY = MARGIN + rowTopY + cellPadding;
|
|
2093
|
+
const effectiveHeader = isHeader && !borderless;
|
|
2094
|
+
renderCellContent(
|
|
2095
|
+
state,
|
|
2096
|
+
cell,
|
|
2097
|
+
cellX,
|
|
2098
|
+
contentY,
|
|
2099
|
+
cellW,
|
|
2100
|
+
state.fonts,
|
|
2101
|
+
fontSize,
|
|
2102
|
+
effectiveHeader,
|
|
2103
|
+
rightAlign,
|
|
2104
|
+
effectiveHeader ? headerTextColor : pdfLib.rgb(0, 0, 0)
|
|
2105
|
+
);
|
|
2106
|
+
cellX += cellW;
|
|
2107
|
+
}
|
|
2108
|
+
state.y += rowHeight;
|
|
2109
|
+
}
|
|
2110
|
+
if (hasHeader && tableRows.length > 0) {
|
|
2111
|
+
renderRow(tableRows[0], true);
|
|
2112
|
+
}
|
|
2113
|
+
let lastPageIndex = state.pageIndex;
|
|
2114
|
+
const startIdx = hasHeader ? 1 : 0;
|
|
2115
|
+
const bodyRowCount = tableRows.length - startIdx;
|
|
2116
|
+
for (let r = startIdx; r < tableRows.length; r++) {
|
|
2117
|
+
const cells = tableRows[r].content || [];
|
|
2118
|
+
let estRowHeight = lineHeight;
|
|
2119
|
+
for (let c = 0; c < cells.length && c < colCount; c++) {
|
|
2120
|
+
const cellContentWidth = colWidths[c] - cellPadding * 2;
|
|
2121
|
+
const cellH = measureCellHeight(cells[c], cellContentWidth, state.fonts, fontSize, false);
|
|
2122
|
+
estRowHeight = Math.max(estRowHeight, cellH);
|
|
2123
|
+
}
|
|
2124
|
+
estRowHeight += rowPadding;
|
|
2125
|
+
if (state.y + estRowHeight > CONTENT_HEIGHT) {
|
|
2126
|
+
ensureSpace(state, estRowHeight + (hasHeader ? lineHeight + rowPadding : 0));
|
|
2127
|
+
if (state.pageIndex !== lastPageIndex && hasHeader) {
|
|
2128
|
+
renderRow(headerRow, true);
|
|
2129
|
+
lastPageIndex = state.pageIndex;
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
const rowTopYBeforeRender = state.y;
|
|
2133
|
+
renderRow(tableRows[r], false);
|
|
2134
|
+
lastPageIndex = state.pageIndex;
|
|
2135
|
+
if (subtotals && colCount >= 2) {
|
|
2136
|
+
const bodyIdx = r - startIdx;
|
|
2137
|
+
const isFirstBody = bodyIdx === 0;
|
|
2138
|
+
const isLastBody = bodyIdx === bodyRowCount - 1;
|
|
2139
|
+
if (isFirstBody || isLastBody) {
|
|
2140
|
+
const valueStartX = region.leftX + colWidths[0];
|
|
2141
|
+
const valueEndX = region.leftX + colWidths.reduce((a, b) => a + b, 0);
|
|
2142
|
+
const pdfRowTopY = PAGE_HEIGHT - MARGIN - rowTopYBeforeRender;
|
|
2143
|
+
const overlineColor = pdfLib.rgb(0, 0, 0);
|
|
2144
|
+
if (isFirstBody) {
|
|
2145
|
+
state.currentPage.drawLine({
|
|
2146
|
+
start: { x: valueStartX, y: pdfRowTopY },
|
|
2147
|
+
end: { x: valueEndX, y: pdfRowTopY },
|
|
2148
|
+
thickness: 0.5,
|
|
2149
|
+
color: overlineColor
|
|
2150
|
+
});
|
|
2151
|
+
} else if (isLastBody) {
|
|
2152
|
+
state.currentPage.drawLine({
|
|
2153
|
+
start: { x: valueStartX, y: pdfRowTopY + 2 },
|
|
2154
|
+
end: { x: valueEndX, y: pdfRowTopY + 2 },
|
|
2155
|
+
thickness: 0.5,
|
|
2156
|
+
color: overlineColor
|
|
2157
|
+
});
|
|
2158
|
+
state.currentPage.drawLine({
|
|
2159
|
+
start: { x: valueStartX, y: pdfRowTopY },
|
|
2160
|
+
end: { x: valueEndX, y: pdfRowTopY },
|
|
2161
|
+
thickness: 0.5,
|
|
2162
|
+
color: overlineColor
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
state.y += 3;
|
|
2169
|
+
}
|
|
2170
|
+
function processBlock(state, node, region = DEFAULT_REGION) {
|
|
1186
2171
|
switch (node.type) {
|
|
1187
2172
|
case "heading": {
|
|
1188
2173
|
const level = node.attrs?.level || 1;
|
|
@@ -1199,34 +2184,34 @@ function processBlock(state, node) {
|
|
|
1199
2184
|
boldItalic: state.fonts.boldItalic
|
|
1200
2185
|
};
|
|
1201
2186
|
const segments = collectInlineSegments(node.content, headingFonts, fontSize);
|
|
1202
|
-
layoutInlineSegments(state, segments, fontSize);
|
|
2187
|
+
layoutInlineSegments(state, segments, fontSize, 0, region);
|
|
1203
2188
|
state.y += spacing / 2;
|
|
1204
2189
|
break;
|
|
1205
2190
|
}
|
|
1206
2191
|
case "paragraph": {
|
|
1207
|
-
const fontSize =
|
|
2192
|
+
const fontSize = BODY_FONT_SIZE;
|
|
1208
2193
|
if (!node.content || node.content.length === 0) {
|
|
1209
|
-
state.y += fontSize * LINE_HEIGHT_FACTOR;
|
|
2194
|
+
state.y += fontSize * LINE_HEIGHT_FACTOR * 0.65;
|
|
1210
2195
|
break;
|
|
1211
2196
|
}
|
|
1212
|
-
processInlineContent(state, node.content, fontSize);
|
|
1213
|
-
state.y +=
|
|
2197
|
+
processInlineContent(state, node.content, fontSize, 0, region);
|
|
2198
|
+
state.y += 2;
|
|
1214
2199
|
break;
|
|
1215
2200
|
}
|
|
1216
2201
|
case "bulletList": {
|
|
1217
2202
|
for (const item of node.content || []) {
|
|
1218
2203
|
for (const child of item.content || []) {
|
|
1219
|
-
const fontSize =
|
|
2204
|
+
const fontSize = BODY_FONT_SIZE;
|
|
1220
2205
|
ensureSpace(state, fontSize * LINE_HEIGHT_FACTOR);
|
|
1221
2206
|
const bulletPdfY = PAGE_HEIGHT - MARGIN - state.y - fontSize;
|
|
1222
2207
|
state.currentPage.drawText("\u2022", {
|
|
1223
|
-
x:
|
|
2208
|
+
x: region.leftX + 8,
|
|
1224
2209
|
y: bulletPdfY,
|
|
1225
2210
|
size: fontSize,
|
|
1226
2211
|
font: state.fonts.regular,
|
|
1227
2212
|
color: pdfLib.rgb(0, 0, 0)
|
|
1228
2213
|
});
|
|
1229
|
-
processInlineContent(state, child.content, fontSize, 24);
|
|
2214
|
+
processInlineContent(state, child.content, fontSize, 24, region);
|
|
1230
2215
|
}
|
|
1231
2216
|
}
|
|
1232
2217
|
state.y += 4;
|
|
@@ -1236,17 +2221,17 @@ function processBlock(state, node) {
|
|
|
1236
2221
|
let num = 1;
|
|
1237
2222
|
for (const item of node.content || []) {
|
|
1238
2223
|
for (const child of item.content || []) {
|
|
1239
|
-
const fontSize =
|
|
2224
|
+
const fontSize = BODY_FONT_SIZE;
|
|
1240
2225
|
ensureSpace(state, fontSize * LINE_HEIGHT_FACTOR);
|
|
1241
2226
|
const numPdfY = PAGE_HEIGHT - MARGIN - state.y - fontSize;
|
|
1242
2227
|
state.currentPage.drawText(`${num}.`, {
|
|
1243
|
-
x:
|
|
2228
|
+
x: region.leftX + 4,
|
|
1244
2229
|
y: numPdfY,
|
|
1245
2230
|
size: fontSize,
|
|
1246
2231
|
font: state.fonts.regular,
|
|
1247
2232
|
color: pdfLib.rgb(0, 0, 0)
|
|
1248
2233
|
});
|
|
1249
|
-
processInlineContent(state, child.content, fontSize, 24);
|
|
2234
|
+
processInlineContent(state, child.content, fontSize, 24, region);
|
|
1250
2235
|
}
|
|
1251
2236
|
num++;
|
|
1252
2237
|
}
|
|
@@ -1256,10 +2241,10 @@ function processBlock(state, node) {
|
|
|
1256
2241
|
case "blockquote": {
|
|
1257
2242
|
const startY = state.y;
|
|
1258
2243
|
for (const child of node.content || []) {
|
|
1259
|
-
processInlineContent(state, child.content, 12, 16);
|
|
2244
|
+
processInlineContent(state, child.content, 12, 16, region);
|
|
1260
2245
|
}
|
|
1261
2246
|
const endY = state.y;
|
|
1262
|
-
const barX =
|
|
2247
|
+
const barX = region.leftX + 4;
|
|
1263
2248
|
const barTop = PAGE_HEIGHT - MARGIN - startY;
|
|
1264
2249
|
const barBottom = PAGE_HEIGHT - MARGIN - endY;
|
|
1265
2250
|
state.currentPage.drawLine({
|
|
@@ -1272,16 +2257,16 @@ function processBlock(state, node) {
|
|
|
1272
2257
|
break;
|
|
1273
2258
|
}
|
|
1274
2259
|
case "horizontalRule": {
|
|
1275
|
-
ensureSpace(state,
|
|
1276
|
-
state.y +=
|
|
2260
|
+
ensureSpace(state, 10);
|
|
2261
|
+
state.y += 4;
|
|
1277
2262
|
const ruleY = PAGE_HEIGHT - MARGIN - state.y;
|
|
1278
2263
|
state.currentPage.drawLine({
|
|
1279
|
-
start: { x:
|
|
1280
|
-
end: { x:
|
|
1281
|
-
thickness:
|
|
1282
|
-
color:
|
|
2264
|
+
start: { x: region.leftX, y: ruleY },
|
|
2265
|
+
end: { x: region.leftX + region.width, y: ruleY },
|
|
2266
|
+
thickness: 2.5,
|
|
2267
|
+
color: state.theme.rule
|
|
1283
2268
|
});
|
|
1284
|
-
state.y +=
|
|
2269
|
+
state.y += 4;
|
|
1285
2270
|
break;
|
|
1286
2271
|
}
|
|
1287
2272
|
case "codeBlock": {
|
|
@@ -1293,9 +2278,9 @@ function processBlock(state, node) {
|
|
|
1293
2278
|
ensureSpace(state, blockHeight);
|
|
1294
2279
|
const boxY = PAGE_HEIGHT - MARGIN - state.y - blockHeight;
|
|
1295
2280
|
state.currentPage.drawRectangle({
|
|
1296
|
-
x:
|
|
2281
|
+
x: region.leftX,
|
|
1297
2282
|
y: boxY,
|
|
1298
|
-
width:
|
|
2283
|
+
width: region.width,
|
|
1299
2284
|
height: blockHeight,
|
|
1300
2285
|
color: pdfLib.rgb(0.95, 0.95, 0.95),
|
|
1301
2286
|
borderColor: pdfLib.rgb(0.85, 0.85, 0.85),
|
|
@@ -1303,18 +2288,325 @@ function processBlock(state, node) {
|
|
|
1303
2288
|
});
|
|
1304
2289
|
state.y += 8;
|
|
1305
2290
|
for (const line of lines) {
|
|
1306
|
-
drawText(state, line || " ", state.fonts.regular, fontSize, 8);
|
|
2291
|
+
drawText(state, line || " ", state.fonts.regular, fontSize, 8, region);
|
|
1307
2292
|
}
|
|
1308
2293
|
state.y += 8;
|
|
1309
2294
|
break;
|
|
1310
2295
|
}
|
|
2296
|
+
case "table": {
|
|
2297
|
+
renderTable(state, node, region);
|
|
2298
|
+
break;
|
|
2299
|
+
}
|
|
2300
|
+
case "imageNode": {
|
|
2301
|
+
const src = node.attrs?.src || "";
|
|
2302
|
+
const image = src ? state.images.get(src) : void 0;
|
|
2303
|
+
if (!image) break;
|
|
2304
|
+
const natW = image.width;
|
|
2305
|
+
const natH = image.height;
|
|
2306
|
+
let w = parseFloat(node.attrs?.width || "") || 0;
|
|
2307
|
+
let h = parseFloat(node.attrs?.height || "") || 0;
|
|
2308
|
+
if (w && !h) h = natH / natW * w;
|
|
2309
|
+
else if (h && !w) w = natW / natH * h;
|
|
2310
|
+
else if (!w && !h) {
|
|
2311
|
+
w = natW;
|
|
2312
|
+
h = natH;
|
|
2313
|
+
}
|
|
2314
|
+
if (w > region.width) {
|
|
2315
|
+
h = h * (region.width / w);
|
|
2316
|
+
w = region.width;
|
|
2317
|
+
}
|
|
2318
|
+
const maxH = CONTENT_HEIGHT * 0.6;
|
|
2319
|
+
if (h > maxH) {
|
|
2320
|
+
w = w * (maxH / h);
|
|
2321
|
+
h = maxH;
|
|
2322
|
+
}
|
|
2323
|
+
ensureSpace(state, h + 4);
|
|
2324
|
+
const align = node.attrs?.align || "left";
|
|
2325
|
+
let x = region.leftX;
|
|
2326
|
+
if (align === "center") x = region.leftX + (region.width - w) / 2;
|
|
2327
|
+
else if (align === "right") x = region.leftX + region.width - w;
|
|
2328
|
+
const pdfY = PAGE_HEIGHT - MARGIN - state.y - h;
|
|
2329
|
+
state.currentPage.drawImage(image, { x, y: pdfY, width: w, height: h });
|
|
2330
|
+
state.y += h + 4;
|
|
2331
|
+
break;
|
|
2332
|
+
}
|
|
2333
|
+
// ─── Layout directives ─────────────────────────────────────────
|
|
2334
|
+
case "panel": {
|
|
2335
|
+
const title = node.attrs?.title || "";
|
|
2336
|
+
const border = node.attrs?.border || "solid";
|
|
2337
|
+
const headerStyle = node.attrs?.headerStyle || "";
|
|
2338
|
+
const padding = 8;
|
|
2339
|
+
const titleFontSize = 10;
|
|
2340
|
+
const titleHeight = title ? titleFontSize * LINE_HEIGHT_FACTOR + 6 : 0;
|
|
2341
|
+
const panelStartY = state.y;
|
|
2342
|
+
const panelStartPage = state.pageIndex;
|
|
2343
|
+
if (title) {
|
|
2344
|
+
state.y += titleHeight;
|
|
2345
|
+
}
|
|
2346
|
+
const isHeaderOnly = border === "none";
|
|
2347
|
+
if (!isHeaderOnly) {
|
|
2348
|
+
state.y += padding;
|
|
2349
|
+
}
|
|
2350
|
+
const innerRegion = {
|
|
2351
|
+
leftX: region.leftX + padding,
|
|
2352
|
+
width: region.width - padding * 2
|
|
2353
|
+
};
|
|
2354
|
+
if (!isHeaderOnly) {
|
|
2355
|
+
for (const child of node.content || []) {
|
|
2356
|
+
processBlock(state, child, innerRegion);
|
|
2357
|
+
}
|
|
2358
|
+
state.y += padding;
|
|
2359
|
+
}
|
|
2360
|
+
const startPage = state.pdfDoc.getPages()[panelStartPage];
|
|
2361
|
+
if (title) {
|
|
2362
|
+
const titleBarY = PAGE_HEIGHT - MARGIN - panelStartY - titleHeight;
|
|
2363
|
+
const isDarkHeader = headerStyle === "dark";
|
|
2364
|
+
startPage.drawRectangle({
|
|
2365
|
+
x: region.leftX,
|
|
2366
|
+
y: titleBarY,
|
|
2367
|
+
width: region.width,
|
|
2368
|
+
height: titleHeight,
|
|
2369
|
+
color: isDarkHeader ? state.theme.panelDarkHeaderBg : pdfLib.rgb(0.93, 0.93, 0.93),
|
|
2370
|
+
borderColor: state.theme.panelBorder,
|
|
2371
|
+
borderWidth: 0.75
|
|
2372
|
+
});
|
|
2373
|
+
startPage.drawText(title, {
|
|
2374
|
+
x: region.leftX + padding,
|
|
2375
|
+
y: titleBarY + 4,
|
|
2376
|
+
size: titleFontSize,
|
|
2377
|
+
font: state.fonts.bold,
|
|
2378
|
+
color: isDarkHeader ? state.theme.panelDarkHeaderText : pdfLib.rgb(0, 0, 0)
|
|
2379
|
+
});
|
|
2380
|
+
}
|
|
2381
|
+
if (border !== "none") {
|
|
2382
|
+
const borderDashArray = border === "dashed" ? [4, 2] : void 0;
|
|
2383
|
+
if (panelStartPage === state.pageIndex) {
|
|
2384
|
+
const borderY = PAGE_HEIGHT - MARGIN - state.y;
|
|
2385
|
+
const panelHeight = state.y - panelStartY;
|
|
2386
|
+
startPage.drawRectangle({
|
|
2387
|
+
x: region.leftX,
|
|
2388
|
+
y: borderY,
|
|
2389
|
+
width: region.width,
|
|
2390
|
+
height: panelHeight,
|
|
2391
|
+
borderColor: state.theme.panelBorder,
|
|
2392
|
+
borderWidth: 0.75,
|
|
2393
|
+
borderDashArray
|
|
2394
|
+
});
|
|
2395
|
+
} else {
|
|
2396
|
+
const startBorderBottom = MARGIN;
|
|
2397
|
+
const startPanelHeight = PAGE_HEIGHT - MARGIN - panelStartY - startBorderBottom;
|
|
2398
|
+
startPage.drawRectangle({
|
|
2399
|
+
x: region.leftX,
|
|
2400
|
+
y: startBorderBottom,
|
|
2401
|
+
width: region.width,
|
|
2402
|
+
height: startPanelHeight,
|
|
2403
|
+
borderColor: state.theme.panelBorder,
|
|
2404
|
+
borderWidth: 0.75,
|
|
2405
|
+
borderDashArray
|
|
2406
|
+
});
|
|
2407
|
+
const contTop = PAGE_HEIGHT - MARGIN;
|
|
2408
|
+
const contBorderY = PAGE_HEIGHT - MARGIN - state.y;
|
|
2409
|
+
const contHeight = contTop - contBorderY;
|
|
2410
|
+
state.currentPage.drawRectangle({
|
|
2411
|
+
x: region.leftX,
|
|
2412
|
+
y: contBorderY,
|
|
2413
|
+
width: region.width,
|
|
2414
|
+
height: contHeight,
|
|
2415
|
+
borderColor: state.theme.panelBorder,
|
|
2416
|
+
borderWidth: 0.75,
|
|
2417
|
+
borderDashArray
|
|
2418
|
+
});
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
state.y += 4;
|
|
2422
|
+
break;
|
|
2423
|
+
}
|
|
2424
|
+
case "columns": {
|
|
2425
|
+
const split = parseInt(node.attrs?.split || "50", 10);
|
|
2426
|
+
const padX = parseFloat(node.attrs?.padX || "0") || 0;
|
|
2427
|
+
const columns = (node.content || []).filter((c) => c.type === "column");
|
|
2428
|
+
if (columns.length === 0) break;
|
|
2429
|
+
const colRegionLeftX = region.leftX + padX;
|
|
2430
|
+
const colRegionWidth = region.width - 2 * padX;
|
|
2431
|
+
const gap = 12;
|
|
2432
|
+
const columnsStartY = state.y;
|
|
2433
|
+
const columnsStartPage = state.pageIndex;
|
|
2434
|
+
let colWidths;
|
|
2435
|
+
if (columns.length === 2) {
|
|
2436
|
+
const firstWidth = (colRegionWidth - gap) * (split / 100);
|
|
2437
|
+
const secondWidth = colRegionWidth - gap - firstWidth;
|
|
2438
|
+
colWidths = [firstWidth, secondWidth];
|
|
2439
|
+
} else {
|
|
2440
|
+
const eachWidth = (colRegionWidth - gap * (columns.length - 1)) / columns.length;
|
|
2441
|
+
colWidths = columns.map(() => eachWidth);
|
|
2442
|
+
}
|
|
2443
|
+
let maxEndY = columnsStartY;
|
|
2444
|
+
let colX = colRegionLeftX;
|
|
2445
|
+
for (let ci = 0; ci < columns.length; ci++) {
|
|
2446
|
+
const col = columns[ci];
|
|
2447
|
+
const colWidth = colWidths[ci];
|
|
2448
|
+
const colRegion = { leftX: colX, width: colWidth };
|
|
2449
|
+
state.y = columnsStartY;
|
|
2450
|
+
state.pageIndex = columnsStartPage;
|
|
2451
|
+
state.currentPage = state.pdfDoc.getPages()[state.pageIndex];
|
|
2452
|
+
const padTop = parseFloat(col.attrs?.padTop || "0") || 0;
|
|
2453
|
+
if (padTop > 0) {
|
|
2454
|
+
state.y += padTop * BODY_FONT_SIZE * LINE_HEIGHT_FACTOR;
|
|
2455
|
+
}
|
|
2456
|
+
for (const child of col.content || []) {
|
|
2457
|
+
processBlock(state, child, colRegion);
|
|
2458
|
+
}
|
|
2459
|
+
maxEndY = Math.max(maxEndY, state.y);
|
|
2460
|
+
colX += colWidth + gap;
|
|
2461
|
+
}
|
|
2462
|
+
state.y = maxEndY;
|
|
2463
|
+
state.y += 4;
|
|
2464
|
+
break;
|
|
2465
|
+
}
|
|
2466
|
+
case "column": {
|
|
2467
|
+
for (const child of node.content || []) {
|
|
2468
|
+
processBlock(state, child, region);
|
|
2469
|
+
}
|
|
2470
|
+
break;
|
|
2471
|
+
}
|
|
2472
|
+
case "watermark": {
|
|
2473
|
+
break;
|
|
2474
|
+
}
|
|
2475
|
+
case "subtotalsBlock": {
|
|
2476
|
+
const stFontSize = BODY_FONT_SIZE;
|
|
2477
|
+
const stLineHeight = stFontSize * LINE_HEIGHT_FACTOR;
|
|
2478
|
+
const valueRightX = region.leftX + region.width;
|
|
2479
|
+
const rows = (node.content || []).filter(
|
|
2480
|
+
(c) => c.type === "paragraph" && c.content?.length
|
|
2481
|
+
);
|
|
2482
|
+
const parsed = [];
|
|
2483
|
+
for (const row of rows) {
|
|
2484
|
+
let label = "";
|
|
2485
|
+
let value = "";
|
|
2486
|
+
for (const seg of row.content || []) {
|
|
2487
|
+
if (seg.type === "text") {
|
|
2488
|
+
const isBold = seg.marks?.some((m) => m.type === "bold");
|
|
2489
|
+
if (isBold) {
|
|
2490
|
+
label += seg.text || "";
|
|
2491
|
+
} else {
|
|
2492
|
+
const t = (seg.text || "").trim();
|
|
2493
|
+
if (t) value += (value ? " " : "") + t;
|
|
2494
|
+
}
|
|
2495
|
+
} else if (seg.type === "variableNode") {
|
|
2496
|
+
const varLabel = seg.attrs?.varLabel || seg.attrs?.varName || "";
|
|
2497
|
+
const isBoldVar = seg.marks?.some((m) => m.type === "bold");
|
|
2498
|
+
if (isBoldVar) {
|
|
2499
|
+
label += varLabel;
|
|
2500
|
+
} else {
|
|
2501
|
+
value += (value ? " " : "") + `[${varLabel}]`;
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
parsed.push({ label: label.trim(), value: value.trim() });
|
|
2506
|
+
}
|
|
2507
|
+
let maxLabelWidth = 0;
|
|
2508
|
+
for (const { label } of parsed) {
|
|
2509
|
+
if (label) {
|
|
2510
|
+
const w = state.fonts.bold.widthOfTextAtSize(label, stFontSize);
|
|
2511
|
+
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
const gap = 12;
|
|
2515
|
+
const labelX = region.width > 300 ? Math.max(region.leftX + region.width - maxLabelWidth - gap - 80, region.leftX + region.width * 0.4) : region.leftX + 4;
|
|
2516
|
+
const valueColStartX = labelX + maxLabelWidth + gap;
|
|
2517
|
+
const isLastRowTotal = parsed.length > 1;
|
|
2518
|
+
for (let ri = 0; ri < parsed.length; ri++) {
|
|
2519
|
+
const isTotal = isLastRowTotal && ri === parsed.length - 1;
|
|
2520
|
+
if (isTotal) {
|
|
2521
|
+
state.y += stLineHeight * 0.6;
|
|
2522
|
+
}
|
|
2523
|
+
ensureSpace(state, stLineHeight + (isTotal ? 8 : 4));
|
|
2524
|
+
const { label, value } = parsed[ri];
|
|
2525
|
+
const pdfY = PAGE_HEIGHT - MARGIN - state.y - stFontSize;
|
|
2526
|
+
if (isTotal) {
|
|
2527
|
+
state.currentPage.drawLine({
|
|
2528
|
+
start: { x: valueColStartX, y: pdfY + stFontSize + 6 },
|
|
2529
|
+
end: { x: valueRightX, y: pdfY + stFontSize + 6 },
|
|
2530
|
+
thickness: 0.75,
|
|
2531
|
+
color: pdfLib.rgb(0, 0, 0)
|
|
2532
|
+
});
|
|
2533
|
+
state.currentPage.drawLine({
|
|
2534
|
+
start: { x: valueColStartX, y: pdfY + stFontSize + 3 },
|
|
2535
|
+
end: { x: valueRightX, y: pdfY + stFontSize + 3 },
|
|
2536
|
+
thickness: 0.75,
|
|
2537
|
+
color: pdfLib.rgb(0, 0, 0)
|
|
2538
|
+
});
|
|
2539
|
+
}
|
|
2540
|
+
if (label) {
|
|
2541
|
+
state.currentPage.drawText(label, {
|
|
2542
|
+
x: labelX,
|
|
2543
|
+
y: pdfY,
|
|
2544
|
+
size: stFontSize,
|
|
2545
|
+
font: state.fonts.bold,
|
|
2546
|
+
color: pdfLib.rgb(0, 0, 0)
|
|
2547
|
+
});
|
|
2548
|
+
}
|
|
2549
|
+
if (value) {
|
|
2550
|
+
const valueFont = state.fonts.bold;
|
|
2551
|
+
const valueWidth = valueFont.widthOfTextAtSize(value, stFontSize);
|
|
2552
|
+
state.currentPage.drawText(value, {
|
|
2553
|
+
x: valueRightX - valueWidth,
|
|
2554
|
+
y: pdfY,
|
|
2555
|
+
size: stFontSize,
|
|
2556
|
+
font: valueFont,
|
|
2557
|
+
color: pdfLib.rgb(0, 0, 0)
|
|
2558
|
+
});
|
|
2559
|
+
}
|
|
2560
|
+
state.y += stLineHeight;
|
|
2561
|
+
}
|
|
2562
|
+
state.y += 4;
|
|
2563
|
+
break;
|
|
2564
|
+
}
|
|
2565
|
+
case "repeatBlock":
|
|
2566
|
+
if (node.content) {
|
|
2567
|
+
for (const child of node.content) {
|
|
2568
|
+
processBlock(state, child, region);
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
break;
|
|
1311
2572
|
default:
|
|
1312
2573
|
if (node.content) {
|
|
1313
|
-
processInlineContent(state, node.content, 12);
|
|
2574
|
+
processInlineContent(state, node.content, 12, 0, region);
|
|
1314
2575
|
}
|
|
1315
2576
|
break;
|
|
1316
2577
|
}
|
|
1317
2578
|
}
|
|
2579
|
+
function renderWatermarksOnPage(page, fonts, watermarkNodes) {
|
|
2580
|
+
for (const wmNode of watermarkNodes) {
|
|
2581
|
+
const text = wmNode.attrs?.text || "";
|
|
2582
|
+
if (!text) continue;
|
|
2583
|
+
const opacity = parseFloat(wmNode.attrs?.opacity || "0.15");
|
|
2584
|
+
const angle = parseFloat(wmNode.attrs?.angle || "-45");
|
|
2585
|
+
const fontSize = 100;
|
|
2586
|
+
const { width, height } = page.getSize();
|
|
2587
|
+
const centerX = width / 2;
|
|
2588
|
+
const centerY = height / 2;
|
|
2589
|
+
const textWidth = fonts.bold.widthOfTextAtSize(text, fontSize);
|
|
2590
|
+
page.drawText(text, {
|
|
2591
|
+
x: centerX - textWidth / 2 * Math.cos(angle * Math.PI / 180),
|
|
2592
|
+
y: centerY - textWidth / 2 * Math.sin(angle * Math.PI / 180),
|
|
2593
|
+
size: fontSize,
|
|
2594
|
+
font: fonts.bold,
|
|
2595
|
+
color: pdfLib.rgb(0.5, 0.5, 0.5),
|
|
2596
|
+
opacity,
|
|
2597
|
+
rotate: pdfLib.degrees(angle)
|
|
2598
|
+
});
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
function collectWatermarkNodes(content) {
|
|
2602
|
+
const nodes = [];
|
|
2603
|
+
for (const block of content.content || []) {
|
|
2604
|
+
if (block.type === "watermark") {
|
|
2605
|
+
nodes.push(block);
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
return nodes;
|
|
2609
|
+
}
|
|
1318
2610
|
function ensureFieldSuffix(fieldName, suffix) {
|
|
1319
2611
|
const cleanName = fieldName.replace(/_signature$/i, "").replace(/_initials$/i, "").replace(/_date$/i, "");
|
|
1320
2612
|
return `${cleanName}${suffix}`;
|
|
@@ -1424,6 +2716,24 @@ async function addFormFields(pdfDoc, fieldPositions, fields) {
|
|
|
1424
2716
|
pdfLib.PDFString.of(field.label)
|
|
1425
2717
|
);
|
|
1426
2718
|
}
|
|
2719
|
+
const initialsFontSize = Math.min(
|
|
2720
|
+
72,
|
|
2721
|
+
Math.max(8, Math.round(position.height * 0.55))
|
|
2722
|
+
);
|
|
2723
|
+
try {
|
|
2724
|
+
initialsField.setFontSize(initialsFontSize);
|
|
2725
|
+
const acro = initialsField;
|
|
2726
|
+
const daRef = acro.acroField.dict.get(pdfLib.PDFName.of("DA"));
|
|
2727
|
+
if (daRef) {
|
|
2728
|
+
const daStr = daRef.value;
|
|
2729
|
+
if (typeof daStr === "string") {
|
|
2730
|
+
const newDa = daStr.replace(/\/Helv\b/, "/TiBo").replace(/\/HeBo\b/, "/TiBo");
|
|
2731
|
+
acro.acroField.dict.set(pdfLib.PDFName.of("DA"), pdfLib.PDFString.of(newDa));
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
} catch (fontError) {
|
|
2735
|
+
console.warn(`Failed to set initials font:`, fontError);
|
|
2736
|
+
}
|
|
1427
2737
|
initialsField.enableReadOnly();
|
|
1428
2738
|
if (field.required) initialsField.enableRequired();
|
|
1429
2739
|
break;
|
|
@@ -1527,47 +2837,367 @@ async function addFormFields(pdfDoc, fieldPositions, fields) {
|
|
|
1527
2837
|
} catch (err) {
|
|
1528
2838
|
warnings.push(`Failed to create ${field.type} field "${field.name}": ${getErrorMessage(err)}`);
|
|
1529
2839
|
}
|
|
1530
|
-
}
|
|
1531
|
-
initPdfMetadata({ PDFName: pdfLib.PDFName, PDFString: pdfLib.PDFString });
|
|
1532
|
-
const metadata = buildMetadataObject(fields, actualFieldNames);
|
|
1533
|
-
setSigniphiMetadata(pdfDoc, metadata);
|
|
1534
|
-
return warnings;
|
|
1535
|
-
}
|
|
1536
|
-
async function generatePdfFromContent(content, options = {}) {
|
|
1537
|
-
const { drawFieldPlaceholders = false, embedFormFields = false, fields = [] } = options;
|
|
1538
|
-
const
|
|
1539
|
-
const
|
|
1540
|
-
const
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
2840
|
+
}
|
|
2841
|
+
initPdfMetadata({ PDFName: pdfLib.PDFName, PDFString: pdfLib.PDFString });
|
|
2842
|
+
const metadata = buildMetadataObject(fields, actualFieldNames);
|
|
2843
|
+
setSigniphiMetadata(pdfDoc, metadata);
|
|
2844
|
+
return warnings;
|
|
2845
|
+
}
|
|
2846
|
+
async function generatePdfFromContent(content, options = {}) {
|
|
2847
|
+
const { drawFieldPlaceholders = false, embedFormFields = false, fields = [], header, footer } = options;
|
|
2848
|
+
const theme = resolveTheme(options.theme);
|
|
2849
|
+
const pdfDoc = await pdfLib.PDFDocument.create();
|
|
2850
|
+
const firstPage = pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
|
|
2851
|
+
const fonts = {
|
|
2852
|
+
regular: await pdfDoc.embedFont(pdfLib.StandardFonts.Helvetica),
|
|
2853
|
+
bold: await pdfDoc.embedFont(pdfLib.StandardFonts.HelveticaBold),
|
|
2854
|
+
italic: await pdfDoc.embedFont(pdfLib.StandardFonts.HelveticaOblique),
|
|
2855
|
+
boldItalic: await pdfDoc.embedFont(pdfLib.StandardFonts.HelveticaBoldOblique)
|
|
2856
|
+
};
|
|
2857
|
+
const watermarkNodes = collectWatermarkNodes(content);
|
|
2858
|
+
const { images, warnings: imageWarnings } = await preloadImages(pdfDoc, content);
|
|
2859
|
+
const state = {
|
|
2860
|
+
currentPage: firstPage,
|
|
2861
|
+
pageIndex: 0,
|
|
2862
|
+
y: 0,
|
|
2863
|
+
fonts,
|
|
2864
|
+
pdfDoc,
|
|
2865
|
+
fieldPositions: /* @__PURE__ */ new Map(),
|
|
2866
|
+
drawFieldPlaceholders,
|
|
2867
|
+
watermarkNodes,
|
|
2868
|
+
theme,
|
|
2869
|
+
images
|
|
2870
|
+
};
|
|
2871
|
+
renderWatermarksOnPage(firstPage, fonts, watermarkNodes);
|
|
2872
|
+
if (content.content) {
|
|
2873
|
+
for (const block of content.content) {
|
|
2874
|
+
processBlock(state, block);
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
const allPages = pdfDoc.getPages();
|
|
2878
|
+
const totalPages = allPages.length;
|
|
2879
|
+
const pageNumFontSize = 9;
|
|
2880
|
+
const hfFontSize = 8;
|
|
2881
|
+
const hfColor = pdfLib.rgb(0.45, 0.45, 0.45);
|
|
2882
|
+
for (let i = 0; i < totalPages; i++) {
|
|
2883
|
+
const page = allPages[i];
|
|
2884
|
+
const label = `Page ${i + 1} of ${totalPages}`;
|
|
2885
|
+
const labelWidth = fonts.bold.widthOfTextAtSize(label, pageNumFontSize);
|
|
2886
|
+
page.drawText(label, {
|
|
2887
|
+
x: PAGE_WIDTH - MARGIN - labelWidth,
|
|
2888
|
+
y: MARGIN / 2 - pageNumFontSize / 2,
|
|
2889
|
+
size: pageNumFontSize,
|
|
2890
|
+
font: fonts.bold,
|
|
2891
|
+
color: pdfLib.rgb(0, 0, 0)
|
|
2892
|
+
});
|
|
2893
|
+
if (header && (header.left || header.right) && !(header.skipFirstPage && i === 0)) {
|
|
2894
|
+
const headerBaseY = PAGE_HEIGHT - MARGIN + 10;
|
|
2895
|
+
if (header.left) {
|
|
2896
|
+
page.drawText(header.left, {
|
|
2897
|
+
x: MARGIN,
|
|
2898
|
+
y: headerBaseY,
|
|
2899
|
+
size: pageNumFontSize,
|
|
2900
|
+
font: fonts.bold,
|
|
2901
|
+
color: theme.pageHeaderText
|
|
2902
|
+
});
|
|
2903
|
+
}
|
|
2904
|
+
if (header.right) {
|
|
2905
|
+
const rightWidth = fonts.regular.widthOfTextAtSize(header.right, hfFontSize);
|
|
2906
|
+
page.drawText(header.right, {
|
|
2907
|
+
x: PAGE_WIDTH - MARGIN - rightWidth,
|
|
2908
|
+
y: headerBaseY,
|
|
2909
|
+
size: hfFontSize,
|
|
2910
|
+
font: fonts.regular,
|
|
2911
|
+
color: hfColor
|
|
2912
|
+
});
|
|
2913
|
+
}
|
|
2914
|
+
page.drawLine({
|
|
2915
|
+
start: { x: MARGIN, y: headerBaseY - 6 },
|
|
2916
|
+
end: { x: PAGE_WIDTH - MARGIN, y: headerBaseY - 6 },
|
|
2917
|
+
thickness: 0.75,
|
|
2918
|
+
color: theme.pageHeaderRule
|
|
2919
|
+
});
|
|
2920
|
+
}
|
|
2921
|
+
if (footer?.left) {
|
|
2922
|
+
page.drawText(footer.left, {
|
|
2923
|
+
x: MARGIN,
|
|
2924
|
+
y: MARGIN / 2 - pageNumFontSize / 2,
|
|
2925
|
+
size: hfFontSize,
|
|
2926
|
+
font: fonts.regular,
|
|
2927
|
+
color: hfColor
|
|
2928
|
+
});
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
let fieldWarnings;
|
|
2932
|
+
if (embedFormFields && fields.length > 0) {
|
|
2933
|
+
const warnings = await addFormFields(pdfDoc, state.fieldPositions, fields);
|
|
2934
|
+
if (warnings.length > 0) fieldWarnings = warnings;
|
|
2935
|
+
}
|
|
2936
|
+
const pdfBytes = new Uint8Array(await pdfDoc.save({ useObjectStreams: false }));
|
|
2937
|
+
return {
|
|
2938
|
+
pdfBytes,
|
|
2939
|
+
fieldPositions: state.fieldPositions,
|
|
2940
|
+
fieldWarnings,
|
|
2941
|
+
imageWarnings: imageWarnings.length > 0 ? imageWarnings : void 0
|
|
2942
|
+
};
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2945
|
+
// src/utils/template-pipeline.ts
|
|
2946
|
+
async function generatePdfFromTiptap(content, values, options = {}) {
|
|
2947
|
+
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, values);
|
|
2948
|
+
const blockExpanded = expandBlockVariables(expanded, enrichedValues);
|
|
2949
|
+
const suppressed = suppressZeroContent(blockExpanded, enrichedValues);
|
|
2950
|
+
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
2951
|
+
const fields = extractFieldsFromContent(replaced);
|
|
2952
|
+
const result = await generatePdfFromContent(replaced, {
|
|
2953
|
+
embedFormFields: fields.length > 0,
|
|
2954
|
+
fields,
|
|
2955
|
+
theme: options.theme,
|
|
2956
|
+
header: options.header,
|
|
2957
|
+
footer: options.footer,
|
|
2958
|
+
drawFieldPlaceholders: options.drawFieldPlaceholders
|
|
2959
|
+
});
|
|
2960
|
+
return { pdfBytes: result.pdfBytes, imageWarnings: result.imageWarnings };
|
|
2961
|
+
}
|
|
2962
|
+
async function generatePdfFromMarkdown(markdown, values, options = {}) {
|
|
2963
|
+
const content = markdownToTiptap(markdown);
|
|
2964
|
+
return generatePdfFromTiptap(content, values, options);
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2967
|
+
// src/utils/markdown-writer.ts
|
|
2968
|
+
function serializeFieldToken(attrs) {
|
|
2969
|
+
const parts = ["field"];
|
|
2970
|
+
if (attrs.fieldType) parts.push(`type:${attrs.fieldType}`);
|
|
2971
|
+
if (attrs.fieldName) parts.push(`name:${attrs.fieldName}`);
|
|
2972
|
+
if (attrs.fieldLabel) parts.push(`label:${attrs.fieldLabel}`);
|
|
2973
|
+
if (attrs.fieldId) parts.push(`id:${attrs.fieldId}`);
|
|
2974
|
+
if (attrs.required) parts.push(`required:true`);
|
|
2975
|
+
if (attrs.options) parts.push(`options:${attrs.options}`);
|
|
2976
|
+
if (attrs.fontSize) parts.push(`fontSize:${attrs.fontSize}`);
|
|
2977
|
+
if (attrs.placeholder) parts.push(`placeholder:${attrs.placeholder}`);
|
|
2978
|
+
if (attrs.defaultValue) parts.push(`defaultValue:${attrs.defaultValue}`);
|
|
2979
|
+
if (attrs.multiline) parts.push(`multiline:true`);
|
|
2980
|
+
if (attrs.maxLength) parts.push(`maxLength:${attrs.maxLength}`);
|
|
2981
|
+
if (attrs.acknowledgements) parts.push(`acks:${btoa(attrs.acknowledgements)}`);
|
|
2982
|
+
return `{{${parts.join("|")}}}`;
|
|
2983
|
+
}
|
|
2984
|
+
function serializeImageNode(attrs) {
|
|
2985
|
+
const hasExtras = attrs.varName || attrs.width || attrs.height || attrs.align;
|
|
2986
|
+
if (!hasExtras && attrs.src) {
|
|
2987
|
+
return ``;
|
|
2988
|
+
}
|
|
2989
|
+
const parts = ["image"];
|
|
2990
|
+
if (attrs.varName) parts.push(`var:${attrs.varName}`);
|
|
2991
|
+
else if (attrs.src) parts.push(`src:${attrs.src}`);
|
|
2992
|
+
if (attrs.alt) parts.push(`alt:${attrs.alt}`);
|
|
2993
|
+
if (attrs.width) parts.push(`width:${attrs.width}`);
|
|
2994
|
+
if (attrs.height) parts.push(`height:${attrs.height}`);
|
|
2995
|
+
if (attrs.align) parts.push(`align:${attrs.align}`);
|
|
2996
|
+
return `{{${parts.join("|")}}}`;
|
|
2997
|
+
}
|
|
2998
|
+
function serializeVariableToken(attrs) {
|
|
2999
|
+
const parts = ["var"];
|
|
3000
|
+
if (attrs.varName) parts.push(`name:${attrs.varName}`);
|
|
3001
|
+
if (attrs.varLabel) parts.push(`label:${attrs.varLabel}`);
|
|
3002
|
+
if (attrs.varDefault) parts.push(`default:${attrs.varDefault}`);
|
|
3003
|
+
if (attrs.suppressZero === "true") parts.push(`suppress:zero`);
|
|
3004
|
+
if (attrs.format) parts.push(`format:${attrs.format}`);
|
|
3005
|
+
if (attrs.block === "true") parts.push(`block:true`);
|
|
3006
|
+
return `{{${parts.join("|")}}}`;
|
|
3007
|
+
}
|
|
3008
|
+
function serializeInline(content) {
|
|
3009
|
+
if (!content) return "";
|
|
3010
|
+
let result = "";
|
|
3011
|
+
for (const node of content) {
|
|
3012
|
+
if (node.type === "fieldNode") {
|
|
3013
|
+
result += serializeFieldToken(node.attrs || {});
|
|
3014
|
+
continue;
|
|
3015
|
+
}
|
|
3016
|
+
if (node.type === "imageNode") {
|
|
3017
|
+
result += serializeImageNode(node.attrs || {});
|
|
3018
|
+
continue;
|
|
3019
|
+
}
|
|
3020
|
+
if (node.type === "variableNode") {
|
|
3021
|
+
let token = serializeVariableToken(node.attrs || {});
|
|
3022
|
+
const varMarks = node.marks || [];
|
|
3023
|
+
const isBold = varMarks.some((m) => m.type === "bold");
|
|
3024
|
+
const isItalic = varMarks.some((m) => m.type === "italic");
|
|
3025
|
+
const isUnderline = varMarks.some((m) => m.type === "underline");
|
|
3026
|
+
if (isBold && isItalic) token = `***${token}***`;
|
|
3027
|
+
else if (isBold) token = `**${token}**`;
|
|
3028
|
+
else if (isItalic) token = `*${token}*`;
|
|
3029
|
+
if (isUnderline) token = `__${token}__`;
|
|
3030
|
+
result += token;
|
|
3031
|
+
continue;
|
|
3032
|
+
}
|
|
3033
|
+
if (node.type === "text") {
|
|
3034
|
+
let text = node.text || "";
|
|
3035
|
+
const marks = node.marks || [];
|
|
3036
|
+
for (const mark of marks) {
|
|
3037
|
+
if (mark.type === "bold") text = `**${text}**`;
|
|
3038
|
+
else if (mark.type === "italic") text = `*${text}*`;
|
|
3039
|
+
else if (mark.type === "underline") text = `__${text}__`;
|
|
3040
|
+
else if (mark.type === "code") text = `\`${text}\``;
|
|
3041
|
+
}
|
|
3042
|
+
result += text;
|
|
3043
|
+
}
|
|
3044
|
+
if (node.type === "hardBreak") {
|
|
3045
|
+
result += " \n";
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
return result;
|
|
3049
|
+
}
|
|
3050
|
+
function serializeBlock(node) {
|
|
3051
|
+
switch (node.type) {
|
|
3052
|
+
case "heading": {
|
|
3053
|
+
const level = node.attrs?.level || 1;
|
|
3054
|
+
const prefix = "#".repeat(level);
|
|
3055
|
+
return `${prefix} ${serializeInline(node.content)}`;
|
|
3056
|
+
}
|
|
3057
|
+
case "paragraph": {
|
|
3058
|
+
const inline = serializeInline(node.content);
|
|
3059
|
+
return inline;
|
|
3060
|
+
}
|
|
3061
|
+
case "bulletList": {
|
|
3062
|
+
return (node.content || []).map((item) => {
|
|
3063
|
+
const inner = (item.content || []).map(serializeBlock).join("\n");
|
|
3064
|
+
return `- ${inner}`;
|
|
3065
|
+
}).join("\n");
|
|
3066
|
+
}
|
|
3067
|
+
case "orderedList": {
|
|
3068
|
+
return (node.content || []).map((item, i) => {
|
|
3069
|
+
const inner = (item.content || []).map(serializeBlock).join("\n");
|
|
3070
|
+
return `${i + 1}. ${inner}`;
|
|
3071
|
+
}).join("\n");
|
|
3072
|
+
}
|
|
3073
|
+
case "listItem": {
|
|
3074
|
+
return (node.content || []).map(serializeBlock).join("\n");
|
|
3075
|
+
}
|
|
3076
|
+
case "blockquote": {
|
|
3077
|
+
return (node.content || []).map(serializeBlock).map((line) => `> ${line}`).join("\n");
|
|
3078
|
+
}
|
|
3079
|
+
case "codeBlock": {
|
|
3080
|
+
const text = serializeInline(node.content);
|
|
3081
|
+
return `\`\`\`
|
|
3082
|
+
${text}
|
|
3083
|
+
\`\`\``;
|
|
3084
|
+
}
|
|
3085
|
+
case "horizontalRule": {
|
|
3086
|
+
return "---";
|
|
3087
|
+
}
|
|
3088
|
+
case "table": {
|
|
3089
|
+
const rows = node.content || [];
|
|
3090
|
+
const isBorderless = node.attrs?.borderless === true;
|
|
3091
|
+
const isSubtotals = node.attrs?.subtotals === true;
|
|
3092
|
+
const serializedRows = [];
|
|
3093
|
+
if (isBorderless && rows.length > 0) {
|
|
3094
|
+
const colCount = rows[0].content?.length || 0;
|
|
3095
|
+
const markerCell = isSubtotals ? "_" : "";
|
|
3096
|
+
const markerRow = "| " + new Array(colCount).fill(markerCell).join(" | ") + " |";
|
|
3097
|
+
const sep = "|" + new Array(colCount).fill("---").join("|") + "|";
|
|
3098
|
+
serializedRows.push(markerRow);
|
|
3099
|
+
serializedRows.push(sep);
|
|
3100
|
+
}
|
|
3101
|
+
for (let ri = 0; ri < rows.length; ri++) {
|
|
3102
|
+
const row = rows[ri];
|
|
3103
|
+
const cells = (row.content || []).map((cell) => {
|
|
3104
|
+
const inner = (cell.content || []).map(serializeBlock).join("");
|
|
3105
|
+
return inner;
|
|
3106
|
+
});
|
|
3107
|
+
serializedRows.push(`| ${cells.join(" | ")} |`);
|
|
3108
|
+
if (!isBorderless && ri === 0 && row.content?.[0]?.type === "tableHeader") {
|
|
3109
|
+
const sep = cells.map(() => "------").join("|");
|
|
3110
|
+
serializedRows.push(`|${sep}|`);
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
return serializedRows.join("\n");
|
|
3114
|
+
}
|
|
3115
|
+
case "tableRow": {
|
|
3116
|
+
const cells = (node.content || []).map((cell) => {
|
|
3117
|
+
const inner = (cell.content || []).map(serializeBlock).join("");
|
|
3118
|
+
return inner;
|
|
3119
|
+
});
|
|
3120
|
+
return `| ${cells.join(" | ")} |`;
|
|
3121
|
+
}
|
|
3122
|
+
case "tableHeader":
|
|
3123
|
+
case "tableCell": {
|
|
3124
|
+
return (node.content || []).map(serializeBlock).join("");
|
|
3125
|
+
}
|
|
3126
|
+
case "imageNode": {
|
|
3127
|
+
return serializeImageNode(node.attrs || {});
|
|
3128
|
+
}
|
|
3129
|
+
case "watermark": {
|
|
3130
|
+
const attrs = node.attrs || {};
|
|
3131
|
+
const parts = [];
|
|
3132
|
+
if (attrs.text) parts.push(`text:${attrs.text}`);
|
|
3133
|
+
if (attrs.opacity && attrs.opacity !== "0.15") parts.push(`opacity:${attrs.opacity}`);
|
|
3134
|
+
if (attrs.angle && attrs.angle !== "-45") parts.push(`angle:${attrs.angle}`);
|
|
3135
|
+
return `:::watermark{${parts.join("|")}}`;
|
|
3136
|
+
}
|
|
3137
|
+
case "panel": {
|
|
3138
|
+
const attrs = node.attrs || {};
|
|
3139
|
+
const parts = [];
|
|
3140
|
+
if (attrs.title) parts.push(`title:${attrs.title}`);
|
|
3141
|
+
if (attrs.border && attrs.border !== "solid") parts.push(`border:${attrs.border}`);
|
|
3142
|
+
else if (attrs.border === "solid") parts.push(`border:solid`);
|
|
3143
|
+
if (attrs.headerStyle) parts.push(`headerStyle:${attrs.headerStyle}`);
|
|
3144
|
+
const attrStr = parts.length > 0 ? `{${parts.join("|")}}` : "";
|
|
3145
|
+
const children = (node.content || []).map(serializeBlock).join("\n\n");
|
|
3146
|
+
return `:::panel${attrStr}
|
|
3147
|
+
${children}
|
|
3148
|
+
:::`;
|
|
3149
|
+
}
|
|
3150
|
+
case "repeatBlock": {
|
|
3151
|
+
const attrs = node.attrs || {};
|
|
3152
|
+
const parts = [];
|
|
3153
|
+
if (attrs.data) parts.push(`data:${attrs.data}`);
|
|
3154
|
+
const attrStr = parts.length > 0 ? `{${parts.join("|")}}` : "";
|
|
3155
|
+
const children = (node.content || []).map(serializeBlock).join("\n\n");
|
|
3156
|
+
return `:::repeat${attrStr}
|
|
3157
|
+
${children}
|
|
3158
|
+
:::`;
|
|
3159
|
+
}
|
|
3160
|
+
case "subtotalsBlock": {
|
|
3161
|
+
const children = (node.content || []).map(serializeBlock).join("\n");
|
|
3162
|
+
return `:::subtotals
|
|
3163
|
+
${children}
|
|
3164
|
+
:::`;
|
|
3165
|
+
}
|
|
3166
|
+
case "columns": {
|
|
3167
|
+
const attrs = node.attrs || {};
|
|
3168
|
+
const splitVal = attrs.split || "50";
|
|
3169
|
+
const cols = (node.content || []).filter((c) => c.type === "column");
|
|
3170
|
+
const colBlocks = cols.map((col) => {
|
|
3171
|
+
const children = (col.content || []).map(serializeBlock).join("\n\n");
|
|
3172
|
+
const colAttrs = col.attrs || {};
|
|
3173
|
+
const padTop = parseFloat(colAttrs.padTop || "0") || 0;
|
|
3174
|
+
const colTag = padTop ? `:::col{padTop:${padTop}}` : ":::col";
|
|
3175
|
+
return `${colTag}
|
|
3176
|
+
${children}
|
|
3177
|
+
:::`;
|
|
3178
|
+
});
|
|
3179
|
+
const padX = parseFloat(attrs.padX || "0") || 0;
|
|
3180
|
+
const colsAttrs = padX ? `split:${splitVal}|padX:${padX}` : `split:${splitVal}`;
|
|
3181
|
+
return `:::columns{${colsAttrs}}
|
|
3182
|
+
${colBlocks.join("\n")}
|
|
3183
|
+
:::`;
|
|
3184
|
+
}
|
|
3185
|
+
case "column": {
|
|
3186
|
+
const children = (node.content || []).map(serializeBlock).join("\n\n");
|
|
3187
|
+
const colAttrs = node.attrs || {};
|
|
3188
|
+
const padTop = parseFloat(colAttrs.padTop || "0") || 0;
|
|
3189
|
+
const colTag = padTop ? `:::col{padTop:${padTop}}` : ":::col";
|
|
3190
|
+
return `${colTag}
|
|
3191
|
+
${children}
|
|
3192
|
+
:::`;
|
|
1558
3193
|
}
|
|
3194
|
+
default:
|
|
3195
|
+
return serializeInline(node.content);
|
|
1559
3196
|
}
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
}
|
|
1565
|
-
const pdfBytes = await pdfDoc.save({ useObjectStreams: false });
|
|
1566
|
-
return {
|
|
1567
|
-
pdfBytes,
|
|
1568
|
-
fieldPositions: state.fieldPositions,
|
|
1569
|
-
fieldWarnings
|
|
1570
|
-
};
|
|
3197
|
+
}
|
|
3198
|
+
function tiptapToMarkdown(doc) {
|
|
3199
|
+
if (!doc.content) return "";
|
|
3200
|
+
return doc.content.map(serializeBlock).join("\n\n");
|
|
1571
3201
|
}
|
|
1572
3202
|
|
|
1573
3203
|
// src/utils/pdf-preview.ts
|
|
@@ -1656,8 +3286,27 @@ function useDocumentGenerator(options = {}) {
|
|
|
1656
3286
|
Placeholder__default.default.configure({
|
|
1657
3287
|
placeholder: options.placeholder || "Start writing your document..."
|
|
1658
3288
|
}),
|
|
3289
|
+
Table__default.default.extend({
|
|
3290
|
+
addAttributes() {
|
|
3291
|
+
return {
|
|
3292
|
+
...this.parent?.(),
|
|
3293
|
+
borderless: { default: false },
|
|
3294
|
+
subtotals: { default: false }
|
|
3295
|
+
};
|
|
3296
|
+
}
|
|
3297
|
+
}).configure({ resizable: false }),
|
|
3298
|
+
TableRow__default.default,
|
|
3299
|
+
TableCell__default.default,
|
|
3300
|
+
TableHeader__default.default,
|
|
1659
3301
|
FieldNode,
|
|
1660
|
-
VariableNode
|
|
3302
|
+
VariableNode,
|
|
3303
|
+
PanelNode,
|
|
3304
|
+
ColumnsNode,
|
|
3305
|
+
ColumnNode,
|
|
3306
|
+
WatermarkNode,
|
|
3307
|
+
ImageNode,
|
|
3308
|
+
RepeatNode,
|
|
3309
|
+
SubtotalsNode
|
|
1661
3310
|
],
|
|
1662
3311
|
content: initialContent || { type: "doc", content: [{ type: "paragraph" }] },
|
|
1663
3312
|
onUpdate: ({ editor: ed }) => {
|
|
@@ -1761,10 +3410,9 @@ function useDocumentGenerator(options = {}) {
|
|
|
1761
3410
|
async (values) => {
|
|
1762
3411
|
if (!editor) throw new Error("Editor is not initialized yet \u2014 wait for the editor to load before generating");
|
|
1763
3412
|
const content = editor.getJSON();
|
|
1764
|
-
const
|
|
1765
|
-
const
|
|
1766
|
-
|
|
1767
|
-
return { pdfBytes: result.pdfBytes, pdfPages: pages };
|
|
3413
|
+
const { pdfBytes: pdfBytes2 } = await generatePdfFromTiptap(content, values);
|
|
3414
|
+
const pdfPages2 = await pdfToImages(pdfBytes2);
|
|
3415
|
+
return { pdfBytes: pdfBytes2, pdfPages: pdfPages2 };
|
|
1768
3416
|
},
|
|
1769
3417
|
[editor]
|
|
1770
3418
|
);
|
|
@@ -2918,15 +4566,47 @@ function FieldEditPopover({
|
|
|
2918
4566
|
] }) })
|
|
2919
4567
|
] });
|
|
2920
4568
|
}
|
|
2921
|
-
|
|
4569
|
+
var sizeClasses = {
|
|
4570
|
+
md: "px-3 py-1.5 text-xs font-medium",
|
|
4571
|
+
sm: "px-2 py-0.5 text-[10px] font-medium"
|
|
4572
|
+
};
|
|
4573
|
+
function ToggleGroup({
|
|
4574
|
+
value,
|
|
4575
|
+
onChange,
|
|
4576
|
+
options,
|
|
4577
|
+
size = "md",
|
|
4578
|
+
className
|
|
4579
|
+
}) {
|
|
4580
|
+
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(
|
|
4581
|
+
"button",
|
|
4582
|
+
{
|
|
4583
|
+
className: cn(
|
|
4584
|
+
"rounded-md transition-colors",
|
|
4585
|
+
sizeClasses[size],
|
|
4586
|
+
value === opt.value ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
|
|
4587
|
+
),
|
|
4588
|
+
onClick: () => onChange(opt.value),
|
|
4589
|
+
children: opt.label
|
|
4590
|
+
},
|
|
4591
|
+
opt.value
|
|
4592
|
+
)) });
|
|
4593
|
+
}
|
|
4594
|
+
function labelToVarName2(label) {
|
|
2922
4595
|
return label.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
2923
4596
|
}
|
|
4597
|
+
function varNameToLabel(name) {
|
|
4598
|
+
return name.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
4599
|
+
}
|
|
2924
4600
|
function VariableInsertPopover({
|
|
2925
4601
|
open,
|
|
2926
4602
|
onOpenChange,
|
|
2927
4603
|
onInsert,
|
|
4604
|
+
predefinedVariables,
|
|
2928
4605
|
children
|
|
2929
4606
|
}) {
|
|
4607
|
+
const hasPredefined = !!predefinedVariables?.length;
|
|
4608
|
+
const [mode, setMode] = React12.useState(hasPredefined ? "existing" : "new");
|
|
4609
|
+
const [search, setSearch] = React12.useState("");
|
|
2930
4610
|
const [varLabel, setVarLabel] = React12.useState("");
|
|
2931
4611
|
const [varName, setVarName] = React12.useState("");
|
|
2932
4612
|
const [varDefault, setVarDefault] = React12.useState("");
|
|
@@ -2936,7 +4616,9 @@ function VariableInsertPopover({
|
|
|
2936
4616
|
setVarName("");
|
|
2937
4617
|
setVarDefault("");
|
|
2938
4618
|
setNameManuallyEdited(false);
|
|
2939
|
-
|
|
4619
|
+
setSearch("");
|
|
4620
|
+
setMode(hasPredefined ? "existing" : "new");
|
|
4621
|
+
}, [hasPredefined]);
|
|
2940
4622
|
const handleOpenChange = React12.useCallback(
|
|
2941
4623
|
(nextOpen) => {
|
|
2942
4624
|
if (!nextOpen) reset();
|
|
@@ -2946,7 +4628,7 @@ function VariableInsertPopover({
|
|
|
2946
4628
|
);
|
|
2947
4629
|
React12.useEffect(() => {
|
|
2948
4630
|
if (!nameManuallyEdited) {
|
|
2949
|
-
setVarName(
|
|
4631
|
+
setVarName(labelToVarName2(varLabel));
|
|
2950
4632
|
}
|
|
2951
4633
|
}, [varLabel, nameManuallyEdited]);
|
|
2952
4634
|
const handleInsert = React12.useCallback(() => {
|
|
@@ -2954,10 +4636,32 @@ function VariableInsertPopover({
|
|
|
2954
4636
|
onInsert({
|
|
2955
4637
|
varName: varName.trim(),
|
|
2956
4638
|
varLabel: varLabel.trim(),
|
|
2957
|
-
varDefault: varDefault.trim()
|
|
4639
|
+
varDefault: varDefault.trim(),
|
|
4640
|
+
format: ""
|
|
2958
4641
|
});
|
|
2959
4642
|
handleOpenChange(false);
|
|
2960
4643
|
}, [varLabel, varName, varDefault, onInsert, handleOpenChange]);
|
|
4644
|
+
const handlePickPredefined = React12.useCallback(
|
|
4645
|
+
(pv) => {
|
|
4646
|
+
onInsert({
|
|
4647
|
+
varName: pv.varName,
|
|
4648
|
+
varLabel: pv.varLabel || varNameToLabel(pv.varName),
|
|
4649
|
+
varDefault: "",
|
|
4650
|
+
format: ""
|
|
4651
|
+
});
|
|
4652
|
+
handleOpenChange(false);
|
|
4653
|
+
},
|
|
4654
|
+
[onInsert, handleOpenChange]
|
|
4655
|
+
);
|
|
4656
|
+
const filteredVars = React12.useMemo(() => {
|
|
4657
|
+
if (!predefinedVariables?.length) return [];
|
|
4658
|
+
const q = search.toLowerCase();
|
|
4659
|
+
if (!q) return predefinedVariables;
|
|
4660
|
+
return predefinedVariables.filter((pv) => {
|
|
4661
|
+
const label = (pv.varLabel || varNameToLabel(pv.varName)).toLowerCase();
|
|
4662
|
+
return pv.varName.toLowerCase().includes(q) || label.includes(q);
|
|
4663
|
+
});
|
|
4664
|
+
}, [predefinedVariables, search]);
|
|
2961
4665
|
return /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open, onOpenChange: handleOpenChange, children: [
|
|
2962
4666
|
/* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children }),
|
|
2963
4667
|
/* @__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 +4669,114 @@ function VariableInsertPopover({
|
|
|
2965
4669
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Braces, { size: 14 }),
|
|
2966
4670
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Insert Variable" })
|
|
2967
4671
|
] }),
|
|
2968
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2969
|
-
|
|
2970
|
-
|
|
4672
|
+
hasPredefined && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4673
|
+
ToggleGroup,
|
|
4674
|
+
{
|
|
4675
|
+
value: mode,
|
|
4676
|
+
onChange: setMode,
|
|
4677
|
+
options: [
|
|
4678
|
+
{ value: "existing", label: "Existing" },
|
|
4679
|
+
{ value: "new", label: "New" }
|
|
4680
|
+
],
|
|
4681
|
+
size: "sm"
|
|
4682
|
+
}
|
|
4683
|
+
),
|
|
4684
|
+
mode === "existing" && hasPredefined ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
4685
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
4686
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { size: 14, className: "absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground" }),
|
|
2971
4687
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2972
4688
|
Input,
|
|
2973
4689
|
{
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
className: "h-8 text-xs"
|
|
4690
|
+
value: search,
|
|
4691
|
+
onChange: (e) => setSearch(e.target.value),
|
|
4692
|
+
placeholder: "Search variables...",
|
|
4693
|
+
className: "h-8 text-xs pl-7"
|
|
2979
4694
|
}
|
|
2980
4695
|
)
|
|
2981
4696
|
] }),
|
|
2982
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2983
|
-
|
|
4697
|
+
/* @__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) => {
|
|
4698
|
+
const label = pv.varLabel || varNameToLabel(pv.varName);
|
|
4699
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4700
|
+
"button",
|
|
4701
|
+
{
|
|
4702
|
+
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",
|
|
4703
|
+
onClick: () => handlePickPredefined(pv),
|
|
4704
|
+
children: [
|
|
4705
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: label }),
|
|
4706
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground font-mono", children: pv.varName })
|
|
4707
|
+
]
|
|
4708
|
+
},
|
|
4709
|
+
pv.varName
|
|
4710
|
+
);
|
|
4711
|
+
}) })
|
|
4712
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
4713
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
4714
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
4715
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "var-label", className: "text-xs", children: "Label" }),
|
|
4716
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4717
|
+
Input,
|
|
4718
|
+
{
|
|
4719
|
+
id: "var-label",
|
|
4720
|
+
value: varLabel,
|
|
4721
|
+
onChange: (e) => setVarLabel(e.target.value),
|
|
4722
|
+
placeholder: "e.g. Company Name",
|
|
4723
|
+
className: "h-8 text-xs"
|
|
4724
|
+
}
|
|
4725
|
+
)
|
|
4726
|
+
] }),
|
|
4727
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
4728
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "var-name", className: "text-xs", children: "Name (identifier)" }),
|
|
4729
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4730
|
+
Input,
|
|
4731
|
+
{
|
|
4732
|
+
id: "var-name",
|
|
4733
|
+
value: varName,
|
|
4734
|
+
onChange: (e) => {
|
|
4735
|
+
setVarName(e.target.value);
|
|
4736
|
+
setNameManuallyEdited(true);
|
|
4737
|
+
},
|
|
4738
|
+
placeholder: "e.g. company_name",
|
|
4739
|
+
className: "h-8 text-xs font-mono"
|
|
4740
|
+
}
|
|
4741
|
+
),
|
|
4742
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] text-muted-foreground mt-0.5", children: "Used as key in data objects" })
|
|
4743
|
+
] }),
|
|
4744
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
4745
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "var-default", className: "text-xs", children: "Default Value" }),
|
|
4746
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4747
|
+
Input,
|
|
4748
|
+
{
|
|
4749
|
+
id: "var-default",
|
|
4750
|
+
value: varDefault,
|
|
4751
|
+
onChange: (e) => setVarDefault(e.target.value),
|
|
4752
|
+
placeholder: "Optional",
|
|
4753
|
+
className: "h-8 text-xs"
|
|
4754
|
+
}
|
|
4755
|
+
)
|
|
4756
|
+
] })
|
|
4757
|
+
] }),
|
|
4758
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 pt-1", children: [
|
|
2984
4759
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2985
|
-
|
|
4760
|
+
Button,
|
|
2986
4761
|
{
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
},
|
|
2993
|
-
placeholder: "e.g. company_name",
|
|
2994
|
-
className: "h-8 text-xs font-mono"
|
|
4762
|
+
size: "sm",
|
|
4763
|
+
className: "h-8 flex-1 text-xs",
|
|
4764
|
+
onClick: handleInsert,
|
|
4765
|
+
disabled: !varLabel.trim() || !varName.trim(),
|
|
4766
|
+
children: "Insert"
|
|
2995
4767
|
}
|
|
2996
4768
|
),
|
|
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
4769
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3002
|
-
|
|
4770
|
+
Button,
|
|
3003
4771
|
{
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
4772
|
+
variant: "outline",
|
|
4773
|
+
size: "sm",
|
|
4774
|
+
className: "h-8 text-xs",
|
|
4775
|
+
onClick: () => handleOpenChange(false),
|
|
4776
|
+
children: "Cancel"
|
|
3009
4777
|
}
|
|
3010
4778
|
)
|
|
3011
4779
|
] })
|
|
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
4780
|
] })
|
|
3035
4781
|
] }) })
|
|
3036
4782
|
] });
|
|
@@ -3252,7 +4998,6 @@ function PreviewPanel({
|
|
|
3252
4998
|
(f) => f.position.page === currentPageIndex + 1 && f.position.width > 0
|
|
3253
4999
|
);
|
|
3254
5000
|
}, [positionedFields, currentPage, currentPageIndex]);
|
|
3255
|
-
const showScrollbars = zoomLevel > 1;
|
|
3256
5001
|
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
5002
|
/* @__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
5003
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
@@ -3368,12 +5113,13 @@ function PreviewPanel({
|
|
|
3368
5113
|
"div",
|
|
3369
5114
|
{
|
|
3370
5115
|
className: cn(
|
|
3371
|
-
"border border-border rounded-lg bg-muted/30 shrink-0"
|
|
3372
|
-
showScrollbars ? "overflow-auto" : "overflow-hidden"
|
|
5116
|
+
"border border-border rounded-lg bg-muted/30 shrink-0 overflow-auto scrollbar-hidden"
|
|
3373
5117
|
),
|
|
3374
5118
|
style: {
|
|
3375
5119
|
width: pageDisplaySize.viewportWidth,
|
|
3376
|
-
height: pageDisplaySize.viewportHeight
|
|
5120
|
+
height: pageDisplaySize.viewportHeight,
|
|
5121
|
+
maxWidth: "100%",
|
|
5122
|
+
maxHeight: "100%"
|
|
3377
5123
|
},
|
|
3378
5124
|
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3379
5125
|
"div",
|
|
@@ -3444,8 +5190,8 @@ function validateMarkdown(markdown) {
|
|
|
3444
5190
|
errors.push(`Malformed token at line ${lineNum}: missing | delimiter`);
|
|
3445
5191
|
} else {
|
|
3446
5192
|
const tokenType = tokenContent.slice(0, pipeIdx);
|
|
3447
|
-
if (tokenType !== "field" && tokenType !== "var") {
|
|
3448
|
-
errors.push(`Unknown token type "${tokenType}" at line ${lineNum} (expected "field" or "
|
|
5193
|
+
if (tokenType !== "field" && tokenType !== "var" && tokenType !== "image") {
|
|
5194
|
+
errors.push(`Unknown token type "${tokenType}" at line ${lineNum} (expected "field", "var" or "image")`);
|
|
3449
5195
|
} else {
|
|
3450
5196
|
const body = tokenContent.slice(pipeIdx + 1);
|
|
3451
5197
|
const pairs = body.split("|");
|
|
@@ -3459,12 +5205,48 @@ function validateMarkdown(markdown) {
|
|
|
3459
5205
|
if (!hasName) {
|
|
3460
5206
|
errors.push(`Variable token at line ${lineNum} is missing required "name" attribute`);
|
|
3461
5207
|
}
|
|
5208
|
+
} else if (tokenType === "image") {
|
|
5209
|
+
const hasSource = pairs.some((p) => p.startsWith("src:") || p.startsWith("var:"));
|
|
5210
|
+
if (!hasSource) {
|
|
5211
|
+
errors.push(`Image token at line ${lineNum} needs a "src" or "var" attribute`);
|
|
5212
|
+
}
|
|
3462
5213
|
}
|
|
3463
5214
|
}
|
|
3464
5215
|
}
|
|
3465
5216
|
searchFrom = closeIdx + 2;
|
|
3466
5217
|
}
|
|
3467
5218
|
}
|
|
5219
|
+
const DIRECTIVE_OPEN_RE = /^:::(panel|columns|col|watermark)(?:\{[^}]*\})?$/;
|
|
5220
|
+
const DIRECTIVE_CLOSE_RE = /^:::$/;
|
|
5221
|
+
const directiveStack = [];
|
|
5222
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5223
|
+
const trimmed = lines[i].trim();
|
|
5224
|
+
const lineNum = i + 1;
|
|
5225
|
+
const openMatch = trimmed.match(DIRECTIVE_OPEN_RE);
|
|
5226
|
+
if (openMatch) {
|
|
5227
|
+
const dtype = openMatch[1];
|
|
5228
|
+
if (dtype !== "watermark") {
|
|
5229
|
+
if (dtype === "col") {
|
|
5230
|
+
const parent = directiveStack[directiveStack.length - 1];
|
|
5231
|
+
if (!parent || parent.type !== "columns") {
|
|
5232
|
+
warnings.push(`:::col at line ${lineNum} appears outside :::columns`);
|
|
5233
|
+
}
|
|
5234
|
+
}
|
|
5235
|
+
directiveStack.push({ type: dtype, line: lineNum });
|
|
5236
|
+
}
|
|
5237
|
+
continue;
|
|
5238
|
+
}
|
|
5239
|
+
if (DIRECTIVE_CLOSE_RE.test(trimmed)) {
|
|
5240
|
+
if (directiveStack.length === 0) {
|
|
5241
|
+
warnings.push(`Stray ::: close at line ${lineNum} with no matching open directive`);
|
|
5242
|
+
} else {
|
|
5243
|
+
directiveStack.pop();
|
|
5244
|
+
}
|
|
5245
|
+
}
|
|
5246
|
+
}
|
|
5247
|
+
for (const open of directiveStack) {
|
|
5248
|
+
errors.push(`Unclosed :::${open.type} directive opened at line ${open.line}`);
|
|
5249
|
+
}
|
|
3468
5250
|
let fields = [];
|
|
3469
5251
|
let variables = [];
|
|
3470
5252
|
try {
|
|
@@ -3498,31 +5280,6 @@ function validateMarkdown(markdown) {
|
|
|
3498
5280
|
fields
|
|
3499
5281
|
};
|
|
3500
5282
|
}
|
|
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
5283
|
function parseCsv(text) {
|
|
3527
5284
|
const lines = text.split(/\r?\n/).filter((l) => l.trim() !== "");
|
|
3528
5285
|
if (lines.length < 2) return [];
|
|
@@ -3572,7 +5329,10 @@ function GeneratePanel({
|
|
|
3572
5329
|
editorContent,
|
|
3573
5330
|
editorVariables,
|
|
3574
5331
|
onGeneratePdf,
|
|
3575
|
-
initialBulkData
|
|
5332
|
+
initialBulkData,
|
|
5333
|
+
initialVariableValues,
|
|
5334
|
+
exportFileName,
|
|
5335
|
+
templateMode
|
|
3576
5336
|
}) {
|
|
3577
5337
|
const [templateSource, setTemplateSource] = React12.useState("editor");
|
|
3578
5338
|
const [importedMarkdown, setImportedMarkdown] = React12.useState(null);
|
|
@@ -3580,6 +5340,7 @@ function GeneratePanel({
|
|
|
3580
5340
|
const [validationResult, setValidationResult] = React12.useState(null);
|
|
3581
5341
|
const [mode, setMode] = React12.useState("single");
|
|
3582
5342
|
const [variableValues, setVariableValues] = React12.useState({});
|
|
5343
|
+
const [variableSearch, setVariableSearch] = React12.useState("");
|
|
3583
5344
|
const [bulkInputFormat, setBulkInputFormat] = React12.useState("json");
|
|
3584
5345
|
const [bulkInput, setBulkInput] = React12.useState("");
|
|
3585
5346
|
const [bulkData, setBulkData] = React12.useState(null);
|
|
@@ -3606,11 +5367,22 @@ function GeneratePanel({
|
|
|
3606
5367
|
const col = pos - textBefore.lastIndexOf("\n");
|
|
3607
5368
|
setJsonCursor({ line, col });
|
|
3608
5369
|
}, []);
|
|
5370
|
+
const lockedVarNames = React12__namespace.default.useMemo(
|
|
5371
|
+
() => templateMode && initialVariableValues ? new Set(Object.keys(initialVariableValues)) : /* @__PURE__ */ new Set(),
|
|
5372
|
+
[templateMode, initialVariableValues]
|
|
5373
|
+
);
|
|
3609
5374
|
const activeVariables = templateSource === "imported" && validationResult?.valid ? validationResult.variables : editorVariables;
|
|
3610
5375
|
const hasVariables = activeVariables.length > 0;
|
|
3611
5376
|
const hasEditorContent = editorMarkdown.trim().length > 0;
|
|
3612
5377
|
const hasImportedContent = templateSource === "imported" && importedMarkdown != null;
|
|
3613
5378
|
const hasContent = hasImportedContent || hasEditorContent;
|
|
5379
|
+
const filteredVariables = React12__namespace.default.useMemo(() => {
|
|
5380
|
+
if (!variableSearch.trim()) return activeVariables;
|
|
5381
|
+
const q = variableSearch.toLowerCase();
|
|
5382
|
+
return activeVariables.filter(
|
|
5383
|
+
(v) => v.varName.toLowerCase().includes(q) || (v.varLabel || "").toLowerCase().includes(q)
|
|
5384
|
+
);
|
|
5385
|
+
}, [activeVariables, variableSearch]);
|
|
3614
5386
|
const getActiveContent = React12.useCallback(() => {
|
|
3615
5387
|
if (templateSource === "imported" && importedMarkdown) {
|
|
3616
5388
|
return markdownToTiptap(importedMarkdown);
|
|
@@ -3778,7 +5550,10 @@ function GeneratePanel({
|
|
|
3778
5550
|
setPreviewError(null);
|
|
3779
5551
|
try {
|
|
3780
5552
|
const content = getActiveContent();
|
|
3781
|
-
const
|
|
5553
|
+
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, variableValues);
|
|
5554
|
+
const blockExpanded = expandBlockVariables(expanded, enrichedValues);
|
|
5555
|
+
const suppressed = suppressZeroContent(blockExpanded, enrichedValues);
|
|
5556
|
+
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
3782
5557
|
const fields = extractFieldsFromContent(replaced);
|
|
3783
5558
|
const result = await generatePdfFromContent(replaced);
|
|
3784
5559
|
const pages = await pdfToImages(result.pdfBytes);
|
|
@@ -3799,7 +5574,10 @@ function GeneratePanel({
|
|
|
3799
5574
|
setExportSuccess(null);
|
|
3800
5575
|
try {
|
|
3801
5576
|
const content = getActiveContent();
|
|
3802
|
-
const
|
|
5577
|
+
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, variableValues);
|
|
5578
|
+
const blockExpanded = expandBlockVariables(expanded, enrichedValues);
|
|
5579
|
+
const suppressed = suppressZeroContent(blockExpanded, enrichedValues);
|
|
5580
|
+
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
3803
5581
|
const fields = extractFieldsFromContent(replaced);
|
|
3804
5582
|
const result = await generatePdfFromContent(replaced, {
|
|
3805
5583
|
drawFieldPlaceholders: false,
|
|
@@ -3807,7 +5585,7 @@ function GeneratePanel({
|
|
|
3807
5585
|
fields
|
|
3808
5586
|
});
|
|
3809
5587
|
const blob = new Blob([result.pdfBytes], { type: "application/pdf" });
|
|
3810
|
-
const fileName = importedFileName ? importedFileName.replace(".md", ".pdf") : "document.pdf";
|
|
5588
|
+
const fileName = exportFileName ? exportFileName.endsWith(".pdf") ? exportFileName : `${exportFileName}.pdf` : importedFileName ? importedFileName.replace(".md", ".pdf") : "document.pdf";
|
|
3811
5589
|
if (onGeneratePdf) {
|
|
3812
5590
|
onGeneratePdf(blob, fileName);
|
|
3813
5591
|
} else {
|
|
@@ -3879,7 +5657,10 @@ function GeneratePanel({
|
|
|
3879
5657
|
const allWarnings = [];
|
|
3880
5658
|
for (let i = 0; i < bulkData.length; i++) {
|
|
3881
5659
|
const values = bulkData[i];
|
|
3882
|
-
const
|
|
5660
|
+
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, values);
|
|
5661
|
+
const blockExpanded = expandBlockVariables(expanded, enrichedValues);
|
|
5662
|
+
const suppressed = suppressZeroContent(blockExpanded, enrichedValues);
|
|
5663
|
+
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
3883
5664
|
const fields = extractFieldsFromContent(replaced);
|
|
3884
5665
|
const result = await generatePdfFromContent(replaced, {
|
|
3885
5666
|
drawFieldPlaceholders: false,
|
|
@@ -3934,13 +5715,15 @@ ${allWarnings.join("\n")}`);
|
|
|
3934
5715
|
}, [mode, bulkInputFormat, bulkInput, initialBulkJson]);
|
|
3935
5716
|
React12__namespace.default.useEffect(() => {
|
|
3936
5717
|
if (templateSource === "editor") {
|
|
3937
|
-
const defaults = {};
|
|
5718
|
+
const defaults = initialVariableValues ? { ...initialVariableValues } : {};
|
|
3938
5719
|
for (const v of editorVariables) {
|
|
3939
|
-
defaults[v.varName]
|
|
5720
|
+
if (!defaults[v.varName]) {
|
|
5721
|
+
defaults[v.varName] = variableValues[v.varName] || v.varDefault || "";
|
|
5722
|
+
}
|
|
3940
5723
|
}
|
|
3941
5724
|
setVariableValues(defaults);
|
|
3942
5725
|
}
|
|
3943
|
-
}, [editorVariables, templateSource]);
|
|
5726
|
+
}, [editorVariables, templateSource, initialVariableValues]);
|
|
3944
5727
|
React12.useEffect(() => {
|
|
3945
5728
|
setPreviewFresh(false);
|
|
3946
5729
|
}, [variableValues, templateSource, importedMarkdown, editorMarkdown, editorContent]);
|
|
@@ -3952,8 +5735,8 @@ ${allWarnings.join("\n")}`);
|
|
|
3952
5735
|
}, [exportSuccess]);
|
|
3953
5736
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-h-0 grid grid-cols-[1fr_1px_1fr]", children: [
|
|
3954
5737
|
/* @__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: [
|
|
5738
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto p-4 space-y-4 scrollbar-hidden", children: [
|
|
5739
|
+
!templateMode && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3957
5740
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
|
|
3958
5741
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-medium", children: "Template" }),
|
|
3959
5742
|
templateSource === "imported" && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -4046,10 +5829,10 @@ ${allWarnings.join("\n")}`);
|
|
|
4046
5829
|
!hasContent && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center py-8 text-muted-foreground", children: [
|
|
4047
5830
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Braces, { size: 32, className: "mb-2 opacity-50" }),
|
|
4048
5831
|
/* @__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." })
|
|
5832
|
+
/* @__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
5833
|
] }),
|
|
4051
5834
|
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(
|
|
5835
|
+
!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
5836
|
ToggleGroup,
|
|
4054
5837
|
{
|
|
4055
5838
|
value: mode,
|
|
@@ -4061,20 +5844,53 @@ ${allWarnings.join("\n")}`);
|
|
|
4061
5844
|
}
|
|
4062
5845
|
) }),
|
|
4063
5846
|
mode === "single" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3 space-y-3", children: [
|
|
4064
|
-
/* @__PURE__ */ jsxRuntime.
|
|
4065
|
-
|
|
4066
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5847
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
5848
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: "Variables" }),
|
|
5849
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground", children: activeVariables.length })
|
|
5850
|
+
] }),
|
|
5851
|
+
activeVariables.length > 5 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
5852
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { size: 14, className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground" }),
|
|
4067
5853
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4068
5854
|
Input,
|
|
4069
5855
|
{
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
5856
|
+
value: variableSearch,
|
|
5857
|
+
onChange: (e) => setVariableSearch(e.target.value),
|
|
5858
|
+
placeholder: "Search variables...",
|
|
5859
|
+
className: "h-8 text-xs pl-8"
|
|
5860
|
+
}
|
|
5861
|
+
),
|
|
5862
|
+
variableSearch && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5863
|
+
"button",
|
|
5864
|
+
{
|
|
5865
|
+
type: "button",
|
|
5866
|
+
onClick: () => setVariableSearch(""),
|
|
5867
|
+
className: "absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground",
|
|
5868
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 12 })
|
|
4075
5869
|
}
|
|
4076
5870
|
)
|
|
4077
|
-
] },
|
|
5871
|
+
] }),
|
|
5872
|
+
filteredVariables.length === 0 && variableSearch.trim() && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground py-2 text-center", children: [
|
|
5873
|
+
"No variables match \u201C",
|
|
5874
|
+
variableSearch,
|
|
5875
|
+
"\u201D"
|
|
5876
|
+
] }),
|
|
5877
|
+
filteredVariables.map((v) => {
|
|
5878
|
+
const isLocked = lockedVarNames.has(v.varName);
|
|
5879
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
5880
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: `gen-${v.varName}`, className: "text-xs", children: v.varLabel || v.varName }),
|
|
5881
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5882
|
+
Input,
|
|
5883
|
+
{
|
|
5884
|
+
id: `gen-${v.varName}`,
|
|
5885
|
+
value: variableValues[v.varName] || "",
|
|
5886
|
+
onChange: (e) => updateVariableValue(v.varName, e.target.value),
|
|
5887
|
+
placeholder: v.varDefault || `Enter ${v.varLabel || v.varName}`,
|
|
5888
|
+
disabled: isLocked,
|
|
5889
|
+
className: cn("h-8 text-xs", isLocked && "opacity-60 cursor-not-allowed")
|
|
5890
|
+
}
|
|
5891
|
+
)
|
|
5892
|
+
] }, v.varName);
|
|
5893
|
+
})
|
|
4078
5894
|
] }),
|
|
4079
5895
|
mode === "bulk" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3 space-y-3", children: [
|
|
4080
5896
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
@@ -4263,56 +6079,60 @@ ${allWarnings.join("\n")}`);
|
|
|
4263
6079
|
] }),
|
|
4264
6080
|
!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
6081
|
] }),
|
|
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
|
-
|
|
6082
|
+
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: [
|
|
6083
|
+
mode === "single" || !hasVariables ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
6084
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6085
|
+
Button,
|
|
6086
|
+
{
|
|
6087
|
+
variant: "secondary",
|
|
6088
|
+
onClick: handlePreview,
|
|
6089
|
+
disabled: isGenerating,
|
|
6090
|
+
className: "h-10 px-4 font-semibold shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
6091
|
+
children: [
|
|
6092
|
+
isGenerating ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: 16 }),
|
|
6093
|
+
isGenerating ? "Generating..." : "Generate PDF"
|
|
6094
|
+
]
|
|
6095
|
+
}
|
|
6096
|
+
),
|
|
6097
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6098
|
+
Button,
|
|
6099
|
+
{
|
|
6100
|
+
onClick: handleExportSingle,
|
|
6101
|
+
disabled: !previewFresh || isExporting,
|
|
6102
|
+
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",
|
|
6103
|
+
children: [
|
|
6104
|
+
isExporting ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { size: 16 }),
|
|
6105
|
+
isExporting ? "Exporting..." : "Export PDF"
|
|
6106
|
+
]
|
|
6107
|
+
}
|
|
6108
|
+
)
|
|
6109
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4293
6110
|
Button,
|
|
4294
6111
|
{
|
|
4295
|
-
onClick:
|
|
4296
|
-
disabled: !
|
|
4297
|
-
className: "h-10 px-4 font-semibold bg-primary hover:bg-primary/90 text-primary-foreground shadow-
|
|
6112
|
+
onClick: handleBulkGenerate,
|
|
6113
|
+
disabled: isExporting || !bulkData || bulkData.length === 0,
|
|
6114
|
+
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
6115
|
children: [
|
|
4299
6116
|
isExporting ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { size: 16 }),
|
|
4300
|
-
isExporting ? "
|
|
6117
|
+
isExporting ? "Generating..." : `Generate All (${bulkData?.length || 0} PDFs)`
|
|
4301
6118
|
]
|
|
4302
6119
|
}
|
|
4303
|
-
)
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
]
|
|
4314
|
-
|
|
4315
|
-
|
|
6120
|
+
),
|
|
6121
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-1 ml-auto", children: [
|
|
6122
|
+
previewError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
6123
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
6124
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: previewError })
|
|
6125
|
+
] }),
|
|
6126
|
+
exportError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
6127
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
6128
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: exportError })
|
|
6129
|
+
] }),
|
|
6130
|
+
exportSuccess && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-emerald-600 max-w-[300px]", children: [
|
|
6131
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCircle2, { size: 14, className: "shrink-0" }),
|
|
6132
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: exportSuccess })
|
|
6133
|
+
] })
|
|
6134
|
+
] })
|
|
6135
|
+
] })
|
|
4316
6136
|
] }),
|
|
4317
6137
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-border" }),
|
|
4318
6138
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col min-h-0 min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4490,7 +6310,13 @@ function DocumentGeneratorInner({
|
|
|
4490
6310
|
showToolbar = true,
|
|
4491
6311
|
showGenerateTab = true,
|
|
4492
6312
|
onGeneratePdf,
|
|
4493
|
-
initialBulkData
|
|
6313
|
+
initialBulkData,
|
|
6314
|
+
defaultTab = "editor",
|
|
6315
|
+
initialVariableValues,
|
|
6316
|
+
predefinedVariables,
|
|
6317
|
+
exportFileName,
|
|
6318
|
+
templateMode,
|
|
6319
|
+
onSaveTemplate
|
|
4494
6320
|
}) {
|
|
4495
6321
|
const {
|
|
4496
6322
|
editor,
|
|
@@ -4517,7 +6343,7 @@ function DocumentGeneratorInner({
|
|
|
4517
6343
|
placeholder,
|
|
4518
6344
|
onChange
|
|
4519
6345
|
});
|
|
4520
|
-
const [activeTab, setActiveTab] = React12.useState(
|
|
6346
|
+
const [activeTab, setActiveTab] = React12.useState(defaultTab);
|
|
4521
6347
|
const [insertPopoverOpen, setInsertPopoverOpen] = React12.useState(false);
|
|
4522
6348
|
const [insertVarPopoverOpen, setInsertVarPopoverOpen] = React12.useState(false);
|
|
4523
6349
|
const [editFieldId, setEditFieldId] = React12.useState(null);
|
|
@@ -4529,6 +6355,7 @@ function DocumentGeneratorInner({
|
|
|
4529
6355
|
const editorWrapperRef = React12.useRef(null);
|
|
4530
6356
|
const [editorCollapsed, setEditorCollapsed] = React12.useState(false);
|
|
4531
6357
|
const [mdExportError, setMdExportError] = React12.useState(null);
|
|
6358
|
+
const [isSaving, setIsSaving] = React12.useState(false);
|
|
4532
6359
|
const [exportSuccess, setExportSuccess] = React12.useState(null);
|
|
4533
6360
|
React12.useEffect(() => {
|
|
4534
6361
|
const handler = (e) => {
|
|
@@ -4599,7 +6426,28 @@ function DocumentGeneratorInner({
|
|
|
4599
6426
|
onExport(result);
|
|
4600
6427
|
setExportSuccess("Document exported successfully");
|
|
4601
6428
|
}
|
|
4602
|
-
}, [exportDocument, onExport]);
|
|
6429
|
+
}, [exportDocument, onExport]);
|
|
6430
|
+
const handleSaveTemplate = React12.useCallback(async () => {
|
|
6431
|
+
const md = exportMarkdown();
|
|
6432
|
+
if (!md.trim()) {
|
|
6433
|
+
setMdExportError("Editor content is empty");
|
|
6434
|
+
return;
|
|
6435
|
+
}
|
|
6436
|
+
const validation = validateMarkdown(md);
|
|
6437
|
+
if (!validation.valid) {
|
|
6438
|
+
setMdExportError(validation.errors.join("; "));
|
|
6439
|
+
return;
|
|
6440
|
+
}
|
|
6441
|
+
setMdExportError(null);
|
|
6442
|
+
setIsSaving(true);
|
|
6443
|
+
try {
|
|
6444
|
+
await onSaveTemplate?.(md);
|
|
6445
|
+
setExportSuccess("Template saved");
|
|
6446
|
+
} catch {
|
|
6447
|
+
} finally {
|
|
6448
|
+
setIsSaving(false);
|
|
6449
|
+
}
|
|
6450
|
+
}, [exportMarkdown, onSaveTemplate]);
|
|
4603
6451
|
const handleExportMarkdown = React12.useCallback(() => {
|
|
4604
6452
|
const md = exportMarkdown();
|
|
4605
6453
|
if (!md.trim()) {
|
|
@@ -4616,10 +6464,10 @@ function DocumentGeneratorInner({
|
|
|
4616
6464
|
const url = URL.createObjectURL(blob);
|
|
4617
6465
|
const a = document.createElement("a");
|
|
4618
6466
|
a.href = url;
|
|
4619
|
-
a.download = "template.md";
|
|
6467
|
+
a.download = exportFileName ? `${exportFileName}.md` : "template.md";
|
|
4620
6468
|
a.click();
|
|
4621
6469
|
URL.revokeObjectURL(url);
|
|
4622
|
-
}, [exportMarkdown]);
|
|
6470
|
+
}, [exportMarkdown, exportFileName]);
|
|
4623
6471
|
React12.useEffect(() => {
|
|
4624
6472
|
if (mdExportError) setMdExportError(null);
|
|
4625
6473
|
}, [markdown]);
|
|
@@ -4655,6 +6503,7 @@ function DocumentGeneratorInner({
|
|
|
4655
6503
|
open: insertVarPopoverOpen,
|
|
4656
6504
|
onOpenChange: setInsertVarPopoverOpen,
|
|
4657
6505
|
onInsert: handleInsertVariable,
|
|
6506
|
+
predefinedVariables,
|
|
4658
6507
|
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4659
6508
|
Button,
|
|
4660
6509
|
{
|
|
@@ -4695,118 +6544,141 @@ function DocumentGeneratorInner({
|
|
|
4695
6544
|
] })
|
|
4696
6545
|
] })
|
|
4697
6546
|
] }),
|
|
4698
|
-
/* @__PURE__ */ jsxRuntime.
|
|
6547
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(
|
|
4699
6548
|
"flex flex-col flex-1 min-h-0",
|
|
4700
6549
|
activeTab !== "editor" && "hidden"
|
|
6550
|
+
), children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(
|
|
6551
|
+
"flex-1 min-h-0",
|
|
6552
|
+
showPreview && !editorCollapsed ? "grid grid-cols-[1fr_1px_1fr]" : "flex flex-col"
|
|
4701
6553
|
), 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,
|
|
6554
|
+
!editorCollapsed && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col min-h-0 min-w-0", children: [
|
|
6555
|
+
showToolbar && !readOnly && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-border", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
6556
|
+
EditorToolbar,
|
|
4727
6557
|
{
|
|
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"
|
|
6558
|
+
editor,
|
|
6559
|
+
insertFieldButton,
|
|
6560
|
+
insertVariableButton,
|
|
6561
|
+
onCollapse: showPreview ? () => setEditorCollapsed(true) : void 0
|
|
4742
6562
|
}
|
|
4743
|
-
)
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4747
|
-
Button,
|
|
6563
|
+
) }),
|
|
6564
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: editorWrapperRef, className: "flex-1 overflow-y-auto min-h-0 scrollbar-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
6565
|
+
react.EditorContent,
|
|
4748
6566
|
{
|
|
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
|
-
]
|
|
6567
|
+
editor,
|
|
6568
|
+
className: "prose prose-sm max-w-none p-4 focus-within:outline-none"
|
|
4757
6569
|
}
|
|
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: [
|
|
6570
|
+
) }),
|
|
6571
|
+
/* @__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
6572
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4784
6573
|
Button,
|
|
4785
6574
|
{
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
6575
|
+
variant: "secondary",
|
|
6576
|
+
onClick: generatePdf,
|
|
6577
|
+
disabled: isGenerating || !editor,
|
|
6578
|
+
className: "h-10 px-4 font-semibold shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
4789
6579
|
children: [
|
|
4790
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.
|
|
4791
|
-
"
|
|
6580
|
+
isGenerating ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: 16 }),
|
|
6581
|
+
isGenerating ? "Generating..." : "Generate PDF"
|
|
4792
6582
|
]
|
|
4793
6583
|
}
|
|
4794
6584
|
),
|
|
4795
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6585
|
+
onSaveTemplate ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4796
6586
|
Button,
|
|
4797
6587
|
{
|
|
4798
|
-
onClick:
|
|
4799
|
-
disabled:
|
|
6588
|
+
onClick: handleSaveTemplate,
|
|
6589
|
+
disabled: isSaving || !editor || !markdown.trim(),
|
|
4800
6590
|
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
6591
|
children: [
|
|
4802
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.
|
|
4803
|
-
exportButtonText
|
|
6592
|
+
isSaving ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Save, { size: 16 }),
|
|
6593
|
+
isSaving ? "Saving..." : exportButtonText || "Save Template"
|
|
4804
6594
|
]
|
|
4805
6595
|
}
|
|
4806
|
-
)
|
|
6596
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs(Popover, { children: [
|
|
6597
|
+
/* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6598
|
+
Button,
|
|
6599
|
+
{
|
|
6600
|
+
disabled: !editor || !markdown.trim() && pdfPages.length === 0,
|
|
6601
|
+
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",
|
|
6602
|
+
children: [
|
|
6603
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileDown, { size: 16 }),
|
|
6604
|
+
"Export",
|
|
6605
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { size: 14 })
|
|
6606
|
+
]
|
|
6607
|
+
}
|
|
6608
|
+
) }),
|
|
6609
|
+
/* @__PURE__ */ jsxRuntime.jsxs(PopoverContent, { align: "start", className: "w-48 p-1", children: [
|
|
6610
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6611
|
+
"button",
|
|
6612
|
+
{
|
|
6613
|
+
onClick: handleExportMarkdown,
|
|
6614
|
+
disabled: !editor || !markdown.trim(),
|
|
6615
|
+
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",
|
|
6616
|
+
children: [
|
|
6617
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileText, { size: 16 }),
|
|
6618
|
+
"Export as MD"
|
|
6619
|
+
]
|
|
6620
|
+
}
|
|
6621
|
+
),
|
|
6622
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6623
|
+
"button",
|
|
6624
|
+
{
|
|
6625
|
+
onClick: handleExport,
|
|
6626
|
+
disabled: isGenerating || !editor || pdfPages.length === 0,
|
|
6627
|
+
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",
|
|
6628
|
+
children: [
|
|
6629
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileDown, { size: 16 }),
|
|
6630
|
+
"Export as PDF"
|
|
6631
|
+
]
|
|
6632
|
+
}
|
|
6633
|
+
)
|
|
6634
|
+
] })
|
|
6635
|
+
] }),
|
|
6636
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-1 ml-auto", children: [
|
|
6637
|
+
mdExportError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
6638
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
6639
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: mdExportError })
|
|
6640
|
+
] }),
|
|
6641
|
+
generationError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
6642
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
6643
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: generationError })
|
|
6644
|
+
] }),
|
|
6645
|
+
fieldWarnings.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-orange-600 max-w-[400px]", children: [
|
|
6646
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
6647
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "truncate", children: [
|
|
6648
|
+
fieldWarnings.length,
|
|
6649
|
+
" field",
|
|
6650
|
+
fieldWarnings.length !== 1 ? "s" : "",
|
|
6651
|
+
" had warnings during generation"
|
|
6652
|
+
] })
|
|
6653
|
+
] }),
|
|
6654
|
+
exportSuccess && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs text-emerald-600 max-w-[300px]", children: [
|
|
6655
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCircle2, { size: 14, className: "shrink-0" }),
|
|
6656
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: exportSuccess })
|
|
6657
|
+
] })
|
|
6658
|
+
] })
|
|
4807
6659
|
] })
|
|
4808
|
-
] })
|
|
4809
|
-
|
|
6660
|
+
] }),
|
|
6661
|
+
showPreview && !editorCollapsed && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-border" }),
|
|
6662
|
+
showPreview && /* @__PURE__ */ jsxRuntime.jsx(
|
|
6663
|
+
PreviewPanel,
|
|
6664
|
+
{
|
|
6665
|
+
pages: pdfPages,
|
|
6666
|
+
isGenerating,
|
|
6667
|
+
positionedFields,
|
|
6668
|
+
headerLeft: editorCollapsed ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
6669
|
+
Button,
|
|
6670
|
+
{
|
|
6671
|
+
variant: "ghost",
|
|
6672
|
+
size: "sm",
|
|
6673
|
+
className: "h-8 w-8 p-0",
|
|
6674
|
+
onClick: () => setEditorCollapsed(false),
|
|
6675
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PanelLeftOpen, { size: 14 })
|
|
6676
|
+
}
|
|
6677
|
+
) : void 0,
|
|
6678
|
+
className: "flex-1 border-0 rounded-none"
|
|
6679
|
+
}
|
|
6680
|
+
)
|
|
6681
|
+
] }) }),
|
|
4810
6682
|
showGenerateTab && activeTab === "generate" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4811
6683
|
GeneratePanel,
|
|
4812
6684
|
{
|
|
@@ -4814,7 +6686,10 @@ function DocumentGeneratorInner({
|
|
|
4814
6686
|
editorContent: editor?.getJSON(),
|
|
4815
6687
|
editorVariables: variables,
|
|
4816
6688
|
onGeneratePdf,
|
|
4817
|
-
initialBulkData
|
|
6689
|
+
initialBulkData,
|
|
6690
|
+
initialVariableValues,
|
|
6691
|
+
exportFileName,
|
|
6692
|
+
templateMode
|
|
4818
6693
|
}
|
|
4819
6694
|
),
|
|
4820
6695
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4955,11 +6830,735 @@ function EditorPanel({
|
|
|
4955
6830
|
] });
|
|
4956
6831
|
}
|
|
4957
6832
|
|
|
6833
|
+
// src/utils/xml-template-parser.ts
|
|
6834
|
+
function extractFieldRefs(expr) {
|
|
6835
|
+
const refs = [];
|
|
6836
|
+
const re = /\[([^\]]+)\](?:\.\[([^\]]+)\])?/g;
|
|
6837
|
+
let m;
|
|
6838
|
+
while ((m = re.exec(expr)) !== null) {
|
|
6839
|
+
if (m[2]) {
|
|
6840
|
+
refs.push(`${m[1]}.${m[2]}`);
|
|
6841
|
+
} else {
|
|
6842
|
+
refs.push(m[1]);
|
|
6843
|
+
}
|
|
6844
|
+
}
|
|
6845
|
+
return refs;
|
|
6846
|
+
}
|
|
6847
|
+
function fieldRefToVarName(ref, bandContext) {
|
|
6848
|
+
if (ref.includes(".")) {
|
|
6849
|
+
return labelToVarName(ref.replace(/\./g, "_"));
|
|
6850
|
+
}
|
|
6851
|
+
if (bandContext) {
|
|
6852
|
+
return labelToVarName(bandContext + "_" + ref);
|
|
6853
|
+
}
|
|
6854
|
+
return labelToVarName(ref);
|
|
6855
|
+
}
|
|
6856
|
+
function fieldRefToLabel(ref) {
|
|
6857
|
+
const parts = ref.split(".");
|
|
6858
|
+
const raw = parts[parts.length - 1];
|
|
6859
|
+
return raw.replace(/_/g, " ");
|
|
6860
|
+
}
|
|
6861
|
+
function resolveExpression(expr, registry, siblingLabel, bandContext) {
|
|
6862
|
+
const trimmed = expr.trim();
|
|
6863
|
+
const suppressZero = /<>\s*0/.test(trimmed);
|
|
6864
|
+
const szOpts = suppressZero ? { suppressZero: true } : void 0;
|
|
6865
|
+
const aggMatch = trimmed.match(
|
|
6866
|
+
/^\[([^\]]+)\]\.sum\(\[([^\]]+)\]\)$/i
|
|
6867
|
+
);
|
|
6868
|
+
if (aggMatch) {
|
|
6869
|
+
const fieldName = aggMatch[2];
|
|
6870
|
+
const varName = labelToVarName("workorder_" + fieldName);
|
|
6871
|
+
const label = fieldName.replace(/_/g, " ");
|
|
6872
|
+
return registry.register(varName, label, szOpts);
|
|
6873
|
+
}
|
|
6874
|
+
const concatSingle = trimmed.match(
|
|
6875
|
+
/^concat\(\s*'[^']*'\s*,\s*\[([^\]]+)\](?:\.\[([^\]]+)\])?\s*\)$/i
|
|
6876
|
+
);
|
|
6877
|
+
if (concatSingle) {
|
|
6878
|
+
const ref = concatSingle[2] ? `${concatSingle[1]}.${concatSingle[2]}` : concatSingle[1];
|
|
6879
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6880
|
+
const label = fieldRefToLabel(ref);
|
|
6881
|
+
return registry.register(varName, label, szOpts);
|
|
6882
|
+
}
|
|
6883
|
+
const concatMulti = trimmed.match(
|
|
6884
|
+
/^concat\(/i
|
|
6885
|
+
);
|
|
6886
|
+
if (concatMulti) {
|
|
6887
|
+
const refs = extractFieldRefs(trimmed);
|
|
6888
|
+
if (refs.length > 0) {
|
|
6889
|
+
const uniqueRefs = [...new Set(refs)];
|
|
6890
|
+
const tokens = uniqueRefs.map((ref) => {
|
|
6891
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6892
|
+
const label = fieldRefToLabel(ref);
|
|
6893
|
+
return registry.register(varName, label, szOpts);
|
|
6894
|
+
});
|
|
6895
|
+
return tokens.join(" ");
|
|
6896
|
+
}
|
|
6897
|
+
}
|
|
6898
|
+
const toLongMatch = trimmed.match(
|
|
6899
|
+
/^ToLong\(\s*\[([^\]]+)\](?:\.\[([^\]]+)\])?\s*\)$/i
|
|
6900
|
+
);
|
|
6901
|
+
if (toLongMatch) {
|
|
6902
|
+
const ref = toLongMatch[2] ? `${toLongMatch[1]}.${toLongMatch[2]}` : toLongMatch[1];
|
|
6903
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6904
|
+
const label = fieldRefToLabel(ref);
|
|
6905
|
+
return registry.register(varName, label, szOpts);
|
|
6906
|
+
}
|
|
6907
|
+
const fmtMatch = trimmed.match(
|
|
6908
|
+
/^FormatString\(\s*'[^']*'\s*,\s*(.+)\s*\)$/i
|
|
6909
|
+
);
|
|
6910
|
+
if (fmtMatch) {
|
|
6911
|
+
return resolveExpression(fmtMatch[1], registry, siblingLabel, bandContext);
|
|
6912
|
+
}
|
|
6913
|
+
const replaceMatch = trimmed.match(
|
|
6914
|
+
/^Replace\(\s*\[([^\]]+)\]/i
|
|
6915
|
+
);
|
|
6916
|
+
if (replaceMatch) {
|
|
6917
|
+
const ref = replaceMatch[1];
|
|
6918
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6919
|
+
const label = fieldRefToLabel(ref);
|
|
6920
|
+
return registry.register(varName, label, szOpts);
|
|
6921
|
+
}
|
|
6922
|
+
const iifLabelMatch = trimmed.match(
|
|
6923
|
+
/^iif\s*\(.+?,\s*'([^']+)'\s*,\s*\?\s*\)$/i
|
|
6924
|
+
);
|
|
6925
|
+
if (iifLabelMatch) {
|
|
6926
|
+
return iifLabelMatch[1];
|
|
6927
|
+
}
|
|
6928
|
+
const iifLabelElse = trimmed.match(
|
|
6929
|
+
/^iif\s*\(.+?,\s*'([^']+)'\s*,\s*'([^']+)'\s*\)$/i
|
|
6930
|
+
);
|
|
6931
|
+
if (iifLabelElse) {
|
|
6932
|
+
return iifLabelElse[1];
|
|
6933
|
+
}
|
|
6934
|
+
const iifEmptyElse = trimmed.match(
|
|
6935
|
+
/^[Ii]if\s*\(.+?,\s*'([^']+)'\s*,\s*''\s*\)$/i
|
|
6936
|
+
);
|
|
6937
|
+
if (iifEmptyElse) {
|
|
6938
|
+
return iifEmptyElse[1];
|
|
6939
|
+
}
|
|
6940
|
+
const nestedIifStaticLabel = trimmed.match(
|
|
6941
|
+
/^iif\s*\(.*,\s*[Ii]if\s*\([^,]+,\s*'([^']+)'\s*,/i
|
|
6942
|
+
);
|
|
6943
|
+
if (nestedIifStaticLabel) {
|
|
6944
|
+
return nestedIifStaticLabel[1];
|
|
6945
|
+
}
|
|
6946
|
+
const iifValueMatch = trimmed.match(
|
|
6947
|
+
/^iif\s*\(/i
|
|
6948
|
+
);
|
|
6949
|
+
if (iifValueMatch) {
|
|
6950
|
+
const refs = extractFieldRefs(trimmed);
|
|
6951
|
+
if (refs.length > 0) {
|
|
6952
|
+
const uniqueRefs = [...new Set(refs)];
|
|
6953
|
+
const innerAggMatch = trimmed.match(
|
|
6954
|
+
/\[([^\]]+)\]\.sum\(\[([^\]]+)\]\)/i
|
|
6955
|
+
);
|
|
6956
|
+
if (innerAggMatch) {
|
|
6957
|
+
const fieldName = innerAggMatch[2];
|
|
6958
|
+
const varName2 = labelToVarName("workorder_" + fieldName);
|
|
6959
|
+
const label2 = fieldName.replace(/_/g, " ");
|
|
6960
|
+
return registry.register(varName2, label2, szOpts);
|
|
6961
|
+
}
|
|
6962
|
+
const ref = uniqueRefs[uniqueRefs.length - 1];
|
|
6963
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6964
|
+
const label = fieldRefToLabel(ref);
|
|
6965
|
+
return registry.register(varName, label, szOpts);
|
|
6966
|
+
}
|
|
6967
|
+
const staticMatch = trimmed.match(/'([^']+)'/);
|
|
6968
|
+
if (staticMatch) return staticMatch[1];
|
|
6969
|
+
return "";
|
|
6970
|
+
}
|
|
6971
|
+
const arithmeticRefs = extractFieldRefs(trimmed);
|
|
6972
|
+
if (arithmeticRefs.length > 1 && /[+\-*/]/.test(trimmed)) {
|
|
6973
|
+
if (siblingLabel) {
|
|
6974
|
+
const cleanLabel = siblingLabel.replace(/[:\s]+$/, "");
|
|
6975
|
+
const varName = labelToVarName(
|
|
6976
|
+
(bandContext ? bandContext + "_" : "") + cleanLabel.replace(/\s+/g, "_")
|
|
6977
|
+
);
|
|
6978
|
+
return registry.register(varName, cleanLabel, szOpts);
|
|
6979
|
+
}
|
|
6980
|
+
const uniqueRefs = [...new Set(arithmeticRefs)];
|
|
6981
|
+
const tokens = uniqueRefs.map((ref) => {
|
|
6982
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6983
|
+
const label = fieldRefToLabel(ref);
|
|
6984
|
+
return registry.register(varName, label, szOpts);
|
|
6985
|
+
});
|
|
6986
|
+
return tokens.join(" ");
|
|
6987
|
+
}
|
|
6988
|
+
const simpleMatch = trimmed.match(
|
|
6989
|
+
/^\[([^\]]+)\](?:\.\[([^\]]+)\])?$/
|
|
6990
|
+
);
|
|
6991
|
+
if (simpleMatch) {
|
|
6992
|
+
const ref = simpleMatch[2] ? `${simpleMatch[1]}.${simpleMatch[2]}` : simpleMatch[1];
|
|
6993
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6994
|
+
const label = fieldRefToLabel(ref);
|
|
6995
|
+
return registry.register(varName, label, szOpts);
|
|
6996
|
+
}
|
|
6997
|
+
if (arithmeticRefs.length >= 1) {
|
|
6998
|
+
const ref = arithmeticRefs[arithmeticRefs.length - 1];
|
|
6999
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
7000
|
+
const label = fieldRefToLabel(ref);
|
|
7001
|
+
return registry.register(varName, label, szOpts);
|
|
7002
|
+
}
|
|
7003
|
+
return "";
|
|
7004
|
+
}
|
|
7005
|
+
var VariableRegistry = class {
|
|
7006
|
+
constructor() {
|
|
7007
|
+
__publicField(this, "map", /* @__PURE__ */ new Map());
|
|
7008
|
+
}
|
|
7009
|
+
register(varName, varLabel, options) {
|
|
7010
|
+
if (!this.map.has(varName)) {
|
|
7011
|
+
this.map.set(varName, { varName, varLabel, varDefault: "" });
|
|
7012
|
+
}
|
|
7013
|
+
const suppress = options?.suppressZero ? "|suppress:zero" : "";
|
|
7014
|
+
return `{{var|name:${varName}|label:${varLabel}${suppress}}}`;
|
|
7015
|
+
}
|
|
7016
|
+
getAll() {
|
|
7017
|
+
return Array.from(this.map.values());
|
|
7018
|
+
}
|
|
7019
|
+
};
|
|
7020
|
+
function parseLocationFloat(s) {
|
|
7021
|
+
if (!s) return { x: 0, y: 0 };
|
|
7022
|
+
const parts = s.split(",");
|
|
7023
|
+
return {
|
|
7024
|
+
x: parseFloat(parts[0]) || 0,
|
|
7025
|
+
y: parseFloat(parts[1]) || 0
|
|
7026
|
+
};
|
|
7027
|
+
}
|
|
7028
|
+
function parseSizeF(s) {
|
|
7029
|
+
if (!s) return { w: 0, h: 0 };
|
|
7030
|
+
const parts = s.split(",");
|
|
7031
|
+
return {
|
|
7032
|
+
w: parseFloat(parts[0]) || 0,
|
|
7033
|
+
h: parseFloat(parts[1]) || 0
|
|
7034
|
+
};
|
|
7035
|
+
}
|
|
7036
|
+
function extractControls(bandEl) {
|
|
7037
|
+
const controlsEl = bandEl.querySelector(":scope > Controls");
|
|
7038
|
+
if (!controlsEl) return [];
|
|
7039
|
+
const items = [];
|
|
7040
|
+
for (const child of Array.from(controlsEl.children)) {
|
|
7041
|
+
items.push(extractControl(child));
|
|
7042
|
+
}
|
|
7043
|
+
return items;
|
|
7044
|
+
}
|
|
7045
|
+
function extractControl(el) {
|
|
7046
|
+
const ctrlType = el.getAttribute("ControlType") || "";
|
|
7047
|
+
const name = el.getAttribute("Name") || "";
|
|
7048
|
+
const text = el.getAttribute("Text") || "";
|
|
7049
|
+
const loc = parseLocationFloat(el.getAttribute("LocationFloat"));
|
|
7050
|
+
const size = parseSizeF(el.getAttribute("SizeF"));
|
|
7051
|
+
const formatString = el.getAttribute("TextFormatString") || null;
|
|
7052
|
+
let expression = null;
|
|
7053
|
+
const bindingsEl = el.querySelector(":scope > ExpressionBindings");
|
|
7054
|
+
if (bindingsEl) {
|
|
7055
|
+
for (const item of Array.from(bindingsEl.children)) {
|
|
7056
|
+
if (item.getAttribute("PropertyName") === "Text" && item.getAttribute("EventName") === "BeforePrint") {
|
|
7057
|
+
expression = item.getAttribute("Expression");
|
|
7058
|
+
break;
|
|
7059
|
+
}
|
|
7060
|
+
}
|
|
7061
|
+
}
|
|
7062
|
+
const children = [];
|
|
7063
|
+
if (ctrlType === "XRPanel") {
|
|
7064
|
+
const panelControls = extractControls(el);
|
|
7065
|
+
children.push(...panelControls);
|
|
7066
|
+
}
|
|
7067
|
+
return {
|
|
7068
|
+
name,
|
|
7069
|
+
type: ctrlType,
|
|
7070
|
+
text,
|
|
7071
|
+
x: loc.x,
|
|
7072
|
+
y: loc.y,
|
|
7073
|
+
width: size.w,
|
|
7074
|
+
expression,
|
|
7075
|
+
formatString,
|
|
7076
|
+
children
|
|
7077
|
+
};
|
|
7078
|
+
}
|
|
7079
|
+
function groupByRow(controls, threshold = 8) {
|
|
7080
|
+
const sorted = [...controls].sort((a, b) => a.y - b.y);
|
|
7081
|
+
const rows = [];
|
|
7082
|
+
for (const ctrl of sorted) {
|
|
7083
|
+
const lastRow = rows[rows.length - 1];
|
|
7084
|
+
if (lastRow && Math.abs(ctrl.y - lastRow.y) < threshold) {
|
|
7085
|
+
lastRow.controls.push(ctrl);
|
|
7086
|
+
} else {
|
|
7087
|
+
rows.push({ y: ctrl.y, controls: [ctrl] });
|
|
7088
|
+
}
|
|
7089
|
+
}
|
|
7090
|
+
for (const row of rows) {
|
|
7091
|
+
row.controls.sort((a, b) => a.x - b.x);
|
|
7092
|
+
}
|
|
7093
|
+
return rows;
|
|
7094
|
+
}
|
|
7095
|
+
function isStaticLabel(ctrl) {
|
|
7096
|
+
return ctrl.type === "XRLabel" && !ctrl.expression;
|
|
7097
|
+
}
|
|
7098
|
+
function isBound(ctrl) {
|
|
7099
|
+
return ctrl.type === "XRLabel" && !!ctrl.expression;
|
|
7100
|
+
}
|
|
7101
|
+
function computeSplitPercent(leftControls, rightControls, contentWidth) {
|
|
7102
|
+
const leftExtent = leftControls.reduce(
|
|
7103
|
+
(max, c) => Math.max(max, c.x + c.width),
|
|
7104
|
+
0
|
|
7105
|
+
);
|
|
7106
|
+
const rightMinX = rightControls.reduce(
|
|
7107
|
+
(min, c) => Math.min(min, c.x),
|
|
7108
|
+
contentWidth
|
|
7109
|
+
);
|
|
7110
|
+
const rightMaxExtent = rightControls.reduce(
|
|
7111
|
+
(max, c) => Math.max(max, c.x + c.width),
|
|
7112
|
+
0
|
|
7113
|
+
);
|
|
7114
|
+
const rightExtent = rightMaxExtent - rightMinX;
|
|
7115
|
+
if (leftExtent + rightExtent === 0) return 50;
|
|
7116
|
+
return Math.round(leftExtent / (leftExtent + rightExtent) * 100);
|
|
7117
|
+
}
|
|
7118
|
+
function renderTopMarginBand(controls, registry, contentWidth, calculatedFieldNames) {
|
|
7119
|
+
const lines = [];
|
|
7120
|
+
const leftControls = controls.filter((c) => c.x < 437);
|
|
7121
|
+
const rightControls = controls.filter((c) => c.x >= 437);
|
|
7122
|
+
const leftEntries = [];
|
|
7123
|
+
if (leftControls.length > 0) {
|
|
7124
|
+
const leftRows = groupByRow(leftControls);
|
|
7125
|
+
for (const row of leftRows) {
|
|
7126
|
+
const parts = [];
|
|
7127
|
+
for (const ctrl of row.controls) {
|
|
7128
|
+
if (isBound(ctrl)) {
|
|
7129
|
+
let resolved = resolveExpression(ctrl.expression, registry, void 0, "workorder");
|
|
7130
|
+
if (ctrl.formatString && /\(###\)\s*###\s*-\s*####/.test(ctrl.formatString) && resolved.startsWith("{{var|")) {
|
|
7131
|
+
resolved = resolved.replace("}}", "|format:phone}}");
|
|
7132
|
+
}
|
|
7133
|
+
if (resolved) parts.push(resolved);
|
|
7134
|
+
} else if (isStaticLabel(ctrl)) {
|
|
7135
|
+
const text = ctrl.text.trim();
|
|
7136
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
7137
|
+
parts.push(text);
|
|
7138
|
+
}
|
|
7139
|
+
}
|
|
7140
|
+
}
|
|
7141
|
+
if (parts.length > 0) {
|
|
7142
|
+
leftEntries.push(parts.join(" "));
|
|
7143
|
+
}
|
|
7144
|
+
}
|
|
7145
|
+
}
|
|
7146
|
+
const rightEntries = [];
|
|
7147
|
+
if (rightControls.length > 0) {
|
|
7148
|
+
const rightRows = groupByRow(rightControls);
|
|
7149
|
+
for (const row of rightRows) {
|
|
7150
|
+
let label = "";
|
|
7151
|
+
let value = "";
|
|
7152
|
+
for (const ctrl of row.controls) {
|
|
7153
|
+
if (isStaticLabel(ctrl)) {
|
|
7154
|
+
const text = ctrl.text.trim();
|
|
7155
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
7156
|
+
label = text;
|
|
7157
|
+
}
|
|
7158
|
+
} else if (isBound(ctrl)) {
|
|
7159
|
+
value = resolveExpression(ctrl.expression, registry, label, "workorder");
|
|
7160
|
+
if (ctrl.expression) {
|
|
7161
|
+
const refs = extractFieldRefs(ctrl.expression);
|
|
7162
|
+
if (refs.some((ref) => calculatedFieldNames.has(ref)) && value.startsWith("{{var|")) {
|
|
7163
|
+
value = value.replace("}}", "|suppress:zero}}");
|
|
7164
|
+
}
|
|
7165
|
+
}
|
|
7166
|
+
if (ctrl.formatString && /\(###\)\s*###\s*-\s*####/.test(ctrl.formatString) && value.startsWith("{{var|")) {
|
|
7167
|
+
value = value.replace("}}", "|format:phone}}");
|
|
7168
|
+
}
|
|
7169
|
+
}
|
|
7170
|
+
}
|
|
7171
|
+
if (label || value) {
|
|
7172
|
+
rightEntries.push({ label, value, y: row.y });
|
|
7173
|
+
}
|
|
7174
|
+
}
|
|
7175
|
+
}
|
|
7176
|
+
if (leftEntries.length > 0 || rightEntries.length > 0) {
|
|
7177
|
+
const outerSplit = computeSplitPercent(leftControls, rightControls, contentWidth);
|
|
7178
|
+
lines.push(`:::columns{split:${outerSplit}}`);
|
|
7179
|
+
const leftMinY = leftControls.length > 0 ? Math.min(...leftControls.map((c) => c.y)) : 0;
|
|
7180
|
+
const rightMinY = rightControls.length > 0 ? Math.min(...rightControls.map((c) => c.y)) : 0;
|
|
7181
|
+
const xmlLineHeight = 17;
|
|
7182
|
+
const leftPadTop = leftMinY > rightMinY ? Math.round((leftMinY - rightMinY) / xmlLineHeight) : 0;
|
|
7183
|
+
const rightPadTop = rightMinY > leftMinY ? Math.round((rightMinY - leftMinY) / xmlLineHeight) : 0;
|
|
7184
|
+
const leftColTag = leftPadTop ? `:::col{padTop:${leftPadTop}}` : ":::col";
|
|
7185
|
+
lines.push(leftColTag);
|
|
7186
|
+
for (const entry of leftEntries) {
|
|
7187
|
+
lines.push(entry);
|
|
7188
|
+
}
|
|
7189
|
+
lines.push(":::");
|
|
7190
|
+
const rightColTag = rightPadTop ? `:::col{padTop:${rightPadTop}}` : ":::col";
|
|
7191
|
+
lines.push(rightColTag);
|
|
7192
|
+
if (rightEntries.length > 0) {
|
|
7193
|
+
lines.push("| | |");
|
|
7194
|
+
lines.push("|---|---|");
|
|
7195
|
+
let prevY = -Infinity;
|
|
7196
|
+
for (const entry of rightEntries) {
|
|
7197
|
+
if (prevY > -Infinity && entry.y - prevY > 25) {
|
|
7198
|
+
lines.push("| | |");
|
|
7199
|
+
}
|
|
7200
|
+
const label = entry.label || "";
|
|
7201
|
+
const colon = entry.label ? ":" : "";
|
|
7202
|
+
const value = entry.value ? ` ${entry.value}` : "";
|
|
7203
|
+
lines.push(`| ${label} | ${colon}${value} |`);
|
|
7204
|
+
prevY = entry.y;
|
|
7205
|
+
}
|
|
7206
|
+
}
|
|
7207
|
+
lines.push(":::");
|
|
7208
|
+
lines.push(":::");
|
|
7209
|
+
lines.push("");
|
|
7210
|
+
}
|
|
7211
|
+
return lines.join("\n");
|
|
7212
|
+
}
|
|
7213
|
+
function renderReportHeaderBand(controls, registry, contentWidth) {
|
|
7214
|
+
const lines = [];
|
|
7215
|
+
const leftControls = controls.filter((c) => c.x < 437);
|
|
7216
|
+
const rightControls = controls.filter((c) => c.x >= 437);
|
|
7217
|
+
const leftEntries = [];
|
|
7218
|
+
if (leftControls.length > 0) {
|
|
7219
|
+
const leftRows = groupByRow(leftControls);
|
|
7220
|
+
for (const row of leftRows) {
|
|
7221
|
+
const parts = [];
|
|
7222
|
+
for (const ctrl of row.controls) {
|
|
7223
|
+
if (isBound(ctrl)) {
|
|
7224
|
+
const resolved = resolveExpression(ctrl.expression, registry, void 0, "workorder");
|
|
7225
|
+
if (resolved) parts.push(resolved);
|
|
7226
|
+
} else if (isStaticLabel(ctrl)) {
|
|
7227
|
+
const text = ctrl.text.trim();
|
|
7228
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
7229
|
+
parts.push(text);
|
|
7230
|
+
}
|
|
7231
|
+
}
|
|
7232
|
+
}
|
|
7233
|
+
if (parts.length > 0) {
|
|
7234
|
+
leftEntries.push(parts.join(" "));
|
|
7235
|
+
}
|
|
7236
|
+
}
|
|
7237
|
+
}
|
|
7238
|
+
const rightEntries = [];
|
|
7239
|
+
if (rightControls.length > 0) {
|
|
7240
|
+
const rightRows = groupByRow(rightControls);
|
|
7241
|
+
for (const row of rightRows) {
|
|
7242
|
+
let label = "";
|
|
7243
|
+
let value = "";
|
|
7244
|
+
for (const ctrl of row.controls) {
|
|
7245
|
+
if (isStaticLabel(ctrl)) {
|
|
7246
|
+
const text = ctrl.text.trim();
|
|
7247
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
7248
|
+
label = text;
|
|
7249
|
+
}
|
|
7250
|
+
} else if (isBound(ctrl)) {
|
|
7251
|
+
value = resolveExpression(ctrl.expression, registry, void 0, "workorder");
|
|
7252
|
+
if (ctrl.formatString && /\(###\)\s*###\s*-\s*####/.test(ctrl.formatString) && value.startsWith("{{var|")) {
|
|
7253
|
+
value = value.replace("}}", "|format:phone}}");
|
|
7254
|
+
}
|
|
7255
|
+
}
|
|
7256
|
+
}
|
|
7257
|
+
if (label || value) {
|
|
7258
|
+
rightEntries.push({ label, value });
|
|
7259
|
+
}
|
|
7260
|
+
}
|
|
7261
|
+
}
|
|
7262
|
+
if (leftEntries.length > 0 || rightEntries.length > 0) {
|
|
7263
|
+
const outerSplit = computeSplitPercent(leftControls, rightControls, contentWidth);
|
|
7264
|
+
lines.push(`:::columns{split:${outerSplit}}`);
|
|
7265
|
+
lines.push(":::col");
|
|
7266
|
+
for (const entry of leftEntries) {
|
|
7267
|
+
lines.push(entry);
|
|
7268
|
+
}
|
|
7269
|
+
lines.push(":::");
|
|
7270
|
+
lines.push(":::col");
|
|
7271
|
+
lines.push("| | |");
|
|
7272
|
+
lines.push("|---|---|");
|
|
7273
|
+
for (const entry of rightEntries) {
|
|
7274
|
+
const label = entry.label || "";
|
|
7275
|
+
const value = entry.value ? ` ${entry.value}` : "";
|
|
7276
|
+
lines.push(`| ${label} |${value} |`);
|
|
7277
|
+
}
|
|
7278
|
+
lines.push(":::");
|
|
7279
|
+
lines.push(":::");
|
|
7280
|
+
lines.push("");
|
|
7281
|
+
}
|
|
7282
|
+
return lines.join("\n");
|
|
7283
|
+
}
|
|
7284
|
+
function renderOperationsSection(pageHeaderControls, detailControls, groupFooterControls, registry, contentWidth) {
|
|
7285
|
+
const lines = [];
|
|
7286
|
+
const headerTexts = [];
|
|
7287
|
+
for (const ctrl of [...pageHeaderControls].sort((a, b) => a.x - b.x)) {
|
|
7288
|
+
if (isStaticLabel(ctrl)) {
|
|
7289
|
+
const text = ctrl.text.trim();
|
|
7290
|
+
if (text) headerTexts.push(text);
|
|
7291
|
+
}
|
|
7292
|
+
}
|
|
7293
|
+
const titleText = headerTexts.join(" ");
|
|
7294
|
+
lines.push(`:::panel{title:${titleText}|headerStyle:dark|border:none}`);
|
|
7295
|
+
lines.push(":::");
|
|
7296
|
+
lines.push(":::repeat{data:operations}");
|
|
7297
|
+
const sortedDetail = [...detailControls].sort((a, b) => a.x - b.x);
|
|
7298
|
+
const detailParts = [];
|
|
7299
|
+
for (const ctrl of sortedDetail) {
|
|
7300
|
+
if (isBound(ctrl)) {
|
|
7301
|
+
const resolved = resolveExpression(ctrl.expression, registry, void 0, "operation");
|
|
7302
|
+
if (resolved) detailParts.push(resolved);
|
|
7303
|
+
}
|
|
7304
|
+
}
|
|
7305
|
+
if (detailParts.length > 0) {
|
|
7306
|
+
if (detailParts.length >= 2) {
|
|
7307
|
+
lines.push(":::columns{split:12}");
|
|
7308
|
+
lines.push(":::col");
|
|
7309
|
+
lines.push(`**${detailParts[0]}**`);
|
|
7310
|
+
lines.push(":::");
|
|
7311
|
+
lines.push(":::col");
|
|
7312
|
+
lines.push(`**${detailParts.slice(1).join(" ")}**`);
|
|
7313
|
+
lines.push(":::");
|
|
7314
|
+
lines.push(":::");
|
|
7315
|
+
} else {
|
|
7316
|
+
lines.push(`**${detailParts[0]}**`);
|
|
7317
|
+
}
|
|
7318
|
+
lines.push("");
|
|
7319
|
+
}
|
|
7320
|
+
if (groupFooterControls.length > 0) {
|
|
7321
|
+
const subtotalsMarkdown = renderGroupFooterBand(groupFooterControls, registry);
|
|
7322
|
+
if (subtotalsMarkdown) lines.push(subtotalsMarkdown);
|
|
7323
|
+
}
|
|
7324
|
+
lines.push("---");
|
|
7325
|
+
lines.push(":::");
|
|
7326
|
+
lines.push("");
|
|
7327
|
+
return lines.join("\n");
|
|
7328
|
+
}
|
|
7329
|
+
function renderGroupFooterBand(controls, registry, _contentWidth) {
|
|
7330
|
+
const rows = groupByRow(controls);
|
|
7331
|
+
const entries = [];
|
|
7332
|
+
for (const row of rows) {
|
|
7333
|
+
const labels = row.controls.filter((c) => c.type === "XRLabel");
|
|
7334
|
+
if (labels.length === 0) continue;
|
|
7335
|
+
let label = "";
|
|
7336
|
+
let value = "";
|
|
7337
|
+
const sorted = [...labels].sort((a, b) => a.x - b.x);
|
|
7338
|
+
for (const ctrl of sorted) {
|
|
7339
|
+
if (isStaticLabel(ctrl)) {
|
|
7340
|
+
const text = ctrl.text.trim();
|
|
7341
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
7342
|
+
label = text;
|
|
7343
|
+
}
|
|
7344
|
+
} else if (isBound(ctrl)) {
|
|
7345
|
+
const resolved = resolveExpression(ctrl.expression, registry, label, "operation");
|
|
7346
|
+
if (resolved) {
|
|
7347
|
+
if (!resolved.startsWith("{{var|")) {
|
|
7348
|
+
if (!label) label = resolved;
|
|
7349
|
+
} else {
|
|
7350
|
+
value = resolved;
|
|
7351
|
+
}
|
|
7352
|
+
}
|
|
7353
|
+
}
|
|
7354
|
+
}
|
|
7355
|
+
if (label && value) {
|
|
7356
|
+
entries.push({ label, value });
|
|
7357
|
+
}
|
|
7358
|
+
}
|
|
7359
|
+
if (entries.length === 0) return "";
|
|
7360
|
+
const lines = [];
|
|
7361
|
+
lines.push(":::subtotals");
|
|
7362
|
+
for (const entry of entries) {
|
|
7363
|
+
lines.push(`**${entry.label}** ${entry.value}`);
|
|
7364
|
+
}
|
|
7365
|
+
lines.push(":::");
|
|
7366
|
+
lines.push("");
|
|
7367
|
+
return lines.join("\n");
|
|
7368
|
+
}
|
|
7369
|
+
function renderReportFooterBand(controls, registry) {
|
|
7370
|
+
const lines = [];
|
|
7371
|
+
const panel = controls.find((c) => c.type === "XRPanel");
|
|
7372
|
+
const thankYouLabel = controls.find(
|
|
7373
|
+
(c) => c.type === "XRLabel" && c.text.includes("Thank You")
|
|
7374
|
+
);
|
|
7375
|
+
const priceRows = [];
|
|
7376
|
+
if (panel && panel.children.length > 0) {
|
|
7377
|
+
const rows = groupByRow(panel.children, 15);
|
|
7378
|
+
for (const row of rows) {
|
|
7379
|
+
const labels = row.controls.filter((c) => c.type === "XRLabel");
|
|
7380
|
+
if (labels.length === 0) continue;
|
|
7381
|
+
let label = "";
|
|
7382
|
+
let value = "";
|
|
7383
|
+
const sorted = [...labels].sort((a, b) => a.x - b.x);
|
|
7384
|
+
for (const ctrl of sorted) {
|
|
7385
|
+
if (isStaticLabel(ctrl) && ctrl.text.includes("Estimate Price Summary")) {
|
|
7386
|
+
continue;
|
|
7387
|
+
}
|
|
7388
|
+
const isLabelPos = ctrl.x < 150;
|
|
7389
|
+
if (isStaticLabel(ctrl)) {
|
|
7390
|
+
const text = ctrl.text.trim();
|
|
7391
|
+
if (isLabelPos && text && !text.match(/^label\d+$/) && text !== "0.00") {
|
|
7392
|
+
label = text;
|
|
7393
|
+
}
|
|
7394
|
+
} else if (isBound(ctrl)) {
|
|
7395
|
+
const resolved = resolveExpression(ctrl.expression, registry, label || void 0, "workorder");
|
|
7396
|
+
if (resolved) {
|
|
7397
|
+
if (isLabelPos) {
|
|
7398
|
+
if (!label) label = resolved;
|
|
7399
|
+
} else {
|
|
7400
|
+
if (!value) value = resolved;
|
|
7401
|
+
}
|
|
7402
|
+
}
|
|
7403
|
+
}
|
|
7404
|
+
}
|
|
7405
|
+
if (label && value) {
|
|
7406
|
+
priceRows.push({ label, value });
|
|
7407
|
+
} else if (value) {
|
|
7408
|
+
priceRows.push({ label: "", value });
|
|
7409
|
+
}
|
|
7410
|
+
}
|
|
7411
|
+
}
|
|
7412
|
+
lines.push(":::columns{split:40|padX:20}");
|
|
7413
|
+
lines.push(":::col{padTop:1}");
|
|
7414
|
+
if (thankYouLabel) {
|
|
7415
|
+
lines.push(`## ***${thankYouLabel.text.trim()}***`);
|
|
7416
|
+
}
|
|
7417
|
+
lines.push(":::");
|
|
7418
|
+
lines.push(":::col{padTop:1}");
|
|
7419
|
+
lines.push(":::panel{title:Estimate Price Summary|border:solid|headerStyle:dark}");
|
|
7420
|
+
if (priceRows.length > 0) {
|
|
7421
|
+
lines.push(":::subtotals");
|
|
7422
|
+
for (const row of priceRows) {
|
|
7423
|
+
const label = row.label ? `**${row.label}**` : "";
|
|
7424
|
+
lines.push(`${label} ${row.value}`);
|
|
7425
|
+
}
|
|
7426
|
+
lines.push(":::");
|
|
7427
|
+
}
|
|
7428
|
+
lines.push(":::");
|
|
7429
|
+
lines.push(":::");
|
|
7430
|
+
lines.push(":::");
|
|
7431
|
+
lines.push("");
|
|
7432
|
+
return lines.join("\n");
|
|
7433
|
+
}
|
|
7434
|
+
function parseXmlTemplate(xmlString) {
|
|
7435
|
+
const warnings = [];
|
|
7436
|
+
const registry = new VariableRegistry();
|
|
7437
|
+
const parser = new DOMParser();
|
|
7438
|
+
const doc = parser.parseFromString(xmlString, "text/xml");
|
|
7439
|
+
const parseError = doc.querySelector("parsererror");
|
|
7440
|
+
if (parseError) {
|
|
7441
|
+
return {
|
|
7442
|
+
markdown: "",
|
|
7443
|
+
variables: [],
|
|
7444
|
+
reportName: "",
|
|
7445
|
+
warnings: ["XML parse error: " + (parseError.textContent || "Unknown error")]
|
|
7446
|
+
};
|
|
7447
|
+
}
|
|
7448
|
+
const root = doc.documentElement;
|
|
7449
|
+
const reportName = root.getAttribute("DisplayName") || root.getAttribute("Name") || "Report";
|
|
7450
|
+
const pageWidth = parseInt(root.getAttribute("PageWidth") || "850", 10);
|
|
7451
|
+
const marginsStr = root.getAttribute("Margins") || "50,50,250,50";
|
|
7452
|
+
const marginParts = marginsStr.split(",").map(Number);
|
|
7453
|
+
const leftMargin = marginParts[0] || 50;
|
|
7454
|
+
const rightMargin = marginParts[1] || 50;
|
|
7455
|
+
const contentWidth = pageWidth - leftMargin - rightMargin;
|
|
7456
|
+
const calculatedFieldNames = /* @__PURE__ */ new Set();
|
|
7457
|
+
const calcFieldsEl = root.querySelector(":scope > CalculatedFields");
|
|
7458
|
+
if (calcFieldsEl) {
|
|
7459
|
+
for (const item of Array.from(calcFieldsEl.children)) {
|
|
7460
|
+
const name = item.getAttribute("Name");
|
|
7461
|
+
if (name) calculatedFieldNames.add(name);
|
|
7462
|
+
}
|
|
7463
|
+
}
|
|
7464
|
+
const markdownSections = [];
|
|
7465
|
+
const watermarkEl = root.querySelector("Watermarks > Item1");
|
|
7466
|
+
if (watermarkEl) {
|
|
7467
|
+
const wmText = watermarkEl.getAttribute("Text");
|
|
7468
|
+
if (wmText) {
|
|
7469
|
+
markdownSections.push(`:::watermark{text:${wmText}|opacity:0.15|angle:45}`);
|
|
7470
|
+
markdownSections.push("");
|
|
7471
|
+
}
|
|
7472
|
+
}
|
|
7473
|
+
const bandsEl = root.querySelector(":scope > Bands");
|
|
7474
|
+
if (!bandsEl) {
|
|
7475
|
+
warnings.push("No <Bands> element found in the XML");
|
|
7476
|
+
return {
|
|
7477
|
+
markdown: "",
|
|
7478
|
+
variables: registry.getAll(),
|
|
7479
|
+
reportName,
|
|
7480
|
+
warnings
|
|
7481
|
+
};
|
|
7482
|
+
}
|
|
7483
|
+
let topMarginControls = [];
|
|
7484
|
+
let reportHeaderControls = [];
|
|
7485
|
+
let pageHeaderControls = [];
|
|
7486
|
+
let detailControls = [];
|
|
7487
|
+
let groupFooterControls = [];
|
|
7488
|
+
let reportFooterControls = [];
|
|
7489
|
+
for (const bandItem of Array.from(bandsEl.children)) {
|
|
7490
|
+
const ctrlType = bandItem.getAttribute("ControlType") || "";
|
|
7491
|
+
if (ctrlType === "TopMarginBand") {
|
|
7492
|
+
topMarginControls = extractControls(bandItem);
|
|
7493
|
+
} else if (ctrlType === "ReportHeaderBand") {
|
|
7494
|
+
reportHeaderControls = extractControls(bandItem);
|
|
7495
|
+
} else if (ctrlType === "PageHeaderBand") {
|
|
7496
|
+
pageHeaderControls = extractControls(bandItem);
|
|
7497
|
+
} else if (ctrlType === "DetailReportBand") {
|
|
7498
|
+
const nestedBands = bandItem.querySelector(":scope > Bands");
|
|
7499
|
+
if (nestedBands) {
|
|
7500
|
+
for (const nested of Array.from(nestedBands.children)) {
|
|
7501
|
+
const nestedType = nested.getAttribute("ControlType") || "";
|
|
7502
|
+
if (nestedType === "DetailBand") {
|
|
7503
|
+
detailControls = extractControls(nested);
|
|
7504
|
+
} else if (nestedType === "GroupFooterBand") {
|
|
7505
|
+
groupFooterControls = extractControls(nested);
|
|
7506
|
+
}
|
|
7507
|
+
}
|
|
7508
|
+
}
|
|
7509
|
+
} else if (ctrlType === "ReportFooterBand") {
|
|
7510
|
+
reportFooterControls = extractControls(bandItem);
|
|
7511
|
+
}
|
|
7512
|
+
}
|
|
7513
|
+
if (topMarginControls.length > 0) {
|
|
7514
|
+
markdownSections.push(renderTopMarginBand(topMarginControls, registry, contentWidth, calculatedFieldNames));
|
|
7515
|
+
}
|
|
7516
|
+
if (reportHeaderControls.length > 0) {
|
|
7517
|
+
markdownSections.push(
|
|
7518
|
+
renderReportHeaderBand(reportHeaderControls, registry, contentWidth)
|
|
7519
|
+
);
|
|
7520
|
+
}
|
|
7521
|
+
if (pageHeaderControls.length > 0 || detailControls.length > 0) {
|
|
7522
|
+
markdownSections.push(
|
|
7523
|
+
renderOperationsSection(
|
|
7524
|
+
pageHeaderControls,
|
|
7525
|
+
detailControls,
|
|
7526
|
+
groupFooterControls,
|
|
7527
|
+
registry)
|
|
7528
|
+
);
|
|
7529
|
+
}
|
|
7530
|
+
if (reportFooterControls.length > 0) {
|
|
7531
|
+
markdownSections.push(
|
|
7532
|
+
renderReportFooterBand(reportFooterControls, registry)
|
|
7533
|
+
);
|
|
7534
|
+
}
|
|
7535
|
+
const markdown = markdownSections.join("\n").replace(/\n{4,}/g, "\n\n\n");
|
|
7536
|
+
return {
|
|
7537
|
+
markdown,
|
|
7538
|
+
variables: registry.getAll(),
|
|
7539
|
+
reportName,
|
|
7540
|
+
warnings
|
|
7541
|
+
};
|
|
7542
|
+
}
|
|
7543
|
+
|
|
4958
7544
|
exports.DocumentGenerator = DocumentGenerator;
|
|
4959
7545
|
exports.EditorPanel = EditorPanel;
|
|
4960
7546
|
exports.FormFieldType = FormFieldType;
|
|
7547
|
+
exports.ImageNode = ImageNode;
|
|
4961
7548
|
exports.PreviewPanel = PreviewPanel;
|
|
7549
|
+
exports.RepeatNode = RepeatNode;
|
|
7550
|
+
exports.SubtotalsNode = SubtotalsNode;
|
|
4962
7551
|
exports.ThemeProvider = ThemeProvider;
|
|
7552
|
+
exports.expandBlockVariables = expandBlockVariables;
|
|
7553
|
+
exports.expandRepeatContent = expandRepeatContent;
|
|
7554
|
+
exports.extractVariablesFromContent = extractVariablesFromContent;
|
|
7555
|
+
exports.generatePdfFromMarkdown = generatePdfFromMarkdown;
|
|
7556
|
+
exports.generatePdfFromTiptap = generatePdfFromTiptap;
|
|
7557
|
+
exports.isZeroLike = isZeroLike;
|
|
7558
|
+
exports.markdownToTiptap = markdownToTiptap;
|
|
7559
|
+
exports.parseXmlTemplate = parseXmlTemplate;
|
|
7560
|
+
exports.suppressZeroContent = suppressZeroContent;
|
|
7561
|
+
exports.tiptapToMarkdown = tiptapToMarkdown;
|
|
4963
7562
|
exports.useDocumentGenerator = useDocumentGenerator;
|
|
4964
7563
|
exports.useTheme = useTheme;
|
|
4965
7564
|
//# sourceMappingURL=index.js.map
|