@signiphi/pdf-compose 0.1.0-beta.8 → 0.1.0-beta.9
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 +12 -5
- package/dist/components/GeneratePanel.d.ts.map +1 -1
- package/dist/extensions/variable-node.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +242 -186
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +242 -187
- package/dist/index.mjs.map +1 -1
- package/dist/utils/markdown-parser.d.ts.map +1 -1
- package/dist/utils/markdown-writer.d.ts.map +1 -1
- package/dist/utils/template-pipeline.d.ts +2 -1
- package/dist/utils/template-pipeline.d.ts.map +1 -1
- package/dist/utils/variable-helpers.d.ts +28 -0
- package/dist/utils/variable-helpers.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -112,14 +112,21 @@ Fields and variables are embedded as inline tokens in the markdown content:
|
|
|
112
112
|
{{var|name:company_name|label:Company Name|default:Acme Corp}}
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
| Attribute
|
|
116
|
-
|
|
|
117
|
-
| `name`
|
|
118
|
-
| `label`
|
|
119
|
-
| `default`
|
|
115
|
+
| Attribute | Description |
|
|
116
|
+
| ---------- | --------------------- |
|
|
117
|
+
| `name` | Variable identifier |
|
|
118
|
+
| `label` | Display label |
|
|
119
|
+
| `default` | Default value |
|
|
120
|
+
| `suppress` | `zero` — remove the containing row/paragraph when the value is zero-like |
|
|
121
|
+
| `format` | Named value format (e.g. `phone`) |
|
|
122
|
+
| `block` | `true` — parse the value as extended markdown and splice it in as block content (headings, tables, lists) |
|
|
120
123
|
|
|
121
124
|
Variables are replaced with actual values at PDF generation time. In bulk mode, each row of data produces a separate PDF.
|
|
122
125
|
|
|
126
|
+
#### Block variables
|
|
127
|
+
|
|
128
|
+
A variable that is the **sole content of its paragraph** can expand into full block content: the resolved value is parsed as extended markdown and the resulting blocks (headings, tables, lists, panels) replace the paragraph. This happens when the token carries `block:true`, or automatically when the resolved value contains a newline. An empty block value removes the paragraph entirely, so optional sections collapse without leaving a gap. Variables mixed inline with other text always substitute as plain text.
|
|
129
|
+
|
|
123
130
|
## Advanced Usage
|
|
124
131
|
|
|
125
132
|
### Using the Hook Directly
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GeneratePanel.d.ts","sourceRoot":"","sources":["../../src/components/GeneratePanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,KAAK,EAAE,yBAAyB,EAA0E,MAAM,UAAU,CAAC;AAelI,UAAU,kBAAkB;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,WAAW,CAAC;IAC5B,eAAe,EAAE,yBAAyB,EAAE,CAAC;IAC7C,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACvD,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IAC3C,qBAAqB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AA4DD,wBAAgB,aAAa,CAAC,EAC5B,cAAc,EACd,aAAa,EACb,eAAe,EACf,aAAa,EACb,eAAe,EACf,qBAAqB,EACrB,cAAc,EACd,YAAY,GACb,EAAE,kBAAkB,
|
|
1
|
+
{"version":3,"file":"GeneratePanel.d.ts","sourceRoot":"","sources":["../../src/components/GeneratePanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,KAAK,EAAE,yBAAyB,EAA0E,MAAM,UAAU,CAAC;AAelI,UAAU,kBAAkB;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,WAAW,CAAC;IAC5B,eAAe,EAAE,yBAAyB,EAAE,CAAC;IAC7C,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACvD,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IAC3C,qBAAqB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AA4DD,wBAAgB,aAAa,CAAC,EAC5B,cAAc,EACd,aAAa,EACb,eAAe,EACf,aAAa,EACb,eAAe,EACf,qBAAqB,EACrB,cAAc,EACd,YAAY,GACb,EAAE,kBAAkB,2CAg/BpB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"variable-node.d.ts","sourceRoot":"","sources":["../../src/extensions/variable-node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,cAAc,CAAC;AAIrD,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,QAAQ,CAAC,UAAU;QAC3B,YAAY,EAAE;YACZ,cAAc,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,UAAU,CAAC;YAC9D,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,sBAAsB,CAAC,KAAK,UAAU,CAAC;YACxF,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,UAAU,CAAC;SACjD,CAAC;KACH;CACF;AAED,eAAO,MAAM,YAAY,
|
|
1
|
+
{"version":3,"file":"variable-node.d.ts","sourceRoot":"","sources":["../../src/extensions/variable-node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,cAAc,CAAC;AAIrD,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,QAAQ,CAAC,UAAU;QAC3B,YAAY,EAAE;YACZ,cAAc,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,UAAU,CAAC;YAC9D,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,sBAAsB,CAAC,KAAK,UAAU,CAAC;YACxF,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,UAAU,CAAC;SACjD,CAAC;KACH;CACF;AAED,eAAO,MAAM,YAAY,gBAiGvB,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { PreviewPanel } from './components/PreviewPanel';
|
|
|
4
4
|
export { useDocumentGenerator } from './hooks/useDocumentGenerator';
|
|
5
5
|
export type { Acknowledgement, DocumentGeneratorField, DocumentGeneratorVariable, DocumentTemplate, VariableValues, GeneratedDocument, DocumentGeneratorExportResult, FormFieldForExport, DocumentGeneratorProps, PredefinedVariable, ThemeMode, ThemeConfig, PdfPage, } from './types';
|
|
6
6
|
export { FormFieldType } from './types';
|
|
7
|
-
export { suppressZeroContent, isZeroLike, expandRepeatContent, extractVariablesFromContent } from './utils/variable-helpers';
|
|
7
|
+
export { suppressZeroContent, isZeroLike, expandRepeatContent, expandBlockVariables, extractVariablesFromContent } from './utils/variable-helpers';
|
|
8
8
|
export { generatePdfFromMarkdown, generatePdfFromTiptap } from './utils/template-pipeline';
|
|
9
9
|
export type { HeadlessPdfResult, HeadlessPdfOptions } from './utils/template-pipeline';
|
|
10
10
|
export type { PdfTheme, PdfPageHeader, PdfPageFooter } from './utils/pdf-generator';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAGnE,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAGzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAGpE,YAAY,EACV,eAAe,EACf,sBAAsB,EACtB,yBAAyB,EACzB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,6BAA6B,EAC7B,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,SAAS,EACT,WAAW,EACX,OAAO,GACR,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAGxC,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAGnE,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAGzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAGpE,YAAY,EACV,eAAe,EACf,sBAAsB,EACtB,yBAAyB,EACzB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,6BAA6B,EAC7B,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,SAAS,EACT,WAAW,EACX,OAAO,GACR,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAGxC,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAGnJ,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAC3F,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACvF,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAGpF,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAG3D,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAG5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,YAAY,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAG1E,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAClE,YAAY,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -253,7 +253,8 @@ var VariableNode = core.Node.create({
|
|
|
253
253
|
varLabel: { default: "" },
|
|
254
254
|
varDefault: { default: "" },
|
|
255
255
|
format: { default: "" },
|
|
256
|
-
suppressZero: { default: "" }
|
|
256
|
+
suppressZero: { default: "" },
|
|
257
|
+
block: { default: "" }
|
|
257
258
|
};
|
|
258
259
|
},
|
|
259
260
|
parseHTML() {
|
|
@@ -560,187 +561,6 @@ function extractFieldsFromContent(content) {
|
|
|
560
561
|
}
|
|
561
562
|
return fields;
|
|
562
563
|
}
|
|
563
|
-
|
|
564
|
-
// src/utils/variable-helpers.ts
|
|
565
|
-
function extractVariablesFromContent(content) {
|
|
566
|
-
const seen = /* @__PURE__ */ new Map();
|
|
567
|
-
function walk(node) {
|
|
568
|
-
if (node.type === "variableNode" && node.attrs) {
|
|
569
|
-
const varName = node.attrs.varName;
|
|
570
|
-
if (varName && !seen.has(varName)) {
|
|
571
|
-
seen.set(varName, {
|
|
572
|
-
varName,
|
|
573
|
-
varLabel: node.attrs.varLabel || varName,
|
|
574
|
-
varDefault: node.attrs.varDefault || ""
|
|
575
|
-
});
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
if (node.content) {
|
|
579
|
-
node.content.forEach(walk);
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
if (content.content) {
|
|
583
|
-
content.content.forEach(walk);
|
|
584
|
-
}
|
|
585
|
-
return Array.from(seen.values());
|
|
586
|
-
}
|
|
587
|
-
function applyFormat(value, format) {
|
|
588
|
-
if (format === "phone") {
|
|
589
|
-
const digits = value.replace(/\D/g, "");
|
|
590
|
-
if (digits.length === 10) {
|
|
591
|
-
return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)} - ${digits.slice(6)}`;
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
return value;
|
|
595
|
-
}
|
|
596
|
-
function replaceVariablesInContent(content, values) {
|
|
597
|
-
function walkNode(node) {
|
|
598
|
-
if (node.type === "variableNode" && node.attrs) {
|
|
599
|
-
const varName = node.attrs.varName;
|
|
600
|
-
let replacement = values[varName] || node.attrs.varDefault || "";
|
|
601
|
-
const format = node.attrs.format;
|
|
602
|
-
if (format && replacement) {
|
|
603
|
-
replacement = applyFormat(replacement, format);
|
|
604
|
-
}
|
|
605
|
-
const textNode = { type: "text", text: replacement };
|
|
606
|
-
if (node.marks && node.marks.length > 0) {
|
|
607
|
-
textNode.marks = node.marks;
|
|
608
|
-
}
|
|
609
|
-
return textNode;
|
|
610
|
-
}
|
|
611
|
-
if (node.type === "imageNode" && node.attrs?.varName) {
|
|
612
|
-
const varName = node.attrs.varName;
|
|
613
|
-
const resolved = values[varName] || node.attrs.src || "";
|
|
614
|
-
return { ...node, attrs: { ...node.attrs, src: resolved } };
|
|
615
|
-
}
|
|
616
|
-
if (node.content) {
|
|
617
|
-
return { ...node, content: node.content.map(walkNode) };
|
|
618
|
-
}
|
|
619
|
-
return node;
|
|
620
|
-
}
|
|
621
|
-
return walkNode(content);
|
|
622
|
-
}
|
|
623
|
-
function labelToVarName(label) {
|
|
624
|
-
return label.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
625
|
-
}
|
|
626
|
-
function isZeroLike(value) {
|
|
627
|
-
if (!value || value.trim() === "") return true;
|
|
628
|
-
const cleaned = value.replace(/[$,\s]/g, "");
|
|
629
|
-
return /^-?0+(\.0+)?$/.test(cleaned);
|
|
630
|
-
}
|
|
631
|
-
function suppressZeroContent(content, values) {
|
|
632
|
-
function hasSuppressedVar(node) {
|
|
633
|
-
if (node.type === "variableNode" && node.attrs?.suppressZero === "true") {
|
|
634
|
-
const varName = node.attrs.varName;
|
|
635
|
-
const val = values[varName] || node.attrs.varDefault || "";
|
|
636
|
-
return isZeroLike(val);
|
|
637
|
-
}
|
|
638
|
-
return (node.content || []).some(hasSuppressedVar);
|
|
639
|
-
}
|
|
640
|
-
function walkNode(node) {
|
|
641
|
-
if (node.type === "table" && node.content) {
|
|
642
|
-
const filtered = node.content.filter((row) => {
|
|
643
|
-
if (row.type !== "tableRow") return true;
|
|
644
|
-
const isHeader = row.content?.[0]?.type === "tableHeader";
|
|
645
|
-
if (isHeader) return true;
|
|
646
|
-
return !hasSuppressedVar(row);
|
|
647
|
-
});
|
|
648
|
-
const bodyRows = filtered.filter(
|
|
649
|
-
(r) => r.type === "tableRow" && r.content?.[0]?.type !== "tableHeader"
|
|
650
|
-
);
|
|
651
|
-
if (bodyRows.length === 0) return null;
|
|
652
|
-
return { ...node, content: filtered };
|
|
653
|
-
}
|
|
654
|
-
if (node.type === "paragraph" && hasSuppressedVar(node)) {
|
|
655
|
-
return null;
|
|
656
|
-
}
|
|
657
|
-
if (node.content) {
|
|
658
|
-
const filtered = node.content.map((child) => walkNode(child)).filter((c) => c !== null);
|
|
659
|
-
return { ...node, content: filtered };
|
|
660
|
-
}
|
|
661
|
-
return node;
|
|
662
|
-
}
|
|
663
|
-
return walkNode(content) || content;
|
|
664
|
-
}
|
|
665
|
-
function expandRepeatContent(content, values) {
|
|
666
|
-
const enrichedValues = { ...values };
|
|
667
|
-
function cloneNode(node, dataKey, index) {
|
|
668
|
-
if (node.type === "variableNode" && node.attrs?.varName) {
|
|
669
|
-
const oldName = node.attrs.varName;
|
|
670
|
-
const bareName = oldName.replace(/^operation_/, "");
|
|
671
|
-
const newName = `${dataKey}_${index}_${bareName}`;
|
|
672
|
-
return { ...node, attrs: { ...node.attrs, varName: newName } };
|
|
673
|
-
}
|
|
674
|
-
if (node.content) {
|
|
675
|
-
return { ...node, content: node.content.map((n) => cloneNode(n, dataKey, index)) };
|
|
676
|
-
}
|
|
677
|
-
return { ...node };
|
|
678
|
-
}
|
|
679
|
-
function walkNode(node) {
|
|
680
|
-
if (node.type === "repeatBlock" && node.attrs?.data) {
|
|
681
|
-
const dataKey = node.attrs.data;
|
|
682
|
-
const arrayJson = values[dataKey];
|
|
683
|
-
if (!arrayJson) return [];
|
|
684
|
-
let items;
|
|
685
|
-
try {
|
|
686
|
-
items = JSON.parse(arrayJson);
|
|
687
|
-
} catch {
|
|
688
|
-
return [];
|
|
689
|
-
}
|
|
690
|
-
const sums = /* @__PURE__ */ new Map();
|
|
691
|
-
for (const item of items) {
|
|
692
|
-
for (const [key, val] of Object.entries(item)) {
|
|
693
|
-
const num = parseFloat(val);
|
|
694
|
-
if (!isNaN(num)) {
|
|
695
|
-
sums.set(key, (sums.get(key) || 0) + num);
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
const woKeysByNorm = /* @__PURE__ */ new Map();
|
|
700
|
-
for (const k of Object.keys(enrichedValues)) {
|
|
701
|
-
if (k.startsWith("workorder_")) {
|
|
702
|
-
const norm = k.replace(/_/g, "");
|
|
703
|
-
woKeysByNorm.set(norm, k);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
for (const [field, total] of sums) {
|
|
707
|
-
const formatted = total % 1 === 0 ? total.toFixed(2) : total.toFixed(2);
|
|
708
|
-
const directKey = `workorder_${field}`;
|
|
709
|
-
enrichedValues[directKey] = formatted;
|
|
710
|
-
const norm = `workorder${field}`.replace(/_/g, "");
|
|
711
|
-
const existing = woKeysByNorm.get(norm);
|
|
712
|
-
if (existing && existing !== directKey) {
|
|
713
|
-
enrichedValues[existing] = formatted;
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
const expanded = [];
|
|
717
|
-
for (let i = 0; i < items.length; i++) {
|
|
718
|
-
const item = items[i];
|
|
719
|
-
for (const [key, val] of Object.entries(item)) {
|
|
720
|
-
enrichedValues[`${dataKey}_${i}_${key}`] = String(val);
|
|
721
|
-
}
|
|
722
|
-
const cloned = (node.content || []).map((n) => cloneNode(n, dataKey, i));
|
|
723
|
-
expanded.push(...cloned);
|
|
724
|
-
}
|
|
725
|
-
return expanded;
|
|
726
|
-
}
|
|
727
|
-
if (node.content) {
|
|
728
|
-
const newContent = [];
|
|
729
|
-
for (const child of node.content) {
|
|
730
|
-
const result2 = walkNode(child);
|
|
731
|
-
if (Array.isArray(result2)) newContent.push(...result2);
|
|
732
|
-
else newContent.push(result2);
|
|
733
|
-
}
|
|
734
|
-
return { ...node, content: newContent };
|
|
735
|
-
}
|
|
736
|
-
return node;
|
|
737
|
-
}
|
|
738
|
-
const result = walkNode(content);
|
|
739
|
-
return {
|
|
740
|
-
content: Array.isArray(result) ? { ...content, content: result } : result,
|
|
741
|
-
values: enrichedValues
|
|
742
|
-
};
|
|
743
|
-
}
|
|
744
564
|
var TOKEN_REGEX = /\{\{(field|var|image)\|([^}]+)\}\}/g;
|
|
745
565
|
var BLOCK_IMAGE_REGEX = /^!\[([^\]]*)\]\(([^)\s]+)\)$/;
|
|
746
566
|
var DIRECTIVE_OPEN = /^:::(panel|columns|col|watermark|repeat|subtotals)(?:\{([^}]*)\})?$/;
|
|
@@ -880,6 +700,9 @@ function parseVariableToken(tokenBody) {
|
|
|
880
700
|
case "format":
|
|
881
701
|
attrs.format = value;
|
|
882
702
|
break;
|
|
703
|
+
case "block":
|
|
704
|
+
attrs.block = value === "true" ? "true" : "";
|
|
705
|
+
break;
|
|
883
706
|
case "_marks":
|
|
884
707
|
attrs._marks = value;
|
|
885
708
|
break;
|
|
@@ -1291,6 +1114,233 @@ function markdownToTiptap(markdown) {
|
|
|
1291
1114
|
return { type: "doc", content: blocks };
|
|
1292
1115
|
}
|
|
1293
1116
|
|
|
1117
|
+
// src/utils/variable-helpers.ts
|
|
1118
|
+
function extractVariablesFromContent(content) {
|
|
1119
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1120
|
+
function walk(node) {
|
|
1121
|
+
if (node.type === "variableNode" && node.attrs) {
|
|
1122
|
+
const varName = node.attrs.varName;
|
|
1123
|
+
if (varName && !seen.has(varName)) {
|
|
1124
|
+
seen.set(varName, {
|
|
1125
|
+
varName,
|
|
1126
|
+
varLabel: node.attrs.varLabel || varName,
|
|
1127
|
+
varDefault: node.attrs.varDefault || ""
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
if (node.content) {
|
|
1132
|
+
node.content.forEach(walk);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
if (content.content) {
|
|
1136
|
+
content.content.forEach(walk);
|
|
1137
|
+
}
|
|
1138
|
+
return Array.from(seen.values());
|
|
1139
|
+
}
|
|
1140
|
+
function applyFormat(value, format) {
|
|
1141
|
+
if (format === "phone") {
|
|
1142
|
+
const digits = value.replace(/\D/g, "");
|
|
1143
|
+
if (digits.length === 10) {
|
|
1144
|
+
return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)} - ${digits.slice(6)}`;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
return value;
|
|
1148
|
+
}
|
|
1149
|
+
function replaceVariablesInContent(content, values) {
|
|
1150
|
+
function walkNode(node) {
|
|
1151
|
+
if (node.type === "variableNode" && node.attrs) {
|
|
1152
|
+
const varName = node.attrs.varName;
|
|
1153
|
+
let replacement = values[varName] || node.attrs.varDefault || "";
|
|
1154
|
+
const format = node.attrs.format;
|
|
1155
|
+
if (format && replacement) {
|
|
1156
|
+
replacement = applyFormat(replacement, format);
|
|
1157
|
+
}
|
|
1158
|
+
const textNode = { type: "text", text: replacement };
|
|
1159
|
+
if (node.marks && node.marks.length > 0) {
|
|
1160
|
+
textNode.marks = node.marks;
|
|
1161
|
+
}
|
|
1162
|
+
return textNode;
|
|
1163
|
+
}
|
|
1164
|
+
if (node.type === "imageNode" && node.attrs?.varName) {
|
|
1165
|
+
const varName = node.attrs.varName;
|
|
1166
|
+
const resolved = values[varName] || node.attrs.src || "";
|
|
1167
|
+
return { ...node, attrs: { ...node.attrs, src: resolved } };
|
|
1168
|
+
}
|
|
1169
|
+
if (node.content) {
|
|
1170
|
+
return { ...node, content: node.content.map(walkNode) };
|
|
1171
|
+
}
|
|
1172
|
+
return node;
|
|
1173
|
+
}
|
|
1174
|
+
return walkNode(content);
|
|
1175
|
+
}
|
|
1176
|
+
function expandBlockVariables(content, values) {
|
|
1177
|
+
function soleVariableOf(node) {
|
|
1178
|
+
if (node.type !== "paragraph" || !node.content) return null;
|
|
1179
|
+
const meaningful = node.content.filter(
|
|
1180
|
+
(c) => !(c.type === "text" && !(c.text || "").trim())
|
|
1181
|
+
);
|
|
1182
|
+
if (meaningful.length === 1 && meaningful[0].type === "variableNode") {
|
|
1183
|
+
return meaningful[0];
|
|
1184
|
+
}
|
|
1185
|
+
return null;
|
|
1186
|
+
}
|
|
1187
|
+
function stripUnsafeNodes(node) {
|
|
1188
|
+
if (!node.content) return node;
|
|
1189
|
+
return {
|
|
1190
|
+
...node,
|
|
1191
|
+
content: node.content.filter((c) => c.type !== "fieldNode" && c.type !== "watermark").map(stripUnsafeNodes)
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
function isCellSafe(blocks) {
|
|
1195
|
+
return blocks.every((b) => b.type === "paragraph" || b.type === "heading");
|
|
1196
|
+
}
|
|
1197
|
+
function walkNode(node, inCell) {
|
|
1198
|
+
if (!node.content) return node;
|
|
1199
|
+
const childInCell = inCell || node.type === "tableCell" || node.type === "tableHeader";
|
|
1200
|
+
const newContent = [];
|
|
1201
|
+
for (const child of node.content) {
|
|
1202
|
+
const varNode = soleVariableOf(child);
|
|
1203
|
+
if (varNode?.attrs) {
|
|
1204
|
+
const varName = varNode.attrs.varName;
|
|
1205
|
+
const value = values[varName] || varNode.attrs.varDefault || "";
|
|
1206
|
+
const isBlock = varNode.attrs.block === "true" || value.includes("\n");
|
|
1207
|
+
if (isBlock) {
|
|
1208
|
+
const parsed = stripUnsafeNodes(markdownToTiptap(value));
|
|
1209
|
+
const blocks = parsed.content || [];
|
|
1210
|
+
if (!childInCell || isCellSafe(blocks)) {
|
|
1211
|
+
newContent.push(...blocks);
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
newContent.push(walkNode(child, childInCell));
|
|
1217
|
+
}
|
|
1218
|
+
return { ...node, content: newContent };
|
|
1219
|
+
}
|
|
1220
|
+
return walkNode(content, false);
|
|
1221
|
+
}
|
|
1222
|
+
function labelToVarName(label) {
|
|
1223
|
+
return label.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
1224
|
+
}
|
|
1225
|
+
function isZeroLike(value) {
|
|
1226
|
+
if (!value || value.trim() === "") return true;
|
|
1227
|
+
const cleaned = value.replace(/[$,\s]/g, "");
|
|
1228
|
+
return /^-?0+(\.0+)?$/.test(cleaned);
|
|
1229
|
+
}
|
|
1230
|
+
function suppressZeroContent(content, values) {
|
|
1231
|
+
function hasSuppressedVar(node) {
|
|
1232
|
+
if (node.type === "variableNode" && node.attrs?.suppressZero === "true") {
|
|
1233
|
+
const varName = node.attrs.varName;
|
|
1234
|
+
const val = values[varName] || node.attrs.varDefault || "";
|
|
1235
|
+
return isZeroLike(val);
|
|
1236
|
+
}
|
|
1237
|
+
return (node.content || []).some(hasSuppressedVar);
|
|
1238
|
+
}
|
|
1239
|
+
function walkNode(node) {
|
|
1240
|
+
if (node.type === "table" && node.content) {
|
|
1241
|
+
const filtered = node.content.filter((row) => {
|
|
1242
|
+
if (row.type !== "tableRow") return true;
|
|
1243
|
+
const isHeader = row.content?.[0]?.type === "tableHeader";
|
|
1244
|
+
if (isHeader) return true;
|
|
1245
|
+
return !hasSuppressedVar(row);
|
|
1246
|
+
});
|
|
1247
|
+
const bodyRows = filtered.filter(
|
|
1248
|
+
(r) => r.type === "tableRow" && r.content?.[0]?.type !== "tableHeader"
|
|
1249
|
+
);
|
|
1250
|
+
if (bodyRows.length === 0) return null;
|
|
1251
|
+
return { ...node, content: filtered };
|
|
1252
|
+
}
|
|
1253
|
+
if (node.type === "paragraph" && hasSuppressedVar(node)) {
|
|
1254
|
+
return null;
|
|
1255
|
+
}
|
|
1256
|
+
if (node.content) {
|
|
1257
|
+
const filtered = node.content.map((child) => walkNode(child)).filter((c) => c !== null);
|
|
1258
|
+
return { ...node, content: filtered };
|
|
1259
|
+
}
|
|
1260
|
+
return node;
|
|
1261
|
+
}
|
|
1262
|
+
return walkNode(content) || content;
|
|
1263
|
+
}
|
|
1264
|
+
function expandRepeatContent(content, values) {
|
|
1265
|
+
const enrichedValues = { ...values };
|
|
1266
|
+
function cloneNode(node, dataKey, index) {
|
|
1267
|
+
if (node.type === "variableNode" && node.attrs?.varName) {
|
|
1268
|
+
const oldName = node.attrs.varName;
|
|
1269
|
+
const bareName = oldName.replace(/^operation_/, "");
|
|
1270
|
+
const newName = `${dataKey}_${index}_${bareName}`;
|
|
1271
|
+
return { ...node, attrs: { ...node.attrs, varName: newName } };
|
|
1272
|
+
}
|
|
1273
|
+
if (node.content) {
|
|
1274
|
+
return { ...node, content: node.content.map((n) => cloneNode(n, dataKey, index)) };
|
|
1275
|
+
}
|
|
1276
|
+
return { ...node };
|
|
1277
|
+
}
|
|
1278
|
+
function walkNode(node) {
|
|
1279
|
+
if (node.type === "repeatBlock" && node.attrs?.data) {
|
|
1280
|
+
const dataKey = node.attrs.data;
|
|
1281
|
+
const arrayJson = values[dataKey];
|
|
1282
|
+
if (!arrayJson) return [];
|
|
1283
|
+
let items;
|
|
1284
|
+
try {
|
|
1285
|
+
items = JSON.parse(arrayJson);
|
|
1286
|
+
} catch {
|
|
1287
|
+
return [];
|
|
1288
|
+
}
|
|
1289
|
+
const sums = /* @__PURE__ */ new Map();
|
|
1290
|
+
for (const item of items) {
|
|
1291
|
+
for (const [key, val] of Object.entries(item)) {
|
|
1292
|
+
const num = parseFloat(val);
|
|
1293
|
+
if (!isNaN(num)) {
|
|
1294
|
+
sums.set(key, (sums.get(key) || 0) + num);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
const woKeysByNorm = /* @__PURE__ */ new Map();
|
|
1299
|
+
for (const k of Object.keys(enrichedValues)) {
|
|
1300
|
+
if (k.startsWith("workorder_")) {
|
|
1301
|
+
const norm = k.replace(/_/g, "");
|
|
1302
|
+
woKeysByNorm.set(norm, k);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
for (const [field, total] of sums) {
|
|
1306
|
+
const formatted = total % 1 === 0 ? total.toFixed(2) : total.toFixed(2);
|
|
1307
|
+
const directKey = `workorder_${field}`;
|
|
1308
|
+
enrichedValues[directKey] = formatted;
|
|
1309
|
+
const norm = `workorder${field}`.replace(/_/g, "");
|
|
1310
|
+
const existing = woKeysByNorm.get(norm);
|
|
1311
|
+
if (existing && existing !== directKey) {
|
|
1312
|
+
enrichedValues[existing] = formatted;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
const expanded = [];
|
|
1316
|
+
for (let i = 0; i < items.length; i++) {
|
|
1317
|
+
const item = items[i];
|
|
1318
|
+
for (const [key, val] of Object.entries(item)) {
|
|
1319
|
+
enrichedValues[`${dataKey}_${i}_${key}`] = String(val);
|
|
1320
|
+
}
|
|
1321
|
+
const cloned = (node.content || []).map((n) => cloneNode(n, dataKey, i));
|
|
1322
|
+
expanded.push(...cloned);
|
|
1323
|
+
}
|
|
1324
|
+
return expanded;
|
|
1325
|
+
}
|
|
1326
|
+
if (node.content) {
|
|
1327
|
+
const newContent = [];
|
|
1328
|
+
for (const child of node.content) {
|
|
1329
|
+
const result2 = walkNode(child);
|
|
1330
|
+
if (Array.isArray(result2)) newContent.push(...result2);
|
|
1331
|
+
else newContent.push(result2);
|
|
1332
|
+
}
|
|
1333
|
+
return { ...node, content: newContent };
|
|
1334
|
+
}
|
|
1335
|
+
return node;
|
|
1336
|
+
}
|
|
1337
|
+
const result = walkNode(content);
|
|
1338
|
+
return {
|
|
1339
|
+
content: Array.isArray(result) ? { ...content, content: result } : result,
|
|
1340
|
+
values: enrichedValues
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1294
1344
|
// src/utils/error-helpers.ts
|
|
1295
1345
|
function getErrorMessage(error) {
|
|
1296
1346
|
if (error instanceof Error) return error.message;
|
|
@@ -2815,7 +2865,8 @@ async function generatePdfFromContent(content, options = {}) {
|
|
|
2815
2865
|
// src/utils/template-pipeline.ts
|
|
2816
2866
|
async function generatePdfFromTiptap(content, values, options = {}) {
|
|
2817
2867
|
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, values);
|
|
2818
|
-
const
|
|
2868
|
+
const blockExpanded = expandBlockVariables(expanded, enrichedValues);
|
|
2869
|
+
const suppressed = suppressZeroContent(blockExpanded, enrichedValues);
|
|
2819
2870
|
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
2820
2871
|
const fields = extractFieldsFromContent(replaced);
|
|
2821
2872
|
const result = await generatePdfFromContent(replaced, {
|
|
@@ -2871,6 +2922,7 @@ function serializeVariableToken(attrs) {
|
|
|
2871
2922
|
if (attrs.varDefault) parts.push(`default:${attrs.varDefault}`);
|
|
2872
2923
|
if (attrs.suppressZero === "true") parts.push(`suppress:zero`);
|
|
2873
2924
|
if (attrs.format) parts.push(`format:${attrs.format}`);
|
|
2925
|
+
if (attrs.block === "true") parts.push(`block:true`);
|
|
2874
2926
|
return `{{${parts.join("|")}}}`;
|
|
2875
2927
|
}
|
|
2876
2928
|
function serializeInline(content) {
|
|
@@ -5418,7 +5470,8 @@ function GeneratePanel({
|
|
|
5418
5470
|
try {
|
|
5419
5471
|
const content = getActiveContent();
|
|
5420
5472
|
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, variableValues);
|
|
5421
|
-
const
|
|
5473
|
+
const blockExpanded = expandBlockVariables(expanded, enrichedValues);
|
|
5474
|
+
const suppressed = suppressZeroContent(blockExpanded, enrichedValues);
|
|
5422
5475
|
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
5423
5476
|
const fields = extractFieldsFromContent(replaced);
|
|
5424
5477
|
const result = await generatePdfFromContent(replaced);
|
|
@@ -5441,7 +5494,8 @@ function GeneratePanel({
|
|
|
5441
5494
|
try {
|
|
5442
5495
|
const content = getActiveContent();
|
|
5443
5496
|
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, variableValues);
|
|
5444
|
-
const
|
|
5497
|
+
const blockExpanded = expandBlockVariables(expanded, enrichedValues);
|
|
5498
|
+
const suppressed = suppressZeroContent(blockExpanded, enrichedValues);
|
|
5445
5499
|
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
5446
5500
|
const fields = extractFieldsFromContent(replaced);
|
|
5447
5501
|
const result = await generatePdfFromContent(replaced, {
|
|
@@ -5523,7 +5577,8 @@ function GeneratePanel({
|
|
|
5523
5577
|
for (let i = 0; i < bulkData.length; i++) {
|
|
5524
5578
|
const values = bulkData[i];
|
|
5525
5579
|
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, values);
|
|
5526
|
-
const
|
|
5580
|
+
const blockExpanded = expandBlockVariables(expanded, enrichedValues);
|
|
5581
|
+
const suppressed = suppressZeroContent(blockExpanded, enrichedValues);
|
|
5527
5582
|
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
5528
5583
|
const fields = extractFieldsFromContent(replaced);
|
|
5529
5584
|
const result = await generatePdfFromContent(replaced, {
|
|
@@ -7412,6 +7467,7 @@ exports.PreviewPanel = PreviewPanel;
|
|
|
7412
7467
|
exports.RepeatNode = RepeatNode;
|
|
7413
7468
|
exports.SubtotalsNode = SubtotalsNode;
|
|
7414
7469
|
exports.ThemeProvider = ThemeProvider;
|
|
7470
|
+
exports.expandBlockVariables = expandBlockVariables;
|
|
7415
7471
|
exports.expandRepeatContent = expandRepeatContent;
|
|
7416
7472
|
exports.extractVariablesFromContent = extractVariablesFromContent;
|
|
7417
7473
|
exports.generatePdfFromMarkdown = generatePdfFromMarkdown;
|