email-builder-utils 1.1.31 → 1.1.35

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.
@@ -1 +1 @@
1
- {"version":3,"file":"convertJsonToHtml.d.ts","sourceRoot":"","sources":["../../src/utils/convertJsonToHtml.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,iBAAiB,GAAU,UAAU,GAAG,oBA4GpD,CAAC"}
1
+ {"version":3,"file":"convertJsonToHtml.d.ts","sourceRoot":"","sources":["../../src/utils/convertJsonToHtml.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,iBAAiB,GAAU,UAAU,GAAG,oBA8GpD,CAAC"}
@@ -12,83 +12,85 @@ const convertJsonToHtml = async (jsonData) => {
12
12
  }
13
13
  const { fontFamily, canvasColor, textColor, padding = {}, borderColor, borderRadius, borderWidth, borderStyle, } = rootData.style || {};
14
14
  const { top = 0, right = 0, bottom = 0, left = 0 } = padding;
15
- const rawHtml = `<!DOCTYPE html>
16
- <html lang="en">
17
- <head>
18
- <meta charset="UTF-8" />
19
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
20
- <meta name="x-apple-disable-message-reformatting" />
21
- <style>
22
- .responsive-table {
23
- width: 100%;
24
- max-width: 600px;
25
- }
26
- @media only screen and (max-width: 600px) {
27
- .responsive-table {
28
- width: 100% !important;
29
- }
30
- .stack-column,
31
- .stack-column td {
32
- display: block !important;
33
- width: 100% !important;
34
- max-width: 100% !important;
35
- }
36
- }
37
- .hide-mobile {
38
- display: block !important;
39
- mso-hide: all !important; /* Hide in Outlook */
40
- }
41
-
42
- .hide-desktop {
43
- display: block !important;
44
- mso-hide: all !important; /* Hide in Outlook */
45
- }
46
-
47
- @media only screen and (max-width: 600px) {
48
- .hide-mobile {
49
- display: none !important;
50
- max-height: 0 !important;
51
- overflow: hidden !important;
52
- mso-hide: all !important;
53
- }
54
- }
55
-
56
- @media only screen and (min-width: 601px) {
57
- .hide-desktop {
58
- display: none !important;
59
- max-height: 0 !important;
60
- overflow: hidden !important;
61
- mso-hide: all !important;
62
- }
63
- }
64
-
65
- </style>
66
- </head>
67
- <body>
68
- <center>
69
- <table
70
- class="responsive-table"
71
- bgcolor="${canvasColor}"
72
- style="
73
- font-family: ${fontFamily};
74
- margin: 0 auto;
75
- table-layout:fixed;
76
- background-color: ${canvasColor};
77
- color: ${textColor};
78
- padding: ${top}px ${right}px ${bottom}px ${left}px;
79
- border: ${borderWidth}px ${borderStyle} ${borderColor};
80
- border-radius: ${borderRadius}px; "
81
- >
82
- <tbody>
83
- <tr>
84
- <td style="padding: 0;">
85
- ${blocksHtml.join("")}
86
- </td>
87
- </tr>
88
- </tbody>
89
- </table>
90
- </center>
91
- </body>
15
+ const rawHtml = `<!DOCTYPE html>
16
+ <html lang="en">
17
+ <head>
18
+ <meta charset="UTF-8" />
19
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
20
+ <meta name="x-apple-disable-message-reformatting" />
21
+ <style>
22
+ .responsive-table {
23
+ width: 100%;
24
+ max-width: 600px;
25
+ }
26
+ @media only screen and (max-width: 600px) {
27
+ .responsive-table {
28
+ width: 100% !important;
29
+ }
30
+ .stack-column,
31
+ .stack-column td {
32
+ display: block !important;
33
+ width: 100% !important;
34
+ max-width: 100% !important;
35
+ }
36
+ }
37
+ .hide-mobile {
38
+ display: block !important;
39
+ mso-hide: all !important; /* Hide in Outlook */
40
+ }
41
+
42
+ .hide-desktop {
43
+ display: block !important;
44
+ mso-hide: all !important; /* Hide in Outlook */
45
+ }
46
+
47
+ @media only screen and (max-width: 600px) {
48
+ .hide-mobile {
49
+ display: none !important;
50
+ max-height: 0 !important;
51
+ overflow: hidden !important;
52
+ mso-hide: all !important;
53
+ }
54
+ }
55
+
56
+ @media only screen and (min-width: 601px) {
57
+ .hide-desktop {
58
+ display: none !important;
59
+ max-height: 0 !important;
60
+ overflow: hidden !important;
61
+ mso-hide: all !important;
62
+ }
63
+ }
64
+
65
+ </style>
66
+ </head>
67
+ <body>
68
+ <center>
69
+ <table
70
+ class="responsive-table"
71
+ bgcolor="${canvasColor}"
72
+ style="
73
+ font-family: ${fontFamily};
74
+ margin: 0 auto;
75
+ table-layout:fixed;
76
+ width:600px;
77
+ max-width:600px;
78
+ background-color: ${canvasColor};
79
+ color: ${textColor};
80
+ padding: ${top}px ${right}px ${bottom}px ${left}px;
81
+ border: ${borderWidth}px ${borderStyle} ${borderColor};
82
+ border-radius: ${borderRadius}px; "
83
+ >
84
+ <tbody>
85
+ <tr>
86
+ <td style="padding: 0;">
87
+ ${blocksHtml.join("")}
88
+ </td>
89
+ </tr>
90
+ </tbody>
91
+ </table>
92
+ </center>
93
+ </body>
92
94
  </html>`;
93
95
  return rawHtml;
94
96
  };
@@ -1,3 +1,5 @@
1
1
  export * from "./convertJsonToHtml";
2
2
  export * from "./generateUniqueId";
3
+ export * from "./common";
4
+ export * from "./jsonToHTML";
3
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC"}
@@ -16,3 +16,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./convertJsonToHtml"), exports);
18
18
  __exportStar(require("./generateUniqueId"), exports);
19
+ __exportStar(require("./common"), exports);
20
+ __exportStar(require("./jsonToHTML"), exports);
@@ -1 +1 @@
1
- {"version":3,"file":"jsonToHTML.d.ts","sourceRoot":"","sources":["../../src/utils/jsonToHTML.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAUrC,UAAU,cAAc;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,UAAU,UAAU;IAClB,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE;QACJ,KAAK,EAAE,cAAc,CAAC;QACtB,KAAK,EAAE,GAAG,CAAC;QACX,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;KAC7B,CAAC;CACH;AAYD,eAAO,MAAM,gBAAgB,kDAAkD,CAAC;AA2DhF,wBAAsB,aAAa,CACjC,SAAS,EAAE,UAAU,EACrB,QAAQ,EAAE,GAAG,EACb,aAAa,EAAE,MAAM,mBAwBtB;AAuiBD,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,mBAyL5E"}
1
+ {"version":3,"file":"jsonToHTML.d.ts","sourceRoot":"","sources":["../../src/utils/jsonToHTML.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAGrC,UAAU,cAAc;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,UAAU,UAAU;IAClB,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE;QACJ,KAAK,EAAE,cAAc,CAAC;QACtB,KAAK,EAAE,GAAG,CAAC;QACX,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;KAC7B,CAAC;CACH;AAYD,eAAO,MAAM,gBAAgB,kDAAkD,CAAC;AAyFhF,wBAAsB,aAAa,CACjC,SAAS,EAAE,UAAU,EACrB,QAAQ,EAAE,GAAG,EACb,aAAa,EAAE,MAAM,mBAwBtB;AAmkBD,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,mBAyL5E"}
@@ -40,19 +40,53 @@ function buildStyles(style, { pxChanges, perChanges }) {
40
40
  Object.entries(style).forEach(([key, value]) => {
41
41
  if (key === "customCss")
42
42
  return;
43
+ const INVALID_KEYS = [
44
+ "columns",
45
+ "cellWidths",
46
+ "cellWidth",
47
+ "childWidth",
48
+ "visibility",
49
+ "hideOnMobile",
50
+ "hideOnDesktop"
51
+ ];
52
+ if (INVALID_KEYS.includes(key))
53
+ return;
54
+ // Prevent null/undefined/"" from leaking into CSS
43
55
  if (value === undefined || value === null || value === "")
44
56
  return;
57
+ // FIX 1 — SANITIZE padding objects
45
58
  if ((key === "padding" || key === "buttonPadding") &&
46
59
  typeof value === "object") {
47
- const padding = value;
48
- value = `${padding.top}px ${padding.right}px ${padding.bottom}px ${padding.left}px`;
60
+ const pad = value;
61
+ const safePad = {
62
+ top: Number.isFinite(pad.top) ? pad.top : 0,
63
+ right: Number.isFinite(pad.right) ? pad.right : 0,
64
+ bottom: Number.isFinite(pad.bottom) ? pad.bottom : 0,
65
+ left: Number.isFinite(pad.left) ? pad.left : 0,
66
+ };
67
+ value = `${safePad.top}px ${safePad.right}px ${safePad.bottom}px ${safePad.left}px`;
49
68
  }
50
69
  const cssKey = key.replace(/([A-Z])/g, "-$1").toLowerCase();
70
+ // FIX 2 — Sanitize invalid px/per values
51
71
  if (pxChanges.includes(key)) {
52
- stylesObj[cssKey] = typeof value === "number" ? `${value}px` : value;
72
+ if (typeof value === "number") {
73
+ stylesObj[cssKey] = `${value}px`;
74
+ }
75
+ else if (typeof value === "string" && value.includes("null")) {
76
+ // Skip invalid styles
77
+ return;
78
+ }
79
+ else {
80
+ stylesObj[cssKey] = value;
81
+ }
53
82
  }
54
83
  else if (perChanges.includes(key)) {
55
- stylesObj[cssKey] = typeof value === "number" ? `${value}%` : value;
84
+ if (typeof value === "number") {
85
+ stylesObj[cssKey] = `${value}%`;
86
+ }
87
+ else {
88
+ stylesObj[cssKey] = value;
89
+ }
56
90
  }
57
91
  else {
58
92
  stylesObj[cssKey] = value;
@@ -93,14 +127,14 @@ function appendOutlookSupport(content, contentStyle, className) {
93
127
  const visibilityClass = className || "";
94
128
  const shouldHideInOutlook = visibilityClass.includes("hide-desktop");
95
129
  if (shouldHideInOutlook) {
96
- return `
97
- <!--[if !mso]><!-->
98
- <table width="100%" style="${exports.tableCommonStyle}" class="${visibilityClass}"><tr><td style="${contentStyle}">${content}</td></tr></table>
99
- <!--<![endif]-->
130
+ return `
131
+ <!--[if !mso]><!-->
132
+ <table width="100%" style="${exports.tableCommonStyle}" class="${visibilityClass}"><tr><td style="${contentStyle}">${content}</td></tr></table>
133
+ <!--<![endif]-->
100
134
  `;
101
135
  }
102
- return `
103
- <table width="100%" style="${exports.tableCommonStyle}" class="${visibilityClass}"><tr><td style="${contentStyle}">${content}</td></tr></table>
136
+ return `
137
+ <table width="100%" style="${exports.tableCommonStyle}; max-width:600px;" class="${visibilityClass}"><tr><td style="${contentStyle}">${content}</td></tr></table>
104
138
  `;
105
139
  }
106
140
  function convertDividerBlockToHtml(blockData) {
@@ -119,21 +153,21 @@ function convertDividerBlockToHtml(blockData) {
119
153
  ]
120
154
  .filter(Boolean)
121
155
  .join(" ");
122
- const dividerContent = `
123
- <table
124
- width="${dividerWidth}%"
125
- cellpadding="0"
126
- cellspacing="0"
127
- >
128
- <tr>
129
- <td
130
- height="${thickness}"
131
- style="font-size:1px; line-height:1px; background:${dividerColor}; width:${dividerWidth};"
132
- >
133
- &nbsp;
134
- </td>
135
- </tr>
136
- </table>
156
+ const dividerContent = `
157
+ <table
158
+ width="${dividerWidth}%"
159
+ cellpadding="0"
160
+ cellspacing="0"
161
+ >
162
+ <tr>
163
+ <td
164
+ height="${thickness}"
165
+ style="font-size:1px; line-height:1px; background:${dividerColor}; width:${dividerWidth};"
166
+ >
167
+ &nbsp;
168
+ </td>
169
+ </tr>
170
+ </table>
137
171
  `;
138
172
  return appendOutlookSupport(dividerContent, convertedStyle, visibilityClass);
139
173
  }
