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,
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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="
|
|
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":"
|
|
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"}
|
package/dist/utils/jsonToHTML.js
CHANGED
|
@@ -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 = ["
|
|
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 =
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
155
|
-
const
|
|
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
|
-
|
|
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 =
|
|
180
|
-
|
|
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.
|
|
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
|
}
|