email-builder-utils 1.1.13 → 1.1.15

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,oBA4EpD,CAAC"}
1
+ {"version":3,"file":"convertJsonToHtml.d.ts","sourceRoot":"","sources":["../../src/utils/convertJsonToHtml.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,iBAAiB,GAAU,UAAU,GAAG,oBA+EpD,CAAC"}
@@ -12,21 +12,23 @@ 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 = `
16
- <!DOCTYPE html>
15
+ const rawHtml = `<!DOCTYPE html>
17
16
  <html lang="en">
18
17
  <head>
19
18
  <meta charset="UTF-8" />
20
19
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
21
- <title>Email Layout</title>
20
+ <meta name="x-apple-disable-message-reformatting" />
22
21
  <style>
23
22
  .responsive-table {
24
23
  width: 100%;
25
24
  max-width: 600px;
26
25
  }
27
-
28
26
  @media only screen and (max-width: 600px) {
29
- .stack-column {
27
+ .responsive-table {
28
+ width: 100% !important;
29
+ }
30
+ .stack-column,
31
+ .stack-column td {
30
32
  display: block !important;
31
33
  width: 100% !important;
32
34
  max-width: 100% !important;
@@ -38,15 +40,15 @@ const convertJsonToHtml = async (jsonData) => {
38
40
  <center>
39
41
  <table
40
42
  class="responsive-table"
41
- style="
42
- font-family: ${fontFamily};
43
- margin: 0 auto;
44
- table-layout:fixed;
45
- background-color: ${canvasColor};
46
- color: ${textColor};
47
- padding: ${top}px ${right}px ${bottom}px ${left}px;
48
- border: ${borderWidth}px ${borderStyle} ${borderColor};
49
- border-radius: ${borderRadius}px; "
43
+ style="
44
+ font-family: ${fontFamily};
45
+ margin: 0 auto;
46
+ table-layout:fixed;
47
+ background-color: ${canvasColor};
48
+ color: ${textColor};
49
+ padding: ${top}px ${right}px ${bottom}px ${left}px;
50
+ border: ${borderWidth}px ${borderStyle} ${borderColor};
51
+ border-radius: ${borderRadius}px; "
50
52
  >
51
53
  <tbody>
52
54
  <tr>
@@ -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;CAClB;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;AAQD,eAAO,MAAM,gBAAgB,kDAAkD,CAAC;AAiDhF,wBAAsB,aAAa,CAAC,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,mBAiB9F"}
1
+ {"version":3,"file":"jsonToHTML.d.ts","sourceRoot":"","sources":["../../src/utils/jsonToHTML.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AASrC,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;CAClB;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,mBAkBtB"}
@@ -4,7 +4,12 @@ exports.tableCommonStyle = void 0;
4
4
  exports.convertToHtml = convertToHtml;
5
5
  const jimp_1 = require("jimp");
6
6
  const types_1 = require("../types");
7
- const addPxToAttributes = ["fontSize", "lineHeight", "borderRadius", "borderWidth"];
7
+ const addPxToAttributes = [
8
+ "fontSize",
9
+ "lineHeight",
10
+ "borderRadius",
11
+ "borderWidth",
12
+ ];
8
13
  const addPxOrPerToAttributes = ["width", "height"];
9
14
  const allPxAttributes = [...addPxToAttributes, ...addPxOrPerToAttributes];
10
15
  exports.tableCommonStyle = "border-collapse:collapse; table-layout:fixed;";
@@ -35,7 +40,8 @@ function buildStyles(style, { pxChanges, perChanges }) {
35
40
  return;
36
41
  if (value === undefined || value === null || value === "")
37
42
  return;
38
- if ((key === "padding" || key === "buttonPadding") && typeof value === "object") {
43
+ if ((key === "padding" || key === "buttonPadding") &&
44
+ typeof value === "object") {
39
45
  const padding = value;
40
46
  value = `${padding.top}px ${padding.right}px ${padding.bottom}px ${padding.left}px`;
41
47
  }
@@ -84,7 +90,10 @@ function appendOutlookSupport(content, contentStyle) {
84
90
  function convertDividerBlockToHtml(blockData) {
85
91
  const { style } = blockData.data;
86
92
  const { thickness, dividerColor, ...rest } = style;
87
- const convertedStyle = buildStyles(rest, { perChanges: [], pxChanges: allPxAttributes });
93
+ const convertedStyle = buildStyles(rest, {
94
+ perChanges: [],
95
+ pxChanges: allPxAttributes,
96
+ });
88
97
  const dividerContent = `
89
98
  <table width="100%" cellpadding="0" cellspacing="0">
90
99
  <tr>
@@ -96,16 +105,24 @@ function convertDividerBlockToHtml(blockData) {
96
105
  }
97
106
  function convertSpacerBlockToHtml(blockData) {
98
107
  const { style } = blockData.data;
99
- const styles = buildStyles(style, { perChanges: [], pxChanges: allPxAttributes });
108
+ const styles = buildStyles(style, {
109
+ perChanges: [],
110
+ pxChanges: allPxAttributes,
111
+ });
100
112
  return appendOutlookSupport(``, styles);
101
113
  }
102
114
  function convertTextBlock(blockData) {
103
115
  const { style, props } = blockData.data;
104
- const styles = buildStyles(style, { perChanges: [], pxChanges: allPxAttributes });
116
+ const styles = buildStyles(style, {
117
+ perChanges: [],
118
+ pxChanges: allPxAttributes,
119
+ });
105
120
  const text = props.text || "";
106
121
  const navigateToUrl = props.navigateToUrl || "";
107
122
  const textContent = appendOutlookSupport(text.replaceAll(/\n/g, "<br>"), styles);
108
- return navigateToUrl ? `<a href="${navigateToUrl}" style="color:inherit; text-decoration:none; cursor:pointer;">${textContent}</a>` : textContent;
123
+ return navigateToUrl
124
+ ? `<a href="${navigateToUrl}" rel="noreferrer noopener" style="color:inherit; text-decoration:none; cursor:pointer;">${textContent}</a>`
125
+ : textContent;
109
126
  }
110
127
  async function appendOutlookForImage(content, outerContainerWidth, innerContainerWidth, imageUrl, style = {}) {
111
128
  const image = await jimp_1.Jimp.read(imageUrl);
@@ -152,7 +169,7 @@ async function convertImageBlock(blockData, cellWidthInPx) {
152
169
  objectFit,
153
170
  borderStyle,
154
171
  borderRadius: borderRadius,
155
- borderColor
172
+ borderColor,
156
173
  };
157
174
  // Add border styles to container for fallback clients
158
175
  const containerStyles = buildStyles({
@@ -163,12 +180,15 @@ async function convertImageBlock(blockData, cellWidthInPx) {
163
180
  pxChanges: addPxToAttributes,
164
181
  });
165
182
  const imageElement = `<img src="${imageUrl}" alt="${altText}" style="${imageTagStyles}" />`;
166
- const innerContainerWidth = ((typeof width === "string" ? parseInt(width.replace("%", "")) : width) / 100) *
167
- (cellWidthInPx - (style?.padding?.left || 0) - (style?.padding?.right || 0));
183
+ const innerContainerWidth = ((typeof width === "string" ? parseInt(width.replace("%", "")) : width) /
184
+ 100) *
185
+ (cellWidthInPx -
186
+ (style?.padding?.left || 0) -
187
+ (style?.padding?.right || 0));
168
188
  const outlookImage = await appendOutlookForImage(imageElement, cellWidthInPx, innerContainerWidth, imageUrl, style);
169
189
  const imageContent = appendOutlookSupport(outlookImage, containerStyles);
170
190
  return navigateToUrl
171
- ? `<a href="${navigateToUrl}" target="_blank" style="display:block; text-decoration:none; cursor:pointer;">${imageContent}</a>`
191
+ ? `<a href="${navigateToUrl}" target="_blank" rel="noreferrer noopener" style="display:block; text-decoration:none; cursor:pointer;">${imageContent}</a>`
172
192
  : imageContent;
173
193
  }
174
194
  function appendOutlookForButton(content, buttonStyle, navigateToUrl, text) {
@@ -213,48 +233,93 @@ function convertButtonBlock(blockData) {
213
233
  color,
214
234
  backgroundColor: buttonColor,
215
235
  };
216
- const convertedButtonStyle = buildStyles(buttonStyle, { perChanges: [], pxChanges: allPxAttributes });
217
- const convertedStyles = buildStyles(rest, { perChanges: [], pxChanges: allPxAttributes });
218
- const buttonElement = `<a href="${navigateToUrl}" style="display:inline-block; text-decoration:none; cursor:pointer;"><button style="${convertedButtonStyle}">${text}</button></a>`;
236
+ const convertedButtonStyle = buildStyles(buttonStyle, {
237
+ perChanges: [],
238
+ pxChanges: allPxAttributes,
239
+ });
240
+ const convertedStyles = buildStyles(rest, {
241
+ perChanges: [],
242
+ pxChanges: allPxAttributes,
243
+ });
244
+ const buttonElement = `<a href="${navigateToUrl}" rel="noreferrer noopener" style="display:inline-block; text-decoration:none; cursor:pointer;"><button style="${convertedButtonStyle}">${text}</button></a>`;
219
245
  const buttonContent = appendOutlookSupport(appendOutlookForButton(buttonElement, style, navigateToUrl, text), convertedStyles);
220
246
  return buttonContent;
221
247
  }
222
- async function processGridItemsInParallel(columns, childrenIds, cellWidths, cellWidthInPx, rootData) {
223
- const gridItemPromises = [];
224
- for (let colIndex = 0; colIndex < columns; colIndex++) {
225
- const childId = childrenIds[colIndex];
226
- const cellWidth = cellWidths ? cellWidths[colIndex] : 100 / columns;
227
- const childBlockData = rootData[childId];
228
- if (childBlockData) {
229
- const gridItemPromise = convertGridCellBlock(childBlockData, rootData, cellWidth, cellWidthInPx);
230
- gridItemPromises.push(gridItemPromise);
231
- }
232
- }
233
- const gridItems = await Promise.all(gridItemPromises);
234
- return gridItems;
235
- }
236
248
  async function convertGridBlock(blockData, rootData, cellWidthInPx) {
237
- const { style, childrenIds = [], props } = blockData.data;
238
- const { columnGap, ...rest } = style;
239
- const { rows, columns, cellWidths } = props;
240
- const styles = buildStyles(rest, { perChanges: [], pxChanges: allPxAttributes });
241
- const gridItems = await processGridItemsInParallel(columns, childrenIds, cellWidths, cellWidthInPx, rootData);
242
- return `
243
- <table cellspacing="${columnGap}" style="width:100%; max-width:100%; ${styles}">
244
- <tbody>
245
- <tr>${gridItems.join("")}</tr>
246
- </tbody>
247
- </table>
249
+ const { style = {}, childrenIds = [], props } = blockData.data;
250
+ const { columns = 1, cellWidths = [] } = props;
251
+ const { columnGap = 0, ...restStyle } = style;
252
+ const tableStyles = buildStyles(restStyle, {
253
+ perChanges: [],
254
+ pxChanges: allPxAttributes,
255
+ });
256
+ const total = childrenIds.length;
257
+ const visualRows = Math.ceil(total / columns);
258
+ let html = `
259
+ <!--[if mso]>
260
+ <table border="0" cellpadding="0" cellspacing="${columnGap}" width="100%" style="${exports.tableCommonStyle}">
261
+ <![endif]-->
262
+ <table border="0" cellpadding="0" cellspacing="${columnGap}" width="100%" role="presentation" style="${exports.tableCommonStyle} ${tableStyles}">
248
263
  `;
264
+ for (let r = 0; r < visualRows; r++) {
265
+ html += "<tr>";
266
+ for (let c = 0; c < columns; c++) {
267
+ const idx = r * columns + c;
268
+ const childId = childrenIds[idx];
269
+ const widthPercent = cellWidths[c] ?? 100 / columns;
270
+ if (childId) {
271
+ const child = rootData[childId];
272
+ const { style: cellStyle = {} } = child.data || {};
273
+ const verticalAlign = cellStyle.verticalAlign || "top";
274
+ const childHtml = child
275
+ ? await convertGridCellBlock(child, rootData, widthPercent, cellWidthInPx)
276
+ : "";
277
+ html += `
278
+ <td
279
+ width="${widthPercent}%"
280
+ class="stack-column"
281
+ style="vertical-align:${verticalAlign}; padding:0; word-break:break-word;"
282
+ >
283
+ ${childHtml}
284
+ </td>`;
285
+ }
286
+ else {
287
+ html += `<td width="${widthPercent}%" class="stack-column" style="padding:0;"></td>`;
288
+ }
289
+ }
290
+ html += "</tr>";
291
+ }
292
+ html += `</table><!--[if mso]></table><![endif]-->`;
293
+ return html;
249
294
  }
250
- async function convertGridCellBlock(blockData, rootData, cellWidth, parentCellWidth) {
251
- const { style, childrenIds } = blockData.data;
252
- const styles = buildStyles(style, { perChanges: [], pxChanges: allPxAttributes });
253
- const cellItems = [];
254
- if (childrenIds && childrenIds?.length > 0) {
255
- for (const childId of childrenIds) {
256
- cellItems.push(await convertToHtml(rootData[childId], rootData, parentCellWidth * (cellWidth || 0) / 100));
295
+ async function convertGridCellBlock(blockData, rootData, cellWidthPercent, parentCellWidthPx) {
296
+ const { style = {}, childrenIds = [] } = blockData.data;
297
+ const styles = buildStyles(style, {
298
+ perChanges: [],
299
+ pxChanges: allPxAttributes,
300
+ });
301
+ const innerHtmlParts = [];
302
+ for (const childId of childrenIds) {
303
+ const child = rootData[childId];
304
+ if (child) {
305
+ const cellWidthPx = parentCellWidthPx * (cellWidthPercent / 100);
306
+ innerHtmlParts.push(await convertToHtml(child, rootData, cellWidthPx));
257
307
  }
258
308
  }
259
- return `<td class="stack-column" style="width:${cellWidth}% ; max-width:${cellWidth}%; ${styles}">${cellItems.join("")}</td>`;
309
+ const innerContent = innerHtmlParts.join("");
310
+ return `
311
+ <table
312
+ role="presentation"
313
+ border="0"
314
+ cellpadding="0"
315
+ cellspacing="0"
316
+ width="100%"
317
+ style="border-collapse:collapse;table-layout:fixed;${styles}"
318
+ >
319
+ <tr>
320
+ <td style="padding:0;">
321
+ ${innerContent}
322
+ </td>
323
+ </tr>
324
+ </table>`;
260
325
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "email-builder-utils",
3
- "version": "1.1.13",
3
+ "version": "1.1.15",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [