@dev.smartpricing/message-composer-utils 3.1.2
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.
- package/dist/types/api/aiIntegration.js +7 -0
- package/dist/types/api/aiIntegration.js.map +1 -0
- package/dist/types/api/apiMessages.js +2 -0
- package/dist/types/api/apiMessages.js.map +1 -0
- package/dist/types/api/index.js +3 -0
- package/dist/types/api/index.js.map +1 -0
- package/dist/types/app.js +23 -0
- package/dist/types/app.js.map +1 -0
- package/dist/types/brand.js +31 -0
- package/dist/types/brand.js.map +1 -0
- package/dist/types/dynamic-values.js +5 -0
- package/dist/types/dynamic-values.js.map +1 -0
- package/dist/types/emailblocks.v1.defaults.js +83 -0
- package/dist/types/emailblocks.v1.defaults.js.map +1 -0
- package/dist/types/emailblocks.v1.js +339 -0
- package/dist/types/emailblocks.v1.js.map +1 -0
- package/dist/types/index.js +16 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/kafka-events.js +2 -0
- package/dist/types/kafka-events.js.map +1 -0
- package/dist/types/language.js +2 -0
- package/dist/types/language.js.map +1 -0
- package/dist/types/media.js +6 -0
- package/dist/types/media.js.map +1 -0
- package/dist/types/message.js +41 -0
- package/dist/types/message.js.map +1 -0
- package/dist/types/meta-template.js +2 -0
- package/dist/types/meta-template.js.map +1 -0
- package/dist/types/meta-webhook.js +2 -0
- package/dist/types/meta-webhook.js.map +1 -0
- package/dist/types/submission-job.js +2 -0
- package/dist/types/submission-job.js.map +1 -0
- package/dist/types/tag.js +2 -0
- package/dist/types/tag.js.map +1 -0
- package/dist/types/utils.js +2 -0
- package/dist/types/utils.js.map +1 -0
- package/dist/types/whatsappblocks.v1.js +24 -0
- package/dist/types/whatsappblocks.v1.js.map +1 -0
- package/dist/utils/array.js +26 -0
- package/dist/utils/array.js.map +1 -0
- package/dist/utils/async.js +22 -0
- package/dist/utils/async.js.map +1 -0
- package/dist/utils/date.js +37 -0
- package/dist/utils/date.js.map +1 -0
- package/dist/utils/index.js +34 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/intl.js +37 -0
- package/dist/utils/intl.js.map +1 -0
- package/dist/utils/meta-status.js +21 -0
- package/dist/utils/meta-status.js.map +1 -0
- package/dist/utils/number.js +24 -0
- package/dist/utils/number.js.map +1 -0
- package/dist/utils/object.js +87 -0
- package/dist/utils/object.js.map +1 -0
- package/dist/utils/string.js +22 -0
- package/dist/utils/string.js.map +1 -0
- package/dist/utils/templates/brand-cascade.js +116 -0
- package/dist/utils/templates/brand-cascade.js.map +1 -0
- package/dist/utils/templates/email-compiler.js +126 -0
- package/dist/utils/templates/email-compiler.js.map +1 -0
- package/dist/utils/templates/email-mjml.js +444 -0
- package/dist/utils/templates/email-mjml.js.map +1 -0
- package/dist/utils/templates/email.js +580 -0
- package/dist/utils/templates/email.js.map +1 -0
- package/dist/utils/templates/html-sanitizer.js +95 -0
- package/dist/utils/templates/html-sanitizer.js.map +1 -0
- package/dist/utils/templates/messages.js +33 -0
- package/dist/utils/templates/messages.js.map +1 -0
- package/dist/utils/templates/meta.js +246 -0
- package/dist/utils/templates/meta.js.map +1 -0
- package/dist/utils/templates/tiptap.js +167 -0
- package/dist/utils/templates/tiptap.js.map +1 -0
- package/dist/utils/templates/whatsapp-compiler.js +168 -0
- package/dist/utils/templates/whatsapp-compiler.js.map +1 -0
- package/dist/utils/templates/whatsapp.js +329 -0
- package/dist/utils/templates/whatsapp.js.map +1 -0
- package/dist/utils/validations.js +39 -0
- package/dist/utils/validations.js.map +1 -0
- package/package.json +43 -0
- package/src/types/api/aiIntegration.ts +59 -0
- package/src/types/api/apiMessages.ts +22 -0
- package/src/types/api/index.ts +2 -0
- package/src/types/app.ts +76 -0
- package/src/types/brand.ts +80 -0
- package/src/types/dynamic-values.ts +47 -0
- package/src/types/emailblocks.v1.defaults.ts +123 -0
- package/src/types/emailblocks.v1.ts +655 -0
- package/src/types/index.ts +15 -0
- package/src/types/kafka-events.ts +46 -0
- package/src/types/language.ts +9 -0
- package/src/types/media.ts +26 -0
- package/src/types/message.ts +635 -0
- package/src/types/meta-template.ts +50 -0
- package/src/types/meta-webhook.ts +32 -0
- package/src/types/submission-job.ts +30 -0
- package/src/types/tag.ts +8 -0
- package/src/types/utils.ts +14 -0
- package/src/types/whatsappblocks.v1.ts +102 -0
- package/src/utils/array.ts +33 -0
- package/src/utils/async.ts +30 -0
- package/src/utils/date.ts +39 -0
- package/src/utils/index.ts +44 -0
- package/src/utils/intl.ts +51 -0
- package/src/utils/meta-status.ts +24 -0
- package/src/utils/number.ts +36 -0
- package/src/utils/object.ts +101 -0
- package/src/utils/string.ts +24 -0
- package/src/utils/templates/brand-cascade.ts +156 -0
- package/src/utils/templates/email-compiler.ts +155 -0
- package/src/utils/templates/email-mjml.ts +602 -0
- package/src/utils/templates/email.ts +713 -0
- package/src/utils/templates/html-sanitizer.ts +121 -0
- package/src/utils/templates/messages.ts +44 -0
- package/src/utils/templates/meta.ts +336 -0
- package/src/utils/templates/tiptap.ts +247 -0
- package/src/utils/templates/whatsapp-compiler.ts +212 -0
- package/src/utils/templates/whatsapp.ts +408 -0
- package/src/utils/validations.ts +49 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EmailMessage,
|
|
3
|
+
EmailBlock,
|
|
4
|
+
EmailGlobalSettings,
|
|
5
|
+
SpacerBlock,
|
|
6
|
+
DividerBlock,
|
|
7
|
+
HeadingBlock,
|
|
8
|
+
ParagraphBlock,
|
|
9
|
+
ImageBlock,
|
|
10
|
+
ButtonBlock,
|
|
11
|
+
GridBlock,
|
|
12
|
+
RawHtmlBlock,
|
|
13
|
+
DiscountBlock,
|
|
14
|
+
HeaderBlock,
|
|
15
|
+
FooterBlock,
|
|
16
|
+
UnsubscribeLinkBlock,
|
|
17
|
+
SocialsBlock,
|
|
18
|
+
TopProductsBlock,
|
|
19
|
+
GridAllowedBlock,
|
|
20
|
+
SocialPlatform,
|
|
21
|
+
} from "../../types/index.js";
|
|
22
|
+
import { SOCIAL_ICONS, SOCIAL_PLATFORMS } from "../../types/index.js";
|
|
23
|
+
|
|
24
|
+
// Default internal horizontal padding for text blocks (heading, paragraph, footer, unsubscribe_link)
|
|
25
|
+
const TEXT_HORIZONTAL_PADDING = 25;
|
|
26
|
+
|
|
27
|
+
import { generateHTML } from "@tiptap/html";
|
|
28
|
+
import type { JSONContent } from "@tiptap/core";
|
|
29
|
+
import { getTipTapExtensions } from "./tiptap.js";
|
|
30
|
+
import { generateText } from "./messages.js";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Converts TipTap JSONContent to HTML for email rendering
|
|
34
|
+
*/
|
|
35
|
+
function generateEmailHtml(content: JSONContent | undefined): string {
|
|
36
|
+
if (!content) return "";
|
|
37
|
+
return generateHTML(content, getTipTapExtensions({ preset: "email" }));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Escapes special characters for XML/MJML attributes
|
|
42
|
+
*/
|
|
43
|
+
function escapeXmlAttribute(value: string | undefined): string {
|
|
44
|
+
if (value === undefined || value === null) {
|
|
45
|
+
return "";
|
|
46
|
+
}
|
|
47
|
+
return value
|
|
48
|
+
.replace(/&/g, "&")
|
|
49
|
+
.replace(/</g, "<")
|
|
50
|
+
.replace(/>/g, ">")
|
|
51
|
+
.replace(/"/g, """)
|
|
52
|
+
.replace(/'/g, "'");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Translates a spacer block to MJML
|
|
57
|
+
*/
|
|
58
|
+
function translateSpacerBlock(
|
|
59
|
+
block: SpacerBlock,
|
|
60
|
+
settings: EmailGlobalSettings,
|
|
61
|
+
): string {
|
|
62
|
+
const bgColor =
|
|
63
|
+
block.settings.backgroundColor === "#FFFFFF00"
|
|
64
|
+
? ""
|
|
65
|
+
: ` container-background-color="${escapeXmlAttribute(block.settings.backgroundColor)}"`;
|
|
66
|
+
const hPad = settings.horizontalSpacing ?? 0;
|
|
67
|
+
|
|
68
|
+
return `<mj-spacer height="${block.settings.height}px" padding="0px ${hPad}px"${bgColor} />`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Translates a divider block to MJML
|
|
73
|
+
*/
|
|
74
|
+
function translateDividerBlock(
|
|
75
|
+
block: DividerBlock,
|
|
76
|
+
settings: EmailGlobalSettings,
|
|
77
|
+
): string {
|
|
78
|
+
const bgColor =
|
|
79
|
+
block.settings.backgroundColor === "#FFFFFF00"
|
|
80
|
+
? ""
|
|
81
|
+
: ` container-background-color="${escapeXmlAttribute(block.settings.backgroundColor)}"`;
|
|
82
|
+
const hPad = settings.horizontalSpacing ?? 0;
|
|
83
|
+
|
|
84
|
+
return `<mj-divider
|
|
85
|
+
border-style="${block.settings.style}"
|
|
86
|
+
border-width="${block.settings.thickness}px"
|
|
87
|
+
width="${block.settings.width}%"
|
|
88
|
+
border-color="${escapeXmlAttribute(block.settings.color)}"
|
|
89
|
+
padding="0px ${hPad}px"${bgColor} />`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Translates a heading block to MJML
|
|
94
|
+
*/
|
|
95
|
+
function translateHeadingBlock(
|
|
96
|
+
block: HeadingBlock,
|
|
97
|
+
settings: EmailGlobalSettings,
|
|
98
|
+
): string {
|
|
99
|
+
const html = generateEmailHtml(block.settings.content);
|
|
100
|
+
const bgColor =
|
|
101
|
+
block.settings.backgroundColor === "#FFFFFF00"
|
|
102
|
+
? ""
|
|
103
|
+
: ` container-background-color="${escapeXmlAttribute(block.settings.backgroundColor)}"`;
|
|
104
|
+
|
|
105
|
+
const textColor = block.settings.textColor ?? settings.textColor;
|
|
106
|
+
const fontFamily = block.settings.fontFamily ?? settings.fontFamily;
|
|
107
|
+
const hPad = TEXT_HORIZONTAL_PADDING + (settings.horizontalSpacing ?? 0);
|
|
108
|
+
|
|
109
|
+
return `<mj-text
|
|
110
|
+
font-family="${escapeXmlAttribute(fontFamily)}"
|
|
111
|
+
font-size="${block.settings.fontSize}px"
|
|
112
|
+
color="${escapeXmlAttribute(textColor)}"
|
|
113
|
+
padding="0px ${hPad}px"${bgColor}>
|
|
114
|
+
<${block.settings.tag} style="font-size: ${block.settings.fontSize}px">${html}</${block.settings.tag}>
|
|
115
|
+
</mj-text>`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Translates a paragraph block to MJML
|
|
120
|
+
*/
|
|
121
|
+
function translateParagraphBlock(
|
|
122
|
+
block: ParagraphBlock,
|
|
123
|
+
settings: EmailGlobalSettings,
|
|
124
|
+
): string {
|
|
125
|
+
const html = generateEmailHtml(block.settings.content);
|
|
126
|
+
|
|
127
|
+
const bgColor =
|
|
128
|
+
block.settings.backgroundColor === "#FFFFFF00"
|
|
129
|
+
? ""
|
|
130
|
+
: ` container-background-color="${escapeXmlAttribute(block.settings.backgroundColor)}"`;
|
|
131
|
+
|
|
132
|
+
const textColor = block.settings.textColor ?? settings.textColor;
|
|
133
|
+
const fontFamily = block.settings.fontFamily ?? settings.fontFamily;
|
|
134
|
+
const hPad = TEXT_HORIZONTAL_PADDING + (settings.horizontalSpacing ?? 0);
|
|
135
|
+
|
|
136
|
+
return `<mj-text
|
|
137
|
+
font-family="${escapeXmlAttribute(fontFamily)}"
|
|
138
|
+
font-size="${block.settings.fontSize}px"
|
|
139
|
+
color="${escapeXmlAttribute(textColor)}"
|
|
140
|
+
padding="0px ${hPad}px"${bgColor}>
|
|
141
|
+
<${block.settings.tag} style="font-size: ${block.settings.fontSize}px">${html}</${block.settings.tag}>
|
|
142
|
+
</mj-text>`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Translates an image block to MJML
|
|
147
|
+
*/
|
|
148
|
+
function translateImageBlock(
|
|
149
|
+
block: ImageBlock,
|
|
150
|
+
settings: EmailGlobalSettings,
|
|
151
|
+
): string {
|
|
152
|
+
const href = block.settings.href
|
|
153
|
+
? ` href="${escapeXmlAttribute(block.settings.href)}"`
|
|
154
|
+
: "";
|
|
155
|
+
const hPad = settings.horizontalSpacing ?? 0;
|
|
156
|
+
|
|
157
|
+
return `<mj-image src="${escapeXmlAttribute(block.settings.src)}"${href} padding="0px ${hPad}px" />`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Translates a button block to MJML
|
|
162
|
+
*/
|
|
163
|
+
function translateButtonBlock(
|
|
164
|
+
block: ButtonBlock,
|
|
165
|
+
settings: EmailGlobalSettings,
|
|
166
|
+
): string {
|
|
167
|
+
const html = generateText(block.settings.content!, "email_v1");
|
|
168
|
+
|
|
169
|
+
const borderRadius =
|
|
170
|
+
block.settings.shape === "square"
|
|
171
|
+
? "0px"
|
|
172
|
+
: block.settings.shape === "rounded"
|
|
173
|
+
? "4px"
|
|
174
|
+
: "50px";
|
|
175
|
+
|
|
176
|
+
const widthAttr =
|
|
177
|
+
block.settings.width === "full_width" ? ' width="100%"' : "";
|
|
178
|
+
|
|
179
|
+
const bgBlock =
|
|
180
|
+
block.settings.backgroundBlock === "#FFFFFF00"
|
|
181
|
+
? ""
|
|
182
|
+
: ` container-background-color="${escapeXmlAttribute(block.settings.backgroundBlock)}"`;
|
|
183
|
+
|
|
184
|
+
// Prepend mailto: or tel: based on linkType
|
|
185
|
+
let href = block.settings.url;
|
|
186
|
+
if (block.settings.linkType === "email" && !href.startsWith("mailto:")) {
|
|
187
|
+
href = `mailto:${href}`;
|
|
188
|
+
} else if (block.settings.linkType === "phone" && !href.startsWith("tel:")) {
|
|
189
|
+
href = `tel:${href}`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const textColor =
|
|
193
|
+
block.settings.textColor ?? settings.button?.textColor ?? "#FFFFFF";
|
|
194
|
+
const backgroundColor =
|
|
195
|
+
block.settings.backgroundColor ??
|
|
196
|
+
settings.button?.backgroundColor ??
|
|
197
|
+
"#000000";
|
|
198
|
+
|
|
199
|
+
const fontFamily = block.settings.fontFamily ?? settings.fontFamily;
|
|
200
|
+
|
|
201
|
+
const hPad = settings.horizontalSpacing ?? 0;
|
|
202
|
+
|
|
203
|
+
return `<mj-button
|
|
204
|
+
href="${escapeXmlAttribute(href)}"
|
|
205
|
+
font-family="${escapeXmlAttribute(fontFamily)}"
|
|
206
|
+
font-size="${block.settings.fontSize}px"
|
|
207
|
+
color="${escapeXmlAttribute(textColor)}"
|
|
208
|
+
background-color="${escapeXmlAttribute(backgroundColor)}"
|
|
209
|
+
border-radius="${borderRadius}"${widthAttr}
|
|
210
|
+
align="${block.settings.alignment}"
|
|
211
|
+
padding="0px ${hPad}px"${bgBlock}>
|
|
212
|
+
${html}
|
|
213
|
+
</mj-button>`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Translates a grid block to MJML (multi-column section)
|
|
218
|
+
*/
|
|
219
|
+
function translateGridBlock(
|
|
220
|
+
block: GridBlock,
|
|
221
|
+
settings: EmailGlobalSettings,
|
|
222
|
+
sectionPadding: string,
|
|
223
|
+
): string {
|
|
224
|
+
const columnWidth = `${100 / block.settings.columns}%`;
|
|
225
|
+
|
|
226
|
+
const columns = block.settings.columnData
|
|
227
|
+
.map(
|
|
228
|
+
(col) => `
|
|
229
|
+
<mj-column width="${columnWidth}">
|
|
230
|
+
${col.blocks.map((b) => translateContentBlockToMjml(b, settings)).join("\n ")}
|
|
231
|
+
</mj-column>`,
|
|
232
|
+
)
|
|
233
|
+
.join("");
|
|
234
|
+
|
|
235
|
+
const bgColor =
|
|
236
|
+
block.settings.backgroundColor === "#FFFFFF00"
|
|
237
|
+
? ""
|
|
238
|
+
: ` background-color="${escapeXmlAttribute(block.settings.backgroundColor)}"`;
|
|
239
|
+
|
|
240
|
+
return `<mj-section padding="${sectionPadding}"${bgColor}>
|
|
241
|
+
${columns}
|
|
242
|
+
</mj-section>`;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Translates a raw HTML block to MJML
|
|
247
|
+
*/
|
|
248
|
+
function translateRawHtmlBlock(block: RawHtmlBlock): string {
|
|
249
|
+
return `<mj-raw>${block.settings.html}</mj-raw>`;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Translates a discount block to MJML
|
|
254
|
+
*/
|
|
255
|
+
function translateDiscountBlock(
|
|
256
|
+
block: DiscountBlock,
|
|
257
|
+
settings: EmailGlobalSettings,
|
|
258
|
+
): string {
|
|
259
|
+
const html = generateEmailHtml(block.settings.content);
|
|
260
|
+
|
|
261
|
+
const borderRadius =
|
|
262
|
+
block.settings.style === "square"
|
|
263
|
+
? "0px"
|
|
264
|
+
: block.settings.style === "rounded"
|
|
265
|
+
? "4px"
|
|
266
|
+
: "50px";
|
|
267
|
+
|
|
268
|
+
const widthAttr =
|
|
269
|
+
block.settings.width === "full_width" ? " width: 100%;" : "";
|
|
270
|
+
|
|
271
|
+
const bgBlock =
|
|
272
|
+
block.settings.backgroundBlock === "#FFFFFF00"
|
|
273
|
+
? ""
|
|
274
|
+
: ` container-background-color="${escapeXmlAttribute(block.settings.backgroundBlock)}"`;
|
|
275
|
+
|
|
276
|
+
const textColor = block.settings.textColor ?? settings.textColor;
|
|
277
|
+
const fontFamily = block.settings.fontFamily ?? settings.fontFamily;
|
|
278
|
+
|
|
279
|
+
const hPad = settings.horizontalSpacing ?? 0;
|
|
280
|
+
|
|
281
|
+
return `<mj-text
|
|
282
|
+
font-family="${escapeXmlAttribute(fontFamily)}"
|
|
283
|
+
font-size="${block.settings.fontSize}px"
|
|
284
|
+
color="${escapeXmlAttribute(textColor)}"
|
|
285
|
+
align="center"
|
|
286
|
+
padding="0px ${hPad}px"${bgBlock}>
|
|
287
|
+
<${block.settings.tag}
|
|
288
|
+
style="
|
|
289
|
+
font-size: ${block.settings.fontSize}px;
|
|
290
|
+
border-radius: ${borderRadius};
|
|
291
|
+
padding: 12px 0px;
|
|
292
|
+
background-color: ${escapeXmlAttribute(block.settings.backgroundComponent)};
|
|
293
|
+
text-align: center; ${widthAttr}"
|
|
294
|
+
>${html}</${block.settings.tag}>
|
|
295
|
+
</mj-text>`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Translates a header block to MJML
|
|
300
|
+
*/
|
|
301
|
+
function translateHeaderBlock(
|
|
302
|
+
block: HeaderBlock,
|
|
303
|
+
settings: EmailGlobalSettings,
|
|
304
|
+
): string {
|
|
305
|
+
const bgColor =
|
|
306
|
+
block.settings.backgroundColor === "#FFFFFF00"
|
|
307
|
+
? ""
|
|
308
|
+
: ` container-background-color="${escapeXmlAttribute(block.settings.backgroundColor)}"`;
|
|
309
|
+
const hPad = settings.horizontalSpacing ?? 0;
|
|
310
|
+
|
|
311
|
+
return `<mj-image
|
|
312
|
+
src="${escapeXmlAttribute(block.settings.src)}"
|
|
313
|
+
alt="${escapeXmlAttribute(block.settings.alt)}"
|
|
314
|
+
width="${block.settings.width}px"
|
|
315
|
+
align="${block.settings.alignment}"
|
|
316
|
+
padding="0px ${hPad}px"${bgColor} />`;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Translates a footer block to MJML
|
|
321
|
+
*/
|
|
322
|
+
function translateFooterBlock(
|
|
323
|
+
block: FooterBlock,
|
|
324
|
+
settings: EmailGlobalSettings,
|
|
325
|
+
): string {
|
|
326
|
+
const html = generateEmailHtml(block.settings.content);
|
|
327
|
+
|
|
328
|
+
const bgColor =
|
|
329
|
+
block.settings.backgroundColor === "#FFFFFF00"
|
|
330
|
+
? ""
|
|
331
|
+
: ` container-background-color="${escapeXmlAttribute(block.settings.backgroundColor)}"`;
|
|
332
|
+
|
|
333
|
+
const textColor = block.settings.textColor ?? settings.textColor;
|
|
334
|
+
const fontFamily = block.settings.fontFamily ?? settings.fontFamily;
|
|
335
|
+
|
|
336
|
+
const hPad = TEXT_HORIZONTAL_PADDING + (settings.horizontalSpacing ?? 0);
|
|
337
|
+
|
|
338
|
+
return `<mj-text
|
|
339
|
+
font-family="${escapeXmlAttribute(fontFamily)}"
|
|
340
|
+
font-size="${block.settings.fontSize}px"
|
|
341
|
+
color="${escapeXmlAttribute(textColor)}"
|
|
342
|
+
padding="0px ${hPad}px"${bgColor}>
|
|
343
|
+
${html}
|
|
344
|
+
</mj-text>`;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Translates an unsubscribe link block to MJML
|
|
349
|
+
*/
|
|
350
|
+
function translateUnsubscribeLinkBlock(
|
|
351
|
+
block: UnsubscribeLinkBlock,
|
|
352
|
+
settings: EmailGlobalSettings,
|
|
353
|
+
): string {
|
|
354
|
+
const html = generateEmailHtml(block.settings.content);
|
|
355
|
+
|
|
356
|
+
const bgColor =
|
|
357
|
+
block.settings.backgroundColor === "#FFFFFF00"
|
|
358
|
+
? ""
|
|
359
|
+
: ` container-background-color="${escapeXmlAttribute(block.settings.backgroundColor)}"`;
|
|
360
|
+
|
|
361
|
+
const textColor = block.settings.textColor ?? settings.textColor;
|
|
362
|
+
const fontFamily = block.settings.fontFamily ?? settings.fontFamily;
|
|
363
|
+
|
|
364
|
+
const hPad = TEXT_HORIZONTAL_PADDING + (settings.horizontalSpacing ?? 0);
|
|
365
|
+
|
|
366
|
+
return `<mj-text
|
|
367
|
+
font-family="${escapeXmlAttribute(fontFamily)}"
|
|
368
|
+
font-size="${block.settings.fontSize}px"
|
|
369
|
+
color="${escapeXmlAttribute(textColor)}"
|
|
370
|
+
align="${block.settings.alignment}"
|
|
371
|
+
padding="0px ${hPad}px"${bgColor}>
|
|
372
|
+
${html}
|
|
373
|
+
</mj-text>`;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Translates a socials block to MJML
|
|
378
|
+
*/
|
|
379
|
+
function translateSocialsBlock(
|
|
380
|
+
block: SocialsBlock,
|
|
381
|
+
settings: EmailGlobalSettings,
|
|
382
|
+
): string {
|
|
383
|
+
const bgColor =
|
|
384
|
+
block.settings.backgroundColor === "#FFFFFF00"
|
|
385
|
+
? ""
|
|
386
|
+
: ` container-background-color="${escapeXmlAttribute(block.settings.backgroundColor)}"`;
|
|
387
|
+
|
|
388
|
+
const enabledSocials = SOCIAL_PLATFORMS.filter(
|
|
389
|
+
(platform: SocialPlatform) => block.settings.socials[platform].enabled,
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
const socialIconsHtml = enabledSocials
|
|
393
|
+
.filter((platform: SocialPlatform) => {
|
|
394
|
+
const url = block.settings.socials[platform].url;
|
|
395
|
+
return url && url.trim() !== "";
|
|
396
|
+
})
|
|
397
|
+
.map((platform: SocialPlatform) => {
|
|
398
|
+
const url = escapeXmlAttribute(block.settings.socials[platform].url);
|
|
399
|
+
const icon = SOCIAL_ICONS[platform];
|
|
400
|
+
return `<a href="${url}" target="_blank" rel="noopener noreferrer" style="display:inline-block;margin:0 4px;"><img src="${icon}" alt="${platform}" width="24" height="24" style="border:0;display:inline-block;" /></a>`;
|
|
401
|
+
})
|
|
402
|
+
.join("");
|
|
403
|
+
|
|
404
|
+
const hPad = settings.horizontalSpacing ?? 0;
|
|
405
|
+
|
|
406
|
+
return `<mj-text align="center" padding="0px ${hPad}px"${bgColor}>
|
|
407
|
+
${socialIconsHtml}
|
|
408
|
+
</mj-text>`;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Translates a top products block to MJML
|
|
413
|
+
* Products are pre-fetched during compilation enrichment
|
|
414
|
+
*/
|
|
415
|
+
function translateTopProductsBlock(
|
|
416
|
+
block: TopProductsBlock,
|
|
417
|
+
settings: EmailGlobalSettings,
|
|
418
|
+
): string {
|
|
419
|
+
const products = block._products;
|
|
420
|
+
if (!products || products.length === 0) return "";
|
|
421
|
+
|
|
422
|
+
const fontFamily = settings.fontFamily || "Arial";
|
|
423
|
+
const displayProducts = products.slice(0, block.settings.maxProducts);
|
|
424
|
+
|
|
425
|
+
const productSections = displayProducts
|
|
426
|
+
.map((product) => {
|
|
427
|
+
const parsed = parseFloat(product.price);
|
|
428
|
+
const showPrice =
|
|
429
|
+
product.price &&
|
|
430
|
+
product.price !== "0" &&
|
|
431
|
+
!isNaN(parsed) &&
|
|
432
|
+
parsed !== 0;
|
|
433
|
+
const priceHtml = showPrice
|
|
434
|
+
? `<mj-text align="left" font-family="${escapeXmlAttribute(fontFamily)}" font-size="10px" line-height="16px" color="#86837C" padding="0px">${escapeXmlAttribute(product.price)}</mj-text>`
|
|
435
|
+
: "";
|
|
436
|
+
|
|
437
|
+
return `<mj-section background-color="#FFFFFF" padding="4px" border-radius="12px" border="2px solid #F1F0EF">
|
|
438
|
+
<mj-group>
|
|
439
|
+
<mj-column width="30%">
|
|
440
|
+
<mj-image padding="0px" border-radius="4px" src="${escapeXmlAttribute(product.image)}" alt="${escapeXmlAttribute(product.name)}" href="${escapeXmlAttribute(product.link)}" />
|
|
441
|
+
</mj-column>
|
|
442
|
+
<mj-column width="70%" padding="8px">
|
|
443
|
+
<mj-text padding="0px" align="left" font-family="${escapeXmlAttribute(fontFamily)}" font-size="12px" line-height="18px" font-weight="500" color="#292826" height="55px">${escapeXmlAttribute(product.name)}</mj-text>
|
|
444
|
+
${priceHtml}
|
|
445
|
+
</mj-column>
|
|
446
|
+
</mj-group>
|
|
447
|
+
</mj-section>`;
|
|
448
|
+
})
|
|
449
|
+
.join("\n");
|
|
450
|
+
|
|
451
|
+
return `<mj-wrapper padding="24px" background-color="${escapeXmlAttribute(block.settings.backgroundColor)}">
|
|
452
|
+
${productSections}
|
|
453
|
+
</mj-wrapper>`;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Translates a single content block to MJML (non-grid blocks only)
|
|
458
|
+
* These are components that go inside mj-column
|
|
459
|
+
*/
|
|
460
|
+
function translateContentBlockToMjml(
|
|
461
|
+
block: GridAllowedBlock,
|
|
462
|
+
settings: EmailGlobalSettings,
|
|
463
|
+
): string {
|
|
464
|
+
switch (block.type) {
|
|
465
|
+
case "discount":
|
|
466
|
+
return translateDiscountBlock(block, settings);
|
|
467
|
+
case "heading":
|
|
468
|
+
return translateHeadingBlock(block, settings);
|
|
469
|
+
case "paragraph":
|
|
470
|
+
return translateParagraphBlock(block, settings);
|
|
471
|
+
case "image":
|
|
472
|
+
return translateImageBlock(block, settings);
|
|
473
|
+
case "button":
|
|
474
|
+
return translateButtonBlock(block, settings);
|
|
475
|
+
default:
|
|
476
|
+
return "";
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Translates a block to MJML section(s)
|
|
482
|
+
* Grid blocks become multi-column sections
|
|
483
|
+
* Other blocks are wrapped in single-column sections
|
|
484
|
+
*/
|
|
485
|
+
function translateBlockToSection(
|
|
486
|
+
block: EmailBlock,
|
|
487
|
+
settings: EmailGlobalSettings,
|
|
488
|
+
isFirst: boolean,
|
|
489
|
+
): string {
|
|
490
|
+
const { verticalSpacing = 0 } = settings;
|
|
491
|
+
const paddingTop = isFirst ? 0 : verticalSpacing;
|
|
492
|
+
const sectionPadding = `${paddingTop}px 0px 0px 0px`;
|
|
493
|
+
|
|
494
|
+
// Wrap single blocks in a section with one column
|
|
495
|
+
let content = "";
|
|
496
|
+
switch (block.type) {
|
|
497
|
+
case "spacer":
|
|
498
|
+
content = translateSpacerBlock(block, settings);
|
|
499
|
+
break;
|
|
500
|
+
case "divider":
|
|
501
|
+
content = translateDividerBlock(block, settings);
|
|
502
|
+
break;
|
|
503
|
+
case "heading":
|
|
504
|
+
content = translateHeadingBlock(block, settings);
|
|
505
|
+
break;
|
|
506
|
+
case "paragraph":
|
|
507
|
+
content = translateParagraphBlock(block, settings);
|
|
508
|
+
break;
|
|
509
|
+
case "image":
|
|
510
|
+
content = translateImageBlock(block, settings);
|
|
511
|
+
break;
|
|
512
|
+
case "button":
|
|
513
|
+
content = translateButtonBlock(block, settings);
|
|
514
|
+
break;
|
|
515
|
+
case "discount":
|
|
516
|
+
content = translateDiscountBlock(block, settings);
|
|
517
|
+
break;
|
|
518
|
+
case "header":
|
|
519
|
+
content = translateHeaderBlock(block, settings);
|
|
520
|
+
break;
|
|
521
|
+
case "raw_html":
|
|
522
|
+
return translateRawHtmlBlock(block);
|
|
523
|
+
case "grid":
|
|
524
|
+
return translateGridBlock(block, settings, sectionPadding);
|
|
525
|
+
case "footer":
|
|
526
|
+
content = translateFooterBlock(block, settings);
|
|
527
|
+
break;
|
|
528
|
+
case "unsubscribe_link":
|
|
529
|
+
content = translateUnsubscribeLinkBlock(block, settings);
|
|
530
|
+
break;
|
|
531
|
+
case "socials":
|
|
532
|
+
content = translateSocialsBlock(block, settings);
|
|
533
|
+
break;
|
|
534
|
+
case "top_products":
|
|
535
|
+
return translateTopProductsBlock(block, settings);
|
|
536
|
+
default:
|
|
537
|
+
return "";
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return `<mj-section padding="${sectionPadding}">
|
|
541
|
+
<mj-column>
|
|
542
|
+
${content}
|
|
543
|
+
</mj-column>
|
|
544
|
+
</mj-section>`;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Generates MJML head section with global settings
|
|
549
|
+
*/
|
|
550
|
+
function generateMjmlHead(settings: EmailGlobalSettings): string {
|
|
551
|
+
const fontFamily = settings.fontFamily || "Arial";
|
|
552
|
+
const textColor = settings.textColor || "#1F2937";
|
|
553
|
+
|
|
554
|
+
return `<mj-head>
|
|
555
|
+
<mj-attributes>
|
|
556
|
+
<mj-text font-family="${escapeXmlAttribute(fontFamily)}" color="${escapeXmlAttribute(textColor)}" padding="0" />
|
|
557
|
+
<mj-all font-family="${escapeXmlAttribute(fontFamily)}" />
|
|
558
|
+
<mj-wrapper padding="0" />
|
|
559
|
+
<mj-section padding="0" />
|
|
560
|
+
<mj-image padding="0" />
|
|
561
|
+
<mj-button padding="0" />
|
|
562
|
+
</mj-attributes>
|
|
563
|
+
<mj-style>
|
|
564
|
+
h1, h2, h3, h4, h5, h6 {
|
|
565
|
+
font-weight: normal;
|
|
566
|
+
}
|
|
567
|
+
.tiptap-emoji img {
|
|
568
|
+
height: 1em !important;
|
|
569
|
+
width: 1em !important;
|
|
570
|
+
vertical-align: middle !important;
|
|
571
|
+
}
|
|
572
|
+
</mj-style>
|
|
573
|
+
</mj-head>`;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Translates an EmailMessage to MJML format
|
|
578
|
+
*
|
|
579
|
+
* @param message - The email message to translate
|
|
580
|
+
* @returns MJML string ready for compilation
|
|
581
|
+
*/
|
|
582
|
+
export function translateEmailToMjml(message: EmailMessage): string {
|
|
583
|
+
const { blocks, settings } = message.body;
|
|
584
|
+
|
|
585
|
+
const sections = blocks
|
|
586
|
+
.map((block, index) =>
|
|
587
|
+
translateBlockToSection(block, settings, index === 0),
|
|
588
|
+
)
|
|
589
|
+
.join("\n ");
|
|
590
|
+
|
|
591
|
+
// Apply defaults for potentially undefined settings
|
|
592
|
+
const contentBackgroundColor = settings.contentBackgroundColor || "#FFFFFF";
|
|
593
|
+
const _backgroundColor = settings.backgroundColor || "#F3F4F6";
|
|
594
|
+
const contentWidth = settings.contentWidth || 600;
|
|
595
|
+
return `<mjml>
|
|
596
|
+
${generateMjmlHead(settings)}
|
|
597
|
+
<mj-body background-color="${escapeXmlAttribute(contentBackgroundColor)}" width="${contentWidth}px">
|
|
598
|
+
|
|
599
|
+
${sections}
|
|
600
|
+
</mj-body>
|
|
601
|
+
</mjml>`;
|
|
602
|
+
}
|