@signiphi/pdf-compose 0.1.0-beta.0 → 0.1.0-beta.10
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/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/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 +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3083 -564
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3070 -567
- 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 +349 -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.mjs
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import * as React12 from 'react';
|
|
2
2
|
import React12__default, { createContext, useCallback, useState, useRef, useEffect, useMemo, useContext } from 'react';
|
|
3
|
-
import { AlertTriangle, RefreshCw, FileText, ChevronDown, Circle, CheckSquare, Calendar, Hash, PenTool, Type, Braces, ChevronLeft, ChevronRight, ZoomOut, ZoomIn, Maximize2, Loader2, Plus,
|
|
3
|
+
import { AlertTriangle, RefreshCw, FileText, ChevronDown, Circle, CheckSquare, Calendar, Hash, PenTool, Type, Braces, Droplets, ChevronLeft, ChevronRight, ZoomOut, ZoomIn, Maximize2, Loader2, Plus, Save, FileDown, CheckCircle2, PanelLeftOpen, PanelLeftClose, Undo, Redo, Bold, Italic, Underline as Underline$1, Code, Heading1, Heading2, Heading3, List, ListOrdered, Quote, Minus, AlignLeft, AlignCenter, AlignRight, Trash2, X, PenLine, Upload, Search, WrapText, Download } from 'lucide-react';
|
|
4
4
|
import { ReactNodeViewRenderer, NodeViewWrapper, useEditor, EditorContent } from '@tiptap/react';
|
|
5
5
|
import StarterKit from '@tiptap/starter-kit';
|
|
6
6
|
import Underline from '@tiptap/extension-underline';
|
|
7
7
|
import TextAlign from '@tiptap/extension-text-align';
|
|
8
8
|
import Placeholder from '@tiptap/extension-placeholder';
|
|
9
|
+
import Table from '@tiptap/extension-table';
|
|
10
|
+
import TableRow from '@tiptap/extension-table-row';
|
|
11
|
+
import TableCell from '@tiptap/extension-table-cell';
|
|
12
|
+
import TableHeader from '@tiptap/extension-table-header';
|
|
9
13
|
import { Node, mergeAttributes } from '@tiptap/core';
|
|
10
14
|
import { clsx } from 'clsx';
|
|
11
15
|
import { twMerge } from 'tailwind-merge';
|
|
12
16
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
13
17
|
import { v4 } from 'uuid';
|
|
14
|
-
import { PDFDocument, StandardFonts, rgb, PDFName as PDFName$1, PDFString as PDFString$1 } from 'pdf-lib';
|
|
18
|
+
import { PDFDocument, StandardFonts, rgb, degrees, PDFName as PDFName$1, PDFString as PDFString$1 } from 'pdf-lib';
|
|
15
19
|
import { NodeSelection } from '@tiptap/pm/state';
|
|
16
20
|
import { Slot } from '@radix-ui/react-slot';
|
|
17
21
|
import { cva } from 'class-variance-authority';
|
|
@@ -21,7 +25,7 @@ import * as LabelPrimitive from '@radix-ui/react-label';
|
|
|
21
25
|
|
|
22
26
|
var __defProp = Object.defineProperty;
|
|
23
27
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
24
|
-
var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);
|
|
28
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
25
29
|
function cn(...inputs) {
|
|
26
30
|
return twMerge(clsx(inputs));
|
|
27
31
|
}
|
|
@@ -213,7 +217,10 @@ var VariableNode = Node.create({
|
|
|
213
217
|
return {
|
|
214
218
|
varName: { default: "" },
|
|
215
219
|
varLabel: { default: "" },
|
|
216
|
-
varDefault: { default: "" }
|
|
220
|
+
varDefault: { default: "" },
|
|
221
|
+
format: { default: "" },
|
|
222
|
+
suppressZero: { default: "" },
|
|
223
|
+
block: { default: "" }
|
|
217
224
|
};
|
|
218
225
|
},
|
|
219
226
|
parseHTML() {
|
|
@@ -279,6 +286,169 @@ var VariableNode = Node.create({
|
|
|
279
286
|
};
|
|
280
287
|
}
|
|
281
288
|
});
|
|
289
|
+
var PanelNode = Node.create({
|
|
290
|
+
name: "panel",
|
|
291
|
+
group: "block",
|
|
292
|
+
content: "block+",
|
|
293
|
+
defining: true,
|
|
294
|
+
addAttributes() {
|
|
295
|
+
return {
|
|
296
|
+
title: { default: "" },
|
|
297
|
+
border: { default: "solid" },
|
|
298
|
+
headerStyle: { default: "" }
|
|
299
|
+
};
|
|
300
|
+
},
|
|
301
|
+
parseHTML() {
|
|
302
|
+
return [{
|
|
303
|
+
tag: "div[data-panel-node]",
|
|
304
|
+
contentElement: "div.panel-content"
|
|
305
|
+
}];
|
|
306
|
+
},
|
|
307
|
+
renderHTML({ node, HTMLAttributes }) {
|
|
308
|
+
const { title, border, headerStyle } = node.attrs;
|
|
309
|
+
const classes = ["panel-node"];
|
|
310
|
+
if (border === "dashed") classes.push("panel-border-dashed");
|
|
311
|
+
else if (border === "none") classes.push("panel-border-none");
|
|
312
|
+
else classes.push("panel-border-solid");
|
|
313
|
+
if (headerStyle === "dark") classes.push("panel-dark");
|
|
314
|
+
const attrs = mergeAttributes(
|
|
315
|
+
{ "data-panel-node": "", class: classes.join(" ") },
|
|
316
|
+
HTMLAttributes
|
|
317
|
+
);
|
|
318
|
+
if (title) {
|
|
319
|
+
return [
|
|
320
|
+
"div",
|
|
321
|
+
attrs,
|
|
322
|
+
["div", { class: "panel-title" }, title],
|
|
323
|
+
["div", { class: "panel-content" }, 0]
|
|
324
|
+
];
|
|
325
|
+
}
|
|
326
|
+
return ["div", attrs, ["div", { class: "panel-content" }, 0]];
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
var ColumnsNode = Node.create({
|
|
330
|
+
name: "columns",
|
|
331
|
+
group: "block",
|
|
332
|
+
content: "column+",
|
|
333
|
+
defining: true,
|
|
334
|
+
addAttributes() {
|
|
335
|
+
return {
|
|
336
|
+
split: { default: "50" },
|
|
337
|
+
padX: { default: "0" }
|
|
338
|
+
};
|
|
339
|
+
},
|
|
340
|
+
parseHTML() {
|
|
341
|
+
return [{ tag: "div[data-columns-node]" }];
|
|
342
|
+
},
|
|
343
|
+
renderHTML({ node, HTMLAttributes }) {
|
|
344
|
+
const split = node.attrs.split || "50";
|
|
345
|
+
return ["div", mergeAttributes(
|
|
346
|
+
{
|
|
347
|
+
"data-columns-node": "",
|
|
348
|
+
class: "columns-node",
|
|
349
|
+
style: `--xpc-col-split: ${split}`
|
|
350
|
+
},
|
|
351
|
+
HTMLAttributes
|
|
352
|
+
), 0];
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
var ColumnNode = Node.create({
|
|
356
|
+
name: "column",
|
|
357
|
+
group: "",
|
|
358
|
+
content: "block+",
|
|
359
|
+
defining: true,
|
|
360
|
+
addAttributes() {
|
|
361
|
+
return {
|
|
362
|
+
padTop: { default: 0 }
|
|
363
|
+
};
|
|
364
|
+
},
|
|
365
|
+
parseHTML() {
|
|
366
|
+
return [{ tag: "div[data-column-node]" }];
|
|
367
|
+
},
|
|
368
|
+
renderHTML({ HTMLAttributes }) {
|
|
369
|
+
return ["div", mergeAttributes(
|
|
370
|
+
{ "data-column-node": "", class: "column-node" },
|
|
371
|
+
HTMLAttributes
|
|
372
|
+
), 0];
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
function WatermarkNodeView({ node, selected }) {
|
|
376
|
+
const { text } = node.attrs;
|
|
377
|
+
return /* @__PURE__ */ jsx(NodeViewWrapper, { children: /* @__PURE__ */ jsxs(
|
|
378
|
+
"div",
|
|
379
|
+
{
|
|
380
|
+
"data-watermark-node": "",
|
|
381
|
+
className: cn(
|
|
382
|
+
"inline-flex items-center gap-2 px-3 py-1.5 rounded-md border text-sm my-1",
|
|
383
|
+
"bg-blue-50 text-blue-600 border-blue-200",
|
|
384
|
+
selected && "ring-2 ring-primary ring-offset-1"
|
|
385
|
+
),
|
|
386
|
+
children: [
|
|
387
|
+
/* @__PURE__ */ jsx(Droplets, { size: 14 }),
|
|
388
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
389
|
+
"Watermark: ",
|
|
390
|
+
text || "(empty)"
|
|
391
|
+
] })
|
|
392
|
+
]
|
|
393
|
+
}
|
|
394
|
+
) });
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// src/extensions/watermark-node.ts
|
|
398
|
+
var WatermarkNode = Node.create({
|
|
399
|
+
name: "watermark",
|
|
400
|
+
group: "block",
|
|
401
|
+
atom: true,
|
|
402
|
+
selectable: true,
|
|
403
|
+
draggable: false,
|
|
404
|
+
addAttributes() {
|
|
405
|
+
return {
|
|
406
|
+
text: { default: "" },
|
|
407
|
+
opacity: { default: "0.15" },
|
|
408
|
+
angle: { default: "-45" }
|
|
409
|
+
};
|
|
410
|
+
},
|
|
411
|
+
parseHTML() {
|
|
412
|
+
return [{ tag: "div[data-watermark-node]" }];
|
|
413
|
+
},
|
|
414
|
+
renderHTML({ HTMLAttributes }) {
|
|
415
|
+
return ["div", mergeAttributes({ "data-watermark-node": "" }, HTMLAttributes)];
|
|
416
|
+
},
|
|
417
|
+
addNodeView() {
|
|
418
|
+
return ReactNodeViewRenderer(WatermarkNodeView);
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
var RepeatNode = Node.create({
|
|
422
|
+
name: "repeatBlock",
|
|
423
|
+
group: "block",
|
|
424
|
+
content: "block+",
|
|
425
|
+
addAttributes() {
|
|
426
|
+
return {
|
|
427
|
+
data: { default: "" }
|
|
428
|
+
};
|
|
429
|
+
},
|
|
430
|
+
parseHTML() {
|
|
431
|
+
return [{ tag: "div[data-repeat-node]" }];
|
|
432
|
+
},
|
|
433
|
+
renderHTML({ HTMLAttributes }) {
|
|
434
|
+
return ["div", mergeAttributes({ "data-repeat-node": "" }, HTMLAttributes), 0];
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
var SubtotalsNode = Node.create({
|
|
438
|
+
name: "subtotalsBlock",
|
|
439
|
+
group: "block",
|
|
440
|
+
content: "block+",
|
|
441
|
+
defining: true,
|
|
442
|
+
parseHTML() {
|
|
443
|
+
return [{ tag: "div[data-subtotals-node]" }];
|
|
444
|
+
},
|
|
445
|
+
renderHTML({ HTMLAttributes }) {
|
|
446
|
+
return ["div", mergeAttributes(
|
|
447
|
+
{ "data-subtotals-node": "", class: "subtotals-block" },
|
|
448
|
+
HTMLAttributes
|
|
449
|
+
), 0];
|
|
450
|
+
}
|
|
451
|
+
});
|
|
282
452
|
|
|
283
453
|
// src/types/index.ts
|
|
284
454
|
var FormFieldType = /* @__PURE__ */ ((FormFieldType2) => {
|
|
@@ -357,158 +527,23 @@ function extractFieldsFromContent(content) {
|
|
|
357
527
|
}
|
|
358
528
|
return fields;
|
|
359
529
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
375
|
-
if (node.content) {
|
|
376
|
-
node.content.forEach(walk);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
if (content.content) {
|
|
380
|
-
content.content.forEach(walk);
|
|
381
|
-
}
|
|
382
|
-
return Array.from(seen.values());
|
|
383
|
-
}
|
|
384
|
-
function replaceVariablesInContent(content, values) {
|
|
385
|
-
function walkNode(node) {
|
|
386
|
-
if (node.type === "variableNode" && node.attrs) {
|
|
387
|
-
const varName = node.attrs.varName;
|
|
388
|
-
const replacement = values[varName] || node.attrs.varDefault || `[${node.attrs.varLabel || varName}]`;
|
|
389
|
-
const textNode = { type: "text", text: replacement };
|
|
390
|
-
if (node.marks && node.marks.length > 0) {
|
|
391
|
-
textNode.marks = node.marks;
|
|
392
|
-
}
|
|
393
|
-
return textNode;
|
|
394
|
-
}
|
|
395
|
-
if (node.content) {
|
|
396
|
-
return { ...node, content: node.content.map(walkNode) };
|
|
397
|
-
}
|
|
398
|
-
return node;
|
|
399
|
-
}
|
|
400
|
-
return walkNode(content);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// src/utils/markdown-writer.ts
|
|
404
|
-
function serializeFieldToken(attrs) {
|
|
405
|
-
const parts = ["field"];
|
|
406
|
-
if (attrs.fieldType) parts.push(`type:${attrs.fieldType}`);
|
|
407
|
-
if (attrs.fieldName) parts.push(`name:${attrs.fieldName}`);
|
|
408
|
-
if (attrs.fieldLabel) parts.push(`label:${attrs.fieldLabel}`);
|
|
409
|
-
if (attrs.fieldId) parts.push(`id:${attrs.fieldId}`);
|
|
410
|
-
if (attrs.required) parts.push(`required:true`);
|
|
411
|
-
if (attrs.options) parts.push(`options:${attrs.options}`);
|
|
412
|
-
if (attrs.fontSize) parts.push(`fontSize:${attrs.fontSize}`);
|
|
413
|
-
if (attrs.placeholder) parts.push(`placeholder:${attrs.placeholder}`);
|
|
414
|
-
if (attrs.defaultValue) parts.push(`defaultValue:${attrs.defaultValue}`);
|
|
415
|
-
if (attrs.multiline) parts.push(`multiline:true`);
|
|
416
|
-
if (attrs.maxLength) parts.push(`maxLength:${attrs.maxLength}`);
|
|
417
|
-
if (attrs.acknowledgements) parts.push(`acks:${btoa(attrs.acknowledgements)}`);
|
|
418
|
-
return `{{${parts.join("|")}}}`;
|
|
419
|
-
}
|
|
420
|
-
function serializeVariableToken(attrs) {
|
|
421
|
-
const parts = ["var"];
|
|
422
|
-
if (attrs.varName) parts.push(`name:${attrs.varName}`);
|
|
423
|
-
if (attrs.varLabel) parts.push(`label:${attrs.varLabel}`);
|
|
424
|
-
if (attrs.varDefault) parts.push(`default:${attrs.varDefault}`);
|
|
425
|
-
return `{{${parts.join("|")}}}`;
|
|
426
|
-
}
|
|
427
|
-
function serializeInline(content) {
|
|
428
|
-
if (!content) return "";
|
|
429
|
-
let result = "";
|
|
430
|
-
for (const node of content) {
|
|
431
|
-
if (node.type === "fieldNode") {
|
|
432
|
-
result += serializeFieldToken(node.attrs || {});
|
|
433
|
-
continue;
|
|
434
|
-
}
|
|
435
|
-
if (node.type === "variableNode") {
|
|
436
|
-
let token = serializeVariableToken(node.attrs || {});
|
|
437
|
-
const varMarks = node.marks || [];
|
|
438
|
-
const isBold = varMarks.some((m) => m.type === "bold");
|
|
439
|
-
const isItalic = varMarks.some((m) => m.type === "italic");
|
|
440
|
-
const isUnderline = varMarks.some((m) => m.type === "underline");
|
|
441
|
-
if (isBold && isItalic) token = `***${token}***`;
|
|
442
|
-
else if (isBold) token = `**${token}**`;
|
|
443
|
-
else if (isItalic) token = `*${token}*`;
|
|
444
|
-
if (isUnderline) token = `__${token}__`;
|
|
445
|
-
result += token;
|
|
446
|
-
continue;
|
|
447
|
-
}
|
|
448
|
-
if (node.type === "text") {
|
|
449
|
-
let text = node.text || "";
|
|
450
|
-
const marks = node.marks || [];
|
|
451
|
-
for (const mark of marks) {
|
|
452
|
-
if (mark.type === "bold") text = `**${text}**`;
|
|
453
|
-
else if (mark.type === "italic") text = `*${text}*`;
|
|
454
|
-
else if (mark.type === "underline") text = `__${text}__`;
|
|
455
|
-
else if (mark.type === "code") text = `\`${text}\``;
|
|
456
|
-
}
|
|
457
|
-
result += text;
|
|
458
|
-
}
|
|
459
|
-
if (node.type === "hardBreak") {
|
|
460
|
-
result += " \n";
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
return result;
|
|
464
|
-
}
|
|
465
|
-
function serializeBlock(node) {
|
|
466
|
-
switch (node.type) {
|
|
467
|
-
case "heading": {
|
|
468
|
-
const level = node.attrs?.level || 1;
|
|
469
|
-
const prefix = "#".repeat(level);
|
|
470
|
-
return `${prefix} ${serializeInline(node.content)}`;
|
|
471
|
-
}
|
|
472
|
-
case "paragraph": {
|
|
473
|
-
const inline = serializeInline(node.content);
|
|
474
|
-
return inline;
|
|
475
|
-
}
|
|
476
|
-
case "bulletList": {
|
|
477
|
-
return (node.content || []).map((item) => {
|
|
478
|
-
const inner = (item.content || []).map(serializeBlock).join("\n");
|
|
479
|
-
return `- ${inner}`;
|
|
480
|
-
}).join("\n");
|
|
481
|
-
}
|
|
482
|
-
case "orderedList": {
|
|
483
|
-
return (node.content || []).map((item, i) => {
|
|
484
|
-
const inner = (item.content || []).map(serializeBlock).join("\n");
|
|
485
|
-
return `${i + 1}. ${inner}`;
|
|
486
|
-
}).join("\n");
|
|
487
|
-
}
|
|
488
|
-
case "listItem": {
|
|
489
|
-
return (node.content || []).map(serializeBlock).join("\n");
|
|
490
|
-
}
|
|
491
|
-
case "blockquote": {
|
|
492
|
-
return (node.content || []).map(serializeBlock).map((line) => `> ${line}`).join("\n");
|
|
493
|
-
}
|
|
494
|
-
case "codeBlock": {
|
|
495
|
-
const text = serializeInline(node.content);
|
|
496
|
-
return `\`\`\`
|
|
497
|
-
${text}
|
|
498
|
-
\`\`\``;
|
|
499
|
-
}
|
|
500
|
-
case "horizontalRule": {
|
|
501
|
-
return "---";
|
|
502
|
-
}
|
|
503
|
-
default:
|
|
504
|
-
return serializeInline(node.content);
|
|
530
|
+
var TOKEN_REGEX = /\{\{(field|var|image)\|([^}]+)\}\}/g;
|
|
531
|
+
var BLOCK_IMAGE_REGEX = /^!\[([^\]]*)\]\(([^)\s]+)\)$/;
|
|
532
|
+
var DIRECTIVE_OPEN = /^:::(panel|columns|col|watermark|repeat|subtotals)(?:\{([^}]*)\})?$/;
|
|
533
|
+
var DIRECTIVE_CLOSE = /^:::$/;
|
|
534
|
+
function parseDirectiveAttrs(str) {
|
|
535
|
+
const attrs = {};
|
|
536
|
+
if (!str) return attrs;
|
|
537
|
+
const pairs = str.split("|");
|
|
538
|
+
for (const pair of pairs) {
|
|
539
|
+
const colonIdx = pair.indexOf(":");
|
|
540
|
+
if (colonIdx === -1) continue;
|
|
541
|
+
const key = pair.slice(0, colonIdx).trim();
|
|
542
|
+
const value = pair.slice(colonIdx + 1).trim();
|
|
543
|
+
if (key) attrs[key] = value;
|
|
505
544
|
}
|
|
545
|
+
return attrs;
|
|
506
546
|
}
|
|
507
|
-
function tiptapToMarkdown(doc) {
|
|
508
|
-
if (!doc.content) return "";
|
|
509
|
-
return doc.content.map(serializeBlock).join("\n\n");
|
|
510
|
-
}
|
|
511
|
-
var TOKEN_REGEX = /\{\{(field|var)\|([^}]+)\}\}/g;
|
|
512
547
|
function injectMarks(token, marks) {
|
|
513
548
|
const existingMatch = token.match(/\|_marks:([^}|]+)/);
|
|
514
549
|
if (existingMatch) {
|
|
@@ -625,6 +660,15 @@ function parseVariableToken(tokenBody) {
|
|
|
625
660
|
case "default":
|
|
626
661
|
attrs.varDefault = value;
|
|
627
662
|
break;
|
|
663
|
+
case "suppress":
|
|
664
|
+
attrs.suppressZero = value === "zero" ? "true" : "";
|
|
665
|
+
break;
|
|
666
|
+
case "format":
|
|
667
|
+
attrs.format = value;
|
|
668
|
+
break;
|
|
669
|
+
case "block":
|
|
670
|
+
attrs.block = value === "true" ? "true" : "";
|
|
671
|
+
break;
|
|
628
672
|
case "_marks":
|
|
629
673
|
attrs._marks = value;
|
|
630
674
|
break;
|
|
@@ -632,6 +676,44 @@ function parseVariableToken(tokenBody) {
|
|
|
632
676
|
}
|
|
633
677
|
return attrs;
|
|
634
678
|
}
|
|
679
|
+
function parseImageToken(tokenBody) {
|
|
680
|
+
const attrs = {
|
|
681
|
+
src: "",
|
|
682
|
+
varName: "",
|
|
683
|
+
alt: "",
|
|
684
|
+
width: "",
|
|
685
|
+
height: "",
|
|
686
|
+
align: ""
|
|
687
|
+
};
|
|
688
|
+
const pairs = tokenBody.split("|");
|
|
689
|
+
for (const pair of pairs) {
|
|
690
|
+
const colonIdx = pair.indexOf(":");
|
|
691
|
+
if (colonIdx === -1) continue;
|
|
692
|
+
const key = pair.slice(0, colonIdx).trim();
|
|
693
|
+
const value = pair.slice(colonIdx + 1).trim();
|
|
694
|
+
switch (key) {
|
|
695
|
+
case "src":
|
|
696
|
+
attrs.src = value;
|
|
697
|
+
break;
|
|
698
|
+
case "var":
|
|
699
|
+
attrs.varName = value;
|
|
700
|
+
break;
|
|
701
|
+
case "alt":
|
|
702
|
+
attrs.alt = value;
|
|
703
|
+
break;
|
|
704
|
+
case "width":
|
|
705
|
+
attrs.width = value;
|
|
706
|
+
break;
|
|
707
|
+
case "height":
|
|
708
|
+
attrs.height = value;
|
|
709
|
+
break;
|
|
710
|
+
case "align":
|
|
711
|
+
attrs.align = value;
|
|
712
|
+
break;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return attrs;
|
|
716
|
+
}
|
|
635
717
|
function parseInline(text) {
|
|
636
718
|
text = preprocessMarkedTokens(text);
|
|
637
719
|
const nodes = [];
|
|
@@ -655,6 +737,8 @@ function parseInline(text) {
|
|
|
655
737
|
const node = { type: "variableNode", attrs };
|
|
656
738
|
if (marks.length > 0) node.marks = marks;
|
|
657
739
|
nodes.push(node);
|
|
740
|
+
} else if (match[1] === "image") {
|
|
741
|
+
nodes.push({ type: "imageNode", attrs: parseImageToken(match[2]) });
|
|
658
742
|
}
|
|
659
743
|
lastIndex = match.index + match[0].length;
|
|
660
744
|
}
|
|
@@ -666,45 +750,63 @@ function parseInline(text) {
|
|
|
666
750
|
function parseFormattedText(text) {
|
|
667
751
|
if (!text) return [];
|
|
668
752
|
const nodes = [];
|
|
669
|
-
const parts = text.split(/(
|
|
753
|
+
const parts = text.split(/(\*\*\*[^*]+\*\*\*|\*\*[^*]+\*\*|\*[^*]+\*|__[^_]+__|`[^`]+`)/);
|
|
670
754
|
for (const part of parts) {
|
|
671
755
|
if (!part) continue;
|
|
672
|
-
if (part.startsWith("
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
});
|
|
756
|
+
if (part.startsWith("***") && part.endsWith("***")) {
|
|
757
|
+
const inner = part.slice(3, -3);
|
|
758
|
+
if (inner) nodes.push({ type: "text", text: inner, marks: [{ type: "bold" }, { type: "italic" }] });
|
|
759
|
+
} else if (part.startsWith("**") && part.endsWith("**")) {
|
|
760
|
+
const inner = part.slice(2, -2);
|
|
761
|
+
if (inner) nodes.push({ type: "text", text: inner, marks: [{ type: "bold" }] });
|
|
678
762
|
} else if (part.startsWith("*") && part.endsWith("*")) {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
text: part.slice(1, -1),
|
|
682
|
-
marks: [{ type: "italic" }]
|
|
683
|
-
});
|
|
763
|
+
const inner = part.slice(1, -1);
|
|
764
|
+
if (inner) nodes.push({ type: "text", text: inner, marks: [{ type: "italic" }] });
|
|
684
765
|
} else if (part.startsWith("__") && part.endsWith("__")) {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
text: part.slice(2, -2),
|
|
688
|
-
marks: [{ type: "underline" }]
|
|
689
|
-
});
|
|
766
|
+
const inner = part.slice(2, -2);
|
|
767
|
+
if (inner) nodes.push({ type: "text", text: inner, marks: [{ type: "underline" }] });
|
|
690
768
|
} else if (part.startsWith("`") && part.endsWith("`")) {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
text: part.slice(1, -1),
|
|
694
|
-
marks: [{ type: "code" }]
|
|
695
|
-
});
|
|
769
|
+
const inner = part.slice(1, -1);
|
|
770
|
+
if (inner) nodes.push({ type: "text", text: inner, marks: [{ type: "code" }] });
|
|
696
771
|
} else {
|
|
697
772
|
nodes.push({ type: "text", text: part });
|
|
698
773
|
}
|
|
699
774
|
}
|
|
700
775
|
return nodes;
|
|
701
776
|
}
|
|
702
|
-
function
|
|
703
|
-
const
|
|
704
|
-
|
|
705
|
-
let
|
|
706
|
-
|
|
707
|
-
|
|
777
|
+
function splitTableCells(row) {
|
|
778
|
+
const cells = [];
|
|
779
|
+
let current = "";
|
|
780
|
+
let depth = 0;
|
|
781
|
+
for (let i = 0; i < row.length; i++) {
|
|
782
|
+
if (row[i] === "{" && row[i + 1] === "{") {
|
|
783
|
+
depth++;
|
|
784
|
+
current += "{{";
|
|
785
|
+
i++;
|
|
786
|
+
} else if (row[i] === "}" && row[i + 1] === "}") {
|
|
787
|
+
depth = Math.max(0, depth - 1);
|
|
788
|
+
current += "}}";
|
|
789
|
+
i++;
|
|
790
|
+
} else if (row[i] === "|" && depth === 0) {
|
|
791
|
+
cells.push(current.trim());
|
|
792
|
+
current = "";
|
|
793
|
+
} else {
|
|
794
|
+
current += row[i];
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
if (current.trim()) {
|
|
798
|
+
cells.push(current.trim());
|
|
799
|
+
}
|
|
800
|
+
return cells;
|
|
801
|
+
}
|
|
802
|
+
function parseBlocks(lines, startIdx, stopCondition) {
|
|
803
|
+
const content = [];
|
|
804
|
+
let i = startIdx;
|
|
805
|
+
while (i < lines.length) {
|
|
806
|
+
const line = lines[i];
|
|
807
|
+
if (stopCondition && stopCondition(line)) {
|
|
808
|
+
break;
|
|
809
|
+
}
|
|
708
810
|
if (line.trim() === "") {
|
|
709
811
|
i++;
|
|
710
812
|
continue;
|
|
@@ -778,13 +880,431 @@ function markdownToTiptap(markdown) {
|
|
|
778
880
|
});
|
|
779
881
|
continue;
|
|
780
882
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
883
|
+
if (line.trimStart().startsWith("|") && line.trimEnd().endsWith("|")) {
|
|
884
|
+
const tableLines = [];
|
|
885
|
+
while (i < lines.length) {
|
|
886
|
+
const tl = lines[i];
|
|
887
|
+
if (tl.trimStart().startsWith("|") && tl.trimEnd().endsWith("|")) {
|
|
888
|
+
tableLines.push(tl);
|
|
889
|
+
i++;
|
|
890
|
+
} else {
|
|
891
|
+
break;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
const separatorRegex = /^[\s|]*-+[\s|:-]*$/;
|
|
895
|
+
const dataLines = tableLines.filter(
|
|
896
|
+
(tl) => !separatorRegex.test(tl.replace(/^\s*\|/, "").replace(/\|\s*$/, ""))
|
|
897
|
+
);
|
|
898
|
+
const firstDataCellsRaw = dataLines.length > 0 ? splitTableCells(dataLines[0]) : [];
|
|
899
|
+
const isSubtotals = firstDataCellsRaw.length >= 2 && firstDataCellsRaw.every((c) => c.trim() === "_");
|
|
900
|
+
const isBorderless = isSubtotals || firstDataCellsRaw.length >= 2 && firstDataCellsRaw.every((c) => c.trim() === "");
|
|
901
|
+
const tableRows = [];
|
|
902
|
+
let isFirstDataRow = true;
|
|
903
|
+
for (const tl of tableLines) {
|
|
904
|
+
if (separatorRegex.test(tl.replace(/^\s*\|/, "").replace(/\|\s*$/, ""))) continue;
|
|
905
|
+
const cells = splitTableCells(tl).filter((c) => c !== "");
|
|
906
|
+
if (isBorderless && isFirstDataRow) {
|
|
907
|
+
isFirstDataRow = false;
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
const cellType = isFirstDataRow && !isBorderless ? "tableHeader" : "tableCell";
|
|
911
|
+
const rowContent = cells.map((cellText) => ({
|
|
912
|
+
type: cellType,
|
|
913
|
+
content: [{ type: "paragraph", content: cellText ? parseInline(cellText) : [] }]
|
|
914
|
+
}));
|
|
915
|
+
tableRows.push({ type: "tableRow", content: rowContent });
|
|
916
|
+
isFirstDataRow = false;
|
|
917
|
+
}
|
|
918
|
+
if (tableRows.length > 0) {
|
|
919
|
+
const tableNode = { type: "table", content: tableRows };
|
|
920
|
+
if (isBorderless) {
|
|
921
|
+
tableNode.attrs = { borderless: true, ...isSubtotals ? { subtotals: true } : {} };
|
|
922
|
+
}
|
|
923
|
+
content.push(tableNode);
|
|
924
|
+
}
|
|
925
|
+
continue;
|
|
926
|
+
}
|
|
927
|
+
const directiveMatch = line.trim().match(DIRECTIVE_OPEN);
|
|
928
|
+
if (directiveMatch) {
|
|
929
|
+
const directiveType = directiveMatch[1];
|
|
930
|
+
const rawAttrs = directiveMatch[2] || "";
|
|
931
|
+
const attrs = parseDirectiveAttrs(rawAttrs);
|
|
932
|
+
i++;
|
|
933
|
+
if (directiveType === "watermark") {
|
|
934
|
+
content.push({
|
|
935
|
+
type: "watermark",
|
|
936
|
+
attrs: {
|
|
937
|
+
text: attrs.text || "",
|
|
938
|
+
opacity: attrs.opacity || "0.15",
|
|
939
|
+
angle: attrs.angle || "-45"
|
|
940
|
+
}
|
|
941
|
+
});
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
944
|
+
if (directiveType === "panel") {
|
|
945
|
+
const inner = parseBlocks(lines, i, (l) => DIRECTIVE_CLOSE.test(l.trim()));
|
|
946
|
+
i = inner.nextIdx;
|
|
947
|
+
if (i < lines.length && DIRECTIVE_CLOSE.test(lines[i].trim())) {
|
|
948
|
+
i++;
|
|
949
|
+
}
|
|
950
|
+
content.push({
|
|
951
|
+
type: "panel",
|
|
952
|
+
attrs: {
|
|
953
|
+
title: attrs.title || "",
|
|
954
|
+
border: attrs.border || "solid",
|
|
955
|
+
headerStyle: attrs.headerStyle || ""
|
|
956
|
+
},
|
|
957
|
+
content: inner.blocks.length > 0 ? inner.blocks : [{ type: "paragraph" }]
|
|
958
|
+
});
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
if (directiveType === "repeat") {
|
|
962
|
+
const inner = parseBlocks(lines, i, (l) => DIRECTIVE_CLOSE.test(l.trim()));
|
|
963
|
+
i = inner.nextIdx;
|
|
964
|
+
if (i < lines.length && DIRECTIVE_CLOSE.test(lines[i].trim())) {
|
|
965
|
+
i++;
|
|
966
|
+
}
|
|
967
|
+
content.push({
|
|
968
|
+
type: "repeatBlock",
|
|
969
|
+
attrs: { data: attrs.data || "" },
|
|
970
|
+
content: inner.blocks.length > 0 ? inner.blocks : [{ type: "paragraph" }]
|
|
971
|
+
});
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
974
|
+
if (directiveType === "subtotals") {
|
|
975
|
+
const inner = parseBlocks(lines, i, (l) => DIRECTIVE_CLOSE.test(l.trim()));
|
|
976
|
+
i = inner.nextIdx;
|
|
977
|
+
if (i < lines.length && DIRECTIVE_CLOSE.test(lines[i].trim())) {
|
|
978
|
+
i++;
|
|
979
|
+
}
|
|
980
|
+
content.push({
|
|
981
|
+
type: "subtotalsBlock",
|
|
982
|
+
content: inner.blocks.length > 0 ? inner.blocks : [{ type: "paragraph" }]
|
|
983
|
+
});
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
if (directiveType === "columns") {
|
|
987
|
+
const splitVal = attrs.split || "50";
|
|
988
|
+
const columns = [];
|
|
989
|
+
while (i < lines.length) {
|
|
990
|
+
const colLine = lines[i].trim();
|
|
991
|
+
if (DIRECTIVE_CLOSE.test(colLine)) {
|
|
992
|
+
if (columns.length > 0) {
|
|
993
|
+
i++;
|
|
994
|
+
break;
|
|
995
|
+
}
|
|
996
|
+
i++;
|
|
997
|
+
break;
|
|
998
|
+
}
|
|
999
|
+
const colMatch = colLine.match(/^:::col(?:\{([^}]*)\})?$/);
|
|
1000
|
+
if (colMatch) {
|
|
1001
|
+
i++;
|
|
1002
|
+
const colInner = parseBlocks(lines, i, (l) => {
|
|
1003
|
+
const trimmed = l.trim();
|
|
1004
|
+
return DIRECTIVE_CLOSE.test(trimmed) || /^:::col(?:\{[^}]*\})?$/.test(trimmed);
|
|
1005
|
+
});
|
|
1006
|
+
i = colInner.nextIdx;
|
|
1007
|
+
if (i < lines.length && DIRECTIVE_CLOSE.test(lines[i].trim())) {
|
|
1008
|
+
i++;
|
|
1009
|
+
}
|
|
1010
|
+
const colAttrsRaw = colMatch[1] || "";
|
|
1011
|
+
const colAttrs = parseDirectiveAttrs(colAttrsRaw);
|
|
1012
|
+
const padTop = parseFloat(colAttrs.padTop || "0") || 0;
|
|
1013
|
+
columns.push({
|
|
1014
|
+
type: "column",
|
|
1015
|
+
attrs: padTop ? { padTop } : void 0,
|
|
1016
|
+
content: colInner.blocks.length > 0 ? colInner.blocks : [{ type: "paragraph" }]
|
|
1017
|
+
});
|
|
1018
|
+
continue;
|
|
1019
|
+
}
|
|
1020
|
+
if (colLine === "") {
|
|
1021
|
+
i++;
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
1024
|
+
break;
|
|
1025
|
+
}
|
|
1026
|
+
const columnsAttrs = { split: splitVal };
|
|
1027
|
+
if (attrs.padX) columnsAttrs.padX = attrs.padX;
|
|
1028
|
+
content.push({
|
|
1029
|
+
type: "columns",
|
|
1030
|
+
attrs: columnsAttrs,
|
|
1031
|
+
content: columns.length > 0 ? columns : [
|
|
1032
|
+
{ type: "column", content: [{ type: "paragraph" }] },
|
|
1033
|
+
{ type: "column", content: [{ type: "paragraph" }] }
|
|
1034
|
+
]
|
|
1035
|
+
});
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
1038
|
+
if (directiveType === "col") {
|
|
1039
|
+
const inner = parseBlocks(lines, i, (l) => DIRECTIVE_CLOSE.test(l.trim()));
|
|
1040
|
+
i = inner.nextIdx;
|
|
1041
|
+
if (i < lines.length && DIRECTIVE_CLOSE.test(lines[i].trim())) {
|
|
1042
|
+
i++;
|
|
1043
|
+
}
|
|
1044
|
+
for (const block of inner.blocks) {
|
|
1045
|
+
content.push(block);
|
|
1046
|
+
}
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1051
|
+
if (DIRECTIVE_CLOSE.test(line.trim())) {
|
|
1052
|
+
i++;
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
const imageMatch = line.trim().match(BLOCK_IMAGE_REGEX);
|
|
1056
|
+
if (imageMatch) {
|
|
1057
|
+
content.push({
|
|
1058
|
+
type: "imageNode",
|
|
1059
|
+
attrs: { src: imageMatch[2], varName: "", alt: imageMatch[1], width: "", height: "", align: "" }
|
|
1060
|
+
});
|
|
1061
|
+
i++;
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
1064
|
+
const inlineNodes = parseInline(line);
|
|
1065
|
+
const nonWhitespace = inlineNodes.filter(
|
|
1066
|
+
(n) => !(n.type === "text" && !(n.text || "").trim())
|
|
1067
|
+
);
|
|
1068
|
+
if (nonWhitespace.length === 1 && nonWhitespace[0].type === "imageNode") {
|
|
1069
|
+
content.push(nonWhitespace[0]);
|
|
1070
|
+
} else {
|
|
1071
|
+
content.push({ type: "paragraph", content: inlineNodes });
|
|
1072
|
+
}
|
|
785
1073
|
i++;
|
|
786
1074
|
}
|
|
787
|
-
return {
|
|
1075
|
+
return { blocks: content, nextIdx: i };
|
|
1076
|
+
}
|
|
1077
|
+
function markdownToTiptap(markdown) {
|
|
1078
|
+
const lines = markdown.split("\n");
|
|
1079
|
+
const { blocks } = parseBlocks(lines, 0);
|
|
1080
|
+
return { type: "doc", content: blocks };
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// src/utils/variable-helpers.ts
|
|
1084
|
+
function extractVariablesFromContent(content) {
|
|
1085
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1086
|
+
function walk(node) {
|
|
1087
|
+
if (node.type === "variableNode" && node.attrs) {
|
|
1088
|
+
const varName = node.attrs.varName;
|
|
1089
|
+
if (varName && !seen.has(varName)) {
|
|
1090
|
+
seen.set(varName, {
|
|
1091
|
+
varName,
|
|
1092
|
+
varLabel: node.attrs.varLabel || varName,
|
|
1093
|
+
varDefault: node.attrs.varDefault || ""
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
if (node.content) {
|
|
1098
|
+
node.content.forEach(walk);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
if (content.content) {
|
|
1102
|
+
content.content.forEach(walk);
|
|
1103
|
+
}
|
|
1104
|
+
return Array.from(seen.values());
|
|
1105
|
+
}
|
|
1106
|
+
function applyFormat(value, format) {
|
|
1107
|
+
if (format === "phone") {
|
|
1108
|
+
const digits = value.replace(/\D/g, "");
|
|
1109
|
+
if (digits.length === 10) {
|
|
1110
|
+
return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)} - ${digits.slice(6)}`;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
return value;
|
|
1114
|
+
}
|
|
1115
|
+
function replaceVariablesInContent(content, values) {
|
|
1116
|
+
function walkNode(node) {
|
|
1117
|
+
if (node.type === "variableNode" && node.attrs) {
|
|
1118
|
+
const varName = node.attrs.varName;
|
|
1119
|
+
let replacement = values[varName] || node.attrs.varDefault || "";
|
|
1120
|
+
const format = node.attrs.format;
|
|
1121
|
+
if (format && replacement) {
|
|
1122
|
+
replacement = applyFormat(replacement, format);
|
|
1123
|
+
}
|
|
1124
|
+
const textNode = { type: "text", text: replacement };
|
|
1125
|
+
if (node.marks && node.marks.length > 0) {
|
|
1126
|
+
textNode.marks = node.marks;
|
|
1127
|
+
}
|
|
1128
|
+
return textNode;
|
|
1129
|
+
}
|
|
1130
|
+
if (node.type === "imageNode" && node.attrs?.varName) {
|
|
1131
|
+
const varName = node.attrs.varName;
|
|
1132
|
+
const resolved = values[varName] || node.attrs.src || "";
|
|
1133
|
+
return { ...node, attrs: { ...node.attrs, src: resolved } };
|
|
1134
|
+
}
|
|
1135
|
+
if (node.content) {
|
|
1136
|
+
return { ...node, content: node.content.map(walkNode) };
|
|
1137
|
+
}
|
|
1138
|
+
return node;
|
|
1139
|
+
}
|
|
1140
|
+
return walkNode(content);
|
|
1141
|
+
}
|
|
1142
|
+
function expandBlockVariables(content, values) {
|
|
1143
|
+
function soleVariableOf(node) {
|
|
1144
|
+
if (node.type !== "paragraph" || !node.content) return null;
|
|
1145
|
+
const meaningful = node.content.filter(
|
|
1146
|
+
(c) => !(c.type === "text" && !(c.text || "").trim())
|
|
1147
|
+
);
|
|
1148
|
+
if (meaningful.length === 1 && meaningful[0].type === "variableNode") {
|
|
1149
|
+
return meaningful[0];
|
|
1150
|
+
}
|
|
1151
|
+
return null;
|
|
1152
|
+
}
|
|
1153
|
+
function stripUnsafeNodes(node) {
|
|
1154
|
+
if (!node.content) return node;
|
|
1155
|
+
return {
|
|
1156
|
+
...node,
|
|
1157
|
+
content: node.content.filter((c) => c.type !== "fieldNode" && c.type !== "watermark").map(stripUnsafeNodes)
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
function isCellSafe(blocks) {
|
|
1161
|
+
return blocks.every((b) => b.type === "paragraph" || b.type === "heading");
|
|
1162
|
+
}
|
|
1163
|
+
function walkNode(node, inCell) {
|
|
1164
|
+
if (!node.content) return node;
|
|
1165
|
+
const childInCell = inCell || node.type === "tableCell" || node.type === "tableHeader";
|
|
1166
|
+
const newContent = [];
|
|
1167
|
+
for (const child of node.content) {
|
|
1168
|
+
const varNode = soleVariableOf(child);
|
|
1169
|
+
if (varNode?.attrs) {
|
|
1170
|
+
const varName = varNode.attrs.varName;
|
|
1171
|
+
const value = values[varName] || varNode.attrs.varDefault || "";
|
|
1172
|
+
const isBlock = varNode.attrs.block === "true" || value.includes("\n");
|
|
1173
|
+
if (isBlock) {
|
|
1174
|
+
const parsed = stripUnsafeNodes(markdownToTiptap(value));
|
|
1175
|
+
const blocks = parsed.content || [];
|
|
1176
|
+
if (!childInCell || isCellSafe(blocks)) {
|
|
1177
|
+
newContent.push(...blocks);
|
|
1178
|
+
continue;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
newContent.push(walkNode(child, childInCell));
|
|
1183
|
+
}
|
|
1184
|
+
return { ...node, content: newContent };
|
|
1185
|
+
}
|
|
1186
|
+
return walkNode(content, false);
|
|
1187
|
+
}
|
|
1188
|
+
function labelToVarName(label) {
|
|
1189
|
+
return label.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
1190
|
+
}
|
|
1191
|
+
function isZeroLike(value) {
|
|
1192
|
+
if (!value || value.trim() === "") return true;
|
|
1193
|
+
const cleaned = value.replace(/[$,\s]/g, "");
|
|
1194
|
+
return /^-?0+(\.0+)?$/.test(cleaned);
|
|
1195
|
+
}
|
|
1196
|
+
function suppressZeroContent(content, values) {
|
|
1197
|
+
function hasSuppressedVar(node) {
|
|
1198
|
+
if (node.type === "variableNode" && node.attrs?.suppressZero === "true") {
|
|
1199
|
+
const varName = node.attrs.varName;
|
|
1200
|
+
const val = values[varName] || node.attrs.varDefault || "";
|
|
1201
|
+
return isZeroLike(val);
|
|
1202
|
+
}
|
|
1203
|
+
return (node.content || []).some(hasSuppressedVar);
|
|
1204
|
+
}
|
|
1205
|
+
function walkNode(node) {
|
|
1206
|
+
if (node.type === "table" && node.content) {
|
|
1207
|
+
const filtered = node.content.filter((row) => {
|
|
1208
|
+
if (row.type !== "tableRow") return true;
|
|
1209
|
+
const isHeader = row.content?.[0]?.type === "tableHeader";
|
|
1210
|
+
if (isHeader) return true;
|
|
1211
|
+
return !hasSuppressedVar(row);
|
|
1212
|
+
});
|
|
1213
|
+
const bodyRows = filtered.filter(
|
|
1214
|
+
(r) => r.type === "tableRow" && r.content?.[0]?.type !== "tableHeader"
|
|
1215
|
+
);
|
|
1216
|
+
if (bodyRows.length === 0) return null;
|
|
1217
|
+
return { ...node, content: filtered };
|
|
1218
|
+
}
|
|
1219
|
+
if (node.type === "paragraph" && hasSuppressedVar(node)) {
|
|
1220
|
+
return null;
|
|
1221
|
+
}
|
|
1222
|
+
if (node.content) {
|
|
1223
|
+
const filtered = node.content.map((child) => walkNode(child)).filter((c) => c !== null);
|
|
1224
|
+
return { ...node, content: filtered };
|
|
1225
|
+
}
|
|
1226
|
+
return node;
|
|
1227
|
+
}
|
|
1228
|
+
return walkNode(content) || content;
|
|
1229
|
+
}
|
|
1230
|
+
function expandRepeatContent(content, values) {
|
|
1231
|
+
const enrichedValues = { ...values };
|
|
1232
|
+
function cloneNode(node, dataKey, index) {
|
|
1233
|
+
if (node.type === "variableNode" && node.attrs?.varName) {
|
|
1234
|
+
const oldName = node.attrs.varName;
|
|
1235
|
+
const bareName = oldName.replace(/^operation_/, "");
|
|
1236
|
+
const newName = `${dataKey}_${index}_${bareName}`;
|
|
1237
|
+
return { ...node, attrs: { ...node.attrs, varName: newName } };
|
|
1238
|
+
}
|
|
1239
|
+
if (node.content) {
|
|
1240
|
+
return { ...node, content: node.content.map((n) => cloneNode(n, dataKey, index)) };
|
|
1241
|
+
}
|
|
1242
|
+
return { ...node };
|
|
1243
|
+
}
|
|
1244
|
+
function walkNode(node) {
|
|
1245
|
+
if (node.type === "repeatBlock" && node.attrs?.data) {
|
|
1246
|
+
const dataKey = node.attrs.data;
|
|
1247
|
+
const arrayJson = values[dataKey];
|
|
1248
|
+
if (!arrayJson) return [];
|
|
1249
|
+
let items;
|
|
1250
|
+
try {
|
|
1251
|
+
items = JSON.parse(arrayJson);
|
|
1252
|
+
} catch {
|
|
1253
|
+
return [];
|
|
1254
|
+
}
|
|
1255
|
+
const sums = /* @__PURE__ */ new Map();
|
|
1256
|
+
for (const item of items) {
|
|
1257
|
+
for (const [key, val] of Object.entries(item)) {
|
|
1258
|
+
const num = parseFloat(val);
|
|
1259
|
+
if (!isNaN(num)) {
|
|
1260
|
+
sums.set(key, (sums.get(key) || 0) + num);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
const woKeysByNorm = /* @__PURE__ */ new Map();
|
|
1265
|
+
for (const k of Object.keys(enrichedValues)) {
|
|
1266
|
+
if (k.startsWith("workorder_")) {
|
|
1267
|
+
const norm = k.replace(/_/g, "");
|
|
1268
|
+
woKeysByNorm.set(norm, k);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
for (const [field, total] of sums) {
|
|
1272
|
+
const formatted = total % 1 === 0 ? total.toFixed(2) : total.toFixed(2);
|
|
1273
|
+
const directKey = `workorder_${field}`;
|
|
1274
|
+
enrichedValues[directKey] = formatted;
|
|
1275
|
+
const norm = `workorder${field}`.replace(/_/g, "");
|
|
1276
|
+
const existing = woKeysByNorm.get(norm);
|
|
1277
|
+
if (existing && existing !== directKey) {
|
|
1278
|
+
enrichedValues[existing] = formatted;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
const expanded = [];
|
|
1282
|
+
for (let i = 0; i < items.length; i++) {
|
|
1283
|
+
const item = items[i];
|
|
1284
|
+
for (const [key, val] of Object.entries(item)) {
|
|
1285
|
+
enrichedValues[`${dataKey}_${i}_${key}`] = String(val);
|
|
1286
|
+
}
|
|
1287
|
+
const cloned = (node.content || []).map((n) => cloneNode(n, dataKey, i));
|
|
1288
|
+
expanded.push(...cloned);
|
|
1289
|
+
}
|
|
1290
|
+
return expanded;
|
|
1291
|
+
}
|
|
1292
|
+
if (node.content) {
|
|
1293
|
+
const newContent = [];
|
|
1294
|
+
for (const child of node.content) {
|
|
1295
|
+
const result2 = walkNode(child);
|
|
1296
|
+
if (Array.isArray(result2)) newContent.push(...result2);
|
|
1297
|
+
else newContent.push(result2);
|
|
1298
|
+
}
|
|
1299
|
+
return { ...node, content: newContent };
|
|
1300
|
+
}
|
|
1301
|
+
return node;
|
|
1302
|
+
}
|
|
1303
|
+
const result = walkNode(content);
|
|
1304
|
+
return {
|
|
1305
|
+
content: Array.isArray(result) ? { ...content, content: result } : result,
|
|
1306
|
+
values: enrichedValues
|
|
1307
|
+
};
|
|
788
1308
|
}
|
|
789
1309
|
|
|
790
1310
|
// src/utils/error-helpers.ts
|
|
@@ -922,10 +1442,40 @@ function buildMetadataObject(fields, actualFieldNames) {
|
|
|
922
1442
|
// src/utils/pdf-generator.ts
|
|
923
1443
|
var PAGE_WIDTH = 595.28;
|
|
924
1444
|
var PAGE_HEIGHT = 841.89;
|
|
925
|
-
var MARGIN =
|
|
1445
|
+
var MARGIN = 40;
|
|
926
1446
|
var CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN;
|
|
927
1447
|
var CONTENT_HEIGHT = PAGE_HEIGHT - 2 * MARGIN;
|
|
928
1448
|
var LINE_HEIGHT_FACTOR = 1.4;
|
|
1449
|
+
var BODY_FONT_SIZE = 10;
|
|
1450
|
+
function hexToRgbColor(hex) {
|
|
1451
|
+
if (!hex) return null;
|
|
1452
|
+
const m = hex.trim().match(/^#?([0-9a-fA-F]{6})$/);
|
|
1453
|
+
if (!m) return null;
|
|
1454
|
+
const n = parseInt(m[1], 16);
|
|
1455
|
+
return rgb((n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255);
|
|
1456
|
+
}
|
|
1457
|
+
function contrastTextColor(fill) {
|
|
1458
|
+
const c = fill;
|
|
1459
|
+
const luminance = 0.299 * c.red + 0.587 * c.green + 0.114 * c.blue;
|
|
1460
|
+
return luminance > 0.6 ? rgb(0, 0, 0) : rgb(1, 1, 1);
|
|
1461
|
+
}
|
|
1462
|
+
function resolveTheme(theme) {
|
|
1463
|
+
const primary = hexToRgbColor(theme?.primaryColor);
|
|
1464
|
+
const accent = hexToRgbColor(theme?.accentColor);
|
|
1465
|
+
const border = hexToRgbColor(theme?.borderColor);
|
|
1466
|
+
return {
|
|
1467
|
+
tableHeaderBg: primary ?? rgb(0.3, 0.3, 0.3),
|
|
1468
|
+
tableHeaderText: primary ? contrastTextColor(primary) : rgb(1, 1, 1),
|
|
1469
|
+
tableBorder: border ?? rgb(0.75, 0.75, 0.75),
|
|
1470
|
+
panelDarkHeaderBg: primary ?? rgb(0.25, 0.25, 0.25),
|
|
1471
|
+
panelDarkHeaderText: primary ? contrastTextColor(primary) : rgb(1, 1, 1),
|
|
1472
|
+
panelBorder: border ?? rgb(0.6, 0.6, 0.6),
|
|
1473
|
+
rule: primary ?? rgb(0, 0, 0),
|
|
1474
|
+
pageHeaderText: primary ?? rgb(0, 0, 0),
|
|
1475
|
+
pageHeaderRule: accent ?? primary ?? rgb(0.75, 0.75, 0.75)
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
var DEFAULT_REGION = { leftX: MARGIN, width: CONTENT_WIDTH };
|
|
929
1479
|
var FIELD_DISPLAY = {
|
|
930
1480
|
["text" /* TEXT */]: { label: "Text", width: 120, height: 30 },
|
|
931
1481
|
["signature" /* SIGNATURE */]: { label: "Signature", width: 200, height: 60 },
|
|
@@ -956,17 +1506,65 @@ function getFieldDimensions(fieldType, attrs, font, fontSize) {
|
|
|
956
1506
|
const height = textLineHeight + 4;
|
|
957
1507
|
return { width, height };
|
|
958
1508
|
}
|
|
1509
|
+
async function loadImageBytes(src) {
|
|
1510
|
+
if (src.startsWith("data:")) {
|
|
1511
|
+
const comma = src.indexOf(",");
|
|
1512
|
+
if (comma === -1) throw new Error("malformed data URL");
|
|
1513
|
+
const meta = src.slice(0, comma);
|
|
1514
|
+
if (!/;base64$/i.test(meta)) throw new Error("data URL must be base64-encoded");
|
|
1515
|
+
const bin = atob(src.slice(comma + 1));
|
|
1516
|
+
const bytes = new Uint8Array(bin.length);
|
|
1517
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
1518
|
+
return bytes;
|
|
1519
|
+
}
|
|
1520
|
+
const res = await fetch(src);
|
|
1521
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1522
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
1523
|
+
}
|
|
1524
|
+
function describeImageSrc(src) {
|
|
1525
|
+
return src.length > 80 ? `${src.slice(0, 77)}...` : src;
|
|
1526
|
+
}
|
|
1527
|
+
async function preloadImages(pdfDoc, content) {
|
|
1528
|
+
const srcs = /* @__PURE__ */ new Set();
|
|
1529
|
+
(function walk(node) {
|
|
1530
|
+
if (node.type === "imageNode") {
|
|
1531
|
+
const src = node.attrs?.src || "";
|
|
1532
|
+
if (src) srcs.add(src);
|
|
1533
|
+
}
|
|
1534
|
+
for (const child of node.content || []) walk(child);
|
|
1535
|
+
})(content);
|
|
1536
|
+
const images = /* @__PURE__ */ new Map();
|
|
1537
|
+
const warnings = [];
|
|
1538
|
+
for (const src of srcs) {
|
|
1539
|
+
try {
|
|
1540
|
+
const bytes = await loadImageBytes(src);
|
|
1541
|
+
const isPng = bytes.length > 4 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71;
|
|
1542
|
+
const isJpeg = bytes.length > 2 && bytes[0] === 255 && bytes[1] === 216;
|
|
1543
|
+
if (isPng) {
|
|
1544
|
+
images.set(src, await pdfDoc.embedPng(bytes));
|
|
1545
|
+
} else if (isJpeg) {
|
|
1546
|
+
images.set(src, await pdfDoc.embedJpg(bytes));
|
|
1547
|
+
} else {
|
|
1548
|
+
warnings.push(`Image skipped (unsupported format \u2014 only PNG/JPEG embed): ${describeImageSrc(src)}`);
|
|
1549
|
+
}
|
|
1550
|
+
} catch (err) {
|
|
1551
|
+
warnings.push(`Image skipped (${getErrorMessage(err)}): ${describeImageSrc(src)}`);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
return { images, warnings };
|
|
1555
|
+
}
|
|
959
1556
|
function ensureSpace(state, neededHeight) {
|
|
960
1557
|
if (state.y + neededHeight > CONTENT_HEIGHT) {
|
|
961
1558
|
const newPage = state.pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
|
|
962
1559
|
state.currentPage = newPage;
|
|
963
1560
|
state.pageIndex++;
|
|
964
1561
|
state.y = 0;
|
|
1562
|
+
renderWatermarksOnPage(newPage, state.fonts, state.watermarkNodes);
|
|
965
1563
|
}
|
|
966
1564
|
}
|
|
967
|
-
function drawText(state, text, font, fontSize, indent = 0) {
|
|
1565
|
+
function drawText(state, text, font, fontSize, indent = 0, region = DEFAULT_REGION) {
|
|
968
1566
|
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
969
|
-
const maxWidth =
|
|
1567
|
+
const maxWidth = region.width - indent;
|
|
970
1568
|
const words = text.split(/\s+/);
|
|
971
1569
|
let line = "";
|
|
972
1570
|
let totalAdvance = 0;
|
|
@@ -977,7 +1575,7 @@ function drawText(state, text, font, fontSize, indent = 0) {
|
|
|
977
1575
|
ensureSpace(state, lineHeight);
|
|
978
1576
|
const pdfY = PAGE_HEIGHT - MARGIN - state.y - fontSize;
|
|
979
1577
|
state.currentPage.drawText(line, {
|
|
980
|
-
x:
|
|
1578
|
+
x: region.leftX + indent,
|
|
981
1579
|
y: pdfY,
|
|
982
1580
|
size: fontSize,
|
|
983
1581
|
font,
|
|
@@ -994,7 +1592,7 @@ function drawText(state, text, font, fontSize, indent = 0) {
|
|
|
994
1592
|
ensureSpace(state, lineHeight);
|
|
995
1593
|
const pdfY = PAGE_HEIGHT - MARGIN - state.y - fontSize;
|
|
996
1594
|
state.currentPage.drawText(line, {
|
|
997
|
-
x:
|
|
1595
|
+
x: region.leftX + indent,
|
|
998
1596
|
y: pdfY,
|
|
999
1597
|
size: fontSize,
|
|
1000
1598
|
font,
|
|
@@ -1038,10 +1636,10 @@ function collectInlineSegments(content, fonts, fontSize) {
|
|
|
1038
1636
|
}
|
|
1039
1637
|
return segments;
|
|
1040
1638
|
}
|
|
1041
|
-
function layoutInlineSegments(state, segments, fontSize, indent = 0) {
|
|
1639
|
+
function layoutInlineSegments(state, segments, fontSize, indent = 0, region = DEFAULT_REGION) {
|
|
1042
1640
|
const textLineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
1043
|
-
const maxX =
|
|
1044
|
-
const startX =
|
|
1641
|
+
const maxX = region.leftX + region.width;
|
|
1642
|
+
const startX = region.leftX + indent;
|
|
1045
1643
|
let currentX = startX;
|
|
1046
1644
|
let currentLineHeight = 0;
|
|
1047
1645
|
let lineHasContent = false;
|
|
@@ -1146,90 +1744,395 @@ function layoutInlineSegments(state, segments, fontSize, indent = 0) {
|
|
|
1146
1744
|
}
|
|
1147
1745
|
flushLine();
|
|
1148
1746
|
}
|
|
1149
|
-
function processInlineContent(state, content, fontSize, indent = 0) {
|
|
1747
|
+
function processInlineContent(state, content, fontSize, indent = 0, region = DEFAULT_REGION) {
|
|
1150
1748
|
if (!content) return;
|
|
1151
1749
|
const segments = collectInlineSegments(content, state.fonts, fontSize);
|
|
1152
1750
|
if (segments.length === 0) return;
|
|
1153
|
-
layoutInlineSegments(state, segments, fontSize, indent);
|
|
1751
|
+
layoutInlineSegments(state, segments, fontSize, indent, region);
|
|
1154
1752
|
}
|
|
1155
|
-
function
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
boldItalic: state.fonts.boldItalic
|
|
1170
|
-
};
|
|
1171
|
-
const segments = collectInlineSegments(node.content, headingFonts, fontSize);
|
|
1172
|
-
layoutInlineSegments(state, segments, fontSize);
|
|
1173
|
-
state.y += spacing / 2;
|
|
1174
|
-
break;
|
|
1175
|
-
}
|
|
1176
|
-
case "paragraph": {
|
|
1177
|
-
const fontSize = 12;
|
|
1178
|
-
if (!node.content || node.content.length === 0) {
|
|
1179
|
-
state.y += fontSize * LINE_HEIGHT_FACTOR;
|
|
1180
|
-
break;
|
|
1753
|
+
function extractCellText(cell) {
|
|
1754
|
+
const parts = [];
|
|
1755
|
+
for (const child of cell.content || []) {
|
|
1756
|
+
if (child.type === "paragraph" || child.type === "heading") {
|
|
1757
|
+
for (const inline of child.content || []) {
|
|
1758
|
+
if (inline.type === "text") {
|
|
1759
|
+
parts.push(inline.text || "");
|
|
1760
|
+
} else if (inline.type === "variableNode") {
|
|
1761
|
+
const label = inline.attrs?.varLabel || inline.attrs?.varName || "Variable";
|
|
1762
|
+
parts.push(`[${label}]`);
|
|
1763
|
+
} else if (inline.type === "fieldNode") {
|
|
1764
|
+
const label = inline.attrs?.fieldLabel || inline.attrs?.fieldName || "Field";
|
|
1765
|
+
parts.push(`[ ${label} ]`);
|
|
1766
|
+
}
|
|
1181
1767
|
}
|
|
1182
|
-
processInlineContent(state, node.content, fontSize);
|
|
1183
|
-
state.y += 4;
|
|
1184
|
-
break;
|
|
1185
1768
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1769
|
+
}
|
|
1770
|
+
return parts.join("");
|
|
1771
|
+
}
|
|
1772
|
+
function isNumericText(text) {
|
|
1773
|
+
return /^\s*\$?\s*[\d,.]+\s*$/.test(text);
|
|
1774
|
+
}
|
|
1775
|
+
function measureCellHeight(cell, maxWidth, fonts, fontSize, isHeader) {
|
|
1776
|
+
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
1777
|
+
const font = isHeader ? fonts.bold : fonts.regular;
|
|
1778
|
+
let totalHeight = 0;
|
|
1779
|
+
for (const child of cell.content || []) {
|
|
1780
|
+
if (child.type === "paragraph" || child.type === "heading") {
|
|
1781
|
+
const text = extractCellText({ content: [child] });
|
|
1782
|
+
if (!text) {
|
|
1783
|
+
totalHeight += lineHeight;
|
|
1784
|
+
continue;
|
|
1785
|
+
}
|
|
1786
|
+
const words = text.split(/\s+/);
|
|
1787
|
+
let line = "";
|
|
1788
|
+
let lineCount = 0;
|
|
1789
|
+
for (const word of words) {
|
|
1790
|
+
const testLine = line ? `${line} ${word}` : word;
|
|
1791
|
+
const testWidth = font.widthOfTextAtSize(testLine, fontSize);
|
|
1792
|
+
if (testWidth > maxWidth && line) {
|
|
1793
|
+
lineCount++;
|
|
1794
|
+
line = word;
|
|
1795
|
+
} else {
|
|
1796
|
+
line = testLine;
|
|
1200
1797
|
}
|
|
1201
1798
|
}
|
|
1202
|
-
|
|
1203
|
-
|
|
1799
|
+
if (line) lineCount++;
|
|
1800
|
+
totalHeight += lineCount * lineHeight;
|
|
1204
1801
|
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1802
|
+
}
|
|
1803
|
+
return Math.max(totalHeight, lineHeight);
|
|
1804
|
+
}
|
|
1805
|
+
function renderCellContent(state, cell, cellX, cellY, cellWidth, fonts, fontSize, isHeader, rightAlign, textColor = rgb(0, 0, 0)) {
|
|
1806
|
+
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
1807
|
+
const cellPadding = 4;
|
|
1808
|
+
const maxTextWidth = cellWidth - cellPadding * 2;
|
|
1809
|
+
let textX = cellX + cellPadding;
|
|
1810
|
+
let textY = cellY;
|
|
1811
|
+
for (const child of cell.content || []) {
|
|
1812
|
+
if (child.type === "paragraph" || child.type === "heading") {
|
|
1813
|
+
const segments = collectInlineSegments(
|
|
1814
|
+
child.content || [],
|
|
1815
|
+
isHeader ? { regular: fonts.bold, bold: fonts.bold, italic: fonts.boldItalic, boldItalic: fonts.boldItalic } : fonts,
|
|
1816
|
+
fontSize
|
|
1817
|
+
);
|
|
1818
|
+
if (rightAlign && segments.length > 0) {
|
|
1819
|
+
const fullText = segments.filter((s) => s.kind === "text").map((s) => s.text).join("");
|
|
1820
|
+
const textWidth = (isHeader ? fonts.bold : fonts.regular).widthOfTextAtSize(fullText, fontSize);
|
|
1821
|
+
if (textWidth <= maxTextWidth) {
|
|
1822
|
+
const pdfY = PAGE_HEIGHT - textY - fontSize;
|
|
1823
|
+
state.currentPage.drawText(fullText, {
|
|
1824
|
+
x: cellX + cellWidth - cellPadding - textWidth,
|
|
1825
|
+
y: pdfY,
|
|
1215
1826
|
size: fontSize,
|
|
1216
|
-
font:
|
|
1217
|
-
color:
|
|
1827
|
+
font: isHeader ? fonts.bold : fonts.regular,
|
|
1828
|
+
color: textColor
|
|
1218
1829
|
});
|
|
1219
|
-
|
|
1830
|
+
return;
|
|
1220
1831
|
}
|
|
1221
|
-
num++;
|
|
1222
1832
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1833
|
+
let currentX = textX;
|
|
1834
|
+
for (const seg of segments) {
|
|
1835
|
+
if (seg.kind === "break") {
|
|
1836
|
+
textY += lineHeight;
|
|
1837
|
+
currentX = textX;
|
|
1838
|
+
continue;
|
|
1839
|
+
}
|
|
1840
|
+
if (seg.kind === "text") {
|
|
1841
|
+
const words = seg.text.split(/(\s+)/);
|
|
1842
|
+
const spaceWidth = seg.font.widthOfTextAtSize(" ", fontSize);
|
|
1843
|
+
for (const token of words) {
|
|
1844
|
+
if (!token) continue;
|
|
1845
|
+
if (/^\s+$/.test(token)) {
|
|
1846
|
+
currentX += spaceWidth;
|
|
1847
|
+
continue;
|
|
1848
|
+
}
|
|
1849
|
+
const wordWidth = seg.font.widthOfTextAtSize(token, fontSize);
|
|
1850
|
+
if (currentX + wordWidth > textX + maxTextWidth && currentX > textX) {
|
|
1851
|
+
textY += lineHeight;
|
|
1852
|
+
currentX = textX;
|
|
1853
|
+
}
|
|
1854
|
+
const pdfY = PAGE_HEIGHT - textY - fontSize;
|
|
1855
|
+
state.currentPage.drawText(token, {
|
|
1856
|
+
x: currentX,
|
|
1857
|
+
y: pdfY,
|
|
1858
|
+
size: fontSize,
|
|
1859
|
+
font: seg.font,
|
|
1860
|
+
color: textColor
|
|
1861
|
+
});
|
|
1862
|
+
currentX += wordWidth;
|
|
1863
|
+
}
|
|
1864
|
+
} else if (seg.kind === "field") {
|
|
1865
|
+
const pdfY = PAGE_HEIGHT - textY - seg.height;
|
|
1866
|
+
if (state.drawFieldPlaceholders) {
|
|
1867
|
+
state.currentPage.drawRectangle({
|
|
1868
|
+
x: currentX,
|
|
1869
|
+
y: pdfY,
|
|
1870
|
+
width: seg.width,
|
|
1871
|
+
height: seg.height,
|
|
1872
|
+
borderColor: rgb(0.5, 0.5, 0.7),
|
|
1873
|
+
borderWidth: 0.5,
|
|
1874
|
+
color: rgb(0.95, 0.95, 1),
|
|
1875
|
+
borderDashArray: [4, 2]
|
|
1876
|
+
});
|
|
1877
|
+
}
|
|
1878
|
+
const fieldId = seg.attrs.fieldId;
|
|
1879
|
+
if (fieldId) {
|
|
1880
|
+
state.fieldPositions.set(fieldId, {
|
|
1881
|
+
x: currentX,
|
|
1882
|
+
y: pdfY,
|
|
1883
|
+
width: seg.width,
|
|
1884
|
+
height: seg.height,
|
|
1885
|
+
page: state.pageIndex + 1
|
|
1886
|
+
});
|
|
1887
|
+
}
|
|
1888
|
+
currentX += seg.width + 4;
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
function renderTable(state, node, region = DEFAULT_REGION) {
|
|
1895
|
+
const tableRows = node.content || [];
|
|
1896
|
+
if (tableRows.length === 0) return;
|
|
1897
|
+
const borderless = node.attrs?.borderless === true;
|
|
1898
|
+
const subtotals = node.attrs?.subtotals === true;
|
|
1899
|
+
const fontSize = 10;
|
|
1900
|
+
const cellPadding = borderless ? 2 : 4;
|
|
1901
|
+
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
1902
|
+
const rowPadding = cellPadding * 2;
|
|
1903
|
+
const borderColor = state.theme.tableBorder;
|
|
1904
|
+
const headerBg = state.theme.tableHeaderBg;
|
|
1905
|
+
const headerTextColor = state.theme.tableHeaderText;
|
|
1906
|
+
let colCount = 0;
|
|
1907
|
+
for (const row of tableRows) {
|
|
1908
|
+
colCount = Math.max(colCount, row.content?.length || 0);
|
|
1909
|
+
}
|
|
1910
|
+
if (colCount === 0) return;
|
|
1911
|
+
const colMaxWidths = new Array(colCount).fill(0);
|
|
1912
|
+
for (const row of tableRows) {
|
|
1913
|
+
const cells = row.content || [];
|
|
1914
|
+
for (let c = 0; c < cells.length && c < colCount; c++) {
|
|
1915
|
+
const cell = cells[c];
|
|
1916
|
+
const isHeader = cell.type === "tableHeader";
|
|
1917
|
+
const text = extractCellText(cell);
|
|
1918
|
+
const font = isHeader ? state.fonts.bold : state.fonts.regular;
|
|
1919
|
+
const textWidth = font.widthOfTextAtSize(text, fontSize);
|
|
1920
|
+
colMaxWidths[c] = Math.max(colMaxWidths[c], textWidth + cellPadding * 2 + 4);
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
const totalMeasured = colMaxWidths.reduce((a, b) => a + b, 0);
|
|
1924
|
+
const minColWidth = 40;
|
|
1925
|
+
let colWidths;
|
|
1926
|
+
if (totalMeasured <= region.width) {
|
|
1927
|
+
const scale = region.width / totalMeasured;
|
|
1928
|
+
colWidths = colMaxWidths.map((w) => Math.max(w * scale, minColWidth));
|
|
1929
|
+
} else {
|
|
1930
|
+
colWidths = colMaxWidths.map(
|
|
1931
|
+
(w) => Math.max(w / totalMeasured * region.width, minColWidth)
|
|
1932
|
+
);
|
|
1933
|
+
}
|
|
1934
|
+
const colSum = colWidths.reduce((a, b) => a + b, 0);
|
|
1935
|
+
if (colSum > 0) {
|
|
1936
|
+
const normFactor = region.width / colSum;
|
|
1937
|
+
colWidths = colWidths.map((w) => w * normFactor);
|
|
1938
|
+
}
|
|
1939
|
+
const headerRow = tableRows[0];
|
|
1940
|
+
const hasHeader = headerRow?.content?.[0]?.type === "tableHeader";
|
|
1941
|
+
function renderRow(row, isHeaderRow) {
|
|
1942
|
+
const cells = row.content || [];
|
|
1943
|
+
const isHeader = isHeaderRow && hasHeader;
|
|
1944
|
+
let rowHeight = lineHeight;
|
|
1945
|
+
for (let c = 0; c < cells.length && c < colCount; c++) {
|
|
1946
|
+
const cellContentWidth = colWidths[c] - cellPadding * 2;
|
|
1947
|
+
const cellH = measureCellHeight(cells[c], cellContentWidth, state.fonts, fontSize, isHeader);
|
|
1948
|
+
rowHeight = Math.max(rowHeight, cellH);
|
|
1949
|
+
}
|
|
1950
|
+
rowHeight += rowPadding;
|
|
1951
|
+
ensureSpace(state, rowHeight);
|
|
1952
|
+
const rowTopY = state.y;
|
|
1953
|
+
let cellX = region.leftX;
|
|
1954
|
+
for (let c = 0; c < cells.length && c < colCount; c++) {
|
|
1955
|
+
const cellW = colWidths[c];
|
|
1956
|
+
const cell = cells[c];
|
|
1957
|
+
const pdfCellTop = PAGE_HEIGHT - MARGIN - rowTopY;
|
|
1958
|
+
const pdfCellBottom = pdfCellTop - rowHeight;
|
|
1959
|
+
if (isHeader && !borderless) {
|
|
1960
|
+
state.currentPage.drawRectangle({
|
|
1961
|
+
x: cellX,
|
|
1962
|
+
y: pdfCellBottom,
|
|
1963
|
+
width: cellW,
|
|
1964
|
+
height: rowHeight,
|
|
1965
|
+
color: headerBg
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1968
|
+
if (!borderless) {
|
|
1969
|
+
state.currentPage.drawRectangle({
|
|
1970
|
+
x: cellX,
|
|
1971
|
+
y: pdfCellBottom,
|
|
1972
|
+
width: cellW,
|
|
1973
|
+
height: rowHeight,
|
|
1974
|
+
borderColor,
|
|
1975
|
+
borderWidth: 0.5
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1978
|
+
const cellText = extractCellText(cell);
|
|
1979
|
+
const rightAlign = !isHeader && isNumericText(cellText);
|
|
1980
|
+
const contentY = MARGIN + rowTopY + cellPadding;
|
|
1981
|
+
const effectiveHeader = isHeader && !borderless;
|
|
1982
|
+
renderCellContent(
|
|
1983
|
+
state,
|
|
1984
|
+
cell,
|
|
1985
|
+
cellX,
|
|
1986
|
+
contentY,
|
|
1987
|
+
cellW,
|
|
1988
|
+
state.fonts,
|
|
1989
|
+
fontSize,
|
|
1990
|
+
effectiveHeader,
|
|
1991
|
+
rightAlign,
|
|
1992
|
+
effectiveHeader ? headerTextColor : rgb(0, 0, 0)
|
|
1993
|
+
);
|
|
1994
|
+
cellX += cellW;
|
|
1995
|
+
}
|
|
1996
|
+
state.y += rowHeight;
|
|
1997
|
+
}
|
|
1998
|
+
if (hasHeader && tableRows.length > 0) {
|
|
1999
|
+
renderRow(tableRows[0], true);
|
|
2000
|
+
}
|
|
2001
|
+
let lastPageIndex = state.pageIndex;
|
|
2002
|
+
const startIdx = hasHeader ? 1 : 0;
|
|
2003
|
+
const bodyRowCount = tableRows.length - startIdx;
|
|
2004
|
+
for (let r = startIdx; r < tableRows.length; r++) {
|
|
2005
|
+
const cells = tableRows[r].content || [];
|
|
2006
|
+
let estRowHeight = lineHeight;
|
|
2007
|
+
for (let c = 0; c < cells.length && c < colCount; c++) {
|
|
2008
|
+
const cellContentWidth = colWidths[c] - cellPadding * 2;
|
|
2009
|
+
const cellH = measureCellHeight(cells[c], cellContentWidth, state.fonts, fontSize, false);
|
|
2010
|
+
estRowHeight = Math.max(estRowHeight, cellH);
|
|
2011
|
+
}
|
|
2012
|
+
estRowHeight += rowPadding;
|
|
2013
|
+
if (state.y + estRowHeight > CONTENT_HEIGHT) {
|
|
2014
|
+
ensureSpace(state, estRowHeight + (hasHeader ? lineHeight + rowPadding : 0));
|
|
2015
|
+
if (state.pageIndex !== lastPageIndex && hasHeader) {
|
|
2016
|
+
renderRow(headerRow, true);
|
|
2017
|
+
lastPageIndex = state.pageIndex;
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
const rowTopYBeforeRender = state.y;
|
|
2021
|
+
renderRow(tableRows[r], false);
|
|
2022
|
+
lastPageIndex = state.pageIndex;
|
|
2023
|
+
if (subtotals && colCount >= 2) {
|
|
2024
|
+
const bodyIdx = r - startIdx;
|
|
2025
|
+
const isFirstBody = bodyIdx === 0;
|
|
2026
|
+
const isLastBody = bodyIdx === bodyRowCount - 1;
|
|
2027
|
+
if (isFirstBody || isLastBody) {
|
|
2028
|
+
const valueStartX = region.leftX + colWidths[0];
|
|
2029
|
+
const valueEndX = region.leftX + colWidths.reduce((a, b) => a + b, 0);
|
|
2030
|
+
const pdfRowTopY = PAGE_HEIGHT - MARGIN - rowTopYBeforeRender;
|
|
2031
|
+
const overlineColor = rgb(0, 0, 0);
|
|
2032
|
+
if (isFirstBody) {
|
|
2033
|
+
state.currentPage.drawLine({
|
|
2034
|
+
start: { x: valueStartX, y: pdfRowTopY },
|
|
2035
|
+
end: { x: valueEndX, y: pdfRowTopY },
|
|
2036
|
+
thickness: 0.5,
|
|
2037
|
+
color: overlineColor
|
|
2038
|
+
});
|
|
2039
|
+
} else if (isLastBody) {
|
|
2040
|
+
state.currentPage.drawLine({
|
|
2041
|
+
start: { x: valueStartX, y: pdfRowTopY + 2 },
|
|
2042
|
+
end: { x: valueEndX, y: pdfRowTopY + 2 },
|
|
2043
|
+
thickness: 0.5,
|
|
2044
|
+
color: overlineColor
|
|
2045
|
+
});
|
|
2046
|
+
state.currentPage.drawLine({
|
|
2047
|
+
start: { x: valueStartX, y: pdfRowTopY },
|
|
2048
|
+
end: { x: valueEndX, y: pdfRowTopY },
|
|
2049
|
+
thickness: 0.5,
|
|
2050
|
+
color: overlineColor
|
|
2051
|
+
});
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
state.y += 3;
|
|
2057
|
+
}
|
|
2058
|
+
function processBlock(state, node, region = DEFAULT_REGION) {
|
|
2059
|
+
switch (node.type) {
|
|
2060
|
+
case "heading": {
|
|
2061
|
+
const level = node.attrs?.level || 1;
|
|
2062
|
+
const fontSizes = { 1: 24, 2: 20, 3: 16 };
|
|
2063
|
+
const fontSize = fontSizes[level] || 16;
|
|
2064
|
+
const spacing = fontSize * 0.6;
|
|
2065
|
+
ensureSpace(state, fontSize * LINE_HEIGHT_FACTOR + spacing);
|
|
2066
|
+
state.y += spacing / 2;
|
|
2067
|
+
if (!node.content) break;
|
|
2068
|
+
const headingFonts = {
|
|
2069
|
+
regular: state.fonts.bold,
|
|
2070
|
+
bold: state.fonts.bold,
|
|
2071
|
+
italic: state.fonts.boldItalic,
|
|
2072
|
+
boldItalic: state.fonts.boldItalic
|
|
2073
|
+
};
|
|
2074
|
+
const segments = collectInlineSegments(node.content, headingFonts, fontSize);
|
|
2075
|
+
layoutInlineSegments(state, segments, fontSize, 0, region);
|
|
2076
|
+
state.y += spacing / 2;
|
|
2077
|
+
break;
|
|
2078
|
+
}
|
|
2079
|
+
case "paragraph": {
|
|
2080
|
+
const fontSize = BODY_FONT_SIZE;
|
|
2081
|
+
if (!node.content || node.content.length === 0) {
|
|
2082
|
+
state.y += fontSize * LINE_HEIGHT_FACTOR * 0.65;
|
|
2083
|
+
break;
|
|
2084
|
+
}
|
|
2085
|
+
processInlineContent(state, node.content, fontSize, 0, region);
|
|
2086
|
+
state.y += 2;
|
|
2087
|
+
break;
|
|
2088
|
+
}
|
|
2089
|
+
case "bulletList": {
|
|
2090
|
+
for (const item of node.content || []) {
|
|
2091
|
+
for (const child of item.content || []) {
|
|
2092
|
+
const fontSize = BODY_FONT_SIZE;
|
|
2093
|
+
ensureSpace(state, fontSize * LINE_HEIGHT_FACTOR);
|
|
2094
|
+
const bulletPdfY = PAGE_HEIGHT - MARGIN - state.y - fontSize;
|
|
2095
|
+
state.currentPage.drawText("\u2022", {
|
|
2096
|
+
x: region.leftX + 8,
|
|
2097
|
+
y: bulletPdfY,
|
|
2098
|
+
size: fontSize,
|
|
2099
|
+
font: state.fonts.regular,
|
|
2100
|
+
color: rgb(0, 0, 0)
|
|
2101
|
+
});
|
|
2102
|
+
processInlineContent(state, child.content, fontSize, 24, region);
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
state.y += 4;
|
|
2106
|
+
break;
|
|
2107
|
+
}
|
|
2108
|
+
case "orderedList": {
|
|
2109
|
+
let num = 1;
|
|
2110
|
+
for (const item of node.content || []) {
|
|
2111
|
+
for (const child of item.content || []) {
|
|
2112
|
+
const fontSize = BODY_FONT_SIZE;
|
|
2113
|
+
ensureSpace(state, fontSize * LINE_HEIGHT_FACTOR);
|
|
2114
|
+
const numPdfY = PAGE_HEIGHT - MARGIN - state.y - fontSize;
|
|
2115
|
+
state.currentPage.drawText(`${num}.`, {
|
|
2116
|
+
x: region.leftX + 4,
|
|
2117
|
+
y: numPdfY,
|
|
2118
|
+
size: fontSize,
|
|
2119
|
+
font: state.fonts.regular,
|
|
2120
|
+
color: rgb(0, 0, 0)
|
|
2121
|
+
});
|
|
2122
|
+
processInlineContent(state, child.content, fontSize, 24, region);
|
|
2123
|
+
}
|
|
2124
|
+
num++;
|
|
2125
|
+
}
|
|
2126
|
+
state.y += 4;
|
|
2127
|
+
break;
|
|
2128
|
+
}
|
|
1226
2129
|
case "blockquote": {
|
|
1227
2130
|
const startY = state.y;
|
|
1228
2131
|
for (const child of node.content || []) {
|
|
1229
|
-
processInlineContent(state, child.content, 12, 16);
|
|
2132
|
+
processInlineContent(state, child.content, 12, 16, region);
|
|
1230
2133
|
}
|
|
1231
2134
|
const endY = state.y;
|
|
1232
|
-
const barX =
|
|
2135
|
+
const barX = region.leftX + 4;
|
|
1233
2136
|
const barTop = PAGE_HEIGHT - MARGIN - startY;
|
|
1234
2137
|
const barBottom = PAGE_HEIGHT - MARGIN - endY;
|
|
1235
2138
|
state.currentPage.drawLine({
|
|
@@ -1242,16 +2145,16 @@ function processBlock(state, node) {
|
|
|
1242
2145
|
break;
|
|
1243
2146
|
}
|
|
1244
2147
|
case "horizontalRule": {
|
|
1245
|
-
ensureSpace(state,
|
|
1246
|
-
state.y +=
|
|
2148
|
+
ensureSpace(state, 10);
|
|
2149
|
+
state.y += 4;
|
|
1247
2150
|
const ruleY = PAGE_HEIGHT - MARGIN - state.y;
|
|
1248
2151
|
state.currentPage.drawLine({
|
|
1249
|
-
start: { x:
|
|
1250
|
-
end: { x:
|
|
1251
|
-
thickness:
|
|
1252
|
-
color:
|
|
2152
|
+
start: { x: region.leftX, y: ruleY },
|
|
2153
|
+
end: { x: region.leftX + region.width, y: ruleY },
|
|
2154
|
+
thickness: 2.5,
|
|
2155
|
+
color: state.theme.rule
|
|
1253
2156
|
});
|
|
1254
|
-
state.y +=
|
|
2157
|
+
state.y += 4;
|
|
1255
2158
|
break;
|
|
1256
2159
|
}
|
|
1257
2160
|
case "codeBlock": {
|
|
@@ -1263,9 +2166,9 @@ function processBlock(state, node) {
|
|
|
1263
2166
|
ensureSpace(state, blockHeight);
|
|
1264
2167
|
const boxY = PAGE_HEIGHT - MARGIN - state.y - blockHeight;
|
|
1265
2168
|
state.currentPage.drawRectangle({
|
|
1266
|
-
x:
|
|
2169
|
+
x: region.leftX,
|
|
1267
2170
|
y: boxY,
|
|
1268
|
-
width:
|
|
2171
|
+
width: region.width,
|
|
1269
2172
|
height: blockHeight,
|
|
1270
2173
|
color: rgb(0.95, 0.95, 0.95),
|
|
1271
2174
|
borderColor: rgb(0.85, 0.85, 0.85),
|
|
@@ -1273,18 +2176,325 @@ function processBlock(state, node) {
|
|
|
1273
2176
|
});
|
|
1274
2177
|
state.y += 8;
|
|
1275
2178
|
for (const line of lines) {
|
|
1276
|
-
drawText(state, line || " ", state.fonts.regular, fontSize, 8);
|
|
2179
|
+
drawText(state, line || " ", state.fonts.regular, fontSize, 8, region);
|
|
1277
2180
|
}
|
|
1278
2181
|
state.y += 8;
|
|
1279
2182
|
break;
|
|
1280
2183
|
}
|
|
2184
|
+
case "table": {
|
|
2185
|
+
renderTable(state, node, region);
|
|
2186
|
+
break;
|
|
2187
|
+
}
|
|
2188
|
+
case "imageNode": {
|
|
2189
|
+
const src = node.attrs?.src || "";
|
|
2190
|
+
const image = src ? state.images.get(src) : void 0;
|
|
2191
|
+
if (!image) break;
|
|
2192
|
+
const natW = image.width;
|
|
2193
|
+
const natH = image.height;
|
|
2194
|
+
let w = parseFloat(node.attrs?.width || "") || 0;
|
|
2195
|
+
let h = parseFloat(node.attrs?.height || "") || 0;
|
|
2196
|
+
if (w && !h) h = natH / natW * w;
|
|
2197
|
+
else if (h && !w) w = natW / natH * h;
|
|
2198
|
+
else if (!w && !h) {
|
|
2199
|
+
w = natW;
|
|
2200
|
+
h = natH;
|
|
2201
|
+
}
|
|
2202
|
+
if (w > region.width) {
|
|
2203
|
+
h = h * (region.width / w);
|
|
2204
|
+
w = region.width;
|
|
2205
|
+
}
|
|
2206
|
+
const maxH = CONTENT_HEIGHT * 0.6;
|
|
2207
|
+
if (h > maxH) {
|
|
2208
|
+
w = w * (maxH / h);
|
|
2209
|
+
h = maxH;
|
|
2210
|
+
}
|
|
2211
|
+
ensureSpace(state, h + 4);
|
|
2212
|
+
const align = node.attrs?.align || "left";
|
|
2213
|
+
let x = region.leftX;
|
|
2214
|
+
if (align === "center") x = region.leftX + (region.width - w) / 2;
|
|
2215
|
+
else if (align === "right") x = region.leftX + region.width - w;
|
|
2216
|
+
const pdfY = PAGE_HEIGHT - MARGIN - state.y - h;
|
|
2217
|
+
state.currentPage.drawImage(image, { x, y: pdfY, width: w, height: h });
|
|
2218
|
+
state.y += h + 4;
|
|
2219
|
+
break;
|
|
2220
|
+
}
|
|
2221
|
+
// ─── Layout directives ─────────────────────────────────────────
|
|
2222
|
+
case "panel": {
|
|
2223
|
+
const title = node.attrs?.title || "";
|
|
2224
|
+
const border = node.attrs?.border || "solid";
|
|
2225
|
+
const headerStyle = node.attrs?.headerStyle || "";
|
|
2226
|
+
const padding = 8;
|
|
2227
|
+
const titleFontSize = 10;
|
|
2228
|
+
const titleHeight = title ? titleFontSize * LINE_HEIGHT_FACTOR + 6 : 0;
|
|
2229
|
+
const panelStartY = state.y;
|
|
2230
|
+
const panelStartPage = state.pageIndex;
|
|
2231
|
+
if (title) {
|
|
2232
|
+
state.y += titleHeight;
|
|
2233
|
+
}
|
|
2234
|
+
const isHeaderOnly = border === "none";
|
|
2235
|
+
if (!isHeaderOnly) {
|
|
2236
|
+
state.y += padding;
|
|
2237
|
+
}
|
|
2238
|
+
const innerRegion = {
|
|
2239
|
+
leftX: region.leftX + padding,
|
|
2240
|
+
width: region.width - padding * 2
|
|
2241
|
+
};
|
|
2242
|
+
if (!isHeaderOnly) {
|
|
2243
|
+
for (const child of node.content || []) {
|
|
2244
|
+
processBlock(state, child, innerRegion);
|
|
2245
|
+
}
|
|
2246
|
+
state.y += padding;
|
|
2247
|
+
}
|
|
2248
|
+
const startPage = state.pdfDoc.getPages()[panelStartPage];
|
|
2249
|
+
if (title) {
|
|
2250
|
+
const titleBarY = PAGE_HEIGHT - MARGIN - panelStartY - titleHeight;
|
|
2251
|
+
const isDarkHeader = headerStyle === "dark";
|
|
2252
|
+
startPage.drawRectangle({
|
|
2253
|
+
x: region.leftX,
|
|
2254
|
+
y: titleBarY,
|
|
2255
|
+
width: region.width,
|
|
2256
|
+
height: titleHeight,
|
|
2257
|
+
color: isDarkHeader ? state.theme.panelDarkHeaderBg : rgb(0.93, 0.93, 0.93),
|
|
2258
|
+
borderColor: state.theme.panelBorder,
|
|
2259
|
+
borderWidth: 0.75
|
|
2260
|
+
});
|
|
2261
|
+
startPage.drawText(title, {
|
|
2262
|
+
x: region.leftX + padding,
|
|
2263
|
+
y: titleBarY + 4,
|
|
2264
|
+
size: titleFontSize,
|
|
2265
|
+
font: state.fonts.bold,
|
|
2266
|
+
color: isDarkHeader ? state.theme.panelDarkHeaderText : rgb(0, 0, 0)
|
|
2267
|
+
});
|
|
2268
|
+
}
|
|
2269
|
+
if (border !== "none") {
|
|
2270
|
+
const borderDashArray = border === "dashed" ? [4, 2] : void 0;
|
|
2271
|
+
if (panelStartPage === state.pageIndex) {
|
|
2272
|
+
const borderY = PAGE_HEIGHT - MARGIN - state.y;
|
|
2273
|
+
const panelHeight = state.y - panelStartY;
|
|
2274
|
+
startPage.drawRectangle({
|
|
2275
|
+
x: region.leftX,
|
|
2276
|
+
y: borderY,
|
|
2277
|
+
width: region.width,
|
|
2278
|
+
height: panelHeight,
|
|
2279
|
+
borderColor: state.theme.panelBorder,
|
|
2280
|
+
borderWidth: 0.75,
|
|
2281
|
+
borderDashArray
|
|
2282
|
+
});
|
|
2283
|
+
} else {
|
|
2284
|
+
const startBorderBottom = MARGIN;
|
|
2285
|
+
const startPanelHeight = PAGE_HEIGHT - MARGIN - panelStartY - startBorderBottom;
|
|
2286
|
+
startPage.drawRectangle({
|
|
2287
|
+
x: region.leftX,
|
|
2288
|
+
y: startBorderBottom,
|
|
2289
|
+
width: region.width,
|
|
2290
|
+
height: startPanelHeight,
|
|
2291
|
+
borderColor: state.theme.panelBorder,
|
|
2292
|
+
borderWidth: 0.75,
|
|
2293
|
+
borderDashArray
|
|
2294
|
+
});
|
|
2295
|
+
const contTop = PAGE_HEIGHT - MARGIN;
|
|
2296
|
+
const contBorderY = PAGE_HEIGHT - MARGIN - state.y;
|
|
2297
|
+
const contHeight = contTop - contBorderY;
|
|
2298
|
+
state.currentPage.drawRectangle({
|
|
2299
|
+
x: region.leftX,
|
|
2300
|
+
y: contBorderY,
|
|
2301
|
+
width: region.width,
|
|
2302
|
+
height: contHeight,
|
|
2303
|
+
borderColor: state.theme.panelBorder,
|
|
2304
|
+
borderWidth: 0.75,
|
|
2305
|
+
borderDashArray
|
|
2306
|
+
});
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
state.y += 4;
|
|
2310
|
+
break;
|
|
2311
|
+
}
|
|
2312
|
+
case "columns": {
|
|
2313
|
+
const split = parseInt(node.attrs?.split || "50", 10);
|
|
2314
|
+
const padX = parseFloat(node.attrs?.padX || "0") || 0;
|
|
2315
|
+
const columns = (node.content || []).filter((c) => c.type === "column");
|
|
2316
|
+
if (columns.length === 0) break;
|
|
2317
|
+
const colRegionLeftX = region.leftX + padX;
|
|
2318
|
+
const colRegionWidth = region.width - 2 * padX;
|
|
2319
|
+
const gap = 12;
|
|
2320
|
+
const columnsStartY = state.y;
|
|
2321
|
+
const columnsStartPage = state.pageIndex;
|
|
2322
|
+
let colWidths;
|
|
2323
|
+
if (columns.length === 2) {
|
|
2324
|
+
const firstWidth = (colRegionWidth - gap) * (split / 100);
|
|
2325
|
+
const secondWidth = colRegionWidth - gap - firstWidth;
|
|
2326
|
+
colWidths = [firstWidth, secondWidth];
|
|
2327
|
+
} else {
|
|
2328
|
+
const eachWidth = (colRegionWidth - gap * (columns.length - 1)) / columns.length;
|
|
2329
|
+
colWidths = columns.map(() => eachWidth);
|
|
2330
|
+
}
|
|
2331
|
+
let maxEndY = columnsStartY;
|
|
2332
|
+
let colX = colRegionLeftX;
|
|
2333
|
+
for (let ci = 0; ci < columns.length; ci++) {
|
|
2334
|
+
const col = columns[ci];
|
|
2335
|
+
const colWidth = colWidths[ci];
|
|
2336
|
+
const colRegion = { leftX: colX, width: colWidth };
|
|
2337
|
+
state.y = columnsStartY;
|
|
2338
|
+
state.pageIndex = columnsStartPage;
|
|
2339
|
+
state.currentPage = state.pdfDoc.getPages()[state.pageIndex];
|
|
2340
|
+
const padTop = parseFloat(col.attrs?.padTop || "0") || 0;
|
|
2341
|
+
if (padTop > 0) {
|
|
2342
|
+
state.y += padTop * BODY_FONT_SIZE * LINE_HEIGHT_FACTOR;
|
|
2343
|
+
}
|
|
2344
|
+
for (const child of col.content || []) {
|
|
2345
|
+
processBlock(state, child, colRegion);
|
|
2346
|
+
}
|
|
2347
|
+
maxEndY = Math.max(maxEndY, state.y);
|
|
2348
|
+
colX += colWidth + gap;
|
|
2349
|
+
}
|
|
2350
|
+
state.y = maxEndY;
|
|
2351
|
+
state.y += 4;
|
|
2352
|
+
break;
|
|
2353
|
+
}
|
|
2354
|
+
case "column": {
|
|
2355
|
+
for (const child of node.content || []) {
|
|
2356
|
+
processBlock(state, child, region);
|
|
2357
|
+
}
|
|
2358
|
+
break;
|
|
2359
|
+
}
|
|
2360
|
+
case "watermark": {
|
|
2361
|
+
break;
|
|
2362
|
+
}
|
|
2363
|
+
case "subtotalsBlock": {
|
|
2364
|
+
const stFontSize = BODY_FONT_SIZE;
|
|
2365
|
+
const stLineHeight = stFontSize * LINE_HEIGHT_FACTOR;
|
|
2366
|
+
const valueRightX = region.leftX + region.width;
|
|
2367
|
+
const rows = (node.content || []).filter(
|
|
2368
|
+
(c) => c.type === "paragraph" && c.content?.length
|
|
2369
|
+
);
|
|
2370
|
+
const parsed = [];
|
|
2371
|
+
for (const row of rows) {
|
|
2372
|
+
let label = "";
|
|
2373
|
+
let value = "";
|
|
2374
|
+
for (const seg of row.content || []) {
|
|
2375
|
+
if (seg.type === "text") {
|
|
2376
|
+
const isBold = seg.marks?.some((m) => m.type === "bold");
|
|
2377
|
+
if (isBold) {
|
|
2378
|
+
label += seg.text || "";
|
|
2379
|
+
} else {
|
|
2380
|
+
const t = (seg.text || "").trim();
|
|
2381
|
+
if (t) value += (value ? " " : "") + t;
|
|
2382
|
+
}
|
|
2383
|
+
} else if (seg.type === "variableNode") {
|
|
2384
|
+
const varLabel = seg.attrs?.varLabel || seg.attrs?.varName || "";
|
|
2385
|
+
const isBoldVar = seg.marks?.some((m) => m.type === "bold");
|
|
2386
|
+
if (isBoldVar) {
|
|
2387
|
+
label += varLabel;
|
|
2388
|
+
} else {
|
|
2389
|
+
value += (value ? " " : "") + `[${varLabel}]`;
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
parsed.push({ label: label.trim(), value: value.trim() });
|
|
2394
|
+
}
|
|
2395
|
+
let maxLabelWidth = 0;
|
|
2396
|
+
for (const { label } of parsed) {
|
|
2397
|
+
if (label) {
|
|
2398
|
+
const w = state.fonts.bold.widthOfTextAtSize(label, stFontSize);
|
|
2399
|
+
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
const gap = 12;
|
|
2403
|
+
const labelX = region.width > 300 ? Math.max(region.leftX + region.width - maxLabelWidth - gap - 80, region.leftX + region.width * 0.4) : region.leftX + 4;
|
|
2404
|
+
const valueColStartX = labelX + maxLabelWidth + gap;
|
|
2405
|
+
const isLastRowTotal = parsed.length > 1;
|
|
2406
|
+
for (let ri = 0; ri < parsed.length; ri++) {
|
|
2407
|
+
const isTotal = isLastRowTotal && ri === parsed.length - 1;
|
|
2408
|
+
if (isTotal) {
|
|
2409
|
+
state.y += stLineHeight * 0.6;
|
|
2410
|
+
}
|
|
2411
|
+
ensureSpace(state, stLineHeight + (isTotal ? 8 : 4));
|
|
2412
|
+
const { label, value } = parsed[ri];
|
|
2413
|
+
const pdfY = PAGE_HEIGHT - MARGIN - state.y - stFontSize;
|
|
2414
|
+
if (isTotal) {
|
|
2415
|
+
state.currentPage.drawLine({
|
|
2416
|
+
start: { x: valueColStartX, y: pdfY + stFontSize + 6 },
|
|
2417
|
+
end: { x: valueRightX, y: pdfY + stFontSize + 6 },
|
|
2418
|
+
thickness: 0.75,
|
|
2419
|
+
color: rgb(0, 0, 0)
|
|
2420
|
+
});
|
|
2421
|
+
state.currentPage.drawLine({
|
|
2422
|
+
start: { x: valueColStartX, y: pdfY + stFontSize + 3 },
|
|
2423
|
+
end: { x: valueRightX, y: pdfY + stFontSize + 3 },
|
|
2424
|
+
thickness: 0.75,
|
|
2425
|
+
color: rgb(0, 0, 0)
|
|
2426
|
+
});
|
|
2427
|
+
}
|
|
2428
|
+
if (label) {
|
|
2429
|
+
state.currentPage.drawText(label, {
|
|
2430
|
+
x: labelX,
|
|
2431
|
+
y: pdfY,
|
|
2432
|
+
size: stFontSize,
|
|
2433
|
+
font: state.fonts.bold,
|
|
2434
|
+
color: rgb(0, 0, 0)
|
|
2435
|
+
});
|
|
2436
|
+
}
|
|
2437
|
+
if (value) {
|
|
2438
|
+
const valueFont = state.fonts.bold;
|
|
2439
|
+
const valueWidth = valueFont.widthOfTextAtSize(value, stFontSize);
|
|
2440
|
+
state.currentPage.drawText(value, {
|
|
2441
|
+
x: valueRightX - valueWidth,
|
|
2442
|
+
y: pdfY,
|
|
2443
|
+
size: stFontSize,
|
|
2444
|
+
font: valueFont,
|
|
2445
|
+
color: rgb(0, 0, 0)
|
|
2446
|
+
});
|
|
2447
|
+
}
|
|
2448
|
+
state.y += stLineHeight;
|
|
2449
|
+
}
|
|
2450
|
+
state.y += 4;
|
|
2451
|
+
break;
|
|
2452
|
+
}
|
|
2453
|
+
case "repeatBlock":
|
|
2454
|
+
if (node.content) {
|
|
2455
|
+
for (const child of node.content) {
|
|
2456
|
+
processBlock(state, child, region);
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
break;
|
|
1281
2460
|
default:
|
|
1282
2461
|
if (node.content) {
|
|
1283
|
-
processInlineContent(state, node.content, 12);
|
|
2462
|
+
processInlineContent(state, node.content, 12, 0, region);
|
|
1284
2463
|
}
|
|
1285
2464
|
break;
|
|
1286
2465
|
}
|
|
1287
2466
|
}
|
|
2467
|
+
function renderWatermarksOnPage(page, fonts, watermarkNodes) {
|
|
2468
|
+
for (const wmNode of watermarkNodes) {
|
|
2469
|
+
const text = wmNode.attrs?.text || "";
|
|
2470
|
+
if (!text) continue;
|
|
2471
|
+
const opacity = parseFloat(wmNode.attrs?.opacity || "0.15");
|
|
2472
|
+
const angle = parseFloat(wmNode.attrs?.angle || "-45");
|
|
2473
|
+
const fontSize = 100;
|
|
2474
|
+
const { width, height } = page.getSize();
|
|
2475
|
+
const centerX = width / 2;
|
|
2476
|
+
const centerY = height / 2;
|
|
2477
|
+
const textWidth = fonts.bold.widthOfTextAtSize(text, fontSize);
|
|
2478
|
+
page.drawText(text, {
|
|
2479
|
+
x: centerX - textWidth / 2 * Math.cos(angle * Math.PI / 180),
|
|
2480
|
+
y: centerY - textWidth / 2 * Math.sin(angle * Math.PI / 180),
|
|
2481
|
+
size: fontSize,
|
|
2482
|
+
font: fonts.bold,
|
|
2483
|
+
color: rgb(0.5, 0.5, 0.5),
|
|
2484
|
+
opacity,
|
|
2485
|
+
rotate: degrees(angle)
|
|
2486
|
+
});
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
function collectWatermarkNodes(content) {
|
|
2490
|
+
const nodes = [];
|
|
2491
|
+
for (const block of content.content || []) {
|
|
2492
|
+
if (block.type === "watermark") {
|
|
2493
|
+
nodes.push(block);
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
return nodes;
|
|
2497
|
+
}
|
|
1288
2498
|
function ensureFieldSuffix(fieldName, suffix) {
|
|
1289
2499
|
const cleanName = fieldName.replace(/_signature$/i, "").replace(/_initials$/i, "").replace(/_date$/i, "");
|
|
1290
2500
|
return `${cleanName}${suffix}`;
|
|
@@ -1394,6 +2604,24 @@ async function addFormFields(pdfDoc, fieldPositions, fields) {
|
|
|
1394
2604
|
PDFString$1.of(field.label)
|
|
1395
2605
|
);
|
|
1396
2606
|
}
|
|
2607
|
+
const initialsFontSize = Math.min(
|
|
2608
|
+
72,
|
|
2609
|
+
Math.max(8, Math.round(position.height * 0.55))
|
|
2610
|
+
);
|
|
2611
|
+
try {
|
|
2612
|
+
initialsField.setFontSize(initialsFontSize);
|
|
2613
|
+
const acro = initialsField;
|
|
2614
|
+
const daRef = acro.acroField.dict.get(PDFName$1.of("DA"));
|
|
2615
|
+
if (daRef) {
|
|
2616
|
+
const daStr = daRef.value;
|
|
2617
|
+
if (typeof daStr === "string") {
|
|
2618
|
+
const newDa = daStr.replace(/\/Helv\b/, "/TiBo").replace(/\/HeBo\b/, "/TiBo");
|
|
2619
|
+
acro.acroField.dict.set(PDFName$1.of("DA"), PDFString$1.of(newDa));
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
} catch (fontError) {
|
|
2623
|
+
console.warn(`Failed to set initials font:`, fontError);
|
|
2624
|
+
}
|
|
1397
2625
|
initialsField.enableReadOnly();
|
|
1398
2626
|
if (field.required) initialsField.enableRequired();
|
|
1399
2627
|
break;
|
|
@@ -1497,47 +2725,367 @@ async function addFormFields(pdfDoc, fieldPositions, fields) {
|
|
|
1497
2725
|
} catch (err) {
|
|
1498
2726
|
warnings.push(`Failed to create ${field.type} field "${field.name}": ${getErrorMessage(err)}`);
|
|
1499
2727
|
}
|
|
1500
|
-
}
|
|
1501
|
-
initPdfMetadata({ PDFName: PDFName$1, PDFString: PDFString$1 });
|
|
1502
|
-
const metadata = buildMetadataObject(fields, actualFieldNames);
|
|
1503
|
-
setSigniphiMetadata(pdfDoc, metadata);
|
|
1504
|
-
return warnings;
|
|
1505
|
-
}
|
|
1506
|
-
async function generatePdfFromContent(content, options = {}) {
|
|
1507
|
-
const { drawFieldPlaceholders = false, embedFormFields = false, fields = [] } = options;
|
|
1508
|
-
const
|
|
1509
|
-
const
|
|
1510
|
-
const
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
2728
|
+
}
|
|
2729
|
+
initPdfMetadata({ PDFName: PDFName$1, PDFString: PDFString$1 });
|
|
2730
|
+
const metadata = buildMetadataObject(fields, actualFieldNames);
|
|
2731
|
+
setSigniphiMetadata(pdfDoc, metadata);
|
|
2732
|
+
return warnings;
|
|
2733
|
+
}
|
|
2734
|
+
async function generatePdfFromContent(content, options = {}) {
|
|
2735
|
+
const { drawFieldPlaceholders = false, embedFormFields = false, fields = [], header, footer } = options;
|
|
2736
|
+
const theme = resolveTheme(options.theme);
|
|
2737
|
+
const pdfDoc = await PDFDocument.create();
|
|
2738
|
+
const firstPage = pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
|
|
2739
|
+
const fonts = {
|
|
2740
|
+
regular: await pdfDoc.embedFont(StandardFonts.Helvetica),
|
|
2741
|
+
bold: await pdfDoc.embedFont(StandardFonts.HelveticaBold),
|
|
2742
|
+
italic: await pdfDoc.embedFont(StandardFonts.HelveticaOblique),
|
|
2743
|
+
boldItalic: await pdfDoc.embedFont(StandardFonts.HelveticaBoldOblique)
|
|
2744
|
+
};
|
|
2745
|
+
const watermarkNodes = collectWatermarkNodes(content);
|
|
2746
|
+
const { images, warnings: imageWarnings } = await preloadImages(pdfDoc, content);
|
|
2747
|
+
const state = {
|
|
2748
|
+
currentPage: firstPage,
|
|
2749
|
+
pageIndex: 0,
|
|
2750
|
+
y: 0,
|
|
2751
|
+
fonts,
|
|
2752
|
+
pdfDoc,
|
|
2753
|
+
fieldPositions: /* @__PURE__ */ new Map(),
|
|
2754
|
+
drawFieldPlaceholders,
|
|
2755
|
+
watermarkNodes,
|
|
2756
|
+
theme,
|
|
2757
|
+
images
|
|
2758
|
+
};
|
|
2759
|
+
renderWatermarksOnPage(firstPage, fonts, watermarkNodes);
|
|
2760
|
+
if (content.content) {
|
|
2761
|
+
for (const block of content.content) {
|
|
2762
|
+
processBlock(state, block);
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
const allPages = pdfDoc.getPages();
|
|
2766
|
+
const totalPages = allPages.length;
|
|
2767
|
+
const pageNumFontSize = 9;
|
|
2768
|
+
const hfFontSize = 8;
|
|
2769
|
+
const hfColor = rgb(0.45, 0.45, 0.45);
|
|
2770
|
+
for (let i = 0; i < totalPages; i++) {
|
|
2771
|
+
const page = allPages[i];
|
|
2772
|
+
const label = `Page ${i + 1} of ${totalPages}`;
|
|
2773
|
+
const labelWidth = fonts.bold.widthOfTextAtSize(label, pageNumFontSize);
|
|
2774
|
+
page.drawText(label, {
|
|
2775
|
+
x: PAGE_WIDTH - MARGIN - labelWidth,
|
|
2776
|
+
y: MARGIN / 2 - pageNumFontSize / 2,
|
|
2777
|
+
size: pageNumFontSize,
|
|
2778
|
+
font: fonts.bold,
|
|
2779
|
+
color: rgb(0, 0, 0)
|
|
2780
|
+
});
|
|
2781
|
+
if (header && (header.left || header.right) && !(header.skipFirstPage && i === 0)) {
|
|
2782
|
+
const headerBaseY = PAGE_HEIGHT - MARGIN + 10;
|
|
2783
|
+
if (header.left) {
|
|
2784
|
+
page.drawText(header.left, {
|
|
2785
|
+
x: MARGIN,
|
|
2786
|
+
y: headerBaseY,
|
|
2787
|
+
size: pageNumFontSize,
|
|
2788
|
+
font: fonts.bold,
|
|
2789
|
+
color: theme.pageHeaderText
|
|
2790
|
+
});
|
|
2791
|
+
}
|
|
2792
|
+
if (header.right) {
|
|
2793
|
+
const rightWidth = fonts.regular.widthOfTextAtSize(header.right, hfFontSize);
|
|
2794
|
+
page.drawText(header.right, {
|
|
2795
|
+
x: PAGE_WIDTH - MARGIN - rightWidth,
|
|
2796
|
+
y: headerBaseY,
|
|
2797
|
+
size: hfFontSize,
|
|
2798
|
+
font: fonts.regular,
|
|
2799
|
+
color: hfColor
|
|
2800
|
+
});
|
|
2801
|
+
}
|
|
2802
|
+
page.drawLine({
|
|
2803
|
+
start: { x: MARGIN, y: headerBaseY - 6 },
|
|
2804
|
+
end: { x: PAGE_WIDTH - MARGIN, y: headerBaseY - 6 },
|
|
2805
|
+
thickness: 0.75,
|
|
2806
|
+
color: theme.pageHeaderRule
|
|
2807
|
+
});
|
|
2808
|
+
}
|
|
2809
|
+
if (footer?.left) {
|
|
2810
|
+
page.drawText(footer.left, {
|
|
2811
|
+
x: MARGIN,
|
|
2812
|
+
y: MARGIN / 2 - pageNumFontSize / 2,
|
|
2813
|
+
size: hfFontSize,
|
|
2814
|
+
font: fonts.regular,
|
|
2815
|
+
color: hfColor
|
|
2816
|
+
});
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
let fieldWarnings;
|
|
2820
|
+
if (embedFormFields && fields.length > 0) {
|
|
2821
|
+
const warnings = await addFormFields(pdfDoc, state.fieldPositions, fields);
|
|
2822
|
+
if (warnings.length > 0) fieldWarnings = warnings;
|
|
2823
|
+
}
|
|
2824
|
+
const pdfBytes = new Uint8Array(await pdfDoc.save({ useObjectStreams: false }));
|
|
2825
|
+
return {
|
|
2826
|
+
pdfBytes,
|
|
2827
|
+
fieldPositions: state.fieldPositions,
|
|
2828
|
+
fieldWarnings,
|
|
2829
|
+
imageWarnings: imageWarnings.length > 0 ? imageWarnings : void 0
|
|
2830
|
+
};
|
|
2831
|
+
}
|
|
2832
|
+
|
|
2833
|
+
// src/utils/template-pipeline.ts
|
|
2834
|
+
async function generatePdfFromTiptap(content, values, options = {}) {
|
|
2835
|
+
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, values);
|
|
2836
|
+
const blockExpanded = expandBlockVariables(expanded, enrichedValues);
|
|
2837
|
+
const suppressed = suppressZeroContent(blockExpanded, enrichedValues);
|
|
2838
|
+
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
2839
|
+
const fields = extractFieldsFromContent(replaced);
|
|
2840
|
+
const result = await generatePdfFromContent(replaced, {
|
|
2841
|
+
embedFormFields: fields.length > 0,
|
|
2842
|
+
fields,
|
|
2843
|
+
theme: options.theme,
|
|
2844
|
+
header: options.header,
|
|
2845
|
+
footer: options.footer,
|
|
2846
|
+
drawFieldPlaceholders: options.drawFieldPlaceholders
|
|
2847
|
+
});
|
|
2848
|
+
return { pdfBytes: result.pdfBytes, imageWarnings: result.imageWarnings };
|
|
2849
|
+
}
|
|
2850
|
+
async function generatePdfFromMarkdown(markdown, values, options = {}) {
|
|
2851
|
+
const content = markdownToTiptap(markdown);
|
|
2852
|
+
return generatePdfFromTiptap(content, values, options);
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
// src/utils/markdown-writer.ts
|
|
2856
|
+
function serializeFieldToken(attrs) {
|
|
2857
|
+
const parts = ["field"];
|
|
2858
|
+
if (attrs.fieldType) parts.push(`type:${attrs.fieldType}`);
|
|
2859
|
+
if (attrs.fieldName) parts.push(`name:${attrs.fieldName}`);
|
|
2860
|
+
if (attrs.fieldLabel) parts.push(`label:${attrs.fieldLabel}`);
|
|
2861
|
+
if (attrs.fieldId) parts.push(`id:${attrs.fieldId}`);
|
|
2862
|
+
if (attrs.required) parts.push(`required:true`);
|
|
2863
|
+
if (attrs.options) parts.push(`options:${attrs.options}`);
|
|
2864
|
+
if (attrs.fontSize) parts.push(`fontSize:${attrs.fontSize}`);
|
|
2865
|
+
if (attrs.placeholder) parts.push(`placeholder:${attrs.placeholder}`);
|
|
2866
|
+
if (attrs.defaultValue) parts.push(`defaultValue:${attrs.defaultValue}`);
|
|
2867
|
+
if (attrs.multiline) parts.push(`multiline:true`);
|
|
2868
|
+
if (attrs.maxLength) parts.push(`maxLength:${attrs.maxLength}`);
|
|
2869
|
+
if (attrs.acknowledgements) parts.push(`acks:${btoa(attrs.acknowledgements)}`);
|
|
2870
|
+
return `{{${parts.join("|")}}}`;
|
|
2871
|
+
}
|
|
2872
|
+
function serializeImageNode(attrs) {
|
|
2873
|
+
const hasExtras = attrs.varName || attrs.width || attrs.height || attrs.align;
|
|
2874
|
+
if (!hasExtras && attrs.src) {
|
|
2875
|
+
return ``;
|
|
2876
|
+
}
|
|
2877
|
+
const parts = ["image"];
|
|
2878
|
+
if (attrs.varName) parts.push(`var:${attrs.varName}`);
|
|
2879
|
+
else if (attrs.src) parts.push(`src:${attrs.src}`);
|
|
2880
|
+
if (attrs.alt) parts.push(`alt:${attrs.alt}`);
|
|
2881
|
+
if (attrs.width) parts.push(`width:${attrs.width}`);
|
|
2882
|
+
if (attrs.height) parts.push(`height:${attrs.height}`);
|
|
2883
|
+
if (attrs.align) parts.push(`align:${attrs.align}`);
|
|
2884
|
+
return `{{${parts.join("|")}}}`;
|
|
2885
|
+
}
|
|
2886
|
+
function serializeVariableToken(attrs) {
|
|
2887
|
+
const parts = ["var"];
|
|
2888
|
+
if (attrs.varName) parts.push(`name:${attrs.varName}`);
|
|
2889
|
+
if (attrs.varLabel) parts.push(`label:${attrs.varLabel}`);
|
|
2890
|
+
if (attrs.varDefault) parts.push(`default:${attrs.varDefault}`);
|
|
2891
|
+
if (attrs.suppressZero === "true") parts.push(`suppress:zero`);
|
|
2892
|
+
if (attrs.format) parts.push(`format:${attrs.format}`);
|
|
2893
|
+
if (attrs.block === "true") parts.push(`block:true`);
|
|
2894
|
+
return `{{${parts.join("|")}}}`;
|
|
2895
|
+
}
|
|
2896
|
+
function serializeInline(content) {
|
|
2897
|
+
if (!content) return "";
|
|
2898
|
+
let result = "";
|
|
2899
|
+
for (const node of content) {
|
|
2900
|
+
if (node.type === "fieldNode") {
|
|
2901
|
+
result += serializeFieldToken(node.attrs || {});
|
|
2902
|
+
continue;
|
|
2903
|
+
}
|
|
2904
|
+
if (node.type === "imageNode") {
|
|
2905
|
+
result += serializeImageNode(node.attrs || {});
|
|
2906
|
+
continue;
|
|
2907
|
+
}
|
|
2908
|
+
if (node.type === "variableNode") {
|
|
2909
|
+
let token = serializeVariableToken(node.attrs || {});
|
|
2910
|
+
const varMarks = node.marks || [];
|
|
2911
|
+
const isBold = varMarks.some((m) => m.type === "bold");
|
|
2912
|
+
const isItalic = varMarks.some((m) => m.type === "italic");
|
|
2913
|
+
const isUnderline = varMarks.some((m) => m.type === "underline");
|
|
2914
|
+
if (isBold && isItalic) token = `***${token}***`;
|
|
2915
|
+
else if (isBold) token = `**${token}**`;
|
|
2916
|
+
else if (isItalic) token = `*${token}*`;
|
|
2917
|
+
if (isUnderline) token = `__${token}__`;
|
|
2918
|
+
result += token;
|
|
2919
|
+
continue;
|
|
2920
|
+
}
|
|
2921
|
+
if (node.type === "text") {
|
|
2922
|
+
let text = node.text || "";
|
|
2923
|
+
const marks = node.marks || [];
|
|
2924
|
+
for (const mark of marks) {
|
|
2925
|
+
if (mark.type === "bold") text = `**${text}**`;
|
|
2926
|
+
else if (mark.type === "italic") text = `*${text}*`;
|
|
2927
|
+
else if (mark.type === "underline") text = `__${text}__`;
|
|
2928
|
+
else if (mark.type === "code") text = `\`${text}\``;
|
|
2929
|
+
}
|
|
2930
|
+
result += text;
|
|
2931
|
+
}
|
|
2932
|
+
if (node.type === "hardBreak") {
|
|
2933
|
+
result += " \n";
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
return result;
|
|
2937
|
+
}
|
|
2938
|
+
function serializeBlock(node) {
|
|
2939
|
+
switch (node.type) {
|
|
2940
|
+
case "heading": {
|
|
2941
|
+
const level = node.attrs?.level || 1;
|
|
2942
|
+
const prefix = "#".repeat(level);
|
|
2943
|
+
return `${prefix} ${serializeInline(node.content)}`;
|
|
2944
|
+
}
|
|
2945
|
+
case "paragraph": {
|
|
2946
|
+
const inline = serializeInline(node.content);
|
|
2947
|
+
return inline;
|
|
2948
|
+
}
|
|
2949
|
+
case "bulletList": {
|
|
2950
|
+
return (node.content || []).map((item) => {
|
|
2951
|
+
const inner = (item.content || []).map(serializeBlock).join("\n");
|
|
2952
|
+
return `- ${inner}`;
|
|
2953
|
+
}).join("\n");
|
|
2954
|
+
}
|
|
2955
|
+
case "orderedList": {
|
|
2956
|
+
return (node.content || []).map((item, i) => {
|
|
2957
|
+
const inner = (item.content || []).map(serializeBlock).join("\n");
|
|
2958
|
+
return `${i + 1}. ${inner}`;
|
|
2959
|
+
}).join("\n");
|
|
2960
|
+
}
|
|
2961
|
+
case "listItem": {
|
|
2962
|
+
return (node.content || []).map(serializeBlock).join("\n");
|
|
2963
|
+
}
|
|
2964
|
+
case "blockquote": {
|
|
2965
|
+
return (node.content || []).map(serializeBlock).map((line) => `> ${line}`).join("\n");
|
|
2966
|
+
}
|
|
2967
|
+
case "codeBlock": {
|
|
2968
|
+
const text = serializeInline(node.content);
|
|
2969
|
+
return `\`\`\`
|
|
2970
|
+
${text}
|
|
2971
|
+
\`\`\``;
|
|
2972
|
+
}
|
|
2973
|
+
case "horizontalRule": {
|
|
2974
|
+
return "---";
|
|
2975
|
+
}
|
|
2976
|
+
case "table": {
|
|
2977
|
+
const rows = node.content || [];
|
|
2978
|
+
const isBorderless = node.attrs?.borderless === true;
|
|
2979
|
+
const isSubtotals = node.attrs?.subtotals === true;
|
|
2980
|
+
const serializedRows = [];
|
|
2981
|
+
if (isBorderless && rows.length > 0) {
|
|
2982
|
+
const colCount = rows[0].content?.length || 0;
|
|
2983
|
+
const markerCell = isSubtotals ? "_" : "";
|
|
2984
|
+
const markerRow = "| " + new Array(colCount).fill(markerCell).join(" | ") + " |";
|
|
2985
|
+
const sep = "|" + new Array(colCount).fill("---").join("|") + "|";
|
|
2986
|
+
serializedRows.push(markerRow);
|
|
2987
|
+
serializedRows.push(sep);
|
|
2988
|
+
}
|
|
2989
|
+
for (let ri = 0; ri < rows.length; ri++) {
|
|
2990
|
+
const row = rows[ri];
|
|
2991
|
+
const cells = (row.content || []).map((cell) => {
|
|
2992
|
+
const inner = (cell.content || []).map(serializeBlock).join("");
|
|
2993
|
+
return inner;
|
|
2994
|
+
});
|
|
2995
|
+
serializedRows.push(`| ${cells.join(" | ")} |`);
|
|
2996
|
+
if (!isBorderless && ri === 0 && row.content?.[0]?.type === "tableHeader") {
|
|
2997
|
+
const sep = cells.map(() => "------").join("|");
|
|
2998
|
+
serializedRows.push(`|${sep}|`);
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
return serializedRows.join("\n");
|
|
3002
|
+
}
|
|
3003
|
+
case "tableRow": {
|
|
3004
|
+
const cells = (node.content || []).map((cell) => {
|
|
3005
|
+
const inner = (cell.content || []).map(serializeBlock).join("");
|
|
3006
|
+
return inner;
|
|
3007
|
+
});
|
|
3008
|
+
return `| ${cells.join(" | ")} |`;
|
|
3009
|
+
}
|
|
3010
|
+
case "tableHeader":
|
|
3011
|
+
case "tableCell": {
|
|
3012
|
+
return (node.content || []).map(serializeBlock).join("");
|
|
3013
|
+
}
|
|
3014
|
+
case "imageNode": {
|
|
3015
|
+
return serializeImageNode(node.attrs || {});
|
|
3016
|
+
}
|
|
3017
|
+
case "watermark": {
|
|
3018
|
+
const attrs = node.attrs || {};
|
|
3019
|
+
const parts = [];
|
|
3020
|
+
if (attrs.text) parts.push(`text:${attrs.text}`);
|
|
3021
|
+
if (attrs.opacity && attrs.opacity !== "0.15") parts.push(`opacity:${attrs.opacity}`);
|
|
3022
|
+
if (attrs.angle && attrs.angle !== "-45") parts.push(`angle:${attrs.angle}`);
|
|
3023
|
+
return `:::watermark{${parts.join("|")}}`;
|
|
3024
|
+
}
|
|
3025
|
+
case "panel": {
|
|
3026
|
+
const attrs = node.attrs || {};
|
|
3027
|
+
const parts = [];
|
|
3028
|
+
if (attrs.title) parts.push(`title:${attrs.title}`);
|
|
3029
|
+
if (attrs.border && attrs.border !== "solid") parts.push(`border:${attrs.border}`);
|
|
3030
|
+
else if (attrs.border === "solid") parts.push(`border:solid`);
|
|
3031
|
+
if (attrs.headerStyle) parts.push(`headerStyle:${attrs.headerStyle}`);
|
|
3032
|
+
const attrStr = parts.length > 0 ? `{${parts.join("|")}}` : "";
|
|
3033
|
+
const children = (node.content || []).map(serializeBlock).join("\n\n");
|
|
3034
|
+
return `:::panel${attrStr}
|
|
3035
|
+
${children}
|
|
3036
|
+
:::`;
|
|
3037
|
+
}
|
|
3038
|
+
case "repeatBlock": {
|
|
3039
|
+
const attrs = node.attrs || {};
|
|
3040
|
+
const parts = [];
|
|
3041
|
+
if (attrs.data) parts.push(`data:${attrs.data}`);
|
|
3042
|
+
const attrStr = parts.length > 0 ? `{${parts.join("|")}}` : "";
|
|
3043
|
+
const children = (node.content || []).map(serializeBlock).join("\n\n");
|
|
3044
|
+
return `:::repeat${attrStr}
|
|
3045
|
+
${children}
|
|
3046
|
+
:::`;
|
|
3047
|
+
}
|
|
3048
|
+
case "subtotalsBlock": {
|
|
3049
|
+
const children = (node.content || []).map(serializeBlock).join("\n");
|
|
3050
|
+
return `:::subtotals
|
|
3051
|
+
${children}
|
|
3052
|
+
:::`;
|
|
3053
|
+
}
|
|
3054
|
+
case "columns": {
|
|
3055
|
+
const attrs = node.attrs || {};
|
|
3056
|
+
const splitVal = attrs.split || "50";
|
|
3057
|
+
const cols = (node.content || []).filter((c) => c.type === "column");
|
|
3058
|
+
const colBlocks = cols.map((col) => {
|
|
3059
|
+
const children = (col.content || []).map(serializeBlock).join("\n\n");
|
|
3060
|
+
const colAttrs = col.attrs || {};
|
|
3061
|
+
const padTop = parseFloat(colAttrs.padTop || "0") || 0;
|
|
3062
|
+
const colTag = padTop ? `:::col{padTop:${padTop}}` : ":::col";
|
|
3063
|
+
return `${colTag}
|
|
3064
|
+
${children}
|
|
3065
|
+
:::`;
|
|
3066
|
+
});
|
|
3067
|
+
const padX = parseFloat(attrs.padX || "0") || 0;
|
|
3068
|
+
const colsAttrs = padX ? `split:${splitVal}|padX:${padX}` : `split:${splitVal}`;
|
|
3069
|
+
return `:::columns{${colsAttrs}}
|
|
3070
|
+
${colBlocks.join("\n")}
|
|
3071
|
+
:::`;
|
|
3072
|
+
}
|
|
3073
|
+
case "column": {
|
|
3074
|
+
const children = (node.content || []).map(serializeBlock).join("\n\n");
|
|
3075
|
+
const colAttrs = node.attrs || {};
|
|
3076
|
+
const padTop = parseFloat(colAttrs.padTop || "0") || 0;
|
|
3077
|
+
const colTag = padTop ? `:::col{padTop:${padTop}}` : ":::col";
|
|
3078
|
+
return `${colTag}
|
|
3079
|
+
${children}
|
|
3080
|
+
:::`;
|
|
1528
3081
|
}
|
|
3082
|
+
default:
|
|
3083
|
+
return serializeInline(node.content);
|
|
1529
3084
|
}
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
}
|
|
1535
|
-
const pdfBytes = await pdfDoc.save({ useObjectStreams: false });
|
|
1536
|
-
return {
|
|
1537
|
-
pdfBytes,
|
|
1538
|
-
fieldPositions: state.fieldPositions,
|
|
1539
|
-
fieldWarnings
|
|
1540
|
-
};
|
|
3085
|
+
}
|
|
3086
|
+
function tiptapToMarkdown(doc) {
|
|
3087
|
+
if (!doc.content) return "";
|
|
3088
|
+
return doc.content.map(serializeBlock).join("\n\n");
|
|
1541
3089
|
}
|
|
1542
3090
|
|
|
1543
3091
|
// src/utils/pdf-preview.ts
|
|
@@ -1626,8 +3174,26 @@ function useDocumentGenerator(options = {}) {
|
|
|
1626
3174
|
Placeholder.configure({
|
|
1627
3175
|
placeholder: options.placeholder || "Start writing your document..."
|
|
1628
3176
|
}),
|
|
3177
|
+
Table.extend({
|
|
3178
|
+
addAttributes() {
|
|
3179
|
+
return {
|
|
3180
|
+
...this.parent?.(),
|
|
3181
|
+
borderless: { default: false },
|
|
3182
|
+
subtotals: { default: false }
|
|
3183
|
+
};
|
|
3184
|
+
}
|
|
3185
|
+
}).configure({ resizable: false }),
|
|
3186
|
+
TableRow,
|
|
3187
|
+
TableCell,
|
|
3188
|
+
TableHeader,
|
|
1629
3189
|
FieldNode,
|
|
1630
|
-
VariableNode
|
|
3190
|
+
VariableNode,
|
|
3191
|
+
PanelNode,
|
|
3192
|
+
ColumnsNode,
|
|
3193
|
+
ColumnNode,
|
|
3194
|
+
WatermarkNode,
|
|
3195
|
+
RepeatNode,
|
|
3196
|
+
SubtotalsNode
|
|
1631
3197
|
],
|
|
1632
3198
|
content: initialContent || { type: "doc", content: [{ type: "paragraph" }] },
|
|
1633
3199
|
onUpdate: ({ editor: ed }) => {
|
|
@@ -1731,10 +3297,9 @@ function useDocumentGenerator(options = {}) {
|
|
|
1731
3297
|
async (values) => {
|
|
1732
3298
|
if (!editor) throw new Error("Editor is not initialized yet \u2014 wait for the editor to load before generating");
|
|
1733
3299
|
const content = editor.getJSON();
|
|
1734
|
-
const
|
|
1735
|
-
const
|
|
1736
|
-
|
|
1737
|
-
return { pdfBytes: result.pdfBytes, pdfPages: pages };
|
|
3300
|
+
const { pdfBytes: pdfBytes2 } = await generatePdfFromTiptap(content, values);
|
|
3301
|
+
const pdfPages2 = await pdfToImages(pdfBytes2);
|
|
3302
|
+
return { pdfBytes: pdfBytes2, pdfPages: pdfPages2 };
|
|
1738
3303
|
},
|
|
1739
3304
|
[editor]
|
|
1740
3305
|
);
|
|
@@ -2888,15 +4453,47 @@ function FieldEditPopover({
|
|
|
2888
4453
|
] }) })
|
|
2889
4454
|
] });
|
|
2890
4455
|
}
|
|
2891
|
-
|
|
4456
|
+
var sizeClasses = {
|
|
4457
|
+
md: "px-3 py-1.5 text-xs font-medium",
|
|
4458
|
+
sm: "px-2 py-0.5 text-[10px] font-medium"
|
|
4459
|
+
};
|
|
4460
|
+
function ToggleGroup({
|
|
4461
|
+
value,
|
|
4462
|
+
onChange,
|
|
4463
|
+
options,
|
|
4464
|
+
size = "md",
|
|
4465
|
+
className
|
|
4466
|
+
}) {
|
|
4467
|
+
return /* @__PURE__ */ jsx("div", { className: cn("inline-flex items-center gap-1 bg-muted/50 p-0.5 rounded-lg", className), children: options.map((opt) => /* @__PURE__ */ jsx(
|
|
4468
|
+
"button",
|
|
4469
|
+
{
|
|
4470
|
+
className: cn(
|
|
4471
|
+
"rounded-md transition-colors",
|
|
4472
|
+
sizeClasses[size],
|
|
4473
|
+
value === opt.value ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
|
|
4474
|
+
),
|
|
4475
|
+
onClick: () => onChange(opt.value),
|
|
4476
|
+
children: opt.label
|
|
4477
|
+
},
|
|
4478
|
+
opt.value
|
|
4479
|
+
)) });
|
|
4480
|
+
}
|
|
4481
|
+
function labelToVarName2(label) {
|
|
2892
4482
|
return label.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
2893
4483
|
}
|
|
4484
|
+
function varNameToLabel(name) {
|
|
4485
|
+
return name.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
4486
|
+
}
|
|
2894
4487
|
function VariableInsertPopover({
|
|
2895
4488
|
open,
|
|
2896
4489
|
onOpenChange,
|
|
2897
4490
|
onInsert,
|
|
4491
|
+
predefinedVariables,
|
|
2898
4492
|
children
|
|
2899
4493
|
}) {
|
|
4494
|
+
const hasPredefined = !!predefinedVariables?.length;
|
|
4495
|
+
const [mode, setMode] = useState(hasPredefined ? "existing" : "new");
|
|
4496
|
+
const [search, setSearch] = useState("");
|
|
2900
4497
|
const [varLabel, setVarLabel] = useState("");
|
|
2901
4498
|
const [varName, setVarName] = useState("");
|
|
2902
4499
|
const [varDefault, setVarDefault] = useState("");
|
|
@@ -2906,7 +4503,9 @@ function VariableInsertPopover({
|
|
|
2906
4503
|
setVarName("");
|
|
2907
4504
|
setVarDefault("");
|
|
2908
4505
|
setNameManuallyEdited(false);
|
|
2909
|
-
|
|
4506
|
+
setSearch("");
|
|
4507
|
+
setMode(hasPredefined ? "existing" : "new");
|
|
4508
|
+
}, [hasPredefined]);
|
|
2910
4509
|
const handleOpenChange = useCallback(
|
|
2911
4510
|
(nextOpen) => {
|
|
2912
4511
|
if (!nextOpen) reset();
|
|
@@ -2916,7 +4515,7 @@ function VariableInsertPopover({
|
|
|
2916
4515
|
);
|
|
2917
4516
|
useEffect(() => {
|
|
2918
4517
|
if (!nameManuallyEdited) {
|
|
2919
|
-
setVarName(
|
|
4518
|
+
setVarName(labelToVarName2(varLabel));
|
|
2920
4519
|
}
|
|
2921
4520
|
}, [varLabel, nameManuallyEdited]);
|
|
2922
4521
|
const handleInsert = useCallback(() => {
|
|
@@ -2924,10 +4523,32 @@ function VariableInsertPopover({
|
|
|
2924
4523
|
onInsert({
|
|
2925
4524
|
varName: varName.trim(),
|
|
2926
4525
|
varLabel: varLabel.trim(),
|
|
2927
|
-
varDefault: varDefault.trim()
|
|
4526
|
+
varDefault: varDefault.trim(),
|
|
4527
|
+
format: ""
|
|
2928
4528
|
});
|
|
2929
4529
|
handleOpenChange(false);
|
|
2930
4530
|
}, [varLabel, varName, varDefault, onInsert, handleOpenChange]);
|
|
4531
|
+
const handlePickPredefined = useCallback(
|
|
4532
|
+
(pv) => {
|
|
4533
|
+
onInsert({
|
|
4534
|
+
varName: pv.varName,
|
|
4535
|
+
varLabel: pv.varLabel || varNameToLabel(pv.varName),
|
|
4536
|
+
varDefault: "",
|
|
4537
|
+
format: ""
|
|
4538
|
+
});
|
|
4539
|
+
handleOpenChange(false);
|
|
4540
|
+
},
|
|
4541
|
+
[onInsert, handleOpenChange]
|
|
4542
|
+
);
|
|
4543
|
+
const filteredVars = useMemo(() => {
|
|
4544
|
+
if (!predefinedVariables?.length) return [];
|
|
4545
|
+
const q = search.toLowerCase();
|
|
4546
|
+
if (!q) return predefinedVariables;
|
|
4547
|
+
return predefinedVariables.filter((pv) => {
|
|
4548
|
+
const label = (pv.varLabel || varNameToLabel(pv.varName)).toLowerCase();
|
|
4549
|
+
return pv.varName.toLowerCase().includes(q) || label.includes(q);
|
|
4550
|
+
});
|
|
4551
|
+
}, [predefinedVariables, search]);
|
|
2931
4552
|
return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: handleOpenChange, children: [
|
|
2932
4553
|
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children }),
|
|
2933
4554
|
/* @__PURE__ */ jsx(PopoverContent, { className: "w-72 p-0", align: "start", sideOffset: 8, children: /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-3", children: [
|
|
@@ -2935,72 +4556,114 @@ function VariableInsertPopover({
|
|
|
2935
4556
|
/* @__PURE__ */ jsx(Braces, { size: 14 }),
|
|
2936
4557
|
/* @__PURE__ */ jsx("span", { children: "Insert Variable" })
|
|
2937
4558
|
] }),
|
|
2938
|
-
/* @__PURE__ */
|
|
2939
|
-
|
|
2940
|
-
|
|
4559
|
+
hasPredefined && /* @__PURE__ */ jsx(
|
|
4560
|
+
ToggleGroup,
|
|
4561
|
+
{
|
|
4562
|
+
value: mode,
|
|
4563
|
+
onChange: setMode,
|
|
4564
|
+
options: [
|
|
4565
|
+
{ value: "existing", label: "Existing" },
|
|
4566
|
+
{ value: "new", label: "New" }
|
|
4567
|
+
],
|
|
4568
|
+
size: "sm"
|
|
4569
|
+
}
|
|
4570
|
+
),
|
|
4571
|
+
mode === "existing" && hasPredefined ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
4572
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
4573
|
+
/* @__PURE__ */ jsx(Search, { size: 14, className: "absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground" }),
|
|
2941
4574
|
/* @__PURE__ */ jsx(
|
|
2942
4575
|
Input,
|
|
2943
4576
|
{
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
className: "h-8 text-xs"
|
|
4577
|
+
value: search,
|
|
4578
|
+
onChange: (e) => setSearch(e.target.value),
|
|
4579
|
+
placeholder: "Search variables...",
|
|
4580
|
+
className: "h-8 text-xs pl-7"
|
|
2949
4581
|
}
|
|
2950
4582
|
)
|
|
2951
4583
|
] }),
|
|
2952
|
-
/* @__PURE__ */
|
|
2953
|
-
|
|
4584
|
+
/* @__PURE__ */ jsx("div", { className: "max-h-48 overflow-y-auto -mx-1", children: filteredVars.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground px-2 py-3 text-center", children: "No variables found" }) : filteredVars.map((pv) => {
|
|
4585
|
+
const label = pv.varLabel || varNameToLabel(pv.varName);
|
|
4586
|
+
return /* @__PURE__ */ jsxs(
|
|
4587
|
+
"button",
|
|
4588
|
+
{
|
|
4589
|
+
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",
|
|
4590
|
+
onClick: () => handlePickPredefined(pv),
|
|
4591
|
+
children: [
|
|
4592
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: label }),
|
|
4593
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground font-mono", children: pv.varName })
|
|
4594
|
+
]
|
|
4595
|
+
},
|
|
4596
|
+
pv.varName
|
|
4597
|
+
);
|
|
4598
|
+
}) })
|
|
4599
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4600
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
4601
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
4602
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "var-label", className: "text-xs", children: "Label" }),
|
|
4603
|
+
/* @__PURE__ */ jsx(
|
|
4604
|
+
Input,
|
|
4605
|
+
{
|
|
4606
|
+
id: "var-label",
|
|
4607
|
+
value: varLabel,
|
|
4608
|
+
onChange: (e) => setVarLabel(e.target.value),
|
|
4609
|
+
placeholder: "e.g. Company Name",
|
|
4610
|
+
className: "h-8 text-xs"
|
|
4611
|
+
}
|
|
4612
|
+
)
|
|
4613
|
+
] }),
|
|
4614
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
4615
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "var-name", className: "text-xs", children: "Name (identifier)" }),
|
|
4616
|
+
/* @__PURE__ */ jsx(
|
|
4617
|
+
Input,
|
|
4618
|
+
{
|
|
4619
|
+
id: "var-name",
|
|
4620
|
+
value: varName,
|
|
4621
|
+
onChange: (e) => {
|
|
4622
|
+
setVarName(e.target.value);
|
|
4623
|
+
setNameManuallyEdited(true);
|
|
4624
|
+
},
|
|
4625
|
+
placeholder: "e.g. company_name",
|
|
4626
|
+
className: "h-8 text-xs font-mono"
|
|
4627
|
+
}
|
|
4628
|
+
),
|
|
4629
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground mt-0.5", children: "Used as key in data objects" })
|
|
4630
|
+
] }),
|
|
4631
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
4632
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "var-default", className: "text-xs", children: "Default Value" }),
|
|
4633
|
+
/* @__PURE__ */ jsx(
|
|
4634
|
+
Input,
|
|
4635
|
+
{
|
|
4636
|
+
id: "var-default",
|
|
4637
|
+
value: varDefault,
|
|
4638
|
+
onChange: (e) => setVarDefault(e.target.value),
|
|
4639
|
+
placeholder: "Optional",
|
|
4640
|
+
className: "h-8 text-xs"
|
|
4641
|
+
}
|
|
4642
|
+
)
|
|
4643
|
+
] })
|
|
4644
|
+
] }),
|
|
4645
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2 pt-1", children: [
|
|
2954
4646
|
/* @__PURE__ */ jsx(
|
|
2955
|
-
|
|
4647
|
+
Button,
|
|
2956
4648
|
{
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
},
|
|
2963
|
-
placeholder: "e.g. company_name",
|
|
2964
|
-
className: "h-8 text-xs font-mono"
|
|
4649
|
+
size: "sm",
|
|
4650
|
+
className: "h-8 flex-1 text-xs",
|
|
4651
|
+
onClick: handleInsert,
|
|
4652
|
+
disabled: !varLabel.trim() || !varName.trim(),
|
|
4653
|
+
children: "Insert"
|
|
2965
4654
|
}
|
|
2966
4655
|
),
|
|
2967
|
-
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground mt-0.5", children: "Used as key in data objects" })
|
|
2968
|
-
] }),
|
|
2969
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
2970
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "var-default", className: "text-xs", children: "Default Value" }),
|
|
2971
4656
|
/* @__PURE__ */ jsx(
|
|
2972
|
-
|
|
4657
|
+
Button,
|
|
2973
4658
|
{
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
4659
|
+
variant: "outline",
|
|
4660
|
+
size: "sm",
|
|
4661
|
+
className: "h-8 text-xs",
|
|
4662
|
+
onClick: () => handleOpenChange(false),
|
|
4663
|
+
children: "Cancel"
|
|
2979
4664
|
}
|
|
2980
4665
|
)
|
|
2981
4666
|
] })
|
|
2982
|
-
] }),
|
|
2983
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-2 pt-1", children: [
|
|
2984
|
-
/* @__PURE__ */ jsx(
|
|
2985
|
-
Button,
|
|
2986
|
-
{
|
|
2987
|
-
size: "sm",
|
|
2988
|
-
className: "h-8 flex-1 text-xs",
|
|
2989
|
-
onClick: handleInsert,
|
|
2990
|
-
disabled: !varLabel.trim() || !varName.trim(),
|
|
2991
|
-
children: "Insert"
|
|
2992
|
-
}
|
|
2993
|
-
),
|
|
2994
|
-
/* @__PURE__ */ jsx(
|
|
2995
|
-
Button,
|
|
2996
|
-
{
|
|
2997
|
-
variant: "outline",
|
|
2998
|
-
size: "sm",
|
|
2999
|
-
className: "h-8 text-xs",
|
|
3000
|
-
onClick: () => handleOpenChange(false),
|
|
3001
|
-
children: "Cancel"
|
|
3002
|
-
}
|
|
3003
|
-
)
|
|
3004
4667
|
] })
|
|
3005
4668
|
] }) })
|
|
3006
4669
|
] });
|
|
@@ -3222,7 +4885,6 @@ function PreviewPanel({
|
|
|
3222
4885
|
(f) => f.position.page === currentPageIndex + 1 && f.position.width > 0
|
|
3223
4886
|
);
|
|
3224
4887
|
}, [positionedFields, currentPage, currentPageIndex]);
|
|
3225
|
-
const showScrollbars = zoomLevel > 1;
|
|
3226
4888
|
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col h-full border border-border rounded-lg overflow-hidden bg-muted/20", className), children: [
|
|
3227
4889
|
/* @__PURE__ */ jsx("div", { className: "border-b border-border px-2 py-1.5 flex-shrink-0", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [
|
|
3228
4890
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
@@ -3338,12 +5000,13 @@ function PreviewPanel({
|
|
|
3338
5000
|
"div",
|
|
3339
5001
|
{
|
|
3340
5002
|
className: cn(
|
|
3341
|
-
"border border-border rounded-lg bg-muted/30 shrink-0"
|
|
3342
|
-
showScrollbars ? "overflow-auto" : "overflow-hidden"
|
|
5003
|
+
"border border-border rounded-lg bg-muted/30 shrink-0 overflow-auto scrollbar-hidden"
|
|
3343
5004
|
),
|
|
3344
5005
|
style: {
|
|
3345
5006
|
width: pageDisplaySize.viewportWidth,
|
|
3346
|
-
height: pageDisplaySize.viewportHeight
|
|
5007
|
+
height: pageDisplaySize.viewportHeight,
|
|
5008
|
+
maxWidth: "100%",
|
|
5009
|
+
maxHeight: "100%"
|
|
3347
5010
|
},
|
|
3348
5011
|
children: /* @__PURE__ */ jsxs(
|
|
3349
5012
|
"div",
|
|
@@ -3414,8 +5077,8 @@ function validateMarkdown(markdown) {
|
|
|
3414
5077
|
errors.push(`Malformed token at line ${lineNum}: missing | delimiter`);
|
|
3415
5078
|
} else {
|
|
3416
5079
|
const tokenType = tokenContent.slice(0, pipeIdx);
|
|
3417
|
-
if (tokenType !== "field" && tokenType !== "var") {
|
|
3418
|
-
errors.push(`Unknown token type "${tokenType}" at line ${lineNum} (expected "field" or "
|
|
5080
|
+
if (tokenType !== "field" && tokenType !== "var" && tokenType !== "image") {
|
|
5081
|
+
errors.push(`Unknown token type "${tokenType}" at line ${lineNum} (expected "field", "var" or "image")`);
|
|
3419
5082
|
} else {
|
|
3420
5083
|
const body = tokenContent.slice(pipeIdx + 1);
|
|
3421
5084
|
const pairs = body.split("|");
|
|
@@ -3429,12 +5092,48 @@ function validateMarkdown(markdown) {
|
|
|
3429
5092
|
if (!hasName) {
|
|
3430
5093
|
errors.push(`Variable token at line ${lineNum} is missing required "name" attribute`);
|
|
3431
5094
|
}
|
|
5095
|
+
} else if (tokenType === "image") {
|
|
5096
|
+
const hasSource = pairs.some((p) => p.startsWith("src:") || p.startsWith("var:"));
|
|
5097
|
+
if (!hasSource) {
|
|
5098
|
+
errors.push(`Image token at line ${lineNum} needs a "src" or "var" attribute`);
|
|
5099
|
+
}
|
|
3432
5100
|
}
|
|
3433
5101
|
}
|
|
3434
5102
|
}
|
|
3435
5103
|
searchFrom = closeIdx + 2;
|
|
3436
5104
|
}
|
|
3437
5105
|
}
|
|
5106
|
+
const DIRECTIVE_OPEN_RE = /^:::(panel|columns|col|watermark)(?:\{[^}]*\})?$/;
|
|
5107
|
+
const DIRECTIVE_CLOSE_RE = /^:::$/;
|
|
5108
|
+
const directiveStack = [];
|
|
5109
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5110
|
+
const trimmed = lines[i].trim();
|
|
5111
|
+
const lineNum = i + 1;
|
|
5112
|
+
const openMatch = trimmed.match(DIRECTIVE_OPEN_RE);
|
|
5113
|
+
if (openMatch) {
|
|
5114
|
+
const dtype = openMatch[1];
|
|
5115
|
+
if (dtype !== "watermark") {
|
|
5116
|
+
if (dtype === "col") {
|
|
5117
|
+
const parent = directiveStack[directiveStack.length - 1];
|
|
5118
|
+
if (!parent || parent.type !== "columns") {
|
|
5119
|
+
warnings.push(`:::col at line ${lineNum} appears outside :::columns`);
|
|
5120
|
+
}
|
|
5121
|
+
}
|
|
5122
|
+
directiveStack.push({ type: dtype, line: lineNum });
|
|
5123
|
+
}
|
|
5124
|
+
continue;
|
|
5125
|
+
}
|
|
5126
|
+
if (DIRECTIVE_CLOSE_RE.test(trimmed)) {
|
|
5127
|
+
if (directiveStack.length === 0) {
|
|
5128
|
+
warnings.push(`Stray ::: close at line ${lineNum} with no matching open directive`);
|
|
5129
|
+
} else {
|
|
5130
|
+
directiveStack.pop();
|
|
5131
|
+
}
|
|
5132
|
+
}
|
|
5133
|
+
}
|
|
5134
|
+
for (const open of directiveStack) {
|
|
5135
|
+
errors.push(`Unclosed :::${open.type} directive opened at line ${open.line}`);
|
|
5136
|
+
}
|
|
3438
5137
|
let fields = [];
|
|
3439
5138
|
let variables = [];
|
|
3440
5139
|
try {
|
|
@@ -3468,31 +5167,6 @@ function validateMarkdown(markdown) {
|
|
|
3468
5167
|
fields
|
|
3469
5168
|
};
|
|
3470
5169
|
}
|
|
3471
|
-
var sizeClasses = {
|
|
3472
|
-
md: "px-3 py-1.5 text-xs font-medium",
|
|
3473
|
-
sm: "px-2 py-0.5 text-[10px] font-medium"
|
|
3474
|
-
};
|
|
3475
|
-
function ToggleGroup({
|
|
3476
|
-
value,
|
|
3477
|
-
onChange,
|
|
3478
|
-
options,
|
|
3479
|
-
size = "md",
|
|
3480
|
-
className
|
|
3481
|
-
}) {
|
|
3482
|
-
return /* @__PURE__ */ jsx("div", { className: cn("inline-flex items-center gap-1 bg-muted/50 p-0.5 rounded-lg", className), children: options.map((opt) => /* @__PURE__ */ jsx(
|
|
3483
|
-
"button",
|
|
3484
|
-
{
|
|
3485
|
-
className: cn(
|
|
3486
|
-
"rounded-md transition-colors",
|
|
3487
|
-
sizeClasses[size],
|
|
3488
|
-
value === opt.value ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
|
|
3489
|
-
),
|
|
3490
|
-
onClick: () => onChange(opt.value),
|
|
3491
|
-
children: opt.label
|
|
3492
|
-
},
|
|
3493
|
-
opt.value
|
|
3494
|
-
)) });
|
|
3495
|
-
}
|
|
3496
5170
|
function parseCsv(text) {
|
|
3497
5171
|
const lines = text.split(/\r?\n/).filter((l) => l.trim() !== "");
|
|
3498
5172
|
if (lines.length < 2) return [];
|
|
@@ -3542,7 +5216,10 @@ function GeneratePanel({
|
|
|
3542
5216
|
editorContent,
|
|
3543
5217
|
editorVariables,
|
|
3544
5218
|
onGeneratePdf,
|
|
3545
|
-
initialBulkData
|
|
5219
|
+
initialBulkData,
|
|
5220
|
+
initialVariableValues,
|
|
5221
|
+
exportFileName,
|
|
5222
|
+
templateMode
|
|
3546
5223
|
}) {
|
|
3547
5224
|
const [templateSource, setTemplateSource] = useState("editor");
|
|
3548
5225
|
const [importedMarkdown, setImportedMarkdown] = useState(null);
|
|
@@ -3550,6 +5227,7 @@ function GeneratePanel({
|
|
|
3550
5227
|
const [validationResult, setValidationResult] = useState(null);
|
|
3551
5228
|
const [mode, setMode] = useState("single");
|
|
3552
5229
|
const [variableValues, setVariableValues] = useState({});
|
|
5230
|
+
const [variableSearch, setVariableSearch] = useState("");
|
|
3553
5231
|
const [bulkInputFormat, setBulkInputFormat] = useState("json");
|
|
3554
5232
|
const [bulkInput, setBulkInput] = useState("");
|
|
3555
5233
|
const [bulkData, setBulkData] = useState(null);
|
|
@@ -3576,11 +5254,22 @@ function GeneratePanel({
|
|
|
3576
5254
|
const col = pos - textBefore.lastIndexOf("\n");
|
|
3577
5255
|
setJsonCursor({ line, col });
|
|
3578
5256
|
}, []);
|
|
5257
|
+
const lockedVarNames = React12__default.useMemo(
|
|
5258
|
+
() => templateMode && initialVariableValues ? new Set(Object.keys(initialVariableValues)) : /* @__PURE__ */ new Set(),
|
|
5259
|
+
[templateMode, initialVariableValues]
|
|
5260
|
+
);
|
|
3579
5261
|
const activeVariables = templateSource === "imported" && validationResult?.valid ? validationResult.variables : editorVariables;
|
|
3580
5262
|
const hasVariables = activeVariables.length > 0;
|
|
3581
5263
|
const hasEditorContent = editorMarkdown.trim().length > 0;
|
|
3582
5264
|
const hasImportedContent = templateSource === "imported" && importedMarkdown != null;
|
|
3583
5265
|
const hasContent = hasImportedContent || hasEditorContent;
|
|
5266
|
+
const filteredVariables = React12__default.useMemo(() => {
|
|
5267
|
+
if (!variableSearch.trim()) return activeVariables;
|
|
5268
|
+
const q = variableSearch.toLowerCase();
|
|
5269
|
+
return activeVariables.filter(
|
|
5270
|
+
(v) => v.varName.toLowerCase().includes(q) || (v.varLabel || "").toLowerCase().includes(q)
|
|
5271
|
+
);
|
|
5272
|
+
}, [activeVariables, variableSearch]);
|
|
3584
5273
|
const getActiveContent = useCallback(() => {
|
|
3585
5274
|
if (templateSource === "imported" && importedMarkdown) {
|
|
3586
5275
|
return markdownToTiptap(importedMarkdown);
|
|
@@ -3748,7 +5437,10 @@ function GeneratePanel({
|
|
|
3748
5437
|
setPreviewError(null);
|
|
3749
5438
|
try {
|
|
3750
5439
|
const content = getActiveContent();
|
|
3751
|
-
const
|
|
5440
|
+
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, variableValues);
|
|
5441
|
+
const blockExpanded = expandBlockVariables(expanded, enrichedValues);
|
|
5442
|
+
const suppressed = suppressZeroContent(blockExpanded, enrichedValues);
|
|
5443
|
+
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
3752
5444
|
const fields = extractFieldsFromContent(replaced);
|
|
3753
5445
|
const result = await generatePdfFromContent(replaced);
|
|
3754
5446
|
const pages = await pdfToImages(result.pdfBytes);
|
|
@@ -3769,7 +5461,10 @@ function GeneratePanel({
|
|
|
3769
5461
|
setExportSuccess(null);
|
|
3770
5462
|
try {
|
|
3771
5463
|
const content = getActiveContent();
|
|
3772
|
-
const
|
|
5464
|
+
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, variableValues);
|
|
5465
|
+
const blockExpanded = expandBlockVariables(expanded, enrichedValues);
|
|
5466
|
+
const suppressed = suppressZeroContent(blockExpanded, enrichedValues);
|
|
5467
|
+
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
3773
5468
|
const fields = extractFieldsFromContent(replaced);
|
|
3774
5469
|
const result = await generatePdfFromContent(replaced, {
|
|
3775
5470
|
drawFieldPlaceholders: false,
|
|
@@ -3777,7 +5472,7 @@ function GeneratePanel({
|
|
|
3777
5472
|
fields
|
|
3778
5473
|
});
|
|
3779
5474
|
const blob = new Blob([result.pdfBytes], { type: "application/pdf" });
|
|
3780
|
-
const fileName = importedFileName ? importedFileName.replace(".md", ".pdf") : "document.pdf";
|
|
5475
|
+
const fileName = exportFileName ? exportFileName.endsWith(".pdf") ? exportFileName : `${exportFileName}.pdf` : importedFileName ? importedFileName.replace(".md", ".pdf") : "document.pdf";
|
|
3781
5476
|
if (onGeneratePdf) {
|
|
3782
5477
|
onGeneratePdf(blob, fileName);
|
|
3783
5478
|
} else {
|
|
@@ -3849,7 +5544,10 @@ function GeneratePanel({
|
|
|
3849
5544
|
const allWarnings = [];
|
|
3850
5545
|
for (let i = 0; i < bulkData.length; i++) {
|
|
3851
5546
|
const values = bulkData[i];
|
|
3852
|
-
const
|
|
5547
|
+
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, values);
|
|
5548
|
+
const blockExpanded = expandBlockVariables(expanded, enrichedValues);
|
|
5549
|
+
const suppressed = suppressZeroContent(blockExpanded, enrichedValues);
|
|
5550
|
+
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
3853
5551
|
const fields = extractFieldsFromContent(replaced);
|
|
3854
5552
|
const result = await generatePdfFromContent(replaced, {
|
|
3855
5553
|
drawFieldPlaceholders: false,
|
|
@@ -3904,13 +5602,15 @@ ${allWarnings.join("\n")}`);
|
|
|
3904
5602
|
}, [mode, bulkInputFormat, bulkInput, initialBulkJson]);
|
|
3905
5603
|
React12__default.useEffect(() => {
|
|
3906
5604
|
if (templateSource === "editor") {
|
|
3907
|
-
const defaults = {};
|
|
5605
|
+
const defaults = initialVariableValues ? { ...initialVariableValues } : {};
|
|
3908
5606
|
for (const v of editorVariables) {
|
|
3909
|
-
defaults[v.varName]
|
|
5607
|
+
if (!defaults[v.varName]) {
|
|
5608
|
+
defaults[v.varName] = variableValues[v.varName] || v.varDefault || "";
|
|
5609
|
+
}
|
|
3910
5610
|
}
|
|
3911
5611
|
setVariableValues(defaults);
|
|
3912
5612
|
}
|
|
3913
|
-
}, [editorVariables, templateSource]);
|
|
5613
|
+
}, [editorVariables, templateSource, initialVariableValues]);
|
|
3914
5614
|
useEffect(() => {
|
|
3915
5615
|
setPreviewFresh(false);
|
|
3916
5616
|
}, [variableValues, templateSource, importedMarkdown, editorMarkdown, editorContent]);
|
|
@@ -3922,8 +5622,8 @@ ${allWarnings.join("\n")}`);
|
|
|
3922
5622
|
}, [exportSuccess]);
|
|
3923
5623
|
return /* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0 grid grid-cols-[1fr_1px_1fr]", children: [
|
|
3924
5624
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col min-h-0 min-w-0", children: [
|
|
3925
|
-
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto p-4 space-y-4", children: [
|
|
3926
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
5625
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto p-4 space-y-4 scrollbar-hidden", children: [
|
|
5626
|
+
!templateMode && /* @__PURE__ */ jsxs("div", { children: [
|
|
3927
5627
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
|
|
3928
5628
|
/* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: "Template" }),
|
|
3929
5629
|
templateSource === "imported" && /* @__PURE__ */ jsxs(
|
|
@@ -4016,10 +5716,10 @@ ${allWarnings.join("\n")}`);
|
|
|
4016
5716
|
!hasContent && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-8 text-muted-foreground", children: [
|
|
4017
5717
|
/* @__PURE__ */ jsx(Braces, { size: 32, className: "mb-2 opacity-50" }),
|
|
4018
5718
|
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "No template content" }),
|
|
4019
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs mt-1", children: "Import a .md template above or add content in the Editor tab." })
|
|
5719
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs mt-1", children: templateMode ? "No template content was provided." : "Import a .md template above or add content in the Editor tab." })
|
|
4020
5720
|
] }),
|
|
4021
5721
|
hasVariables && hasContent && /* @__PURE__ */ jsxs("div", { className: "border border-border rounded-lg overflow-hidden", children: [
|
|
4022
|
-
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 px-3 py-2 bg-muted/30 border-b border-border", children: /* @__PURE__ */ jsx(
|
|
5722
|
+
!templateMode && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 px-3 py-2 bg-muted/30 border-b border-border", children: /* @__PURE__ */ jsx(
|
|
4023
5723
|
ToggleGroup,
|
|
4024
5724
|
{
|
|
4025
5725
|
value: mode,
|
|
@@ -4031,20 +5731,53 @@ ${allWarnings.join("\n")}`);
|
|
|
4031
5731
|
}
|
|
4032
5732
|
) }),
|
|
4033
5733
|
mode === "single" && /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-3", children: [
|
|
4034
|
-
/* @__PURE__ */
|
|
4035
|
-
|
|
4036
|
-
/* @__PURE__ */ jsx(
|
|
5734
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
5735
|
+
/* @__PURE__ */ jsx("h3", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: "Variables" }),
|
|
5736
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: activeVariables.length })
|
|
5737
|
+
] }),
|
|
5738
|
+
activeVariables.length > 5 && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
5739
|
+
/* @__PURE__ */ jsx(Search, { size: 14, className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground" }),
|
|
4037
5740
|
/* @__PURE__ */ jsx(
|
|
4038
5741
|
Input,
|
|
4039
5742
|
{
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
5743
|
+
value: variableSearch,
|
|
5744
|
+
onChange: (e) => setVariableSearch(e.target.value),
|
|
5745
|
+
placeholder: "Search variables...",
|
|
5746
|
+
className: "h-8 text-xs pl-8"
|
|
5747
|
+
}
|
|
5748
|
+
),
|
|
5749
|
+
variableSearch && /* @__PURE__ */ jsx(
|
|
5750
|
+
"button",
|
|
5751
|
+
{
|
|
5752
|
+
type: "button",
|
|
5753
|
+
onClick: () => setVariableSearch(""),
|
|
5754
|
+
className: "absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground",
|
|
5755
|
+
children: /* @__PURE__ */ jsx(X, { size: 12 })
|
|
4045
5756
|
}
|
|
4046
5757
|
)
|
|
4047
|
-
] },
|
|
5758
|
+
] }),
|
|
5759
|
+
filteredVariables.length === 0 && variableSearch.trim() && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground py-2 text-center", children: [
|
|
5760
|
+
"No variables match \u201C",
|
|
5761
|
+
variableSearch,
|
|
5762
|
+
"\u201D"
|
|
5763
|
+
] }),
|
|
5764
|
+
filteredVariables.map((v) => {
|
|
5765
|
+
const isLocked = lockedVarNames.has(v.varName);
|
|
5766
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
5767
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: `gen-${v.varName}`, className: "text-xs", children: v.varLabel || v.varName }),
|
|
5768
|
+
/* @__PURE__ */ jsx(
|
|
5769
|
+
Input,
|
|
5770
|
+
{
|
|
5771
|
+
id: `gen-${v.varName}`,
|
|
5772
|
+
value: variableValues[v.varName] || "",
|
|
5773
|
+
onChange: (e) => updateVariableValue(v.varName, e.target.value),
|
|
5774
|
+
placeholder: v.varDefault || `Enter ${v.varLabel || v.varName}`,
|
|
5775
|
+
disabled: isLocked,
|
|
5776
|
+
className: cn("h-8 text-xs", isLocked && "opacity-60 cursor-not-allowed")
|
|
5777
|
+
}
|
|
5778
|
+
)
|
|
5779
|
+
] }, v.varName);
|
|
5780
|
+
})
|
|
4048
5781
|
] }),
|
|
4049
5782
|
mode === "bulk" && /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-3", children: [
|
|
4050
5783
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
@@ -4233,56 +5966,60 @@ ${allWarnings.join("\n")}`);
|
|
|
4233
5966
|
] }),
|
|
4234
5967
|
!hasVariables && hasContent && /* @__PURE__ */ jsx("div", { className: "p-3 rounded-md bg-muted/30 border border-border", children: /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: 'This template has no variables. You can still preview and export the PDF as-is, or add variables in the Editor tab using the "Insert Variable" button.' }) })
|
|
4235
5968
|
] }),
|
|
4236
|
-
|
|
4237
|
-
/* @__PURE__ */
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
5969
|
+
hasContent && /* @__PURE__ */ jsxs("div", { className: "border-t border-border px-4 py-3 flex items-center gap-2 bg-gradient-to-b from-background to-muted/20", children: [
|
|
5970
|
+
mode === "single" || !hasVariables ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5971
|
+
/* @__PURE__ */ jsxs(
|
|
5972
|
+
Button,
|
|
5973
|
+
{
|
|
5974
|
+
variant: "secondary",
|
|
5975
|
+
onClick: handlePreview,
|
|
5976
|
+
disabled: isGenerating,
|
|
5977
|
+
className: "h-10 px-4 font-semibold shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
5978
|
+
children: [
|
|
5979
|
+
isGenerating ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsx(RefreshCw, { size: 16 }),
|
|
5980
|
+
isGenerating ? "Generating..." : "Generate PDF"
|
|
5981
|
+
]
|
|
5982
|
+
}
|
|
5983
|
+
),
|
|
5984
|
+
/* @__PURE__ */ jsxs(
|
|
5985
|
+
Button,
|
|
5986
|
+
{
|
|
5987
|
+
onClick: handleExportSingle,
|
|
5988
|
+
disabled: !previewFresh || isExporting,
|
|
5989
|
+
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",
|
|
5990
|
+
children: [
|
|
5991
|
+
isExporting ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsx(Download, { size: 16 }),
|
|
5992
|
+
isExporting ? "Exporting..." : "Export PDF"
|
|
5993
|
+
]
|
|
5994
|
+
}
|
|
5995
|
+
)
|
|
5996
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
4263
5997
|
Button,
|
|
4264
5998
|
{
|
|
4265
|
-
onClick:
|
|
4266
|
-
disabled: !
|
|
4267
|
-
className: "h-10 px-4 font-semibold bg-primary hover:bg-primary/90 text-primary-foreground shadow-
|
|
5999
|
+
onClick: handleBulkGenerate,
|
|
6000
|
+
disabled: isExporting || !bulkData || bulkData.length === 0,
|
|
6001
|
+
className: "h-10 px-4 font-semibold bg-primary hover:bg-primary/90 text-primary-foreground shadow-lg hover:shadow-xl transition-all duration-200 text-sm",
|
|
4268
6002
|
children: [
|
|
4269
6003
|
isExporting ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsx(Download, { size: 16 }),
|
|
4270
|
-
isExporting ? "
|
|
6004
|
+
isExporting ? "Generating..." : `Generate All (${bulkData?.length || 0} PDFs)`
|
|
4271
6005
|
]
|
|
4272
6006
|
}
|
|
4273
|
-
)
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
]
|
|
4284
|
-
|
|
4285
|
-
|
|
6007
|
+
),
|
|
6008
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 ml-auto", children: [
|
|
6009
|
+
previewError && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
6010
|
+
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
6011
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: previewError })
|
|
6012
|
+
] }),
|
|
6013
|
+
exportError && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
6014
|
+
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
6015
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: exportError })
|
|
6016
|
+
] }),
|
|
6017
|
+
exportSuccess && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-emerald-600 max-w-[300px]", children: [
|
|
6018
|
+
/* @__PURE__ */ jsx(CheckCircle2, { size: 14, className: "shrink-0" }),
|
|
6019
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: exportSuccess })
|
|
6020
|
+
] })
|
|
6021
|
+
] })
|
|
6022
|
+
] })
|
|
4286
6023
|
] }),
|
|
4287
6024
|
/* @__PURE__ */ jsx("div", { className: "bg-border" }),
|
|
4288
6025
|
/* @__PURE__ */ jsx("div", { className: "flex flex-col min-h-0 min-w-0", children: /* @__PURE__ */ jsx(
|
|
@@ -4460,7 +6197,13 @@ function DocumentGeneratorInner({
|
|
|
4460
6197
|
showToolbar = true,
|
|
4461
6198
|
showGenerateTab = true,
|
|
4462
6199
|
onGeneratePdf,
|
|
4463
|
-
initialBulkData
|
|
6200
|
+
initialBulkData,
|
|
6201
|
+
defaultTab = "editor",
|
|
6202
|
+
initialVariableValues,
|
|
6203
|
+
predefinedVariables,
|
|
6204
|
+
exportFileName,
|
|
6205
|
+
templateMode,
|
|
6206
|
+
onSaveTemplate
|
|
4464
6207
|
}) {
|
|
4465
6208
|
const {
|
|
4466
6209
|
editor,
|
|
@@ -4487,7 +6230,7 @@ function DocumentGeneratorInner({
|
|
|
4487
6230
|
placeholder,
|
|
4488
6231
|
onChange
|
|
4489
6232
|
});
|
|
4490
|
-
const [activeTab, setActiveTab] = useState(
|
|
6233
|
+
const [activeTab, setActiveTab] = useState(defaultTab);
|
|
4491
6234
|
const [insertPopoverOpen, setInsertPopoverOpen] = useState(false);
|
|
4492
6235
|
const [insertVarPopoverOpen, setInsertVarPopoverOpen] = useState(false);
|
|
4493
6236
|
const [editFieldId, setEditFieldId] = useState(null);
|
|
@@ -4499,6 +6242,7 @@ function DocumentGeneratorInner({
|
|
|
4499
6242
|
const editorWrapperRef = useRef(null);
|
|
4500
6243
|
const [editorCollapsed, setEditorCollapsed] = useState(false);
|
|
4501
6244
|
const [mdExportError, setMdExportError] = useState(null);
|
|
6245
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
4502
6246
|
const [exportSuccess, setExportSuccess] = useState(null);
|
|
4503
6247
|
useEffect(() => {
|
|
4504
6248
|
const handler = (e) => {
|
|
@@ -4569,7 +6313,28 @@ function DocumentGeneratorInner({
|
|
|
4569
6313
|
onExport(result);
|
|
4570
6314
|
setExportSuccess("Document exported successfully");
|
|
4571
6315
|
}
|
|
4572
|
-
}, [exportDocument, onExport]);
|
|
6316
|
+
}, [exportDocument, onExport]);
|
|
6317
|
+
const handleSaveTemplate = useCallback(async () => {
|
|
6318
|
+
const md = exportMarkdown();
|
|
6319
|
+
if (!md.trim()) {
|
|
6320
|
+
setMdExportError("Editor content is empty");
|
|
6321
|
+
return;
|
|
6322
|
+
}
|
|
6323
|
+
const validation = validateMarkdown(md);
|
|
6324
|
+
if (!validation.valid) {
|
|
6325
|
+
setMdExportError(validation.errors.join("; "));
|
|
6326
|
+
return;
|
|
6327
|
+
}
|
|
6328
|
+
setMdExportError(null);
|
|
6329
|
+
setIsSaving(true);
|
|
6330
|
+
try {
|
|
6331
|
+
await onSaveTemplate?.(md);
|
|
6332
|
+
setExportSuccess("Template saved");
|
|
6333
|
+
} catch {
|
|
6334
|
+
} finally {
|
|
6335
|
+
setIsSaving(false);
|
|
6336
|
+
}
|
|
6337
|
+
}, [exportMarkdown, onSaveTemplate]);
|
|
4573
6338
|
const handleExportMarkdown = useCallback(() => {
|
|
4574
6339
|
const md = exportMarkdown();
|
|
4575
6340
|
if (!md.trim()) {
|
|
@@ -4586,10 +6351,10 @@ function DocumentGeneratorInner({
|
|
|
4586
6351
|
const url = URL.createObjectURL(blob);
|
|
4587
6352
|
const a = document.createElement("a");
|
|
4588
6353
|
a.href = url;
|
|
4589
|
-
a.download = "template.md";
|
|
6354
|
+
a.download = exportFileName ? `${exportFileName}.md` : "template.md";
|
|
4590
6355
|
a.click();
|
|
4591
6356
|
URL.revokeObjectURL(url);
|
|
4592
|
-
}, [exportMarkdown]);
|
|
6357
|
+
}, [exportMarkdown, exportFileName]);
|
|
4593
6358
|
useEffect(() => {
|
|
4594
6359
|
if (mdExportError) setMdExportError(null);
|
|
4595
6360
|
}, [markdown]);
|
|
@@ -4625,6 +6390,7 @@ function DocumentGeneratorInner({
|
|
|
4625
6390
|
open: insertVarPopoverOpen,
|
|
4626
6391
|
onOpenChange: setInsertVarPopoverOpen,
|
|
4627
6392
|
onInsert: handleInsertVariable,
|
|
6393
|
+
predefinedVariables,
|
|
4628
6394
|
children: /* @__PURE__ */ jsxs(
|
|
4629
6395
|
Button,
|
|
4630
6396
|
{
|
|
@@ -4665,118 +6431,141 @@ function DocumentGeneratorInner({
|
|
|
4665
6431
|
] })
|
|
4666
6432
|
] })
|
|
4667
6433
|
] }),
|
|
4668
|
-
/* @__PURE__ */
|
|
6434
|
+
/* @__PURE__ */ jsx("div", { className: cn(
|
|
4669
6435
|
"flex flex-col flex-1 min-h-0",
|
|
4670
6436
|
activeTab !== "editor" && "hidden"
|
|
6437
|
+
), children: /* @__PURE__ */ jsxs("div", { className: cn(
|
|
6438
|
+
"flex-1 min-h-0",
|
|
6439
|
+
showPreview && !editorCollapsed ? "grid grid-cols-[1fr_1px_1fr]" : "flex flex-col"
|
|
4671
6440
|
), children: [
|
|
4672
|
-
/* @__PURE__ */ jsxs("div", { className:
|
|
4673
|
-
"
|
|
4674
|
-
|
|
4675
|
-
), children: [
|
|
4676
|
-
!editorCollapsed && /* @__PURE__ */ jsxs("div", { className: "flex flex-col min-h-0 min-w-0", children: [
|
|
4677
|
-
showToolbar && !readOnly && /* @__PURE__ */ jsx("div", { className: "border-b border-border", children: /* @__PURE__ */ jsx(
|
|
4678
|
-
EditorToolbar,
|
|
4679
|
-
{
|
|
4680
|
-
editor,
|
|
4681
|
-
insertFieldButton,
|
|
4682
|
-
insertVariableButton,
|
|
4683
|
-
onCollapse: showPreview ? () => setEditorCollapsed(true) : void 0
|
|
4684
|
-
}
|
|
4685
|
-
) }),
|
|
4686
|
-
/* @__PURE__ */ jsx("div", { ref: editorWrapperRef, className: "flex-1 overflow-y-auto min-h-0", children: /* @__PURE__ */ jsx(
|
|
4687
|
-
EditorContent,
|
|
4688
|
-
{
|
|
4689
|
-
editor,
|
|
4690
|
-
className: "prose prose-sm max-w-none p-4 focus-within:outline-none"
|
|
4691
|
-
}
|
|
4692
|
-
) })
|
|
4693
|
-
] }),
|
|
4694
|
-
showPreview && !editorCollapsed && /* @__PURE__ */ jsx("div", { className: "bg-border" }),
|
|
4695
|
-
showPreview && /* @__PURE__ */ jsx(
|
|
4696
|
-
PreviewPanel,
|
|
6441
|
+
!editorCollapsed && /* @__PURE__ */ jsxs("div", { className: "flex flex-col min-h-0 min-w-0", children: [
|
|
6442
|
+
showToolbar && !readOnly && /* @__PURE__ */ jsx("div", { className: "border-b border-border", children: /* @__PURE__ */ jsx(
|
|
6443
|
+
EditorToolbar,
|
|
4697
6444
|
{
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
Button,
|
|
4703
|
-
{
|
|
4704
|
-
variant: "ghost",
|
|
4705
|
-
size: "sm",
|
|
4706
|
-
className: "h-8 w-8 p-0",
|
|
4707
|
-
onClick: () => setEditorCollapsed(false),
|
|
4708
|
-
children: /* @__PURE__ */ jsx(PanelLeftOpen, { size: 14 })
|
|
4709
|
-
}
|
|
4710
|
-
) : void 0,
|
|
4711
|
-
className: "flex-1 border-0 rounded-none"
|
|
6445
|
+
editor,
|
|
6446
|
+
insertFieldButton,
|
|
6447
|
+
insertVariableButton,
|
|
6448
|
+
onCollapse: showPreview ? () => setEditorCollapsed(true) : void 0
|
|
4712
6449
|
}
|
|
4713
|
-
)
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
/* @__PURE__ */ jsxs(
|
|
4717
|
-
Button,
|
|
6450
|
+
) }),
|
|
6451
|
+
/* @__PURE__ */ jsx("div", { ref: editorWrapperRef, className: "flex-1 overflow-y-auto min-h-0 scrollbar-hidden", children: /* @__PURE__ */ jsx(
|
|
6452
|
+
EditorContent,
|
|
4718
6453
|
{
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
disabled: isGenerating || !editor,
|
|
4722
|
-
className: "h-10 px-4 font-semibold shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
4723
|
-
children: [
|
|
4724
|
-
isGenerating ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsx(RefreshCw, { size: 16 }),
|
|
4725
|
-
isGenerating ? "Generating..." : "Generate PDF"
|
|
4726
|
-
]
|
|
6454
|
+
editor,
|
|
6455
|
+
className: "prose prose-sm max-w-none p-4 focus-within:outline-none"
|
|
4727
6456
|
}
|
|
4728
|
-
),
|
|
4729
|
-
/* @__PURE__ */ jsxs("div", { className: "flex
|
|
4730
|
-
mdExportError && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
4731
|
-
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
4732
|
-
/* @__PURE__ */ jsx("span", { className: "truncate", children: mdExportError })
|
|
4733
|
-
] }),
|
|
4734
|
-
generationError && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
4735
|
-
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
4736
|
-
/* @__PURE__ */ jsx("span", { className: "truncate", children: generationError })
|
|
4737
|
-
] }),
|
|
4738
|
-
fieldWarnings.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-orange-600 max-w-[400px]", children: [
|
|
4739
|
-
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
4740
|
-
/* @__PURE__ */ jsxs("span", { className: "truncate", children: [
|
|
4741
|
-
fieldWarnings.length,
|
|
4742
|
-
" field",
|
|
4743
|
-
fieldWarnings.length !== 1 ? "s" : "",
|
|
4744
|
-
" had warnings during generation"
|
|
4745
|
-
] })
|
|
4746
|
-
] }),
|
|
4747
|
-
exportSuccess && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-emerald-600 max-w-[300px]", children: [
|
|
4748
|
-
/* @__PURE__ */ jsx(CheckCircle2, { size: 14, className: "shrink-0" }),
|
|
4749
|
-
/* @__PURE__ */ jsx("span", { children: exportSuccess })
|
|
4750
|
-
] })
|
|
4751
|
-
] }),
|
|
4752
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
6457
|
+
) }),
|
|
6458
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-4 py-3 border-t border-border bg-gradient-to-b from-background to-muted/20", children: [
|
|
4753
6459
|
/* @__PURE__ */ jsxs(
|
|
4754
6460
|
Button,
|
|
4755
6461
|
{
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
6462
|
+
variant: "secondary",
|
|
6463
|
+
onClick: generatePdf,
|
|
6464
|
+
disabled: isGenerating || !editor,
|
|
6465
|
+
className: "h-10 px-4 font-semibold shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
4759
6466
|
children: [
|
|
4760
|
-
/* @__PURE__ */ jsx(
|
|
4761
|
-
"
|
|
6467
|
+
isGenerating ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsx(RefreshCw, { size: 16 }),
|
|
6468
|
+
isGenerating ? "Generating..." : "Generate PDF"
|
|
4762
6469
|
]
|
|
4763
6470
|
}
|
|
4764
6471
|
),
|
|
4765
|
-
/* @__PURE__ */ jsxs(
|
|
6472
|
+
onSaveTemplate ? /* @__PURE__ */ jsxs(
|
|
4766
6473
|
Button,
|
|
4767
6474
|
{
|
|
4768
|
-
onClick:
|
|
4769
|
-
disabled:
|
|
6475
|
+
onClick: handleSaveTemplate,
|
|
6476
|
+
disabled: isSaving || !editor || !markdown.trim(),
|
|
4770
6477
|
className: "h-10 px-4 font-semibold bg-primary hover:bg-primary/90 text-primary-foreground shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
4771
6478
|
children: [
|
|
4772
|
-
/* @__PURE__ */ jsx(
|
|
4773
|
-
exportButtonText
|
|
6479
|
+
isSaving ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsx(Save, { size: 16 }),
|
|
6480
|
+
isSaving ? "Saving..." : exportButtonText || "Save Template"
|
|
4774
6481
|
]
|
|
4775
6482
|
}
|
|
4776
|
-
)
|
|
6483
|
+
) : /* @__PURE__ */ jsxs(Popover, { children: [
|
|
6484
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
6485
|
+
Button,
|
|
6486
|
+
{
|
|
6487
|
+
disabled: !editor || !markdown.trim() && pdfPages.length === 0,
|
|
6488
|
+
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",
|
|
6489
|
+
children: [
|
|
6490
|
+
/* @__PURE__ */ jsx(FileDown, { size: 16 }),
|
|
6491
|
+
"Export",
|
|
6492
|
+
/* @__PURE__ */ jsx(ChevronDown, { size: 14 })
|
|
6493
|
+
]
|
|
6494
|
+
}
|
|
6495
|
+
) }),
|
|
6496
|
+
/* @__PURE__ */ jsxs(PopoverContent, { align: "start", className: "w-48 p-1", children: [
|
|
6497
|
+
/* @__PURE__ */ jsxs(
|
|
6498
|
+
"button",
|
|
6499
|
+
{
|
|
6500
|
+
onClick: handleExportMarkdown,
|
|
6501
|
+
disabled: !editor || !markdown.trim(),
|
|
6502
|
+
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",
|
|
6503
|
+
children: [
|
|
6504
|
+
/* @__PURE__ */ jsx(FileText, { size: 16 }),
|
|
6505
|
+
"Export as MD"
|
|
6506
|
+
]
|
|
6507
|
+
}
|
|
6508
|
+
),
|
|
6509
|
+
/* @__PURE__ */ jsxs(
|
|
6510
|
+
"button",
|
|
6511
|
+
{
|
|
6512
|
+
onClick: handleExport,
|
|
6513
|
+
disabled: isGenerating || !editor || pdfPages.length === 0,
|
|
6514
|
+
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",
|
|
6515
|
+
children: [
|
|
6516
|
+
/* @__PURE__ */ jsx(FileDown, { size: 16 }),
|
|
6517
|
+
"Export as PDF"
|
|
6518
|
+
]
|
|
6519
|
+
}
|
|
6520
|
+
)
|
|
6521
|
+
] })
|
|
6522
|
+
] }),
|
|
6523
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 ml-auto", children: [
|
|
6524
|
+
mdExportError && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
6525
|
+
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
6526
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: mdExportError })
|
|
6527
|
+
] }),
|
|
6528
|
+
generationError && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
6529
|
+
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
6530
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: generationError })
|
|
6531
|
+
] }),
|
|
6532
|
+
fieldWarnings.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-orange-600 max-w-[400px]", children: [
|
|
6533
|
+
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
6534
|
+
/* @__PURE__ */ jsxs("span", { className: "truncate", children: [
|
|
6535
|
+
fieldWarnings.length,
|
|
6536
|
+
" field",
|
|
6537
|
+
fieldWarnings.length !== 1 ? "s" : "",
|
|
6538
|
+
" had warnings during generation"
|
|
6539
|
+
] })
|
|
6540
|
+
] }),
|
|
6541
|
+
exportSuccess && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-emerald-600 max-w-[300px]", children: [
|
|
6542
|
+
/* @__PURE__ */ jsx(CheckCircle2, { size: 14, className: "shrink-0" }),
|
|
6543
|
+
/* @__PURE__ */ jsx("span", { children: exportSuccess })
|
|
6544
|
+
] })
|
|
6545
|
+
] })
|
|
4777
6546
|
] })
|
|
4778
|
-
] })
|
|
4779
|
-
|
|
6547
|
+
] }),
|
|
6548
|
+
showPreview && !editorCollapsed && /* @__PURE__ */ jsx("div", { className: "bg-border" }),
|
|
6549
|
+
showPreview && /* @__PURE__ */ jsx(
|
|
6550
|
+
PreviewPanel,
|
|
6551
|
+
{
|
|
6552
|
+
pages: pdfPages,
|
|
6553
|
+
isGenerating,
|
|
6554
|
+
positionedFields,
|
|
6555
|
+
headerLeft: editorCollapsed ? /* @__PURE__ */ jsx(
|
|
6556
|
+
Button,
|
|
6557
|
+
{
|
|
6558
|
+
variant: "ghost",
|
|
6559
|
+
size: "sm",
|
|
6560
|
+
className: "h-8 w-8 p-0",
|
|
6561
|
+
onClick: () => setEditorCollapsed(false),
|
|
6562
|
+
children: /* @__PURE__ */ jsx(PanelLeftOpen, { size: 14 })
|
|
6563
|
+
}
|
|
6564
|
+
) : void 0,
|
|
6565
|
+
className: "flex-1 border-0 rounded-none"
|
|
6566
|
+
}
|
|
6567
|
+
)
|
|
6568
|
+
] }) }),
|
|
4780
6569
|
showGenerateTab && activeTab === "generate" && /* @__PURE__ */ jsx(
|
|
4781
6570
|
GeneratePanel,
|
|
4782
6571
|
{
|
|
@@ -4784,7 +6573,10 @@ function DocumentGeneratorInner({
|
|
|
4784
6573
|
editorContent: editor?.getJSON(),
|
|
4785
6574
|
editorVariables: variables,
|
|
4786
6575
|
onGeneratePdf,
|
|
4787
|
-
initialBulkData
|
|
6576
|
+
initialBulkData,
|
|
6577
|
+
initialVariableValues,
|
|
6578
|
+
exportFileName,
|
|
6579
|
+
templateMode
|
|
4788
6580
|
}
|
|
4789
6581
|
),
|
|
4790
6582
|
/* @__PURE__ */ jsx(
|
|
@@ -4797,7 +6589,7 @@ function DocumentGeneratorInner({
|
|
|
4797
6589
|
onDelete: deleteField,
|
|
4798
6590
|
onClose: handleCloseFieldEditPopover
|
|
4799
6591
|
},
|
|
4800
|
-
editFieldId
|
|
6592
|
+
editFieldId ?? "field-edit"
|
|
4801
6593
|
),
|
|
4802
6594
|
/* @__PURE__ */ jsx(
|
|
4803
6595
|
VariableEditPopover,
|
|
@@ -4809,7 +6601,7 @@ function DocumentGeneratorInner({
|
|
|
4809
6601
|
onDelete: deleteVariable,
|
|
4810
6602
|
onClose: handleCloseVarEditPopover
|
|
4811
6603
|
},
|
|
4812
|
-
editVarName
|
|
6604
|
+
editVarName ?? "var-edit"
|
|
4813
6605
|
)
|
|
4814
6606
|
] });
|
|
4815
6607
|
}
|
|
@@ -4925,6 +6717,717 @@ function EditorPanel({
|
|
|
4925
6717
|
] });
|
|
4926
6718
|
}
|
|
4927
6719
|
|
|
4928
|
-
|
|
6720
|
+
// src/utils/xml-template-parser.ts
|
|
6721
|
+
function extractFieldRefs(expr) {
|
|
6722
|
+
const refs = [];
|
|
6723
|
+
const re = /\[([^\]]+)\](?:\.\[([^\]]+)\])?/g;
|
|
6724
|
+
let m;
|
|
6725
|
+
while ((m = re.exec(expr)) !== null) {
|
|
6726
|
+
if (m[2]) {
|
|
6727
|
+
refs.push(`${m[1]}.${m[2]}`);
|
|
6728
|
+
} else {
|
|
6729
|
+
refs.push(m[1]);
|
|
6730
|
+
}
|
|
6731
|
+
}
|
|
6732
|
+
return refs;
|
|
6733
|
+
}
|
|
6734
|
+
function fieldRefToVarName(ref, bandContext) {
|
|
6735
|
+
if (ref.includes(".")) {
|
|
6736
|
+
return labelToVarName(ref.replace(/\./g, "_"));
|
|
6737
|
+
}
|
|
6738
|
+
if (bandContext) {
|
|
6739
|
+
return labelToVarName(bandContext + "_" + ref);
|
|
6740
|
+
}
|
|
6741
|
+
return labelToVarName(ref);
|
|
6742
|
+
}
|
|
6743
|
+
function fieldRefToLabel(ref) {
|
|
6744
|
+
const parts = ref.split(".");
|
|
6745
|
+
const raw = parts[parts.length - 1];
|
|
6746
|
+
return raw.replace(/_/g, " ");
|
|
6747
|
+
}
|
|
6748
|
+
function resolveExpression(expr, registry, siblingLabel, bandContext) {
|
|
6749
|
+
const trimmed = expr.trim();
|
|
6750
|
+
const suppressZero = /<>\s*0/.test(trimmed);
|
|
6751
|
+
const szOpts = suppressZero ? { suppressZero: true } : void 0;
|
|
6752
|
+
const aggMatch = trimmed.match(
|
|
6753
|
+
/^\[([^\]]+)\]\.sum\(\[([^\]]+)\]\)$/i
|
|
6754
|
+
);
|
|
6755
|
+
if (aggMatch) {
|
|
6756
|
+
const fieldName = aggMatch[2];
|
|
6757
|
+
const varName = labelToVarName("workorder_" + fieldName);
|
|
6758
|
+
const label = fieldName.replace(/_/g, " ");
|
|
6759
|
+
return registry.register(varName, label, szOpts);
|
|
6760
|
+
}
|
|
6761
|
+
const concatSingle = trimmed.match(
|
|
6762
|
+
/^concat\(\s*'[^']*'\s*,\s*\[([^\]]+)\](?:\.\[([^\]]+)\])?\s*\)$/i
|
|
6763
|
+
);
|
|
6764
|
+
if (concatSingle) {
|
|
6765
|
+
const ref = concatSingle[2] ? `${concatSingle[1]}.${concatSingle[2]}` : concatSingle[1];
|
|
6766
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6767
|
+
const label = fieldRefToLabel(ref);
|
|
6768
|
+
return registry.register(varName, label, szOpts);
|
|
6769
|
+
}
|
|
6770
|
+
const concatMulti = trimmed.match(
|
|
6771
|
+
/^concat\(/i
|
|
6772
|
+
);
|
|
6773
|
+
if (concatMulti) {
|
|
6774
|
+
const refs = extractFieldRefs(trimmed);
|
|
6775
|
+
if (refs.length > 0) {
|
|
6776
|
+
const uniqueRefs = [...new Set(refs)];
|
|
6777
|
+
const tokens = uniqueRefs.map((ref) => {
|
|
6778
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6779
|
+
const label = fieldRefToLabel(ref);
|
|
6780
|
+
return registry.register(varName, label, szOpts);
|
|
6781
|
+
});
|
|
6782
|
+
return tokens.join(" ");
|
|
6783
|
+
}
|
|
6784
|
+
}
|
|
6785
|
+
const toLongMatch = trimmed.match(
|
|
6786
|
+
/^ToLong\(\s*\[([^\]]+)\](?:\.\[([^\]]+)\])?\s*\)$/i
|
|
6787
|
+
);
|
|
6788
|
+
if (toLongMatch) {
|
|
6789
|
+
const ref = toLongMatch[2] ? `${toLongMatch[1]}.${toLongMatch[2]}` : toLongMatch[1];
|
|
6790
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6791
|
+
const label = fieldRefToLabel(ref);
|
|
6792
|
+
return registry.register(varName, label, szOpts);
|
|
6793
|
+
}
|
|
6794
|
+
const fmtMatch = trimmed.match(
|
|
6795
|
+
/^FormatString\(\s*'[^']*'\s*,\s*(.+)\s*\)$/i
|
|
6796
|
+
);
|
|
6797
|
+
if (fmtMatch) {
|
|
6798
|
+
return resolveExpression(fmtMatch[1], registry, siblingLabel, bandContext);
|
|
6799
|
+
}
|
|
6800
|
+
const replaceMatch = trimmed.match(
|
|
6801
|
+
/^Replace\(\s*\[([^\]]+)\]/i
|
|
6802
|
+
);
|
|
6803
|
+
if (replaceMatch) {
|
|
6804
|
+
const ref = replaceMatch[1];
|
|
6805
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6806
|
+
const label = fieldRefToLabel(ref);
|
|
6807
|
+
return registry.register(varName, label, szOpts);
|
|
6808
|
+
}
|
|
6809
|
+
const iifLabelMatch = trimmed.match(
|
|
6810
|
+
/^iif\s*\(.+?,\s*'([^']+)'\s*,\s*\?\s*\)$/i
|
|
6811
|
+
);
|
|
6812
|
+
if (iifLabelMatch) {
|
|
6813
|
+
return iifLabelMatch[1];
|
|
6814
|
+
}
|
|
6815
|
+
const iifLabelElse = trimmed.match(
|
|
6816
|
+
/^iif\s*\(.+?,\s*'([^']+)'\s*,\s*'([^']+)'\s*\)$/i
|
|
6817
|
+
);
|
|
6818
|
+
if (iifLabelElse) {
|
|
6819
|
+
return iifLabelElse[1];
|
|
6820
|
+
}
|
|
6821
|
+
const iifEmptyElse = trimmed.match(
|
|
6822
|
+
/^[Ii]if\s*\(.+?,\s*'([^']+)'\s*,\s*''\s*\)$/i
|
|
6823
|
+
);
|
|
6824
|
+
if (iifEmptyElse) {
|
|
6825
|
+
return iifEmptyElse[1];
|
|
6826
|
+
}
|
|
6827
|
+
const nestedIifStaticLabel = trimmed.match(
|
|
6828
|
+
/^iif\s*\(.*,\s*[Ii]if\s*\([^,]+,\s*'([^']+)'\s*,/i
|
|
6829
|
+
);
|
|
6830
|
+
if (nestedIifStaticLabel) {
|
|
6831
|
+
return nestedIifStaticLabel[1];
|
|
6832
|
+
}
|
|
6833
|
+
const iifValueMatch = trimmed.match(
|
|
6834
|
+
/^iif\s*\(/i
|
|
6835
|
+
);
|
|
6836
|
+
if (iifValueMatch) {
|
|
6837
|
+
const refs = extractFieldRefs(trimmed);
|
|
6838
|
+
if (refs.length > 0) {
|
|
6839
|
+
const uniqueRefs = [...new Set(refs)];
|
|
6840
|
+
const innerAggMatch = trimmed.match(
|
|
6841
|
+
/\[([^\]]+)\]\.sum\(\[([^\]]+)\]\)/i
|
|
6842
|
+
);
|
|
6843
|
+
if (innerAggMatch) {
|
|
6844
|
+
const fieldName = innerAggMatch[2];
|
|
6845
|
+
const varName2 = labelToVarName("workorder_" + fieldName);
|
|
6846
|
+
const label2 = fieldName.replace(/_/g, " ");
|
|
6847
|
+
return registry.register(varName2, label2, szOpts);
|
|
6848
|
+
}
|
|
6849
|
+
const ref = uniqueRefs[uniqueRefs.length - 1];
|
|
6850
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6851
|
+
const label = fieldRefToLabel(ref);
|
|
6852
|
+
return registry.register(varName, label, szOpts);
|
|
6853
|
+
}
|
|
6854
|
+
const staticMatch = trimmed.match(/'([^']+)'/);
|
|
6855
|
+
if (staticMatch) return staticMatch[1];
|
|
6856
|
+
return "";
|
|
6857
|
+
}
|
|
6858
|
+
const arithmeticRefs = extractFieldRefs(trimmed);
|
|
6859
|
+
if (arithmeticRefs.length > 1 && /[+\-*/]/.test(trimmed)) {
|
|
6860
|
+
if (siblingLabel) {
|
|
6861
|
+
const cleanLabel = siblingLabel.replace(/[:\s]+$/, "");
|
|
6862
|
+
const varName = labelToVarName(
|
|
6863
|
+
(bandContext ? bandContext + "_" : "") + cleanLabel.replace(/\s+/g, "_")
|
|
6864
|
+
);
|
|
6865
|
+
return registry.register(varName, cleanLabel, szOpts);
|
|
6866
|
+
}
|
|
6867
|
+
const uniqueRefs = [...new Set(arithmeticRefs)];
|
|
6868
|
+
const tokens = uniqueRefs.map((ref) => {
|
|
6869
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6870
|
+
const label = fieldRefToLabel(ref);
|
|
6871
|
+
return registry.register(varName, label, szOpts);
|
|
6872
|
+
});
|
|
6873
|
+
return tokens.join(" ");
|
|
6874
|
+
}
|
|
6875
|
+
const simpleMatch = trimmed.match(
|
|
6876
|
+
/^\[([^\]]+)\](?:\.\[([^\]]+)\])?$/
|
|
6877
|
+
);
|
|
6878
|
+
if (simpleMatch) {
|
|
6879
|
+
const ref = simpleMatch[2] ? `${simpleMatch[1]}.${simpleMatch[2]}` : simpleMatch[1];
|
|
6880
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6881
|
+
const label = fieldRefToLabel(ref);
|
|
6882
|
+
return registry.register(varName, label, szOpts);
|
|
6883
|
+
}
|
|
6884
|
+
if (arithmeticRefs.length >= 1) {
|
|
6885
|
+
const ref = arithmeticRefs[arithmeticRefs.length - 1];
|
|
6886
|
+
const varName = fieldRefToVarName(ref, bandContext);
|
|
6887
|
+
const label = fieldRefToLabel(ref);
|
|
6888
|
+
return registry.register(varName, label, szOpts);
|
|
6889
|
+
}
|
|
6890
|
+
return "";
|
|
6891
|
+
}
|
|
6892
|
+
var VariableRegistry = class {
|
|
6893
|
+
constructor() {
|
|
6894
|
+
__publicField(this, "map", /* @__PURE__ */ new Map());
|
|
6895
|
+
}
|
|
6896
|
+
register(varName, varLabel, options) {
|
|
6897
|
+
if (!this.map.has(varName)) {
|
|
6898
|
+
this.map.set(varName, { varName, varLabel, varDefault: "" });
|
|
6899
|
+
}
|
|
6900
|
+
const suppress = options?.suppressZero ? "|suppress:zero" : "";
|
|
6901
|
+
return `{{var|name:${varName}|label:${varLabel}${suppress}}}`;
|
|
6902
|
+
}
|
|
6903
|
+
getAll() {
|
|
6904
|
+
return Array.from(this.map.values());
|
|
6905
|
+
}
|
|
6906
|
+
};
|
|
6907
|
+
function parseLocationFloat(s) {
|
|
6908
|
+
if (!s) return { x: 0, y: 0 };
|
|
6909
|
+
const parts = s.split(",");
|
|
6910
|
+
return {
|
|
6911
|
+
x: parseFloat(parts[0]) || 0,
|
|
6912
|
+
y: parseFloat(parts[1]) || 0
|
|
6913
|
+
};
|
|
6914
|
+
}
|
|
6915
|
+
function parseSizeF(s) {
|
|
6916
|
+
if (!s) return { w: 0, h: 0 };
|
|
6917
|
+
const parts = s.split(",");
|
|
6918
|
+
return {
|
|
6919
|
+
w: parseFloat(parts[0]) || 0,
|
|
6920
|
+
h: parseFloat(parts[1]) || 0
|
|
6921
|
+
};
|
|
6922
|
+
}
|
|
6923
|
+
function extractControls(bandEl) {
|
|
6924
|
+
const controlsEl = bandEl.querySelector(":scope > Controls");
|
|
6925
|
+
if (!controlsEl) return [];
|
|
6926
|
+
const items = [];
|
|
6927
|
+
for (const child of Array.from(controlsEl.children)) {
|
|
6928
|
+
items.push(extractControl(child));
|
|
6929
|
+
}
|
|
6930
|
+
return items;
|
|
6931
|
+
}
|
|
6932
|
+
function extractControl(el) {
|
|
6933
|
+
const ctrlType = el.getAttribute("ControlType") || "";
|
|
6934
|
+
const name = el.getAttribute("Name") || "";
|
|
6935
|
+
const text = el.getAttribute("Text") || "";
|
|
6936
|
+
const loc = parseLocationFloat(el.getAttribute("LocationFloat"));
|
|
6937
|
+
const size = parseSizeF(el.getAttribute("SizeF"));
|
|
6938
|
+
const formatString = el.getAttribute("TextFormatString") || null;
|
|
6939
|
+
let expression = null;
|
|
6940
|
+
const bindingsEl = el.querySelector(":scope > ExpressionBindings");
|
|
6941
|
+
if (bindingsEl) {
|
|
6942
|
+
for (const item of Array.from(bindingsEl.children)) {
|
|
6943
|
+
if (item.getAttribute("PropertyName") === "Text" && item.getAttribute("EventName") === "BeforePrint") {
|
|
6944
|
+
expression = item.getAttribute("Expression");
|
|
6945
|
+
break;
|
|
6946
|
+
}
|
|
6947
|
+
}
|
|
6948
|
+
}
|
|
6949
|
+
const children = [];
|
|
6950
|
+
if (ctrlType === "XRPanel") {
|
|
6951
|
+
const panelControls = extractControls(el);
|
|
6952
|
+
children.push(...panelControls);
|
|
6953
|
+
}
|
|
6954
|
+
return {
|
|
6955
|
+
name,
|
|
6956
|
+
type: ctrlType,
|
|
6957
|
+
text,
|
|
6958
|
+
x: loc.x,
|
|
6959
|
+
y: loc.y,
|
|
6960
|
+
width: size.w,
|
|
6961
|
+
expression,
|
|
6962
|
+
formatString,
|
|
6963
|
+
children
|
|
6964
|
+
};
|
|
6965
|
+
}
|
|
6966
|
+
function groupByRow(controls, threshold = 8) {
|
|
6967
|
+
const sorted = [...controls].sort((a, b) => a.y - b.y);
|
|
6968
|
+
const rows = [];
|
|
6969
|
+
for (const ctrl of sorted) {
|
|
6970
|
+
const lastRow = rows[rows.length - 1];
|
|
6971
|
+
if (lastRow && Math.abs(ctrl.y - lastRow.y) < threshold) {
|
|
6972
|
+
lastRow.controls.push(ctrl);
|
|
6973
|
+
} else {
|
|
6974
|
+
rows.push({ y: ctrl.y, controls: [ctrl] });
|
|
6975
|
+
}
|
|
6976
|
+
}
|
|
6977
|
+
for (const row of rows) {
|
|
6978
|
+
row.controls.sort((a, b) => a.x - b.x);
|
|
6979
|
+
}
|
|
6980
|
+
return rows;
|
|
6981
|
+
}
|
|
6982
|
+
function isStaticLabel(ctrl) {
|
|
6983
|
+
return ctrl.type === "XRLabel" && !ctrl.expression;
|
|
6984
|
+
}
|
|
6985
|
+
function isBound(ctrl) {
|
|
6986
|
+
return ctrl.type === "XRLabel" && !!ctrl.expression;
|
|
6987
|
+
}
|
|
6988
|
+
function computeSplitPercent(leftControls, rightControls, contentWidth) {
|
|
6989
|
+
const leftExtent = leftControls.reduce(
|
|
6990
|
+
(max, c) => Math.max(max, c.x + c.width),
|
|
6991
|
+
0
|
|
6992
|
+
);
|
|
6993
|
+
const rightMinX = rightControls.reduce(
|
|
6994
|
+
(min, c) => Math.min(min, c.x),
|
|
6995
|
+
contentWidth
|
|
6996
|
+
);
|
|
6997
|
+
const rightMaxExtent = rightControls.reduce(
|
|
6998
|
+
(max, c) => Math.max(max, c.x + c.width),
|
|
6999
|
+
0
|
|
7000
|
+
);
|
|
7001
|
+
const rightExtent = rightMaxExtent - rightMinX;
|
|
7002
|
+
if (leftExtent + rightExtent === 0) return 50;
|
|
7003
|
+
return Math.round(leftExtent / (leftExtent + rightExtent) * 100);
|
|
7004
|
+
}
|
|
7005
|
+
function renderTopMarginBand(controls, registry, contentWidth, calculatedFieldNames) {
|
|
7006
|
+
const lines = [];
|
|
7007
|
+
const leftControls = controls.filter((c) => c.x < 437);
|
|
7008
|
+
const rightControls = controls.filter((c) => c.x >= 437);
|
|
7009
|
+
const leftEntries = [];
|
|
7010
|
+
if (leftControls.length > 0) {
|
|
7011
|
+
const leftRows = groupByRow(leftControls);
|
|
7012
|
+
for (const row of leftRows) {
|
|
7013
|
+
const parts = [];
|
|
7014
|
+
for (const ctrl of row.controls) {
|
|
7015
|
+
if (isBound(ctrl)) {
|
|
7016
|
+
let resolved = resolveExpression(ctrl.expression, registry, void 0, "workorder");
|
|
7017
|
+
if (ctrl.formatString && /\(###\)\s*###\s*-\s*####/.test(ctrl.formatString) && resolved.startsWith("{{var|")) {
|
|
7018
|
+
resolved = resolved.replace("}}", "|format:phone}}");
|
|
7019
|
+
}
|
|
7020
|
+
if (resolved) parts.push(resolved);
|
|
7021
|
+
} else if (isStaticLabel(ctrl)) {
|
|
7022
|
+
const text = ctrl.text.trim();
|
|
7023
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
7024
|
+
parts.push(text);
|
|
7025
|
+
}
|
|
7026
|
+
}
|
|
7027
|
+
}
|
|
7028
|
+
if (parts.length > 0) {
|
|
7029
|
+
leftEntries.push(parts.join(" "));
|
|
7030
|
+
}
|
|
7031
|
+
}
|
|
7032
|
+
}
|
|
7033
|
+
const rightEntries = [];
|
|
7034
|
+
if (rightControls.length > 0) {
|
|
7035
|
+
const rightRows = groupByRow(rightControls);
|
|
7036
|
+
for (const row of rightRows) {
|
|
7037
|
+
let label = "";
|
|
7038
|
+
let value = "";
|
|
7039
|
+
for (const ctrl of row.controls) {
|
|
7040
|
+
if (isStaticLabel(ctrl)) {
|
|
7041
|
+
const text = ctrl.text.trim();
|
|
7042
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
7043
|
+
label = text;
|
|
7044
|
+
}
|
|
7045
|
+
} else if (isBound(ctrl)) {
|
|
7046
|
+
value = resolveExpression(ctrl.expression, registry, label, "workorder");
|
|
7047
|
+
if (ctrl.expression) {
|
|
7048
|
+
const refs = extractFieldRefs(ctrl.expression);
|
|
7049
|
+
if (refs.some((ref) => calculatedFieldNames.has(ref)) && value.startsWith("{{var|")) {
|
|
7050
|
+
value = value.replace("}}", "|suppress:zero}}");
|
|
7051
|
+
}
|
|
7052
|
+
}
|
|
7053
|
+
if (ctrl.formatString && /\(###\)\s*###\s*-\s*####/.test(ctrl.formatString) && value.startsWith("{{var|")) {
|
|
7054
|
+
value = value.replace("}}", "|format:phone}}");
|
|
7055
|
+
}
|
|
7056
|
+
}
|
|
7057
|
+
}
|
|
7058
|
+
if (label || value) {
|
|
7059
|
+
rightEntries.push({ label, value, y: row.y });
|
|
7060
|
+
}
|
|
7061
|
+
}
|
|
7062
|
+
}
|
|
7063
|
+
if (leftEntries.length > 0 || rightEntries.length > 0) {
|
|
7064
|
+
const outerSplit = computeSplitPercent(leftControls, rightControls, contentWidth);
|
|
7065
|
+
lines.push(`:::columns{split:${outerSplit}}`);
|
|
7066
|
+
const leftMinY = leftControls.length > 0 ? Math.min(...leftControls.map((c) => c.y)) : 0;
|
|
7067
|
+
const rightMinY = rightControls.length > 0 ? Math.min(...rightControls.map((c) => c.y)) : 0;
|
|
7068
|
+
const xmlLineHeight = 17;
|
|
7069
|
+
const leftPadTop = leftMinY > rightMinY ? Math.round((leftMinY - rightMinY) / xmlLineHeight) : 0;
|
|
7070
|
+
const rightPadTop = rightMinY > leftMinY ? Math.round((rightMinY - leftMinY) / xmlLineHeight) : 0;
|
|
7071
|
+
const leftColTag = leftPadTop ? `:::col{padTop:${leftPadTop}}` : ":::col";
|
|
7072
|
+
lines.push(leftColTag);
|
|
7073
|
+
for (const entry of leftEntries) {
|
|
7074
|
+
lines.push(entry);
|
|
7075
|
+
}
|
|
7076
|
+
lines.push(":::");
|
|
7077
|
+
const rightColTag = rightPadTop ? `:::col{padTop:${rightPadTop}}` : ":::col";
|
|
7078
|
+
lines.push(rightColTag);
|
|
7079
|
+
if (rightEntries.length > 0) {
|
|
7080
|
+
lines.push("| | |");
|
|
7081
|
+
lines.push("|---|---|");
|
|
7082
|
+
let prevY = -Infinity;
|
|
7083
|
+
for (const entry of rightEntries) {
|
|
7084
|
+
if (prevY > -Infinity && entry.y - prevY > 25) {
|
|
7085
|
+
lines.push("| | |");
|
|
7086
|
+
}
|
|
7087
|
+
const label = entry.label || "";
|
|
7088
|
+
const colon = entry.label ? ":" : "";
|
|
7089
|
+
const value = entry.value ? ` ${entry.value}` : "";
|
|
7090
|
+
lines.push(`| ${label} | ${colon}${value} |`);
|
|
7091
|
+
prevY = entry.y;
|
|
7092
|
+
}
|
|
7093
|
+
}
|
|
7094
|
+
lines.push(":::");
|
|
7095
|
+
lines.push(":::");
|
|
7096
|
+
lines.push("");
|
|
7097
|
+
}
|
|
7098
|
+
return lines.join("\n");
|
|
7099
|
+
}
|
|
7100
|
+
function renderReportHeaderBand(controls, registry, contentWidth) {
|
|
7101
|
+
const lines = [];
|
|
7102
|
+
const leftControls = controls.filter((c) => c.x < 437);
|
|
7103
|
+
const rightControls = controls.filter((c) => c.x >= 437);
|
|
7104
|
+
const leftEntries = [];
|
|
7105
|
+
if (leftControls.length > 0) {
|
|
7106
|
+
const leftRows = groupByRow(leftControls);
|
|
7107
|
+
for (const row of leftRows) {
|
|
7108
|
+
const parts = [];
|
|
7109
|
+
for (const ctrl of row.controls) {
|
|
7110
|
+
if (isBound(ctrl)) {
|
|
7111
|
+
const resolved = resolveExpression(ctrl.expression, registry, void 0, "workorder");
|
|
7112
|
+
if (resolved) parts.push(resolved);
|
|
7113
|
+
} else if (isStaticLabel(ctrl)) {
|
|
7114
|
+
const text = ctrl.text.trim();
|
|
7115
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
7116
|
+
parts.push(text);
|
|
7117
|
+
}
|
|
7118
|
+
}
|
|
7119
|
+
}
|
|
7120
|
+
if (parts.length > 0) {
|
|
7121
|
+
leftEntries.push(parts.join(" "));
|
|
7122
|
+
}
|
|
7123
|
+
}
|
|
7124
|
+
}
|
|
7125
|
+
const rightEntries = [];
|
|
7126
|
+
if (rightControls.length > 0) {
|
|
7127
|
+
const rightRows = groupByRow(rightControls);
|
|
7128
|
+
for (const row of rightRows) {
|
|
7129
|
+
let label = "";
|
|
7130
|
+
let value = "";
|
|
7131
|
+
for (const ctrl of row.controls) {
|
|
7132
|
+
if (isStaticLabel(ctrl)) {
|
|
7133
|
+
const text = ctrl.text.trim();
|
|
7134
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
7135
|
+
label = text;
|
|
7136
|
+
}
|
|
7137
|
+
} else if (isBound(ctrl)) {
|
|
7138
|
+
value = resolveExpression(ctrl.expression, registry, void 0, "workorder");
|
|
7139
|
+
if (ctrl.formatString && /\(###\)\s*###\s*-\s*####/.test(ctrl.formatString) && value.startsWith("{{var|")) {
|
|
7140
|
+
value = value.replace("}}", "|format:phone}}");
|
|
7141
|
+
}
|
|
7142
|
+
}
|
|
7143
|
+
}
|
|
7144
|
+
if (label || value) {
|
|
7145
|
+
rightEntries.push({ label, value });
|
|
7146
|
+
}
|
|
7147
|
+
}
|
|
7148
|
+
}
|
|
7149
|
+
if (leftEntries.length > 0 || rightEntries.length > 0) {
|
|
7150
|
+
const outerSplit = computeSplitPercent(leftControls, rightControls, contentWidth);
|
|
7151
|
+
lines.push(`:::columns{split:${outerSplit}}`);
|
|
7152
|
+
lines.push(":::col");
|
|
7153
|
+
for (const entry of leftEntries) {
|
|
7154
|
+
lines.push(entry);
|
|
7155
|
+
}
|
|
7156
|
+
lines.push(":::");
|
|
7157
|
+
lines.push(":::col");
|
|
7158
|
+
lines.push("| | |");
|
|
7159
|
+
lines.push("|---|---|");
|
|
7160
|
+
for (const entry of rightEntries) {
|
|
7161
|
+
const label = entry.label || "";
|
|
7162
|
+
const value = entry.value ? ` ${entry.value}` : "";
|
|
7163
|
+
lines.push(`| ${label} |${value} |`);
|
|
7164
|
+
}
|
|
7165
|
+
lines.push(":::");
|
|
7166
|
+
lines.push(":::");
|
|
7167
|
+
lines.push("");
|
|
7168
|
+
}
|
|
7169
|
+
return lines.join("\n");
|
|
7170
|
+
}
|
|
7171
|
+
function renderOperationsSection(pageHeaderControls, detailControls, groupFooterControls, registry, contentWidth) {
|
|
7172
|
+
const lines = [];
|
|
7173
|
+
const headerTexts = [];
|
|
7174
|
+
for (const ctrl of [...pageHeaderControls].sort((a, b) => a.x - b.x)) {
|
|
7175
|
+
if (isStaticLabel(ctrl)) {
|
|
7176
|
+
const text = ctrl.text.trim();
|
|
7177
|
+
if (text) headerTexts.push(text);
|
|
7178
|
+
}
|
|
7179
|
+
}
|
|
7180
|
+
const titleText = headerTexts.join(" ");
|
|
7181
|
+
lines.push(`:::panel{title:${titleText}|headerStyle:dark|border:none}`);
|
|
7182
|
+
lines.push(":::");
|
|
7183
|
+
lines.push(":::repeat{data:operations}");
|
|
7184
|
+
const sortedDetail = [...detailControls].sort((a, b) => a.x - b.x);
|
|
7185
|
+
const detailParts = [];
|
|
7186
|
+
for (const ctrl of sortedDetail) {
|
|
7187
|
+
if (isBound(ctrl)) {
|
|
7188
|
+
const resolved = resolveExpression(ctrl.expression, registry, void 0, "operation");
|
|
7189
|
+
if (resolved) detailParts.push(resolved);
|
|
7190
|
+
}
|
|
7191
|
+
}
|
|
7192
|
+
if (detailParts.length > 0) {
|
|
7193
|
+
if (detailParts.length >= 2) {
|
|
7194
|
+
lines.push(":::columns{split:12}");
|
|
7195
|
+
lines.push(":::col");
|
|
7196
|
+
lines.push(`**${detailParts[0]}**`);
|
|
7197
|
+
lines.push(":::");
|
|
7198
|
+
lines.push(":::col");
|
|
7199
|
+
lines.push(`**${detailParts.slice(1).join(" ")}**`);
|
|
7200
|
+
lines.push(":::");
|
|
7201
|
+
lines.push(":::");
|
|
7202
|
+
} else {
|
|
7203
|
+
lines.push(`**${detailParts[0]}**`);
|
|
7204
|
+
}
|
|
7205
|
+
lines.push("");
|
|
7206
|
+
}
|
|
7207
|
+
if (groupFooterControls.length > 0) {
|
|
7208
|
+
const subtotalsMarkdown = renderGroupFooterBand(groupFooterControls, registry);
|
|
7209
|
+
if (subtotalsMarkdown) lines.push(subtotalsMarkdown);
|
|
7210
|
+
}
|
|
7211
|
+
lines.push("---");
|
|
7212
|
+
lines.push(":::");
|
|
7213
|
+
lines.push("");
|
|
7214
|
+
return lines.join("\n");
|
|
7215
|
+
}
|
|
7216
|
+
function renderGroupFooterBand(controls, registry, _contentWidth) {
|
|
7217
|
+
const rows = groupByRow(controls);
|
|
7218
|
+
const entries = [];
|
|
7219
|
+
for (const row of rows) {
|
|
7220
|
+
const labels = row.controls.filter((c) => c.type === "XRLabel");
|
|
7221
|
+
if (labels.length === 0) continue;
|
|
7222
|
+
let label = "";
|
|
7223
|
+
let value = "";
|
|
7224
|
+
const sorted = [...labels].sort((a, b) => a.x - b.x);
|
|
7225
|
+
for (const ctrl of sorted) {
|
|
7226
|
+
if (isStaticLabel(ctrl)) {
|
|
7227
|
+
const text = ctrl.text.trim();
|
|
7228
|
+
if (text && !text.match(/^label\d+$/)) {
|
|
7229
|
+
label = text;
|
|
7230
|
+
}
|
|
7231
|
+
} else if (isBound(ctrl)) {
|
|
7232
|
+
const resolved = resolveExpression(ctrl.expression, registry, label, "operation");
|
|
7233
|
+
if (resolved) {
|
|
7234
|
+
if (!resolved.startsWith("{{var|")) {
|
|
7235
|
+
if (!label) label = resolved;
|
|
7236
|
+
} else {
|
|
7237
|
+
value = resolved;
|
|
7238
|
+
}
|
|
7239
|
+
}
|
|
7240
|
+
}
|
|
7241
|
+
}
|
|
7242
|
+
if (label && value) {
|
|
7243
|
+
entries.push({ label, value });
|
|
7244
|
+
}
|
|
7245
|
+
}
|
|
7246
|
+
if (entries.length === 0) return "";
|
|
7247
|
+
const lines = [];
|
|
7248
|
+
lines.push(":::subtotals");
|
|
7249
|
+
for (const entry of entries) {
|
|
7250
|
+
lines.push(`**${entry.label}** ${entry.value}`);
|
|
7251
|
+
}
|
|
7252
|
+
lines.push(":::");
|
|
7253
|
+
lines.push("");
|
|
7254
|
+
return lines.join("\n");
|
|
7255
|
+
}
|
|
7256
|
+
function renderReportFooterBand(controls, registry) {
|
|
7257
|
+
const lines = [];
|
|
7258
|
+
const panel = controls.find((c) => c.type === "XRPanel");
|
|
7259
|
+
const thankYouLabel = controls.find(
|
|
7260
|
+
(c) => c.type === "XRLabel" && c.text.includes("Thank You")
|
|
7261
|
+
);
|
|
7262
|
+
const priceRows = [];
|
|
7263
|
+
if (panel && panel.children.length > 0) {
|
|
7264
|
+
const rows = groupByRow(panel.children, 15);
|
|
7265
|
+
for (const row of rows) {
|
|
7266
|
+
const labels = row.controls.filter((c) => c.type === "XRLabel");
|
|
7267
|
+
if (labels.length === 0) continue;
|
|
7268
|
+
let label = "";
|
|
7269
|
+
let value = "";
|
|
7270
|
+
const sorted = [...labels].sort((a, b) => a.x - b.x);
|
|
7271
|
+
for (const ctrl of sorted) {
|
|
7272
|
+
if (isStaticLabel(ctrl) && ctrl.text.includes("Estimate Price Summary")) {
|
|
7273
|
+
continue;
|
|
7274
|
+
}
|
|
7275
|
+
const isLabelPos = ctrl.x < 150;
|
|
7276
|
+
if (isStaticLabel(ctrl)) {
|
|
7277
|
+
const text = ctrl.text.trim();
|
|
7278
|
+
if (isLabelPos && text && !text.match(/^label\d+$/) && text !== "0.00") {
|
|
7279
|
+
label = text;
|
|
7280
|
+
}
|
|
7281
|
+
} else if (isBound(ctrl)) {
|
|
7282
|
+
const resolved = resolveExpression(ctrl.expression, registry, label || void 0, "workorder");
|
|
7283
|
+
if (resolved) {
|
|
7284
|
+
if (isLabelPos) {
|
|
7285
|
+
if (!label) label = resolved;
|
|
7286
|
+
} else {
|
|
7287
|
+
if (!value) value = resolved;
|
|
7288
|
+
}
|
|
7289
|
+
}
|
|
7290
|
+
}
|
|
7291
|
+
}
|
|
7292
|
+
if (label && value) {
|
|
7293
|
+
priceRows.push({ label, value });
|
|
7294
|
+
} else if (value) {
|
|
7295
|
+
priceRows.push({ label: "", value });
|
|
7296
|
+
}
|
|
7297
|
+
}
|
|
7298
|
+
}
|
|
7299
|
+
lines.push(":::columns{split:40|padX:20}");
|
|
7300
|
+
lines.push(":::col{padTop:1}");
|
|
7301
|
+
if (thankYouLabel) {
|
|
7302
|
+
lines.push(`## ***${thankYouLabel.text.trim()}***`);
|
|
7303
|
+
}
|
|
7304
|
+
lines.push(":::");
|
|
7305
|
+
lines.push(":::col{padTop:1}");
|
|
7306
|
+
lines.push(":::panel{title:Estimate Price Summary|border:solid|headerStyle:dark}");
|
|
7307
|
+
if (priceRows.length > 0) {
|
|
7308
|
+
lines.push(":::subtotals");
|
|
7309
|
+
for (const row of priceRows) {
|
|
7310
|
+
const label = row.label ? `**${row.label}**` : "";
|
|
7311
|
+
lines.push(`${label} ${row.value}`);
|
|
7312
|
+
}
|
|
7313
|
+
lines.push(":::");
|
|
7314
|
+
}
|
|
7315
|
+
lines.push(":::");
|
|
7316
|
+
lines.push(":::");
|
|
7317
|
+
lines.push(":::");
|
|
7318
|
+
lines.push("");
|
|
7319
|
+
return lines.join("\n");
|
|
7320
|
+
}
|
|
7321
|
+
function parseXmlTemplate(xmlString) {
|
|
7322
|
+
const warnings = [];
|
|
7323
|
+
const registry = new VariableRegistry();
|
|
7324
|
+
const parser = new DOMParser();
|
|
7325
|
+
const doc = parser.parseFromString(xmlString, "text/xml");
|
|
7326
|
+
const parseError = doc.querySelector("parsererror");
|
|
7327
|
+
if (parseError) {
|
|
7328
|
+
return {
|
|
7329
|
+
markdown: "",
|
|
7330
|
+
variables: [],
|
|
7331
|
+
reportName: "",
|
|
7332
|
+
warnings: ["XML parse error: " + (parseError.textContent || "Unknown error")]
|
|
7333
|
+
};
|
|
7334
|
+
}
|
|
7335
|
+
const root = doc.documentElement;
|
|
7336
|
+
const reportName = root.getAttribute("DisplayName") || root.getAttribute("Name") || "Report";
|
|
7337
|
+
const pageWidth = parseInt(root.getAttribute("PageWidth") || "850", 10);
|
|
7338
|
+
const marginsStr = root.getAttribute("Margins") || "50,50,250,50";
|
|
7339
|
+
const marginParts = marginsStr.split(",").map(Number);
|
|
7340
|
+
const leftMargin = marginParts[0] || 50;
|
|
7341
|
+
const rightMargin = marginParts[1] || 50;
|
|
7342
|
+
const contentWidth = pageWidth - leftMargin - rightMargin;
|
|
7343
|
+
const calculatedFieldNames = /* @__PURE__ */ new Set();
|
|
7344
|
+
const calcFieldsEl = root.querySelector(":scope > CalculatedFields");
|
|
7345
|
+
if (calcFieldsEl) {
|
|
7346
|
+
for (const item of Array.from(calcFieldsEl.children)) {
|
|
7347
|
+
const name = item.getAttribute("Name");
|
|
7348
|
+
if (name) calculatedFieldNames.add(name);
|
|
7349
|
+
}
|
|
7350
|
+
}
|
|
7351
|
+
const markdownSections = [];
|
|
7352
|
+
const watermarkEl = root.querySelector("Watermarks > Item1");
|
|
7353
|
+
if (watermarkEl) {
|
|
7354
|
+
const wmText = watermarkEl.getAttribute("Text");
|
|
7355
|
+
if (wmText) {
|
|
7356
|
+
markdownSections.push(`:::watermark{text:${wmText}|opacity:0.15|angle:45}`);
|
|
7357
|
+
markdownSections.push("");
|
|
7358
|
+
}
|
|
7359
|
+
}
|
|
7360
|
+
const bandsEl = root.querySelector(":scope > Bands");
|
|
7361
|
+
if (!bandsEl) {
|
|
7362
|
+
warnings.push("No <Bands> element found in the XML");
|
|
7363
|
+
return {
|
|
7364
|
+
markdown: "",
|
|
7365
|
+
variables: registry.getAll(),
|
|
7366
|
+
reportName,
|
|
7367
|
+
warnings
|
|
7368
|
+
};
|
|
7369
|
+
}
|
|
7370
|
+
let topMarginControls = [];
|
|
7371
|
+
let reportHeaderControls = [];
|
|
7372
|
+
let pageHeaderControls = [];
|
|
7373
|
+
let detailControls = [];
|
|
7374
|
+
let groupFooterControls = [];
|
|
7375
|
+
let reportFooterControls = [];
|
|
7376
|
+
for (const bandItem of Array.from(bandsEl.children)) {
|
|
7377
|
+
const ctrlType = bandItem.getAttribute("ControlType") || "";
|
|
7378
|
+
if (ctrlType === "TopMarginBand") {
|
|
7379
|
+
topMarginControls = extractControls(bandItem);
|
|
7380
|
+
} else if (ctrlType === "ReportHeaderBand") {
|
|
7381
|
+
reportHeaderControls = extractControls(bandItem);
|
|
7382
|
+
} else if (ctrlType === "PageHeaderBand") {
|
|
7383
|
+
pageHeaderControls = extractControls(bandItem);
|
|
7384
|
+
} else if (ctrlType === "DetailReportBand") {
|
|
7385
|
+
const nestedBands = bandItem.querySelector(":scope > Bands");
|
|
7386
|
+
if (nestedBands) {
|
|
7387
|
+
for (const nested of Array.from(nestedBands.children)) {
|
|
7388
|
+
const nestedType = nested.getAttribute("ControlType") || "";
|
|
7389
|
+
if (nestedType === "DetailBand") {
|
|
7390
|
+
detailControls = extractControls(nested);
|
|
7391
|
+
} else if (nestedType === "GroupFooterBand") {
|
|
7392
|
+
groupFooterControls = extractControls(nested);
|
|
7393
|
+
}
|
|
7394
|
+
}
|
|
7395
|
+
}
|
|
7396
|
+
} else if (ctrlType === "ReportFooterBand") {
|
|
7397
|
+
reportFooterControls = extractControls(bandItem);
|
|
7398
|
+
}
|
|
7399
|
+
}
|
|
7400
|
+
if (topMarginControls.length > 0) {
|
|
7401
|
+
markdownSections.push(renderTopMarginBand(topMarginControls, registry, contentWidth, calculatedFieldNames));
|
|
7402
|
+
}
|
|
7403
|
+
if (reportHeaderControls.length > 0) {
|
|
7404
|
+
markdownSections.push(
|
|
7405
|
+
renderReportHeaderBand(reportHeaderControls, registry, contentWidth)
|
|
7406
|
+
);
|
|
7407
|
+
}
|
|
7408
|
+
if (pageHeaderControls.length > 0 || detailControls.length > 0) {
|
|
7409
|
+
markdownSections.push(
|
|
7410
|
+
renderOperationsSection(
|
|
7411
|
+
pageHeaderControls,
|
|
7412
|
+
detailControls,
|
|
7413
|
+
groupFooterControls,
|
|
7414
|
+
registry)
|
|
7415
|
+
);
|
|
7416
|
+
}
|
|
7417
|
+
if (reportFooterControls.length > 0) {
|
|
7418
|
+
markdownSections.push(
|
|
7419
|
+
renderReportFooterBand(reportFooterControls, registry)
|
|
7420
|
+
);
|
|
7421
|
+
}
|
|
7422
|
+
const markdown = markdownSections.join("\n").replace(/\n{4,}/g, "\n\n\n");
|
|
7423
|
+
return {
|
|
7424
|
+
markdown,
|
|
7425
|
+
variables: registry.getAll(),
|
|
7426
|
+
reportName,
|
|
7427
|
+
warnings
|
|
7428
|
+
};
|
|
7429
|
+
}
|
|
7430
|
+
|
|
7431
|
+
export { DocumentGenerator, EditorPanel, FormFieldType, PreviewPanel, RepeatNode, SubtotalsNode, ThemeProvider, expandBlockVariables, expandRepeatContent, extractVariablesFromContent, generatePdfFromMarkdown, generatePdfFromTiptap, isZeroLike, markdownToTiptap, parseXmlTemplate, suppressZeroContent, tiptapToMarkdown, useDocumentGenerator, useTheme };
|
|
4929
7432
|
//# sourceMappingURL=index.mjs.map
|
|
4930
7433
|
//# sourceMappingURL=index.mjs.map
|