@marlinjai/email-editor-core 0.3.0 → 0.4.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/server.mjs CHANGED
@@ -1,179 +1,31 @@
1
1
  import {
2
2
  CURRENT_TEMPLATE_VERSION,
3
- allSections,
4
- buildGradientCSS,
5
- isLeafBlockType,
6
3
  isTemplateMigrationError,
7
- isWrapper,
8
4
  migrateTemplate
9
- } from "./chunk-2ZL7A2I6.mjs";
5
+ } from "./chunk-NQPMHQ5G.mjs";
6
+ import {
7
+ DEFAULT_MJML_ATTRIBUTES,
8
+ EDITOR_DATA_ATTRIBUTE,
9
+ EDITOR_MARKER,
10
+ MjmlBuilder,
11
+ addPx,
12
+ base64Utf8,
13
+ encodeEditorData,
14
+ mjmlOptions,
15
+ paddingWithGap,
16
+ stripEditorMarkup
17
+ } from "./chunk-OOCYGZMB.mjs";
10
18
 
11
19
  // src/compiler/MJMLCompiler.ts
12
20
  import mjml2html from "mjml";
13
-
14
- // src/compiler/blockToRawHtml.ts
15
- function escapeAttr(s) {
16
- return s.replace(/"/g, "&quot;").replace(/</g, "&lt;");
17
- }
18
- function spacingToCss(p) {
19
- if (!p) return "";
20
- const t = p.top ?? "0", r = p.right ?? "0", b = p.bottom ?? "0", l = p.left ?? "0";
21
- return `padding:${t} ${r} ${b} ${l};`;
22
- }
23
- function textToHtml(b) {
24
- const styles = [];
25
- if (b.color) styles.push(`color:${b.color}`);
26
- if (b.fontSize) styles.push(`font-size:${b.fontSize}`);
27
- if (b.fontFamily) styles.push(`font-family:${b.fontFamily}`);
28
- if (b.lineHeight) styles.push(`line-height:${b.lineHeight}`);
29
- if (b.align) styles.push(`text-align:${b.align}`);
30
- styles.push("margin:0");
31
- const padding = spacingToCss(b.padding);
32
- return `<div style="${styles.join(";")};${padding}">${b.content}</div>`;
33
- }
34
- function imageToHtml(b) {
35
- const align = b.align ?? "center";
36
- const wrap = `style="text-align:${align};${spacingToCss(b.padding)}"`;
37
- const widthAttr = b.width ? ` width="${escapeAttr(b.width)}"` : "";
38
- const heightAttr = b.height ? ` height="${escapeAttr(b.height)}"` : "";
39
- const radius = b.borderRadius ? `border-radius:${b.borderRadius};` : "";
40
- const img = `<img src="${escapeAttr(b.src)}" alt="${escapeAttr(b.alt ?? "")}"${widthAttr}${heightAttr} style="display:block;max-width:100%;${radius}border:0;outline:none;text-decoration:none;" />`;
41
- const inner = b.href ? `<a href="${escapeAttr(b.href)}" target="_blank" rel="noopener" style="text-decoration:none;">${img}</a>` : img;
42
- return `<div ${wrap}>${inner}</div>`;
43
- }
44
- function buttonToHtml(b) {
45
- const align = b.align ?? "center";
46
- const bg = b.backgroundColor ?? "#000000";
47
- const fg = b.color ?? "#ffffff";
48
- const radius = b.borderRadius ?? "3px";
49
- const innerPadding = b.innerPadding ?? "10px 25px";
50
- const border = b.border ? `border:${b.border};` : "";
51
- return `<div style="text-align:${align};${spacingToCss(b.padding)}">
52
- <table role="presentation" cellpadding="0" cellspacing="0" border="0" style="border-collapse:separate;line-height:100%;display:inline-block;">
53
- <tr>
54
- <td align="center" valign="middle" role="presentation" style="background-color:${bg};border-radius:${radius};${border}padding:${innerPadding};">
55
- <a href="${escapeAttr(b.href)}" target="_blank" rel="noopener" style="display:inline-block;color:${fg};font-family:inherit;font-size:14px;font-weight:600;line-height:120%;text-decoration:none;text-transform:none;">${escapeAttr(b.label)}</a>
56
- </td>
57
- </tr>
58
- </table>
59
- </div>`;
60
- }
61
- function dividerToHtml(b) {
62
- const color = b.borderColor ?? "#cccccc";
63
- const w = b.borderWidth ?? "1px";
64
- const style = b.borderStyle ?? "solid";
65
- const width = b.width ?? "100%";
66
- return `<div style="${spacingToCss(b.padding)}">
67
- <div style="border-top:${w} ${style} ${color};width:${width};font-size:1px;line-height:1px;">&nbsp;</div>
68
- </div>`;
69
- }
70
- function spacerToHtml(b) {
71
- return `<div style="height:${b.height};line-height:${b.height};font-size:1px;">&nbsp;</div>`;
72
- }
73
- function socialToHtml(b) {
74
- const items = (b.links ?? []).map((link) => {
75
- const icon = link.icon ?? "";
76
- return `<a href="${escapeAttr(link.url)}" target="_blank" rel="noopener" style="display:inline-block;margin:0 4px;">
77
- <img src="${escapeAttr(icon)}" alt="${escapeAttr(link.platform)}" width="24" height="24" style="display:inline-block;border:0;" />
78
- </a>`;
79
- }).join("");
80
- return `<div style="text-align:center;">${items}</div>`;
81
- }
82
- function blockToRawHtml(block) {
83
- if (!isLeafBlockType(block.type)) {
84
- throw new Error(`blockToRawHtml only handles leaf blocks; got "${block.type}"`);
85
- }
86
- switch (block.type) {
87
- case "text" /* TEXT */:
88
- return textToHtml(block);
89
- case "image" /* IMAGE */:
90
- return imageToHtml(block);
91
- case "button" /* BUTTON */:
92
- return buttonToHtml(block);
93
- case "divider" /* DIVIDER */:
94
- return dividerToHtml(block);
95
- case "spacer" /* SPACER */:
96
- return spacerToHtml(block);
97
- case "social" /* SOCIAL */:
98
- return socialToHtml(block);
99
- default:
100
- throw new Error(`Unhandled leaf block type: ${block.type}`);
101
- }
102
- }
103
-
104
- // src/compiler/MJMLCompiler.ts
105
- var DEFAULT_MJML_ATTRIBUTES = `
106
- <mj-all font-family="Georgia, serif" />
107
- <mj-text font-size="14px" line-height="1.6" />
108
- `;
109
- var EDITOR_DATA_ATTRIBUTE = {
110
- social: "data-ee-social",
111
- subColumns: "data-ee-subcols"
112
- };
113
- function encodeEditorData(value) {
114
- return Buffer.from(JSON.stringify(value), "utf8").toString("base64");
115
- }
116
- function finishAttributes(attrs, classes, extra) {
117
- const out = [...attrs];
118
- const taken = new Set(attrs.map((a) => a.slice(0, a.indexOf("="))));
119
- const allClasses = [...classes];
120
- if (extra) {
121
- for (const [name, value] of Object.entries(extra)) {
122
- if (name === "css-class") {
123
- for (const c of value.replace(/"/g, "&quot;").split(/\s+/)) if (c && !allClasses.includes(c)) allClasses.push(c);
124
- continue;
125
- }
126
- if (taken.has(name)) continue;
127
- out.push(`${name}="${value.replace(/"/g, "&quot;")}"`);
128
- }
129
- }
130
- if (allClasses.length > 0) out.push(`css-class="${allClasses.join(" ")}"`);
131
- return out.join(" ");
132
- }
133
- function quoted(value) {
134
- return value.replace(/"/g, "&quot;");
135
- }
136
- function spacingToString(spacing) {
137
- if (!spacing) return "";
138
- const { top, right, bottom, left } = spacing;
139
- if (!top && !right && !bottom && !left) return "";
140
- return [top || "0", right || "0", bottom || "0", left || "0"].join(" ");
141
- }
142
- var MJMLCompiler = class {
143
- constructor() {
144
- /**
145
- * Set true while emitting a column with sub-columns. Causes generateHead
146
- * to inject the responsive media query exactly once per template.
147
- */
148
- this.needsSubColumnStyles = false;
149
- /**
150
- * Platform brand colors for social icons
151
- */
152
- this.SOCIAL_COLORS = {
153
- facebook: "#1877F2",
154
- twitter: "#000000",
155
- instagram: "#E4405F",
156
- linkedin: "#0A66C2",
157
- youtube: "#FF0000",
158
- pinterest: "#BD081C",
159
- github: "#181717"
160
- };
161
- }
21
+ var MJMLCompiler = class extends MjmlBuilder {
162
22
  /**
163
23
  * Compile email template to MJML and HTML
164
24
  */
165
- compile(template, options = {}) {
25
+ async compile(template, options = {}) {
166
26
  try {
167
- this.needsSubColumnStyles = false;
168
- const mjml = this.templateToMJML(template);
169
- const result = mjml2html(mjml, {
170
- // Never resolve <mj-include>: its path is read from the server's disk,
171
- // and a stored document (a Raw block's html) must not reach local files.
172
- ignoreIncludes: true,
173
- validationLevel: "soft",
174
- minify: false,
175
- ...options.webFonts === false ? { fonts: {} } : {}
176
- });
27
+ const mjml = this.toMJML(template, { editor: options.editor });
28
+ const result = await mjml2html(mjml, mjmlOptions(options));
177
29
  return {
178
30
  mjml,
179
31
  html: result.html,
@@ -187,651 +39,6 @@ var MJMLCompiler = class {
187
39
  };
188
40
  }
189
41
  }
190
- /**
191
- * The MJML markup for a document, without compiling it to HTML.
192
- */
193
- toMJML(template) {
194
- this.needsSubColumnStyles = false;
195
- return this.templateToMJML(template);
196
- }
197
- /**
198
- * Convert template to MJML markup
199
- */
200
- templateToMJML(template) {
201
- const { metadata, sections } = template;
202
- const body = sections.map((item) => isWrapper(item) ? this.wrapperToMJML(item) : this.sectionToMJML(item)).join("\n");
203
- const gradientCSS = this.collectGradientStyles(sections);
204
- const head = this.generateHead(metadata, gradientCSS);
205
- const bodyAttributes = finishAttributes([], [], metadata.mjmlHead?.bodyAttributes);
206
- return `
207
- <mjml>
208
- ${head}
209
- <mj-body${bodyAttributes ? ` ${bodyAttributes}` : ""}>
210
- ${body}
211
- </mj-body>
212
- </mjml>
213
- `.trim();
214
- }
215
- /**
216
- * Generate MJML head section with all head components
217
- * Supports: mj-title, mj-preview, mj-font, mj-breakpoint, mj-style
218
- */
219
- generateHead(metadata, gradientCSS) {
220
- const parts = ["<mj-head>"];
221
- if (metadata.title) {
222
- parts.push(`<mj-title>${metadata.title}</mj-title>`);
223
- }
224
- if (metadata.previewText || metadata.subject) {
225
- parts.push(`<mj-preview>${metadata.previewText || metadata.subject || ""}</mj-preview>`);
226
- }
227
- if (metadata.fonts && metadata.fonts.length > 0) {
228
- for (const font of metadata.fonts) {
229
- parts.push(`<mj-font name="${font.name}" href="${font.href}" />`);
230
- }
231
- }
232
- if (metadata.breakpoint) {
233
- parts.push(`<mj-breakpoint width="${metadata.breakpoint}" />`);
234
- }
235
- const mjmlHead = metadata.mjmlHead;
236
- const attributes = mjmlHead?.attributes ?? DEFAULT_MJML_ATTRIBUTES;
237
- if (attributes.trim()) parts.push(`<mj-attributes>${attributes}</mj-attributes>`);
238
- if (mjmlHead?.headRaw) parts.push(mjmlHead.headRaw);
239
- if (metadata.customCSS) {
240
- parts.push(`<mj-style>${metadata.customCSS}</mj-style>`);
241
- }
242
- if (metadata.inlineCSS) {
243
- parts.push(`<mj-style inline="inline">${metadata.inlineCSS}</mj-style>`);
244
- }
245
- if (gradientCSS) {
246
- parts.push(`<mj-style>${gradientCSS}</mj-style>`);
247
- }
248
- if (this.needsSubColumnStyles) {
249
- parts.push(`<mj-style>
250
- @media only screen and (max-width:480px) {
251
- table.ee-sub-cols td.ee-sub-col {
252
- display: block !important;
253
- width: 100% !important;
254
- padding-bottom: 12px !important;
255
- }
256
- table.ee-sub-cols td.ee-sub-col:last-child {
257
- padding-bottom: 0 !important;
258
- }
259
- }
260
- </mj-style>`);
261
- }
262
- parts.push("</mj-head>");
263
- return parts.join("\n");
264
- }
265
- /**
266
- * Collect background-image CSS rules for all wrappers, sections and columns that have gradients.
267
- * Returns a string of CSS rules to inject as a single mj-style block.
268
- */
269
- collectGradientStyles(items) {
270
- const rules = [];
271
- for (const item of items) {
272
- if (isWrapper(item) && item.backgroundGradient) {
273
- const css = buildGradientCSS(item.backgroundGradient);
274
- if (css) rules.push(`.el-grad-${item.id} { background-image: ${css}; }`);
275
- }
276
- }
277
- for (const section of allSections(items)) {
278
- if (section.backgroundGradient) {
279
- const css = buildGradientCSS(section.backgroundGradient);
280
- if (css) {
281
- rules.push(`.el-grad-${section.id} { background-image: ${css}; }`);
282
- }
283
- }
284
- for (const column of section.columns) {
285
- if (column.backgroundGradient) {
286
- const css = buildGradientCSS(column.backgroundGradient);
287
- if (css) {
288
- rules.push(`.el-grad-${column.id} { background-image: ${css}; }`);
289
- }
290
- }
291
- }
292
- }
293
- return rules.join("\n");
294
- }
295
- /**
296
- * Convert a wrapper to an `<mj-wrapper>` around its sections. The wrapper's
297
- * attributes are MJML's own for `mj-wrapper`; the sections inside follow
298
- * MJML's rules there (a full-width section inside a full-width wrapper
299
- * renders at standard width, which the inspector explains).
300
- */
301
- wrapperToMJML(wrapper) {
302
- if (wrapper.hidden) return "";
303
- const attrs = [];
304
- const cssClasses = ["el-wrapper", `el-${wrapper.id}`];
305
- this.pushBackground(attrs, cssClasses, wrapper);
306
- if (wrapper.border) attrs.push(`border="${quoted(wrapper.border)}"`);
307
- if (wrapper.borderTop) attrs.push(`border-top="${quoted(wrapper.borderTop)}"`);
308
- if (wrapper.borderRight) attrs.push(`border-right="${quoted(wrapper.borderRight)}"`);
309
- if (wrapper.borderBottom) attrs.push(`border-bottom="${quoted(wrapper.borderBottom)}"`);
310
- if (wrapper.borderLeft) attrs.push(`border-left="${quoted(wrapper.borderLeft)}"`);
311
- if (wrapper.borderRadius) attrs.push(`border-radius="${quoted(wrapper.borderRadius)}"`);
312
- if (wrapper.fullWidth) attrs.push('full-width="full-width"');
313
- const padding = spacingToString(wrapper.padding);
314
- if (padding) attrs.push(`padding="${quoted(padding)}"`);
315
- if (wrapper.gap) attrs.push(`gap="${quoted(wrapper.gap)}"`);
316
- if (wrapper.textAlign) attrs.push(`text-align="${quoted(wrapper.textAlign)}"`);
317
- for (const c of (wrapper.cssClass ?? "").split(/\s+/)) if (c && !cssClasses.includes(quoted(c))) cssClasses.push(quoted(c));
318
- const sections = wrapper.sections.map((section) => this.sectionToMJML(section)).filter(Boolean).join("\n");
319
- return `
320
- <mj-wrapper ${finishAttributes(attrs, cssClasses, wrapper.extraAttributes)}>
321
- ${sections}
322
- </mj-wrapper>
323
- `.trim();
324
- }
325
- /** Background attributes shared by sections and wrappers: a gradient (with its Outlook fallback colour), or colour and image. */
326
- pushBackground(attrs, cssClasses, node) {
327
- if (node.backgroundGradient) {
328
- const fallbackColor = node.backgroundGradient.stops[0]?.color;
329
- if (fallbackColor) attrs.push(`background-color="${quoted(fallbackColor)}"`);
330
- cssClasses.push(`el-grad-${node.id}`);
331
- return;
332
- }
333
- if (node.backgroundColor) attrs.push(`background-color="${quoted(node.backgroundColor)}"`);
334
- if (node.backgroundImage) attrs.push(`background-url="${quoted(node.backgroundImage)}"`);
335
- if (node.backgroundPosition) attrs.push(`background-position="${quoted(node.backgroundPosition)}"`);
336
- if (node.backgroundRepeat) attrs.push(`background-repeat="${quoted(node.backgroundRepeat)}"`);
337
- if (node.backgroundSize) attrs.push(`background-size="${quoted(node.backgroundSize)}"`);
338
- }
339
- /**
340
- * Convert section to MJML
341
- * Supports background images, full-width, and mj-group for non-stacking columns
342
- */
343
- sectionToMJML(section) {
344
- if (section.hidden) return "";
345
- if (section.bodyRaw) {
346
- const blocks = section.columns.flatMap((c) => c.blocks).filter((b) => !b.hidden);
347
- if (blocks.every((b) => b.type === "raw") && section.columns.every((c) => !c.subColumns?.length)) {
348
- return blocks.map((b) => `<mj-raw>${b.html}</mj-raw>`).join("\n");
349
- }
350
- }
351
- const attrs = [];
352
- const cssClasses = ["el-section", `el-${section.id}`];
353
- this.pushBackground(attrs, cssClasses, section);
354
- if (section.fullWidth) {
355
- attrs.push('full-width="full-width"');
356
- }
357
- if (section.padding) {
358
- const padding = spacingToString(section.padding);
359
- if (padding) attrs.push(`padding="${padding}"`);
360
- }
361
- const columns = section.columns.map((col) => this.columnToMJML(col)).join("\n");
362
- const sectionAttrs = finishAttributes(attrs, cssClasses, section.extraAttributes);
363
- if (section.noStack && section.columns.length > 1) {
364
- return `
365
- <mj-section ${sectionAttrs}>
366
- <mj-group>
367
- ${columns}
368
- </mj-group>
369
- </mj-section>
370
- `.trim();
371
- }
372
- return `
373
- <mj-section ${sectionAttrs}>
374
- ${columns}
375
- </mj-section>
376
- `.trim();
377
- }
378
- /**
379
- * Convert column to MJML
380
- */
381
- columnToMJML(column) {
382
- if (column.hidden) return "";
383
- if (column.subColumns && column.subColumns.length > 0) {
384
- return this.subColumnsToMJML(column);
385
- }
386
- const cssClasses = ["el-column", `el-${column.id}`];
387
- const attrs = [];
388
- if (column.width) {
389
- attrs.push(`width="${column.width}%"`);
390
- }
391
- if (column.backgroundGradient) {
392
- const fallbackColor = column.backgroundGradient.stops[0]?.color;
393
- if (fallbackColor) attrs.push(`background-color="${fallbackColor}"`);
394
- cssClasses.push(`el-grad-${column.id}`);
395
- } else {
396
- if (column.backgroundColor) {
397
- attrs.push(`background-color="${column.backgroundColor}"`);
398
- }
399
- }
400
- if (column.verticalAlign) {
401
- attrs.push(`vertical-align="${column.verticalAlign}"`);
402
- }
403
- if (column.padding) {
404
- const padding = spacingToString(column.padding);
405
- if (padding) attrs.push(`padding="${padding}"`);
406
- }
407
- const blocks = column.blocks.map((block) => this.blockToMJML(block)).join("\n");
408
- return `
409
- <mj-column ${finishAttributes(attrs, cssClasses, column.extraAttributes)}>
410
- ${blocks}
411
- </mj-column>
412
- `.trim();
413
- }
414
- /**
415
- * Emit a group column as an mj-column wrapping a hand-built nested
416
- * table inside an mj-raw island. The parent mj-section structure
417
- * stays standard MJML; only the nested area escapes MJML's parser.
418
- *
419
- * The responsive media query (table.ee-sub-cols td.ee-sub-col) is
420
- * injected once at document head via generateHead when the
421
- * needsSubColumnStyles flag is set.
422
- */
423
- subColumnsToMJML(column) {
424
- this.needsSubColumnStyles = true;
425
- const subs = column.subColumns ?? [];
426
- const sumRaw = subs.reduce((a, s) => a + (s.width || 0), 0) || 100;
427
- const widths = subs.map((s) => Math.round((s.width || 0) / sumRaw * 1e4) / 100);
428
- const cells = subs.map((sc, i) => {
429
- const inner = (sc.blocks ?? []).map((b) => blockToRawHtml(b)).join("\n");
430
- const valign = sc.verticalAlign ?? "top";
431
- const hasPadding = sc.paddingTop || sc.paddingRight || sc.paddingBottom || sc.paddingLeft;
432
- const padding = hasPadding ? `padding:${sc.paddingTop || "0"} ${sc.paddingRight || "0"} ${sc.paddingBottom || "0"} ${sc.paddingLeft || "0"};` : "padding:10px;";
433
- const bg = sc.backgroundColor ? `background-color:${sc.backgroundColor};` : "";
434
- return `<td class="ee-sub-col" width="${widths[i]}%" valign="${valign}" style="${padding}${bg}">${inner}</td>`;
435
- }).join("");
436
- const colAttrs = [];
437
- if (column.width) colAttrs.push(`width="${column.width}%"`);
438
- if (column.backgroundColor) colAttrs.push(`background-color="${column.backgroundColor}"`);
439
- if (column.verticalAlign) colAttrs.push(`vertical-align="${column.verticalAlign}"`);
440
- if (column.padding) {
441
- const padding = spacingToString(column.padding);
442
- if (padding) colAttrs.push(`padding="${padding}"`);
443
- }
444
- return `
445
- <mj-column ${finishAttributes(colAttrs, ["el-column", `el-${column.id}`], column.extraAttributes)}>
446
- <mj-raw>
447
- <table role="presentation" class="ee-sub-cols" ${EDITOR_DATA_ATTRIBUTE.subColumns}="${encodeEditorData(subs)}" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;">
448
- <tr>${cells}</tr>
449
- </table>
450
- </mj-raw>
451
- </mj-column>
452
- `.trim();
453
- }
454
- /**
455
- * Convert block to MJML based on type
456
- */
457
- blockToMJML(block) {
458
- if (block.hidden) return "";
459
- switch (block.type) {
460
- case "text":
461
- return this.textBlockToMJML(block);
462
- case "image":
463
- return this.imageBlockToMJML(block);
464
- case "button":
465
- return this.buttonBlockToMJML(block);
466
- case "divider":
467
- return this.dividerBlockToMJML(block);
468
- case "spacer":
469
- return this.spacerBlockToMJML(block);
470
- case "social":
471
- return this.socialBlockToMJML(block);
472
- case "hero":
473
- return this.heroBlockToMJML(block);
474
- case "accordion":
475
- return this.accordionBlockToMJML(block);
476
- case "raw":
477
- return this.rawBlockToMJML(block);
478
- case "navbar":
479
- return this.navbarBlockToMJML(block);
480
- case "carousel":
481
- return this.carouselBlockToMJML(block);
482
- case "table":
483
- return this.tableBlockToMJML(block);
484
- case "header":
485
- return this.headerBlockToMJML();
486
- case "footer":
487
- return this.footerBlockToMJML();
488
- default:
489
- console.warn(`Unknown block type: ${block.type}`);
490
- return "";
491
- }
492
- }
493
- /**
494
- * Convert text block to MJML
495
- *
496
- * Note: MJML applies styles to the container <td>, but TipTap content
497
- * (wrapped in <p> tags) doesn't inherit these styles. We solve this by
498
- * wrapping the content in a <div> with explicit inline styles.
499
- */
500
- textBlockToMJML(block) {
501
- const attrs = [];
502
- const inlineStyles = [];
503
- if (block.align) {
504
- attrs.push(`align="${block.align}"`);
505
- inlineStyles.push(`text-align:${block.align}`);
506
- }
507
- if (block.color) {
508
- attrs.push(`color="${block.color}"`);
509
- inlineStyles.push(`color:${block.color}`);
510
- }
511
- if (block.fontSize) {
512
- attrs.push(`font-size="${block.fontSize}"`);
513
- inlineStyles.push(`font-size:${block.fontSize}`);
514
- }
515
- if (block.fontFamily) {
516
- attrs.push(`font-family="${block.fontFamily}"`);
517
- inlineStyles.push(`font-family:${block.fontFamily}`);
518
- }
519
- if (block.lineHeight) {
520
- attrs.push(`line-height="${block.lineHeight}"`);
521
- inlineStyles.push(`line-height:${block.lineHeight}`);
522
- }
523
- if (block.padding) {
524
- const padding = spacingToString(block.padding);
525
- if (padding) attrs.push(`padding="${padding}"`);
526
- }
527
- const styledContent = inlineStyles.length > 0 ? `<div style="${inlineStyles.join(";")}">${block.content}</div>` : block.content;
528
- return `<mj-text ${finishAttributes(attrs, ["el-text", `el-${block.id}`], block.extraAttributes)}>${styledContent}</mj-text>`;
529
- }
530
- /**
531
- * Convert image block to MJML
532
- */
533
- imageBlockToMJML(block) {
534
- const attrs = [`src="${block.src}"`];
535
- if (block.alt) attrs.push(`alt="${block.alt}"`);
536
- if (block.width) attrs.push(`width="${block.width}"`);
537
- if (block.height) attrs.push(`height="${block.height}"`);
538
- if (block.align) attrs.push(`align="${block.align}"`);
539
- if (block.href) attrs.push(`href="${block.href}"`);
540
- if (block.borderRadius) attrs.push(`border-radius="${block.borderRadius}"`);
541
- if (block.padding) {
542
- const padding = spacingToString(block.padding);
543
- if (padding) attrs.push(`padding="${padding}"`);
544
- }
545
- return `<mj-image ${finishAttributes(attrs, ["el-image", `el-${block.id}`], block.extraAttributes)} />`;
546
- }
547
- /**
548
- * Convert button block to MJML
549
- */
550
- buttonBlockToMJML(block) {
551
- const attrs = [`href="${block.href}"`];
552
- if (block.align) attrs.push(`align="${block.align}"`);
553
- if (block.backgroundColor) attrs.push(`background-color="${block.backgroundColor}"`);
554
- if (block.color) attrs.push(`color="${block.color}"`);
555
- if (block.borderRadius) attrs.push(`border-radius="${block.borderRadius}"`);
556
- if (block.border) attrs.push(`border="${block.border}"`);
557
- if (block.innerPadding) attrs.push(`inner-padding="${block.innerPadding}"`);
558
- if (block.padding) {
559
- const padding = spacingToString(block.padding);
560
- if (padding) attrs.push(`padding="${padding}"`);
561
- }
562
- return `<mj-button ${finishAttributes(attrs, ["el-button", `el-${block.id}`], block.extraAttributes)}>${block.label}</mj-button>`;
563
- }
564
- /**
565
- * Convert divider block to MJML
566
- */
567
- dividerBlockToMJML(block) {
568
- const attrs = [];
569
- if (block.borderColor) attrs.push(`border-color="${block.borderColor}"`);
570
- if (block.borderWidth) attrs.push(`border-width="${block.borderWidth}"`);
571
- if (block.borderStyle) attrs.push(`border-style="${block.borderStyle}"`);
572
- if (block.width) attrs.push(`width="${block.width}"`);
573
- if (block.padding) {
574
- const padding = spacingToString(block.padding);
575
- if (padding) attrs.push(`padding="${padding}"`);
576
- }
577
- return `<mj-divider ${finishAttributes(attrs, ["el-divider", `el-${block.id}`], block.extraAttributes)} />`;
578
- }
579
- /**
580
- * Convert spacer block to MJML
581
- */
582
- spacerBlockToMJML(block) {
583
- return `<mj-spacer ${finishAttributes([`height="${block.height}"`], ["el-spacer", `el-${block.id}`], block.extraAttributes)} />`;
584
- }
585
- /**
586
- * Get white SVG icon as data URI for a platform
587
- * Uses clean, minimal SVGs optimized for email
588
- */
589
- getSocialIconDataUri(platform) {
590
- const svgIcons = {
591
- facebook: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>',
592
- twitter: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"><path d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z"/></svg>',
593
- instagram: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"><path d="M12 0C8.74 0 8.333.015 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.74 0 12s.015 3.667.072 4.947c.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.74 24 12 24s3.667-.015 4.947-.072c4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>',
594
- linkedin: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>',
595
- youtube: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"><path d="M23.498 6.186a3.016 3.016 0 00-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 00.502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 002.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 002.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>',
596
- pinterest: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"><path d="M12 0C5.373 0 0 5.372 0 12c0 5.084 3.163 9.426 7.627 11.174-.105-.949-.2-2.405.042-3.441.218-.937 1.407-5.965 1.407-5.965s-.359-.719-.359-1.782c0-1.668.967-2.914 2.171-2.914 1.023 0 1.518.769 1.518 1.69 0 1.029-.655 2.568-.994 3.995-.283 1.194.599 2.169 1.777 2.169 2.133 0 3.772-2.249 3.772-5.495 0-2.873-2.064-4.882-5.012-4.882-3.414 0-5.418 2.561-5.418 5.207 0 1.031.397 2.138.893 2.738a.36.36 0 01.083.345c-.091.378-.293 1.194-.333 1.361-.052.218-.174.265-.402.159-1.495-.696-2.428-2.882-2.428-4.64 0-3.779 2.744-7.253 7.917-7.253 4.158 0 7.389 2.963 7.389 6.923 0 4.13-2.607 7.461-6.229 7.461-1.217 0-2.36-.632-2.75-1.378l-.748 2.853c-.271 1.043-1.002 2.35-1.492 3.146C9.57 23.812 10.763 24 12 24c6.627 0 12-5.373 12-12S18.627 0 12 0z"/></svg>',
597
- github: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>'
598
- };
599
- const svg = svgIcons[platform] || svgIcons.facebook;
600
- return `data:image/svg+xml;base64,${Buffer.from(svg).toString("base64")}`;
601
- }
602
- /**
603
- * Convert social block to MJML using raw HTML table for reliable horizontal layout
604
- * Uses styled anchor tags with background colors for full width control
605
- */
606
- socialBlockToMJML(block) {
607
- const iconSize = block.iconSize || "40px";
608
- const iconPadding = block.iconPadding || "8px";
609
- const borderRadius = block.borderRadius || "999px";
610
- const align = block.align || "center";
611
- const isVertical = block.mode === "vertical";
612
- const sizeNum = parseInt(iconSize, 10) || 40;
613
- const innerIconSize = Math.round(sizeNum * 0.5);
614
- const paddingNum = parseInt(iconPadding, 10) || 8;
615
- const getLinkColor = (link) => {
616
- return link.color || this.SOCIAL_COLORS[link.platform] || "#666666";
617
- };
618
- if (isVertical) {
619
- const verticalButtons = block.links.map((link) => {
620
- const bgColor = getLinkColor(link);
621
- const iconUrl = this.getSocialIconDataUri(link.platform);
622
- return `<mj-button
623
- href="${link.url}"
624
- background-color="${bgColor}"
625
- border-radius="${borderRadius}"
626
- width="${iconSize}"
627
- height="${iconSize}"
628
- padding="${iconPadding} 0"
629
- inner-padding="0"
630
- align="${align}"
631
- css-class="el-social-btn el-${block.id}-${link.platform}"
632
- ><img src="${iconUrl}" alt="${link.platform}" width="${innerIconSize}" height="${innerIconSize}" style="display:block;margin:auto;" /></mj-button>`;
633
- }).join("\n");
634
- return `<!-- Social Icons (vertical) -->
635
- ${verticalButtons}`;
636
- }
637
- const alignMargin = align === "center" ? "0 auto" : align === "right" ? "0 0 0 auto" : "0 auto 0 0";
638
- const socialCells = block.links.map((link, index) => {
639
- const bgColor = getLinkColor(link);
640
- const iconUrl = this.getSocialIconDataUri(link.platform);
641
- const isLast = index === block.links.length - 1;
642
- const cellPadding = isLast ? "0" : `0 ${paddingNum}px 0 0`;
643
- const paddingForCentering = Math.round((sizeNum - innerIconSize) / 2);
644
- return `<td style="padding: ${cellPadding};">
645
- <a href="${link.url}" target="_blank" style="display: block; width: ${iconSize}; height: ${iconSize}; background-color: ${bgColor}; border-radius: ${borderRadius}; text-decoration: none;">
646
- <img src="${iconUrl}" alt="${link.platform}" width="${innerIconSize}" height="${innerIconSize}" style="display: block; margin: ${paddingForCentering}px auto 0 auto;" />
647
- </a>
648
- </td>`;
649
- }).join("\n ");
650
- return `<!-- Social Icons (horizontal) -->
651
- <mj-raw>
652
- <table align="${align}" role="presentation" cellpadding="0" cellspacing="0" style="margin: ${alignMargin};" ${EDITOR_DATA_ATTRIBUTE.social}="${encodeEditorData(block)}">
653
- <tr>
654
- ${socialCells}
655
- </tr>
656
- </table>
657
- </mj-raw>`;
658
- }
659
- /**
660
- * Convert hero block to MJML
661
- */
662
- heroBlockToMJML(block) {
663
- const attrs = [`background-url="${block.backgroundImage}"`];
664
- if (block.backgroundHeight) attrs.push(`background-height="${block.backgroundHeight}"`);
665
- if (block.backgroundWidth) attrs.push(`background-width="${block.backgroundWidth}"`);
666
- if (block.backgroundColor) attrs.push(`background-color="${block.backgroundColor}"`);
667
- if (block.verticalAlign) attrs.push(`vertical-align="${block.verticalAlign}"`);
668
- if (block.mode) attrs.push(`mode="${block.mode}"`);
669
- return `
670
- <mj-hero ${finishAttributes(attrs, ["el-hero", `el-${block.id}`], block.extraAttributes)}>
671
- <mj-text align="center" color="#ffffff" font-size="32px" font-weight="bold">
672
- Hero Title
673
- </mj-text>
674
- <mj-text align="center" color="#ffffff" font-size="16px">
675
- Add your hero content here
676
- </mj-text>
677
- <mj-button href="#" background-color="#ffffff" color="#944923">
678
- Call to Action
679
- </mj-button>
680
- </mj-hero>
681
- `.trim();
682
- }
683
- /**
684
- * Convert accordion block to MJML
685
- */
686
- accordionBlockToMJML(block) {
687
- const attrs = [];
688
- if (block.iconPosition) attrs.push(`icon-position="${block.iconPosition}"`);
689
- if (block.borderColor) attrs.push(`border="${block.borderColor}"`);
690
- if (block.fontFamily) attrs.push(`font-family="${block.fontFamily}"`);
691
- const items = block.items.map(
692
- (item) => `
693
- <mj-accordion-element>
694
- <mj-accordion-title>${item.title}</mj-accordion-title>
695
- <mj-accordion-text>${item.content}</mj-accordion-text>
696
- </mj-accordion-element>`
697
- ).join("\n");
698
- return `
699
- <mj-accordion ${finishAttributes(attrs, ["el-accordion", `el-${block.id}`], block.extraAttributes)}>
700
- ${items}
701
- </mj-accordion>
702
- `.trim();
703
- }
704
- /**
705
- * Convert raw HTML block to MJML
706
- */
707
- rawBlockToMJML(block) {
708
- return `<mj-raw css-class="el-raw el-${block.id}">${block.html}</mj-raw>`;
709
- }
710
- /**
711
- * Convert navbar block to MJML
712
- */
713
- navbarBlockToMJML(block) {
714
- const attrs = [];
715
- if (block.hamburger) attrs.push('hamburger="hamburger"');
716
- if (block.baseUrl) attrs.push(`base-url="${block.baseUrl}"`);
717
- if (block.align) attrs.push(`align="${block.align}"`);
718
- if (block.icoColor) attrs.push(`ico-color="${block.icoColor}"`);
719
- if (block.padding) {
720
- const padding = spacingToString(block.padding);
721
- if (padding) attrs.push(`padding="${padding}"`);
722
- }
723
- const links = block.links.map((link) => {
724
- const linkAttrs = [`href="${link.href}"`];
725
- if (link.color) linkAttrs.push(`color="${link.color}"`);
726
- return ` <mj-navbar-link ${linkAttrs.join(" ")}>${link.label}</mj-navbar-link>`;
727
- }).join("\n");
728
- return `
729
- <mj-navbar ${finishAttributes(attrs, ["el-navbar", `el-${block.id}`], block.extraAttributes)}>
730
- ${links}
731
- </mj-navbar>
732
- `.trim();
733
- }
734
- /**
735
- * Convert carousel block to MJML
736
- */
737
- carouselBlockToMJML(block) {
738
- const attrs = [];
739
- if (block.thumbnails) attrs.push(`thumbnails="${block.thumbnails}"`);
740
- if (block.borderRadius) attrs.push(`border-radius="${block.borderRadius}"`);
741
- if (block.iconWidth) attrs.push(`icon-width="${block.iconWidth}"`);
742
- if (block.tbBorderRadius) attrs.push(`tb-border-radius="${block.tbBorderRadius}"`);
743
- if (block.padding) {
744
- const padding = spacingToString(block.padding);
745
- if (padding) attrs.push(`padding="${padding}"`);
746
- }
747
- const images = block.images.map((img) => {
748
- const imgAttrs = [`src="${img.src}"`];
749
- if (img.alt) imgAttrs.push(`alt="${img.alt}"`);
750
- if (img.href) imgAttrs.push(`href="${img.href}"`);
751
- if (img.thumbnailSrc) imgAttrs.push(`thumbnails-src="${img.thumbnailSrc}"`);
752
- return ` <mj-carousel-image ${imgAttrs.join(" ")} />`;
753
- }).join("\n");
754
- return `
755
- <mj-carousel ${finishAttributes(attrs, ["el-carousel", `el-${block.id}`], block.extraAttributes)}>
756
- ${images}
757
- </mj-carousel>
758
- `.trim();
759
- }
760
- /**
761
- * Convert table block to MJML
762
- */
763
- tableBlockToMJML(block) {
764
- const attrs = [];
765
- if (block.align) attrs.push(`align="${block.align}"`);
766
- if (block.color) attrs.push(`color="${block.color}"`);
767
- if (block.fontFamily) attrs.push(`font-family="${block.fontFamily}"`);
768
- if (block.fontSize) attrs.push(`font-size="${block.fontSize}"`);
769
- if (block.cellpadding) attrs.push(`cellpadding="${block.cellpadding}"`);
770
- if (block.cellspacing) attrs.push(`cellspacing="${block.cellspacing}"`);
771
- if (block.border) attrs.push(`border="${block.border}"`);
772
- if (block.padding) {
773
- const padding = spacingToString(block.padding);
774
- if (padding) attrs.push(`padding="${padding}"`);
775
- }
776
- const headerCells = block.headers.map((h) => `<th style="padding: 8px; border-bottom: 1px solid #ddd; text-align: left;">${h}</th>`).join("");
777
- const headerRow = `<tr style="background-color: #f5f5f5;">${headerCells}</tr>`;
778
- const dataRows = block.rows.map((row) => {
779
- const cells = row.map((cell) => `<td style="padding: 8px; border-bottom: 1px solid #eee;">${cell}</td>`).join("");
780
- return `<tr>${cells}</tr>`;
781
- }).join("\n");
782
- return `
783
- <mj-table ${finishAttributes(attrs, ["el-table", `el-${block.id}`], block.extraAttributes)}>
784
- ${headerRow}
785
- ${dataRows}
786
- </mj-table>
787
- `.trim();
788
- }
789
- /**
790
- * Generate the locked header block.
791
- *
792
- * It used to render one client's company name and a logo from an image host;
793
- * both are gone. What is left is a placeholder image slot. Keep this in step
794
- * with `headerBlockDefinition` in `packages/blocks/src/branded/index.ts`:
795
- * which of the two runs depends on how the registry is configured.
796
- *
797
- * The placeholder is a `data:` URI rather than an address on an image host,
798
- * so a workspace whose `asset_policy` is `service_only` can still send a mail
799
- * that contains this block.
800
- */
801
- headerBlockToMJML() {
802
- return `
803
- <mj-wrapper background-color="#ffffff" padding="20px" css-class="el-header-wrapper">
804
- <mj-section css-class="el-header-section">
805
- <mj-column css-class="el-header-column">
806
- <!-- Nothing: see headerBlockDefinition in packages/blocks. -->
807
- <mj-spacer height="1px" css-class="el-header-spacer" />
808
- </mj-column>
809
- </mj-section>
810
- </mj-wrapper>
811
- `.trim();
812
- }
813
- /**
814
- * Generate the locked footer block: the unsubscribe link and nothing else.
815
- *
816
- * It used to carry a copyright line naming one client's company. A locked
817
- * block carries no props, so whoever sends the mail cannot correct a name
818
- * that is not theirs; the line is gone rather than replaced with a fake one.
819
- * Keep this in step with `footerBlockDefinition` in
820
- * `packages/blocks/src/branded/index.ts`.
821
- */
822
- footerBlockToMJML() {
823
- return `
824
- <mj-wrapper background-color="#f5f5f5" padding="20px" css-class="el-footer-wrapper">
825
- <mj-section css-class="el-footer-section">
826
- <mj-column css-class="el-footer-column">
827
- <mj-text align="center" font-size="12px" color="#666666" css-class="el-footer-text-2">
828
- <a href="{{unsubscribe_url}}" style="color: #374151; text-decoration: underline;">Unsubscribe</a>
829
- </mj-text>
830
- </mj-column>
831
- </mj-section>
832
- </mj-wrapper>
833
- `.trim();
834
- }
835
42
  };
836
43
  function createMJMLCompiler() {
837
44
  return new MJMLCompiler();
@@ -852,10 +59,10 @@ var MJMLExporter = class {
852
59
  /**
853
60
  * Export template to MJML + HTML
854
61
  */
855
- export(template) {
62
+ async export(template) {
856
63
  const mjml = this.exportMJML(template);
857
64
  try {
858
- const result = mjml2html2(mjml, {
65
+ const result = await mjml2html2(mjml, {
859
66
  // Never resolve <mj-include>: see MJMLCompiler.compile.
860
67
  ignoreIncludes: true,
861
68
  validationLevel: this.options.validationLevel,
@@ -1141,6 +348,12 @@ function decodeEditorData(value) {
1141
348
  return null;
1142
349
  }
1143
350
  }
351
+ function subtractPx(a, b) {
352
+ const px = (v) => v === "0" ? 0 : /^-?\d+(\.\d+)?px$/.test(v) ? Number.parseFloat(v) : NaN;
353
+ const diff = px(a) - px(b);
354
+ if (Number.isNaN(diff) || diff < 0) return void 0;
355
+ return diff === 0 ? "0" : `${Number.isInteger(diff) ? diff : Number(diff.toFixed(2))}px`;
356
+ }
1144
357
  function parsePadding(value) {
1145
358
  const parts = value.trim().split(/\s+/);
1146
359
  if (parts.length < 1 || parts.length > 4 || parts.some((p) => !/^-?[0-9.]+(px|%|em|rem)?$/.test(p))) return null;
@@ -1447,6 +660,24 @@ var Importer = class {
1447
660
  if (typeof node.content === "string" && node.content.trim()) {
1448
661
  this.warn("stray_text", "info", path, node, "Text directly in mj-wrapper is ignored by MJML, and by the import.");
1449
662
  }
663
+ if (wrapper.gap) {
664
+ let first = true;
665
+ for (const section of wrapper.sections) {
666
+ if (section.bodyRaw) continue;
667
+ if (first) {
668
+ first = false;
669
+ continue;
670
+ }
671
+ const top = section.padding?.top;
672
+ if (!top) continue;
673
+ const without = subtractPx(top, wrapper.gap);
674
+ if (without === void 0) continue;
675
+ const padding = { ...section.padding, top: without };
676
+ const isDefault = padding.top === "20px" && (padding.right ?? "0") === "0" && padding.bottom === "20px" && (padding.left ?? "0") === "0";
677
+ if (isDefault) delete section.padding;
678
+ else section.padding = padding;
679
+ }
680
+ }
1450
681
  return wrapper;
1451
682
  }
1452
683
  section(node, path) {
@@ -1843,7 +1074,7 @@ var Importer = class {
1843
1074
  return block;
1844
1075
  }
1845
1076
  // Fallbacks: compile the whole source once, with each fallback marked, and cut them out.
1846
- resolveFallbacks(metadata) {
1077
+ async resolveFallbacks(metadata) {
1847
1078
  if (this.fallbacks.length === 0) return;
1848
1079
  const marker = (i, edge) => ({
1849
1080
  tagName: "mj-raw",
@@ -1857,7 +1088,7 @@ var Importer = class {
1857
1088
  }
1858
1089
  let html = "";
1859
1090
  try {
1860
- html = mjml2html3(serialize(this.root), { ignoreIncludes: true, validationLevel: "skip", minify: false, keepComments: true }).html;
1091
+ html = (await mjml2html3(serialize(this.root), { ignoreIncludes: true, validationLevel: "skip", minify: false, keepComments: true })).html;
1861
1092
  } catch {
1862
1093
  html = "";
1863
1094
  }
@@ -1921,7 +1152,7 @@ function supportingCss(fullHtml, fragments, breakpoint = "480px") {
1921
1152
  }
1922
1153
  return out.join("\n");
1923
1154
  }
1924
- function importMjml(source) {
1155
+ async function importMjml(source) {
1925
1156
  if (typeof source !== "string") throw new MjmlImportError("not_mjml", "The MJML must be a string.");
1926
1157
  scanMjml(source, ENDING_TAGS);
1927
1158
  const root = parseMjml(source);
@@ -1937,7 +1168,7 @@ function importMjml(source) {
1937
1168
  }
1938
1169
  const metadata = importer.head(head, body);
1939
1170
  const sections = importer.body(body);
1940
- importer.resolveFallbacks(metadata);
1171
+ await importer.resolveFallbacks(metadata);
1941
1172
  importer.summarize();
1942
1173
  const document = { version: CURRENT_TEMPLATE_VERSION, metadata, sections };
1943
1174
  try {
@@ -1951,16 +1182,23 @@ function importMjml(source) {
1951
1182
  export {
1952
1183
  DEFAULT_MJML_ATTRIBUTES,
1953
1184
  EDITOR_DATA_ATTRIBUTE,
1185
+ EDITOR_MARKER,
1954
1186
  MAX_MJML_BYTES,
1955
1187
  MAX_MJML_DEPTH,
1956
1188
  MAX_MJML_ELEMENTS,
1957
1189
  MJMLCompiler,
1958
1190
  MJMLExporter,
1191
+ MjmlBuilder,
1959
1192
  MjmlImportError,
1193
+ addPx,
1194
+ base64Utf8,
1960
1195
  createMJMLCompiler,
1961
1196
  createMJMLExporter,
1962
1197
  encodeEditorData,
1963
1198
  exportTemplate,
1964
1199
  importMjml,
1965
- isMjmlImportError
1200
+ isMjmlImportError,
1201
+ mjmlOptions,
1202
+ paddingWithGap,
1203
+ stripEditorMarkup
1966
1204
  };