email-builder-utils 1.1.27 → 1.1.29

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.
@@ -9,7 +9,8 @@ export declare enum BlockType {
9
9
  DIVIDER = "Divider",
10
10
  EMAILLAYOUT = "EmailLayout",
11
11
  VIDEO = "Video",
12
- SHAPE = "Shape"
12
+ SHAPE = "Shape",
13
+ VDivider = "VDivider"
13
14
  }
14
15
  export declare enum visibility {
15
16
  PUBLIC = "PUBLIC",
@@ -1 +1 @@
1
- {"version":3,"file":"Template.d.ts","sourceRoot":"","sources":["../../src/types/Template.ts"],"names":[],"mappings":"AAAA,oBAAY,SAAS;IACnB,IAAI,SAAS;IACb,KAAK,UAAU;IACf,MAAM,WAAW;IACjB,IAAI,YAAY;IAChB,KAAK,UAAU;IACf,QAAQ,WAAW;IACnB,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,WAAW,gBAAgB;IAC3B,KAAK,UAAU;IACf,KAAK,UAAU;CAChB;AAED,oBAAY,UAAU;IACpB,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,WAAW,gBAAgB;CAC5B;AAED,UAAU,MAAM;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,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;CAChB;AAED,UAAU,MAAM;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE;QACJ,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,MAAM,EAAE,CAAC;KACvB,CAAC;CACH"}
1
+ {"version":3,"file":"Template.d.ts","sourceRoot":"","sources":["../../src/types/Template.ts"],"names":[],"mappings":"AAAA,oBAAY,SAAS;IACnB,IAAI,SAAS;IACb,KAAK,UAAU;IACf,MAAM,WAAW;IACjB,IAAI,YAAY;IAChB,KAAK,UAAU;IACf,QAAQ,WAAW;IACnB,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,WAAW,gBAAgB;IAC3B,KAAK,UAAU;IACf,KAAK,UAAU;IACf,QAAQ,aAAa;CACtB;AAED,oBAAY,UAAU;IACpB,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,WAAW,gBAAgB;CAC5B;AAED,UAAU,MAAM;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,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;CAGhB;AAED,UAAU,MAAM;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE;QACJ,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,MAAM,EAAE,CAAC;KACvB,CAAC;CACH"}
@@ -14,6 +14,7 @@ var BlockType;
14
14
  BlockType["EMAILLAYOUT"] = "EmailLayout";
15
15
  BlockType["VIDEO"] = "Video";
16
16
  BlockType["SHAPE"] = "Shape";
17
+ BlockType["VDivider"] = "VDivider";
17
18
  })(BlockType || (exports.BlockType = BlockType = {}));
18
19
  var visibility;
