email-builder-utils 1.0.5 → 1.0.8

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,GAAI,UAAU,GAAG,WAsC9C,CAAA"}
1
+ {"version":3,"file":"convertJsonToHtml.d.ts","sourceRoot":"","sources":["../../src/utils/convertJsonToHtml.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,iBAAiB,GAAI,UAAU,GAAG,WA6B9C,CAAA"}
@@ -4,7 +4,7 @@ exports.convertJsonToHtml = void 0;
4
4
  const jsonToHTML_1 = require("./jsonToHTML");
5
5
  const convertJsonToHtml = (jsonData) => {
6
6
  const rootData = jsonData?.root?.data;
7
- const blocksHtml = rootData?.childrenIds.map((childId) => (0, jsonToHTML_1.convertToHtml)(jsonData[childId], jsonData)).join("");
7
+ const blocksHtml = rootData?.childrenIds.map((childId) => (0, jsonToHTML_1.convertToHtml)(jsonData[childId], jsonData, 600)).join("");
8
8
  const rawHtml = `
9
9
  <!DOCTYPE html>
10
10
  <html lang="en">
@@ -13,23 +13,14 @@ const convertJsonToHtml = (jsonData) => {
13
13
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
14
14
  <title>Email Layout</title>
15
15
  <style>
16
- @media screen and (max-width: 600px) {
17
- .ebr-table-wrapper {
18
- width: 360px !important;
19
- max-width: 360px !important;
20
- }
21
- }
22
-
23
- @media screen and (min-width: 601px) {
24
- .ebr-table-wrapper {
25
- width: 600px !important;
26
- max-width: 600px !important;
27
- }
16
+ .responsive-table {
17
+ width: 100%;
18
+ max-width: 600px;
28
19
  }
29
20
  </style>
30
21
  </head>
31
22
  <body>
32
- <table class="ebr-table-wrapper" style="font-family:${rootData.style?.fontFamily}; width:600px; max-width:600px; margin:0 auto; background-color:${rootData.style?.canvasColor}; color:${rootData.style?.textColor}; ${jsonToHTML_1.tableCommonStyle}">
23
+ <table class="responsive-table" style="font-family:${rootData.style?.fontFamily}; margin:0 auto; background-color:${rootData.style?.canvasColor}; color:${rootData.style?.textColor}; ${jsonToHTML_1.tableCommonStyle}">
33
24
  <tbody>
34
25
  <tr>
35
26
  <td style="padding:0;">${blocksHtml}</td>
@@ -17,6 +17,6 @@ interface IBlockData {
17
17
  };
18
18
  }
19
19
  export declare const tableCommonStyle = "border-collapse:collapse; table-layout:fixed;";
20
- export declare function convertToHtml(blockData: IBlockData, rootData: any): string;
20
+ export declare function convertToHtml(blockData: IBlockData, rootData: any, cellWidthInPx: number): Promise<string>;
21
21
  export {};
22
22
  //# sourceMappingURL=jsonToHTML.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"jsonToHTML.d.ts","sourceRoot":"","sources":["../../src/utils/jsonToHTML.ts"],"names":[],"mappings":"AAAA,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;AAGD,eAAO,MAAM,gBAAgB,kDAAkD,CAAC;AA4ChF,wBAAgB,aAAa,CAAC,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,UAiBjE"}
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;AASD,eAAO,MAAM,gBAAgB,kDAAkD,CAAC;AAqDhF,wBAAsB,aAAa,CAAC,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,mBAiB9F"}
@@ -2,8 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.tableCommonStyle = void 0;
4
4
  exports.convertToHtml = convertToHtml;
5
+ const jimp_1 = require("jimp");
5
6
  const types_1 = require("../types");
6
- const addPxToAttributes = ["width", "height", "fontSize", "lineHeight"];
7
+ const addPxToAttributes = ["fontSize", "lineHeight"];
8
+ const addPxOrPerToAttributes = ["width", "height"];
9
+ const allPxAttributes = [...addPxToAttributes, ...addPxOrPerToAttributes];
7
10
  exports.tableCommonStyle = "border-collapse:collapse; table-layout:fixed;";
8
11
  function cleanJson(obj) {
9
12
  if (typeof obj !== "object" || obj === null)
@@ -25,10 +28,11 @@ function jsonToPlainString(obj) {
25
28
  .map(([key, value]) => `${key}:${jsonToPlainString(value)}; `)
26
29
  .join("");
27
30
  }
28
- function buildStyles(style) {
31
+ function buildStyles(style, { pxChanges, perChanges }) {
29
32
  const stylesObj = {};
30
33
  Object.entries(style).forEach(([key, value]) => {
31
- const appendPx = addPxToAttributes.includes(key);
34
+ const appendPx = pxChanges.includes(key);
35
+ const appendPer = perChanges.includes(key);
32
36
  if (value === undefined || value === null || value === "")
33
37
  return null;
34
38
  if ((key === "padding" || key === "buttonPadding") && typeof value === "object" && value !== null) {
@@ -36,20 +40,29 @@ function buildStyles(style) {
36
40
  value = `${padding.top}px ${padding.right}px ${padding.bottom}px ${padding.left}px`;
37
41
  }
38
42
  const cssKey = key.replace(/([A-Z])/g, "-$1").toLowerCase();
39
- return (stylesObj[cssKey] = appendPx ? `${value}px` : value);
43
+ if (appendPx) {
44
+ stylesObj[cssKey] = `${value}px`;
45
+ }
46
+ else if (appendPer) {
47
+ stylesObj[cssKey] = `${value}%`;
48
+ }
49
+ else {
50
+ stylesObj[cssKey] = value;
51
+ }
52
+ return;
40
53
  });
41
54
  return jsonToPlainString(cleanJson(stylesObj)).trim();
42
55
  }
43
- function convertToHtml(blockData, rootData) {
56
+ async function convertToHtml(blockData, rootData, cellWidthInPx) {
44
57
  switch (blockData.type) {
45
58
  case types_1.BlockType.TEXT:
46
59
  return convertTextBlock(blockData);
47
60
  case types_1.BlockType.IMAGE:
48
- return convertImageBlock(blockData);
61
+ return await convertImageBlock(blockData, cellWidthInPx);
49
62
  case types_1.BlockType.BUTTON:
50
63
  return convertButtonBlock(blockData);
51
64
  case types_1.BlockType.GRID:
52
- return convertGridBlock(blockData, rootData);
65
+ return await convertGridBlock(blockData, rootData, cellWidthInPx);
53
66
  case types_1.BlockType.DIVIDER:
54
67
  return convertDividerBlockToHtml(blockData);
55
68
  case types_1.BlockType.SPACER:
@@ -60,57 +73,60 @@ function convertToHtml(blockData, rootData) {
60
73
  }
61
74
  function appendOutlookSupport(content, contentStyle) {
62
75
  return `
63
- <table width="100%" style="${exports.tableCommonStyle}">
64
- <tr>
65
- <td style="${contentStyle}">
66
- ${content}
67
- </td>
68
- </tr>
69
- </table>
76
+ <table width="100%" style="${exports.tableCommonStyle}"><tr><td style="${contentStyle}">${content}</td></tr></table>
70
77
  `;
71
78
  }
72
79
  function convertDividerBlockToHtml(blockData) {
73
80
  const { style } = blockData.data;
74
81
  const { thickness, dividerColor, ...rest } = style;
75
- const convertedStyle = buildStyles(rest);
82
+ const convertedStyle = buildStyles(rest, { perChanges: [], pxChanges: allPxAttributes });
76
83
  return appendOutlookSupport(`<hr style="height:${thickness}px; background-color: ${dividerColor};" />`, convertedStyle);
77
84
  }
78
85
  function convertSpacerBlockToHtml(blockData) {
79
86
  const { style } = blockData.data;
80
- const styles = buildStyles(style);
87
+ const styles = buildStyles(style, { perChanges: [], pxChanges: allPxAttributes });
81
88
  return appendOutlookSupport(``, styles);
82
89
  }
83
90
  function convertTextBlock(blockData) {
84
91
  const { style, props } = blockData.data;
85
- const styles = buildStyles(style);
92
+ const styles = buildStyles(style, { perChanges: [], pxChanges: allPxAttributes });
86
93
  const text = props.text || "";
87
94
  const navigateToUrl = props.navigateToUrl || "";
88
95
  const textContent = appendOutlookSupport(text.replaceAll(/\n/g, "<br>"), styles);
89
96
  return navigateToUrl ? `<a href="${navigateToUrl}" style="color:inherit; text-decoration:none; cursor:pointer;">${textContent}</a>` : textContent;
90
97
  }
91
- function appendOutlookForImage(content, imageStyle, imageUrl) {
98
+ async function appendOutlookForImage(content, outerContainerWidth, innerContainerWidth, imageUrl) {
99
+ const image = await jimp_1.Jimp.read(imageUrl);
100
+ const originalWidth = image.bitmap.width;
101
+ const originalHeight = image.bitmap.height;
102
+ // Calculate width scaling factor based on outer and inner widths
103
+ const widthScalingFactor = Math.min(outerContainerWidth / originalWidth, innerContainerWidth / originalWidth, 1);
104
+ // Scale the height proportionally
105
+ const scaledWidth = Math.round(originalWidth * widthScalingFactor);
106
+ const scaledHeight = Math.round(originalHeight * widthScalingFactor); // Maintain aspect ratio
107
+ // VML for Outlook
108
+ const outlookImage = `<!--[if mso]>
109
+ <v:rect xmlns:v="urn:schemas-microsoft-com:vml" fill="t" stroke="f" style="width:${scaledWidth}px;height:${scaledHeight}px;">
110
+ <v:fill src="${imageUrl}" type="frame" />
111
+ </v:rect>
112
+ <![endif]-->`;
92
113
  return `
93
- <!--[if mso]>
94
- <v:rect xmlns:v="urn:schemas-microsoft-com:vml"
95
- fill="t" stroke="f"
96
- style="${imageStyle}">
97
- <v:fill src="${imageUrl}" type="frame"/>
98
- </v:rect>
99
- <![endif]-->
114
+ ${outlookImage}
100
115
  <!--[if !mso]><!-->
101
116
  ${content}
102
117
  <!--<![endif]-->
103
118
  `;
104
119
  }
105
- function convertImageBlock(blockData) {
120
+ async function convertImageBlock(blockData, cellWidthInPx) {
106
121
  const { style, props } = blockData.data;
107
122
  const { altText, imageUrl, navigateToUrl } = props;
108
123
  const { width, height, objectFit, ...containerStyle } = style;
109
124
  const imageStyle = { width, height, objectFit };
110
- const containerStyles = buildStyles(containerStyle);
111
- const imageTagStyles = buildStyles(imageStyle);
125
+ const containerStyles = buildStyles(containerStyle, { perChanges: [], pxChanges: addPxToAttributes });
126
+ const imageTagStyles = buildStyles(imageStyle, { perChanges: addPxOrPerToAttributes, pxChanges: addPxToAttributes });
112
127
  const imageElement = `<img src="${imageUrl}" alt="${altText}" style="${imageTagStyles}" />`;
113
- const imageContent = appendOutlookSupport(appendOutlookForImage(imageElement, imageTagStyles, imageUrl), containerStyles);
128
+ const outlookImage = await appendOutlookForImage(imageElement, cellWidthInPx, parseInt(width.replace("%", "")) * cellWidthInPx, imageUrl);
129
+ const imageContent = appendOutlookSupport(outlookImage, containerStyles);
114
130
  return navigateToUrl ? `<a href="${navigateToUrl}" target="_blank" style="display:block; text-decoration:none; cursor:pointer;">${imageContent}</a>` : imageContent;
115
131
  }
116
132
  function appendOutlookForButton(content, buttonStyle, navigateToUrl, text) {
@@ -145,26 +161,32 @@ function convertButtonBlock(blockData) {
145
161
  textColor,
146
162
  backgroundColor: buttonColor,
147
163
  };
148
- const convertedButtonStyle = buildStyles(buttonStyle);
149
- const convertedStyles = buildStyles(rest);
164
+ const convertedButtonStyle = buildStyles(buttonStyle, { perChanges: [], pxChanges: allPxAttributes });
165
+ const convertedStyles = buildStyles(rest, { perChanges: [], pxChanges: allPxAttributes });
150
166
  const buttonElement = `<a href="${navigateToUrl}" style="display:inline-block; text-decoration:none; cursor:pointer;"><button style="${convertedButtonStyle}">${text}</button></a>`;
151
167
  const buttonContent = appendOutlookSupport(appendOutlookForButton(buttonElement, style, navigateToUrl, text), convertedStyles);
152
168
  return buttonContent;
153
169
  }
154
- function convertGridBlock(blockData, rootData) {
155
- const { style, childrenIds = [], props } = blockData.data;
156
- const { columnGap, ...rest } = style;
157
- const { rows, columns, cellWidths } = props;
158
- const styles = buildStyles(rest);
159
- const gridItems = [];
170
+ async function processGridItemsInParallel(columns, childrenIds, cellWidths, cellWidthInPx, rootData) {
171
+ const gridItemPromises = [];
160
172
  for (let colIndex = 0; colIndex < columns; colIndex++) {
161
173
  const childId = childrenIds[colIndex];
162
174
  const cellWidth = cellWidths ? cellWidths[colIndex] : 100 / columns;
163
175
  const childBlockData = rootData[childId];
164
176
  if (childBlockData) {
165
- gridItems.push(convertGridCellBlock(childBlockData, rootData, cellWidth));
177
+ const gridItemPromise = convertGridCellBlock(childBlockData, rootData, cellWidth, cellWidthInPx);
178
+ gridItemPromises.push(gridItemPromise);
166
179
  }
167
180
  }
181
+ const gridItems = await Promise.all(gridItemPromises);
182
+ return gridItems;
183
+ }
184
+ async function convertGridBlock(blockData, rootData, cellWidthInPx) {
185
+ const { style, childrenIds = [], props } = blockData.data;
186
+ const { columnGap, ...rest } = style;
187
+ const { rows, columns, cellWidths } = props;
188
+ const styles = buildStyles(rest, { perChanges: [], pxChanges: allPxAttributes });
189
+ const gridItems = await processGridItemsInParallel(columns, childrenIds, cellWidths, cellWidthInPx, rootData);
168
190
  return `
169
191
  <table cellspacing="${columnGap}" style="width:100%; max-width:100%; ${styles}">
170
192
  <tbody>
@@ -173,9 +195,14 @@ function convertGridBlock(blockData, rootData) {
173
195
  </table>
174
196
  `;
175
197
  }
176
- function convertGridCellBlock(blockData, rootData, cellWidth) {
198
+ async function convertGridCellBlock(blockData, rootData, cellWidth, parentCellWidth) {
177
199
  const { style, childrenIds } = blockData.data;
178
- const styles = buildStyles(style);
179
- const cellItems = childrenIds && childrenIds.length > 0 ? childrenIds.map((childId) => convertToHtml(rootData[childId], rootData)).join("") : "";
180
- return `<td style="width:${cellWidth}% ; max-width:${cellWidth}%; ${styles}">${cellItems}</td>`;
200
+ const styles = buildStyles(style, { perChanges: [], pxChanges: allPxAttributes });
201
+ const cellItems = [];
202
+ if (childrenIds && childrenIds?.length > 0) {
203
+ for (const childId of childrenIds) {
204
+ cellItems.push(await convertToHtml(rootData[childId], rootData, parentCellWidth * ((style.width || "0").replace("px", "") / 100)));
205
+ }
206
+ }
207
+ return `<td style="width:${cellWidth}% ; max-width:${cellWidth}%; ${styles}">${cellItems.join("")}</td>`;
181
208
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "email-builder-utils",
3
- "version": "1.0.5",
3
+ "version": "1.0.8",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [
@@ -24,6 +24,10 @@
24
24
  "description": "",
25
25
  "devDependencies": {
26
26
  "@types/node": "^22.13.10",
27
+ "@types/pngjs": "^6.0.5",
27
28
  "typescript": "^5.8.2"
29
+ },
30
+ "dependencies": {
31
+ "jimp": "^1.6.0"
28
32
  }
29
33
  }