email-builder-utils 1.1.1 → 1.1.3

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.
@@ -11,48 +11,48 @@ const convertJsonToHtml = async (jsonData) => {
11
11
  (rootData.style?.padding?.left || 0) -
12
12
  (rootData.style?.padding?.right || 0)));
13
13
  }
14
- const rawHtml = `
15
- <!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
- <title>Email Layout</title>
21
- <style>
22
- .responsive-table {
23
- width: 100%;
24
- max-width: 600px;
25
- }
26
-
27
- @media only screen and (max-width: 600px) {
28
- .stack-column {
29
- display: block !important;
30
- width: 100% !important;
31
- max-width: 100% !important;
32
- }
33
- }
34
- </style>
35
- </head>
36
- <body>
37
- <center>
38
- <table
39
- class="responsive-table"
40
- style="font-family: ${rootData.style?.fontFamily};
41
- margin: 0 auto;
42
- background-color: ${rootData.style?.canvasColor};
43
- color: ${rootData.style?.textColor};
44
- ${jsonToHTML_1.tableCommonStyle}"
45
- >
46
- <tbody>
47
- <tr>
48
- <td style="padding: 0;">
49
- ${blocksHtml.join("")}
50
- </td>
51
- </tr>
52
- </tbody>
53
- </table>
54
- </center>
55
- </body>
14
+ const rawHtml = `
15
+ <!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
+ <title>Email Layout</title>
21
+ <style>
22
+ .responsive-table {
23
+ width: 100%;
24
+ max-width: 600px;
25
+ }
26
+
27
+ @media only screen and (max-width: 600px) {
28
+ .stack-column {
29
+ display: block !important;
30
+ width: 100% !important;
31
+ max-width: 100% !important;
32
+ }
33
+ }
34
+ </style>
35
+ </head>
36
+ <body>
37
+ <center>
38
+ <table
39
+ class="responsive-table"
40
+ style="font-family: ${rootData.style?.fontFamily};
41
+ margin: 0 auto;
42
+ background-color: ${rootData.style?.canvasColor};
43
+ color: ${rootData.style?.textColor};
44
+ ${jsonToHTML_1.tableCommonStyle}"
45
+ >
46
+ <tbody>
47
+ <tr>
48
+ <td style="padding: 0;">
49
+ ${blocksHtml.join("")}
50
+ </td>
51
+ </tr>
52
+ </tbody>
53
+ </table>
54
+ </center>
55
+ </body>
56
56
  </html>`;
57
57
  return rawHtml;
58
58
  };
@@ -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;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;AAyDhF,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;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;AAMD,eAAO,MAAM,gBAAgB,kDAAkD,CAAC;AAiDhF,wBAAsB,aAAa,CAAC,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,mBAiB9F"}
@@ -11,9 +11,8 @@ exports.tableCommonStyle = "border-collapse:collapse; table-layout:fixed;";
11
11
  function cleanJson(obj) {
12
12
  if (typeof obj !== "object" || obj === null)
13
13
  return obj;
14
- if (Array.isArray(obj)) {
14
+ if (Array.isArray(obj))
15
15
  return obj.map(cleanJson);
16
- }
17
16
  return Object.fromEntries(Object.entries(obj)
18
17
  .filter(([_, value]) => value !== undefined && value !== null && value !== "")
19
18
  .map(([key, value]) => [key, cleanJson(value)]));
@@ -21,41 +20,35 @@ function cleanJson(obj) {
21
20
  function jsonToPlainString(obj) {
22
21
  if (typeof obj !== "object" || obj === null)
23
22
  return String(obj);
24
- if (Array.isArray(obj)) {
23
+ if (Array.isArray(obj))
25
24
  return obj.map(jsonToPlainString).join(", ");
26
- }
27
25
  return Object.entries(obj)
28
26
  .map(([key, value]) => `${key}:${jsonToPlainString(value)}; `)
29
27
  .join("");
30
28
  }
31
29
  function buildStyles(style, { pxChanges, perChanges }) {
32
- if (!style) {
30
+ if (!style)
33
31
  style = {};
34
- }
35
32
  const stylesObj = {};
36
33
  Object.entries(style).forEach(([key, value]) => {
37
- if (key === "customCss") {
34
+ if (key === "customCss")
38
35
  return;
39
- }
40
- const appendPx = pxChanges.includes(key);
41
- const appendPer = perChanges.includes(key);
42
36
  if (value === undefined || value === null || value === "")
43
- return null;
44
- if ((key === "padding" || key === "buttonPadding") && typeof value === "object" && value !== null) {
37
+ return;
38
+ if ((key === "padding" || key === "buttonPadding") && typeof value === "object") {
45
39
  const padding = value;
46
40
  value = `${padding.top}px ${padding.right}px ${padding.bottom}px ${padding.left}px`;
47
41
  }
48
42
  const cssKey = key.replace(/([A-Z])/g, "-$1").toLowerCase();
49
- if (appendPx) {
43
+ if (pxChanges.includes(key)) {
50
44
  stylesObj[cssKey] = `${value}px`;
51
45
  }
52
- else if (appendPer) {
46
+ else if (perChanges.includes(key)) {
53
47
  stylesObj[cssKey] = `${value}%`;
54
48
  }
55
49
  else {
56
50
  stylesObj[cssKey] = value;
57
51
  }
58
- return;
59
52
  });
60
53
  return `${jsonToPlainString(cleanJson(stylesObj))}${style.customCss || ""}`.trim();
61
54
  }
@@ -78,8 +71,8 @@ async function convertToHtml(blockData, rootData, cellWidthInPx) {
78
71
  }
79
72
  }
80
73
  function appendOutlookSupport(content, contentStyle) {
81
- return `
82
- <table width="100%" style="${exports.tableCommonStyle}"><tr><td style="${contentStyle}">${content}</td></tr></table>
74
+ return `
75
+ <table width="100%" style="${exports.tableCommonStyle}"><tr><td style="${contentStyle}">${content}</td></tr></table>
83
76
  `;