19
20
  (function (visibility) {
@@ -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;CAChB;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;AAOD,eAAO,MAAM,gBAAgB,kDAAkD,CAAC;AAiDhF,wBAAsB,aAAa,CAAC,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,mBAqB9F;AAsVD,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,mBAoK5E"}
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;CAGhB;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;AAsbD,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,mBA8K5E"}
@@ -6,7 +6,12 @@ exports.convertVideoBlock = convertVideoBlock;
6
6
  const jimp_1 = require("jimp");
7
7
  const types_1 = require("../types");
8
8
  const common_1 = require("./common");
9
- const addPxToAttributes = ["fontSize", "lineHeight", "borderRadius", "borderWidth"];
9
+ const addPxToAttributes = [
10
+ "fontSize",
11
+ "lineHeight",
12
+ "borderRadius",
13
+ "borderWidth",
14
+ ];
10
15
  const addPxOrPerToAttributes = ["width", "height"];
11
16
  const allPxAttributes = [...addPxToAttributes, ...addPxOrPerToAttributes];
12
17
  exports.tableCommonStyle = "border-collapse:collapse; table-layout:fixed;";
@@ -37,7 +42,8 @@ function buildStyles(style, { pxChanges, perChanges }) {
37
42
  return;
38
43
  if (value === undefined || value === null || value === "")
39
44
  return;
40
- if ((key === "padding" || key === "buttonPadding") && typeof value === "object") {
45
+ if ((key === "padding" || key === "buttonPadding") &&
46
+ typeof value === "object") {
41
47
  const padding = value;
42
48
  value = `${padding.top}px ${padding.right}px ${padding.bottom}px ${padding.left}px`;
43
49
  }
@@ -72,6 +78,8 @@ async function convertToHtml(blockData, rootData, cellWidthInPx) {
72
78
  return convertVideoBlock(blockData, cellWidthInPx);
73
79
  case types_1.BlockType.SHAPE:
74
80
  return await convertShapeBlock(blockData);
81
+ case types_1.BlockType.VDivider:
82
+ return convertVerticalDividerBlockToHtml(blockData);
75
83
  default:
76
84
  return "";
77
85
  }
@@ -82,16 +90,17 @@ function appendOutlookSupport(content, contentStyle) {
82
90
  `;
83
91
  }
84
92
  function convertDividerBlockToHtml(blockData) {
85
- const { style } = blockData.data;
86
- const { thickness, dividerColor, ...rest } = style;
93
+ const { style, props } = blockData.data;
94
+ const { thickness, dividerColor, width, ...rest } = style;
87
95
  const convertedStyle = buildStyles(rest, {
88
96
  perChanges: [],
89
97
  pxChanges: allPxAttributes,
90
98
  });
99
+ const dividerWidth = width || "100%";
91
100
  const dividerContent = `
92
- <table width="100%" cellpadding="0" cellspacing="0">
101
+ <table width="${dividerWidth}%" cellpadding="0" cellspacing="0">
93
102
  <tr>
94
- <td height="${thickness}" style="font-size:1px; line-height:1px; background:${dividerColor};">&nbsp;</td>
103
+ <td height="${thickness}" style="font-size:1px; line-height:1px; background:${dividerColor}; width:${dividerWidth};">&nbsp;</td>
95
104
  </tr>
96
105
  </table>
97
106
  `;
@@ -124,20 +133,36 @@ function convertSpacerBlockToHtml(blockData) {
124
133
  function convertTextBlock(blockData) {
125
134
  const { style, props } = blockData.data;
126
135
  const { width, backgroundColor, padding, borderRadius, borderStyle, borderColor, borderWidth, textContainerBackgroundColor, textContainerPadding, ...rest } = style;
127
- const textBoxStyle = { width, backgroundColor, padding, borderRadius, borderStyle, borderColor, borderWidth };
136
+ const textBoxStyle = {
137
+ width,
138
+ backgroundColor,
139
+ padding,
140
+ borderRadius,
141
+ borderStyle,
142
+ borderColor,
143
+ borderWidth,
144
+ };
128
145
  const convertedTextStyle = buildStyles(textBoxStyle, {
129
146
  perChanges: [],
130
147
  pxChanges: allPxAttributes,
131
148
  });
132
- const styles = buildStyles({ padding: textContainerPadding, backgroundColor: textContainerBackgroundColor, ...rest }, {
149
+ const styles = buildStyles({
150
+ padding: textContainerPadding,
151
+ backgroundColor: textContainerBackgroundColor,
152
+ ...rest,
153
+ }, {
133
154
  perChanges: [],
134
155
  pxChanges: allPxAttributes,
135
156
  });
136
- const sanitizedText = (props.text ?? "").replaceAll(/<p>/g, "<div>").replaceAll(/<\/p>/g, "</div>");
157
+ const sanitizedText = (props.text ?? "")
158
+ .replaceAll(/<p>/g, "<div>")
159
+ .replaceAll(/<\/p>/g, "</div>");
137
160
  const navigateToUrl = props.navigateToUrl || "";
138
161
  const convertedTextBox = `<div style="display: inline-block; max-width: 100%; box-sizing: border-box; ${convertedTextStyle}">${sanitizedText.replaceAll(/\n/g, "<br>")}</div>`;
139
162
  const textContent = appendOutlookSupport(convertedTextBox, styles);
140
- return navigateToUrl ? `<a href="${navigateToUrl}" rel="noreferrer noopener" style="color:inherit; text-decoration:none; cursor:pointer;">${textContent}</a>` : textContent;
163
+ return navigateToUrl
164
+ ? `<a href="${navigateToUrl}" rel="noreferrer noopener" style="color:inherit; text-decoration:none; cursor:pointer;">${textContent}</a>`
165
+ : textContent;
141
166
  }
142
167
  async function appendOutlookForImage(content, outerContainerWidth, innerContainerWidth, imageUrl, style = {}) {
143
168
  const image = await jimp_1.Jimp.read(imageUrl);
@@ -150,8 +175,12 @@ async function appendOutlookForImage(content, outerContainerWidth, innerContaine
150
175
  const borderColor = style?.borderColor || "transparent";
151
176
  const borderRadius = parseInt(style?.borderRadius) || 0;
152
177
  const useRoundRect = borderRadius > 0;
153
- const arcsize = useRoundRect ? Math.min(borderRadius / scaledHeight, 1).toFixed(2) : "";
154
- const borderAttributes = borderWidth > 0 ? `strokeweight="${borderWidth}px" strokecolor="${borderColor}"` : `stroked="false"`;
178
+ const arcsize = useRoundRect
179
+ ? Math.min(borderRadius / scaledHeight, 1).toFixed(2)
180
+ : "";
181
+ const borderAttributes = borderWidth > 0
182
+ ? `strokeweight="${borderWidth}px" strokecolor="${borderColor}"`
183
+ : `stroked="false"`;
155
184
  const outlookImage = `<!--[if mso]>
156
185
  <v:${useRoundRect ? "roundrect" : "rect"} xmlns:v="urn:schemas-microsoft-com:vml"
157
186
  style="width:${scaledWidth}px;height:${scaledHeight}px;"
@@ -196,7 +225,11 @@ async function convertImageBlock(blockData, cellWidthInPx) {
196
225
  pxChanges: addPxToAttributes,
197
226
  });
198
227
  const imageElement = `<img src="${imageUrl}" alt="${altText}" style="${imageTagStyles}; max-width: ${originalWidth}px; max-height: ${originalHeight}px;" />`;
199
- const innerContainerWidth = ((typeof width === "string" ? parseInt(width.replace("%", "")) : width) / 100) * (cellWidthInPx - (style?.padding?.left || 0) - (style?.padding?.right || 0));
228
+ const innerContainerWidth = ((typeof width === "string" ? parseInt(width.replace("%", "")) : width) /
229
+ 100) *
230
+ (cellWidthInPx -
231
+ (style?.padding?.left || 0) -
232
+ (style?.padding?.right || 0));
200
233
  const outlookImage = await appendOutlookForImage(imageElement, cellWidthInPx, innerContainerWidth, imageUrl, style);
201
234
  const imageContent = appendOutlookSupport(outlookImage, containerStyles);
202
235
  return navigateToUrl
@@ -205,7 +238,9 @@ async function convertImageBlock(blockData, cellWidthInPx) {
205
238
  }
206
239
  function appendOutlookForButton(content, buttonStyle, navigateToUrl, text) {
207
240
  const { width = 200, height = 44, borderRadius = 0, borderColor = "transparent", borderWidth = 0, buttonColor = "none", buttonPadding = { top: 0, bottom: 0, left: 0, right: 0 }, color = "#000000", fontFamily = "Arial, sans-serif", fontSize = 16, fontWeight = 400, } = buttonStyle;
208
- const borderAttributes = borderWidth > 0 ? `strokeweight="${borderWidth}px" strokecolor="${borderColor}"` : `stroked="false"`;
241
+ const borderAttributes = borderWidth > 0
242
+ ? `strokeweight="${borderWidth}px" strokecolor="${borderColor}"`
243
+ : `stroked="false"`;
209
244
  return `
210
245
  <!--[if mso]>
211
246
  <v:${borderRadius ? "roundrect" : "rect"} xmlns:v="urn:schemas-microsoft-com:vml" href="${navigateToUrl}"
@@ -387,7 +422,9 @@ async function convertVideoBlock(blockData, cellWidthInPx) {
387
422
  <v:rect fill="t" style="position:absolute;width:${innerContainerWidth}px;height:${calculatedHeight}px; stroked="t"
388
423
  strokeweight="${borderWidth}px"
389
424
  strokecolor="${borderColor}"
390
- ${borderRadius > 0 ? `arcsize="${Math.min(borderRadius / calculatedHeight, 1).toFixed(2)}"` : ""}
425
+ ${borderRadius > 0
426
+ ? `arcsize="${Math.min(borderRadius / calculatedHeight, 1).toFixed(2)}"`
427
+ : ""}
391
428
  >
392
429
  <v:fill src="${resolvedThumbnail}" type="frame" color="${style?.backgroundColor || "#FFFFFF"}"/>
393
430
  </v:rect>
@@ -486,10 +523,278 @@ function computeArcSize(borderRadius, widthPx) {
486
523
  return Math.min(px / widthPx, 1).toFixed(2);
487
524
  }
488
525
  // ---------- Updated convertShapeBlock function ----------
526
+ // async function convertShapeBlock(blockData: IBlockData) {
527
+ // const { style, props } = blockData.data;
528
+ // const { shape, text, imageUrl } = props as any;
529
+ // const {
530
+ // width = "100",
531
+ // height = "150",
532
+ // padding = {},
533
+ // backgroundColor = "#2F80ED",
534
+ // borderRadius,
535
+ // borderWidth = 0,
536
+ // borderStyle = "solid",
537
+ // borderColor = "transparent",
538
+ // customCss,
539
+ // shapeColor,
540
+ // alignment = "left",
541
+ // msoBakeImageWithText,
542
+ // color = "#000000",
543
+ // fontSize = 14,
544
+ // verticalAlign = "center",
545
+ // } = style || {};
546
+ // const borderRadiusMap: Record<string, string> = {
547
+ // rectangle: "0",
548
+ // rounded: "10px",
549
+ // circle: "50%",
550
+ // oval: "50%",
551
+ // };
552
+ // let resolvedBorderRadius = borderRadius || borderRadiusMap[shape] || "0";
553
+ // let resolvedWidthPx =
554
+ // typeof width === "number"
555
+ // ? width
556
+ // : parseInt(width.toString().replace("px", ""), 10) || 100;
557
+ // let resolvedHeightPx =
558
+ // typeof height === "number"
559
+ // ? height
560
+ // : parseInt(height.toString().replace("px", ""), 10) || 150;
561
+ // // --- Shape specific constraints ---
562
+ // if (shape === "circle") {
563
+ // const side = Math.min(resolvedWidthPx, resolvedHeightPx);
564
+ // resolvedWidthPx = side;
565
+ // resolvedHeightPx = side;
566
+ // resolvedBorderRadius = "50%";
567
+ // } else if (shape === "oval") {
568
+ // resolvedBorderRadius = "50% / 50%";
569
+ // }
570
+ // const finalBackgroundColor = shapeColor || backgroundColor;
571
+ // const alignmentStyles = {
572
+ // left: "margin-right:auto;margin-left:0;",
573
+ // center: "margin-left:auto;margin-right:auto;",
574
+ // right: "margin-left:auto;margin-right:0;",
575
+ // };
576
+ // const alignmentStyle =
577
+ // alignmentStyles[alignment as keyof typeof alignmentStyles] || "";
578
+ // const verticalAlignStyles = {
579
+ // top: "align-items:flex-start;padding-top:8px;",
580
+ // center: "align-items:center;",
581
+ // bottom: "align-items:flex-end;padding-bottom:8px;",
582
+ // };
583
+ // const verticalAlignStyle =
584
+ // verticalAlignStyles[verticalAlign as keyof typeof verticalAlignStyles] ||
585
+ // verticalAlignStyles.center;
586
+ // // Text styling (safe across clients)
587
+ // const textSizeStyle = `font-size:${fontSize}px;line-height:1.3;word-break:break-word;overflow-wrap:break-word;text-align:center;color:${color};`;
588
+ // // ============================
589
+ // // Modern HTML (non-MSO)
590
+ // // ============================
591
+ // let nonMsoContent = "";
592
+ // if (imageUrl && text) {
593
+ // nonMsoContent = `
594
+ // <div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
595
+ // border:${borderWidth}px ${borderStyle} ${borderColor};
596
+ // border-radius:${resolvedBorderRadius};
597
+ // background:${finalBackgroundColor} url('${imageUrl}') center/cover no-repeat;
598
+ // overflow:hidden;${alignmentStyle}${customCss || ""}">
599
+ // <div style="width:100%;height:100%;display:flex;${verticalAlignStyle}justify-content:center;overflow:hidden;">
600
+ // <div style="${textSizeStyle}padding:6px;max-width:90%;-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden;">
601
+ // ${text}
602
+ // </div>
603
+ // </div>
604
+ // </div>`;
605
+ // } else if (imageUrl) {
606
+ // nonMsoContent = `
607
+ // <div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
608
+ // border:${borderWidth}px ${borderStyle} ${borderColor};
609
+ // border-radius:${resolvedBorderRadius};
610
+ // overflow:hidden;${alignmentStyle}${customCss || ""}">
611
+ // <img src="${imageUrl}" alt="${text || "shape image"}"
612
+ // width="${resolvedWidthPx}" height="${resolvedHeightPx}"
613
+ // style="width:100%;height:100%;object-fit:cover;border-radius:${resolvedBorderRadius};display:block;" />
614
+ // </div>`;
615
+ // } else {
616
+ // const circlePadding =
617
+ // shape === "circle" ? Math.round(resolvedHeightPx * 0.15) : 8;
618
+ // nonMsoContent = `
619
+ // <div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
620
+ // background:${finalBackgroundColor};
621
+ // border:${borderWidth}px ${borderStyle} ${borderColor};
622
+ // border-radius:${resolvedBorderRadius};
623
+ // overflow:hidden;${alignmentStyle}${customCss || ""}">
624
+ // <div style="width:100%;height:100%;display:flex;${verticalAlignStyle}justify-content:center;padding:${circlePadding}px;box-sizing:border-box;">
625
+ // <div style="${textSizeStyle}max-width:90%;overflow:hidden;">
626
+ // ${text || ""}
627
+ // </div>
628
+ // </div>
629
+ // </div>`;
630
+ // }
631
+ // // ============================
632
+ // // Outlook (VML) version
633
+ // // ============================
634
+ // const outlookContent = await appendOutlookForShape(
635
+ // nonMsoContent,
636
+ // resolvedWidthPx,
637
+ // resolvedWidthPx,
638
+ // {
639
+ // shape,
640
+ // imageUrl,
641
+ // backgroundColor,
642
+ // shapeColor,
643
+ // borderWidth,
644
+ // borderColor,
645
+ // borderRadius: resolvedBorderRadius,
646
+ // heightPx: resolvedHeightPx,
647
+ // text,
648
+ // textColor: color,
649
+ // textSize: fontSize,
650
+ // verticalAlign,
651
+ // alignment,
652
+ // padding,
653
+ // msoBakeImageWithText,
654
+ // }
655
+ // );
656
+ // // ============================
657
+ // // Final combined block
658
+ // // ============================
659
+ // return `
660
+ // <table width="100%" style="border-collapse:collapse;table-layout:fixed;">
661
+ // <tr>
662
+ // <td style="padding:${padding.top || 0}px ${padding.right || 0}px ${
663
+ // padding.bottom || 0
664
+ // }px ${padding.left || 0}px;text-align:${alignment};">
665
+ // ${outlookContent}
666
+ // <!--[if !mso]><!-->
667
+ // ${nonMsoContent}
668
+ // <!--<![endif]-->
669
+ // </td>
670
+ // </tr>
671
+ // </table>`;
672
+ // }
673
+ // // ---------- Updated VML builder with better text containment ----------
674
+ // function buildVMLShape({
675
+ // shape,
676
+ // widthPx,
677
+ // heightPx,
678
+ // imageUrl,
679
+ // backgroundColor,
680
+ // borderWidth,
681
+ // borderColor,
682
+ // borderRadius,
683
+ // text,
684
+ // textColor = "#000000",
685
+ // textSize = 14,
686
+ // verticalAlign = "center",
687
+ // msoHasBakedText = false,
688
+ // }: any) {
689
+ // // --- Basic setup ---
690
+ // const bw = borderWidth || 0;
691
+ // const bc = borderColor || "transparent";
692
+ // const borderAttrs =
693
+ // bw > 0 ? `strokeweight="${bw}px" strokecolor="${bc}"` : `stroked="false"`;
694
+ // const fillColor = backgroundColor || "#2F80ED";
695
+ // const fillMarkup = `<v:fill ${
696
+ // imageUrl ? `src="${imageUrl}" type="frame" aspect="atleast"` : ""
697
+ // } color="${fillColor}" />`;
698
+ // // --- Shape tag ---
699
+ // let tag = "rect";
700
+ // let extraAttr = "";
701
+ // if (shape === "circle" || shape === "oval") {
702
+ // tag = "oval";
703
+ // } else if (shape === "rounded") {
704
+ // tag = "roundrect";
705
+ // extraAttr = `arcsize="${computeArcSize(borderRadius, widthPx)}"`;
706
+ // }
707
+ // // --- Text alignment ---
708
+ // const vAlignMap = { top: "top", center: "middle", bottom: "bottom" };
709
+ // const vAlign = vAlignMap[verticalAlign as keyof typeof vAlignMap] || "middle";
710
+ // const safeFontSize = Math.max(textSize, 10);
711
+ // // --- Text inside shape ---
712
+ // const textboxMarkup =
713
+ // text && !msoHasBakedText
714
+ // ? `<v:textbox inset="6pt,6pt,6pt,6pt" style="mso-fit-shape-to-text:false;">
715
+ // <div style="display:table;width:100%;height:100%;">
716
+ // <div style="display:table-cell;vertical-align:${vAlign};text-align:center;">
717
+ // <div style="color:${textColor};font-family:Arial, sans-serif;font-size:${safeFontSize}px;line-height:1.3;word-wrap:break-word;">
718
+ // ${text}
719
+ // </div>
720
+ // </div>
721
+ // </div>
722
+ // </v:textbox>`
723
+ // : `<v:textbox inset="0,0,0,0"><div style="display:none;">.</div></v:textbox>`;
724
+ // // --- Final shape markup ---
725
+ // return `
726
+ // <v:${tag} xmlns:v="urn:schemas-microsoft-com:vml"
727
+ // style="width:${widthPx}px;height:${heightPx}px;
728
+ // mso-position-horizontal:center;
729
+ // mso-position-vertical:center;"
730
+ // ${borderAttrs}
731
+ // fill="true" fillcolor="${fillColor}"${extraAttr}>
732
+ // ${fillMarkup}
733
+ // ${textboxMarkup}
734
+ // </v:${tag}>`;
735
+ // }
736
+ // // ---------- Updated appendOutlookForShape ----------
737
+ // async function appendOutlookForShape(
738
+ // content: string,
739
+ // outerContainerWidth: number,
740
+ // innerContainerWidth: number,
741
+ // opts: {
742
+ // shape: string;
743
+ // imageUrl?: string;
744
+ // backgroundColor?: string;
745
+ // shapeColor?: string;
746
+ // borderWidth?: number;
747
+ // borderColor?: string;
748
+ // borderRadius?: string | number;
749
+ // heightPx: number;
750
+ // text?: string;
751
+ // textColor?: string;
752
+ // textSize?: number;
753
+ // verticalAlign?: "top" | "middle" | "bottom";
754
+ // alignment?: "left" | "center" | "right";
755
+ // padding?: { top?: number; right?: number; bottom?: number; left?: number };
756
+ // msoBakeImageWithText?: string;
757
+ // }
758
+ // ) {
759
+ // const widthPx = Math.round(
760
+ // Math.min(outerContainerWidth, innerContainerWidth)
761
+ // );
762
+ // const heightPx = Math.max(1, Math.round(opts.heightPx));
763
+ // const vml = buildVMLShape({
764
+ // shape: opts.shape,
765
+ // widthPx,
766
+ // heightPx,
767
+ // imageUrl: opts.msoBakeImageWithText || opts.imageUrl,
768
+ // backgroundColor: opts.shapeColor || opts.backgroundColor,
769
+ // borderWidth: opts.borderWidth,
770
+ // borderColor: opts.borderColor,
771
+ // borderRadius: opts.borderRadius,
772
+ // text: opts.text,
773
+ // textColor: opts.textColor,
774
+ // textSize: opts.textSize,
775
+ // msoHasBakedText: Boolean(opts.msoBakeImageWithText),
776
+ // });
777
+ // const pad = opts.padding || {};
778
+ // const align = opts.alignment || "left";
779
+ // const valign = opts.verticalAlign || "middle";
780
+ // return `<!--[if mso]>
781
+ // <table align="${align}" border="0" cellpadding="0" cellspacing="0"
782
+ // style="width:${widthPx}px;height:${heightPx}px;">
783
+ // <tr>
784
+ // <td valign="${valign}"
785
+ // style="padding:${pad.top || 0}px ${pad.right || 0}px ${
786
+ // pad.bottom || 0
787
+ // }px ${pad.left || 0}px;">
788
+ // ${vml}
789
+ // </td>
790
+ // </tr>
791
+ // </table>
792
+ // <![endif]-->`;
793
+ // }
489
794
  async function convertShapeBlock(blockData) {
490
795
  const { style, props } = blockData.data;
491
796
  const { shape, text, imageUrl } = props;
492
- const { width = "100", height = "150", padding = {}, backgroundColor = "#2F80ED", borderRadius, borderWidth = 0, borderStyle = "solid", borderColor = "transparent", customCss, shapeColor, alignment = "left", msoBakeImageWithText, color = "#000000", fontSize = 14, verticalAlign = "center", } = style || {};
797
+ const { width = "100", height = "150", padding = {}, backgroundColor = "#2F80ED", borderRadius, borderWidth = 0, borderStyle = "solid", borderColor = "transparent", customCss, shapeColor, alignment = "left", msoBakeImageWithText, color = "#000000", fontSize = 14, textAlign = "center", verticalAlign = "middle", } = style || {};
493
798
  const borderRadiusMap = {
494
799
  rectangle: "0",
495
800
  rounded: "10px",
@@ -503,7 +808,7 @@ async function convertShapeBlock(blockData) {
503
808
  let resolvedHeightPx = typeof height === "number"
504
809
  ? height
505
810
  : parseInt(height.toString().replace("px", ""), 10) || 150;
506
- // --- Shape specific constraints ---
811
+ // --- Shape-specific constraints ---
507
812
  if (shape === "circle") {
508
813
  const side = Math.min(resolvedWidthPx, resolvedHeightPx);
509
814
  resolvedWidthPx = side;
@@ -514,25 +819,38 @@ async function convertShapeBlock(blockData) {
514
819
  resolvedBorderRadius = "50% / 50%";
515
820
  }
516
821
  const finalBackgroundColor = shapeColor || backgroundColor;
822
+ // --- Horizontal alignment for outer container ---
517
823
  const alignmentStyles = {
518
824
  left: "margin-right:auto;margin-left:0;",
519
825
  center: "margin-left:auto;margin-right:auto;",
520
826
  right: "margin-left:auto;margin-right:0;",
521
827
  };
522
828
  const alignmentStyle = alignmentStyles[alignment] || "";
523
- const verticalAlignStyles = {
524
- top: "align-items:flex-start;padding-top:8px;",
525
- center: "align-items:center;",
526
- bottom: "align-items:flex-end;padding-bottom:8px;",
829
+ // --- Text + vertical alignment maps ---
830
+ const textAlignMap = {
831
+ left: "left",
832
+ center: "center",
833
+ right: "right",
834
+ justify: "justify",
527
835
  };
528
- const verticalAlignStyle = verticalAlignStyles[verticalAlign] ||
529
- verticalAlignStyles.center;
530
- // Text styling (safe across clients)
531
- const textSizeStyle = `font-size:${fontSize}px;line-height:1.3;word-break:break-word;overflow-wrap:break-word;text-align:center;color:${color};`;
836
+ const textAlignStyle = textAlignMap[textAlign] || "center";
837
+ const flexJustify = textAlign === "left"
838
+ ? "flex-start"
839
+ : textAlign === "right"
840
+ ? "flex-end"
841
+ : "center";
842
+ const flexAlign = verticalAlign === "top"
843
+ ? "flex-start"
844
+ : verticalAlign === "bottom"
845
+ ? "flex-end"
846
+ : "center";
847
+ // --- Text styling ---
848
+ const textSizeStyle = `font-size:${fontSize}px;line-height:1.3;word-break:break-word;overflow-wrap:break-word;color:${color};`;
532
849
  // ============================
533
850
  // Modern HTML (non-MSO)
534
851
  // ============================
535
852
  let nonMsoContent = "";
853
+ // --- Case 1: Image + Text ---
536
854
  if (imageUrl && text) {
537
855
  nonMsoContent = `
538
856
  <div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
@@ -540,13 +858,14 @@ async function convertShapeBlock(blockData) {
540
858
  border-radius:${resolvedBorderRadius};
541
859
  background:${finalBackgroundColor} url('${imageUrl}') center/cover no-repeat;
542
860
  overflow:hidden;${alignmentStyle}${customCss || ""}">
543
- <div style="width:100%;height:100%;display:flex;${verticalAlignStyle}justify-content:center;overflow:hidden;">
544
- <div style="${textSizeStyle}padding:6px;max-width:90%;-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden;">
861
+ <div style="width:100%;height:100%;display:flex;justify-content:${flexJustify};align-items:${flexAlign};overflow:hidden;padding:6px;box-sizing:border-box;">
862
+ <div style="${textSizeStyle}text-align:${textAlignStyle};max-width:90%;overflow:hidden;">
545
863
  ${text}
546
864
  </div>
547
865
  </div>
548
866
  </div>`;
549
867
  }
868
+ // --- Case 2: Image only ---
550
869
  else if (imageUrl) {
551
870
  nonMsoContent = `
552
871
  <div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
@@ -558,24 +877,22 @@ async function convertShapeBlock(blockData) {
558
877
  style="width:100%;height:100%;object-fit:cover;border-radius:${resolvedBorderRadius};display:block;" />
559
878
  </div>`;
560
879
  }
880
+ // --- Case 3: Text only ---
561
881
  else {
562
- const circlePadding = shape === "circle" ? Math.round(resolvedHeightPx * 0.15) : 8;
563
882
  nonMsoContent = `
564
883
  <div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
565
884
  background:${finalBackgroundColor};
566
885
  border:${borderWidth}px ${borderStyle} ${borderColor};
567
886
  border-radius:${resolvedBorderRadius};
568
887
  overflow:hidden;${alignmentStyle}${customCss || ""}">
569
- <div style="width:100%;height:100%;display:flex;${verticalAlignStyle}justify-content:center;padding:${circlePadding}px;box-sizing:border-box;">
570
- <div style="${textSizeStyle}max-width:90%;overflow:hidden;">
888
+ <div style="width:100%;height:100%;display:flex;justify-content:${flexJustify};align-items:${flexAlign};padding:8px;box-sizing:border-box;">
889
+ <div style="${textSizeStyle}text-align:${textAlignStyle};max-width:90%;overflow:hidden;">
571
890
  ${text || ""}
572
891
  </div>
573
892
  </div>
574
893
  </div>`;
575
894
  }
576
- // ============================
577
- // Outlook (VML) version
578
- // ============================
895
+ // Outlook (VML) fallback
579
896
  const outlookContent = await appendOutlookForShape(nonMsoContent, resolvedWidthPx, resolvedWidthPx, {
580
897
  shape,
581
898
  imageUrl,
@@ -589,13 +906,12 @@ async function convertShapeBlock(blockData) {
589
906
  textColor: color,
590
907
  textSize: fontSize,
591
908
  verticalAlign,
909
+ textAlign, // ✅ added
592
910
  alignment,
593
911
  padding,
594
912
  msoBakeImageWithText,
595
913
  });
596
- // ============================
597
- // Final combined block
598
- // ============================
914
+ // Combine into table wrapper
599
915
  return `
600
916
  <table width="100%" style="border-collapse:collapse;table-layout:fixed;">
601
917
  <tr>
@@ -608,17 +924,14 @@ async function convertShapeBlock(blockData) {
608
924
  </tr>
609
925
  </table>`;
610
926
  }
611
- // ---------- Updated VML builder with better text containment ----------
612
- function buildVMLShape({ shape, widthPx, heightPx, imageUrl, backgroundColor, borderWidth, borderColor, borderRadius, text, textColor = "#000000", textSize = 14, verticalAlign = "center", msoHasBakedText = false, }) {
613
- // --- Basic setup ---
927
+ // ---------- Updated VML builder ----------
928
+ function buildVMLShape({ shape, widthPx, heightPx, imageUrl, backgroundColor, borderWidth, borderColor, borderRadius, text, textColor = "#000000", textSize = 14, verticalAlign = "middle", textAlign = "center", msoHasBakedText = false, }) {
614
929
  const bw = borderWidth || 0;
615
930
  const bc = borderColor || "transparent";
616
- const borderAttrs = bw > 0
617
- ? `strokeweight="${bw}px" strokecolor="${bc}"`
618
- : `stroked="false"`;
931
+ const borderAttrs = bw > 0 ? `strokeweight="${bw}px" strokecolor="${bc}"` : `stroked="false"`;
619
932
  const fillColor = backgroundColor || "#2F80ED";
933
+ // Use frame for img fill so sizing is preserved
620
934
  const fillMarkup = `<v:fill ${imageUrl ? `src="${imageUrl}" type="frame" aspect="atleast"` : ""} color="${fillColor}" />`;
621
- // --- Shape tag ---
622
935
  let tag = "rect";
623
936
  let extraAttr = "";
624
937
  if (shape === "circle" || shape === "oval") {
@@ -628,15 +941,22 @@ function buildVMLShape({ shape, widthPx, heightPx, imageUrl, backgroundColor, bo
628
941
  tag = "roundrect";
629
942
  extraAttr = `arcsize="${computeArcSize(borderRadius, widthPx)}"`;
630
943
  }
631
- // --- Text alignment ---
632
- const vAlignMap = { top: "top", center: "middle", bottom: "bottom" };
944
+ // maps for vml
945
+ const vAlignMap = { top: "top", middle: "middle", bottom: "bottom" };
946
+ const hAlignMap = {
947
+ left: "left",
948
+ center: "center",
949
+ right: "right",
950
+ justify: "left",
951
+ }; // justify -> left fallback in VML
633
952
  const vAlign = vAlignMap[verticalAlign] || "middle";
634
- const safeFontSize = Math.max(textSize, 10);
635
- // --- Text inside shape ---
953
+ const hAlign = hAlignMap[textAlign] || "center";
954
+ const safeFontSize = Math.max(Math.round(textSize), 10);
955
+ // Build the textbox with table/cell for reliable vertical centering in Outlook
636
956
  const textboxMarkup = text && !msoHasBakedText
637
957
  ? `<v:textbox inset="6pt,6pt,6pt,6pt" style="mso-fit-shape-to-text:false;">
638
958
  <div style="display:table;width:100%;height:100%;">
639
- <div style="display:table-cell;vertical-align:${vAlign};text-align:center;">
959
+ <div style="display:table-cell;vertical-align:${vAlign};text-align:${hAlign};padding:0 6px;">
640
960
  <div style="color:${textColor};font-family:Arial, sans-serif;font-size:${safeFontSize}px;line-height:1.3;word-wrap:break-word;">
641
961
  ${text}
642
962
  </div>
@@ -644,20 +964,17 @@ function buildVMLShape({ shape, widthPx, heightPx, imageUrl, backgroundColor, bo
644
964
  </div>
645
965
  </v:textbox>`
646
966
  : `<v:textbox inset="0,0,0,0"><div style="display:none;">.</div></v:textbox>`;
647
- // --- Final shape markup ---
967
+ // Return VML shape
648
968
  return `
649
969
  <v:${tag} xmlns:v="urn:schemas-microsoft-com:vml"
650
- style="width:${widthPx}px;height:${heightPx}px;
651
- mso-position-horizontal:center;
652
- mso-position-vertical:center;"
970
+ style="width:${widthPx}px;height:${heightPx}px;display:inline-block;"
653
971
  ${borderAttrs}
654
972
  fill="true" fillcolor="${fillColor}"${extraAttr}>
655
973
  ${fillMarkup}
656
974
  ${textboxMarkup}
657
975
  </v:${tag}>`;
658
976
  }
659
- // ---------- Updated appendOutlookForShape ----------
660
- async function appendOutlookForShape(content, outerContainerWidth, innerContainerWidth, opts) {
977
+ function appendOutlookForShape(content, outerContainerWidth, innerContainerWidth, opts) {
661
978
  const widthPx = Math.round(Math.min(outerContainerWidth, innerContainerWidth));
662
979
  const heightPx = Math.max(1, Math.round(opts.heightPx));
663
980
  const vml = buildVMLShape({
@@ -672,14 +989,17 @@ async function appendOutlookForShape(content, outerContainerWidth, innerContaine
672
989
  text: opts.text,
673
990
  textColor: opts.textColor,
674
991
  textSize: opts.textSize,
992
+ verticalAlign: opts.verticalAlign,
993
+ textAlign: opts.textAlign,
675
994
  msoHasBakedText: Boolean(opts.msoBakeImageWithText),
676
995
  });
677
996
  const pad = opts.padding || {};
678
997
  const align = opts.alignment || "left";
679
998
  const valign = opts.verticalAlign || "middle";
999
+ // Outlook wrapper table ensures the cell's valign works if vml alone doesn't
680
1000
  return `<!--[if mso]>
681
1001
  <table align="${align}" border="0" cellpadding="0" cellspacing="0"
682
- style="width:${widthPx}px;height:${heightPx}px;">
1002
+ style="width:${widthPx}px;height:${heightPx}px;border-collapse:collapse;">
683
1003
  <tr>
684
1004
  <td valign="${valign}"
685
1005
  style="padding:${pad.top || 0}px ${pad.right || 0}px ${pad.bottom || 0}px ${pad.left || 0}px;">
@@ -687,5 +1007,30 @@ async function appendOutlookForShape(content, outerContainerWidth, innerContaine
687
1007
  </td>
688
1008
  </tr>
689
1009
  </table>
690
- <![endif]-->`;
1010
+ <![endif]-->`;
1011
+ }
1012
+ function convertVerticalDividerBlockToHtml(blockData) {
1013
+ const { style } = blockData.data;
1014
+ const { width, height, dividerColor, ...rest } = style;
1015
+ // Convert other styles to inline-safe HTML attributes
1016
+ const convertedStyle = buildStyles(rest, {
1017
+ perChanges: [],
1018
+ pxChanges: allPxAttributes,
1019
+ });
1020
+ // Outlook-safe vertical divider
1021
+ const dividerContent = `
1022
+ <table cellpadding="0" cellspacing="0" border="0" align="center" style="width:auto; ${convertedStyle}">
1023
+ <tr>
1024
+ <td style="vertical-align: middle; text-align: center;">
1025
+ <!--[if mso | IE]>
1026
+ <v:rect xmlns:v="urn:schemas-microsoft-com:vml" fillcolor="${dividerColor}" style="width:${width}px;height:${height}px;" stroke="f"></v:rect>
1027
+ <![endif]-->
1028
+ <!--[if !mso]><!-- -->
1029
+ <div style="display:inline-block;width:${width}px;height:${height}px;background:${dividerColor};line-height:0;font-size:0;">&nbsp;</div>
1030
+ <!--<![endif]-->
1031
+ </td>
1032
+ </tr>
1033
+ </table>
1034
+ `;
1035
+ return appendOutlookSupport(dividerContent, convertedStyle);
691
1036
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "email-builder-utils",
3
- "version": "1.1.27",
3
+ "version": "1.1.29",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [