@marlinjai/email-editor-core 0.2.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/README.md +1 -1
- package/dist/MjmlBuilder-DsiOyhiL.d.mts +667 -0
- package/dist/MjmlBuilder-DsiOyhiL.d.ts +667 -0
- package/dist/{TemplateModel-v__r5Nvi.d.mts → TemplateModel-CmpvrapI.d.ts} +2 -433
- package/dist/{TemplateModel-v__r5Nvi.d.ts → TemplateModel-PcCoyBdo.d.mts} +2 -433
- package/dist/browser.d.mts +13 -0
- package/dist/browser.d.ts +13 -0
- package/dist/browser.js +959 -0
- package/dist/browser.mjs +30 -0
- package/dist/chunk-NQPMHQ5G.mjs +490 -0
- package/dist/chunk-OOCYGZMB.mjs +1406 -0
- package/dist/index.d.mts +7 -30
- package/dist/index.d.ts +7 -30
- package/dist/index.js +881 -33
- package/dist/index.mjs +46 -51
- package/dist/server.d.mts +9 -167
- package/dist/server.d.ts +9 -167
- package/dist/server.js +172 -79
- package/dist/server.mjs +55 -817
- package/package.json +13 -7
- package/dist/TemplateModel-Dq4GoWpl.d.mts +0 -14743
- package/dist/TemplateModel-Dq4GoWpl.d.ts +0 -14743
- package/dist/chunk-2ZL7A2I6.mjs +0 -1029
- package/dist/chunk-MBTNU2JZ.mjs +0 -395
package/dist/browser.mjs
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EDITOR_MARKER,
|
|
3
|
+
MjmlBuilder,
|
|
4
|
+
mjmlOptions,
|
|
5
|
+
stripEditorMarkup
|
|
6
|
+
} from "./chunk-OOCYGZMB.mjs";
|
|
7
|
+
|
|
8
|
+
// src/browser.ts
|
|
9
|
+
import mjml2html from "mjml-browser";
|
|
10
|
+
var builder = new MjmlBuilder();
|
|
11
|
+
async function compileInBrowser(template, options = {}) {
|
|
12
|
+
try {
|
|
13
|
+
const mjml = builder.toMJML(template, { editor: options.editor });
|
|
14
|
+
const result = await mjml2html(mjml, mjmlOptions(options));
|
|
15
|
+
return {
|
|
16
|
+
mjml,
|
|
17
|
+
html: result.html,
|
|
18
|
+
errors: result.errors.length > 0 ? result.errors.map((e) => e.formattedMessage) : void 0
|
|
19
|
+
};
|
|
20
|
+
} catch (error) {
|
|
21
|
+
return { mjml: "", html: "", errors: [error instanceof Error ? error.message : "Unknown compilation error"] };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export {
|
|
25
|
+
EDITOR_MARKER,
|
|
26
|
+
MjmlBuilder,
|
|
27
|
+
compileInBrowser,
|
|
28
|
+
mjmlOptions,
|
|
29
|
+
stripEditorMarkup
|
|
30
|
+
};
|
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
// src/schema/validation.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var SpacingSchema = z.object({
|
|
4
|
+
top: z.string().optional(),
|
|
5
|
+
right: z.string().optional(),
|
|
6
|
+
bottom: z.string().optional(),
|
|
7
|
+
left: z.string().optional()
|
|
8
|
+
});
|
|
9
|
+
var GradientStopSchema = z.object({
|
|
10
|
+
color: z.string().min(1),
|
|
11
|
+
position: z.number().min(0).max(100)
|
|
12
|
+
});
|
|
13
|
+
var BackgroundGradientSchema = z.object({
|
|
14
|
+
type: z.enum(["linear", "radial"]),
|
|
15
|
+
angle: z.number().min(0).max(360),
|
|
16
|
+
stops: z.array(GradientStopSchema).min(1)
|
|
17
|
+
});
|
|
18
|
+
var ExtraAttributesSchema = z.record(z.string().regex(/^[a-z][a-z0-9-]*$/), z.string());
|
|
19
|
+
var MjmlHeadSchema = z.object({
|
|
20
|
+
attributes: z.string().optional(),
|
|
21
|
+
bodyAttributes: ExtraAttributesSchema.optional(),
|
|
22
|
+
headRaw: z.string().optional()
|
|
23
|
+
});
|
|
24
|
+
var CustomFontSchema = z.object({
|
|
25
|
+
name: z.string(),
|
|
26
|
+
href: z.string()
|
|
27
|
+
});
|
|
28
|
+
var TemplateMetadataSchema = z.object({
|
|
29
|
+
name: z.string().optional(),
|
|
30
|
+
subject: z.string().optional(),
|
|
31
|
+
previewText: z.string().optional(),
|
|
32
|
+
title: z.string().optional(),
|
|
33
|
+
// Epoch milliseconds (what the editor's store emits) or an ISO 8601 string.
|
|
34
|
+
createdAt: z.union([z.string(), z.number()]).optional(),
|
|
35
|
+
updatedAt: z.union([z.string(), z.number()]).optional(),
|
|
36
|
+
// Head component settings
|
|
37
|
+
fonts: z.array(CustomFontSchema).optional(),
|
|
38
|
+
breakpoint: z.string().optional(),
|
|
39
|
+
customCSS: z.string().optional(),
|
|
40
|
+
inlineCSS: z.string().optional(),
|
|
41
|
+
mjmlHead: MjmlHeadSchema.optional()
|
|
42
|
+
});
|
|
43
|
+
var TextBlockSchema = z.object({
|
|
44
|
+
id: z.string(),
|
|
45
|
+
type: z.literal("text"),
|
|
46
|
+
hidden: z.boolean().optional(),
|
|
47
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
48
|
+
content: z.string(),
|
|
49
|
+
align: z.enum(["left", "center", "right", "justify"]).optional(),
|
|
50
|
+
color: z.string().optional(),
|
|
51
|
+
fontSize: z.string().optional(),
|
|
52
|
+
fontFamily: z.string().optional(),
|
|
53
|
+
padding: SpacingSchema.optional(),
|
|
54
|
+
lineHeight: z.string().optional()
|
|
55
|
+
});
|
|
56
|
+
var ImageBlockSchema = z.object({
|
|
57
|
+
id: z.string(),
|
|
58
|
+
type: z.literal("image"),
|
|
59
|
+
hidden: z.boolean().optional(),
|
|
60
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
61
|
+
src: z.string().min(1),
|
|
62
|
+
// Allow any non-empty string, not just URLs
|
|
63
|
+
alt: z.string().optional(),
|
|
64
|
+
width: z.string().optional(),
|
|
65
|
+
height: z.string().optional(),
|
|
66
|
+
align: z.enum(["left", "center", "right"]).optional(),
|
|
67
|
+
href: z.string().optional(),
|
|
68
|
+
// Allow any string for link
|
|
69
|
+
padding: SpacingSchema.optional(),
|
|
70
|
+
borderRadius: z.string().optional()
|
|
71
|
+
// Rounded corners
|
|
72
|
+
});
|
|
73
|
+
var ButtonBlockSchema = z.object({
|
|
74
|
+
id: z.string(),
|
|
75
|
+
type: z.literal("button"),
|
|
76
|
+
hidden: z.boolean().optional(),
|
|
77
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
78
|
+
label: z.string().min(1),
|
|
79
|
+
href: z.string().min(1),
|
|
80
|
+
// Allow any non-empty string
|
|
81
|
+
align: z.enum(["left", "center", "right"]).optional(),
|
|
82
|
+
backgroundColor: z.string().optional(),
|
|
83
|
+
color: z.string().optional(),
|
|
84
|
+
borderRadius: z.string().optional(),
|
|
85
|
+
border: z.string().optional(),
|
|
86
|
+
// CSS border shorthand
|
|
87
|
+
padding: SpacingSchema.optional(),
|
|
88
|
+
innerPadding: z.string().optional()
|
|
89
|
+
});
|
|
90
|
+
var DividerBlockSchema = z.object({
|
|
91
|
+
id: z.string(),
|
|
92
|
+
type: z.literal("divider"),
|
|
93
|
+
hidden: z.boolean().optional(),
|
|
94
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
95
|
+
borderColor: z.string().optional(),
|
|
96
|
+
borderWidth: z.string().optional(),
|
|
97
|
+
borderStyle: z.enum(["solid", "dashed", "dotted"]).optional(),
|
|
98
|
+
width: z.string().optional(),
|
|
99
|
+
// Width of the divider line
|
|
100
|
+
padding: SpacingSchema.optional()
|
|
101
|
+
});
|
|
102
|
+
var SpacerBlockSchema = z.object({
|
|
103
|
+
id: z.string(),
|
|
104
|
+
type: z.literal("spacer"),
|
|
105
|
+
hidden: z.boolean().optional(),
|
|
106
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
107
|
+
height: z.string()
|
|
108
|
+
});
|
|
109
|
+
var HeaderBlockSchema = z.object({
|
|
110
|
+
id: z.string(),
|
|
111
|
+
type: z.literal("header"),
|
|
112
|
+
hidden: z.boolean().optional(),
|
|
113
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
114
|
+
locked: z.literal(true)
|
|
115
|
+
});
|
|
116
|
+
var FooterBlockSchema = z.object({
|
|
117
|
+
id: z.string(),
|
|
118
|
+
type: z.literal("footer"),
|
|
119
|
+
hidden: z.boolean().optional(),
|
|
120
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
121
|
+
locked: z.literal(true)
|
|
122
|
+
});
|
|
123
|
+
var SocialBlockSchema = z.object({
|
|
124
|
+
id: z.string(),
|
|
125
|
+
type: z.literal("social"),
|
|
126
|
+
hidden: z.boolean().optional(),
|
|
127
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
128
|
+
mode: z.enum(["horizontal", "vertical"]).optional(),
|
|
129
|
+
align: z.enum(["left", "center", "right"]).optional(),
|
|
130
|
+
iconSize: z.string().optional(),
|
|
131
|
+
iconPadding: z.string().optional(),
|
|
132
|
+
borderRadius: z.string().optional(),
|
|
133
|
+
// For round icons
|
|
134
|
+
links: z.array(
|
|
135
|
+
z.object({
|
|
136
|
+
platform: z.enum(["facebook", "twitter", "instagram", "linkedin", "youtube", "pinterest", "github"]),
|
|
137
|
+
url: z.string(),
|
|
138
|
+
color: z.string().optional()
|
|
139
|
+
// Per-icon color override (uses platform default if not set)
|
|
140
|
+
})
|
|
141
|
+
)
|
|
142
|
+
});
|
|
143
|
+
var HeroBlockSchema = z.object({
|
|
144
|
+
id: z.string(),
|
|
145
|
+
type: z.literal("hero"),
|
|
146
|
+
hidden: z.boolean().optional(),
|
|
147
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
148
|
+
backgroundImage: z.string().min(1),
|
|
149
|
+
backgroundHeight: z.string().optional(),
|
|
150
|
+
backgroundWidth: z.string().optional(),
|
|
151
|
+
backgroundColor: z.string().optional(),
|
|
152
|
+
verticalAlign: z.enum(["top", "middle", "bottom"]).optional(),
|
|
153
|
+
mode: z.enum(["fluid-height", "fixed-height"]).optional()
|
|
154
|
+
});
|
|
155
|
+
var AccordionBlockSchema = z.object({
|
|
156
|
+
id: z.string(),
|
|
157
|
+
type: z.literal("accordion"),
|
|
158
|
+
hidden: z.boolean().optional(),
|
|
159
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
160
|
+
items: z.array(
|
|
161
|
+
z.object({
|
|
162
|
+
title: z.string(),
|
|
163
|
+
content: z.string()
|
|
164
|
+
})
|
|
165
|
+
),
|
|
166
|
+
iconPosition: z.enum(["left", "right"]).optional(),
|
|
167
|
+
borderColor: z.string().optional(),
|
|
168
|
+
fontFamily: z.string().optional()
|
|
169
|
+
});
|
|
170
|
+
var RawBlockSchema = z.object({
|
|
171
|
+
id: z.string(),
|
|
172
|
+
type: z.literal("raw"),
|
|
173
|
+
hidden: z.boolean().optional(),
|
|
174
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
175
|
+
html: z.string()
|
|
176
|
+
});
|
|
177
|
+
var NavbarBlockSchema = z.object({
|
|
178
|
+
id: z.string(),
|
|
179
|
+
type: z.literal("navbar"),
|
|
180
|
+
hidden: z.boolean().optional(),
|
|
181
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
182
|
+
links: z.array(
|
|
183
|
+
z.object({
|
|
184
|
+
label: z.string(),
|
|
185
|
+
href: z.string(),
|
|
186
|
+
color: z.string().optional()
|
|
187
|
+
})
|
|
188
|
+
),
|
|
189
|
+
hamburger: z.boolean().optional(),
|
|
190
|
+
baseUrl: z.string().optional(),
|
|
191
|
+
align: z.enum(["left", "center", "right"]).optional(),
|
|
192
|
+
icoColor: z.string().optional(),
|
|
193
|
+
padding: SpacingSchema.optional()
|
|
194
|
+
});
|
|
195
|
+
var CarouselBlockSchema = z.object({
|
|
196
|
+
id: z.string(),
|
|
197
|
+
type: z.literal("carousel"),
|
|
198
|
+
hidden: z.boolean().optional(),
|
|
199
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
200
|
+
images: z.array(
|
|
201
|
+
z.object({
|
|
202
|
+
src: z.string(),
|
|
203
|
+
alt: z.string().optional(),
|
|
204
|
+
href: z.string().optional(),
|
|
205
|
+
thumbnailSrc: z.string().optional()
|
|
206
|
+
})
|
|
207
|
+
),
|
|
208
|
+
thumbnails: z.enum(["visible", "hidden"]).optional(),
|
|
209
|
+
borderRadius: z.string().optional(),
|
|
210
|
+
iconWidth: z.string().optional(),
|
|
211
|
+
tbBorderRadius: z.string().optional(),
|
|
212
|
+
padding: SpacingSchema.optional()
|
|
213
|
+
});
|
|
214
|
+
var TableBlockSchema = z.object({
|
|
215
|
+
id: z.string(),
|
|
216
|
+
type: z.literal("table"),
|
|
217
|
+
hidden: z.boolean().optional(),
|
|
218
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
219
|
+
headers: z.array(z.string()),
|
|
220
|
+
rows: z.array(z.array(z.string())),
|
|
221
|
+
align: z.enum(["left", "center", "right"]).optional(),
|
|
222
|
+
color: z.string().optional(),
|
|
223
|
+
fontFamily: z.string().optional(),
|
|
224
|
+
fontSize: z.string().optional(),
|
|
225
|
+
cellpadding: z.string().optional(),
|
|
226
|
+
cellspacing: z.string().optional(),
|
|
227
|
+
border: z.string().optional(),
|
|
228
|
+
padding: SpacingSchema.optional()
|
|
229
|
+
});
|
|
230
|
+
var BlockSchema = z.discriminatedUnion("type", [
|
|
231
|
+
TextBlockSchema,
|
|
232
|
+
ImageBlockSchema,
|
|
233
|
+
ButtonBlockSchema,
|
|
234
|
+
DividerBlockSchema,
|
|
235
|
+
SpacerBlockSchema,
|
|
236
|
+
HeaderBlockSchema,
|
|
237
|
+
FooterBlockSchema,
|
|
238
|
+
SocialBlockSchema,
|
|
239
|
+
HeroBlockSchema,
|
|
240
|
+
AccordionBlockSchema,
|
|
241
|
+
RawBlockSchema,
|
|
242
|
+
NavbarBlockSchema,
|
|
243
|
+
CarouselBlockSchema,
|
|
244
|
+
TableBlockSchema
|
|
245
|
+
]);
|
|
246
|
+
var ColumnSchema = z.object({
|
|
247
|
+
id: z.string(),
|
|
248
|
+
width: z.number().min(0).max(100).optional(),
|
|
249
|
+
backgroundColor: z.string().optional(),
|
|
250
|
+
backgroundGradient: BackgroundGradientSchema.optional(),
|
|
251
|
+
padding: SpacingSchema.optional(),
|
|
252
|
+
verticalAlign: z.enum(["top", "middle", "bottom"]).optional(),
|
|
253
|
+
// Vertical content alignment
|
|
254
|
+
hidden: z.boolean().optional(),
|
|
255
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
256
|
+
blocks: z.array(BlockSchema)
|
|
257
|
+
});
|
|
258
|
+
var sectionFields = {
|
|
259
|
+
id: z.string(),
|
|
260
|
+
type: z.literal("section"),
|
|
261
|
+
backgroundColor: z.string().optional(),
|
|
262
|
+
backgroundImage: z.string().optional(),
|
|
263
|
+
backgroundPosition: z.string().optional(),
|
|
264
|
+
backgroundRepeat: z.enum(["repeat", "no-repeat"]).optional(),
|
|
265
|
+
backgroundSize: z.string().optional(),
|
|
266
|
+
backgroundGradient: BackgroundGradientSchema.optional(),
|
|
267
|
+
padding: SpacingSchema.optional(),
|
|
268
|
+
noStack: z.boolean().optional(),
|
|
269
|
+
fullWidth: z.boolean().optional(),
|
|
270
|
+
hidden: z.boolean().optional(),
|
|
271
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
272
|
+
bodyRaw: z.boolean().optional(),
|
|
273
|
+
columns: z.array(ColumnSchema).min(1)
|
|
274
|
+
};
|
|
275
|
+
var SectionSchema = z.object({
|
|
276
|
+
...sectionFields,
|
|
277
|
+
isWrapper: z.undefined({ invalid_type_error: "isWrapper is a schema 1.0 field; a 1.1 document holds a wrapper instead" }).optional()
|
|
278
|
+
});
|
|
279
|
+
var SectionSchemaV1_0 = z.object({
|
|
280
|
+
...sectionFields,
|
|
281
|
+
isWrapper: z.boolean().optional()
|
|
282
|
+
});
|
|
283
|
+
var WrapperSchema = z.object({
|
|
284
|
+
id: z.string(),
|
|
285
|
+
type: z.literal("wrapper"),
|
|
286
|
+
hidden: z.boolean().optional(),
|
|
287
|
+
backgroundColor: z.string().optional(),
|
|
288
|
+
backgroundImage: z.string().optional(),
|
|
289
|
+
backgroundGradient: BackgroundGradientSchema.optional(),
|
|
290
|
+
backgroundPosition: z.string().optional(),
|
|
291
|
+
backgroundRepeat: z.enum(["repeat", "no-repeat"]).optional(),
|
|
292
|
+
backgroundSize: z.string().optional(),
|
|
293
|
+
border: z.string().optional(),
|
|
294
|
+
borderTop: z.string().optional(),
|
|
295
|
+
borderRight: z.string().optional(),
|
|
296
|
+
borderBottom: z.string().optional(),
|
|
297
|
+
borderLeft: z.string().optional(),
|
|
298
|
+
borderRadius: z.string().optional(),
|
|
299
|
+
padding: SpacingSchema.optional(),
|
|
300
|
+
fullWidth: z.boolean().optional(),
|
|
301
|
+
cssClass: z.string().optional(),
|
|
302
|
+
gap: z.string().regex(/^[0-9]+(\.[0-9]+)?px$/, "gap is a length in px, e.g. 16px").optional(),
|
|
303
|
+
textAlign: z.enum(["left", "center", "right"]).optional(),
|
|
304
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
305
|
+
sections: z.array(SectionSchema)
|
|
306
|
+
});
|
|
307
|
+
var TopLevelItemSchema = z.discriminatedUnion("type", [SectionSchema, WrapperSchema]);
|
|
308
|
+
var EmailTemplateSchemaV1_0 = z.object({
|
|
309
|
+
id: z.string().optional(),
|
|
310
|
+
version: z.literal("1.0"),
|
|
311
|
+
metadata: TemplateMetadataSchema,
|
|
312
|
+
sections: z.array(SectionSchemaV1_0)
|
|
313
|
+
});
|
|
314
|
+
var EmailTemplateSchemaV1_1 = z.object({
|
|
315
|
+
id: z.string().optional(),
|
|
316
|
+
version: z.literal("1.1"),
|
|
317
|
+
metadata: TemplateMetadataSchema,
|
|
318
|
+
sections: z.array(TopLevelItemSchema)
|
|
319
|
+
});
|
|
320
|
+
var EmailTemplateSchema = z.discriminatedUnion("version", [EmailTemplateSchemaV1_0, EmailTemplateSchemaV1_1]);
|
|
321
|
+
function validateTemplate(template) {
|
|
322
|
+
return EmailTemplateSchema.safeParse(template);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// src/schema/migrate.ts
|
|
326
|
+
import { nanoid } from "nanoid";
|
|
327
|
+
var CURRENT_TEMPLATE_VERSION = "1.1";
|
|
328
|
+
var SUPPORTED_TEMPLATE_VERSIONS = ["1.0", CURRENT_TEMPLATE_VERSION];
|
|
329
|
+
var TemplateMigrationError = class extends Error {
|
|
330
|
+
constructor(code, message, options = {}) {
|
|
331
|
+
super(message);
|
|
332
|
+
this.name = "TemplateMigrationError";
|
|
333
|
+
this.code = code;
|
|
334
|
+
this.version = options.version;
|
|
335
|
+
this.issues = options.issues ?? [];
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
function isTemplateMigrationError(error) {
|
|
339
|
+
return error instanceof TemplateMigrationError;
|
|
340
|
+
}
|
|
341
|
+
function parseVersion(version) {
|
|
342
|
+
const match = /^(\d+)\.(\d+)$/.exec(version);
|
|
343
|
+
if (!match) return null;
|
|
344
|
+
return [Number(match[1]), Number(match[2])];
|
|
345
|
+
}
|
|
346
|
+
function compareVersions(a, b) {
|
|
347
|
+
return a[0] !== b[0] ? a[0] - b[0] : a[1] - b[1];
|
|
348
|
+
}
|
|
349
|
+
function migrateTemplate(doc) {
|
|
350
|
+
if (typeof doc !== "object" || doc === null || Array.isArray(doc)) {
|
|
351
|
+
throw new TemplateMigrationError(
|
|
352
|
+
"INVALID_INPUT",
|
|
353
|
+
`Expected a template document object, received ${doc === null ? "null" : Array.isArray(doc) ? "an array" : typeof doc}.`
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
const version = doc.version;
|
|
357
|
+
if (typeof version !== "string" || version.length === 0) {
|
|
358
|
+
throw new TemplateMigrationError(
|
|
359
|
+
"MISSING_VERSION",
|
|
360
|
+
'The template document has no "version" field, so its schema cannot be determined.'
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
const parsed = parseVersion(version);
|
|
364
|
+
if (!parsed) {
|
|
365
|
+
throw new TemplateMigrationError(
|
|
366
|
+
"UNSUPPORTED_VERSION",
|
|
367
|
+
`Template schema version "${version}" is not of the form "major.minor".`,
|
|
368
|
+
{ version }
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
const current = parseVersion(CURRENT_TEMPLATE_VERSION);
|
|
372
|
+
if (compareVersions(parsed, current) > 0) {
|
|
373
|
+
throw new TemplateMigrationError(
|
|
374
|
+
"NEWER_VERSION",
|
|
375
|
+
`Template schema version "${version}" is newer than this editor supports (${CURRENT_TEMPLATE_VERSION}). Upgrade the @marlinjai/email-editor packages to open it.`,
|
|
376
|
+
{ version }
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
if (!SUPPORTED_TEMPLATE_VERSIONS.includes(version)) {
|
|
380
|
+
throw new TemplateMigrationError(
|
|
381
|
+
"UNSUPPORTED_VERSION",
|
|
382
|
+
`Template schema version "${version}" is not supported and has no migration to ${CURRENT_TEMPLATE_VERSION}.`,
|
|
383
|
+
{ version }
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
let next = doc;
|
|
387
|
+
if (version === "1.0") {
|
|
388
|
+
assertValid(EmailTemplateSchemaV1_0, next, version);
|
|
389
|
+
next = migrateV1_0ToV1_1(next);
|
|
390
|
+
}
|
|
391
|
+
assertValid(EmailTemplateSchemaV1_1, next, version);
|
|
392
|
+
return next;
|
|
393
|
+
}
|
|
394
|
+
function assertValid(schema, doc, version) {
|
|
395
|
+
const result = schema.safeParse(doc);
|
|
396
|
+
if (result.success) return;
|
|
397
|
+
const issues = result.error.issues.map((issue) => ({ path: issue.path, message: issue.message }));
|
|
398
|
+
const first = issues[0];
|
|
399
|
+
throw new TemplateMigrationError(
|
|
400
|
+
"INVALID_DOCUMENT",
|
|
401
|
+
`Template document does not match schema version ${version}: ${first ? `${first.path.join(".") || "(root)"}: ${first.message}` : "unknown validation error"}${issues.length > 1 ? ` (and ${issues.length - 1} more)` : ""}.`,
|
|
402
|
+
{ version, issues }
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
function migrateV1_0ToV1_1(doc) {
|
|
406
|
+
if (!doc.sections.some((s) => s.isWrapper === true && !s.bodyRaw)) {
|
|
407
|
+
return { ...doc, version: "1.1", sections: doc.sections.map(stripIsWrapper) };
|
|
408
|
+
}
|
|
409
|
+
const ids = /* @__PURE__ */ new Set();
|
|
410
|
+
for (const s of doc.sections) {
|
|
411
|
+
ids.add(s.id);
|
|
412
|
+
for (const c of s.columns ?? []) {
|
|
413
|
+
ids.add(c.id);
|
|
414
|
+
for (const b of c.blocks ?? []) ids.add(b.id);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
const freshId = (base) => {
|
|
418
|
+
let candidate = `${base}-inner`;
|
|
419
|
+
for (let n = 2; ids.has(candidate); n++) candidate = `${base}-inner-${n}`;
|
|
420
|
+
ids.add(candidate);
|
|
421
|
+
return candidate;
|
|
422
|
+
};
|
|
423
|
+
const sections = doc.sections.map((s) => {
|
|
424
|
+
if (s.isWrapper !== true || s.bodyRaw) return stripIsWrapper(s);
|
|
425
|
+
const { isWrapper: _flag, noStack: _noStack, ...rest } = s;
|
|
426
|
+
const wrapper = { id: s.id, type: "wrapper", sections: [{ id: freshId(s.id), type: "section", columns: s.columns }] };
|
|
427
|
+
if (rest.hidden !== void 0) wrapper.hidden = rest.hidden;
|
|
428
|
+
if (rest.backgroundColor !== void 0) wrapper.backgroundColor = rest.backgroundColor;
|
|
429
|
+
if (rest.backgroundImage !== void 0) wrapper.backgroundImage = rest.backgroundImage;
|
|
430
|
+
if (rest.backgroundGradient !== void 0) wrapper.backgroundGradient = rest.backgroundGradient;
|
|
431
|
+
if (rest.backgroundPosition !== void 0) wrapper.backgroundPosition = rest.backgroundPosition;
|
|
432
|
+
if (rest.backgroundRepeat !== void 0) wrapper.backgroundRepeat = rest.backgroundRepeat;
|
|
433
|
+
if (rest.backgroundSize !== void 0) wrapper.backgroundSize = rest.backgroundSize;
|
|
434
|
+
if (rest.fullWidth !== void 0) wrapper.fullWidth = rest.fullWidth;
|
|
435
|
+
if (rest.padding !== void 0) wrapper.padding = rest.padding;
|
|
436
|
+
if (rest.extraAttributes !== void 0) wrapper.extraAttributes = rest.extraAttributes;
|
|
437
|
+
return wrapper;
|
|
438
|
+
});
|
|
439
|
+
return { ...doc, version: "1.1", sections };
|
|
440
|
+
}
|
|
441
|
+
function stripIsWrapper(s) {
|
|
442
|
+
if (!("isWrapper" in s)) return s;
|
|
443
|
+
const { isWrapper: _flag, ...rest } = s;
|
|
444
|
+
return rest;
|
|
445
|
+
}
|
|
446
|
+
function withTemplateId(doc) {
|
|
447
|
+
if (typeof doc.id === "string" && doc.id.length > 0) return doc;
|
|
448
|
+
return { ...doc, id: nanoid() };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export {
|
|
452
|
+
SpacingSchema,
|
|
453
|
+
GradientStopSchema,
|
|
454
|
+
BackgroundGradientSchema,
|
|
455
|
+
ExtraAttributesSchema,
|
|
456
|
+
MjmlHeadSchema,
|
|
457
|
+
CustomFontSchema,
|
|
458
|
+
TemplateMetadataSchema,
|
|
459
|
+
TextBlockSchema,
|
|
460
|
+
ImageBlockSchema,
|
|
461
|
+
ButtonBlockSchema,
|
|
462
|
+
DividerBlockSchema,
|
|
463
|
+
SpacerBlockSchema,
|
|
464
|
+
HeaderBlockSchema,
|
|
465
|
+
FooterBlockSchema,
|
|
466
|
+
SocialBlockSchema,
|
|
467
|
+
HeroBlockSchema,
|
|
468
|
+
AccordionBlockSchema,
|
|
469
|
+
RawBlockSchema,
|
|
470
|
+
NavbarBlockSchema,
|
|
471
|
+
CarouselBlockSchema,
|
|
472
|
+
TableBlockSchema,
|
|
473
|
+
BlockSchema,
|
|
474
|
+
ColumnSchema,
|
|
475
|
+
SectionSchema,
|
|
476
|
+
SectionSchemaV1_0,
|
|
477
|
+
WrapperSchema,
|
|
478
|
+
TopLevelItemSchema,
|
|
479
|
+
EmailTemplateSchemaV1_0,
|
|
480
|
+
EmailTemplateSchemaV1_1,
|
|
481
|
+
EmailTemplateSchema,
|
|
482
|
+
validateTemplate,
|
|
483
|
+
CURRENT_TEMPLATE_VERSION,
|
|
484
|
+
SUPPORTED_TEMPLATE_VERSIONS,
|
|
485
|
+
TemplateMigrationError,
|
|
486
|
+
isTemplateMigrationError,
|
|
487
|
+
migrateTemplate,
|
|
488
|
+
migrateV1_0ToV1_1,
|
|
489
|
+
withTemplateId
|
|
490
|
+
};
|