@cymbal/atoms-email-renderer 0.0.1 → 0.1.0
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/index.cjs +908 -454
- package/dist/index.d.ts +17 -8
- package/dist/index.mjs +908 -454
- package/package.json +9 -5
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Mjml, MjmlHead, MjmlStyle, MjmlBody, MjmlSection, MjmlColumn, MjmlText, MjmlRaw, MjmlGroup, MjmlDivider, MjmlImage, MjmlButton } from '@faire/mjml-react';
|
|
2
|
+
import { renderToMjml } from '@faire/mjml-react/utils/renderToMjml';
|
|
3
|
+
import mjml2html from 'mjml';
|
|
2
4
|
import React from 'react';
|
|
3
5
|
|
|
4
6
|
const MIN_LAYOUT_BASIS = 1;
|
|
5
|
-
const DEFAULT_LAYOUT_BASIS = 100;
|
|
6
7
|
|
|
7
8
|
function resolveLayoutVisualStyleValue({
|
|
8
9
|
override,
|
|
@@ -29,31 +30,10 @@ function mergeResolvedLayoutWidth(overrides, global) {
|
|
|
29
30
|
if (widthOverride === void 0 || widthOverride === null) {
|
|
30
31
|
return { ...global.width };
|
|
31
32
|
}
|
|
32
|
-
if (widthOverride && typeof widthOverride === "object" && "type" in widthOverride && widthOverride.type === "max") {
|
|
33
|
-
const legacyMaxWidth = widthOverride;
|
|
34
|
-
return {
|
|
35
|
-
basis: global.width.basis,
|
|
36
|
-
max: legacyMaxWidth.max ?? 200
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
if (widthOverride && typeof widthOverride === "object" && "type" in widthOverride && widthOverride.type === "maxWidth") {
|
|
40
|
-
const legacyMaxWidth = widthOverride;
|
|
41
|
-
return {
|
|
42
|
-
basis: global.width.basis,
|
|
43
|
-
max: legacyMaxWidth.maxWidth ?? 200
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
if (widthOverride && typeof widthOverride === "object" && "type" in widthOverride && widthOverride.type === "basis") {
|
|
47
|
-
const legacyBasis = widthOverride;
|
|
48
|
-
return {
|
|
49
|
-
basis: legacyBasis.basis ?? DEFAULT_LAYOUT_BASIS,
|
|
50
|
-
max: "none"
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
const normalizedWidth = widthOverride;
|
|
54
33
|
return {
|
|
55
|
-
basis:
|
|
56
|
-
max:
|
|
34
|
+
basis: widthOverride.basis ?? global.width.basis,
|
|
35
|
+
max: widthOverride.max ?? global.width.max,
|
|
36
|
+
type: widthOverride.type ?? global.width.type
|
|
57
37
|
};
|
|
58
38
|
}
|
|
59
39
|
function resolveTextStyles(overrides, global) {
|
|
@@ -90,8 +70,9 @@ function resolveColumnsStyles(overrides, global) {
|
|
|
90
70
|
}
|
|
91
71
|
return {
|
|
92
72
|
gap: overrides.gap ?? global.gap,
|
|
93
|
-
|
|
94
|
-
|
|
73
|
+
verticalGap: overrides.verticalGap ?? global.verticalGap,
|
|
74
|
+
contentVerticalAlign: overrides.contentVerticalAlign ?? global.contentVerticalAlign,
|
|
75
|
+
contentHorizontalAlign: overrides.contentHorizontalAlign ?? global.contentHorizontalAlign,
|
|
95
76
|
format: overrides.format ?? global.format
|
|
96
77
|
};
|
|
97
78
|
}
|
|
@@ -113,7 +94,7 @@ function resolveRowsStyles(overrides, global) {
|
|
|
113
94
|
}
|
|
114
95
|
return {
|
|
115
96
|
gap: overrides.gap ?? global.gap,
|
|
116
|
-
|
|
97
|
+
contentHorizontalAlign: overrides.contentHorizontalAlign ?? global.contentHorizontalAlign
|
|
117
98
|
};
|
|
118
99
|
}
|
|
119
100
|
function resolvePadding(overrides, global) {
|
|
@@ -235,31 +216,54 @@ function calculateRowChildWidth({
|
|
|
235
216
|
globalAtomStyles
|
|
236
217
|
}) {
|
|
237
218
|
const resolved = resolveAtomLayoutStyles(child, globalAtomStyles);
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
219
|
+
if (resolved.width.type === "content") {
|
|
220
|
+
return Math.min(estimateContentWidthForAtom({ atom: child, globalAtomStyles }) ?? contentWidth, contentWidth);
|
|
221
|
+
}
|
|
222
|
+
if (resolved.width.type === "max") {
|
|
223
|
+
return Math.min(resolved.width.max, contentWidth);
|
|
241
224
|
}
|
|
242
|
-
return
|
|
225
|
+
return contentWidth;
|
|
226
|
+
}
|
|
227
|
+
function estimateContentWidthForAtom({
|
|
228
|
+
atom,
|
|
229
|
+
globalAtomStyles
|
|
230
|
+
}) {
|
|
231
|
+
if (atom.type === "image" && atom.width > 0) {
|
|
232
|
+
return atom.width;
|
|
233
|
+
}
|
|
234
|
+
if (atom.type === "button") {
|
|
235
|
+
const resolvedText = resolveTextStyles(atom.textStyles, globalAtomStyles.buttonStyles.textStyles);
|
|
236
|
+
const resolvedPadding = resolvePadding(atom.innerPadding, globalAtomStyles.buttonStyles.innerPadding);
|
|
237
|
+
const textLength = Math.max(1, atom.text.replace(/<[^>]*>/g, "").length);
|
|
238
|
+
const averageCharacterWidth = resolvedText.fontSizePx * 0.55;
|
|
239
|
+
return Math.ceil(textLength * averageCharacterWidth + resolvedPadding.left + resolvedPadding.right);
|
|
240
|
+
}
|
|
241
|
+
return null;
|
|
243
242
|
}
|
|
244
243
|
const COLUMN_WIDTH_SPLIT_EPS = 1e-6;
|
|
245
|
-
function
|
|
244
|
+
function calculateColumnChildWidthAllocationFromSpecs({
|
|
246
245
|
contentWidth,
|
|
247
|
-
children,
|
|
248
246
|
gap,
|
|
249
|
-
|
|
247
|
+
widthSpecs
|
|
250
248
|
}) {
|
|
251
|
-
const childCount =
|
|
249
|
+
const childCount = widthSpecs.length;
|
|
252
250
|
if (childCount === 0) {
|
|
253
|
-
return
|
|
251
|
+
return {
|
|
252
|
+
allocatedPercents: [],
|
|
253
|
+
distributableWidthPx: 0,
|
|
254
|
+
isCappedByMax: [],
|
|
255
|
+
minimumFeasiblePercents: [],
|
|
256
|
+
widthsPx: []
|
|
257
|
+
};
|
|
254
258
|
}
|
|
255
259
|
const totalGap = gap * (childCount - 1);
|
|
256
260
|
const distributable = Math.max(0, contentWidth - totalGap);
|
|
257
|
-
const
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
(layoutStyles) => layoutStyles.width.max === "none" ? Number.POSITIVE_INFINITY : layoutStyles.width.max
|
|
261
|
+
const bases = widthSpecs.map((widthSpec) => widthSpec.basis);
|
|
262
|
+
const caps = widthSpecs.map(
|
|
263
|
+
(widthSpec) => widthSpec.type === "max" ? widthSpec.max : Number.POSITIVE_INFINITY
|
|
261
264
|
);
|
|
262
265
|
const widths = Array(childCount).fill(0);
|
|
266
|
+
const isCappedByMax = Array(childCount).fill(false);
|
|
263
267
|
const active = new Set(Array.from({ length: childCount }, (_, index) => index));
|
|
264
268
|
let remaining = distributable;
|
|
265
269
|
let guard = 0;
|
|
@@ -288,6 +292,7 @@ function calculateColumnChildWidths({
|
|
|
288
292
|
for (const index of clampedIndices) {
|
|
289
293
|
const assigned = caps[index];
|
|
290
294
|
widths[index] = assigned;
|
|
295
|
+
isCappedByMax[index] = true;
|
|
291
296
|
remaining -= assigned;
|
|
292
297
|
active.delete(index);
|
|
293
298
|
}
|
|
@@ -301,507 +306,956 @@ function calculateColumnChildWidths({
|
|
|
301
306
|
break;
|
|
302
307
|
}
|
|
303
308
|
}
|
|
304
|
-
|
|
309
|
+
const allocatedPercents = widths.map((width) => {
|
|
310
|
+
if (distributable <= 0) {
|
|
311
|
+
return 0;
|
|
312
|
+
}
|
|
313
|
+
return Math.round(width / distributable * 100);
|
|
314
|
+
});
|
|
315
|
+
const uncappedIndices = widthSpecs.map((widthSpec, index) => widthSpec.type !== "max" ? index : null).filter((index) => index !== null);
|
|
316
|
+
const minimumFeasiblePercents = allocatedPercents.map((allocatedPercent, index) => {
|
|
317
|
+
if (widthSpecs[index]?.type !== "max" && uncappedIndices.length === 1) {
|
|
318
|
+
return allocatedPercent;
|
|
319
|
+
}
|
|
320
|
+
return distributable > 0 ? 1 : 0;
|
|
321
|
+
});
|
|
322
|
+
return {
|
|
323
|
+
allocatedPercents,
|
|
324
|
+
distributableWidthPx: distributable,
|
|
325
|
+
isCappedByMax,
|
|
326
|
+
minimumFeasiblePercents,
|
|
327
|
+
widthsPx: widths
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function calculateColumnChildWidthAllocation({
|
|
331
|
+
contentWidth,
|
|
332
|
+
children,
|
|
333
|
+
gap,
|
|
334
|
+
globalAtomStyles
|
|
335
|
+
}) {
|
|
336
|
+
const widthSpecs = children.map((child) => resolveAtomLayoutStyles(child, globalAtomStyles).width);
|
|
337
|
+
return calculateColumnChildWidthAllocationFromSpecs({ contentWidth, gap, widthSpecs });
|
|
305
338
|
}
|
|
306
339
|
function ensureUrlHasProtocolForPreview(url) {
|
|
307
340
|
const trimmed = url.trim();
|
|
308
341
|
if (trimmed === "") {
|
|
309
342
|
return "";
|
|
310
343
|
}
|
|
311
|
-
if (
|
|
344
|
+
if (/^\$[a-z-]+:[a-zA-Z0-9-]+\$$/.test(trimmed)) {
|
|
345
|
+
return trimmed;
|
|
346
|
+
}
|
|
347
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(trimmed)) {
|
|
312
348
|
return trimmed;
|
|
313
349
|
}
|
|
314
|
-
if (
|
|
350
|
+
if (/^(?:https?:\/\/|mailto:|tel:)/i.test(trimmed)) {
|
|
315
351
|
return trimmed;
|
|
316
352
|
}
|
|
317
353
|
return `https://${trimmed}`;
|
|
318
354
|
}
|
|
319
355
|
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
356
|
+
const ZWNJ_PADDING = "‌‌‌‌‌‌‌‌‌‌‌‌‌‌‌‌‌‌‌‌";
|
|
357
|
+
const COMMON_CSS = `
|
|
358
|
+
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,700;1,400;1,700&family=Roboto:ital,wght@0,400;0,700;1,400;1,700&display=swap');
|
|
359
|
+
|
|
360
|
+
a.inherit-color {
|
|
361
|
+
color: inherit !important;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.display-link-block a {
|
|
365
|
+
display: block !important;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.table-min-width-100 table {
|
|
369
|
+
min-width: 100% !important;
|
|
370
|
+
}`;
|
|
371
|
+
function roundWidth(width) {
|
|
372
|
+
return Math.max(0, Math.round(width));
|
|
373
|
+
}
|
|
374
|
+
function boxOuterWidth(availableWidth, layoutStyles) {
|
|
375
|
+
if (layoutStyles.width.type === "max") {
|
|
376
|
+
return roundWidth(Math.min(layoutStyles.width.max, availableWidth));
|
|
332
377
|
}
|
|
333
|
-
|
|
334
|
-
|
|
378
|
+
return roundWidth(availableWidth);
|
|
379
|
+
}
|
|
380
|
+
function isTransparentColor(color) {
|
|
381
|
+
if (!color) {
|
|
382
|
+
return true;
|
|
335
383
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
const SYSTEM_SAFE_FONTS = /* @__PURE__ */ new Set([
|
|
340
|
-
"Arial",
|
|
341
|
-
"Arial Black",
|
|
342
|
-
"Arial Narrow",
|
|
343
|
-
"Calibri",
|
|
344
|
-
"Consolas",
|
|
345
|
-
"Courier",
|
|
346
|
-
"Courier New",
|
|
347
|
-
"Franklin Gothic",
|
|
348
|
-
"Garamond",
|
|
349
|
-
"Georgia",
|
|
350
|
-
"Helvetica",
|
|
351
|
-
"Lucida Console",
|
|
352
|
-
"Lucida Grande",
|
|
353
|
-
"Tahoma",
|
|
354
|
-
"Times New Roman",
|
|
355
|
-
"Trebuchet MS",
|
|
356
|
-
"Verdana"
|
|
357
|
-
]);
|
|
358
|
-
function collectFontFamilies(doc) {
|
|
359
|
-
const families = /* @__PURE__ */ new Set();
|
|
360
|
-
const rootStyles = doc.globalAtomStyles;
|
|
361
|
-
const addFromTextStyles = (ts) => {
|
|
362
|
-
if (!ts) {
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
if (ts.fontFamily) {
|
|
366
|
-
families.add(ts.fontFamily);
|
|
367
|
-
}
|
|
368
|
-
};
|
|
369
|
-
addFromTextStyles(rootStyles.titleStyles.textStyles);
|
|
370
|
-
addFromTextStyles(rootStyles.subtitleStyles.textStyles);
|
|
371
|
-
addFromTextStyles(rootStyles.headingStyles.textStyles);
|
|
372
|
-
addFromTextStyles(rootStyles.subheadingStyles.textStyles);
|
|
373
|
-
addFromTextStyles(rootStyles.sectionStyles.textStyles);
|
|
374
|
-
addFromTextStyles(rootStyles.bodyStyles.textStyles);
|
|
375
|
-
addFromTextStyles(rootStyles.accentStyles.textStyles);
|
|
376
|
-
addFromTextStyles(rootStyles.captionStyles.textStyles);
|
|
377
|
-
addFromTextStyles(rootStyles.promoStyles.textStyles);
|
|
378
|
-
addFromTextStyles(rootStyles.inputStyles.textStyles);
|
|
379
|
-
addFromTextStyles(rootStyles.optionStyles.textStyles);
|
|
380
|
-
const walkAtom = (atom) => {
|
|
381
|
-
if (atom.type === "text" || atom.type === "button" || atom.type === "input" || atom.type === "contest" || atom.type === "option") {
|
|
382
|
-
addFromTextStyles(atom.textStyles);
|
|
383
|
-
}
|
|
384
|
-
if ("children" in atom && atom.children) {
|
|
385
|
-
atom.children.forEach(walkAtom);
|
|
386
|
-
}
|
|
387
|
-
};
|
|
388
|
-
walkAtom(doc.rootRow);
|
|
389
|
-
return [...families].filter((f) => !SYSTEM_SAFE_FONTS.has(f));
|
|
384
|
+
const normalized = color.trim().toLowerCase();
|
|
385
|
+
return normalized === "transparent" || normalized === "rgba(0,0,0,0)" || normalized === "#00000000";
|
|
390
386
|
}
|
|
391
387
|
function backgroundColorFromAtomBg(bg) {
|
|
392
|
-
if (bg === "none") {
|
|
388
|
+
if (bg === "none" || isTransparentColor(bg.color)) {
|
|
393
389
|
return void 0;
|
|
394
390
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
return
|
|
391
|
+
return bg.color;
|
|
392
|
+
}
|
|
393
|
+
function isImageBackground(bg) {
|
|
394
|
+
return bg !== "none" && bg.type === "image" && bg.imageUrl.trim() !== "";
|
|
399
395
|
}
|
|
400
396
|
function cornerRadiusToCssValue(radius) {
|
|
397
|
+
if (radius.topLeft === 0 && radius.topRight === 0 && radius.bottomRight === 0 && radius.bottomLeft === 0) {
|
|
398
|
+
return void 0;
|
|
399
|
+
}
|
|
401
400
|
return `${radius.topLeft}px ${radius.topRight}px ${radius.bottomRight}px ${radius.bottomLeft}px`;
|
|
402
401
|
}
|
|
403
|
-
function
|
|
402
|
+
function paddingToCss(padding) {
|
|
403
|
+
return `${padding.top}px ${padding.right}px ${padding.bottom}px ${padding.left}px`;
|
|
404
|
+
}
|
|
405
|
+
function layoutPaddingToCss(ls) {
|
|
406
|
+
return `${(ls.padding?.top ?? 0) + (ls.margin?.top ?? 0)}px ${ls.padding?.right ?? 0}px ${(ls.padding?.bottom ?? 0) + (ls.margin?.bottom ?? 0)}px ${ls.padding?.left ?? 0}px`;
|
|
407
|
+
}
|
|
408
|
+
function rowChildSectionPadding(ls, index, count, gap) {
|
|
409
|
+
const top = (index === 0 ? (ls.padding?.top ?? 0) + (ls.margin?.top ?? 0) : 0) + (index > 0 ? gap : 0);
|
|
410
|
+
const bottom = index === count - 1 ? (ls.padding?.bottom ?? 0) + (ls.margin?.bottom ?? 0) : 0;
|
|
411
|
+
return `${top}px ${ls.padding?.right ?? 0}px ${bottom}px ${ls.padding?.left ?? 0}px`;
|
|
412
|
+
}
|
|
413
|
+
function atomBorderToCss(border) {
|
|
414
|
+
const spec = `${border.width}px ${border.style} ${border.color}`;
|
|
415
|
+
if (border.side === "all") {
|
|
416
|
+
return { border: spec };
|
|
417
|
+
}
|
|
404
418
|
return {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
419
|
+
borderTop: border.side === "top" ? spec : "none",
|
|
420
|
+
borderRight: border.side === "right" ? spec : "none",
|
|
421
|
+
borderBottom: border.side === "bottom" ? spec : "none",
|
|
422
|
+
borderLeft: border.side === "left" ? spec : "none"
|
|
409
423
|
};
|
|
410
424
|
}
|
|
411
|
-
function
|
|
412
|
-
const
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
425
|
+
function mjmlLayoutProps(ls) {
|
|
426
|
+
const backgroundColor = backgroundColorFromAtomBg(ls.background);
|
|
427
|
+
const backgroundImageProps = isImageBackground(ls.background) ? {
|
|
428
|
+
backgroundUrl: ls.background.imageUrl,
|
|
429
|
+
backgroundSize: ls.background.imageSize,
|
|
430
|
+
backgroundRepeat: ls.background.imageRepeat ? "repeat" : "no-repeat"
|
|
431
|
+
} : {};
|
|
432
|
+
return compactRecord({
|
|
433
|
+
padding: layoutPaddingToCss(ls),
|
|
434
|
+
backgroundColor,
|
|
435
|
+
borderRadius: cornerRadiusToCssValue(ls.borderRadius),
|
|
436
|
+
...ls.border !== "none" ? atomBorderToCss(ls.border) : {},
|
|
437
|
+
...backgroundImageProps
|
|
420
438
|
});
|
|
439
|
+
}
|
|
440
|
+
function suppressLayoutBackground(layoutStyles) {
|
|
421
441
|
return {
|
|
422
|
-
...
|
|
423
|
-
|
|
424
|
-
...ls.border !== "none" ? atomBorderToReactStyle(ls.border) : {},
|
|
425
|
-
...ls.borderRadius ? { borderRadius: cornerRadiusToCssValue(ls.borderRadius) } : {},
|
|
426
|
-
...layoutHorizontalBlockStyle(blockAlign)
|
|
442
|
+
...layoutStyles,
|
|
443
|
+
background: "none"
|
|
427
444
|
};
|
|
428
445
|
}
|
|
446
|
+
function textStyleToMjmlProps(styles) {
|
|
447
|
+
return compactRecord({
|
|
448
|
+
color: styles.color,
|
|
449
|
+
fontWeight: Number(styles.fontWeight),
|
|
450
|
+
fontStyle: styles.fontStyle === "normal" ? void 0 : styles.fontStyle,
|
|
451
|
+
textTransform: styles.textTransform === "none" ? void 0 : styles.textTransform,
|
|
452
|
+
textDecoration: styles.textDecoration === "none" ? void 0 : styles.textDecoration,
|
|
453
|
+
fontSize: `${styles.fontSizePx}px`,
|
|
454
|
+
fontFamily: styles.fontFamily,
|
|
455
|
+
align: styles.align,
|
|
456
|
+
lineHeight: lineHeightToCss(styles.lineHeight),
|
|
457
|
+
letterSpacing: styles.letterSpacingEm === 0 ? void 0 : `${styles.letterSpacingEm}em`
|
|
458
|
+
});
|
|
459
|
+
}
|
|
429
460
|
function textStyleToCss(styles) {
|
|
430
|
-
return {
|
|
461
|
+
return compactRecord({
|
|
431
462
|
color: styles.color,
|
|
432
463
|
fontWeight: Number(styles.fontWeight),
|
|
433
|
-
fontStyle: styles.fontStyle,
|
|
434
|
-
textTransform: styles.textTransform,
|
|
435
|
-
textDecoration: styles.textDecoration,
|
|
436
|
-
fontSize: styles.fontSizePx
|
|
464
|
+
fontStyle: styles.fontStyle === "normal" ? void 0 : styles.fontStyle,
|
|
465
|
+
textTransform: styles.textTransform === "none" ? void 0 : styles.textTransform,
|
|
466
|
+
textDecoration: styles.textDecoration === "none" ? void 0 : styles.textDecoration,
|
|
467
|
+
fontSize: `${styles.fontSizePx}px`,
|
|
437
468
|
fontFamily: styles.fontFamily,
|
|
438
469
|
textAlign: styles.align,
|
|
439
|
-
lineHeight: styles.lineHeight,
|
|
440
|
-
letterSpacing: `${styles.letterSpacingEm}em`,
|
|
470
|
+
lineHeight: lineHeightToCss(styles.lineHeight),
|
|
471
|
+
letterSpacing: styles.letterSpacingEm === 0 ? void 0 : `${styles.letterSpacingEm}em`,
|
|
441
472
|
margin: 0
|
|
442
|
-
};
|
|
473
|
+
});
|
|
443
474
|
}
|
|
444
|
-
function
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
475
|
+
function lineHeightToCss(lineHeight) {
|
|
476
|
+
if (typeof lineHeight === "number") {
|
|
477
|
+
return `${lineHeight * 100}%`;
|
|
478
|
+
}
|
|
479
|
+
const trimmed = lineHeight.trim();
|
|
480
|
+
if (/^\d*\.?\d+$/.test(trimmed)) {
|
|
481
|
+
return `${Number(trimmed) * 100}%`;
|
|
482
|
+
}
|
|
483
|
+
return trimmed;
|
|
484
|
+
}
|
|
485
|
+
function compactRecord(record) {
|
|
486
|
+
return Object.fromEntries(
|
|
487
|
+
Object.entries(record).filter(([, value]) => value !== void 0 && value !== null && value !== "")
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
function alignAttr(align) {
|
|
491
|
+
return align;
|
|
492
|
+
}
|
|
493
|
+
function escapeHtml(value) {
|
|
494
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
495
|
+
}
|
|
496
|
+
function cssStyle(style) {
|
|
497
|
+
return Object.entries(style).filter(([, value]) => value !== void 0 && value !== "").map(([key, value]) => `${key.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`)}:${value}`).join(";");
|
|
498
|
+
}
|
|
499
|
+
function textHtmlWithLineBreaks(html, underlineLinks, linkColor) {
|
|
500
|
+
return paragraphAwareLineBreaks(
|
|
501
|
+
emailTransformHtml(
|
|
502
|
+
markdownLinksToAnchors(html, underlineLinks, linkColor),
|
|
503
|
+
underlineLinks,
|
|
504
|
+
linkColor
|
|
505
|
+
)
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
function paragraphAwareLineBreaks(html) {
|
|
509
|
+
const normalizedHtml = html.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
510
|
+
const paragraphTagRegex = /<p\b([^>]*)>/gi;
|
|
511
|
+
let currentIndex = 0;
|
|
512
|
+
let output = "";
|
|
513
|
+
let match = paragraphTagRegex.exec(normalizedHtml);
|
|
514
|
+
while (match) {
|
|
515
|
+
let plainSegment = normalizedHtml.slice(currentIndex, match.index);
|
|
516
|
+
if (plainSegment.endsWith("\n")) {
|
|
517
|
+
plainSegment = plainSegment.slice(0, -1);
|
|
518
|
+
}
|
|
519
|
+
output += lineBreaksForPlainTextSegment(plainSegment);
|
|
520
|
+
const contentStartIndex = paragraphTagRegex.lastIndex;
|
|
521
|
+
const closingIndex = normalizedHtml.toLowerCase().indexOf("</p>", contentStartIndex);
|
|
522
|
+
if (closingIndex === -1) {
|
|
523
|
+
output += lineBreaksForPlainTextSegment(normalizedHtml.slice(match.index));
|
|
524
|
+
currentIndex = normalizedHtml.length;
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
output += emailParagraphHtml(match[1] ?? "", normalizedHtml.slice(contentStartIndex, closingIndex));
|
|
528
|
+
currentIndex = closingIndex + "</p>".length;
|
|
529
|
+
if (normalizedHtml[currentIndex] === "\n") {
|
|
530
|
+
currentIndex += 1;
|
|
531
|
+
}
|
|
532
|
+
paragraphTagRegex.lastIndex = currentIndex;
|
|
533
|
+
match = paragraphTagRegex.exec(normalizedHtml);
|
|
534
|
+
}
|
|
535
|
+
if (currentIndex < normalizedHtml.length) {
|
|
536
|
+
output += lineBreaksForPlainTextSegment(normalizedHtml.slice(currentIndex));
|
|
537
|
+
}
|
|
538
|
+
return output;
|
|
539
|
+
}
|
|
540
|
+
function lineBreaksForPlainTextSegment(segment) {
|
|
541
|
+
return segment.replace(/\n/g, "<br />");
|
|
542
|
+
}
|
|
543
|
+
function emailParagraphHtml(attrs, content) {
|
|
544
|
+
const textAlign = extractTextAlignFromAttributes(attrs);
|
|
545
|
+
const style = textAlign ? `margin:0;text-align:${escapeHtml(textAlign)};` : "margin:0;";
|
|
546
|
+
return `<p style="${style}">${lineBreaksForPlainTextSegment(content)}</p>`;
|
|
547
|
+
}
|
|
548
|
+
function extractTextAlignFromAttributes(attrs) {
|
|
549
|
+
const styleMatch = attrs.match(/style=(["'])(.*?)\1/i);
|
|
550
|
+
const style = styleMatch?.[2];
|
|
551
|
+
if (!style) {
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
const textAlignMatch = style.match(/(?:^|;)\s*text-align\s*:\s*(left|right|center|justify)\s*(?:;|$)/i);
|
|
555
|
+
return textAlignMatch?.[1]?.toLowerCase() ?? null;
|
|
556
|
+
}
|
|
557
|
+
function markdownLinksToAnchors(html, underlineLinks, linkColor) {
|
|
558
|
+
return html.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (_match, label, rawHref, offset) => {
|
|
559
|
+
const href = ensureUrlHasProtocolForPreview(rawHref);
|
|
560
|
+
const textDecoration = getActiveSpanTextDecorationAtOffset(html, offset) ?? defaultLinkTextDecoration(underlineLinks);
|
|
561
|
+
const color = getActiveSpanColorAtOffset(html, offset) ?? defaultLinkColor(linkColor);
|
|
562
|
+
return `<a class="inherit-color" style="color: ${escapeHtml(color)} !important; text-decoration: ${escapeHtml(
|
|
563
|
+
textDecoration
|
|
564
|
+
)};" href="${escapeHtml(href)}" target="_blank" rel="noopener noreferrer">${escapeHtml(label)}</a>`;
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
function emailTransformHtml(html, underlineLinks, linkColor) {
|
|
568
|
+
html = html.replace(/style="([^"]*font-size:\s*(\d+)px[^"]*)"/gi, (fullMatch, styleContent, fontSize) => {
|
|
569
|
+
if (/line-height\s*:/i.test(styleContent)) {
|
|
570
|
+
return fullMatch;
|
|
571
|
+
}
|
|
572
|
+
return `style="${styleContent}; line-height: ${textPixelLineHeight(Number(fontSize))};"`;
|
|
573
|
+
});
|
|
574
|
+
html = html.replace(
|
|
575
|
+
/(<span[^>]*>)?<a([^>]*)style="([^"]*?)"([^>]*)>(.*?)<\/a>/gi,
|
|
576
|
+
(_match, span, aAttrsStart, aStyle, aAttrsEnd, aContent) => {
|
|
577
|
+
const nextStyle = appendAnchorEmailStyle(
|
|
578
|
+
aStyle,
|
|
579
|
+
extractTextDecorationFromSpanTag(span) ?? defaultLinkTextDecoration(underlineLinks),
|
|
580
|
+
extractColorFromSpanTag(span) ?? defaultLinkColor(linkColor)
|
|
581
|
+
);
|
|
582
|
+
return `${span ?? ""}<a${ensureClassAttr(`${aAttrsStart}${aAttrsEnd}`, "inherit-color")} style="${nextStyle}">${aContent}</a>`;
|
|
583
|
+
}
|
|
584
|
+
);
|
|
585
|
+
html = html.replace(/(<span[^>]*>)?<a((?:(?!style=)[^>])*)>(.*?)<\/a>/gi, (_match, span, attrs, content) => {
|
|
586
|
+
const textDecoration = extractTextDecorationFromSpanTag(span) ?? defaultLinkTextDecoration(underlineLinks);
|
|
587
|
+
const color = extractColorFromSpanTag(span) ?? defaultLinkColor(linkColor);
|
|
588
|
+
return `${span ?? ""}<a${ensureClassAttr(attrs, "inherit-color")} style="${appendAnchorEmailStyle("", textDecoration, color)}">${content}</a>`;
|
|
589
|
+
});
|
|
590
|
+
return html;
|
|
591
|
+
}
|
|
592
|
+
function defaultLinkColor(linkColor) {
|
|
593
|
+
return linkColor.trim() === "" ? "inherit" : linkColor;
|
|
594
|
+
}
|
|
595
|
+
function defaultLinkTextDecoration(underlineLinks) {
|
|
596
|
+
return underlineLinks ? "underline" : "none";
|
|
597
|
+
}
|
|
598
|
+
function extractTextDecorationFromSpanTag(spanTag) {
|
|
599
|
+
if (!spanTag) {
|
|
600
|
+
return null;
|
|
601
|
+
}
|
|
602
|
+
const match = spanTag.match(/text-decoration\s*:\s*([^;"]+)/i);
|
|
603
|
+
return match?.[1]?.trim() ?? null;
|
|
604
|
+
}
|
|
605
|
+
function extractColorFromSpanTag(spanTag) {
|
|
606
|
+
if (!spanTag) {
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
const match = spanTag.match(/(?:^|[;"\s])color\s*:\s*([^;"]+)/i);
|
|
610
|
+
const color = match?.[1]?.trim();
|
|
611
|
+
if (!color || color === "undefined") {
|
|
612
|
+
return null;
|
|
613
|
+
}
|
|
614
|
+
return color;
|
|
615
|
+
}
|
|
616
|
+
function getActiveSpanTextDecorationAtOffset(html, offset) {
|
|
617
|
+
const spanStack = [];
|
|
618
|
+
const spanTagRegex = /<span[^>]*>|<\/span>/gi;
|
|
619
|
+
const prefix = html.slice(0, offset);
|
|
620
|
+
let match = spanTagRegex.exec(prefix);
|
|
621
|
+
while (match) {
|
|
622
|
+
if (match[0].startsWith("</")) {
|
|
623
|
+
spanStack.pop();
|
|
624
|
+
} else {
|
|
625
|
+
spanStack.push(extractTextDecorationFromSpanTag(match[0]));
|
|
626
|
+
}
|
|
627
|
+
match = spanTagRegex.exec(prefix);
|
|
628
|
+
}
|
|
629
|
+
for (let i = spanStack.length - 1; i >= 0; i -= 1) {
|
|
630
|
+
if (spanStack[i] !== null) {
|
|
631
|
+
return spanStack[i];
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
function getActiveSpanColorAtOffset(html, offset) {
|
|
637
|
+
const spanStack = [];
|
|
638
|
+
const spanTagRegex = /<span[^>]*>|<\/span>/gi;
|
|
639
|
+
const prefix = html.slice(0, offset);
|
|
640
|
+
let match = spanTagRegex.exec(prefix);
|
|
641
|
+
while (match) {
|
|
642
|
+
if (match[0].startsWith("</")) {
|
|
643
|
+
spanStack.pop();
|
|
644
|
+
} else {
|
|
645
|
+
spanStack.push(extractColorFromSpanTag(match[0]));
|
|
646
|
+
}
|
|
647
|
+
match = spanTagRegex.exec(prefix);
|
|
648
|
+
}
|
|
649
|
+
for (let i = spanStack.length - 1; i >= 0; i -= 1) {
|
|
650
|
+
if (spanStack[i] !== null) {
|
|
651
|
+
return spanStack[i];
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
function ensureClassAttr(attrs, className) {
|
|
657
|
+
if (/class="/i.test(attrs)) {
|
|
658
|
+
return attrs.replace(
|
|
659
|
+
/class="([^"]*)"/i,
|
|
660
|
+
(_match, classes) => classes.split(/\s+/).includes(className) ? `class="${classes}"` : `class="${classes} ${className}"`
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
return `${attrs} class="${className}"`;
|
|
664
|
+
}
|
|
665
|
+
function appendAnchorEmailStyle(style, textDecoration, color) {
|
|
666
|
+
const withColor = /color\s*:/i.test(style) ? style : `${style} color: ${color} !important;`;
|
|
667
|
+
return /text-decoration\s*:/i.test(withColor) ? withColor.trim() : `${withColor} text-decoration: ${textDecoration};`.trim();
|
|
668
|
+
}
|
|
669
|
+
function textPixelLineHeight(fontSize) {
|
|
670
|
+
if (fontSize < 12) {
|
|
671
|
+
return `${Math.round(1.5 * fontSize)}px`;
|
|
672
|
+
}
|
|
673
|
+
const t = (fontSize - 12) / (40 - 12);
|
|
674
|
+
const factor = 1.5 - 0.5 * t;
|
|
675
|
+
return `${Math.round(fontSize * factor)}px`;
|
|
676
|
+
}
|
|
677
|
+
function rawTableStyle(width, extra) {
|
|
678
|
+
return cssStyle({
|
|
679
|
+
borderCollapse: "collapse",
|
|
680
|
+
borderSpacing: 0,
|
|
681
|
+
margin: 0,
|
|
682
|
+
padding: 0,
|
|
683
|
+
tableLayout: "fixed",
|
|
684
|
+
width: `${width}px`,
|
|
685
|
+
...extra
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
function rawCellStyle(width, extra) {
|
|
689
|
+
return cssStyle({
|
|
690
|
+
margin: 0,
|
|
691
|
+
padding: 0,
|
|
692
|
+
width: `${width}px`,
|
|
693
|
+
...extra
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
function rawLayoutStyle(ls, extra) {
|
|
697
|
+
return cssStyle({
|
|
698
|
+
padding: layoutPaddingToCss(ls),
|
|
699
|
+
backgroundColor: backgroundColorFromAtomBg(ls.background),
|
|
700
|
+
backgroundImage: isImageBackground(ls.background) ? `url("${ls.background.imageUrl}")` : void 0,
|
|
701
|
+
backgroundSize: isImageBackground(ls.background) ? ls.background.imageSize : void 0,
|
|
702
|
+
backgroundRepeat: isImageBackground(ls.background) ? ls.background.imageRepeat ? "repeat" : "no-repeat" : void 0,
|
|
703
|
+
backgroundPosition: isImageBackground(ls.background) ? "center" : void 0,
|
|
704
|
+
...ls.border !== "none" ? atomBorderToCss(ls.border) : {},
|
|
705
|
+
borderRadius: cornerRadiusToCssValue(ls.borderRadius),
|
|
706
|
+
boxSizing: "border-box",
|
|
707
|
+
...extra
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
function renderRawWrappedBox(html, width, layout) {
|
|
711
|
+
const hasLayout = layoutPaddingToCss(layout) !== "0px 0px 0px 0px" || backgroundColorFromAtomBg(layout.background) || isImageBackground(layout.background) || layout.border !== "none" || cornerRadiusToCssValue(layout.borderRadius);
|
|
712
|
+
if (!hasLayout) {
|
|
713
|
+
return html;
|
|
454
714
|
}
|
|
715
|
+
return `<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="${width}" style="${rawTableStyle(
|
|
716
|
+
width
|
|
717
|
+
)}"><tr><td width="${width}" style="${rawCellStyle(width, { padding: 0 })};${rawLayoutStyle(layout)}">${html}</td></tr></table>`;
|
|
455
718
|
}
|
|
456
|
-
const
|
|
719
|
+
const RawHtml = ({ html }) => /* @__PURE__ */ React.createElement(MjmlRaw, { dangerouslySetInnerHTML: { __html: html } });
|
|
720
|
+
function renderTextAtom(atom, ctx) {
|
|
457
721
|
const rootTextStyles = getRootTextStyles(atom.textType ?? "body", ctx.globalAtomStyles);
|
|
458
722
|
const resolvedText = resolveTextStyles(atom.textStyles, rootTextStyles.textStyles);
|
|
459
723
|
const resolvedLayout = resolveAtomLayoutStyles(atom, ctx.globalAtomStyles);
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}
|
|
468
|
-
|
|
724
|
+
return /* @__PURE__ */ React.createElement(
|
|
725
|
+
MjmlText,
|
|
726
|
+
{
|
|
727
|
+
...textStyleToMjmlProps(resolvedText),
|
|
728
|
+
...mjmlLayoutProps(resolvedLayout),
|
|
729
|
+
cssClass: "atoms-text",
|
|
730
|
+
dangerouslySetInnerHTML: { __html: textHtmlWithLineBreaks(atom.text, ctx.underlineLinks, ctx.linkColor) }
|
|
731
|
+
}
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
function renderButtonAtom(atom, ctx) {
|
|
469
735
|
const resolvedText = resolveTextStyles(atom.textStyles, ctx.globalAtomStyles.buttonStyles.textStyles);
|
|
470
736
|
const resolvedInnerPadding = resolvePadding(atom.innerPadding, ctx.globalAtomStyles.buttonStyles.innerPadding);
|
|
471
737
|
const resolvedLayout = resolveAtomLayoutStyles(atom, ctx.globalAtomStyles);
|
|
472
|
-
const
|
|
473
|
-
const
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
738
|
+
const href = atom.href ? ensureUrlHasProtocolForPreview(atom.href) : "#";
|
|
739
|
+
const width = roundWidth(ctx.contentWidthPx);
|
|
740
|
+
const widthProp = resolvedLayout.width.type === "content" ? void 0 : `${width}px`;
|
|
741
|
+
return /* @__PURE__ */ React.createElement(
|
|
742
|
+
MjmlButton,
|
|
743
|
+
{
|
|
744
|
+
...textStyleToMjmlProps(resolvedText),
|
|
745
|
+
...mjmlLayoutProps(resolvedLayout),
|
|
746
|
+
cssClass: "display-link-block",
|
|
747
|
+
href,
|
|
748
|
+
width: widthProp,
|
|
749
|
+
padding: layoutPaddingToCss(resolvedLayout),
|
|
750
|
+
innerPadding: paddingToCss(resolvedInnerPadding),
|
|
751
|
+
backgroundColor: backgroundColorFromAtomBg(resolvedLayout.background),
|
|
752
|
+
borderRadius: cornerRadiusToCssValue(resolvedLayout.borderRadius),
|
|
753
|
+
dangerouslySetInnerHTML: { __html: atom.text }
|
|
754
|
+
}
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
function renderImageAtom(atom, ctx) {
|
|
486
758
|
const resolvedLayout = resolveAtomLayoutStyles(atom, ctx.globalAtomStyles);
|
|
487
|
-
const
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
759
|
+
const width = roundWidth(ctx.contentWidthPx);
|
|
760
|
+
const height = scaledImageHeight(atom, width);
|
|
761
|
+
return /* @__PURE__ */ React.createElement(
|
|
762
|
+
MjmlImage,
|
|
763
|
+
{
|
|
764
|
+
...mjmlLayoutProps(resolvedLayout),
|
|
765
|
+
src: atom.src,
|
|
766
|
+
href: atom.href ? ensureUrlHasProtocolForPreview(atom.href) : void 0,
|
|
767
|
+
alt: "",
|
|
768
|
+
width: `${width}px`,
|
|
769
|
+
height: height === void 0 ? void 0 : `${height}px`,
|
|
770
|
+
padding: layoutPaddingToCss(resolvedLayout),
|
|
771
|
+
borderRadius: cornerRadiusToCssValue(resolvedLayout.borderRadius)
|
|
772
|
+
}
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
function scaledImageHeight(atom, width) {
|
|
776
|
+
if (atom.width <= 0 || atom.height <= 0) {
|
|
777
|
+
return void 0;
|
|
496
778
|
}
|
|
497
|
-
return
|
|
498
|
-
}
|
|
499
|
-
|
|
779
|
+
return Math.max(1, Math.round(width * atom.height / atom.width));
|
|
780
|
+
}
|
|
781
|
+
function renderDividerAtom(atom, ctx) {
|
|
500
782
|
const resolvedDivider = resolveDividerStyles(atom.dividerStyles, ctx.globalAtomStyles.dividerStyles.dividerStyles);
|
|
501
783
|
const resolvedLayout = resolveAtomLayoutStyles(atom, ctx.globalAtomStyles);
|
|
502
|
-
const wrapperStyle = layoutToWrapperStyle(resolvedLayout, ctx.parentRowBlockHorizontalAlign);
|
|
503
784
|
return /* @__PURE__ */ React.createElement(
|
|
504
|
-
|
|
785
|
+
MjmlDivider,
|
|
505
786
|
{
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
}
|
|
787
|
+
...mjmlLayoutProps(resolvedLayout),
|
|
788
|
+
borderColor: resolvedDivider.color,
|
|
789
|
+
borderStyle: resolvedDivider.style,
|
|
790
|
+
borderWidth: `${resolvedDivider.width}px`,
|
|
791
|
+
padding: layoutPaddingToCss(resolvedLayout)
|
|
510
792
|
}
|
|
511
793
|
);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
border: "1px solid #555",
|
|
515
|
-
borderRadius: 8,
|
|
516
|
-
boxSizing: "border-box",
|
|
517
|
-
color: "#999",
|
|
518
|
-
fontSize: 14,
|
|
519
|
-
minHeight: 40,
|
|
520
|
-
padding: "10px 12px",
|
|
521
|
-
width: "100%"
|
|
522
|
-
};
|
|
523
|
-
const EmailInputAtom = ({ atom, ctx }) => {
|
|
524
|
-
const resolvedLayout = resolveAtomLayoutStyles(atom, ctx.globalAtomStyles);
|
|
525
|
-
const wrapperStyle = layoutToWrapperStyle(resolvedLayout, ctx.parentRowBlockHorizontalAlign);
|
|
526
|
-
const resolvedText = resolveTextStyles(atom.textStyles, ctx.globalAtomStyles.inputStyles.textStyles);
|
|
527
|
-
const resolvedInnerPadding = resolvePadding(atom.innerPadding, ctx.globalAtomStyles.inputStyles.innerPadding);
|
|
528
|
-
const fieldStyle = {
|
|
529
|
-
...fieldBoxStyle,
|
|
530
|
-
...textStyleToCss(resolvedText),
|
|
531
|
-
backgroundColor: backgroundColorFromAtomBg(resolvedLayout.background),
|
|
532
|
-
border: resolvedLayout.border !== "none" ? `${resolvedLayout.border.width}px ${resolvedLayout.border.style} ${resolvedLayout.border.color}` : fieldBoxStyle.border,
|
|
533
|
-
borderRadius: cornerRadiusToCssValue(resolvedLayout.borderRadius),
|
|
534
|
-
padding: `${resolvedInnerPadding.top}px ${resolvedInnerPadding.right}px ${resolvedInnerPadding.bottom}px ${resolvedInnerPadding.left}px`
|
|
535
|
-
};
|
|
536
|
-
return /* @__PURE__ */ React.createElement("div", { style: wrapperStyle }, /* @__PURE__ */ React.createElement("div", { style: fieldStyle }, atom.placeholder || "Text input"));
|
|
537
|
-
};
|
|
538
|
-
const EmailQuestionAtom = ({ atom, ctx }) => {
|
|
539
|
-
const resolvedLayout = resolveAtomLayoutStyles(atom, ctx.globalAtomStyles);
|
|
540
|
-
const wrapperStyle = layoutToWrapperStyle(resolvedLayout, ctx.parentRowBlockHorizontalAlign);
|
|
541
|
-
const resolvedInnerPadding = resolvePadding(atom.innerPadding, ctx.globalAtomStyles.questionStyles.innerPadding);
|
|
542
|
-
const dropdownStyle = {
|
|
543
|
-
...fieldBoxStyle,
|
|
544
|
-
padding: `${resolvedInnerPadding.top}px ${resolvedInnerPadding.right}px ${resolvedInnerPadding.bottom}px ${resolvedInnerPadding.left}px`
|
|
545
|
-
};
|
|
546
|
-
const optionNodes = atom.children.map((option) => /* @__PURE__ */ React.createElement(
|
|
547
|
-
Text,
|
|
548
|
-
{
|
|
549
|
-
key: option.id,
|
|
550
|
-
style: {
|
|
551
|
-
...textStyleToCss(resolveTextStyles(option.textStyles, ctx.globalAtomStyles.optionStyles.textStyles)),
|
|
552
|
-
margin: "0 0 6px 0"
|
|
553
|
-
}
|
|
554
|
-
},
|
|
555
|
-
"[ ] ",
|
|
556
|
-
option.text
|
|
557
|
-
));
|
|
558
|
-
return /* @__PURE__ */ React.createElement("div", { style: wrapperStyle }, atom.questionType === "dropdown" ? /* @__PURE__ */ React.createElement("div", { style: dropdownStyle }, "Select an option") : optionNodes);
|
|
559
|
-
};
|
|
560
|
-
const EmailOptionAtom = ({ atom, ctx }) => {
|
|
561
|
-
const resolvedLayout = resolveAtomLayoutStyles(atom, ctx.globalAtomStyles);
|
|
562
|
-
const wrapperStyle = layoutToWrapperStyle(resolvedLayout, ctx.parentRowBlockHorizontalAlign);
|
|
563
|
-
const resolvedText = resolveTextStyles(atom.textStyles, ctx.globalAtomStyles.optionStyles.textStyles);
|
|
564
|
-
return /* @__PURE__ */ React.createElement("div", { style: wrapperStyle }, /* @__PURE__ */ React.createElement(Text, { style: { ...textStyleToCss(resolvedText), margin: 0 } }, atom.text));
|
|
565
|
-
};
|
|
566
|
-
const EmailContestAtom = ({ atom, ctx }) => {
|
|
567
|
-
const resolvedLayout = resolveAtomLayoutStyles(atom, ctx.globalAtomStyles);
|
|
568
|
-
const wrapperStyle = layoutToWrapperStyle(resolvedLayout, ctx.parentRowBlockHorizontalAlign);
|
|
569
|
-
const resolvedText = resolveTextStyles(atom.textStyles, ctx.globalAtomStyles.buttonStyles.textStyles);
|
|
570
|
-
const resolvedButtonLayout = resolvedLayout;
|
|
571
|
-
const resolvedInnerPadding = resolvePadding(atom.innerPadding, ctx.globalAtomStyles.buttonStyles.innerPadding);
|
|
572
|
-
const buttonBackground = resolvedButtonLayout.background !== "none" ? backgroundColorFromAtomBg(resolvedButtonLayout.background) : void 0;
|
|
573
|
-
const buttonStyle = {
|
|
574
|
-
...textStyleToCss(resolvedText),
|
|
575
|
-
backgroundColor: buttonBackground,
|
|
576
|
-
borderRadius: cornerRadiusToCssValue(resolvedButtonLayout.borderRadius),
|
|
577
|
-
boxSizing: "border-box",
|
|
578
|
-
display: "block",
|
|
579
|
-
minHeight: Math.max(42, resolvedText.fontSizePx * 2.5),
|
|
580
|
-
padding: `${resolvedInnerPadding.top}px ${resolvedInnerPadding.right}px ${resolvedInnerPadding.bottom}px ${resolvedInnerPadding.left}px`,
|
|
581
|
-
textDecoration: "none",
|
|
582
|
-
width: "100%"
|
|
583
|
-
};
|
|
584
|
-
const label = atom.contestType === "facebook" ? "Share link on Facebook" : atom.contestType === "instagram" ? `Follow ${atom.identifier}` : "Share this contest";
|
|
585
|
-
return /* @__PURE__ */ React.createElement("div", { style: wrapperStyle }, /* @__PURE__ */ React.createElement(Button, { href: "#", style: buttonStyle }, label, " +", atom.entriesPerSubmission));
|
|
586
|
-
};
|
|
587
|
-
const EmailAutomationUpcomingEventsAtom = ({
|
|
588
|
-
atom,
|
|
589
|
-
ctx
|
|
590
|
-
}) => {
|
|
591
|
-
return null;
|
|
592
|
-
};
|
|
593
|
-
const EmailAutomationTriggeredEventAtom = ({
|
|
594
|
-
atom,
|
|
595
|
-
ctx
|
|
596
|
-
}) => {
|
|
597
|
-
return null;
|
|
598
|
-
};
|
|
599
|
-
const EmailFormAtom = ({ atom, ctx }) => {
|
|
600
|
-
const rowsAtom = {
|
|
601
|
-
atomKey: atom.atomKey,
|
|
602
|
-
type: "rows",
|
|
603
|
-
source: null,
|
|
604
|
-
children: atom.children,
|
|
605
|
-
rowsStyles: atom.rowsStyles,
|
|
606
|
-
layoutStyles: atom.layoutStyles
|
|
607
|
-
};
|
|
608
|
-
const belowSubmitRowsAtom = {
|
|
609
|
-
atomKey: atom.atomKey,
|
|
610
|
-
type: "rows",
|
|
611
|
-
source: null,
|
|
612
|
-
children: atom.belowSubmitChildren,
|
|
613
|
-
rowsStyles: null,
|
|
614
|
-
layoutStyles: null
|
|
615
|
-
};
|
|
616
|
-
return /* @__PURE__ */ React.createElement(Section, null, /* @__PURE__ */ React.createElement(EmailRowsAtom, { atom: rowsAtom, ctx }), atom.belowSubmitChildren.length > 0 && /* @__PURE__ */ React.createElement(EmailRowsAtom, { atom: belowSubmitRowsAtom, ctx }));
|
|
617
|
-
};
|
|
618
|
-
const EmailAtom = ({ atom, ctx }) => {
|
|
794
|
+
}
|
|
795
|
+
function renderColumnContentAtom(atom, ctx) {
|
|
619
796
|
switch (atom.type) {
|
|
620
797
|
case "text":
|
|
621
|
-
return
|
|
798
|
+
return React.cloneElement(renderTextAtom(atom, ctx), { key: ctx.keyPrefix });
|
|
622
799
|
case "button":
|
|
623
|
-
return
|
|
800
|
+
return React.cloneElement(renderButtonAtom(atom, ctx), { key: ctx.keyPrefix });
|
|
624
801
|
case "image":
|
|
625
|
-
return
|
|
802
|
+
return React.cloneElement(renderImageAtom(atom, ctx), { key: ctx.keyPrefix });
|
|
626
803
|
case "divider":
|
|
627
|
-
return
|
|
628
|
-
case "option":
|
|
629
|
-
return /* @__PURE__ */ React.createElement(EmailOptionAtom, { atom, ctx });
|
|
804
|
+
return React.cloneElement(renderDividerAtom(atom, ctx), { key: ctx.keyPrefix });
|
|
630
805
|
case "rows":
|
|
631
|
-
return
|
|
806
|
+
return renderRowsAsColumnContent(atom, ctx);
|
|
632
807
|
case "columns":
|
|
633
|
-
return /* @__PURE__ */ React.createElement(
|
|
634
|
-
case "
|
|
635
|
-
|
|
808
|
+
return /* @__PURE__ */ React.createElement(RawHtml, { key: ctx.keyPrefix, html: renderRawColumnsAtom(atom, ctx) });
|
|
809
|
+
case "automation-upcoming-events":
|
|
810
|
+
case "automation-triggered-event":
|
|
811
|
+
case "form":
|
|
636
812
|
case "question":
|
|
637
|
-
|
|
813
|
+
case "input":
|
|
814
|
+
case "option":
|
|
638
815
|
case "contest":
|
|
639
|
-
return
|
|
640
|
-
|
|
641
|
-
|
|
816
|
+
return null;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
function renderChildrenAsColumnContent({
|
|
820
|
+
children,
|
|
821
|
+
resolvedRows,
|
|
822
|
+
ctx
|
|
823
|
+
}) {
|
|
824
|
+
const gap = resolvedRows.gap;
|
|
825
|
+
const nodes = [];
|
|
826
|
+
children.forEach((child, index) => {
|
|
827
|
+
if (index > 0 && gap > 0) {
|
|
828
|
+
nodes.push(
|
|
829
|
+
/* @__PURE__ */ React.createElement(
|
|
830
|
+
RawHtml,
|
|
831
|
+
{
|
|
832
|
+
key: `${ctx.keyPrefix}-gap-${index}`,
|
|
833
|
+
html: `<div style="height:${gap}px;line-height:${gap}px"> </div>`
|
|
834
|
+
}
|
|
835
|
+
)
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
const childLayout = resolveAtomLayoutStyles(child, ctx.globalAtomStyles);
|
|
839
|
+
const childOuterWidth = roundWidth(
|
|
840
|
+
calculateRowChildWidth({
|
|
841
|
+
contentWidth: ctx.contentWidthPx,
|
|
842
|
+
child,
|
|
843
|
+
globalAtomStyles: ctx.globalAtomStyles
|
|
844
|
+
})
|
|
845
|
+
);
|
|
846
|
+
const childContentWidth = roundWidth(computeAtomContentWidth(childOuterWidth, childLayout));
|
|
847
|
+
const childCtx = {
|
|
848
|
+
...ctx,
|
|
849
|
+
contentWidthPx: childContentWidth,
|
|
850
|
+
parentRowBlockHorizontalAlign: resolvedRows.contentHorizontalAlign,
|
|
851
|
+
keyPrefix: `${ctx.keyPrefix}-${child.atomKey}-${index}`
|
|
852
|
+
};
|
|
853
|
+
nodes.push(renderColumnContentAtom(child, childCtx));
|
|
854
|
+
});
|
|
855
|
+
return nodes;
|
|
856
|
+
}
|
|
857
|
+
function renderRowsAsColumnContent(atom, ctx) {
|
|
858
|
+
const resolvedRows = resolveRowsStyles(atom.rowsStyles, ctx.globalAtomStyles.rowsStyles.rowsStyles);
|
|
859
|
+
return renderChildrenAsColumnContent({ children: atom.children, resolvedRows, ctx });
|
|
860
|
+
}
|
|
861
|
+
function renderBodyAtom(atom, ctx) {
|
|
862
|
+
switch (atom.type) {
|
|
863
|
+
case "rows":
|
|
864
|
+
return renderRowsAsBodySections(atom, ctx);
|
|
865
|
+
case "columns":
|
|
866
|
+
return renderColumnsAsBodySection(atom, ctx);
|
|
867
|
+
case "text":
|
|
868
|
+
case "button":
|
|
869
|
+
case "image":
|
|
870
|
+
case "divider":
|
|
871
|
+
return renderLeafAsBodySection(atom, ctx);
|
|
642
872
|
case "automation-upcoming-events":
|
|
643
|
-
return /* @__PURE__ */ React.createElement(EmailAutomationUpcomingEventsAtom, { atom, ctx });
|
|
644
873
|
case "automation-triggered-event":
|
|
645
|
-
|
|
874
|
+
case "form":
|
|
875
|
+
case "question":
|
|
876
|
+
case "input":
|
|
877
|
+
case "option":
|
|
878
|
+
case "contest":
|
|
879
|
+
return null;
|
|
646
880
|
}
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
const
|
|
881
|
+
}
|
|
882
|
+
function renderRowsAsBodySections(atom, ctx) {
|
|
883
|
+
const resolvedLayoutBase = resolveAtomLayoutStyles(atom, ctx.globalAtomStyles);
|
|
884
|
+
const resolvedLayout = ctx.suppressRowsBackgroundAtomKey === atom.atomKey ? suppressLayoutBackground(resolvedLayoutBase) : resolvedLayoutBase;
|
|
650
885
|
const resolvedRows = resolveRowsStyles(atom.rowsStyles, ctx.globalAtomStyles.rowsStyles.rowsStyles);
|
|
651
|
-
const
|
|
652
|
-
const
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
style: {
|
|
663
|
-
width: "100%",
|
|
664
|
-
display: "flex",
|
|
665
|
-
flexDirection: "column",
|
|
666
|
-
alignItems: rowCrossAlign
|
|
667
|
-
}
|
|
668
|
-
},
|
|
669
|
-
atom.children.map((child, i) => {
|
|
670
|
-
const childResolved = resolveAtomLayoutStyles(child, ctx.globalAtomStyles);
|
|
671
|
-
const childMaxWidth = childResolved.width.max;
|
|
672
|
-
const slotOuterWidth = calculateRowChildWidth({
|
|
886
|
+
const outerWidth = boxOuterWidth(ctx.contentWidthPx, resolvedLayout);
|
|
887
|
+
const innerWidth = roundWidth(computeAtomContentWidth(outerWidth, resolvedLayout));
|
|
888
|
+
if (atom.children.length === 0) {
|
|
889
|
+
return [
|
|
890
|
+
/* @__PURE__ */ React.createElement(MjmlSection, { key: `${ctx.keyPrefix}-empty`, ...mjmlLayoutProps(resolvedLayout) }, /* @__PURE__ */ React.createElement(MjmlColumn, { width: `${innerWidth}px`, padding: "0" }, /* @__PURE__ */ React.createElement(MjmlText, { padding: "0" }, "\xA0")))
|
|
891
|
+
];
|
|
892
|
+
}
|
|
893
|
+
return atom.children.map((child, index) => {
|
|
894
|
+
const childResolved = resolveAtomLayoutStyles(child, ctx.globalAtomStyles);
|
|
895
|
+
const childOuterWidth = roundWidth(
|
|
896
|
+
calculateRowChildWidth({
|
|
673
897
|
contentWidth: innerWidth,
|
|
674
898
|
child,
|
|
675
899
|
globalAtomStyles: ctx.globalAtomStyles
|
|
900
|
+
})
|
|
901
|
+
);
|
|
902
|
+
const childContentWidth = roundWidth(computeAtomContentWidth(childOuterWidth, childResolved));
|
|
903
|
+
const childHorizontalAlign = effectiveBlockHorizontalAlign({
|
|
904
|
+
layoutHorizontalAlign: childResolved.horizontalAlign,
|
|
905
|
+
parentRowBlockHorizontalAlign: resolvedRows.contentHorizontalAlign
|
|
906
|
+
});
|
|
907
|
+
const childCtx = {
|
|
908
|
+
...ctx,
|
|
909
|
+
contentWidthPx: childContentWidth,
|
|
910
|
+
parentRowBlockHorizontalAlign: resolvedRows.contentHorizontalAlign,
|
|
911
|
+
keyPrefix: `${ctx.keyPrefix}-${child.atomKey}-${index}`
|
|
912
|
+
};
|
|
913
|
+
if (child.type === "columns") {
|
|
914
|
+
return renderColumnsAsBodySection(child, childCtx, {
|
|
915
|
+
key: `${ctx.keyPrefix}-${child.atomKey}-${index}`,
|
|
916
|
+
paddingTop: index > 0 ? resolvedRows.gap : 0,
|
|
917
|
+
padding: rowChildSectionPadding(resolvedLayout, index, atom.children.length, resolvedRows.gap),
|
|
918
|
+
width: childOuterWidth,
|
|
919
|
+
align: childHorizontalAlign});
|
|
920
|
+
}
|
|
921
|
+
if (child.type === "rows") {
|
|
922
|
+
return renderLeafAsBodySection(child, childCtx, {
|
|
923
|
+
key: `${ctx.keyPrefix}-${child.atomKey}-${index}`,
|
|
924
|
+
paddingTop: index > 0 ? resolvedRows.gap : 0,
|
|
925
|
+
padding: rowChildSectionPadding(resolvedLayout, index, atom.children.length, resolvedRows.gap),
|
|
926
|
+
width: childOuterWidth,
|
|
927
|
+
align: childHorizontalAlign,
|
|
928
|
+
parentLayout: resolvedLayout
|
|
676
929
|
});
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
const
|
|
693
|
-
|
|
930
|
+
}
|
|
931
|
+
return renderLeafAsBodySection(child, childCtx, {
|
|
932
|
+
key: `${ctx.keyPrefix}-${child.atomKey}-${index}`,
|
|
933
|
+
paddingTop: index > 0 ? resolvedRows.gap : 0,
|
|
934
|
+
padding: rowChildSectionPadding(resolvedLayout, index, atom.children.length, resolvedRows.gap),
|
|
935
|
+
width: childOuterWidth,
|
|
936
|
+
align: childHorizontalAlign,
|
|
937
|
+
parentLayout: resolvedLayout
|
|
938
|
+
});
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
function renderLeafAsBodySection(atom, ctx, opts) {
|
|
942
|
+
const width = opts?.width ?? ctx.contentWidthPx;
|
|
943
|
+
const sectionPaddingTop = opts?.paddingTop ?? 0;
|
|
944
|
+
const sectionLayout = opts?.parentLayout;
|
|
945
|
+
const sectionProps = compactRecord({
|
|
946
|
+
...sectionLayout ? mjmlLayoutProps(sectionLayout) : {},
|
|
947
|
+
padding: opts?.padding ?? `${sectionPaddingTop}px 0 0 0`,
|
|
948
|
+
textAlign: opts?.align
|
|
949
|
+
});
|
|
950
|
+
return /* @__PURE__ */ React.createElement(MjmlSection, { key: opts?.key ?? ctx.keyPrefix, ...sectionProps }, /* @__PURE__ */ React.createElement(MjmlColumn, { width: `${width}px`, padding: "0" }, renderColumnContentAtom(atom, { ...ctx, contentWidthPx: width })));
|
|
951
|
+
}
|
|
952
|
+
function renderColumnsAsBodySection(atom, ctx, opts) {
|
|
694
953
|
const resolvedCols = resolveColumnsStyles(atom.columnsStyles, ctx.globalAtomStyles.columnsStyles.columnsStyles);
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
const
|
|
699
|
-
const
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
top: "top",
|
|
705
|
-
middle: "middle",
|
|
706
|
-
bottom: "bottom"
|
|
707
|
-
};
|
|
708
|
-
const vAlign = verticalAlignMap[resolvedCols.verticalAlign] ?? "top";
|
|
709
|
-
const childCount = atom.children.length;
|
|
710
|
-
const totalGapPx = childCount > 1 ? gapPx * (childCount - 1) : 0;
|
|
711
|
-
const rawColumnWidths = childCount === 0 ? [] : calculateColumnChildWidths({
|
|
712
|
-
contentWidth: innerWidthPx,
|
|
954
|
+
if (resolvedCols.format === "table") {
|
|
955
|
+
return /* @__PURE__ */ React.createElement(MjmlSection, { key: opts?.key ?? ctx.keyPrefix, padding: opts?.padding ?? `${opts?.paddingTop ?? 0}px 0 0 0` }, /* @__PURE__ */ React.createElement(MjmlColumn, { padding: "0" }, /* @__PURE__ */ React.createElement(RawHtml, { html: renderRawColumnsAtom(atom, ctx) })));
|
|
956
|
+
}
|
|
957
|
+
const resolvedLayout = resolveAtomLayoutStyles(atom, ctx.globalAtomStyles);
|
|
958
|
+
const outerWidth = opts?.width ?? boxOuterWidth(ctx.contentWidthPx, resolvedLayout);
|
|
959
|
+
const innerWidth = roundWidth(computeAtomContentWidth(outerWidth, resolvedLayout));
|
|
960
|
+
const gapPx = Math.max(0, Math.round(resolvedCols.gap));
|
|
961
|
+
const columnWidths = calculateColumnChildWidthAllocation({
|
|
962
|
+
contentWidth: innerWidth,
|
|
713
963
|
children: atom.children,
|
|
714
964
|
gap: gapPx,
|
|
715
965
|
globalAtomStyles: ctx.globalAtomStyles
|
|
966
|
+
}).widthsPx.map(roundWidth);
|
|
967
|
+
const groupWidth = roundWidth(
|
|
968
|
+
columnWidths.reduce((sum, width) => sum + width, 0) + gapPx * Math.max(0, atom.children.length - 1)
|
|
969
|
+
);
|
|
970
|
+
const vAlign = resolvedCols.contentVerticalAlign === "middle" ? "middle" : resolvedCols.contentVerticalAlign;
|
|
971
|
+
const sectionProps = compactRecord({
|
|
972
|
+
...mjmlLayoutProps(resolvedLayout),
|
|
973
|
+
padding: opts?.padding ?? `${opts?.paddingTop ?? 0}px 0 0 0`,
|
|
974
|
+
textAlign: opts?.align ?? resolvedCols.contentHorizontalAlign
|
|
716
975
|
});
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
const outerTableStyle = {
|
|
723
|
-
...wrapperStyle,
|
|
724
|
-
width: totalTableWidth,
|
|
725
|
-
...narrowColumnsMargins,
|
|
726
|
-
...resolvedLayout.width.max !== "none" ? { maxWidth: resolvedLayout.width.max } : {}
|
|
727
|
-
};
|
|
728
|
-
const innerRowStyle = {
|
|
729
|
-
width: totalTableWidth,
|
|
730
|
-
tableLayout: "fixed"
|
|
731
|
-
};
|
|
732
|
-
const spacerStyle = {
|
|
733
|
-
width: gapPx,
|
|
734
|
-
padding: 0,
|
|
735
|
-
verticalAlign: vAlign,
|
|
736
|
-
lineHeight: 0,
|
|
737
|
-
fontSize: 0
|
|
738
|
-
};
|
|
739
|
-
const cells = [];
|
|
740
|
-
atom.children.forEach((child, i) => {
|
|
741
|
-
if (i > 0 && gapPx > 0) {
|
|
742
|
-
cells.push(
|
|
743
|
-
/* @__PURE__ */ React.createElement(Column, { key: `${atom.atomKey}-gap-${i}`, style: spacerStyle }, "\xA0")
|
|
744
|
-
);
|
|
745
|
-
}
|
|
746
|
-
const wPx = columnWidthsPx[i] ?? 0;
|
|
747
|
-
const childResolved = resolvedChildLayouts[i];
|
|
748
|
-
const childContentWidthPx = computeAtomContentWidth(wPx, childResolved);
|
|
749
|
-
const columnStyle = {
|
|
750
|
-
width: wPx,
|
|
751
|
-
verticalAlign: vAlign
|
|
752
|
-
};
|
|
976
|
+
return /* @__PURE__ */ React.createElement(MjmlSection, { key: opts?.key ?? ctx.keyPrefix, ...sectionProps }, /* @__PURE__ */ React.createElement(MjmlGroup, { width: `${groupWidth}px` }, atom.children.map((child, index) => {
|
|
977
|
+
const childWidth = columnWidths[index] ?? 0;
|
|
978
|
+
const gapBefore = index === 0 ? 0 : gapPx;
|
|
979
|
+
const columnWidth = childWidth + gapBefore;
|
|
980
|
+
const childLayout = resolveAtomLayoutStyles(child, ctx.globalAtomStyles);
|
|
753
981
|
const childCtx = {
|
|
754
982
|
...ctx,
|
|
755
|
-
contentWidthPx:
|
|
756
|
-
parentRowBlockHorizontalAlign: null
|
|
983
|
+
contentWidthPx: roundWidth(computeAtomContentWidth(childWidth, childLayout)),
|
|
984
|
+
parentRowBlockHorizontalAlign: null,
|
|
985
|
+
keyPrefix: `${ctx.keyPrefix}-${child.atomKey}-${index}`
|
|
757
986
|
};
|
|
758
|
-
|
|
759
|
-
|
|
987
|
+
return /* @__PURE__ */ React.createElement(
|
|
988
|
+
MjmlColumn,
|
|
989
|
+
{
|
|
990
|
+
key: `${child.atomKey}-${index}`,
|
|
991
|
+
width: `${columnWidth}px`,
|
|
992
|
+
verticalAlign: vAlign,
|
|
993
|
+
padding: gapBefore === 0 ? "0" : `0 0 0 ${gapBefore}px`
|
|
994
|
+
},
|
|
995
|
+
renderColumnContentAtom(child, childCtx)
|
|
996
|
+
);
|
|
997
|
+
})));
|
|
998
|
+
}
|
|
999
|
+
function renderRawAtom(atom, ctx) {
|
|
1000
|
+
switch (atom.type) {
|
|
1001
|
+
case "text":
|
|
1002
|
+
return renderRawTextAtom(atom, ctx);
|
|
1003
|
+
case "button":
|
|
1004
|
+
return renderRawButtonAtom(atom, ctx);
|
|
1005
|
+
case "image":
|
|
1006
|
+
return renderRawImageAtom(atom, ctx);
|
|
1007
|
+
case "divider":
|
|
1008
|
+
return renderRawDividerAtom(atom, ctx);
|
|
1009
|
+
case "rows":
|
|
1010
|
+
return renderRawRowsAtom(atom, ctx);
|
|
1011
|
+
case "columns":
|
|
1012
|
+
return renderRawColumnsAtom(atom, ctx);
|
|
1013
|
+
case "automation-upcoming-events":
|
|
1014
|
+
case "automation-triggered-event":
|
|
1015
|
+
case "form":
|
|
1016
|
+
case "question":
|
|
1017
|
+
case "input":
|
|
1018
|
+
case "option":
|
|
1019
|
+
case "contest":
|
|
1020
|
+
return "";
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
function renderRawTextAtom(atom, ctx) {
|
|
1024
|
+
const rootTextStyles = getRootTextStyles(atom.textType ?? "body", ctx.globalAtomStyles);
|
|
1025
|
+
const resolvedText = resolveTextStyles(atom.textStyles, rootTextStyles.textStyles);
|
|
1026
|
+
const resolvedLayout = resolveAtomLayoutStyles(atom, ctx.globalAtomStyles);
|
|
1027
|
+
const width = roundWidth(ctx.contentWidthPx);
|
|
1028
|
+
const html = `<div style="${cssStyle({ ...textStyleToCss(resolvedText), width: `${width}px`, wordBreak: "break-word" })}">${textHtmlWithLineBreaks(
|
|
1029
|
+
atom.text,
|
|
1030
|
+
ctx.underlineLinks,
|
|
1031
|
+
ctx.linkColor
|
|
1032
|
+
)}</div>`;
|
|
1033
|
+
return renderRawWrappedBox(html, width, resolvedLayout);
|
|
1034
|
+
}
|
|
1035
|
+
function renderRawButtonAtom(atom, ctx) {
|
|
1036
|
+
const resolvedText = resolveTextStyles(atom.textStyles, ctx.globalAtomStyles.buttonStyles.textStyles);
|
|
1037
|
+
const resolvedInnerPadding = resolvePadding(atom.innerPadding, ctx.globalAtomStyles.buttonStyles.innerPadding);
|
|
1038
|
+
const resolvedLayout = resolveAtomLayoutStyles(atom, ctx.globalAtomStyles);
|
|
1039
|
+
const width = roundWidth(ctx.contentWidthPx);
|
|
1040
|
+
const href = atom.href ? ensureUrlHasProtocolForPreview(atom.href) : "#";
|
|
1041
|
+
const backgroundColor = backgroundColorFromAtomBg(resolvedLayout.background);
|
|
1042
|
+
const borderRadius = cornerRadiusToCssValue(resolvedLayout.borderRadius);
|
|
1043
|
+
const isContentWidth = resolvedLayout.width.type === "content";
|
|
1044
|
+
const linkStyle = cssStyle({
|
|
1045
|
+
...textStyleToCss(resolvedText),
|
|
1046
|
+
backgroundColor,
|
|
1047
|
+
borderRadius,
|
|
1048
|
+
display: isContentWidth ? "inline-block" : "block",
|
|
1049
|
+
padding: paddingToCss(resolvedInnerPadding),
|
|
1050
|
+
textDecoration: "none",
|
|
1051
|
+
width: isContentWidth ? void 0 : `${width}px`,
|
|
1052
|
+
maxWidth: isContentWidth ? `${width}px` : void 0,
|
|
1053
|
+
boxSizing: "border-box"
|
|
1054
|
+
});
|
|
1055
|
+
const tableWidthAttribute = isContentWidth ? "" : ` width="${width}"`;
|
|
1056
|
+
const tableStyle = isContentWidth ? cssStyle({ borderCollapse: "separate" }) : rawTableStyle(width, { borderCollapse: "separate" });
|
|
1057
|
+
const cellStyle = isContentWidth ? cssStyle({ backgroundColor, borderRadius }) : rawCellStyle(width, { backgroundColor, borderRadius });
|
|
1058
|
+
const html = `<table class="display-link-block" role="presentation" border="0" cellpadding="0" cellspacing="0"${tableWidthAttribute} style="${tableStyle}"><tr><td align="center" bgcolor="${escapeHtml(backgroundColor ?? "")}" style="${cellStyle}"><a href="${escapeHtml(href)}" target="_blank" style="${linkStyle}">${atom.text}</a></td></tr></table>`;
|
|
1059
|
+
return renderRawWrappedBox(html, width, resolvedLayout);
|
|
1060
|
+
}
|
|
1061
|
+
function renderRawImageAtom(atom, ctx) {
|
|
1062
|
+
const resolvedLayout = resolveAtomLayoutStyles(atom, ctx.globalAtomStyles);
|
|
1063
|
+
const width = roundWidth(ctx.contentWidthPx);
|
|
1064
|
+
const height = scaledImageHeight(atom, width);
|
|
1065
|
+
const imageStyle = cssStyle({
|
|
1066
|
+
borderRadius: cornerRadiusToCssValue(resolvedLayout.borderRadius),
|
|
1067
|
+
display: "block",
|
|
1068
|
+
width: `${width}px`,
|
|
1069
|
+
height: height === void 0 ? void 0 : `${height}px`
|
|
1070
|
+
});
|
|
1071
|
+
const heightAttribute = height === void 0 ? "" : ` height="${height}"`;
|
|
1072
|
+
const image = `<img src="${escapeHtml(atom.src)}" alt="" width="${width}"${heightAttribute} style="${imageStyle}">`;
|
|
1073
|
+
const html = atom.href ? `<a href="${escapeHtml(ensureUrlHasProtocolForPreview(atom.href))}" target="_blank" style="${cssStyle({
|
|
1074
|
+
display: "inline-block",
|
|
1075
|
+
width: `${width}px`
|
|
1076
|
+
})}">${image}</a>` : image;
|
|
1077
|
+
return renderRawWrappedBox(html, width, resolvedLayout);
|
|
1078
|
+
}
|
|
1079
|
+
function renderRawDividerAtom(atom, ctx) {
|
|
1080
|
+
const resolvedDivider = resolveDividerStyles(atom.dividerStyles, ctx.globalAtomStyles.dividerStyles.dividerStyles);
|
|
1081
|
+
const resolvedLayout = resolveAtomLayoutStyles(atom, ctx.globalAtomStyles);
|
|
1082
|
+
const width = roundWidth(ctx.contentWidthPx);
|
|
1083
|
+
const dividerStyle = cssStyle({
|
|
1084
|
+
borderTop: `${resolvedDivider.width}px ${resolvedDivider.style} ${resolvedDivider.color}`,
|
|
1085
|
+
width: `${width}px`,
|
|
1086
|
+
margin: 0
|
|
1087
|
+
});
|
|
1088
|
+
return renderRawWrappedBox(`<div style="${dividerStyle}"></div>`, width, resolvedLayout);
|
|
1089
|
+
}
|
|
1090
|
+
function renderRawChildrenRows({
|
|
1091
|
+
children,
|
|
1092
|
+
resolvedLayout,
|
|
1093
|
+
resolvedRows,
|
|
1094
|
+
ctx
|
|
1095
|
+
}) {
|
|
1096
|
+
const outerWidth = boxOuterWidth(ctx.contentWidthPx, resolvedLayout);
|
|
1097
|
+
const innerWidth = roundWidth(computeAtomContentWidth(outerWidth, resolvedLayout));
|
|
1098
|
+
const rows = children.map((child, index) => {
|
|
1099
|
+
const childLayout = resolveAtomLayoutStyles(child, ctx.globalAtomStyles);
|
|
1100
|
+
const childOuterWidth = roundWidth(
|
|
1101
|
+
calculateRowChildWidth({
|
|
1102
|
+
contentWidth: innerWidth,
|
|
1103
|
+
child,
|
|
1104
|
+
globalAtomStyles: ctx.globalAtomStyles
|
|
1105
|
+
})
|
|
760
1106
|
);
|
|
1107
|
+
const childContentWidth = roundWidth(computeAtomContentWidth(childOuterWidth, childLayout));
|
|
1108
|
+
const childAlign = effectiveBlockHorizontalAlign({
|
|
1109
|
+
layoutHorizontalAlign: childLayout.horizontalAlign,
|
|
1110
|
+
parentRowBlockHorizontalAlign: resolvedRows.contentHorizontalAlign
|
|
1111
|
+
});
|
|
1112
|
+
const childHtml = renderRawAtom(child, {
|
|
1113
|
+
...ctx,
|
|
1114
|
+
contentWidthPx: childContentWidth,
|
|
1115
|
+
parentRowBlockHorizontalAlign: resolvedRows.contentHorizontalAlign,
|
|
1116
|
+
keyPrefix: `${ctx.keyPrefix}-${child.atomKey}-${index}`
|
|
1117
|
+
});
|
|
1118
|
+
return `<tr><td width="${innerWidth}" align="${childAlign}" style="${rawCellStyle(innerWidth, {
|
|
1119
|
+
paddingTop: index > 0 && resolvedRows.gap > 0 ? `${resolvedRows.gap}px` : void 0
|
|
1120
|
+
})}">${childHtml}</td></tr>`;
|
|
1121
|
+
}).join("");
|
|
1122
|
+
const table = `<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="${innerWidth}" style="${rawTableStyle(
|
|
1123
|
+
innerWidth
|
|
1124
|
+
)}">${rows}</table>`;
|
|
1125
|
+
return renderRawWrappedBox(table, outerWidth, resolvedLayout);
|
|
1126
|
+
}
|
|
1127
|
+
function renderRawRowsAtom(atom, ctx) {
|
|
1128
|
+
return renderRawChildrenRows({
|
|
1129
|
+
children: atom.children,
|
|
1130
|
+
resolvedLayout: resolveAtomLayoutStyles(atom, ctx.globalAtomStyles),
|
|
1131
|
+
resolvedRows: resolveRowsStyles(atom.rowsStyles, ctx.globalAtomStyles.rowsStyles.rowsStyles),
|
|
1132
|
+
ctx
|
|
761
1133
|
});
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
const
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
const
|
|
1134
|
+
}
|
|
1135
|
+
function renderRawColumnsAtom(atom, ctx) {
|
|
1136
|
+
const resolvedLayout = resolveAtomLayoutStyles(atom, ctx.globalAtomStyles);
|
|
1137
|
+
const resolvedCols = resolveColumnsStyles(atom.columnsStyles, ctx.globalAtomStyles.columnsStyles.columnsStyles);
|
|
1138
|
+
const gapPx = Math.max(0, Math.round(resolvedCols.gap));
|
|
1139
|
+
const verticalGapPx = Math.max(0, Math.round(resolvedCols.verticalGap));
|
|
1140
|
+
const outerWidth = boxOuterWidth(ctx.contentWidthPx, resolvedLayout);
|
|
1141
|
+
const innerWidth = roundWidth(computeAtomContentWidth(outerWidth, resolvedLayout));
|
|
1142
|
+
const columnWidths = calculateColumnChildWidthAllocation({
|
|
1143
|
+
contentWidth: innerWidth,
|
|
1144
|
+
children: atom.children,
|
|
1145
|
+
gap: gapPx,
|
|
1146
|
+
globalAtomStyles: ctx.globalAtomStyles
|
|
1147
|
+
}).widthsPx.map(roundWidth);
|
|
1148
|
+
const maxRows = Math.max(1, ...atom.children.map((child) => child.type === "rows" ? child.children.length : 1));
|
|
1149
|
+
const vAlign = resolvedCols.contentVerticalAlign === "middle" ? "middle" : resolvedCols.contentVerticalAlign;
|
|
1150
|
+
const rows = Array.from({ length: maxRows }).map((_, rowIndex) => {
|
|
1151
|
+
const cells = atom.children.map((child, columnIndex) => {
|
|
1152
|
+
const columnWidth = columnWidths[columnIndex] ?? 0;
|
|
1153
|
+
const rowChild = child.type === "rows" ? child.children[rowIndex] : rowIndex === 0 ? child : null;
|
|
1154
|
+
const rowStyles = child.type === "rows" ? resolveRowsStyles(child.rowsStyles, ctx.globalAtomStyles.rowsStyles.rowsStyles) : ctx.globalAtomStyles.rowsStyles.rowsStyles;
|
|
1155
|
+
const cellHtml = rowChild ? (() => {
|
|
1156
|
+
const rowChildLayout = resolveAtomLayoutStyles(rowChild, ctx.globalAtomStyles);
|
|
1157
|
+
const rowChildOuterWidth = roundWidth(
|
|
1158
|
+
calculateRowChildWidth({
|
|
1159
|
+
contentWidth: columnWidth,
|
|
1160
|
+
child: rowChild,
|
|
1161
|
+
globalAtomStyles: ctx.globalAtomStyles
|
|
1162
|
+
})
|
|
1163
|
+
);
|
|
1164
|
+
const rowChildContentWidth = roundWidth(
|
|
1165
|
+
computeAtomContentWidth(rowChildOuterWidth, rowChildLayout)
|
|
1166
|
+
);
|
|
1167
|
+
return renderRawAtom(rowChild, {
|
|
1168
|
+
...ctx,
|
|
1169
|
+
contentWidthPx: rowChildContentWidth,
|
|
1170
|
+
parentRowBlockHorizontalAlign: rowStyles.contentHorizontalAlign,
|
|
1171
|
+
keyPrefix: `${ctx.keyPrefix}-${rowChild.atomKey}-${rowIndex}-${columnIndex}`
|
|
1172
|
+
});
|
|
1173
|
+
})() : " ";
|
|
1174
|
+
const gapCell = columnIndex > 0 && gapPx > 0 ? `<td width="${gapPx}" valign="${vAlign}" style="${rawCellStyle(gapPx, {
|
|
1175
|
+
fontSize: 0,
|
|
1176
|
+
lineHeight: 0,
|
|
1177
|
+
verticalAlign: vAlign
|
|
1178
|
+
})}"> </td>` : "";
|
|
1179
|
+
const cellVerticalAlign = rowChild?.type === "image" ? "middle" : vAlign;
|
|
1180
|
+
return `${gapCell}<td width="${columnWidth}" valign="${cellVerticalAlign}" style="${rawCellStyle(columnWidth, {
|
|
1181
|
+
paddingTop: rowIndex > 0 && verticalGapPx > 0 ? `${verticalGapPx}px` : void 0,
|
|
1182
|
+
verticalAlign: cellVerticalAlign
|
|
1183
|
+
})}">${cellHtml}</td>`;
|
|
1184
|
+
}).join("");
|
|
1185
|
+
return `<tr>${cells}</tr>`;
|
|
1186
|
+
}).join("");
|
|
1187
|
+
const totalWidth = columnWidths.reduce((sum, width) => sum + width, 0) + gapPx * Math.max(0, atom.children.length - 1);
|
|
1188
|
+
const table = `<div class="table-min-width-100"><table role="presentation" border="0" cellpadding="0" cellspacing="0" width="${totalWidth}" align="${alignAttr(
|
|
1189
|
+
resolvedCols.contentHorizontalAlign
|
|
1190
|
+
)}" style="${rawTableStyle(totalWidth)}">${rows}</table></div>`;
|
|
1191
|
+
return renderRawWrappedBox(table, outerWidth, resolvedLayout);
|
|
1192
|
+
}
|
|
1193
|
+
function topUnsubscribeHtml(unsubscribeLink) {
|
|
1194
|
+
return `<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#fff;background-color:#fff;width:100%"><tr><td><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%"><tr><td class="top-unsubscribe" style="font-size:0;padding-top:16px;padding-bottom:16px;text-align:left"><div style="font-family:'Google Sans',Roboto,RobotoDraft,Helvetica,Arial,sans-serif;font-size:14px;font-weight:500;line-height:1;text-align:left;color:#3771e0"><a style="color:#3771e0;text-decoration:none" href="${escapeHtml(
|
|
1195
|
+
unsubscribeLink
|
|
1196
|
+
)}" target="_blank" rel="noopener noreferrer">Unsubscribe</a></div></td></tr></table></td></tr></table>`;
|
|
1197
|
+
}
|
|
1198
|
+
function preheaderHtml(text) {
|
|
1199
|
+
return `<div class="preview-text" style="display:none!important;overflow:hidden;max-width:0;max-height:0;font-size:1px;line-height:1px;opacity:0">${escapeHtml(
|
|
1200
|
+
text
|
|
1201
|
+
)}${ZWNJ_PADDING.repeat(10)}</div>`;
|
|
1202
|
+
}
|
|
1203
|
+
const AtomDocumentContentsEmail = ({ document: doc, options }) => {
|
|
1204
|
+
const rootLayout = resolveAtomLayoutStyles(doc.rootRow, doc.globalAtomStyles);
|
|
1205
|
+
const canvasBackground = doc.canvasBackground;
|
|
1206
|
+
const bodyBackground = isImageBackground(canvasBackground) || canvasBackground === "none" && isImageBackground(rootLayout.background) ? canvasBackground === "none" ? rootLayout.background : canvasBackground : rootLayout.background;
|
|
1207
|
+
const shouldSuppressRootRowsBackground = canvasBackground === "none" && isImageBackground(rootLayout.background);
|
|
1208
|
+
const bodyBackgroundColor = backgroundColorFromAtomBg(bodyBackground);
|
|
1209
|
+
const bodyBackgroundCss = isImageBackground(bodyBackground) ? `
|
|
1210
|
+
.atoms-body > div {
|
|
1211
|
+
background-image: url("${bodyBackground.imageUrl}");
|
|
1212
|
+
background-size: ${bodyBackground.imageSize};
|
|
1213
|
+
background-repeat: ${bodyBackground.imageRepeat ? "repeat" : "no-repeat"};
|
|
1214
|
+
background-position: center;
|
|
1215
|
+
}
|
|
1216
|
+
` : "";
|
|
778
1217
|
const ctx = {
|
|
779
1218
|
globalAtomStyles: doc.globalAtomStyles,
|
|
1219
|
+
linkColor: doc.linkColor,
|
|
1220
|
+
underlineLinks: doc.underlineLinks,
|
|
780
1221
|
contentWidthPx: doc.maxWidth,
|
|
781
|
-
parentRowBlockHorizontalAlign: null
|
|
1222
|
+
parentRowBlockHorizontalAlign: null,
|
|
1223
|
+
keyPrefix: "root",
|
|
1224
|
+
suppressRowsBackgroundAtomKey: shouldSuppressRootRowsBackground ? doc.rootRow.atomKey : void 0
|
|
782
1225
|
};
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
1226
|
+
return /* @__PURE__ */ React.createElement(Mjml, null, /* @__PURE__ */ React.createElement(MjmlHead, null, /* @__PURE__ */ React.createElement(MjmlStyle, null, `
|
|
1227
|
+
${COMMON_CSS}
|
|
1228
|
+
@media screen and (max-width:600px) {
|
|
1229
|
+
.top-unsubscribe {
|
|
1230
|
+
padding-left: 22px;
|
|
1231
|
+
padding-right: 22px;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
.atoms-text div {
|
|
1235
|
+
white-space: normal;
|
|
1236
|
+
word-break: break-word;
|
|
1237
|
+
}
|
|
1238
|
+
${bodyBackgroundCss}
|
|
1239
|
+
`)), /* @__PURE__ */ React.createElement(MjmlBody, { width: doc.maxWidth, backgroundColor: bodyBackgroundColor, cssClass: "atoms-body" }, options?.emailPreviewText ? /* @__PURE__ */ React.createElement(RawHtml, { html: preheaderHtml(options.emailPreviewText) }) : null, options?.topUnsubscribeLink ? /* @__PURE__ */ React.createElement(RawHtml, { html: topUnsubscribeHtml(options.topUnsubscribeLink) }) : null, options?.automationPreviewHeaderHtml ? /* @__PURE__ */ React.createElement(RawHtml, { html: options.automationPreviewHeaderHtml }) : null, renderBodyAtom(doc.rootRow, ctx), options?.cymbalBrandingUrl ? /* @__PURE__ */ React.createElement(MjmlSection, { padding: "0" }, /* @__PURE__ */ React.createElement(MjmlColumn, null, /* @__PURE__ */ React.createElement(MjmlText, { color: "#999999", fontSize: "12px", align: "center", padding: "0" }, "Powered by", " ", /* @__PURE__ */ React.createElement("a", { href: options.cymbalBrandingUrl, target: "_blank", style: { color: "#999999" } }, "Cymbal")))) : null, options?.trackingPixelUrl ? /* @__PURE__ */ React.createElement(
|
|
1240
|
+
RawHtml,
|
|
794
1241
|
{
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
height: "
|
|
798
|
-
style: { display: "none", height: 1, width: 1 },
|
|
799
|
-
alt: ""
|
|
1242
|
+
html: `<img src="${escapeHtml(
|
|
1243
|
+
options.trackingPixelUrl
|
|
1244
|
+
)}" width="1" height="1" style="display:none;height:1px;width:1px" alt="">`
|
|
800
1245
|
}
|
|
801
1246
|
) : null));
|
|
802
1247
|
};
|
|
803
1248
|
async function renderAtomDocumentToHtml(doc, options) {
|
|
804
|
-
|
|
1249
|
+
const mjml = renderToMjml(/* @__PURE__ */ React.createElement(AtomDocumentContentsEmail, { document: doc, options }));
|
|
1250
|
+
const result = await mjml2html(mjml, { keepComments: false, beautify: false, minify: true });
|
|
1251
|
+
if (typeof result === "string") {
|
|
1252
|
+
return result;
|
|
1253
|
+
}
|
|
1254
|
+
const errors = result.errors ?? [];
|
|
1255
|
+
if (errors.length > 0) {
|
|
1256
|
+
throw new Error(`MJML render failed: ${errors.map((error) => error.formattedMessage).join("; ")}`);
|
|
1257
|
+
}
|
|
1258
|
+
return result.html;
|
|
805
1259
|
}
|
|
806
1260
|
|
|
807
1261
|
export { renderAtomDocumentToHtml };
|