@endora-commerce/email-components 0.100.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/LICENSE +21 -0
- package/README.md +45 -0
- package/dist/components/email-branding-preview-context.d.ts +7 -0
- package/dist/components/email-branding-preview-context.d.ts.map +1 -0
- package/dist/components/email-branding-preview-context.js +8 -0
- package/dist/components/email-branding-preview-context.js.map +1 -0
- package/dist/components/email-embeds-context.d.ts +11 -0
- package/dist/components/email-embeds-context.d.ts.map +1 -0
- package/dist/components/email-embeds-context.js +15 -0
- package/dist/components/email-embeds-context.js.map +1 -0
- package/dist/config.d.ts +333 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +900 -0
- package/dist/config.js.map +1 -0
- package/dist/defaults/default-footer.d.ts +13 -0
- package/dist/defaults/default-footer.d.ts.map +1 -0
- package/dist/defaults/default-footer.js +42 -0
- package/dist/defaults/default-footer.js.map +1 -0
- package/dist/defaults/default-header.d.ts +5 -0
- package/dist/defaults/default-header.d.ts.map +1 -0
- package/dist/defaults/default-header.js +34 -0
- package/dist/defaults/default-header.js.map +1 -0
- package/dist/defaults/simple-email-body.d.ts +20 -0
- package/dist/defaults/simple-email-body.d.ts.map +1 -0
- package/dist/defaults/simple-email-body.js +41 -0
- package/dist/defaults/simple-email-body.js.map +1 -0
- package/dist/directives/directive-engine.d.ts +7 -0
- package/dist/directives/directive-engine.d.ts.map +1 -0
- package/dist/directives/directive-engine.js +144 -0
- package/dist/directives/directive-engine.js.map +1 -0
- package/dist/editor/email-row-layout-presets.d.ts +33 -0
- package/dist/editor/email-row-layout-presets.d.ts.map +1 -0
- package/dist/editor/email-row-layout-presets.js +145 -0
- package/dist/editor/email-row-layout-presets.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/render/escape-html.d.ts +4 -0
- package/dist/render/escape-html.d.ts.map +1 -0
- package/dist/render/escape-html.js +21 -0
- package/dist/render/escape-html.js.map +1 -0
- package/dist/render/order-labels.d.ts +23 -0
- package/dist/render/order-labels.d.ts.map +1 -0
- package/dist/render/order-labels.js +46 -0
- package/dist/render/order-labels.js.map +1 -0
- package/dist/render/render-email-html.d.ts +18 -0
- package/dist/render/render-email-html.d.ts.map +1 -0
- package/dist/render/render-email-html.js +659 -0
- package/dist/render/render-email-html.js.map +1 -0
- package/dist/render/render-email-text.d.ts +11 -0
- package/dist/render/render-email-text.d.ts.map +1 -0
- package/dist/render/render-email-text.js +167 -0
- package/dist/render/render-email-text.js.map +1 -0
- package/dist/render/sample-variables.d.ts +15 -0
- package/dist/render/sample-variables.d.ts.map +1 -0
- package/dist/render/sample-variables.js +43 -0
- package/dist/render/sample-variables.js.map +1 -0
- package/dist/render/sanitize-email-html.d.ts +9 -0
- package/dist/render/sanitize-email-html.d.ts.map +1 -0
- package/dist/render/sanitize-email-html.js +122 -0
- package/dist/render/sanitize-email-html.js.map +1 -0
- package/dist/render/social-icon-svg.d.ts +6 -0
- package/dist/render/social-icon-svg.d.ts.map +1 -0
- package/dist/render/social-icon-svg.js +41 -0
- package/dist/render/social-icon-svg.js.map +1 -0
- package/dist/schema/component-types.d.ts +269 -0
- package/dist/schema/component-types.d.ts.map +1 -0
- package/dist/schema/component-types.js +105 -0
- package/dist/schema/component-types.js.map +1 -0
- package/dist/schema/envelope.d.ts +21 -0
- package/dist/schema/envelope.d.ts.map +1 -0
- package/dist/schema/envelope.js +8 -0
- package/dist/schema/envelope.js.map +1 -0
- package/dist/tree/walk-embeds.d.ts +7 -0
- package/dist/tree/walk-embeds.d.ts.map +1 -0
- package/dist/tree/walk-embeds.js +115 -0
- package/dist/tree/walk-embeds.js.map +1 -0
- package/package.json +71 -0
- package/tailwind.css +14 -0
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
// Pure email-HTML renderer (feature 047 + email builder expansion).
|
|
2
|
+
// Table-based, inline-styled HTML. Directive markers left intact for the engine.
|
|
3
|
+
import { EMAIL_ORDER_BLOCK_MARGIN_DEFAULT, EMAIL_ORDER_LABELED_FIELDS, } from '../schema/component-types.js';
|
|
4
|
+
import { escapeAttr, escapeHtml } from './escape-html.js';
|
|
5
|
+
import { orderSummaryColumnLabels } from './order-labels.js';
|
|
6
|
+
import { emailHtmlToPlainText, sanitizeEmailHtml } from './sanitize-email-html.js';
|
|
7
|
+
import { EMAIL_SOCIAL_BRAND_COLORS, EMAIL_SOCIAL_LABELS, socialIconDataUri, } from './social-icon-svg.js';
|
|
8
|
+
const DEFAULT_ACCENT = '#1f2937';
|
|
9
|
+
const DEFAULT_MAX_DEPTH = 3;
|
|
10
|
+
function bool(props, key, fallback) {
|
|
11
|
+
const v = props?.[key];
|
|
12
|
+
if (typeof v === 'boolean')
|
|
13
|
+
return v;
|
|
14
|
+
// Puck radio fields sometimes persist Yes/No as strings.
|
|
15
|
+
if (v === 'true')
|
|
16
|
+
return true;
|
|
17
|
+
if (v === 'false')
|
|
18
|
+
return false;
|
|
19
|
+
return fallback;
|
|
20
|
+
}
|
|
21
|
+
function asNodes(list) {
|
|
22
|
+
if (!Array.isArray(list))
|
|
23
|
+
return [];
|
|
24
|
+
const out = [];
|
|
25
|
+
for (const raw of list) {
|
|
26
|
+
if (raw && typeof raw === 'object' && typeof raw.type === 'string') {
|
|
27
|
+
out.push(raw);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
function treeContent(tree) {
|
|
33
|
+
return asNodes(tree?.content);
|
|
34
|
+
}
|
|
35
|
+
function str(props, key, fallback = '') {
|
|
36
|
+
const v = props?.[key];
|
|
37
|
+
return typeof v === 'string' ? v : fallback;
|
|
38
|
+
}
|
|
39
|
+
function num(props, key, fallback) {
|
|
40
|
+
const v = props?.[key];
|
|
41
|
+
return typeof v === 'number' && Number.isFinite(v) ? v : fallback;
|
|
42
|
+
}
|
|
43
|
+
function align(props, fallback = 'left') {
|
|
44
|
+
const v = props?.['align'];
|
|
45
|
+
return v === 'left' || v === 'center' || v === 'right' ? v : fallback;
|
|
46
|
+
}
|
|
47
|
+
function multiline(text) {
|
|
48
|
+
return escapeHtml(text).replace(/\r?\n/g, '<br />');
|
|
49
|
+
}
|
|
50
|
+
/** Nested column content uses tighter horizontal padding than the outer mail shell. */
|
|
51
|
+
const cellPaddingStack = ['0 24px'];
|
|
52
|
+
function withCellPadding(padding, fn) {
|
|
53
|
+
cellPaddingStack.push(padding);
|
|
54
|
+
try {
|
|
55
|
+
return fn();
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
cellPaddingStack.pop();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function row(inner, padding, extraTdStyle = '') {
|
|
62
|
+
const pad = padding ?? cellPaddingStack[cellPaddingStack.length - 1] ?? '0 24px';
|
|
63
|
+
return `<tr><td style="padding:${pad};font-family:Arial,Helvetica,sans-serif;${extraTdStyle}">${inner}</td></tr>`;
|
|
64
|
+
}
|
|
65
|
+
function orderBlockPadding(marginTop, marginBottom) {
|
|
66
|
+
const top = Math.max(0, marginTop ?? EMAIL_ORDER_BLOCK_MARGIN_DEFAULT);
|
|
67
|
+
const bottom = Math.max(0, marginBottom ?? EMAIL_ORDER_BLOCK_MARGIN_DEFAULT);
|
|
68
|
+
return `${top}px 24px ${bottom}px 24px`;
|
|
69
|
+
}
|
|
70
|
+
/** Resolve Puck slot children from zones (`id:slot`) or inline props.content. */
|
|
71
|
+
function slotChildren(node, ctx, slotName = 'content') {
|
|
72
|
+
const id = node.props?.['id'];
|
|
73
|
+
if (typeof id === 'string' && ctx.tree.zones) {
|
|
74
|
+
const zone = ctx.tree.zones[`${id}:${slotName}`];
|
|
75
|
+
if (Array.isArray(zone) && zone.length > 0)
|
|
76
|
+
return asNodes(zone);
|
|
77
|
+
}
|
|
78
|
+
const inline = node.props?.[slotName];
|
|
79
|
+
if (Array.isArray(inline))
|
|
80
|
+
return asNodes(inline);
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
function renderHeading(p) {
|
|
84
|
+
const size = p.level === 'h1' ? '28px' : p.level === 'h2' ? '22px' : '18px';
|
|
85
|
+
return row(`<${p.level} style="margin:0;padding:12px 0;font-size:${size};line-height:1.3;color:#15202b;text-align:${p.align};font-weight:bold;">${multiline(p.text)}</${p.level}>`);
|
|
86
|
+
}
|
|
87
|
+
function renderText(p) {
|
|
88
|
+
return row(`<p style="margin:0;padding:8px 0;font-size:15px;line-height:1.5;color:#1f2937;text-align:${p.align};">${multiline(p.text)}</p>`);
|
|
89
|
+
}
|
|
90
|
+
function renderButton(p, accent) {
|
|
91
|
+
const bg = p.backgroundColor || accent;
|
|
92
|
+
const fg = p.textColor || '#ffffff';
|
|
93
|
+
const href = p.href || '#';
|
|
94
|
+
const btn = `<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:8px 0;">` +
|
|
95
|
+
`<tr><td style="background-color:${escapeAttr(bg)};border-radius:6px;">` +
|
|
96
|
+
`<a href="${escapeAttr(href)}" target="_blank" rel="noreferrer" ` +
|
|
97
|
+
`style="display:inline-block;padding:12px 22px;font-family:Arial,Helvetica,sans-serif;font-size:14px;` +
|
|
98
|
+
`font-weight:bold;color:${escapeAttr(fg)};text-decoration:none;">${escapeHtml(p.label)}</a>` +
|
|
99
|
+
`</td></tr></table>`;
|
|
100
|
+
return row(`<div style="text-align:${p.align};">${btn}</div>`);
|
|
101
|
+
}
|
|
102
|
+
function renderImage(p) {
|
|
103
|
+
if (!p.src)
|
|
104
|
+
return '';
|
|
105
|
+
const margin = p.align === 'center' ? '0 auto' : p.align === 'right' ? '0 0 0 auto' : '0';
|
|
106
|
+
const img = `<img src="${escapeAttr(p.src)}" alt="${escapeAttr(p.alt)}" width="${p.width}" ` +
|
|
107
|
+
`style="display:block;border:0;outline:none;max-width:100%;height:auto;width:${p.width}px;margin:${margin};" />`;
|
|
108
|
+
const wrapped = p.href
|
|
109
|
+
? `<a href="${escapeAttr(p.href)}" target="_blank" rel="noreferrer" style="display:inline-block;margin:${margin};">${img}</a>`
|
|
110
|
+
: img;
|
|
111
|
+
// Table cell align is more reliable than text-align + display:block img.
|
|
112
|
+
return `<tr><td align="${p.align}" style="padding:0 24px;font-family:Arial,Helvetica,sans-serif;">${wrapped}</td></tr>`;
|
|
113
|
+
}
|
|
114
|
+
function renderLogo(p) {
|
|
115
|
+
return renderImage({
|
|
116
|
+
src: p.src,
|
|
117
|
+
alt: p.alt || 'Logo',
|
|
118
|
+
href: p.href,
|
|
119
|
+
width: p.width || 160,
|
|
120
|
+
align: p.align || 'center',
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function renderSpacer(p) {
|
|
124
|
+
const h = Math.max(0, p.height);
|
|
125
|
+
return `<tr><td style="height:${h}px;line-height:${h}px;font-size:0;"> </td></tr>`;
|
|
126
|
+
}
|
|
127
|
+
function renderDivider(p) {
|
|
128
|
+
const thickness = Math.min(8, Math.max(1, p.thickness || 1));
|
|
129
|
+
const color = p.color || '#e5e7eb';
|
|
130
|
+
return row(`<hr style="border:0;border-top:${thickness}px solid ${escapeAttr(color)};margin:8px 0;" />`);
|
|
131
|
+
}
|
|
132
|
+
function renderSection(p, innerRows) {
|
|
133
|
+
const bg = p.backgroundColor || 'transparent';
|
|
134
|
+
const py = Math.max(0, p.paddingY ?? 16);
|
|
135
|
+
const px = Math.max(0, p.paddingX ?? 24);
|
|
136
|
+
return (`<tr><td style="padding:0;background-color:${escapeAttr(bg)};">` +
|
|
137
|
+
`<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">` +
|
|
138
|
+
`<tr><td style="padding:${py}px ${px}px;font-family:Arial,Helvetica,sans-serif;">` +
|
|
139
|
+
`<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">${innerRows}</table>` +
|
|
140
|
+
`</td></tr></table></td></tr>`);
|
|
141
|
+
}
|
|
142
|
+
function wrapAsInnerTable(innerRows) {
|
|
143
|
+
if (!innerRows) {
|
|
144
|
+
return `<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="font-size:0;line-height:0;"> </td></tr></table>`;
|
|
145
|
+
}
|
|
146
|
+
return `<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">${innerRows}</table>`;
|
|
147
|
+
}
|
|
148
|
+
function renderRow(p, columns) {
|
|
149
|
+
if (columns.length === 0)
|
|
150
|
+
return '';
|
|
151
|
+
const gap = Math.max(0, p.gap ?? 16);
|
|
152
|
+
const valign = p.verticalAlign === 'middle' || p.verticalAlign === 'bottom' ? p.verticalAlign : 'top';
|
|
153
|
+
const n = columns.length;
|
|
154
|
+
const spanTotal = columns.reduce((sum, col) => sum + Math.max(1, col.span || 1), 0) || n;
|
|
155
|
+
const halfGap = Math.floor(gap / 2);
|
|
156
|
+
const tds = columns
|
|
157
|
+
.map((col, i) => {
|
|
158
|
+
const span = Math.max(1, col.span || 1);
|
|
159
|
+
const widthPct = Math.max(1, Math.floor((100 * span) / spanTotal));
|
|
160
|
+
const padLeft = i === 0 ? 0 : halfGap;
|
|
161
|
+
const padRight = i === n - 1 ? 0 : gap - halfGap;
|
|
162
|
+
return (`<td width="${widthPct}%" valign="${valign}" ` +
|
|
163
|
+
`style="width:${widthPct}%;vertical-align:${valign};padding:0 ${padRight}px 0 ${padLeft}px;font-family:Arial,Helvetica,sans-serif;">` +
|
|
164
|
+
wrapAsInnerTable(col.innerRows) +
|
|
165
|
+
`</td>`);
|
|
166
|
+
})
|
|
167
|
+
.join('');
|
|
168
|
+
return row(`<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;table-layout:fixed;">` +
|
|
169
|
+
`<tr>${tds}</tr></table>`);
|
|
170
|
+
}
|
|
171
|
+
function renderRichText(p) {
|
|
172
|
+
const safe = sanitizeEmailHtml(p.html || '');
|
|
173
|
+
if (!safe)
|
|
174
|
+
return '';
|
|
175
|
+
return row(`<div style="text-align:${p.align};font-size:15px;line-height:1.5;color:#1f2937;">${safe}</div>`);
|
|
176
|
+
}
|
|
177
|
+
function renderProductCard(p, accent) {
|
|
178
|
+
const showImage = p.showImage;
|
|
179
|
+
const showPrice = p.showPrice;
|
|
180
|
+
const showSku = p.showSku;
|
|
181
|
+
let img = '';
|
|
182
|
+
if (showImage) {
|
|
183
|
+
if (p.imageSrc) {
|
|
184
|
+
img = `<img src="${escapeAttr(p.imageSrc)}" alt="" width="120" style="display:block;border:0;max-width:120px;height:auto;" />`;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
img =
|
|
188
|
+
`<div style="width:120px;height:120px;background-color:#f3f4f6;border:1px solid #e5e7eb;` +
|
|
189
|
+
`font-family:Arial,Helvetica,sans-serif;font-size:11px;color:#9ca3af;text-align:center;line-height:120px;">No image</div>`;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const cta = p.href && p.ctaLabel
|
|
193
|
+
? `<a href="${escapeAttr(p.href)}" target="_blank" rel="noreferrer" ` +
|
|
194
|
+
`style="display:inline-block;margin-top:8px;padding:10px 16px;background-color:${escapeAttr(accent)};` +
|
|
195
|
+
`color:#ffffff;text-decoration:none;font-weight:bold;border-radius:6px;font-size:13px;">${escapeHtml(p.ctaLabel)}</a>`
|
|
196
|
+
: '';
|
|
197
|
+
const body = `<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0"><tr>` +
|
|
198
|
+
(img ? `<td valign="top" style="width:128px;padding-right:16px;">${img}</td>` : '') +
|
|
199
|
+
`<td valign="top" style="font-family:Arial,Helvetica,sans-serif;font-size:15px;color:#1f2937;text-align:${p.align || 'left'};">` +
|
|
200
|
+
`<div style="font-weight:bold;margin-bottom:4px;">${multiline(p.title)}</div>` +
|
|
201
|
+
(showSku && p.sku ? `<div style="margin-bottom:4px;font-size:13px;color:#6b7280;">${multiline(p.sku)}</div>` : '') +
|
|
202
|
+
(showPrice && p.price ? `<div style="margin-bottom:4px;">${multiline(p.price)}</div>` : '') +
|
|
203
|
+
cta +
|
|
204
|
+
`</td></tr></table>`;
|
|
205
|
+
return row(body, '12px 24px');
|
|
206
|
+
}
|
|
207
|
+
function renderProductGrid(p, accent) {
|
|
208
|
+
const items = Array.isArray(p.items) ? p.items : [];
|
|
209
|
+
if (items.length === 0)
|
|
210
|
+
return '';
|
|
211
|
+
const columns = Math.min(3, Math.max(1, p.columns || 2));
|
|
212
|
+
const gap = Math.max(0, p.gap ?? 16);
|
|
213
|
+
const halfGap = Math.floor(gap / 2);
|
|
214
|
+
const showImage = p.showImage;
|
|
215
|
+
const showPrice = p.showPrice;
|
|
216
|
+
const showSku = p.showSku;
|
|
217
|
+
const ctaLabel = p.ctaLabel || 'View';
|
|
218
|
+
const widthPct = Math.floor(100 / columns);
|
|
219
|
+
const rowsHtml = [];
|
|
220
|
+
for (let i = 0; i < items.length; i += columns) {
|
|
221
|
+
const slice = items.slice(i, i + columns);
|
|
222
|
+
while (slice.length < columns)
|
|
223
|
+
slice.push(null);
|
|
224
|
+
const tds = slice
|
|
225
|
+
.map((item, ci) => {
|
|
226
|
+
const padLeft = ci === 0 ? 0 : halfGap;
|
|
227
|
+
const padRight = ci === columns - 1 ? 0 : gap - halfGap;
|
|
228
|
+
if (!item) {
|
|
229
|
+
return `<td width="${widthPct}%" style="width:${widthPct}%;padding:0 ${padRight}px 12px ${padLeft}px;"></td>`;
|
|
230
|
+
}
|
|
231
|
+
const img = showImage && item.imageSrc
|
|
232
|
+
? `<img src="${escapeAttr(item.imageSrc)}" alt="" width="100%" style="display:block;border:0;max-width:100%;height:auto;margin-bottom:8px;" />`
|
|
233
|
+
: showImage
|
|
234
|
+
? `<div style="width:100%;height:100px;background:#f3f4f6;margin-bottom:8px;"></div>`
|
|
235
|
+
: '';
|
|
236
|
+
const cta = item.href
|
|
237
|
+
? `<a href="${escapeAttr(item.href)}" target="_blank" rel="noreferrer" style="display:inline-block;margin-top:8px;padding:8px 12px;background-color:${escapeAttr(accent)};color:#ffffff;text-decoration:none;font-size:12px;font-weight:bold;border-radius:4px;">${escapeHtml(ctaLabel)}</a>`
|
|
238
|
+
: '';
|
|
239
|
+
return (`<td width="${widthPct}%" valign="top" style="width:${widthPct}%;padding:0 ${padRight}px 12px ${padLeft}px;font-family:Arial,Helvetica,sans-serif;font-size:14px;color:#1f2937;">` +
|
|
240
|
+
img +
|
|
241
|
+
`<div style="font-weight:bold;margin-bottom:4px;">${multiline(item.title)}</div>` +
|
|
242
|
+
(showSku && item.sku ? `<div style="font-size:12px;color:#6b7280;margin-bottom:4px;">${multiline(item.sku)}</div>` : '') +
|
|
243
|
+
(showPrice && item.price ? `<div style="margin-bottom:4px;">${multiline(item.price)}</div>` : '') +
|
|
244
|
+
cta +
|
|
245
|
+
`</td>`);
|
|
246
|
+
})
|
|
247
|
+
.join('');
|
|
248
|
+
rowsHtml.push(`<tr>${tds}</tr>`);
|
|
249
|
+
}
|
|
250
|
+
return row(`<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;table-layout:fixed;">${rowsHtml.join('')}</table>`);
|
|
251
|
+
}
|
|
252
|
+
function renderCategoryGrid(p) {
|
|
253
|
+
const items = Array.isArray(p.items) ? p.items : [];
|
|
254
|
+
if (items.length === 0)
|
|
255
|
+
return '';
|
|
256
|
+
const columns = Math.min(3, Math.max(1, p.columns || 2));
|
|
257
|
+
const gap = Math.max(0, p.gap ?? 16);
|
|
258
|
+
const halfGap = Math.floor(gap / 2);
|
|
259
|
+
const showImage = p.showImage;
|
|
260
|
+
const widthPct = Math.floor(100 / columns);
|
|
261
|
+
const rowsHtml = [];
|
|
262
|
+
for (let i = 0; i < items.length; i += columns) {
|
|
263
|
+
const slice = items.slice(i, i + columns);
|
|
264
|
+
while (slice.length < columns)
|
|
265
|
+
slice.push(null);
|
|
266
|
+
const tds = slice
|
|
267
|
+
.map((item, ci) => {
|
|
268
|
+
const padLeft = ci === 0 ? 0 : halfGap;
|
|
269
|
+
const padRight = ci === columns - 1 ? 0 : gap - halfGap;
|
|
270
|
+
if (!item) {
|
|
271
|
+
return `<td width="${widthPct}%" style="width:${widthPct}%;padding:0 ${padRight}px 12px ${padLeft}px;"></td>`;
|
|
272
|
+
}
|
|
273
|
+
const img = showImage && item.imageSrc
|
|
274
|
+
? `<img src="${escapeAttr(item.imageSrc)}" alt="" width="100%" style="display:block;border:0;max-width:100%;height:auto;margin-bottom:8px;" />`
|
|
275
|
+
: showImage
|
|
276
|
+
? `<div style="width:100%;height:80px;background:#f3f4f6;margin-bottom:8px;"></div>`
|
|
277
|
+
: '';
|
|
278
|
+
const title = item.href
|
|
279
|
+
? `<a href="${escapeAttr(item.href)}" target="_blank" rel="noreferrer" style="color:#1f2937;text-decoration:none;font-weight:bold;">${multiline(item.title)}</a>`
|
|
280
|
+
: `<div style="font-weight:bold;">${multiline(item.title)}</div>`;
|
|
281
|
+
return (`<td width="${widthPct}%" valign="top" style="width:${widthPct}%;padding:0 ${padRight}px 12px ${padLeft}px;font-family:Arial,Helvetica,sans-serif;font-size:14px;color:#1f2937;">` +
|
|
282
|
+
img +
|
|
283
|
+
title +
|
|
284
|
+
`</td>`);
|
|
285
|
+
})
|
|
286
|
+
.join('');
|
|
287
|
+
rowsHtml.push(`<tr>${tds}</tr>`);
|
|
288
|
+
}
|
|
289
|
+
return row(`<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;table-layout:fixed;">${rowsHtml.join('')}</table>`);
|
|
290
|
+
}
|
|
291
|
+
function renderOrderSummaryLegacy(title, body) {
|
|
292
|
+
return row(`<div style="font-weight:bold;margin-bottom:8px;font-size:16px;color:#15202b;">${multiline(title)}</div>` +
|
|
293
|
+
`<div style="font-size:14px;line-height:1.5;color:#1f2937;white-space:pre-wrap;">${multiline(body)}</div>`);
|
|
294
|
+
}
|
|
295
|
+
function renderOrderSummary(p, language) {
|
|
296
|
+
const title = p.title || 'Order summary';
|
|
297
|
+
const showSku = p.showSku === true;
|
|
298
|
+
const showName = p.showName !== false;
|
|
299
|
+
const showQty = p.showQuantity !== false;
|
|
300
|
+
const showPrice = p.showPrice !== false;
|
|
301
|
+
const showTotals = p.showTotals === true;
|
|
302
|
+
const col = orderSummaryColumnLabels(language);
|
|
303
|
+
const headers = [];
|
|
304
|
+
if (showSku)
|
|
305
|
+
headers.push(col.sku);
|
|
306
|
+
if (showName)
|
|
307
|
+
headers.push(col.item);
|
|
308
|
+
if (showQty)
|
|
309
|
+
headers.push(col.qty);
|
|
310
|
+
if (showPrice)
|
|
311
|
+
headers.push(col.price);
|
|
312
|
+
const headerRow = headers.length > 0
|
|
313
|
+
? `<tr>${headers
|
|
314
|
+
.map((h) => `<th align="left" style="padding:6px 8px;border-bottom:1px solid #e5e7eb;font-size:12px;color:#6b7280;text-transform:uppercase;">${escapeHtml(h)}</th>`)
|
|
315
|
+
.join('')}</tr>`
|
|
316
|
+
: '';
|
|
317
|
+
const cells = [];
|
|
318
|
+
if (showSku) {
|
|
319
|
+
cells.push(`<td style="padding:8px;border-bottom:1px solid #f3f4f6;font-size:14px;color:#1f2937;">{{var item.sku}}</td>`);
|
|
320
|
+
}
|
|
321
|
+
if (showName) {
|
|
322
|
+
cells.push(`<td style="padding:8px;border-bottom:1px solid #f3f4f6;font-size:14px;color:#1f2937;">{{var item.name}}</td>`);
|
|
323
|
+
}
|
|
324
|
+
if (showQty) {
|
|
325
|
+
cells.push(`<td style="padding:8px;border-bottom:1px solid #f3f4f6;font-size:14px;color:#1f2937;">{{var item.quantity}}</td>`);
|
|
326
|
+
}
|
|
327
|
+
if (showPrice) {
|
|
328
|
+
cells.push(`<td style="padding:8px;border-bottom:1px solid #f3f4f6;font-size:14px;color:#1f2937;">{{var item.price}}</td>`);
|
|
329
|
+
}
|
|
330
|
+
const loop = cells.length > 0 ? `{{for item in order.items}}<tr>${cells.join('')}</tr>{{/for}}` : '';
|
|
331
|
+
const totals = showTotals
|
|
332
|
+
? `<div style="margin-top:12px;font-size:14px;color:#1f2937;white-space:pre-wrap;">{{var order.summaryText}}</div>`
|
|
333
|
+
: '';
|
|
334
|
+
return row(`<div style="font-weight:bold;margin-bottom:8px;font-size:16px;color:#15202b;">${multiline(title)}</div>` +
|
|
335
|
+
`<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;">` +
|
|
336
|
+
headerRow +
|
|
337
|
+
loop +
|
|
338
|
+
`</table>` +
|
|
339
|
+
totals, orderBlockPadding(p.marginTop, p.marginBottom));
|
|
340
|
+
}
|
|
341
|
+
function renderOrderLabeledVar(p, varKey) {
|
|
342
|
+
const title = p.title?.trim() ?? '';
|
|
343
|
+
const heading = title
|
|
344
|
+
? `<div style="font-weight:bold;margin-bottom:6px;font-size:14px;color:#15202b;">${multiline(title)}</div>`
|
|
345
|
+
: '';
|
|
346
|
+
return row(heading +
|
|
347
|
+
`<div style="font-size:14px;line-height:1.5;color:#1f2937;white-space:pre-wrap;">{{var ${varKey}}}</div>`, orderBlockPadding(p.marginTop, p.marginBottom));
|
|
348
|
+
}
|
|
349
|
+
function renderTable(p) {
|
|
350
|
+
const columns = Array.isArray(p.columns) ? p.columns : [];
|
|
351
|
+
const rows = Array.isArray(p.tableRows) ? p.tableRows : [];
|
|
352
|
+
if (columns.length === 0 && rows.length === 0)
|
|
353
|
+
return '';
|
|
354
|
+
const colCount = Math.max(1, columns.length);
|
|
355
|
+
const header = columns.length > 0
|
|
356
|
+
? `<thead><tr>${columns
|
|
357
|
+
.map((c) => `<th align="left" style="padding:6px 8px;border-bottom:2px solid #e5e7eb;font-size:12px;color:#6b7280;text-transform:uppercase;">${escapeHtml(c?.label ?? '')}</th>`)
|
|
358
|
+
.join('')}</tr></thead>`
|
|
359
|
+
: '';
|
|
360
|
+
const body = rows
|
|
361
|
+
.map((rowData, ri) => {
|
|
362
|
+
const cells = [...(rowData.cells ?? [])];
|
|
363
|
+
while (cells.length < colCount)
|
|
364
|
+
cells.push({ value: '' });
|
|
365
|
+
const bg = p.striped && ri % 2 === 1 ? 'background-color:#f9fafb;' : '';
|
|
366
|
+
return `<tr>${cells
|
|
367
|
+
.slice(0, colCount)
|
|
368
|
+
.map((cell) => `<td style="padding:6px 8px;border-bottom:1px solid #f3f4f6;font-size:14px;color:#1f2937;${bg}">${multiline(cell?.value ?? '')}</td>`)
|
|
369
|
+
.join('')}</tr>`;
|
|
370
|
+
})
|
|
371
|
+
.join('');
|
|
372
|
+
return row(`<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;width:100%;max-width:100%;">${header}<tbody>${body}</tbody></table>`);
|
|
373
|
+
}
|
|
374
|
+
function renderSocial(p) {
|
|
375
|
+
const links = Array.isArray(p.links) ? p.links : [];
|
|
376
|
+
const active = links.filter((l) => l?.enabled !== false && l?.href);
|
|
377
|
+
if (active.length === 0)
|
|
378
|
+
return '';
|
|
379
|
+
const showIcons = p.showIcons !== false;
|
|
380
|
+
const showLabels = p.showLabels === true;
|
|
381
|
+
const iconSize = Math.min(48, Math.max(16, p.iconSize || 28));
|
|
382
|
+
const useBrand = p.useBrandColors !== false;
|
|
383
|
+
const fallbackColor = p.color || '#1f2937';
|
|
384
|
+
const parts = active
|
|
385
|
+
.map((l) => {
|
|
386
|
+
const network = (l.network in EMAIL_SOCIAL_LABELS ? l.network : 'other');
|
|
387
|
+
const label = l.label || EMAIL_SOCIAL_LABELS[network];
|
|
388
|
+
const color = useBrand ? EMAIL_SOCIAL_BRAND_COLORS[network] : fallbackColor;
|
|
389
|
+
const icon = showIcons
|
|
390
|
+
? `<img src="${escapeAttr(socialIconDataUri(network, color))}" width="${iconSize}" height="${iconSize}" ` +
|
|
391
|
+
`alt="" style="display:inline-block;border:0;outline:none;width:${iconSize}px;height:${iconSize}px;vertical-align:middle;" />`
|
|
392
|
+
: '';
|
|
393
|
+
const text = showLabels
|
|
394
|
+
? `<span style="font-size:13px;color:${escapeAttr(color)};vertical-align:middle;${showIcons ? 'padding-left:6px;' : ''}">${escapeHtml(label)}</span>`
|
|
395
|
+
: '';
|
|
396
|
+
if (!icon && !text) {
|
|
397
|
+
return `<a href="${escapeAttr(l.href)}" target="_blank" rel="noreferrer" style="color:${escapeAttr(color)};text-decoration:underline;margin:0 8px;">${escapeHtml(label)}</a>`;
|
|
398
|
+
}
|
|
399
|
+
return (`<a href="${escapeAttr(l.href)}" target="_blank" rel="noreferrer" ` +
|
|
400
|
+
`style="display:inline-block;text-decoration:none;margin:0 8px;vertical-align:middle;" aria-label="${escapeAttr(label)}">` +
|
|
401
|
+
icon +
|
|
402
|
+
text +
|
|
403
|
+
`</a>`);
|
|
404
|
+
})
|
|
405
|
+
.join('');
|
|
406
|
+
return row(`<div style="text-align:${p.align};padding:8px 0;line-height:1;">${parts}</div>`);
|
|
407
|
+
}
|
|
408
|
+
function prepareFooterLegalHtml(text) {
|
|
409
|
+
let t = text || '';
|
|
410
|
+
const alreadyLinked = /href\s*=\s*["']\s*\{\{\s*var\s+unsubscribeUrl\s*\}\}/i.test(t);
|
|
411
|
+
if (!alreadyLinked && t.includes('{{var unsubscribeUrl}}')) {
|
|
412
|
+
if (/Unsubscribe:\s*\{\{\s*var\s+unsubscribeUrl\s*\}\}/i.test(t)) {
|
|
413
|
+
t = t.replace(/Unsubscribe:\s*\{\{\s*var\s+unsubscribeUrl\s*\}\}/gi, '<a href="{{var unsubscribeUrl}}">Unsubscribe</a>');
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
t = t.replace(/\{\{\s*var\s+unsubscribeUrl\s*\}\}/g, '<a href="{{var unsubscribeUrl}}">Unsubscribe</a>');
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return sanitizeEmailHtml(t.replace(/\r?\n/g, '<br />'));
|
|
420
|
+
}
|
|
421
|
+
function renderCallout(p) {
|
|
422
|
+
const bg = p.backgroundColor || '#f3f4f6';
|
|
423
|
+
const border = p.borderColor || '#d1d5db';
|
|
424
|
+
const textColor = p.textColor || '#1f2937';
|
|
425
|
+
return row(`<div style="background-color:${escapeAttr(bg)};border:1px solid ${escapeAttr(border)};border-radius:6px;` +
|
|
426
|
+
`padding:14px 16px;text-align:${p.align};font-size:15px;line-height:1.5;color:${escapeAttr(textColor)};">${multiline(p.text)}</div>`, '8px 24px');
|
|
427
|
+
}
|
|
428
|
+
function renderFooterLegal(p) {
|
|
429
|
+
const html = prepareFooterLegalHtml(p.text);
|
|
430
|
+
if (!html)
|
|
431
|
+
return '';
|
|
432
|
+
return row(`<div style="margin:0;padding:8px 0;font-size:12px;line-height:1.5;color:#6b7280;text-align:${p.align};">${html}</div>`);
|
|
433
|
+
}
|
|
434
|
+
function renderEmbed(kind, code, ctx) {
|
|
435
|
+
if (!code || ctx.depth >= ctx.maxDepth)
|
|
436
|
+
return '';
|
|
437
|
+
const tree = ctx.embeds[kind][code];
|
|
438
|
+
if (!tree)
|
|
439
|
+
return '';
|
|
440
|
+
return renderNodes(treeContent(tree), { ...ctx, tree, depth: ctx.depth + 1 });
|
|
441
|
+
}
|
|
442
|
+
function renderNode(node, ctx) {
|
|
443
|
+
const p = node.props ?? {};
|
|
444
|
+
switch (node.type) {
|
|
445
|
+
case 'transactional_emails.EmailHeading':
|
|
446
|
+
return renderHeading({
|
|
447
|
+
level: p['level'] ?? 'h2',
|
|
448
|
+
text: str(p, 'text'),
|
|
449
|
+
align: align(p),
|
|
450
|
+
});
|
|
451
|
+
case 'transactional_emails.EmailText':
|
|
452
|
+
return renderText({ text: str(p, 'text'), align: align(p) });
|
|
453
|
+
case 'transactional_emails.EmailButton':
|
|
454
|
+
return renderButton({
|
|
455
|
+
label: str(p, 'label', 'Button'),
|
|
456
|
+
href: str(p, 'href'),
|
|
457
|
+
align: align(p, 'left'),
|
|
458
|
+
backgroundColor: str(p, 'backgroundColor'),
|
|
459
|
+
textColor: str(p, 'textColor'),
|
|
460
|
+
}, ctx.accent);
|
|
461
|
+
case 'transactional_emails.EmailImage':
|
|
462
|
+
return renderImage({
|
|
463
|
+
src: str(p, 'src'),
|
|
464
|
+
alt: str(p, 'alt'),
|
|
465
|
+
href: str(p, 'href'),
|
|
466
|
+
width: num(p, 'width', 200),
|
|
467
|
+
align: align(p, 'center'),
|
|
468
|
+
...(p['imageSource'] === 'library' || p['imageSource'] === 'url'
|
|
469
|
+
? { imageSource: p['imageSource'] }
|
|
470
|
+
: {}),
|
|
471
|
+
...(typeof p['assetId'] === 'string' ? { assetId: p['assetId'] } : {}),
|
|
472
|
+
});
|
|
473
|
+
case 'transactional_emails.EmailLogo':
|
|
474
|
+
return renderLogo({
|
|
475
|
+
src: str(p, 'src', '{{var branding.logoUrl}}'),
|
|
476
|
+
alt: str(p, 'alt', 'Logo'),
|
|
477
|
+
href: str(p, 'href'),
|
|
478
|
+
width: num(p, 'width', 160),
|
|
479
|
+
align: align(p, 'center'),
|
|
480
|
+
});
|
|
481
|
+
case 'transactional_emails.EmailDivider':
|
|
482
|
+
return renderDivider({
|
|
483
|
+
thickness: num(p, 'thickness', 1),
|
|
484
|
+
color: str(p, 'color', '#e5e7eb'),
|
|
485
|
+
});
|
|
486
|
+
case 'transactional_emails.EmailSpacer':
|
|
487
|
+
return renderSpacer({ height: num(p, 'height', 16) });
|
|
488
|
+
case 'transactional_emails.EmailTable':
|
|
489
|
+
return renderTable({
|
|
490
|
+
columns: Array.isArray(p['columns']) ? p['columns'] : [],
|
|
491
|
+
tableRows: Array.isArray(p['tableRows'])
|
|
492
|
+
? p['tableRows']
|
|
493
|
+
: [],
|
|
494
|
+
striped: bool(p, 'striped', true),
|
|
495
|
+
});
|
|
496
|
+
case 'transactional_emails.EmailSection': {
|
|
497
|
+
const children = slotChildren(node, ctx, 'content');
|
|
498
|
+
const inner = renderNodes(children, ctx);
|
|
499
|
+
return renderSection({
|
|
500
|
+
backgroundColor: str(p, 'backgroundColor', '#ffffff'),
|
|
501
|
+
paddingY: num(p, 'paddingY', 16),
|
|
502
|
+
paddingX: num(p, 'paddingX', 24),
|
|
503
|
+
}, inner);
|
|
504
|
+
}
|
|
505
|
+
case 'transactional_emails.EmailRow': {
|
|
506
|
+
const columnNodes = slotChildren(node, ctx, 'content').filter((n) => n.type === 'transactional_emails.EmailColumn');
|
|
507
|
+
const equalFallback = columnNodes.length > 0 ? Math.floor(12 / columnNodes.length) : 6;
|
|
508
|
+
const columns = columnNodes.map((col) => ({
|
|
509
|
+
innerRows: withCellPadding('4px 0', () => renderNodes(slotChildren(col, ctx, 'content'), ctx)),
|
|
510
|
+
span: num(col.props, 'span', equalFallback),
|
|
511
|
+
}));
|
|
512
|
+
const valignRaw = str(p, 'verticalAlign', 'top');
|
|
513
|
+
const verticalAlign = valignRaw === 'middle' || valignRaw === 'bottom' ? valignRaw : 'top';
|
|
514
|
+
return renderRow({
|
|
515
|
+
gap: num(p, 'gap', 16),
|
|
516
|
+
verticalAlign,
|
|
517
|
+
}, columns);
|
|
518
|
+
}
|
|
519
|
+
case 'transactional_emails.EmailColumn': {
|
|
520
|
+
// Standalone column (should only live inside EmailRow) — stack children full-width.
|
|
521
|
+
return renderNodes(slotChildren(node, ctx, 'content'), ctx);
|
|
522
|
+
}
|
|
523
|
+
case 'transactional_emails.EmailRichText':
|
|
524
|
+
return renderRichText({
|
|
525
|
+
content: p['content'],
|
|
526
|
+
html: str(p, 'html'),
|
|
527
|
+
align: align(p),
|
|
528
|
+
});
|
|
529
|
+
case 'catalog.EmailProductCard':
|
|
530
|
+
return renderProductCard({
|
|
531
|
+
productSlug: str(p, 'productSlug'),
|
|
532
|
+
productId: str(p, 'productId'),
|
|
533
|
+
imageSrc: str(p, 'imageSrc'),
|
|
534
|
+
title: str(p, 'title', 'Product'),
|
|
535
|
+
sku: str(p, 'sku'),
|
|
536
|
+
price: str(p, 'price'),
|
|
537
|
+
href: str(p, 'href'),
|
|
538
|
+
ctaLabel: str(p, 'ctaLabel', 'View'),
|
|
539
|
+
showImage: bool(p, 'showImage', true),
|
|
540
|
+
showPrice: bool(p, 'showPrice', true),
|
|
541
|
+
showSku: bool(p, 'showSku', false),
|
|
542
|
+
align: align(p, 'left'),
|
|
543
|
+
}, ctx.accent);
|
|
544
|
+
case 'catalog.EmailProductGrid':
|
|
545
|
+
return renderProductGrid({
|
|
546
|
+
productSlugs: Array.isArray(p['productSlugs']) ? p['productSlugs'] : [],
|
|
547
|
+
columns: num(p, 'columns', 2),
|
|
548
|
+
gap: num(p, 'gap', 16),
|
|
549
|
+
showImage: bool(p, 'showImage', true),
|
|
550
|
+
showPrice: bool(p, 'showPrice', true),
|
|
551
|
+
showSku: bool(p, 'showSku', false),
|
|
552
|
+
ctaLabel: str(p, 'ctaLabel', 'View'),
|
|
553
|
+
items: Array.isArray(p['items']) ? p['items'] : [],
|
|
554
|
+
}, ctx.accent);
|
|
555
|
+
case 'catalog.EmailCategoryGrid':
|
|
556
|
+
return renderCategoryGrid({
|
|
557
|
+
categorySlugs: Array.isArray(p['categorySlugs']) ? p['categorySlugs'] : [],
|
|
558
|
+
columns: num(p, 'columns', 2),
|
|
559
|
+
gap: num(p, 'gap', 16),
|
|
560
|
+
showImage: bool(p, 'showImage', true),
|
|
561
|
+
items: Array.isArray(p['items']) ? p['items'] : [],
|
|
562
|
+
});
|
|
563
|
+
case 'orders.EmailOrderSummary': {
|
|
564
|
+
const rawBody = typeof p['body'] === 'string' ? p['body'] : '';
|
|
565
|
+
const hasNewFlags = 'showName' in p ||
|
|
566
|
+
'showSku' in p ||
|
|
567
|
+
'showQuantity' in p ||
|
|
568
|
+
'showPrice' in p ||
|
|
569
|
+
'showTotals' in p;
|
|
570
|
+
if (rawBody && !hasNewFlags) {
|
|
571
|
+
return renderOrderSummaryLegacy(str(p, 'title', 'Order summary'), rawBody);
|
|
572
|
+
}
|
|
573
|
+
return renderOrderSummary({
|
|
574
|
+
title: str(p, 'title', 'Order summary'),
|
|
575
|
+
showSku: p['showSku'] === true,
|
|
576
|
+
showName: p['showName'] !== false,
|
|
577
|
+
showQuantity: p['showQuantity'] !== false,
|
|
578
|
+
showPrice: p['showPrice'] !== false,
|
|
579
|
+
showTotals: p['showTotals'] === true,
|
|
580
|
+
marginTop: num(p, 'marginTop', EMAIL_ORDER_BLOCK_MARGIN_DEFAULT),
|
|
581
|
+
marginBottom: num(p, 'marginBottom', EMAIL_ORDER_BLOCK_MARGIN_DEFAULT),
|
|
582
|
+
}, ctx.language);
|
|
583
|
+
}
|
|
584
|
+
case 'orders.EmailOrderId':
|
|
585
|
+
case 'orders.EmailBillingAddress':
|
|
586
|
+
case 'orders.EmailShippingAddress':
|
|
587
|
+
case 'orders.EmailOrderTotals':
|
|
588
|
+
case 'orders.EmailAppliedDiscounts':
|
|
589
|
+
case 'orders.EmailDeliveryMethod':
|
|
590
|
+
case 'orders.EmailPaymentMethod': {
|
|
591
|
+
const meta = EMAIL_ORDER_LABELED_FIELDS[node.type];
|
|
592
|
+
return renderOrderLabeledVar({
|
|
593
|
+
title: str(p, 'title', meta.defaultTitle),
|
|
594
|
+
marginTop: num(p, 'marginTop', EMAIL_ORDER_BLOCK_MARGIN_DEFAULT),
|
|
595
|
+
marginBottom: num(p, 'marginBottom', EMAIL_ORDER_BLOCK_MARGIN_DEFAULT),
|
|
596
|
+
}, meta.varKey);
|
|
597
|
+
}
|
|
598
|
+
case 'transactional_emails.EmailSocial':
|
|
599
|
+
return renderSocial({
|
|
600
|
+
links: Array.isArray(p['links']) ? p['links'] : [],
|
|
601
|
+
align: align(p, 'center'),
|
|
602
|
+
showIcons: bool(p, 'showIcons', true),
|
|
603
|
+
showLabels: bool(p, 'showLabels', false),
|
|
604
|
+
iconSize: num(p, 'iconSize', 28),
|
|
605
|
+
useBrandColors: bool(p, 'useBrandColors', true),
|
|
606
|
+
color: str(p, 'color', '#1f2937'),
|
|
607
|
+
});
|
|
608
|
+
case 'transactional_emails.EmailCallout':
|
|
609
|
+
return renderCallout({
|
|
610
|
+
text: str(p, 'text'),
|
|
611
|
+
backgroundColor: str(p, 'backgroundColor', '#f3f4f6'),
|
|
612
|
+
borderColor: str(p, 'borderColor', '#d1d5db'),
|
|
613
|
+
textColor: str(p, 'textColor', '#1f2937'),
|
|
614
|
+
align: align(p),
|
|
615
|
+
});
|
|
616
|
+
case 'transactional_emails.EmailFooterLegal':
|
|
617
|
+
return renderFooterLegal({
|
|
618
|
+
text: str(p, 'text', 'You received this email because you are subscribed.\n<a href="{{var unsubscribeUrl}}">Unsubscribe</a>'),
|
|
619
|
+
align: align(p, 'center'),
|
|
620
|
+
});
|
|
621
|
+
case 'transactional_emails.EmailInsertBlock':
|
|
622
|
+
return renderEmbed('blocks', str(p, 'code'), ctx);
|
|
623
|
+
case 'transactional_emails.EmailInsertTemplate':
|
|
624
|
+
return renderEmbed('templates', str(p, 'code'), ctx);
|
|
625
|
+
default:
|
|
626
|
+
return '';
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
function renderNodes(nodes, ctx) {
|
|
630
|
+
return nodes.map((n) => renderNode(n, ctx)).join('');
|
|
631
|
+
}
|
|
632
|
+
function wrapDocument(body) {
|
|
633
|
+
return (`<!DOCTYPE html><html lang="en"><head><meta charset="utf-8" />` +
|
|
634
|
+
`<meta name="viewport" content="width=device-width, initial-scale=1.0" />` +
|
|
635
|
+
`<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />` +
|
|
636
|
+
`</head>` +
|
|
637
|
+
`<body style="margin:0;padding:0;background-color:#f3f4f6;-webkit-text-size-adjust:100%;">` +
|
|
638
|
+
`<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:#f3f4f6;width:100%;">` +
|
|
639
|
+
`<tr><td align="center" style="padding:24px 12px;">` +
|
|
640
|
+
`<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" ` +
|
|
641
|
+
`style="width:100%;max-width:600px;background-color:#ffffff;border-radius:8px;">` +
|
|
642
|
+
body +
|
|
643
|
+
`</table></td></tr></table></body></html>`);
|
|
644
|
+
}
|
|
645
|
+
export function renderEmailHtml(tree, opts = {}) {
|
|
646
|
+
const safeTree = tree ?? { root: { props: {} }, content: [], zones: {} };
|
|
647
|
+
const ctx = {
|
|
648
|
+
embeds: opts.embeds ?? { blocks: {}, templates: {} },
|
|
649
|
+
accent: opts.accentColor ?? DEFAULT_ACCENT,
|
|
650
|
+
depth: 0,
|
|
651
|
+
maxDepth: opts.maxDepth ?? DEFAULT_MAX_DEPTH,
|
|
652
|
+
tree: safeTree,
|
|
653
|
+
language: opts.language ?? 'en-US',
|
|
654
|
+
};
|
|
655
|
+
const body = renderNodes(treeContent(safeTree), ctx);
|
|
656
|
+
return opts.document === false ? body : wrapDocument(body);
|
|
657
|
+
}
|
|
658
|
+
export { emailHtmlToPlainText, sanitizeEmailHtml };
|
|
659
|
+
//# sourceMappingURL=render-email-html.js.map
|