email-builder-utils 1.1.49 → 1.1.52

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.
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.extractVimeoId = exports.extractYouTubeId = void 0;
4
4
  exports.getVisibilityClass = getVisibilityClass;
5
5
  exports.encodeBlockPropsAttr = encodeBlockPropsAttr;
6
+ exports.buildOutlookBgAttr = buildOutlookBgAttr;
6
7
  const extractYouTubeId = (url) => {
7
8
  try {
8
9
  const u = new URL(url);
@@ -53,3 +54,16 @@ function encodeBlockPropsAttr(props) {
53
54
  .replace(/</g, "&lt;")
54
55
  .replace(/>/g, "&gt;");
55
56
  }
57
+ function toOutlookBgColor(color) {
58
+ if (!color || color === 'transparent')
59
+ return '';
60
+ const m = color.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*[\d.]+)?\s*\)/);
61
+ if (m) {
62
+ return '#' + [m[1], m[2], m[3]].map(n => parseInt(n).toString(16).padStart(2, '0')).join('');
63
+ }
64
+ return color;
65
+ }
66
+ function buildOutlookBgAttr(color) {
67
+ const normalized = toOutlookBgColor(color);
68
+ return normalized ? ` bgcolor="${normalized}"` : '';
69
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"convertJsonToHtml.d.ts","sourceRoot":"","sources":["../../src/utils/convertJsonToHtml.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,GAAU,UAAU,GAAG,oBAuLpD,CAAC"}
1
+ {"version":3,"file":"convertJsonToHtml.d.ts","sourceRoot":"","sources":["../../src/utils/convertJsonToHtml.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,GAAU,UAAU,GAAG,oBAqNpD,CAAC"}
@@ -13,159 +13,189 @@ const convertJsonToHtml = async (jsonData) => {
13
13
  }
14
14
  const { fontFamily, canvasColor, textColor, padding = {}, borderColor, borderRadius, borderWidth, borderStyle, } = rootData.style || {};
15
15
  const { top = 0, right = 0, bottom = 0, left = 0 } = padding;
16
- const rawHtml = `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
17
- <html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
18
- <head>
19
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
20
- <meta http-equiv="X-UA-Compatible" content="IE=edge" />
21
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
22
- <meta name="x-apple-disable-message-reformatting" />
23
- <meta name="format-detection" content="telephone=no,address=no,email=no,date=no,url=no" />
24
- <!--[if gte mso 9]><xml>
25
- <o:OfficeDocumentSettings>
26
- <o:AllowPNG/>
27
- <o:PixelsPerInch>96</o:PixelsPerInch>
28
- </o:OfficeDocumentSettings>
29
- </xml><![endif]-->
30
- <style>
31
- html, body {
32
- margin: 0 !important;
33
- padding: 0 !important;
34
- width: 100% !important;
35
- -ms-text-size-adjust: 100%;
36
- -webkit-text-size-adjust: 100%;
37
- }
38
-
39
- body, table, td, p, a {
40
- font-family: ${(0, fontFallback_1.withFontFallback)(fontFamily)};
41
- }
42
-
43
- table, td {
44
- mso-table-lspace: 0pt;
45
- mso-table-rspace: 0pt;
46
- border-collapse: collapse !important;
47
- }
48
-
49
- img {
50
- -ms-interpolation-mode: bicubic;
51
- border: 0;
52
- outline: none;
53
- text-decoration: none;
54
- display: block;
55
- height: auto;
56
- line-height: 100%;
57
- }
58
-
59
- a {
60
- text-decoration: none;
61
- color: inherit;
62
- }
63
-
64
- .email-container {
65
- width: 100%;
66
- max-width: 600px;
67
- margin: 0 auto;
68
- }
69
-
70
- .stack-column,
71
- .stack-column-cell {
72
- vertical-align: top;
73
- }
74
-
75
- .hide-mobile {
76
- display: block !important;
77
- max-height: none !important;
78
- overflow: visible !important;
79
- }
80
-
81
- .hide-desktop {
82
- display: none !important;
83
- max-height: 0 !important;
84
- overflow: hidden !important;
85
- mso-hide: all !important;
86
- }
87
-
88
- @media only screen and (max-width: 600px) {
89
- .email-container {
90
- width: 100% !important;
91
- max-width: 100% !important;
92
- }
93
-
94
- .stack-column,
95
- .stack-column-cell {
96
- display: block !important;
97
- width: 100% !important;
98
- max-width: 100% !important;
99
- }
100
-
101
- .hide-mobile {
102
- display: none !important;
103
- max-height: 0 !important;
104
- overflow: hidden !important;
105
- mso-hide: all !important;
106
- }
107
-
108
- .hide-desktop {
109
- display: block !important;
110
- max-height: none !important;
111
- overflow: visible !important;
112
- }
113
- }
114
- </style>
115
- </head>
116
- <body style="margin:0; padding:0; background-color:${canvasColor || '#ffffff'};">
117
- <center style="width:100%; background-color:${canvasColor || '#ffffff'};">
118
-
119
- <table role="presentation" width="100%" border="0" cellspacing="0" cellpadding="0" bgcolor="${canvasColor || '#ffffff'}">
120
- <tr>
121
- <td align="center">
122
-
123
- <!--[if mso]>
124
- <table role="presentation" border="0" cellspacing="0" cellpadding="0" width="600">
125
- <tr>
126
- <td>
127
- <![endif]-->
128
-
129
- <table
130
- role="presentation"
131
- class="email-container"
132
- bgcolor="${canvasColor || '#ffffff'}"
133
- cellpadding="0"
134
- cellspacing="0"
135
- border="0"
136
- width="100%"
137
- style="
138
- font-family: ${(0, fontFallback_1.withFontFallback)(fontFamily)};
139
- table-layout: fixed;
140
- width: 100%;
141
- max-width: 600px;
142
- margin: 0 auto;
143
- background-color: ${canvasColor || '#ffffff'};
144
- ${textColor ? `color: ${textColor};` : ''}
145
- ${borderWidth ? `border: ${borderWidth}px ${borderStyle || 'solid'} ${borderColor || 'transparent'};` : ''}
146
- ${borderRadius ? `border-radius: ${borderRadius}px;` : ''}"
147
- >
148
- <tbody>
149
- <tr>
150
- <td style="padding: ${top}px ${right}px ${bottom}px ${left}px;">
151
- ${blocksHtml.join("")}
152
- </td>
153
- </tr>
154
- </tbody>
155
- </table>
156
-
157
- <!--[if mso]>
158
- </td>
159
- </tr>
160
- </table>
161
- <![endif]-->
162
-
163
- </td>
164
- </tr>
165
- </table>
166
-
167
- </center>
168
- </body>
16
+ const rawHtml = `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
17
+ <html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
18
+ <head>
19
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
20
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
21
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
22
+ <meta name="x-apple-disable-message-reformatting" />
23
+ <meta name="format-detection" content="telephone=no,address=no,email=no,date=no,url=no" />
24
+ <!--[if gte mso 9]><xml>
25
+ <o:OfficeDocumentSettings>
26
+ <o:AllowPNG/>
27
+ <o:PixelsPerInch>96</o:PixelsPerInch>
28
+ </o:OfficeDocumentSettings>
29
+ </xml><![endif]-->
30
+ <style>
31
+ html, body {
32
+ margin: 0 !important;
33
+ padding: 0 !important;
34
+ width: 100% !important;
35
+ -ms-text-size-adjust: 100%;
36
+ -webkit-text-size-adjust: 100%;
37
+ }
38
+
39
+ body, table, td, p, a {
40
+ font-family: ${(0, fontFallback_1.withFontFallback)(fontFamily)};
41
+ }
42
+
43
+ table, td {
44
+ mso-table-lspace: 0pt;
45
+ mso-table-rspace: 0pt;
46
+ border-collapse: collapse !important;
47
+ }
48
+
49
+ img {
50
+ -ms-interpolation-mode: bicubic;
51
+ border: 0;
52
+ outline: none;
53
+ text-decoration: none;
54
+ display: block;
55
+ height: auto;
56
+ line-height: 100%;
57
+ }
58
+
59
+ a {
60
+ text-decoration: none;
61
+ color: inherit;
62
+ }
63
+
64
+ .email-container {
65
+ width: 100%;
66
+ max-width: 600px;
67
+ margin: 0 auto;
68
+ }
69
+
70
+ .stack-column,
71
+ .stack-column-cell {
72
+ vertical-align: top;
73
+ }
74
+
75
+ .hide-mobile {
76
+ display: block !important;
77
+ max-height: none !important;
78
+ overflow: visible !important;
79
+ }
80
+
81
+ .hide-desktop {
82
+ display: none !important;
83
+ max-height: 0 !important;
84
+ overflow: hidden !important;
85
+ mso-hide: all !important;
86
+ }
87
+
88
+ @media only screen and (max-width: 600px) {
89
+ body {
90
+ overflow-x: hidden !important;
91
+ }
92
+
93
+ .email-container {
94
+ width: 100% !important;
95
+ max-width: 100% !important;
96
+ overflow-x: hidden !important;
97
+ }
98
+
99
+ .stack-column,
100
+ .stack-column-cell {
101
+ display: block !important;
102
+ width: 100% !important;
103
+ max-width: 100% !important;
104
+ /* Prevents horizontal padding from adding to 100% width once display:block is applied */
105
+ box-sizing: border-box !important;
106
+ }
107
+
108
+ /* Images: drop the inline pixel height so aspect ratio is preserved as width shrinks,
109
+ and cap width to the container. Matches the canvas ImageBlock (height:auto, max-width:100%). */
110
+ img {
111
+ height: auto !important;
112
+ max-width: 100% !important;
113
+ }
114
+
115
+ /* Safety net for clients that keep inline max-width:600px on nested wrappers */
116
+ table[style*="max-width:600px"] {
117
+ width: 100% !important;
118
+ max-width: 100% !important;
119
+ }
120
+
121
+ /* Column-gap spacer tds have no stacking behaviour — collapse them when columns stack */
122
+ .col-gap-spacer {
123
+ display: none !important;
124
+ width: 0 !important;
125
+ max-height: 0 !important;
126
+ overflow: hidden !important;
127
+ }
128
+
129
+ .hide-mobile {
130
+ display: none !important;
131
+ max-height: 0 !important;
132
+ overflow: hidden !important;
133
+ mso-hide: all !important;
134
+ }
135
+
136
+ .hide-desktop {
137
+ display: block !important;
138
+ max-height: none !important;
139
+ overflow: visible !important;
140
+ }
141
+ }
142
+ </style>
143
+ </head>
144
+ <body bgcolor="${canvasColor || '#ffffff'}" style="margin:0; padding:0; background-color:${canvasColor || '#ffffff'};">
145
+ <center style="width:100%; background-color:${canvasColor || '#ffffff'};">
146
+
147
+ <table role="presentation" width="100%" border="0" cellspacing="0" cellpadding="0" bgcolor="${canvasColor || '#ffffff'}">
148
+ <tr>
149
+ <td align="center" bgcolor="${canvasColor || '#ffffff'}">
150
+
151
+ <!--[if mso]>
152
+ <table role="presentation" border="0" cellspacing="0" cellpadding="0" width="600">
153
+ <tr>
154
+ <td>
155
+ <![endif]-->
156
+
157
+ <table
158
+ role="presentation"
159
+ class="email-container"
160
+ align="center"
161
+ bgcolor="${canvasColor || '#ffffff'}"
162
+ cellpadding="0"
163
+ cellspacing="0"
164
+ border="0"
165
+ width="100%"
166
+ style="
167
+ font-family: ${(0, fontFallback_1.withFontFallback)(fontFamily)};
168
+ table-layout: fixed;
169
+ width: 100%;
170
+ max-width: 600px;
171
+ margin: 0 auto;
172
+ overflow: hidden;
173
+ background-color: ${canvasColor || '#ffffff'};
174
+ ${textColor ? `color: ${textColor};` : ''}
175
+ ${borderWidth ? `border: ${borderWidth}px ${borderStyle || 'solid'} ${borderColor || 'transparent'};` : ''}
176
+ ${borderRadius ? `border-radius: ${borderRadius}px;` : ''}"
177
+ >
178
+ <tbody>
179
+ <tr>
180
+ <td bgcolor="${canvasColor || '#ffffff'}" style="padding: ${top}px ${right}px ${bottom}px ${left}px; max-width: 600px; overflow: hidden; box-sizing: border-box; word-break: break-word; overflow-wrap: anywhere;">
181
+ ${blocksHtml.join("")}
182
+ </td>
183
+ </tr>
184
+ </tbody>
185
+ </table>
186
+
187
+ <!--[if mso]>
188
+ </td>
189
+ </tr>
190
+ </table>
191
+ <![endif]-->
192
+
193
+ </td>
194
+ </tr>
195
+ </table>
196
+
197
+ </center>
198
+ </body>
169
199
  </html>`;
170
200
  return rawHtml;
171
201
  };
@@ -1 +1 @@
1
- {"version":3,"file":"outlookSupport.d.ts","sourceRoot":"","sources":["../../src/utils/outlookSupport.ts"],"names":[],"mappings":"AAIA,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOtD;AAOD,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,SAAS,CAAC,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,UAqClB;AAED,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,MAAM,EACf,mBAAmB,EAAE,MAAM,EAC3B,mBAAmB,EAAE,MAAM,EAC3B,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,GAAQ,EACf,UAAU,CAAC,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,MAAM,mBA2ErB;AAED,wBAAsB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAO7G"}
1
+ {"version":3,"file":"outlookSupport.d.ts","sourceRoot":"","sources":["../../src/utils/outlookSupport.ts"],"names":[],"mappings":"AAKA,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOtD;AAOD,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,SAAS,CAAC,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,UAyClB;AAED,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,MAAM,EACf,mBAAmB,EAAE,MAAM,EAC3B,mBAAmB,EAAE,MAAM,EAC3B,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,GAAQ,EACf,UAAU,CAAC,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,MAAM,mBA2FrB;AAED,wBAAsB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAe7G"}
@@ -5,6 +5,7 @@ exports.appendOutlookSupport = appendOutlookSupport;
5
5
  exports.appendOutlookForImage = appendOutlookForImage;
6
6
  exports.loadImageNaturalDimensions = loadImageNaturalDimensions;
7
7
  const buildStyles_1 = require("./buildStyles");
8
+ const common_1 = require("./common");
8
9
  // Converts rgba(r,g,b,a) → #rrggbb for use in HTML bgcolor attributes.
9
10
  // Old Outlook's bgcolor attribute only accepts solid hex or named colors — rgba is silently ignored.
10
11
  function toOutlookBgColor(color) {
@@ -23,40 +24,46 @@ function extractBgColor(styleStr) {
23
24
  function appendOutlookSupport(content, contentStyle, className, msoWidth) {
24
25
  const visibilityClass = className || "";
25
26
  const shouldHideInOutlook = visibilityClass.includes("hide-desktop");
26
- // bgcolor attribute is the reliable fallback for solid colors in Outlook and webmail
27
- // forwarding chains where CSS background-color gets stripped.
27
+ // Old Outlook (Word engine) often ignores CSS background-color on deeply-nested <td>
28
+ // elements. The bgcolor HTML attribute is the reliable fallback for solid colors.
29
+ // rgba() is stripped from CSS by the Word engine — convert to hex first.
28
30
  const rawBg = extractBgColor(contentStyle);
29
- const bgAttr = rawBg ? ` bgcolor="${toOutlookBgColor(rawBg)}"` : '';
31
+ const bgAttr = (0, common_1.buildOutlookBgAttr)(rawBg);
30
32
  if (shouldHideInOutlook) {
31
- return `
32
- <!--[if !mso]><!-->
33
- <table data-ebr-role="wrapper" role="presentation" width="100%" style="${buildStyles_1.tableCommonStyle}" class="${visibilityClass}"><tr><td${bgAttr} style="${contentStyle}">${content}</td></tr></table>
34
- <!--<![endif]-->
33
+ return `
34
+ <!--[if !mso]><!-->
35
+ <table data-ebr-role="wrapper" role="presentation" width="100%" style="${buildStyles_1.tableCommonStyle}" class="${visibilityClass}"><tr><td${bgAttr} style="${contentStyle}">${content}</td></tr></table>
36
+ <!--<![endif]-->
35
37
  `;
36
38
  }
39
+ // When an explicit pixel width is provided (e.g. inside a column cell), use dual MSO/non-MSO
40
+ // tables. Old Outlook (Word engine) ignores max-width and can resolve width="100%" to the
41
+ // full email width (600px) rather than the column width, causing images/buttons to expand.
37
42
  if (msoWidth) {
38
- return `
39
- <!--[if mso]>
40
- <table role="presentation" border="0" cellpadding="0" cellspacing="0" width="${msoWidth}" style="border-collapse:collapse;width:${msoWidth}px;"><tr><td width="${msoWidth}"${bgAttr} style="${contentStyle}">
41
- <![endif]-->
42
- <!--[if !mso]><!-->
43
- <table data-ebr-role="wrapper" role="presentation" width="100%" border="0" cellpadding="0" cellspacing="0" style="${buildStyles_1.tableCommonStyle}; max-width:600px;" class="${visibilityClass}"><tr><td width="100%"${bgAttr} style="${contentStyle}">
44
- <!--<![endif]-->
45
- ${content}
46
- <!--[if mso]></td></tr></table><![endif]-->
47
- <!--[if !mso]><!-->
48
- </td></tr></table>
49
- <!--<![endif]-->
43
+ return `
44
+ <!--[if mso]>
45
+ <table role="presentation" border="0" cellpadding="0" cellspacing="0" width="${msoWidth}" style="border-collapse:collapse;width:${msoWidth}px;"><tr><td width="${msoWidth}"${bgAttr} style="${contentStyle}">
46
+ <![endif]-->
47
+ <!--[if !mso]><!-->
48
+ <table data-ebr-role="wrapper" role="presentation" align="center" width="100%" border="0" cellpadding="0" cellspacing="0" style="${buildStyles_1.tableCommonStyle}; max-width:600px;" class="${visibilityClass}"><tr><td width="100%"${bgAttr} style="${contentStyle}">
49
+ <!--<![endif]-->
50
+ ${content}
51
+ <!--[if mso]></td></tr></table><![endif]-->
52
+ <!--[if !mso]><!-->
53
+ </td></tr></table>
54
+ <!--<![endif]-->
50
55
  `;
51
56
  }
52
- return `
53
- <table data-ebr-role="wrapper" role="presentation" width="100%" border="0" cellpadding="0" cellspacing="0" style="${buildStyles_1.tableCommonStyle}; max-width:600px;" class="${visibilityClass}"><tr><td width="100%"${bgAttr} style="${contentStyle}">${content}</td></tr></table>
57
+ return `
58
+ <table data-ebr-role="wrapper" role="presentation" align="center" width="100%" border="0" cellpadding="0" cellspacing="0" style="${buildStyles_1.tableCommonStyle}; max-width:600px;" class="${visibilityClass}"><tr><td width="100%"${bgAttr} style="${contentStyle}">${content}</td></tr></table>
54
59
  `;
55
60
  }
56
61
  async function appendOutlookForImage(content, outerContainerWidth, innerContainerWidth, imageUrl, style = {}, finalWidth, finalHeight) {
62
+ // OUTLOOK FIX: Use provided dimensions or calculate from image
57
63
  let vmlWidth;
58
64
  let vmlHeight;
59
65
  if (finalWidth && finalHeight) {
66
+ // Use pre-calculated dimensions (preferred for accuracy)
60
67
  vmlWidth = finalWidth;
61
68
  vmlHeight = finalHeight;
62
69
  }
@@ -80,53 +87,70 @@ async function appendOutlookForImage(content, outerContainerWidth, innerContaine
80
87
  const borderColor = style?.borderColor || "transparent";
81
88
  const borderRadius = parseInt(style?.borderRadius) || 0;
82
89
  const useRoundRect = borderRadius > 0;
83
- const arcsize = useRoundRect ? Math.min(borderRadius / vmlHeight, 1).toFixed(2) : "";
90
+ const arcsize = useRoundRect
91
+ ? Math.min(borderRadius / vmlHeight, 1).toFixed(2)
92
+ : "";
84
93
  const borderAttributes = borderWidth > 0
85
94
  ? `strokeweight="${borderWidth}px" strokecolor="${borderColor}"`
86
95
  : `stroked="false"`;
96
+ // OUTLOOK FIX: For Outlook 2019+ (version 2512), VML type="frame" causes stretching
97
+ // Solution: Use simple IMG tag with fixed dimensions for Outlook, only use VML for border radius
87
98
  let outlookImage;
88
99
  if (useRoundRect && borderRadius > 0) {
89
- outlookImage = `<!--[if mso]>
90
- <table border="0" cellpadding="0" cellspacing="0" width="${vmlWidth}" style="width:${vmlWidth}px;">
91
- <tr>
92
- <td align="center" valign="top" width="${vmlWidth}" style="width:${vmlWidth}px;">
93
- <v:roundrect xmlns:v="urn:schemas-microsoft-com:vml"
94
- style="width:${vmlWidth}px;height:${vmlHeight}px;"
95
- ${borderAttributes}
96
- arcsize="${arcsize}"
97
- fill="true" fillcolor="none">
98
- <v:fill src="${imageUrl}" type="tile" aspect="atmost" />
99
- <v:textbox inset="0,0,0,0"><div style="display:none;">.</div></v:textbox>
100
- </v:roundrect>
101
- </td>
102
- </tr>
103
- </table>
100
+ // Use VML for border radius - wrap in table to constrain width for Old Outlook (Word engine)
101
+ // Use aspect="atmost" to prevent image from stretching beyond its bounds
102
+ outlookImage = `<!--[if mso]>
103
+ <table border="0" cellpadding="0" cellspacing="0" width="${vmlWidth}" style="width:${vmlWidth}px;">
104
+ <tr>
105
+ <td align="center" valign="top" width="${vmlWidth}" style="width:${vmlWidth}px;">
106
+ <v:roundrect xmlns:v="urn:schemas-microsoft-com:vml"
107
+ style="width:${vmlWidth}px;height:${vmlHeight}px;"
108
+ ${borderAttributes}
109
+ arcsize="${arcsize}"
110
+ fill="true" fillcolor="none">
111
+ <v:fill src="${imageUrl}" type="tile" aspect="atmost" />
112
+ <v:textbox inset="0,0,0,0"><div style="display:none;">.</div></v:textbox>
113
+ </v:roundrect>
114
+ </td>
115
+ </tr>
116
+ </table>
104
117
  <![endif]-->`;
105
118
  }
106
119
  else {
107
- const borderStyleAttr = borderWidth > 0 ? `border: ${borderWidth}px solid ${borderColor};` : '';
108
- outlookImage = `<!--[if mso]>
109
- <table border="0" cellpadding="0" cellspacing="0" width="${vmlWidth}" style="width:${vmlWidth}px;">
110
- <tr>
111
- <td align="center" valign="top" width="${vmlWidth}" style="width:${vmlWidth}px;">
112
- <img src="${imageUrl}" alt="Image" border="0" width="${vmlWidth}" height="${vmlHeight}" style="display:block; width:${vmlWidth}px; height:${vmlHeight}px; max-width:${vmlWidth}px; ${borderStyleAttr}" />
113
- </td>
114
- </tr>
115
- </table>
120
+ // For images without border radius, wrap in a table with explicit width for Old Outlook (Word engine)
121
+ // This prevents stretching/overflow in Outlook 2007-2019 and Outlook Classic
122
+ const borderStyleAttr = borderWidth > 0
123
+ ? `border: ${borderWidth}px solid ${borderColor};`
124
+ : '';
125
+ outlookImage = `<!--[if mso]>
126
+ <table border="0" cellpadding="0" cellspacing="0" width="${vmlWidth}" style="width:${vmlWidth}px;">
127
+ <tr>
128
+ <td align="center" valign="top" width="${vmlWidth}" style="width:${vmlWidth}px;">
129
+ <img src="${imageUrl}" alt="Image" border="0" width="${vmlWidth}" height="${vmlHeight}" style="display:block; width:${vmlWidth}px; height:${vmlHeight}px; max-width:${vmlWidth}px; ${borderStyleAttr}" />
130
+ </td>
131
+ </tr>
132
+ </table>
116
133
  <![endif]-->`;
117
134
  }
118
- return `
119
- ${outlookImage}
120
- <!--[if !mso]><!-->
121
- ${content}
122
- <!--<![endif]-->
135
+ return `
136
+ ${outlookImage}
137
+ <!--[if !mso]><!-->
138
+ ${content}
139
+ <!--<![endif]-->
123
140
  `;
124
141
  }
125
142
  async function loadImageNaturalDimensions(imageUrl) {
126
- return new Promise((resolve, reject) => {
127
- const img = new Image();
128
- img.onload = () => resolve({ width: img.naturalWidth, height: img.naturalHeight });
129
- img.onerror = () => reject(new Error(`Failed to load image: ${imageUrl}`));
130
- img.src = imageUrl;
131
- });
143
+ // Browser environment: use native Image element
144
+ if (typeof window !== 'undefined' && typeof Image !== 'undefined') {
145
+ return new Promise((resolve, reject) => {
146
+ const img = new Image();
147
+ img.onload = () => resolve({ width: img.naturalWidth, height: img.naturalHeight });
148
+ img.onerror = () => reject(new Error(`Failed to load image: ${imageUrl}`));
149
+ img.src = imageUrl;
150
+ });
151
+ }
152
+ // Node.js (BE) environment: use jimp to fetch and read dimensions
153
+ const { Jimp } = await import('jimp');
154
+ const image = await Jimp.read(imageUrl);
155
+ return { width: image.bitmap.width, height: image.bitmap.height };
132
156
  }
package/package.json CHANGED
@@ -1,34 +1,34 @@
1
- {
2
- "name": "email-builder-utils",
3
- "version": "1.1.49",
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
- "user": "^0.0.0"
33
- }
34
- }
1
+ {
2
+ "name": "email-builder-utils",
3
+ "version": "1.1.52",
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
+ "user": "^0.0.0"
33
+ }
34
+ }