@marlinjai/email-editor-core 0.2.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.
@@ -0,0 +1,395 @@
1
+ // src/schema/gradient.ts
2
+ function buildGradientCSS(gradient) {
3
+ if (gradient.stops.length === 0) return void 0;
4
+ const stopList = gradient.stops.map((s) => `${s.color} ${s.position}%`).join(", ");
5
+ if (gradient.type === "radial") {
6
+ return `radial-gradient(circle, ${stopList})`;
7
+ }
8
+ return `linear-gradient(${gradient.angle}deg, ${stopList})`;
9
+ }
10
+
11
+ // src/store/mst/models/BlockModel.ts
12
+ import { types } from "mobx-state-tree";
13
+ var BlockType = /* @__PURE__ */ ((BlockType2) => {
14
+ BlockType2["TEXT"] = "text";
15
+ BlockType2["IMAGE"] = "image";
16
+ BlockType2["BUTTON"] = "button";
17
+ BlockType2["DIVIDER"] = "divider";
18
+ BlockType2["SPACER"] = "spacer";
19
+ BlockType2["SOCIAL"] = "social";
20
+ BlockType2["HERO"] = "hero";
21
+ BlockType2["ACCORDION"] = "accordion";
22
+ BlockType2["RAW"] = "raw";
23
+ BlockType2["NAVBAR"] = "navbar";
24
+ BlockType2["CAROUSEL"] = "carousel";
25
+ BlockType2["TABLE"] = "table";
26
+ BlockType2["HEADER"] = "header";
27
+ BlockType2["FOOTER"] = "footer";
28
+ return BlockType2;
29
+ })(BlockType || {});
30
+ var SocialLinkModel = types.model("SocialLink", {
31
+ platform: types.string,
32
+ url: types.string,
33
+ color: types.maybe(types.string)
34
+ });
35
+ var NavbarLinkModel = types.model("NavbarLink", {
36
+ href: types.string,
37
+ label: types.string,
38
+ color: types.maybe(types.string)
39
+ });
40
+ var CarouselImageModel = types.model("CarouselImage", {
41
+ src: types.string,
42
+ alt: types.maybe(types.string),
43
+ href: types.maybe(types.string),
44
+ thumbnailSrc: types.maybe(types.string)
45
+ });
46
+ var AccordionItemModel = types.model("AccordionItem", {
47
+ title: types.string,
48
+ content: types.string
49
+ });
50
+ var SpacingModel = types.model("Spacing", {
51
+ top: types.maybe(types.string),
52
+ right: types.maybe(types.string),
53
+ bottom: types.maybe(types.string),
54
+ left: types.maybe(types.string)
55
+ });
56
+ var BlockModel = types.model("Block", {
57
+ id: types.identifier,
58
+ type: types.enumeration("BlockType", Object.values(BlockType)),
59
+ hidden: types.optional(types.boolean, false),
60
+ // === Common Text Properties ===
61
+ content: types.optional(types.string, ""),
62
+ color: types.maybe(types.string),
63
+ backgroundColor: types.maybe(types.string),
64
+ fontSize: types.maybe(types.string),
65
+ fontFamily: types.maybe(types.string),
66
+ align: types.maybe(types.enumeration(["left", "center", "right", "justify"])),
67
+ lineHeight: types.maybe(types.string),
68
+ // === Spacing ===
69
+ paddingTop: types.maybe(types.string),
70
+ paddingRight: types.maybe(types.string),
71
+ paddingBottom: types.maybe(types.string),
72
+ paddingLeft: types.maybe(types.string),
73
+ // === Image Properties ===
74
+ src: types.maybe(types.string),
75
+ alt: types.maybe(types.string),
76
+ width: types.maybe(types.string),
77
+ height: types.maybe(types.string),
78
+ // === Button Properties ===
79
+ href: types.maybe(types.string),
80
+ label: types.maybe(types.string),
81
+ borderRadius: types.maybe(types.string),
82
+ border: types.maybe(types.string),
83
+ innerPadding: types.maybe(types.string),
84
+ // === Divider Properties ===
85
+ borderColor: types.maybe(types.string),
86
+ borderWidth: types.maybe(types.string),
87
+ borderStyle: types.maybe(types.enumeration(["solid", "dashed", "dotted"])),
88
+ // === Spacer Properties ===
89
+ // (uses height from image properties)
90
+ // === Social Properties ===
91
+ links: types.optional(types.array(SocialLinkModel), []),
92
+ iconSize: types.maybe(types.string),
93
+ iconPadding: types.maybe(types.string),
94
+ mode: types.maybe(types.enumeration(["horizontal", "vertical", "fixed-height", "fluid-height"])),
95
+ // === Hero Properties ===
96
+ backgroundImage: types.maybe(types.string),
97
+ backgroundHeight: types.maybe(types.string),
98
+ backgroundWidth: types.maybe(types.string),
99
+ verticalAlign: types.maybe(types.enumeration(["top", "middle", "bottom"])),
100
+ // === Accordion Properties ===
101
+ items: types.optional(types.array(AccordionItemModel), []),
102
+ iconPosition: types.maybe(types.enumeration(["left", "right"])),
103
+ // === Raw Properties ===
104
+ html: types.maybe(types.string),
105
+ // === Navbar Properties ===
106
+ navLinks: types.optional(types.array(NavbarLinkModel), []),
107
+ hamburger: types.optional(types.boolean, false),
108
+ baseUrl: types.maybe(types.string),
109
+ icoColor: types.maybe(types.string),
110
+ // === Carousel Properties ===
111
+ images: types.optional(types.array(CarouselImageModel), []),
112
+ thumbnails: types.maybe(types.enumeration(["visible", "hidden"])),
113
+ iconWidth: types.maybe(types.string),
114
+ tbBorderRadius: types.maybe(types.string),
115
+ // === Table Properties ===
116
+ headers: types.optional(types.array(types.string), []),
117
+ rows: types.optional(types.array(types.array(types.string)), []),
118
+ cellpadding: types.maybe(types.string),
119
+ cellspacing: types.maybe(types.string),
120
+ // === Locked Blocks (Header/Footer) ===
121
+ locked: types.optional(types.boolean, false)
122
+ }).actions((self) => ({
123
+ /**
124
+ * Update any style property by name
125
+ */
126
+ updateStyle(property, value) {
127
+ if (property in self) {
128
+ self[property] = value;
129
+ }
130
+ },
131
+ /**
132
+ * Update multiple properties at once
133
+ */
134
+ updateProperties(updates) {
135
+ Object.entries(updates).forEach(([key, value]) => {
136
+ if (key in self) {
137
+ self[key] = value;
138
+ }
139
+ });
140
+ },
141
+ /**
142
+ * Set block content (for text blocks)
143
+ */
144
+ setContent(content) {
145
+ self.content = content;
146
+ },
147
+ /**
148
+ * Toggle block visibility
149
+ */
150
+ toggleHidden() {
151
+ self.hidden = !self.hidden;
152
+ },
153
+ /**
154
+ * Set padding values
155
+ */
156
+ setPadding(padding) {
157
+ if (padding.top !== void 0) self.paddingTop = padding.top;
158
+ if (padding.right !== void 0) self.paddingRight = padding.right;
159
+ if (padding.bottom !== void 0) self.paddingBottom = padding.bottom;
160
+ if (padding.left !== void 0) self.paddingLeft = padding.left;
161
+ },
162
+ /**
163
+ * Add a social link
164
+ */
165
+ addSocialLink(platform, url, color) {
166
+ self.links.push(SocialLinkModel.create({ platform, url, color }));
167
+ },
168
+ /**
169
+ * Remove a social link by index
170
+ */
171
+ removeSocialLink(index) {
172
+ self.links.splice(index, 1);
173
+ },
174
+ /**
175
+ * Add a navbar link
176
+ */
177
+ addNavbarLink(href, label, color) {
178
+ self.navLinks.push(NavbarLinkModel.create({ href, label, color }));
179
+ },
180
+ /**
181
+ * Remove a navbar link by index
182
+ */
183
+ removeNavbarLink(index) {
184
+ self.navLinks.splice(index, 1);
185
+ },
186
+ /**
187
+ * Add a carousel image
188
+ */
189
+ addCarouselImage(src, alt, href, thumbnailSrc) {
190
+ self.images.push(CarouselImageModel.create({ src, alt, href, thumbnailSrc }));
191
+ },
192
+ /**
193
+ * Remove a carousel image by index
194
+ */
195
+ removeCarouselImage(index) {
196
+ self.images.splice(index, 1);
197
+ },
198
+ /**
199
+ * Add an accordion item
200
+ */
201
+ addAccordionItem(title, content) {
202
+ self.items.push(AccordionItemModel.create({ title, content }));
203
+ },
204
+ /**
205
+ * Remove an accordion item by index
206
+ */
207
+ removeAccordionItem(index) {
208
+ self.items.splice(index, 1);
209
+ },
210
+ /**
211
+ * Add a table row
212
+ */
213
+ addTableRow(cells) {
214
+ self.rows.push(cells);
215
+ },
216
+ /**
217
+ * Remove a table row by index
218
+ */
219
+ removeTableRow(index) {
220
+ self.rows.splice(index, 1);
221
+ }
222
+ })).views((self) => ({
223
+ /**
224
+ * Computed style object for React rendering
225
+ */
226
+ get computedStyle() {
227
+ const style = {};
228
+ if (self.color) style.color = self.color;
229
+ if (self.backgroundColor) style.backgroundColor = self.backgroundColor;
230
+ if (self.fontSize) style.fontSize = self.fontSize;
231
+ if (self.fontFamily) style.fontFamily = self.fontFamily;
232
+ if (self.align) style.textAlign = self.align;
233
+ if (self.lineHeight) style.lineHeight = self.lineHeight;
234
+ if (self.paddingTop) style.paddingTop = self.paddingTop;
235
+ if (self.paddingRight) style.paddingRight = self.paddingRight;
236
+ if (self.paddingBottom) style.paddingBottom = self.paddingBottom;
237
+ if (self.paddingLeft) style.paddingLeft = self.paddingLeft;
238
+ if (self.borderRadius) style.borderRadius = self.borderRadius;
239
+ if (self.border) style.border = self.border;
240
+ return style;
241
+ },
242
+ /**
243
+ * Get padding as a single object
244
+ */
245
+ get padding() {
246
+ return {
247
+ top: self.paddingTop || void 0,
248
+ right: self.paddingRight || void 0,
249
+ bottom: self.paddingBottom || void 0,
250
+ left: self.paddingLeft || void 0
251
+ };
252
+ },
253
+ /**
254
+ * Get padding as a CSS string (e.g., "10px 20px 10px 20px")
255
+ */
256
+ get paddingString() {
257
+ const { paddingTop, paddingRight, paddingBottom, paddingLeft } = self;
258
+ if (!paddingTop && !paddingRight && !paddingBottom && !paddingLeft) {
259
+ return void 0;
260
+ }
261
+ return `${paddingTop || "0"} ${paddingRight || "0"} ${paddingBottom || "0"} ${paddingLeft || "0"}`;
262
+ },
263
+ /**
264
+ * MJML attributes for export
265
+ */
266
+ get mjmlAttributes() {
267
+ const attrs = {};
268
+ if (self.color) attrs.color = self.color;
269
+ if (self.backgroundColor) attrs["background-color"] = self.backgroundColor;
270
+ if (self.fontSize) attrs["font-size"] = self.fontSize;
271
+ if (self.fontFamily) attrs["font-family"] = self.fontFamily;
272
+ if (self.align) attrs.align = self.align;
273
+ if (self.lineHeight) attrs["line-height"] = self.lineHeight;
274
+ const paddingParts = [];
275
+ if (self.paddingTop) paddingParts.push(`top:${self.paddingTop}`);
276
+ if (self.paddingRight) paddingParts.push(`right:${self.paddingRight}`);
277
+ if (self.paddingBottom) paddingParts.push(`bottom:${self.paddingBottom}`);
278
+ if (self.paddingLeft) paddingParts.push(`left:${self.paddingLeft}`);
279
+ if (paddingParts.length > 0) {
280
+ const padding = `${self.paddingTop || "0"} ${self.paddingRight || "0"} ${self.paddingBottom || "0"} ${self.paddingLeft || "0"}`;
281
+ attrs.padding = padding;
282
+ }
283
+ if (self.src) attrs.src = self.src;
284
+ if (self.alt) attrs.alt = self.alt;
285
+ if (self.width) attrs.width = self.width;
286
+ if (self.height) attrs.height = self.height;
287
+ if (self.href) attrs.href = self.href;
288
+ if (self.borderRadius) attrs["border-radius"] = self.borderRadius;
289
+ if (self.border) attrs.border = self.border;
290
+ if (self.innerPadding) attrs["inner-padding"] = self.innerPadding;
291
+ if (self.borderColor) attrs["border-color"] = self.borderColor;
292
+ if (self.borderWidth) attrs["border-width"] = self.borderWidth;
293
+ if (self.borderStyle) attrs["border-style"] = self.borderStyle;
294
+ return attrs;
295
+ },
296
+ /**
297
+ * Check if this is a text-based block
298
+ */
299
+ get isTextBlock() {
300
+ return self.type === "text" /* TEXT */;
301
+ },
302
+ /**
303
+ * Check if this is an image block
304
+ */
305
+ get isImageBlock() {
306
+ return self.type === "image" /* IMAGE */;
307
+ },
308
+ /**
309
+ * Check if this is a button block
310
+ */
311
+ get isButtonBlock() {
312
+ return self.type === "button" /* BUTTON */;
313
+ },
314
+ /**
315
+ * Check if this block is locked (header/footer)
316
+ */
317
+ get isLocked() {
318
+ return self.locked || self.type === "header" /* HEADER */ || self.type === "footer" /* FOOTER */;
319
+ },
320
+ /**
321
+ * Get display name for layers panel
322
+ */
323
+ get displayName() {
324
+ switch (self.type) {
325
+ case "text" /* TEXT */:
326
+ const text = self.content.replace(/<[^>]*>/g, "").trim();
327
+ return text.length > 20 ? text.substring(0, 20) + "..." : text || "Text";
328
+ case "image" /* IMAGE */:
329
+ return self.alt || "Image";
330
+ case "button" /* BUTTON */:
331
+ return self.label || "Button";
332
+ case "divider" /* DIVIDER */:
333
+ return "Divider";
334
+ case "spacer" /* SPACER */:
335
+ return `Spacer (${self.height || "20px"})`;
336
+ case "social" /* SOCIAL */:
337
+ return "Social Links";
338
+ case "hero" /* HERO */:
339
+ return "Hero";
340
+ case "accordion" /* ACCORDION */:
341
+ return "Accordion";
342
+ case "raw" /* RAW */:
343
+ return "Custom HTML";
344
+ case "navbar" /* NAVBAR */:
345
+ return "Navigation";
346
+ case "carousel" /* CAROUSEL */:
347
+ return "Carousel";
348
+ case "table" /* TABLE */:
349
+ return "Table";
350
+ case "header" /* HEADER */:
351
+ return "Header";
352
+ case "footer" /* FOOTER */:
353
+ return "Footer";
354
+ default:
355
+ return "Block";
356
+ }
357
+ }
358
+ }));
359
+
360
+ // src/registry/blockCategories.ts
361
+ var LEAF_BLOCK_TYPES = [
362
+ "text" /* TEXT */,
363
+ "image" /* IMAGE */,
364
+ "button" /* BUTTON */,
365
+ "divider" /* DIVIDER */,
366
+ "spacer" /* SPACER */,
367
+ "social" /* SOCIAL */
368
+ ];
369
+ var CONTAINER_BLOCK_TYPES = [
370
+ "hero" /* HERO */,
371
+ "accordion" /* ACCORDION */,
372
+ "raw" /* RAW */,
373
+ "navbar" /* NAVBAR */,
374
+ "carousel" /* CAROUSEL */,
375
+ "table" /* TABLE */,
376
+ "header" /* HEADER */,
377
+ "footer" /* FOOTER */
378
+ ];
379
+ function isLeafBlockType(t) {
380
+ return LEAF_BLOCK_TYPES.includes(t);
381
+ }
382
+
383
+ export {
384
+ buildGradientCSS,
385
+ BlockType,
386
+ SocialLinkModel,
387
+ NavbarLinkModel,
388
+ CarouselImageModel,
389
+ AccordionItemModel,
390
+ SpacingModel,
391
+ BlockModel,
392
+ LEAF_BLOCK_TYPES,
393
+ CONTAINER_BLOCK_TYPES,
394
+ isLeafBlockType
395
+ };