@@ -214,21 +248,21 @@ async function appendOutlookForImage(content, outerContainerWidth, innerContaine
214
248
  const borderAttributes = borderWidth > 0
215
249
  ? `strokeweight="${borderWidth}px" strokecolor="${borderColor}"`
216
250
  : `stroked="false"`;
217
- const outlookImage = `<!--[if mso]>
218
- <v:${useRoundRect ? "roundrect" : "rect"} xmlns:v="urn:schemas-microsoft-com:vml"
219
- style="width:${scaledWidth}px;height:${scaledHeight}px;"
220
- ${borderAttributes}
221
- ${useRoundRect ? `arcsize="${arcsize}"` : ""}
222
- fill="true" fillcolor="none">
223
- <v:fill src="${imageUrl}" type="frame" />
224
- <v:textbox inset="0,0,0,0"><div style="display:none;">.</div></v:textbox>
225
- </v:${useRoundRect ? "roundrect" : "rect"}>
251
+ const outlookImage = `<!--[if mso]>
252
+ <v:${useRoundRect ? "roundrect" : "rect"} xmlns:v="urn:schemas-microsoft-com:vml"
253
+ style="width:${scaledWidth}px;height:${scaledHeight}px;"
254
+ ${borderAttributes}
255
+ ${useRoundRect ? `arcsize="${arcsize}"` : ""}
256
+ fill="true" fillcolor="none">
257
+ <v:fill src="${imageUrl}" type="frame" />
258
+ <v:textbox inset="0,0,0,0"><div style="display:none;">.</div></v:textbox>
259
+ </v:${useRoundRect ? "roundrect" : "rect"}>
226
260
  <![endif]-->`;
227
- return `
228
- ${outlookImage}
229
- <!--[if !mso]><!-->
230
- ${content}
231
- <!--<![endif]-->
261
+ return `
262
+ ${outlookImage}
263
+ <!--[if !mso]><!-->
264
+ ${content}
265
+ <!--<![endif]-->
232
266
  `;
233
267
  }
