@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/dist/index.mjs CHANGED
@@ -219,7 +219,8 @@ var VariableNode = Node.create({
219
219
  varLabel: { default: "" },
220
220
  varDefault: { default: "" },
221
221
  format: { default: "" },
222
- suppressZero: { default: "" }
222
+ suppressZero: { default: "" },
223
+ block: { default: "" }
223
224
  };
224
225
  },
225
226
  parseHTML() {
@@ -526,187 +527,6 @@ function extractFieldsFromContent(content) {
526
527
  }
527
528
  return fields;
528
529
  }
529
-
530
- // src/utils/variable-helpers.ts
531
- function extractVariablesFromContent(content) {
532
- const seen = /* @__PURE__ */ new Map();
533
- function walk(node) {
534
- if (node.type === "variableNode" && node.attrs) {
535
- const varName = node.attrs.varName;
536
- if (varName && !seen.has(varName)) {
537
- seen.set(varName, {
538
- varName,
539
- varLabel: node.attrs.varLabel || varName,
540
- varDefault: node.attrs.varDefault || ""
541
- });
542
- }
543
- }
544
- if (node.content) {
545
- node.content.forEach(walk);
546
- }
547
- }
548
- if (content.content) {
549
- content.content.forEach(walk);
550
- }
551
- return Array.from(seen.values());
552
- }
553
- function applyFormat(value, format) {
554
- if (format === "phone") {
555
- const digits = value.replace(/\D/g, "");
556
- if (digits.length === 10) {
557
- return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)} - ${digits.slice(6)}`;
558
- }
559
- }
560
- return value;
561
- }
562
- function replaceVariablesInContent(content, values) {
563
- function walkNode(node) {
564
- if (node.type === "variableNode" && node.attrs) {
565
- const varName = node.attrs.varName;
566
- let replacement = values[varName] || node.attrs.varDefault || "";
567
- const format = node.attrs.format;
568
- if (format && replacement) {
569
- replacement = applyFormat(replacement, format);
570
- }
571
- const textNode = { type: "text", text: replacement };
572
- if (node.marks && node.marks.length > 0) {
573
- textNode.marks = node.marks;
574
- }
575
- return textNode;
576
- }
577
- if (node.type === "imageNode" && node.attrs?.varName) {
578
- const varName = node.attrs.varName;
579
- const resolved = values[varName] || node.attrs.src || "";
580
- return { ...node, attrs: { ...node.attrs, src: resolved } };
581
- }
582
- if (node.content) {
583
- return { ...node, content: node.content.map(walkNode) };
584
- }
585
- return node;
586
- }
587
- return walkNode(content);
588
- }
589
- function labelToVarName(label) {
590
- return label.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
591
- }
592
- function isZeroLike(value) {
593
- if (!value || value.trim() === "") return true;
594
- const cleaned = value.replace(/[$,\s]/g, "");
595
- return /^-?0+(\.0+)?$/.test(cleaned);
596
- }
597
- function suppressZeroContent(content, values) {
598
- function hasSuppressedVar(node) {
599
- if (node.type === "variableNode" && node.attrs?.suppressZero === "true") {
600
- const varName = node.attrs.varName;
601
- const val = values[varName] || node.attrs.varDefault || "";
602
- return isZeroLike(val);
603
- }
604
- return (node.content || []).some(hasSuppressedVar);
605
- }
606
- function walkNode(node) {
607
- if (node.type === "table" && node.content) {
608
- const filtered = node.content.filter((row) => {
609
- if (row.type !== "tableRow") return true;
610
- const isHeader = row.content?.[0]?.type === "tableHeader";
611
- if (isHeader) return true;
612
- return !hasSuppressedVar(row);
613
- });
614
- const bodyRows = filtered.filter(
615
- (r) => r.type === "tableRow" && r.content?.[0]?.type !== "tableHeader"
616
- );
617
- if (bodyRows.length === 0) return null;
618
- return { ...node, content: filtered };
619
- }
620
- if (node.type === "paragraph" && hasSuppressedVar(node)) {
621
- return null;
622
- }
623
- if (node.content) {
624
- const filtered = node.content.map((child) => walkNode(child)).filter((c) => c !== null);
625
- return { ...node, content: filtered };
626
- }
627
- return node;
628
- }
629
- return walkNode(content) || content;
630
- }
631
- function expandRepeatContent(content, values) {
632
- const enrichedValues = { ...values };
633
- function cloneNode(node, dataKey, index) {
634
- if (node.type === "variableNode" && node.attrs?.varName) {
635
- const oldName = node.attrs.varName;
636
- const bareName = oldName.replace(/^operation_/, "");
637
- const newName = `${dataKey}_${index}_${bareName}`;
638
- return { ...node, attrs: { ...node.attrs, varName: newName } };
639
- }
640
- if (node.content) {
641
- return { ...node, content: node.content.map((n) => cloneNode(n, dataKey, index)) };
642
- }
643
- return { ...node };
644
- }
645
- function walkNode(node) {
646
- if (node.type === "repeatBlock" && node.attrs?.data) {
647
- const dataKey = node.attrs.data;
648
- const arrayJson = values[dataKey];
649
- if (!arrayJson) return [];
650
- let items;
651
- try {
652
- items = JSON.parse(arrayJson);
653
- } catch {
654
- return [];
655
- }
656
- const sums = /* @__PURE__ */ new Map();
657
- for (const item of items) {
658
- for (const [key, val] of Object.entries(item)) {
659
- const num = parseFloat(val);
660
- if (!isNaN(num)) {
661
- sums.set(key, (sums.get(key) || 0) + num);
662
- }
663
- }
664
- }
665
- const woKeysByNorm = /* @__PURE__ */ new Map();
666
- for (const k of Object.keys(enrichedValues)) {
667
- if (k.startsWith("workorder_")) {
668
- const norm = k.replace(/_/g, "");
669
- woKeysByNorm.set(norm, k);
670
- }
671
- }
672
- for (const [field, total] of sums) {
673
- const formatted = total % 1 === 0 ? total.toFixed(2) : total.toFixed(2);
674
- const directKey = `workorder_${field}`;
675
- enrichedValues[directKey] = formatted;
676
- const norm = `workorder${field}`.replace(/_/g, "");
677
- const existing = woKeysByNorm.get(norm);
678
- if (existing && existing !== directKey) {
679
- enrichedValues[existing] = formatted;
680
- }
681
- }
682
- const expanded = [];
683
- for (let i = 0; i < items.length; i++) {
684
- const item = items[i];
685
- for (const [key, val] of Object.entries(item)) {
686
- enrichedValues[`${dataKey}_${i}_${key}`] = String(val);
687
- }
688
- const cloned = (node.content || []).map((n) => cloneNode(n, dataKey, i));
689
- expanded.push(...cloned);
690
- }
691
- return expanded;
692
- }
693
- if (node.content) {
694
- const newContent = [];
695
- for (const child of node.content) {
696
- const result2 = walkNode(child);
697
- if (Array.isArray(result2)) newContent.push(...result2);
698
- else newContent.push(result2);
699
- }
700
- return { ...node, content: newContent };
701
- }
702
- return node;
703
- }
704
- const result = walkNode(content);
705
- return {
706
- content: Array.isArray(result) ? { ...content, content: result } : result,
707
- values: enrichedValues
708
- };
709
- }
710
530
  var TOKEN_REGEX = /\{\{(field|var|image)\|([^}]+)\}\}/g;
711
531
  var BLOCK_IMAGE_REGEX = /^!\[([^\]]*)\]\(([^)\s]+)\)$/;
712
532
  var DIRECTIVE_OPEN = /^:::(panel|columns|col|watermark|repeat|subtotals)(?:\{([^}]*)\})?$/;
@@ -846,6 +666,9 @@ function parseVariableToken(tokenBody) {
846
666
  case "format":
847
667
  attrs.format = value;
848
668
  break;
669
+ case "block":
670
+ attrs.block = value === "true" ? "true" : "";
671
+ break;
849
672
  case "_marks":
850
673
  attrs._marks = value;
851
674
  break;
@@ -1257,6 +1080,233 @@ function markdownToTiptap(markdown) {
1257
1080
  return { type: "doc", content: blocks };
1258
1081
  }
1259
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
+ };
1308
+ }
1309
+
1260
1310
  // src/utils/error-helpers.ts
1261
1311
  function getErrorMessage(error) {
1262
1312
  if (error instanceof Error) return error.message;
@@ -2781,7 +2831,8 @@ async function generatePdfFromContent(content, options = {}) {
2781
2831
  // src/utils/template-pipeline.ts
2782
2832
  async function generatePdfFromTiptap(content, values, options = {}) {
2783
2833
  const { content: expanded, values: enrichedValues } = expandRepeatContent(content, values);
2784
- const suppressed = suppressZeroContent(expanded, enrichedValues);
2834
+ const blockExpanded = expandBlockVariables(expanded, enrichedValues);
2835
+ const suppressed = suppressZeroContent(blockExpanded, enrichedValues);
2785
2836
  const replaced = replaceVariablesInContent(suppressed, enrichedValues);
2786
2837
  const fields = extractFieldsFromContent(replaced);
2787
2838
  const result = await generatePdfFromContent(replaced, {
@@ -2837,6 +2888,7 @@ function serializeVariableToken(attrs) {
2837
2888
  if (attrs.varDefault) parts.push(`default:${attrs.varDefault}`);
2838
2889
  if (attrs.suppressZero === "true") parts.push(`suppress:zero`);
2839
2890
  if (attrs.format) parts.push(`format:${attrs.format}`);
2891
+ if (attrs.block === "true") parts.push(`block:true`);
2840
2892
  return `{{${parts.join("|")}}}`;
2841
2893
  }
2842
2894
  function serializeInline(content) {
@@ -5384,7 +5436,8 @@ function GeneratePanel({
5384
5436
  try {
5385
5437
  const content = getActiveContent();
5386
5438
  const { content: expanded, values: enrichedValues } = expandRepeatContent(content, variableValues);
5387
- const suppressed = suppressZeroContent(expanded, enrichedValues);
5439
+ const blockExpanded = expandBlockVariables(expanded, enrichedValues);
5440
+ const suppressed = suppressZeroContent(blockExpanded, enrichedValues);
5388
5441
  const replaced = replaceVariablesInContent(suppressed, enrichedValues);
5389
5442
  const fields = extractFieldsFromContent(replaced);
5390
5443
  const result = await generatePdfFromContent(replaced);
@@ -5407,7 +5460,8 @@ function GeneratePanel({
5407
5460
  try {
5408
5461
  const content = getActiveContent();
5409
5462
  const { content: expanded, values: enrichedValues } = expandRepeatContent(content, variableValues);
5410
- const suppressed = suppressZeroContent(expanded, enrichedValues);
5463
+ const blockExpanded = expandBlockVariables(expanded, enrichedValues);
5464
+ const suppressed = suppressZeroContent(blockExpanded, enrichedValues);
5411
5465
  const replaced = replaceVariablesInContent(suppressed, enrichedValues);
5412
5466
  const fields = extractFieldsFromContent(replaced);
5413
5467
  const result = await generatePdfFromContent(replaced, {
@@ -5489,7 +5543,8 @@ function GeneratePanel({
5489
5543
  for (let i = 0; i < bulkData.length; i++) {
5490
5544
  const values = bulkData[i];
5491
5545
  const { content: expanded, values: enrichedValues } = expandRepeatContent(content, values);
5492
- const suppressed = suppressZeroContent(expanded, enrichedValues);
5546
+ const blockExpanded = expandBlockVariables(expanded, enrichedValues);
5547
+ const suppressed = suppressZeroContent(blockExpanded, enrichedValues);
5493
5548
  const replaced = replaceVariablesInContent(suppressed, enrichedValues);
5494
5549
  const fields = extractFieldsFromContent(replaced);
5495
5550
  const result = await generatePdfFromContent(replaced, {
@@ -7371,6 +7426,6 @@ function parseXmlTemplate(xmlString) {
7371
7426
  };
7372
7427
  }
7373
7428
 
7374
- export { DocumentGenerator, EditorPanel, FormFieldType, PreviewPanel, RepeatNode, SubtotalsNode, ThemeProvider, expandRepeatContent, extractVariablesFromContent, generatePdfFromMarkdown, generatePdfFromTiptap, isZeroLike, markdownToTiptap, parseXmlTemplate, suppressZeroContent, tiptapToMarkdown, useDocumentGenerator, useTheme };
7429
+ export { DocumentGenerator, EditorPanel, FormFieldType, PreviewPanel, RepeatNode, SubtotalsNode, ThemeProvider, expandBlockVariables, expandRepeatContent, extractVariablesFromContent, generatePdfFromMarkdown, generatePdfFromTiptap, isZeroLike, markdownToTiptap, parseXmlTemplate, suppressZeroContent, tiptapToMarkdown, useDocumentGenerator, useTheme };
7375
7430
  //# sourceMappingURL=index.mjs.map
7376
7431
  //# sourceMappingURL=index.mjs.map