@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.js CHANGED
@@ -32,18 +32,25 @@ var server_exports = {};
32
32
  __export(server_exports, {
33
33
  DEFAULT_MJML_ATTRIBUTES: () => DEFAULT_MJML_ATTRIBUTES,
34
34
  EDITOR_DATA_ATTRIBUTE: () => EDITOR_DATA_ATTRIBUTE,
35
+ EDITOR_MARKER: () => EDITOR_MARKER,
35
36
  MAX_MJML_BYTES: () => MAX_MJML_BYTES,
36
37
  MAX_MJML_DEPTH: () => MAX_MJML_DEPTH,
37
38
  MAX_MJML_ELEMENTS: () => MAX_MJML_ELEMENTS,
38
39
  MJMLCompiler: () => MJMLCompiler,
39
40
  MJMLExporter: () => MJMLExporter,
41
+ MjmlBuilder: () => MjmlBuilder,
40
42
  MjmlImportError: () => MjmlImportError,
43
+ addPx: () => addPx,
44
+ base64Utf8: () => base64Utf8,
41
45
  createMJMLCompiler: () => createMJMLCompiler,
42
46
  createMJMLExporter: () => createMJMLExporter,
43
47
  encodeEditorData: () => encodeEditorData,
44
48
  exportTemplate: () => exportTemplate,
45
49
  importMjml: () => importMjml,
46
- isMjmlImportError: () => isMjmlImportError
50
+ isMjmlImportError: () => isMjmlImportError,
51
+ mjmlOptions: () => mjmlOptions,
52
+ paddingWithGap: () => paddingWithGap,
53
+ stripEditorMarkup: () => stripEditorMarkup
47
54
  });
48
55
  module.exports = __toCommonJS(server_exports);
49
56
 
@@ -82,6 +89,9 @@ function spacingToCss(p) {
82
89
  const t = p.top ?? "0", r = p.right ?? "0", b = p.bottom ?? "0", l = p.left ?? "0";
83
90
  return `padding:${t} ${r} ${b} ${l};`;
84
91
  }