84
77
  }
85
78
  function convertDividerBlockToHtml(blockData) {
@@ -101,26 +94,41 @@ function convertTextBlock(blockData) {
101
94
  const textContent = appendOutlookSupport(text.replaceAll(/\n/g, "<br>"), styles);
102
95
  return navigateToUrl ? `<a href="${navigateToUrl}" style="color:inherit; text-decoration:none; cursor:pointer;">${textContent}</a>` : textContent;
103
96
  }
104
- async function appendOutlookForImage(content, outerContainerWidth, innerContainerWidth, imageUrl) {
97
+ async function appendOutlookForImage(content, outerContainerWidth, innerContainerWidth, imageUrl, style = {}) {
105
98
  const image = await jimp_1.Jimp.read(imageUrl);
106
99
  const originalWidth = image.bitmap.width;
107
100
  const originalHeight = image.bitmap.height;
108
101
  // Calculate width scaling factor based on outer and inner widths
109
102
  const widthScalingFactor = Math.min(outerContainerWidth / originalWidth, innerContainerWidth / originalWidth);
110
- // Scale the height proportionally
103
+ // Scale height proportionally
111
104
  const scaledWidth = Math.round(originalWidth * widthScalingFactor);
112
105
  const scaledHeight = Math.round(originalHeight * widthScalingFactor); // Maintain aspect ratio
113
- // VML for Outlook
114
- const outlookImage = `<!--[if mso]>
115
- <v:rect xmlns:v="urn:schemas-microsoft-com:vml" style="width:${scaledWidth}px;height:${scaledHeight}px; border:none;">
116
- <v:fill src="${imageUrl}" type="frame" />
117
- </v:rect>
106
+ // Extract border styles
107
+ const borderWidth = style?.borderWidth || 0;
108
+ const borderColor = style?.borderColor || "transparent";
109
+ const borderRadius = style?.borderRadius || 0;
110
+ const useRoundRect = borderRadius > 0;
111
+ const arcsize = useRoundRect ? Math.min(borderRadius / scaledWidth, 1) : 0;
112
+ // Conditionally add stroke attributes
113
+ const borderAttributes = borderWidth > 0
114
+ ? `strokeweight="${borderWidth}px" strokecolor="${borderColor}"`
115
+ : `stroked="false"`;
116
+ const outlookImage = `<!--[if mso]>
117
+ <v:${useRoundRect ? "roundrect" : "rect"} xmlns:v="urn:schemas-microsoft-com:vml"
118
+ style="width:${scaledWidth}px;height:${scaledHeight}px;"
119
+ ${borderAttributes}
120
+ ${useRoundRect ? `arcsize="${arcsize}"` : ""}
121
+ fill="true"
122
+ fillcolor="none">
123
+ <v:fill src="${imageUrl}" type="frame" />
124
+ <v:textbox inset="0,0,0,0"><div style="display:none;">.</div></v:textbox>
125
+ </v:${useRoundRect ? "roundrect" : "rect"}>
118
126
  <![endif]-->`;
119
- return `
120
- ${outlookImage}
121
- <!--[if !mso]><!-->
122
- ${content}
123
- <!--<![endif]-->
127
+ return `
128
+ ${outlookImage}
129
+ <!--[if !mso]><!-->
130
+ ${content}
131
+ <!--<![endif]-->
124
132
  `;
125
133
  }
126
134
  async function convertImageBlock(blockData, cellWidthInPx) {
@@ -131,24 +139,26 @@ async function convertImageBlock(blockData, cellWidthInPx) {
131
139
  const containerStyles = buildStyles(containerStyle, { perChanges: [], pxChanges: addPxToAttributes });
132
140
  const imageTagStyles = buildStyles(imageStyle, { perChanges: addPxOrPerToAttributes, pxChanges: addPxToAttributes });
133
141
  const imageElement = `<img src="${imageUrl}" alt="${altText}" style="${imageTagStyles}" />`;
134
- const outlookImage = await appendOutlookForImage(imageElement, cellWidthInPx, ((typeof width === "string" ? parseInt(width.replace("%", "")) : width) / 100) * (cellWidthInPx - style?.padding?.left - style?.padding?.right), imageUrl);
142
+ const outlookImage = await appendOutlookForImage(imageElement, cellWidthInPx, ((typeof width === "string" ? parseInt(width.replace("%", "")) : width) / 100) *
143
+ (cellWidthInPx - style?.padding?.left - style?.padding?.right), imageUrl, style);
135
144
  const imageContent = appendOutlookSupport(outlookImage, containerStyles);
145
+ console.log(imageContent);
136
146
  return navigateToUrl ? `<a href="${navigateToUrl}" target="_blank" style="display:block; text-decoration:none; cursor:pointer;">${imageContent}</a>` : imageContent;
137
147
  }
138
148
  function appendOutlookForButton(content, buttonStyle, navigateToUrl, text) {
139
149
  const { width, buttonColor, borderColor, borderRadius, borderWidth, height, buttonPadding, color, fontFamily, fontSize, fontWeight } = buttonStyle;
140
- return `
141
- <!--[if mso]>
142
- <v:${borderRadius ? "roundrect" : "rect"} xmlns:v="urn:schemas-microsoft-com:vml" href="${navigateToUrl}" xmlns:w="urn:schemas-microsoft-com:office:word" style="height:${height || 44}px;width:${width || 200}px;v-text-anchor:middle;" arcsize="${borderRadius || 0}px" strokeweight="${borderWidth || 0}px" strokecolor="${borderColor || "transparent"}" fillcolor="${buttonColor || "none"}">
143
- <w:anchorlock/>
144
- <v:textbox inset="${buttonPadding?.top || 0}px, ${buttonPadding?.left || 0}px, ${buttonPadding?.bottom || 0}px, ${buttonPadding?.right || 0}px">
145
- <center style="font-family:${fontFamily || ""};font-size:${fontSize}px;font-weight:${fontWeight};color:${color};">${text}</center>
146
- </v:textbox>
147
- </v:${borderRadius ? "roundrect" : "rect"}>
148
- <![endif]-->
149
- <!--[if !mso]><!-->
150
- ${content}
151
- <!--<![endif]-->
150
+ return `
151
+ <!--[if mso]>
152
+ <v:${borderRadius ? "roundrect" : "rect"} xmlns:v="urn:schemas-microsoft-com:vml" href="${navigateToUrl}" xmlns:w="urn:schemas-microsoft-com:office:word" style="height:${height || 44}px;width:${width || 200}px;v-text-anchor:middle;" arcsize="${borderRadius || 0}px" strokeweight="${borderWidth || 0}px" strokecolor="${borderColor || "transparent"}" fillcolor="${buttonColor || "none"}">
153
+ <w:anchorlock/>
154
+ <v:textbox inset="${buttonPadding?.top || 0}px, ${buttonPadding?.left || 0}px, ${buttonPadding?.bottom || 0}px, ${buttonPadding?.right || 0}px">
155
+ <center style="font-family:${fontFamily || ""};font-size:${fontSize}px;font-weight:${fontWeight};color:${color};">${text}</center>
156
+ </v:textbox>
157
+ </v:${borderRadius ? "roundrect" : "rect"}>
158
+ <![endif]-->
159
+ <!--[if !mso]><!-->
160
+ ${content}
161
+ <!--<![endif]-->
152
162
  `;
153
163
  }
154
164
  function convertButtonBlock(blockData) {
@@ -195,12 +205,12 @@ async function convertGridBlock(blockData, rootData, cellWidthInPx) {
195
205
  const { rows, columns, cellWidths } = props;
196
206
  const styles = buildStyles(rest, { perChanges: [], pxChanges: allPxAttributes });
197
207
  const gridItems = await processGridItemsInParallel(columns, childrenIds, cellWidths, cellWidthInPx, rootData);
198
- return `
199
- <table cellspacing="${columnGap}" style="width:100%; max-width:100%; ${styles}">
200
- <tbody>
201
- <tr>${gridItems.join("")}</tr>
202
- </tbody>
203
- </table>
208
+ return `
209
+ <table cellspacing="${columnGap}" style="width:100%; max-width:100%; ${styles}">
210
+ <tbody>
211
+ <tr>${gridItems.join("")}</tr>
212
+ </tbody>
213
+ </table>
204
214
  `;
205
215
  }
206
216
  async function convertGridCellBlock(blockData, rootData, cellWidth, parentCellWidth) {
package/package.json CHANGED
@@ -1,33 +1,33 @@
1
- {
2
- "name": "email-builder-utils",
3
- "version": "1.1.1",
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.3",
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
+ }