234
268
  async function computeScaledDimensions(imageUrl, maxContainerWidthPx) {
@@ -291,23 +325,23 @@ function appendOutlookForButton(content, buttonStyle, navigateToUrl, text) {
291
325
  const borderAttributes = borderWidth > 0
292
326
  ? `strokeweight="${borderWidth}px" strokecolor="${borderColor}"`
293
327
  : `stroked="false"`;
294
- return `
295
- <!--[if mso]>
296
- <v:${borderRadius ? "roundrect" : "rect"} xmlns:v="urn:schemas-microsoft-com:vml" href="${navigateToUrl}"
297
- style="height:${height}px;v-text-anchor:middle;width:${width}px;"
298
- arcsize="${borderRadius / height}" ${borderAttributes}
299
- fillcolor="${buttonColor}">
300
- <w:anchorlock/>
301
- <v:textbox inset="${buttonPadding.top}px,${buttonPadding.left}px,${buttonPadding.bottom}px,${buttonPadding.right}px">
302
- <center style="font-family:${fontFamily};font-size:${fontSize}px;font-weight:${fontWeight};color:${color};">
303
- ${text}
304
- </center>
305
- </v:textbox>
306
- </v:${borderRadius ? "roundrect" : "rect"}>
307
- <![endif]-->
308
- <!--[if !mso]><!-->
309
- ${content}
310
- <!--<![endif]-->
328
+ return `
329
+ <!--[if mso]>
330
+ <v:${borderRadius ? "roundrect" : "rect"} xmlns:v="urn:schemas-microsoft-com:vml" href="${navigateToUrl}"
331
+ style="height:${height}px;v-text-anchor:middle;width:${width}px;"
332
+ arcsize="${borderRadius / height}" ${borderAttributes}
333
+ fillcolor="${buttonColor}">
334
+ <w:anchorlock/>
335
+ <v:textbox inset="${buttonPadding.top}px,${buttonPadding.left}px,${buttonPadding.bottom}px,${buttonPadding.right}px">
336
+ <center style="font-family:${fontFamily};font-size:${fontSize}px;font-weight:${fontWeight};color:${color};">
337
+ ${text}
338
+ </center>
339
+ </v:textbox>
340
+ </v:${borderRadius ? "roundrect" : "rect"}>
341
+ <![endif]-->
342
+ <!--[if !mso]><!-->
343
+ ${content}
344
+ <!--<![endif]-->
311
345
  `;
312
346
  }
313
347
  function convertButtonBlock(blockData) {
@@ -346,90 +380,106 @@ async function convertGridBlock(blockData, rootData, cellWidthInPx) {
346
380
  const { columns = 1, cellWidths = [], responsive = true } = props;
347
381
  const { columnGap = 0, ...restStyle } = style;
348
382
  const gridVisibilityClass = (0, common_1.getVisibilityClass)(props);
383
+ // FIX: avoid table-layout:fixed – causes shrink in many clients
349
384
  const tableStyles = buildStyles(restStyle, {
350
385
  perChanges: [],
351
386
  pxChanges: allPxAttributes,
352
387
  });
353
388
  const total = childrenIds.length;
354
389
  const visualRows = Math.ceil(total / columns);
355
- // Fix: Calculate visible cells per row to adjust widths
356
- let html = `
357
- <!--[if mso]>
358
- <table border="0" cellpadding="0" cellspacing="${columnGap}" width="100%"
359
- style="${exports.tableCommonStyle}border-collapse: separate;border-spacing:${columnGap}px;" class="${gridVisibilityClass}">
360
- <![endif]-->
361
- <table border="0" cellpadding="0" cellspacing="${columnGap}" width="100%" role="presentation"
362
- style="${exports.tableCommonStyle} ${tableStyles}border-collapse: separate;border-spacing:${columnGap}px;" class="${gridVisibilityClass}">
390
+ let html = `
391
+ <!--[if mso]>
392
+ <table border="0" cellpadding="0" cellspacing="${columnGap}" width="100%"
393
+ style="border-collapse:separate;border-spacing:${columnGap}px;"
394
+ class="${gridVisibilityClass}">
395
+ <![endif]-->
396
+ <table border="0" cellpadding="0" cellspacing="${columnGap}" width="100%"
397
+ role="presentation"
398
+ style="border-collapse:separate;border-spacing:${columnGap}px; ${tableStyles}; max-width:600px;"
399
+ class="${gridVisibilityClass}">
363
400
  `;
364
401
  for (let r = 0; r < visualRows; r++) {
365
402
  html += "<tr>";
366
- let visibleCellsInRow = 0;
367
- const rowCellVisibility = [];
403
+ // COUNT visible cells only
404
+ let visibleCells = 0;
405
+ const rowIds = [];
368
406
  for (let c = 0; c < columns; c++) {
369
407
  const idx = r * columns + c;
370
- const childId = childrenIds[idx];
371
- if (childId) {
372
- const child = rootData[childId];
373
- const { props: childProps = {} } = child.data || {};
374
- const isVisible = !childProps.hideOnDesktop;
375
- rowCellVisibility.push(isVisible);
376
- if (isVisible)
377
- visibleCellsInRow++;
378
- }
379
- else {
380
- rowCellVisibility.push(false);
408
+ const id = childrenIds[idx] ?? null;
409
+ rowIds.push(id);
410
+ if (id) {
411
+ const child = rootData[id];
412
+ const isHidden = child?.data?.props?.hideOnDesktop;
413
+ if (!isHidden)
414
+ visibleCells++;
381
415
  }
382
416
  }
383
- const widthPerVisibleCell = visibleCellsInRow > 0 ? 100 / visibleCellsInRow : 100 / columns;
417
+ // FIX: fallback safe-width
418
+ const safeWidth = visibleCells > 0 ? Math.min(100 / visibleCells, 50) : Math.min(100 / columns, 50);
384
419
  for (let c = 0; c < columns; c++) {
385
420
  const idx = r * columns + c;
386
- const childId = childrenIds[idx];
387
- const widthPercent = cellWidths[c] ?? widthPerVisibleCell;
388
- if (childId) {
389
- const child = rootData[childId];
390
- const { style: cellStyle = {}, props: childProps = {} } = child.data || {};
421
+ const id = rowIds[c];
422
+ let widthPercent = cellWidths[c] ?? safeWidth;
423
+ // FIX: never exceed reasonable width
424
+ if (widthPercent <= 0 || widthPercent > 100) {
425
+ widthPercent = safeWidth;
426
+ }
427
+ // FIX: Cap width percent to prevent overflow in Outlook
428
+ widthPercent = Math.min(widthPercent, 50);
429
+ if (id) {
430
+ const child = rootData[id];
431
+ const { style: cellStyle = {}, props: childProps = {} } = child.data;
391
432
  const verticalAlign = cellStyle.verticalAlign || "top";
392
- const { html: childHtml, styles } = await convertGridCellBlock(child, rootData, widthPercent, cellWidthInPx);
393
- const cellVisibilityClass = (0, common_1.getVisibilityClass)(childProps);
394
- if (!childProps.hideOnDesktop) {
395
- html += `
396
- <td
397
- width="${widthPercent}%"
398
- class="${[responsive ? "stack-column" : "", cellVisibilityClass]
399
- .filter(Boolean)
400
- .join(" ")}"
401
- style="vertical-align:${verticalAlign}; word-break:break-word; ${styles}"
402
- >
403
- ${childHtml}
404
- </td>`;
433
+ const childVisible = !childProps.hideOnDesktop;
434
+ const visibilityClass = (0, common_1.getVisibilityClass)(childProps);
435
+ // Only render if visible
436
+ if (childVisible) {
437
+ const { html: childHtml, styles } = await convertGridCellBlock(child, rootData, widthPercent, cellWidthInPx);
438
+ html += `
439
+ <td
440
+ width="${Math.round(widthPercent)}%"
441
+ class="${[
442
+ responsive ? "stack-column" : "",
443
+ visibilityClass,
444
+ ].filter(Boolean).join(" ")}"
445
+ style="vertical-align:${verticalAlign};word-break:break-word;${styles}"
446
+ >
447
+ ${childHtml}
448
+ </td>`;
405
449
  }
406
450
  }
407
451
  else {
408
- html += `<td width="${widthPercent}%" ${responsive ? 'class="stack-column"' : ""} style=""></td>`;
452
+ // SAFE empty cell (keeps layout stable)
453
+ html += `
454
+ <td width="${Math.round(widthPercent)}%"
455
+ ${responsive ? 'class="stack-column"' : ""}
456
+ style="vertical-align:top;">
457
+ </td>`;
409
458
  }
410
459
  }
411
460
  html += "</tr>";
412
461
  }
413
- html += `</table><!--[if mso]></table><![endif]-->`;
462
+ html += "</table><!--[if mso]></table><![endif]-->";
414
463
  return html;
415
464
  }
416
465
  async function convertGridCellBlock(blockData, rootData, cellWidthPercent, parentCellWidthPx) {
417
466
  const { style = {}, childrenIds = [], props = {} } = blockData.data;
467
+ const visibilityClass = (0, common_1.getVisibilityClass)(props);
418
468
  const styles = buildStyles(style, {
419
469
  perChanges: [],
420
470
  pxChanges: allPxAttributes,
421
471
  });
422
- const innerHtmlParts = [];
472
+ const parts = [];
473
+ // FIX: do NOT re-calc px based on parent → causes shrinking
474
+ const safeCellWidthPx = Math.max(parentCellWidthPx, 20);
423
475
  for (const childId of childrenIds) {
424
476
  const child = rootData[childId];
425
477
  if (child) {
426
- const cellWidthPx = parentCellWidthPx * (cellWidthPercent / 100);
427
- innerHtmlParts.push(await convertToHtml(child, rootData, cellWidthPx));
478
+ parts.push(await convertToHtml(child, rootData, safeCellWidthPx));
428
479
  }
429
480
  }
430
- const cellContent = innerHtmlParts.join("");
431
481
  return {
432
- html: cellContent,
482
+ html: parts.join(""),
433
483
  styles,
434
484
  };
435
485
  }
@@ -494,116 +544,116 @@ async function convertVideoBlock(blockData, cellWidthInPx) {
494
544
  const vmlTop = calculatedHeight / 2 - playIconHeight / 2;
495
545
  const shouldHideInOutlook = hideOnDesktop;
496
546
  const outlookVideoContent = shouldHideInOutlook
497
- ? `<!--[if !mso]><!-->
498
- <v:group xmlns:v="urn:schemas-microsoft-com:vml"
499
- coordsize="${innerContainerWidth},${calculatedHeight}"
500
- href="${videoLink}"
501
- style="width:${innerContainerWidth}px;height:${calculatedHeight}px;">
502
- <v:rect fill="t" style="position:absolute;width:${innerContainerWidth}px;height:${calculatedHeight}px; stroked="t"
503
- strokeweight="${borderWidth}px"
504
- strokecolor="${borderColor}"
505
- ${borderRadius > 0 ? `arcsize="${Math.min(borderRadius / calculatedHeight, 1).toFixed(2)}"` : ""}
506
- >
507
- <v:fill src="${resolvedThumbnail}" type="frame" color="${style?.backgroundColor || "#FFFFFF"}"/>
508
- </v:rect>
509
- <v:shape type="#_x0000_t75"
510
- style="position:absolute;
511
- left:${vmlLeft.toFixed(1)}px;
512
- top:${vmlTop.toFixed(1)}px;
513
- width:${playIconWidth}px;
514
- height:${playIconHeight}px;"
515
- alt="Play" href="${videoLink}" title="${altText || "Video"}"
516
- stroked="f" filled="t">
517
- <v:imagedata src="https://app-rsrc.getbee.io/public/resources/components/widgetBar/video-content-icon-sets/light/type-01.png" />
518
- </v:shape>
519
- </v:group>
547
+ ? `<!--[if !mso]><!-->
548
+ <v:group xmlns:v="urn:schemas-microsoft-com:vml"
549
+ coordsize="${innerContainerWidth},${calculatedHeight}"
550
+ href="${videoLink}"
551
+ style="width:${innerContainerWidth}px;height:${calculatedHeight}px;">
552
+ <v:rect fill="t" style="position:absolute;width:${innerContainerWidth}px;height:${calculatedHeight}px;" stroked="t"
553
+ strokeweight="${borderWidth}px"
554
+ strokecolor="${borderColor}"
555
+ ${borderRadius > 0 ? `arcsize="${Math.min(borderRadius / calculatedHeight, 1).toFixed(2)}"` : ""}
556
+ >
557
+ <v:fill src="${resolvedThumbnail}" type="frame" color="${style?.backgroundColor || "#FFFFFF"}"/>
558
+ </v:rect>
559
+ <v:shape type="#_x0000_t75"
560
+ style="position:absolute;
561
+ left:${vmlLeft.toFixed(1)}px;
562
+ top:${vmlTop.toFixed(1)}px;
563
+ width:${playIconWidth}px;
564
+ height:${playIconHeight}px;"
565
+ alt="Play" href="${videoLink}" title="${altText || "Video"}"
566
+ stroked="f" filled="t">
567
+ <v:imagedata src="https://app-rsrc.getbee.io/public/resources/components/widgetBar/video-content-icon-sets/light/type-01.png" />
568
+ </v:shape>
569
+ </v:group>
520
570
  <!--<![endif]-->`
521
- : `<!--[if mso]>
522
- <v:group xmlns:v="urn:schemas-microsoft-com:vml"
523
- coordsize="${innerContainerWidth},${calculatedHeight}"
524
- href="${videoLink}"
525
- style="width:${innerContainerWidth}px;height:${calculatedHeight}px;">
526
- <v:rect fill="t" style="position:absolute;width:${innerContainerWidth}px;height:${calculatedHeight}px; stroked="t"
527
- strokeweight="${borderWidth}px"
528
- strokecolor="${borderColor}"
529
- ${borderRadius > 0 ? `arcsize="${Math.min(borderRadius / calculatedHeight, 1).toFixed(2)}"` : ""}
530
- >
531
- <v:fill src="${resolvedThumbnail}" type="frame" color="${style?.backgroundColor || "#FFFFFF"}"/>
532
- </v:rect>
533
- <v:shape type="#_x0000_t75"
534
- style="position:absolute;
535
- left:${vmlLeft.toFixed(1)}px;
536
- top:${vmlTop.toFixed(1)}px;
537
- width:${playIconWidth}px;
538
- height:${playIconHeight}px;"
539
- alt="Play" href="${videoLink}" title="${altText || "Video"}"
540
- stroked="f" filled="t">
541
- <v:imagedata src="https://app-rsrc.getbee.io/public/resources/components/widgetBar/video-content-icon-sets/light/type-01.png" />
542
- </v:shape>
543
- </v:group>
571
+ : `<!--[if mso]>
572
+ <v:group xmlns:v="urn:schemas-microsoft-com:vml"
573
+ coordsize="${innerContainerWidth},${calculatedHeight}"
574
+ href="${videoLink}"
575
+ style="width:${innerContainerWidth}px;height:${calculatedHeight}px;">
576
+ <v:rect fill="t" style="position:absolute;width:${innerContainerWidth}px;height:${calculatedHeight}px; stroked="t"
577
+ strokeweight="${borderWidth}px"
578
+ strokecolor="${borderColor}"
579
+ ${borderRadius > 0 ? `arcsize="${Math.min(borderRadius / calculatedHeight, 1).toFixed(2)}"` : ""}
580
+ >
581
+ <v:fill src="${resolvedThumbnail}" type="frame" color="${style?.backgroundColor || "#FFFFFF"}"/>
582
+ </v:rect>
583
+ <v:shape type="#_x0000_t75"
584
+ style="position:absolute;
585
+ left:${vmlLeft.toFixed(1)}px;
586
+ top:${vmlTop.toFixed(1)}px;
587
+ width:${playIconWidth}px;
588
+ height:${playIconHeight}px;"
589
+ alt="Play" href="${videoLink}" title="${altText || "Video"}"
590
+ stroked="f" filled="t">
591
+ <v:imagedata src="https://app-rsrc.getbee.io/public/resources/components/widgetBar/video-content-icon-sets/light/type-01.png" />
592
+ </v:shape>
593
+ </v:group>
544
594
  <![endif]-->`;
545
- const nonOutlookVideoContent = `<!--[if !mso]><!-->
546
- <table
547
- width="${innerContainerWidth}"
548
- cellpadding="0"
549
- cellspacing="0"
550
- border="0"
551
- role="presentation"
552
- align="${style?.textAlign || "left"}"
553
- style="
554
- max-width: ${innerContainerWidth}px;
555
- width: 100%;
556
- height: ${calculatedHeight}px;
557
- background-color: ${style?.backgroundColor || "#FFFFFF"};
558
- background-image: url('${resolvedThumbnail}');
559
- background-size: cover;
560
- background-position: center;
561
- background-repeat: no-repeat;
562
- box-sizing: border-box;
563
- border: ${borderWidth}px ${style?.borderStyle || "solid"} ${borderColor};
564
- border-radius: ${borderRadius}px;
565
- "
566
- >
567
- <tr>
568
- <td style="padding: 0; height: ${calculatedHeight}px; text-align: center; vertical-align: middle;" valign="middle">
569
- <a href="${videoLink}" target="_blank" style="display:inline-block; border: 0; outline: none; text-decoration: none;">
570
- <img
571
- src="https://app-rsrc.getbee.io/public/resources/components/widgetBar/video-content-icon-sets/light/type-01.png"
572
- width="${playIconWidth}"
573
- alt="Play"
574
- style="display: block;
575
- border: 0;
576
- outline: none;
577
- text-decoration: none;
578
- height: auto;"
579
- />
580
- </a>
581
- </td>
582
- </tr>
583
- </table>
595
+ const nonOutlookVideoContent = `<!--[if !mso]><!-->
596
+ <table
597
+ width="${innerContainerWidth}"
598
+ cellpadding="0"
599
+ cellspacing="0"
600
+ border="0"
601
+ role="presentation"
602
+ align="${style?.textAlign || "left"}"
603
+ style="
604
+ max-width: ${innerContainerWidth}px;
605
+ width: 100%;
606
+ height: ${calculatedHeight}px;
607
+ background-color: ${style?.backgroundColor || "#FFFFFF"};
608
+ background-image: url('${resolvedThumbnail}');
609
+ background-size: contain;
610
+ background-position: center;
611
+ background-repeat: no-repeat;
612
+ box-sizing: border-box;
613
+ border: ${borderWidth}px ${style?.borderStyle || "solid"} ${borderColor};
614
+ border-radius: ${borderRadius}px;
615
+ "
616
+ >
617
+ <tr>
618
+ <td style="padding: 0; height: ${calculatedHeight}px; text-align: center; vertical-align: middle;" valign="middle">
619
+ <a href="${videoLink}" target="_blank" style="display:inline-block; border: 0; outline: none; text-decoration: none;">
620
+ <img
621
+ src="https://app-rsrc.getbee.io/public/resources/components/widgetBar/video-content-icon-sets/light/type-01.png"
622
+ width="${playIconWidth}"
623
+ alt="Play"
624
+ style="display: block;
625
+ border: 0;
626
+ outline: none;
627
+ text-decoration: none;
628
+ height: auto;"
629
+ />
630
+ </a>
631
+ </td>
632
+ </tr>
633
+ </table>
584
634
  <!--<![endif]-->`;
585
635
  const videoContent = `${outlookVideoContent}${nonOutlookVideoContent}`;
586
- const wrapperHtml = `
587
- <table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="margin:0; padding:0; border-collapse: collapse;" class="${visibilityClass}">
588
- <tr>
589
- <td align="${style?.textAlign || "left"}" style="padding:0; ${outerContainerStyles}">
590
- <table border="0" cellpadding="0" cellspacing="0" role="presentation"
591
- align="${style?.textAlign || "left"}"
592
- style="
593
- margin:0;
594
- max-width:${cellWidthInPx}px;
595
- width:${percentWidth};
596
- border-collapse:collapse;
597
- ">
598
- <tr>
599
- <td align="${style?.textAlign || "left"}" style="text-align:${style?.textAlign || "left"}; padding:0;">
600
- ${videoContent}
601
- </td>
602
- </tr>
603
- </table>
604
- </td>
605
- </tr>
606
- </table>
636
+ const wrapperHtml = `
637
+ <table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="margin:0; padding:0; border-collapse: collapse; max-width:600px;" class="${visibilityClass}">
638
+ <tr>
639
+ <td align="${style?.textAlign || "left"}" style="padding:0; ${outerContainerStyles}">
640
+ <table border="0" cellpadding="0" cellspacing="0" role="presentation"
641
+ align="${style?.textAlign || "left"}"
642
+ style="
643
+ margin:0;
644
+ max-width:${cellWidthInPx}px;
645
+ width:${percentWidth};
646
+ border-collapse:collapse;
647
+ ">
648
+ <tr>
649
+ <td align="${style?.textAlign || "left"}" style="text-align:${style?.textAlign || "left"}; padding:0;">
650
+ ${videoContent}
651
+ </td>
652
+ </tr>
653
+ </table>
654
+ </td>
655
+ </tr>
656
+ </table>
607
657
  `;
608
658
  return wrapperHtml;
609
659
  }
@@ -623,154 +673,6 @@ function computeArcSize(borderRadius, widthPx) {
623
673
  const px = parseFloat(s.replace("px", "")) || 0;
624
674
  return Math.min(px / widthPx, 1).toFixed(2);
625
675
  }
626
- // ---------- Updated convertShapeBlock function ----------
627
- // async function convertShapeBlock(blockData: IBlockData) {
628
- // const { style, props } = blockData.data;
629
- // const { shape, text, imageUrl } = props as any;
630
- // const {
631
- // width = "100",
632
- // height = "150",
633
- // padding = {},
634
- // backgroundColor = "#2F80ED",
635
- // borderRadius,
636
- // borderWidth = 0,
637
- // borderStyle = "solid",
638
- // borderColor = "transparent",
639
- // customCss,
640
- // shapeColor,
641
- // alignment = "left",
642
- // msoBakeImageWithText,
643
- // color = "#000000",
644
- // fontSize = 14,
645
- // verticalAlign = "center",
646
- // } = style || {};
647
- // const borderRadiusMap: Record<string, string> = {
648
- // rectangle: "0",
649
- // rounded: "10px",
650
- // circle: "50%",
651
- // oval: "50%",
652
- // };
653
- // let resolvedBorderRadius = borderRadius || borderRadiusMap[shape] || "0";
654
- // let resolvedWidthPx =
655
- // typeof width === "number"
656
- // ? width
657
- // : parseInt(width.toString().replace("px", ""), 10) || 100;
658
- // let resolvedHeightPx =
659
- // typeof height === "number"
660
- // ? height
661
- // : parseInt(height.toString().replace("px", ""), 10) || 150;
662
- // // --- Shape specific constraints ---
663
- // if (shape === "circle") {
664
- // const side = Math.min(resolvedWidthPx, resolvedHeightPx);
665
- // resolvedWidthPx = side;
666
- // resolvedHeightPx = side;
667
- // resolvedBorderRadius = "50%";
668
- // } else if (shape === "oval") {
669
- // resolvedBorderRadius = "50% / 50%";
670
- // }
671
- // const finalBackgroundColor = shapeColor || backgroundColor;
672
- // const alignmentStyles = {
673
- // left: "margin-right:auto;margin-left:0;",
674
- // center: "margin-left:auto;margin-right:auto;",
675
- // right: "margin-left:auto;margin-right:0;",
676
- // };
677
- // const alignmentStyle =
678
- // alignmentStyles[alignment as keyof typeof alignmentStyles] || "";
679
- // const verticalAlignStyles = {
680
- // top: "align-items:flex-start;padding-top:8px;",
681
- // center: "align-items:center;",
682
- // bottom: "align-items:flex-end;padding-bottom:8px;",
683
- // };
684
- // const verticalAlignStyle =
685
- // verticalAlignStyles[verticalAlign as keyof typeof verticalAlignStyles] ||
686
- // verticalAlignStyles.center;
687
- // // Text styling (safe across clients)
688
- // const textSizeStyle = `font-size:${fontSize}px;line-height:1.3;word-break:break-word;overflow-wrap:break-word;text-align:center;color:${color};`;
689
- // // ============================
690
- // // Modern HTML (non-MSO)
691
- // // ============================
692
- // let nonMsoContent = "";
693
- // if (imageUrl && text) {
694
- // nonMsoContent = `
695
- // <div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
696
- // border:${borderWidth}px ${borderStyle} ${borderColor};
697
- // border-radius:${resolvedBorderRadius};
698
- // background:${finalBackgroundColor} url('${imageUrl}') center/cover no-repeat;
699
- // overflow:hidden;${alignmentStyle}${customCss || ""}">
700
- // <div style="width:100%;height:100%;display:flex;${verticalAlignStyle}justify-content:center;overflow:hidden;">
701
- // <div style="${textSizeStyle}padding:6px;max-width:90%;-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden;">
702
- // ${text}
703
- // </div>
704
- // </div>
705
- // </div>`;
706
- // } else if (imageUrl) {
707
- // nonMsoContent = `
708
- // <div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
709
- // border:${borderWidth}px ${borderStyle} ${borderColor};
710
- // border-radius:${resolvedBorderRadius};
711
- // overflow:hidden;${alignmentStyle}${customCss || ""}">
712
- // <img src="${imageUrl}" alt="${text || "shape image"}"
713
- // width="${resolvedWidthPx}" height="${resolvedHeightPx}"
714
- // style="width:100%;height:100%;object-fit:cover;border-radius:${resolvedBorderRadius};display:block;" />
715
- // </div>`;
716
- // } else {
717
- // const circlePadding =
718
- // shape === "circle" ? Math.round(resolvedHeightPx * 0.15) : 8;
719
- // nonMsoContent = `
720
- // <div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
721
- // background:${finalBackgroundColor};
722
- // border:${borderWidth}px ${borderStyle} ${borderColor};
723
- // border-radius:${resolvedBorderRadius};
724
- // overflow:hidden;${alignmentStyle}${customCss || ""}">
725
- // <div style="width:100%;height:100%;display:flex;${verticalAlignStyle}justify-content:center;padding:${circlePadding}px;box-sizing:border-box;">
726
- // <div style="${textSizeStyle}max-width:90%;overflow:hidden;">
727
- // ${text || ""}
728
- // </div>
729
- // </div>
730
- // </div>`;
731
- // }
732
- // // ============================
733
- // // Outlook (VML) version
734
- // // ============================
735
- // const outlookContent = await appendOutlookForShape(
736
- // nonMsoContent,
737
- // resolvedWidthPx,
738
- // resolvedWidthPx,
739
- // {
740
- // shape,
741
- // imageUrl,
742
- // backgroundColor,
743
- // shapeColor,
744
- // borderWidth,
745
- // borderColor,
746
- // borderRadius: resolvedBorderRadius,
747
- // heightPx: resolvedHeightPx,
748
- // text,
749
- // textColor: color,
750
- // textSize: fontSize,
751
- // verticalAlign,
752
- // alignment,
753
- // padding,
754
- // msoBakeImageWithText,
755
- // }
756
- // );
757
- // // ============================
758
- // // Final combined block
759
- // // ============================
760
- // return `
761
- // <table width="100%" style="border-collapse:collapse;table-layout:fixed;">
762
- // <tr>
763
- // <td style="padding:${padding.top || 0}px ${padding.right || 0}px ${
764
- // padding.bottom || 0
765
- // }px ${padding.left || 0}px;text-align:${alignment};">
766
- // ${outlookContent}
767
- // <!--[if !mso]><!-->
768
- // ${nonMsoContent}
769
- // <!--<![endif]-->
770
- // </td>
771
- // </tr>
772
- // </table>`;
773
- // }
774
676
  async function convertShapeBlock(blockData) {
775
677
  const { style, props } = blockData.data;
776
678
  const { shape, text, imageUrl } = props;
@@ -833,44 +735,44 @@ async function convertShapeBlock(blockData) {
833
735
  let nonMsoContent = "";
834
736
  // --- Case 1: Image + Text ---
835
737
  if (imageUrl && text) {
836
- nonMsoContent = `
837
- <div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
838
- border:${borderWidth}px ${borderStyle} ${borderColor};
839
- border-radius:${resolvedBorderRadius};
840
- background:${finalBackgroundColor} url('${imageUrl}') center/cover no-repeat;
841
- overflow:hidden;${alignmentStyle}${customCss || ""}">
842
- <div style="width:100%;height:100%;display:flex;justify-content:${flexJustify};align-items:${flexAlign};overflow:hidden;padding:6px;box-sizing:border-box;">
843
- <div style="${textSizeStyle}text-align:${textAlignStyle};max-width:90%;overflow:hidden;">
844
- ${text}
845
- </div>
846
- </div>
738
+ nonMsoContent = `
739
+ <div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
740
+ border:${borderWidth}px ${borderStyle} ${borderColor};
741
+ border-radius:${resolvedBorderRadius};
742
+ background:${finalBackgroundColor} url('${imageUrl}') center/cover no-repeat;
743
+ overflow:hidden;${alignmentStyle}${customCss || ""}">
744
+ <div style="width:100%;height:100%;display:flex;justify-content:${flexJustify};align-items:${flexAlign};overflow:hidden;padding:6px;box-sizing:border-box;">
745
+ <div style="${textSizeStyle}text-align:${textAlignStyle};max-width:90%;overflow:hidden;">
746
+ ${text}
747
+ </div>
748
+ </div>
847
749
  </div>`;
848
750
  }
849
751
  // --- Case 2: Image only ---
850
752
  else if (imageUrl) {
851
- nonMsoContent = `
852
- <div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
853
- border:${borderWidth}px ${borderStyle} ${borderColor};
854
- border-radius:${resolvedBorderRadius};
855
- overflow:hidden;${alignmentStyle}${customCss || ""}">
856
- <img src="${imageUrl}" alt="${text || "shape image"}"
857
- width="${resolvedWidthPx}" height="${resolvedHeightPx}"
858
- style="width:100%;height:100%;object-fit:cover;border-radius:${resolvedBorderRadius};display:block;" />
753
+ nonMsoContent = `
754
+ <div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
755
+ border:${borderWidth}px ${borderStyle} ${borderColor};
756
+ border-radius:${resolvedBorderRadius};
757
+ overflow:hidden;${alignmentStyle}${customCss || ""}">
758
+ <img src="${imageUrl}" alt="${text || "shape image"}"
759
+ width="${resolvedWidthPx}" height="${resolvedHeightPx}"
760
+ style="width:100%;height:100%;object-fit:cover;border-radius:${resolvedBorderRadius};display:block;" />
859
761
  </div>`;
860
762
  }
861
763
  // --- Case 3: Text only ---
862
764
  else {
863
- nonMsoContent = `
864
- <div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
865
- background:${finalBackgroundColor};
866
- border:${borderWidth}px ${borderStyle} ${borderColor};
867
- border-radius:${resolvedBorderRadius};
868
- overflow:hidden;${alignmentStyle}${customCss || ""}">
869
- <div style="width:100%;height:100%;display:flex;justify-content:${flexJustify};align-items:${flexAlign};padding:8px;box-sizing:border-box;">
870
- <div style="${textSizeStyle}text-align:${textAlignStyle};max-width:90%;overflow:hidden;">
871
- ${text || ""}
872
- </div>
873
- </div>
765
+ nonMsoContent = `
766
+ <div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
767
+ background:${finalBackgroundColor};
768
+ border:${borderWidth}px ${borderStyle} ${borderColor};
769
+ border-radius:${resolvedBorderRadius};
770
+ overflow:hidden;${alignmentStyle}${customCss || ""}">
771
+ <div style="width:100%;height:100%;display:flex;justify-content:${flexJustify};align-items:${flexAlign};padding:8px;box-sizing:border-box;">
772
+ <div style="${textSizeStyle}text-align:${textAlignStyle};max-width:90%;overflow:hidden;">
773
+ ${text || ""}
774
+ </div>
775
+ </div>
874
776
  </div>`;
875
777
  }
876
778
  // Outlook (VML) fallback
@@ -893,16 +795,16 @@ async function convertShapeBlock(blockData) {
893
795
  msoBakeImageWithText,
894
796
  }, visibilityClass);
895
797
  // Combine into table wrapper
896
- return `
897
- <table width="100%" style="border-collapse:collapse;table-layout:fixed;" class="${visibilityClass}">
898
- <tr>
899
- <td style="padding:${padding.top || 0}px ${padding.right || 0}px ${padding.bottom || 0}px ${padding.left || 0}px;text-align:${alignment};">
900
- ${outlookContent}
901
- <!--[if !mso]><!-->
902
- ${nonMsoContent}
903
- <!--<![endif]-->
904
- </td>
905
- </tr>
798
+ return `
799
+ <table width="100%" style="border-collapse:collapse;table-layout:fixed;max-width:600px;" class="${visibilityClass}">
800
+ <tr>
801
+ <td style="padding:${padding.top || 0}px ${padding.right || 0}px ${padding.bottom || 0}px ${padding.left || 0}px;text-align:${alignment};">
802
+ ${outlookContent}
803
+ <!--[if !mso]><!-->
804
+ ${nonMsoContent}
805
+ <!--<![endif]-->
806
+ </td>
807
+ </tr>
906
808
  </table>`;
907
809
  }
908
810
  // ---------- Updated VML builder ----------
@@ -935,24 +837,24 @@ function buildVMLShape({ shape, widthPx, heightPx, imageUrl, backgroundColor, bo
935
837
  const safeFontSize = Math.max(Math.round(textSize), 10);
936
838
  // Build the textbox with table/cell for reliable vertical centering in Outlook
937
839
  const textboxMarkup = text && !msoHasBakedText
938
- ? `<v:textbox inset="6pt,6pt,6pt,6pt" style="mso-fit-shape-to-text:false;">
939
- <div style="display:table;width:100%;height:100%;">
940
- <div style="display:table-cell;vertical-align:${vAlign};text-align:${hAlign};padding:0 6px;">
941
- <div style="color:${textColor};font-family:Arial, sans-serif;font-size:${safeFontSize}px;line-height:1.3;word-wrap:break-word;">
942
- ${text}
943
- </div>
944
- </div>
945
- </div>
840
+ ? `<v:textbox inset="6pt,6pt,6pt,6pt" style="mso-fit-shape-to-text:false;">
841
+ <div style="display:table;width:100%;height:100%;">
842
+ <div style="display:table-cell;vertical-align:${vAlign};text-align:${hAlign};padding:0 6px;">
843
+ <div style="color:${textColor};font-family:Arial, sans-serif;font-size:${safeFontSize}px;line-height:1.3;word-wrap:break-word;">
844
+ ${text}
845
+ </div>
846
+ </div>
847
+ </div>
946
848
  </v:textbox>`
947
849
  : `<v:textbox inset="0,0,0,0"><div style="display:none;">.</div></v:textbox>`;
948
850
  // Return VML shape
949
- return `
950
- <v:${tag} xmlns:v="urn:schemas-microsoft-com:vml"
951
- style="width:${widthPx}px;height:${heightPx}px;display:inline-block;"
952
- ${borderAttrs}
953
- fill="true" fillcolor="${fillColor}"${extraAttr}>
954
- ${fillMarkup}
955
- ${textboxMarkup}
851
+ return `
852
+ <v:${tag} xmlns:v="urn:schemas-microsoft-com:vml"
853
+ style="width:${widthPx}px;height:${heightPx}px;display:inline-block;"
854
+ ${borderAttrs}
855
+ fill="true" fillcolor="${fillColor}"${extraAttr}>
856
+ ${fillMarkup}
857
+ ${textboxMarkup}
956
858
  </v:${tag}>`;
957
859
  }
958
860
  function appendOutlookForShape(content, outerContainerWidth, innerContainerWidth, opts, visibilityClass) {
@@ -980,28 +882,28 @@ function appendOutlookForShape(content, outerContainerWidth, innerContainerWidth
980
882
  const shouldHideInOutlook = visibilityClass.includes("hide-desktop");
981
883
  // Fix: Properly handle Outlook visibility with conditional comments
982
884
  if (shouldHideInOutlook) {
983
- return `<!--[if !mso]><!-->
984
- <table align="${align}" border="0" cellpadding="0" cellspacing="0"
985
- style="width:${widthPx}px;height:${heightPx}px;border-collapse:collapse;" class="${visibilityClass}">
986
- <tr>
987
- <td valign="${valign}"
988
- style="padding:${pad.top || 0}px ${pad.right || 0}px ${pad.bottom || 0}px ${pad.left || 0}px;">
989
- ${vml}
990
- </td>
991
- </tr>
992
- </table>
885
+ return `<!--[if !mso]><!-->
886
+ <table align="${align}" border="0" cellpadding="0" cellspacing="0"
887
+ style="width:${widthPx}px;height:${heightPx}px;border-collapse:collapse;" class="${visibilityClass}">
888
+ <tr>
889
+ <td valign="${valign}"
890
+ style="padding:${pad.top || 0}px ${pad.right || 0}px ${pad.bottom || 0}px ${pad.left || 0}px;">
891
+ ${vml}
892
+ </td>
893
+ </tr>
894
+ </table>
993
895
  <!--<![endif]-->`;
994
896
  }
995
- return `<!--[if mso]>
996
- <table align="${align}" border="0" cellpadding="0" cellspacing="0"
997
- style="width:${widthPx}px;height:${heightPx}px;border-collapse:collapse;" class="${visibilityClass}">
998
- <tr>
999
- <td valign="${valign}"
1000
- style="padding:${pad.top || 0}px ${pad.right || 0}px ${pad.bottom || 0}px ${pad.left || 0}px;">
1001
- ${vml}
1002
- </td>
1003
- </tr>
1004
- </table>
897
+ return `<!--[if mso]>
898
+ <table align="${align}" border="0" cellpadding="0" cellspacing="0"
899
+ style="width:${widthPx}px;height:${heightPx}px;border-collapse:collapse;" class="${visibilityClass}">
900
+ <tr>
901
+ <td valign="${valign}"
902
+ style="padding:${pad.top || 0}px ${pad.right || 0}px ${pad.bottom || 0}px ${pad.left || 0}px;">
903
+ ${vml}
904
+ </td>
905
+ </tr>
906
+ </table>
1005
907
  <![endif]-->`;
1006
908
  }
1007
909
  function convertVerticalDividerBlockToHtml(blockData) {
@@ -1014,19 +916,19 @@ function convertVerticalDividerBlockToHtml(blockData) {
1014
916
  pxChanges: allPxAttributes,
1015
917
  });
1016
918
  // Outlook-safe vertical divider
1017
- const dividerContent = `
1018
- <table cellpadding="0" cellspacing="0" border="0" align="center" style="width:auto; ${convertedStyle}">
1019
- <tr>
1020
- <td style="vertical-align: middle; text-align: center;">
1021
- <!--[if mso | IE]>
1022
- <v:rect xmlns:v="urn:schemas-microsoft-com:vml" fillcolor="${dividerColor}" style="width:${width}px;height:${height}px;" stroke="f"></v:rect>
1023
- <![endif]-->
1024
- <!--[if !mso]><!-- -->
1025
- <div style="display:inline-block;width:${width}px;height:${height}px;background:${dividerColor};line-height:0;font-size:0;">&nbsp;</div>
1026
- <!--<![endif]-->
1027
- </td>
1028
- </tr>
1029
- </table>
919
+ const dividerContent = `
920
+ <table cellpadding="0" cellspacing="0" border="0" align="center" style="width:auto; ${convertedStyle}">
921
+ <tr>
922
+ <td style="vertical-align: middle; text-align: center;">
923
+ <!--[if mso | IE]>
924
+ <v:rect xmlns:v="urn:schemas-microsoft-com:vml" fillcolor="${dividerColor}" style="width:${width}px;height:${height}px;" stroke="f"></v:rect>
925
+ <![endif]-->
926
+ <!--[if !mso]><!-- -->
927
+ <div style="display:inline-block;width:${width}px;height:${height}px;background:${dividerColor};line-height:0;font-size:0;">&nbsp;</div>
928
+ <!--<![endif]-->
929
+ </td>
930
+ </tr>
931
+ </table>
1030
932
  `;
1031
933
  return appendOutlookSupport(dividerContent, convertedStyle, visibilityClass);
1032
934
  }
package/package.json CHANGED
@@ -1,33 +1,33 @@
1
- {
2
- "name": "email-builder-utils",
3
- "version": "1.1.31",
4
- "main": "dist/index.js",
5
- "types": "dist/index.d.ts",
6
- "files": [
7
- "dist"
8
- ],
9
- "scripts": {
10
- "test": "echo \"Error: no test specified\" && exit 1",
11
- "build": "tsc",
12
- "start": "npm run build && node dist/index.js"
13
- },
14
- "repository": {
15
- "type": "git",
16
- "url": "git+https://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@github.com/Biztecno-Infra/email-builder-utils.git"
17
- },
18
- "author": "",
19
- "license": "ISC",
20
- "bugs": {
21
- "url": "https://github.com/Biztecno-Infra/email-builder-utils/issues"
22
- },
23
- "homepage": "https://github.com/Biztecno-Infra/email-builder-utils#readme",
24
- "description": "",
25
- "devDependencies": {
26
- "@types/node": "^22.13.10",
27
- "@types/pngjs": "^6.0.5",
28
- "typescript": "^5.8.2"
29
- },
30
- "dependencies": {
31
- "jimp": "^1.6.0"
32
- }
33
- }
1
+ {
2
+ "name": "email-builder-utils",
3
+ "version": "1.1.35",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1",
11
+ "build": "tsc",
12
+ "start": "npm run build && node dist/index.js"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://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@github.com/Biztecno-Infra/email-builder-utils.git"
17
+ },
18
+ "author": "",
19
+ "license": "ISC",
20
+ "bugs": {
21
+ "url": "https://github.com/Biztecno-Infra/email-builder-utils/issues"
22
+ },
23
+ "homepage": "https://github.com/Biztecno-Infra/email-builder-utils#readme",
24
+ "description": "",
25
+ "devDependencies": {
26
+ "@types/node": "^22.13.10",
27
+ "@types/pngjs": "^6.0.5",
28
+ "typescript": "^5.8.2"
29
+ },
30
+ "dependencies": {
31
+ "jimp": "^1.6.0"
32
+ }
33
+ }