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