92
+ function identity(b) {
93
+ return `class="el-${b.type} el-${b.id}"`;
94
+ }
85
95
  function textToHtml(b) {
86
96
  const styles = [];
87
97
  if (b.color) styles.push(`color:${b.color}`);
@@ -91,11 +101,11 @@ function textToHtml(b) {
91
101
  if (b.align) styles.push(`text-align:${b.align}`);
92
102
  styles.push("margin:0");
93
103
  const padding = spacingToCss(b.padding);
94
- return `<div style="${styles.join(";")};${padding}">${b.content}</div>`;
104
+ return `<div ${identity(b)} style="${styles.join(";")};${padding}">${b.content}</div>`;
95
105
  }
96
106
  function imageToHtml(b) {
97
107
  const align = b.align ?? "center";
98
- const wrap = `style="text-align:${align};${spacingToCss(b.padding)}"`;
108
+ const wrap = `${identity(b)} style="text-align:${align};${spacingToCss(b.padding)}"`;
99
109
  const widthAttr = b.width ? ` width="${escapeAttr(b.width)}"` : "";
100
110
  const heightAttr = b.height ? ` height="${escapeAttr(b.height)}"` : "";
101
111
  const radius = b.borderRadius ? `border-radius:${b.borderRadius};` : "";
@@ -110,7 +120,7 @@ function buttonToHtml(b) {
110
120
  const radius = b.borderRadius ?? "3px";
111
121
  const innerPadding = b.innerPadding ?? "10px 25px";
112
122
  const border = b.border ? `border:${b.border};` : "";
113
- return `<div style="text-align:${align};${spacingToCss(b.padding)}">
123
+ return `<div ${identity(b)} style="text-align:${align};${spacingToCss(b.padding)}">
114
124
  <table role="presentation" cellpadding="0" cellspacing="0" border="0" style="border-collapse:separate;line-height:100%;display:inline-block;">
115
125
  <tr>
116
126
  <td align="center" valign="middle" role="presentation" style="background-color:${bg};border-radius:${radius};${border}padding:${innerPadding};">
@@ -125,12 +135,12 @@ function dividerToHtml(b) {
125
135
  const w = b.borderWidth ?? "1px";
126
136
  const style = b.borderStyle ?? "solid";
127
137
  const width = b.width ?? "100%";
128
- return `<div style="${spacingToCss(b.padding)}">
138
+ return `<div ${identity(b)} style="${spacingToCss(b.padding)}">
129
139
  <div style="border-top:${w} ${style} ${color};width:${width};font-size:1px;line-height:1px;">&nbsp;</div>
130
140
  </div>`;
131
141
  }
132
142
  function spacerToHtml(b) {
133
- return `<div style="height:${b.height};line-height:${b.height};font-size:1px;">&nbsp;</div>`;
143
+ return `<div ${identity(b)} style="height:${b.height};line-height:${b.height};font-size:1px;">&nbsp;</div>`;
134
144
  }
135
145
  function socialToHtml(b) {
136
146
  const items = (b.links ?? []).map((link) => {
@@ -139,7 +149,7 @@ function socialToHtml(b) {
139
149
  <img src="${escapeAttr(icon)}" alt="${escapeAttr(link.platform)}" width="24" height="24" style="display:inline-block;border:0;" />
140
150
  </a>`;
141
151
  }).join("");
142
- return `<div style="text-align:center;">${items}</div>`;
152
+ return `<div ${identity(b)} style="text-align:center;">${items}</div>`;
143
153
  }
144
154
  function blockToRawHtml(block) {
145
155
  if (!isLeafBlockType(block.type)) {
@@ -181,7 +191,7 @@ function buildGradientCSS(gradient) {
181
191
  return `linear-gradient(${gradient.angle}deg, ${stopList})`;
182
192
  }
183
193
 
184
- // src/compiler/MJMLCompiler.ts
194
+ // src/compiler/MjmlBuilder.ts
185
195
  var DEFAULT_MJML_ATTRIBUTES = `
186
196
  <mj-all font-family="Georgia, serif" />
187
197
  <mj-text font-size="14px" line-height="1.6" />
@@ -190,8 +200,30 @@ var EDITOR_DATA_ATTRIBUTE = {
190
200
  social: "data-ee-social",
191
201
  subColumns: "data-ee-subcols"
192
202
  };
203
+ function base64Utf8(text) {
204
+ const bytes = new TextEncoder().encode(text);
205
+ let binary = "";
206
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
207
+ return btoa(binary);
208
+ }
209
+ var EDITOR_MARKER = { begin: "<!--ee:begin-->", end: "<!--ee:end-->" };
210
+ function stripEditorMarkup(markup) {
211
+ return markup.replace(/<!--ee:begin-->[\s\S]*?<!--ee:end-->/g, "").replace(/<!--\/?ee:raw:[^>]*-->/g, "");
212
+ }
213
+ function rawMarkers(id, html, editor) {
214
+ return editor ? `<!--ee:raw:${id}-->${html}<!--/ee:raw:${id}-->` : html;
215
+ }
216
+ function textInlineStyles(block) {
217
+ const styles = [];
218
+ if (block.align) styles.push(`text-align:${block.align}`);
219
+ if (block.color) styles.push(`color:${block.color}`);
220
+ if (block.fontSize) styles.push(`font-size:${block.fontSize}`);
221
+ if (block.fontFamily) styles.push(`font-family:${block.fontFamily}`);
222
+ if (block.lineHeight) styles.push(`line-height:${block.lineHeight}`);
223
+ return styles;
224
+ }
193
225
  function encodeEditorData(value) {
194
- return Buffer.from(JSON.stringify(value), "utf8").toString("base64");
226
+ return base64Utf8(JSON.stringify(value));
195
227
  }
196
228
  function finishAttributes(attrs, classes, extra) {
197
229
  const out = [...attrs];
@@ -219,13 +251,37 @@ function spacingToString(spacing) {
219
251
  if (!top && !right && !bottom && !left) return "";
220
252
  return [top || "0", right || "0", bottom || "0", left || "0"].join(" ");
221
253
  }
222
- var MJMLCompiler = class {
254
+ var MJML_SECTION_PADDING = { top: "20px", right: "0", bottom: "20px", left: "0" };
255
+ function addPx(a, b) {
256
+ const px = (v) => v === "0" ? 0 : /^-?\d+(\.\d+)?px$/.test(v) ? Number.parseFloat(v) : NaN;
257
+ const sum = px(a) + px(b);
258
+ if (Number.isNaN(sum)) return void 0;
259
+ return `${Number.isInteger(sum) ? sum : Number(sum.toFixed(2))}px`;
260
+ }
261
+ function paddingWithGap(spacing, gap) {
262
+ const base = spacing ?? MJML_SECTION_PADDING;
263
+ const top = addPx(base.top || "0", gap);
264
+ return top === void 0 ? base : { ...base, top };
265
+ }
266
+ function mjmlOptions(options = {}) {
267
+ return {
268
+ // Never resolve <mj-include>: its path is read from the server's disk,
269
+ // and a stored document (a Raw block's html) must not reach local files.
270
+ ignoreIncludes: true,
271
+ validationLevel: "soft",
272
+ minify: false,
273
+ ...options.webFonts === false ? { fonts: {} } : {}
274
+ };
275
+ }
276
+ var MjmlBuilder = class {
223
277
  constructor() {
224
278
  /**
225
279
  * Set true while emitting a column with sub-columns. Causes generateHead
226
280
  * to inject the responsive media query exactly once per template.
227
281
  */
228
282
  this.needsSubColumnStyles = false;
283
+ /** Set for the duration of one build in editor mode; see {@link BuildOptions}. */
284
+ this.editor = false;
229
285
  /**
230
286
  * Platform brand colors for social icons
231
287
  */
@@ -239,40 +295,25 @@ var MJMLCompiler = class {
239
295
  github: "#181717"
240
296
  };
241
297
  }
242
- /**
243
- * Compile email template to MJML and HTML
244
- */
245
- compile(template, options = {}) {
246
- try {
247
- this.needsSubColumnStyles = false;
248
- const mjml = this.templateToMJML(template);
249
- const result = (0, import_mjml.default)(mjml, {
250
- // Never resolve <mj-include>: its path is read from the server's disk,
251
- // and a stored document (a Raw block's html) must not reach local files.
252
- ignoreIncludes: true,
253
- validationLevel: "soft",
254
- minify: false,
255
- ...options.webFonts === false ? { fonts: {} } : {}
256
- });
257
- return {
258
- mjml,
259
- html: result.html,
260
- errors: result.errors.length > 0 ? result.errors.map((e) => e.formattedMessage) : void 0
261
- };
262
- } catch (error) {
263
- return {
264
- mjml: "",
265
- html: "",
266
- errors: [error instanceof Error ? error.message : "Unknown compilation error"]
267
- };
268
- }
298
+ /** `fragment` fenced as an editor addition, or nothing outside editor mode. */
299
+ ee(fragment) {
300
+ return this.editor ? `${EDITOR_MARKER.begin}${fragment}${EDITOR_MARKER.end}` : "";
301
+ }
302
+ /** The label of a ghost: what the node is, so the person can find and unhide it. */
303
+ ghostText(cssClasses, label) {
304
+ return `<mj-text css-class="ee-ghost ${cssClasses.join(" ")}" padding="8px 25px" font-size="12px" line-height="1.4" color="#6b7280" container-background-color="#f3f4f6"><span style="display:block;border:1px dashed #9ca3af;padding:6px 10px;">${label} (hidden)</span></mj-text>`;
269
305
  }
270
306
  /**
271
307
  * The MJML markup for a document, without compiling it to HTML.
272
308
  */
273
- toMJML(template) {
309
+ toMJML(template, options = {}) {
274
310
  this.needsSubColumnStyles = false;
275
- return this.templateToMJML(template);
311
+ this.editor = options.editor === true;
312
+ try {
313
+ return this.templateToMJML(template);
314
+ } finally {
315
+ this.editor = false;
316
+ }
276
317
  }
277
318
  /**
278
319
  * Convert template to MJML markup
@@ -379,7 +420,9 @@ var MJMLCompiler = class {
379
420
  * renders at standard width, which the inspector explains).
380
421
  */
381
422
  wrapperToMJML(wrapper) {
382
- if (wrapper.hidden) return "";
423
+ if (wrapper.hidden) {
424
+ return this.ee(`<mj-wrapper css-class="ee-ghost el-wrapper el-${wrapper.id}" padding="0"><mj-section padding="0"><mj-column>${this.ghostText([], "Container")}</mj-column></mj-section></mj-wrapper>`);
425
+ }
383
426
  const attrs = [];
384
427
  const cssClasses = ["el-wrapper", `el-${wrapper.id}`];
385
428
  this.pushBackground(attrs, cssClasses, wrapper);
@@ -395,7 +438,13 @@ var MJMLCompiler = class {
395
438
  if (wrapper.gap) attrs.push(`gap="${quoted(wrapper.gap)}"`);
396
439
  if (wrapper.textAlign) attrs.push(`text-align="${quoted(wrapper.textAlign)}"`);
397
440
  for (const c of (wrapper.cssClass ?? "").split(/\s+/)) if (c && !cssClasses.includes(quoted(c))) cssClasses.push(quoted(c));
398
- const sections = wrapper.sections.map((section) => this.sectionToMJML(section)).filter(Boolean).join("\n");
441
+ let first = true;
442
+ const sections = wrapper.sections.map((section) => {
443
+ if (section.hidden) return this.sectionToMJML(section);
444
+ const out = this.sectionToMJML(section, first ? void 0 : wrapper.gap);
445
+ first = false;
446
+ return out;
447
+ }).filter(Boolean).join("\n");
399
448
  return `
400
449
  <mj-wrapper ${finishAttributes(attrs, cssClasses, wrapper.extraAttributes)}>
401
450
  ${sections}
@@ -420,12 +469,14 @@ var MJMLCompiler = class {
420
469
  * Convert section to MJML
421
470
  * Supports background images, full-width, and mj-group for non-stacking columns
422
471
  */
423
- sectionToMJML(section) {
424
- if (section.hidden) return "";
472
+ sectionToMJML(section, gapAbove) {
473
+ if (section.hidden) {
474
+ return this.ee(`<mj-section css-class="ee-ghost el-section el-${section.id}" padding="0"><mj-column>${this.ghostText([], "Section")}</mj-column></mj-section>`);
475
+ }
425
476
  if (section.bodyRaw) {
426
477
  const blocks = section.columns.flatMap((c) => c.blocks).filter((b) => !b.hidden);
427
478
  if (blocks.every((b) => b.type === "raw") && section.columns.every((c) => !c.subColumns?.length)) {
428
- return blocks.map((b) => `<mj-raw>${b.html}</mj-raw>`).join("\n");
479
+ return blocks.map((b) => `<mj-raw>${rawMarkers(b.id, b.html, this.editor)}</mj-raw>`).join("\n");
429
480
  }
430
481
  }
431
482
  const attrs = [];
@@ -434,8 +485,9 @@ var MJMLCompiler = class {
434
485
  if (section.fullWidth) {
435
486
  attrs.push('full-width="full-width"');
436
487
  }
437
- if (section.padding) {
438
- const padding = spacingToString(section.padding);
488
+ const sectionPadding = gapAbove ? paddingWithGap(section.padding, gapAbove) : section.padding;
489
+ if (sectionPadding) {
490
+ const padding = spacingToString(sectionPadding);
439
491
  if (padding) attrs.push(`padding="${padding}"`);
440
492
  }
441
493
  const columns = section.columns.map((col) => this.columnToMJML(col)).join("\n");
@@ -485,9 +537,10 @@ var MJMLCompiler = class {
485
537
  if (padding) attrs.push(`padding="${padding}"`);
486
538
  }
487
539
  const blocks = column.blocks.map((block) => this.blockToMJML(block)).join("\n");
540
+ const floor = column.blocks.length === 0 ? this.ee('<mj-spacer height="48px" css-class="ee-ghost ee-empty" />') : "";
488
541
  return `
489
542
  <mj-column ${finishAttributes(attrs, cssClasses, column.extraAttributes)}>
490
- ${blocks}
543
+ ${blocks}${floor}
491
544
  </mj-column>
492
545
  `.trim();
493
546
  }
@@ -511,7 +564,7 @@ var MJMLCompiler = class {
511
564
  const hasPadding = sc.paddingTop || sc.paddingRight || sc.paddingBottom || sc.paddingLeft;
512
565
  const padding = hasPadding ? `padding:${sc.paddingTop || "0"} ${sc.paddingRight || "0"} ${sc.paddingBottom || "0"} ${sc.paddingLeft || "0"};` : "padding:10px;";
513
566
  const bg = sc.backgroundColor ? `background-color:${sc.backgroundColor};` : "";
514
- return `<td class="ee-sub-col" width="${widths[i]}%" valign="${valign}" style="${padding}${bg}">${inner}</td>`;
567
+ return `<td class="ee-sub-col el-subcolumn el-${sc.id}" width="${widths[i]}%" valign="${valign}" style="${padding}${bg}">${inner}</td>`;
515
568
  }).join("");
516
569
  const colAttrs = [];
517
570
  if (column.width) colAttrs.push(`width="${column.width}%"`);
@@ -535,7 +588,7 @@ var MJMLCompiler = class {
535
588
  * Convert block to MJML based on type
536
589
  */
537
590
  blockToMJML(block) {
538
- if (block.hidden) return "";
591
+ if (block.hidden) return this.ee(this.ghostText([`el-${block.type}`, `el-${block.id}`], block.type.charAt(0).toUpperCase() + block.type.slice(1)));
539
592
  switch (block.type) {
540
593
  case "text":
541
594
  return this.textBlockToMJML(block);
@@ -579,27 +632,12 @@ var MJMLCompiler = class {
579
632
  */
580
633
  textBlockToMJML(block) {
581
634
  const attrs = [];
582
- const inlineStyles = [];
583
- if (block.align) {
584
- attrs.push(`align="${block.align}"`);
585
- inlineStyles.push(`text-align:${block.align}`);
586
- }
587
- if (block.color) {
588
- attrs.push(`color="${block.color}"`);
589
- inlineStyles.push(`color:${block.color}`);
590
- }
591
- if (block.fontSize) {
592
- attrs.push(`font-size="${block.fontSize}"`);
593
- inlineStyles.push(`font-size:${block.fontSize}`);
594
- }
595
- if (block.fontFamily) {
596
- attrs.push(`font-family="${block.fontFamily}"`);
597
- inlineStyles.push(`font-family:${block.fontFamily}`);
598
- }
599
- if (block.lineHeight) {
600
- attrs.push(`line-height="${block.lineHeight}"`);
601
- inlineStyles.push(`line-height:${block.lineHeight}`);
602
- }
635
+ const inlineStyles = textInlineStyles(block);
636
+ if (block.align) attrs.push(`align="${block.align}"`);
637
+ if (block.color) attrs.push(`color="${block.color}"`);
638
+ if (block.fontSize) attrs.push(`font-size="${block.fontSize}"`);
639
+ if (block.fontFamily) attrs.push(`font-family="${block.fontFamily}"`);
640
+ if (block.lineHeight) attrs.push(`line-height="${block.lineHeight}"`);
603
641
  if (block.padding) {
604
642
  const padding = spacingToString(block.padding);
605
643
  if (padding) attrs.push(`padding="${padding}"`);
@@ -677,7 +715,7 @@ var MJMLCompiler = class {
677
715
  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>'
678
716
  };
679
717
  const svg = svgIcons[platform] || svgIcons.facebook;
680
- return `data:image/svg+xml;base64,${Buffer.from(svg).toString("base64")}`;
718
+ return `data:image/svg+xml;base64,${base64Utf8(svg)}`;
681
719
  }
682
720
  /**
683
721
  * Convert social block to MJML using raw HTML table for reliable horizontal layout
@@ -785,7 +823,7 @@ ${items}
785
823
  * Convert raw HTML block to MJML
786
824
  */
787
825
  rawBlockToMJML(block) {
788
- return `<mj-raw css-class="el-raw el-${block.id}">${block.html}</mj-raw>`;
826
+ return `<mj-raw css-class="el-raw el-${block.id}">${rawMarkers(block.id, block.html, this.editor)}</mj-raw>`;
789
827
  }
790
828
  /**
791
829
  * Convert navbar block to MJML
@@ -913,6 +951,30 @@ ${dataRows}
913
951
  `.trim();
914
952
  }
915
953
  };
954
+
955
+ // src/compiler/MJMLCompiler.ts
956
+ var MJMLCompiler = class extends MjmlBuilder {
957
+ /**
958
+ * Compile email template to MJML and HTML
959
+ */
960
+ async compile(template, options = {}) {
961
+ try {
962
+ const mjml = this.toMJML(template, { editor: options.editor });
963
+ const result = await (0, import_mjml.default)(mjml, mjmlOptions(options));
964
+ return {
965
+ mjml,
966
+ html: result.html,
967
+ errors: result.errors.length > 0 ? result.errors.map((e) => e.formattedMessage) : void 0
968
+ };
969
+ } catch (error) {
970
+ return {
971
+ mjml: "",
972
+ html: "",
973
+ errors: [error instanceof Error ? error.message : "Unknown compilation error"]
974
+ };
975
+ }
976
+ }
977
+ };
916
978
  function createMJMLCompiler() {
917
979
  return new MJMLCompiler();
918
980
  }
@@ -932,10 +994,10 @@ var MJMLExporter = class {
932
994
  /**
933
995
  * Export template to MJML + HTML
934
996
  */
935
- export(template) {
997
+ async export(template) {
936
998
  const mjml = this.exportMJML(template);
937
999
  try {
938
- const result = (0, import_mjml2.default)(mjml, {
1000
+ const result = await (0, import_mjml2.default)(mjml, {
939
1001
  // Never resolve <mj-include>: see MJMLCompiler.compile.
940
1002
  ignoreIncludes: true,
941
1003
  validationLevel: this.options.validationLevel,
@@ -1666,6 +1728,12 @@ function decodeEditorData(value) {
1666
1728
  return null;
1667
1729
  }
1668
1730
  }
1731
+ function subtractPx(a, b) {
1732
+ const px = (v) => v === "0" ? 0 : /^-?\d+(\.\d+)?px$/.test(v) ? Number.parseFloat(v) : NaN;
1733
+ const diff = px(a) - px(b);
1734
+ if (Number.isNaN(diff) || diff < 0) return void 0;
1735
+ return diff === 0 ? "0" : `${Number.isInteger(diff) ? diff : Number(diff.toFixed(2))}px`;
1736
+ }
1669
1737
  function parsePadding(value) {
1670
1738
  const parts = value.trim().split(/\s+/);
1671
1739
  if (parts.length < 1 || parts.length > 4 || parts.some((p) => !/^-?[0-9.]+(px|%|em|rem)?$/.test(p))) return null;
@@ -1972,6 +2040,24 @@ var Importer = class {
1972
2040
  if (typeof node.content === "string" && node.content.trim()) {
1973
2041
  this.warn("stray_text", "info", path, node, "Text directly in mj-wrapper is ignored by MJML, and by the import.");
1974
2042
  }
2043
+ if (wrapper.gap) {
2044
+ let first = true;
2045
+ for (const section of wrapper.sections) {
2046
+ if (section.bodyRaw) continue;
2047
+ if (first) {
2048
+ first = false;
2049
+ continue;
2050
+ }
2051
+ const top = section.padding?.top;
2052
+ if (!top) continue;
2053
+ const without = subtractPx(top, wrapper.gap);
2054
+ if (without === void 0) continue;
2055
+ const padding = { ...section.padding, top: without };
2056
+ const isDefault = padding.top === "20px" && (padding.right ?? "0") === "0" && padding.bottom === "20px" && (padding.left ?? "0") === "0";
2057
+ if (isDefault) delete section.padding;
2058
+ else section.padding = padding;
2059
+ }
2060
+ }
1975
2061
  return wrapper;
1976
2062
  }
1977
2063
  section(node, path) {
@@ -2368,7 +2454,7 @@ var Importer = class {
2368
2454
  return block;
2369
2455
  }
2370
2456
  // Fallbacks: compile the whole source once, with each fallback marked, and cut them out.
2371
- resolveFallbacks(metadata) {
2457
+ async resolveFallbacks(metadata) {
2372
2458
  if (this.fallbacks.length === 0) return;
2373
2459
  const marker = (i, edge) => ({
2374
2460
  tagName: "mj-raw",
@@ -2382,7 +2468,7 @@ var Importer = class {
2382
2468
  }
2383
2469
  let html = "";
2384
2470
  try {
2385
- html = (0, import_mjml3.default)(serialize(this.root), { ignoreIncludes: true, validationLevel: "skip", minify: false, keepComments: true }).html;
2471
+ html = (await (0, import_mjml3.default)(serialize(this.root), { ignoreIncludes: true, validationLevel: "skip", minify: false, keepComments: true })).html;
2386
2472
  } catch {
2387
2473
  html = "";
2388
2474
  }
@@ -2446,7 +2532,7 @@ function supportingCss(fullHtml, fragments, breakpoint = "480px") {
2446
2532
  }
2447
2533
  return out.join("\n");
2448
2534
  }
2449
- function importMjml(source) {
2535
+ async function importMjml(source) {
2450
2536
  if (typeof source !== "string") throw new MjmlImportError("not_mjml", "The MJML must be a string.");
2451
2537
  scanMjml(source, ENDING_TAGS);
2452
2538
  const root = parseMjml(source);
@@ -2462,7 +2548,7 @@ function importMjml(source) {
2462
2548
  }
2463
2549
  const metadata = importer.head(head, body);
2464
2550
  const sections = importer.body(body);
2465
- importer.resolveFallbacks(metadata);
2551
+ await importer.resolveFallbacks(metadata);
2466
2552
  importer.summarize();
2467
2553
  const document = { version: CURRENT_TEMPLATE_VERSION, metadata, sections };
2468
2554
  try {
@@ -2477,16 +2563,23 @@ function importMjml(source) {
2477
2563
  0 && (module.exports = {
2478
2564
  DEFAULT_MJML_ATTRIBUTES,
2479
2565
  EDITOR_DATA_ATTRIBUTE,
2566
+ EDITOR_MARKER,
2480
2567
  MAX_MJML_BYTES,
2481
2568
  MAX_MJML_DEPTH,
2482
2569
  MAX_MJML_ELEMENTS,
2483
2570
  MJMLCompiler,
2484
2571
  MJMLExporter,
2572
+ MjmlBuilder,
2485
2573
  MjmlImportError,
2574
+ addPx,
2575
+ base64Utf8,
2486
2576
  createMJMLCompiler,
2487
2577
  createMJMLExporter,
2488
2578
  encodeEditorData,
2489
2579
  exportTemplate,
2490
2580
  importMjml,
2491
- isMjmlImportError
2581
+ isMjmlImportError,
2582
+ mjmlOptions,
2583
+ paddingWithGap,
2584
+ stripEditorMarkup
2492
2585
  });