@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.
- package/LICENSE +21 -0
- package/README.md +34 -0
- package/dist/TemplateModel-Dq4GoWpl.d.mts +14743 -0
- package/dist/TemplateModel-Dq4GoWpl.d.ts +14743 -0
- package/dist/TemplateModel-v__r5Nvi.d.mts +8786 -0
- package/dist/TemplateModel-v__r5Nvi.d.ts +8786 -0
- package/dist/chunk-2ZL7A2I6.mjs +1029 -0
- package/dist/chunk-MBTNU2JZ.mjs +395 -0
- package/dist/index.d.mts +35664 -0
- package/dist/index.d.ts +35664 -0
- package/dist/index.js +3299 -0
- package/dist/index.mjs +2287 -0
- package/dist/server.d.mts +341 -0
- package/dist/server.d.ts +341 -0
- package/dist/server.js +2492 -0
- package/dist/server.mjs +1966 -0
- package/package.json +73 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3299 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AccordionBlockSchema: () => AccordionBlockSchema,
|
|
24
|
+
AccordionItemModel: () => AccordionItemModel,
|
|
25
|
+
BackgroundGradientSchema: () => BackgroundGradientSchema,
|
|
26
|
+
BlockModel: () => BlockModel,
|
|
27
|
+
BlockRegistryImpl: () => BlockRegistryImpl,
|
|
28
|
+
BlockSchema: () => BlockSchema,
|
|
29
|
+
BlockType: () => BlockType,
|
|
30
|
+
ButtonBlockSchema: () => ButtonBlockSchema,
|
|
31
|
+
CONTAINER_BLOCK_TYPES: () => CONTAINER_BLOCK_TYPES,
|
|
32
|
+
CURRENT_TEMPLATE_VERSION: () => CURRENT_TEMPLATE_VERSION,
|
|
33
|
+
CarouselBlockSchema: () => CarouselBlockSchema,
|
|
34
|
+
CarouselImageModel: () => CarouselImageModel,
|
|
35
|
+
ColumnModel: () => ColumnModel,
|
|
36
|
+
ColumnSchema: () => ColumnSchema,
|
|
37
|
+
CustomFontSchema: () => CustomFontSchema,
|
|
38
|
+
DividerBlockSchema: () => DividerBlockSchema,
|
|
39
|
+
EditorUIStore: () => EditorUIStore,
|
|
40
|
+
EmailTemplateSchema: () => EmailTemplateSchema,
|
|
41
|
+
EmailTemplateSchemaV1_0: () => EmailTemplateSchemaV1_0,
|
|
42
|
+
EmailTemplateSchemaV1_1: () => EmailTemplateSchemaV1_1,
|
|
43
|
+
ExtraAttributesSchema: () => ExtraAttributesSchema,
|
|
44
|
+
FontDefinitionModel: () => FontDefinitionModel,
|
|
45
|
+
FooterBlockSchema: () => FooterBlockSchema,
|
|
46
|
+
GradientStopSchema: () => GradientStopSchema,
|
|
47
|
+
HeaderBlockSchema: () => HeaderBlockSchema,
|
|
48
|
+
HeroBlockSchema: () => HeroBlockSchema,
|
|
49
|
+
HistoryManager: () => HistoryManager,
|
|
50
|
+
ImageBlockSchema: () => ImageBlockSchema,
|
|
51
|
+
LEAF_BLOCK_TYPES: () => LEAF_BLOCK_TYPES,
|
|
52
|
+
MJML_WRAPPER_DEFAULT_PADDING: () => MJML_WRAPPER_DEFAULT_PADDING,
|
|
53
|
+
MjmlHeadSchema: () => MjmlHeadSchema,
|
|
54
|
+
NavbarBlockSchema: () => NavbarBlockSchema,
|
|
55
|
+
NavbarLinkModel: () => NavbarLinkModel,
|
|
56
|
+
RawBlockSchema: () => RawBlockSchema,
|
|
57
|
+
RootStore: () => RootStore,
|
|
58
|
+
SUPPORTED_TEMPLATE_VERSIONS: () => SUPPORTED_TEMPLATE_VERSIONS,
|
|
59
|
+
SectionModel: () => SectionModel,
|
|
60
|
+
SectionSchema: () => SectionSchema,
|
|
61
|
+
SectionSchemaV1_0: () => SectionSchemaV1_0,
|
|
62
|
+
SelectionManager: () => SelectionManager,
|
|
63
|
+
SocialBlockSchema: () => SocialBlockSchema,
|
|
64
|
+
SocialLinkModel: () => SocialLinkModel,
|
|
65
|
+
SpacerBlockSchema: () => SpacerBlockSchema,
|
|
66
|
+
SpacingModel: () => SpacingModel,
|
|
67
|
+
SpacingSchema: () => SpacingSchema,
|
|
68
|
+
SubColumnModel: () => SubColumnModel,
|
|
69
|
+
TableBlockSchema: () => TableBlockSchema,
|
|
70
|
+
TemplateMetadataModel: () => TemplateMetadataModel,
|
|
71
|
+
TemplateMetadataSchema: () => TemplateMetadataSchema,
|
|
72
|
+
TemplateMigrationError: () => TemplateMigrationError,
|
|
73
|
+
TemplateModel: () => TemplateModel,
|
|
74
|
+
TextBlockSchema: () => TextBlockSchema,
|
|
75
|
+
ThemeColorModel: () => ThemeColorModel,
|
|
76
|
+
TopLevelItemModel: () => TopLevelItemModel,
|
|
77
|
+
TopLevelItemSchema: () => TopLevelItemSchema,
|
|
78
|
+
WrapperModel: () => WrapperModel,
|
|
79
|
+
WrapperSchema: () => WrapperSchema,
|
|
80
|
+
allSections: () => allSections,
|
|
81
|
+
buildGradientCSS: () => buildGradientCSS,
|
|
82
|
+
cloneSectionSnapshot: () => cloneSectionSnapshot,
|
|
83
|
+
createBlockRegistry: () => createBlockRegistry,
|
|
84
|
+
createColumn: () => createColumn,
|
|
85
|
+
createEmptyStore: () => createEmptyStore,
|
|
86
|
+
createHistoryManager: () => createHistoryManager,
|
|
87
|
+
createPrebuiltTemplateRegistry: () => createPrebuiltTemplateRegistry,
|
|
88
|
+
createRootStore: () => createRootStore,
|
|
89
|
+
createSection: () => createSection,
|
|
90
|
+
createSelectionManager: () => createSelectionManager,
|
|
91
|
+
createSubColumn: () => createSubColumn,
|
|
92
|
+
createTemplate: () => createTemplate,
|
|
93
|
+
createTemplateWithDefaultSection: () => createTemplateWithDefaultSection,
|
|
94
|
+
createWrapper: () => createWrapper,
|
|
95
|
+
isLeafBlockType: () => isLeafBlockType,
|
|
96
|
+
isTemplateMigrationError: () => isTemplateMigrationError,
|
|
97
|
+
isWrapper: () => isWrapper,
|
|
98
|
+
isWrapperInstance: () => isWrapperInstance,
|
|
99
|
+
migrateTemplate: () => migrateTemplate,
|
|
100
|
+
migrateV1_0ToV1_1: () => migrateV1_0ToV1_1,
|
|
101
|
+
validateTemplate: () => validateTemplate,
|
|
102
|
+
withTemplateId: () => withTemplateId
|
|
103
|
+
});
|
|
104
|
+
module.exports = __toCommonJS(index_exports);
|
|
105
|
+
|
|
106
|
+
// src/schema/gradient.ts
|
|
107
|
+
function buildGradientCSS(gradient) {
|
|
108
|
+
if (gradient.stops.length === 0) return void 0;
|
|
109
|
+
const stopList = gradient.stops.map((s) => `${s.color} ${s.position}%`).join(", ");
|
|
110
|
+
if (gradient.type === "radial") {
|
|
111
|
+
return `radial-gradient(circle, ${stopList})`;
|
|
112
|
+
}
|
|
113
|
+
return `linear-gradient(${gradient.angle}deg, ${stopList})`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/schema/types.ts
|
|
117
|
+
function isWrapper(item) {
|
|
118
|
+
return item.type === "wrapper";
|
|
119
|
+
}
|
|
120
|
+
function allSections(items) {
|
|
121
|
+
return items.flatMap((item) => isWrapper(item) ? item.sections : [item]);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/schema/validation.ts
|
|
125
|
+
var import_zod = require("zod");
|
|
126
|
+
var SpacingSchema = import_zod.z.object({
|
|
127
|
+
top: import_zod.z.string().optional(),
|
|
128
|
+
right: import_zod.z.string().optional(),
|
|
129
|
+
bottom: import_zod.z.string().optional(),
|
|
130
|
+
left: import_zod.z.string().optional()
|
|
131
|
+
});
|
|
132
|
+
var GradientStopSchema = import_zod.z.object({
|
|
133
|
+
color: import_zod.z.string().min(1),
|
|
134
|
+
position: import_zod.z.number().min(0).max(100)
|
|
135
|
+
});
|
|
136
|
+
var BackgroundGradientSchema = import_zod.z.object({
|
|
137
|
+
type: import_zod.z.enum(["linear", "radial"]),
|
|
138
|
+
angle: import_zod.z.number().min(0).max(360),
|
|
139
|
+
stops: import_zod.z.array(GradientStopSchema).min(1)
|
|
140
|
+
});
|
|
141
|
+
var ExtraAttributesSchema = import_zod.z.record(import_zod.z.string().regex(/^[a-z][a-z0-9-]*$/), import_zod.z.string());
|
|
142
|
+
var MjmlHeadSchema = import_zod.z.object({
|
|
143
|
+
attributes: import_zod.z.string().optional(),
|
|
144
|
+
bodyAttributes: ExtraAttributesSchema.optional(),
|
|
145
|
+
headRaw: import_zod.z.string().optional()
|
|
146
|
+
});
|
|
147
|
+
var CustomFontSchema = import_zod.z.object({
|
|
148
|
+
name: import_zod.z.string(),
|
|
149
|
+
href: import_zod.z.string()
|
|
150
|
+
});
|
|
151
|
+
var TemplateMetadataSchema = import_zod.z.object({
|
|
152
|
+
name: import_zod.z.string().optional(),
|
|
153
|
+
subject: import_zod.z.string().optional(),
|
|
154
|
+
previewText: import_zod.z.string().optional(),
|
|
155
|
+
title: import_zod.z.string().optional(),
|
|
156
|
+
// Epoch milliseconds (what the editor's store emits) or an ISO 8601 string.
|
|
157
|
+
createdAt: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]).optional(),
|
|
158
|
+
updatedAt: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]).optional(),
|
|
159
|
+
// Head component settings
|
|
160
|
+
fonts: import_zod.z.array(CustomFontSchema).optional(),
|
|
161
|
+
breakpoint: import_zod.z.string().optional(),
|
|
162
|
+
customCSS: import_zod.z.string().optional(),
|
|
163
|
+
inlineCSS: import_zod.z.string().optional(),
|
|
164
|
+
mjmlHead: MjmlHeadSchema.optional()
|
|
165
|
+
});
|
|
166
|
+
var TextBlockSchema = import_zod.z.object({
|
|
167
|
+
id: import_zod.z.string(),
|
|
168
|
+
type: import_zod.z.literal("text"),
|
|
169
|
+
hidden: import_zod.z.boolean().optional(),
|
|
170
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
171
|
+
content: import_zod.z.string(),
|
|
172
|
+
align: import_zod.z.enum(["left", "center", "right", "justify"]).optional(),
|
|
173
|
+
color: import_zod.z.string().optional(),
|
|
174
|
+
fontSize: import_zod.z.string().optional(),
|
|
175
|
+
fontFamily: import_zod.z.string().optional(),
|
|
176
|
+
padding: SpacingSchema.optional(),
|
|
177
|
+
lineHeight: import_zod.z.string().optional()
|
|
178
|
+
});
|
|
179
|
+
var ImageBlockSchema = import_zod.z.object({
|
|
180
|
+
id: import_zod.z.string(),
|
|
181
|
+
type: import_zod.z.literal("image"),
|
|
182
|
+
hidden: import_zod.z.boolean().optional(),
|
|
183
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
184
|
+
src: import_zod.z.string().min(1),
|
|
185
|
+
// Allow any non-empty string, not just URLs
|
|
186
|
+
alt: import_zod.z.string().optional(),
|
|
187
|
+
width: import_zod.z.string().optional(),
|
|
188
|
+
height: import_zod.z.string().optional(),
|
|
189
|
+
align: import_zod.z.enum(["left", "center", "right"]).optional(),
|
|
190
|
+
href: import_zod.z.string().optional(),
|
|
191
|
+
// Allow any string for link
|
|
192
|
+
padding: SpacingSchema.optional(),
|
|
193
|
+
borderRadius: import_zod.z.string().optional()
|
|
194
|
+
// Rounded corners
|
|
195
|
+
});
|
|
196
|
+
var ButtonBlockSchema = import_zod.z.object({
|
|
197
|
+
id: import_zod.z.string(),
|
|
198
|
+
type: import_zod.z.literal("button"),
|
|
199
|
+
hidden: import_zod.z.boolean().optional(),
|
|
200
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
201
|
+
label: import_zod.z.string().min(1),
|
|
202
|
+
href: import_zod.z.string().min(1),
|
|
203
|
+
// Allow any non-empty string
|
|
204
|
+
align: import_zod.z.enum(["left", "center", "right"]).optional(),
|
|
205
|
+
backgroundColor: import_zod.z.string().optional(),
|
|
206
|
+
color: import_zod.z.string().optional(),
|
|
207
|
+
borderRadius: import_zod.z.string().optional(),
|
|
208
|
+
border: import_zod.z.string().optional(),
|
|
209
|
+
// CSS border shorthand
|
|
210
|
+
padding: SpacingSchema.optional(),
|
|
211
|
+
innerPadding: import_zod.z.string().optional()
|
|
212
|
+
});
|
|
213
|
+
var DividerBlockSchema = import_zod.z.object({
|
|
214
|
+
id: import_zod.z.string(),
|
|
215
|
+
type: import_zod.z.literal("divider"),
|
|
216
|
+
hidden: import_zod.z.boolean().optional(),
|
|
217
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
218
|
+
borderColor: import_zod.z.string().optional(),
|
|
219
|
+
borderWidth: import_zod.z.string().optional(),
|
|
220
|
+
borderStyle: import_zod.z.enum(["solid", "dashed", "dotted"]).optional(),
|
|
221
|
+
width: import_zod.z.string().optional(),
|
|
222
|
+
// Width of the divider line
|
|
223
|
+
padding: SpacingSchema.optional()
|
|
224
|
+
});
|
|
225
|
+
var SpacerBlockSchema = import_zod.z.object({
|
|
226
|
+
id: import_zod.z.string(),
|
|
227
|
+
type: import_zod.z.literal("spacer"),
|
|
228
|
+
hidden: import_zod.z.boolean().optional(),
|
|
229
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
230
|
+
height: import_zod.z.string()
|
|
231
|
+
});
|
|
232
|
+
var HeaderBlockSchema = import_zod.z.object({
|
|
233
|
+
id: import_zod.z.string(),
|
|
234
|
+
type: import_zod.z.literal("header"),
|
|
235
|
+
hidden: import_zod.z.boolean().optional(),
|
|
236
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
237
|
+
locked: import_zod.z.literal(true)
|
|
238
|
+
});
|
|
239
|
+
var FooterBlockSchema = import_zod.z.object({
|
|
240
|
+
id: import_zod.z.string(),
|
|
241
|
+
type: import_zod.z.literal("footer"),
|
|
242
|
+
hidden: import_zod.z.boolean().optional(),
|
|
243
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
244
|
+
locked: import_zod.z.literal(true)
|
|
245
|
+
});
|
|
246
|
+
var SocialBlockSchema = import_zod.z.object({
|
|
247
|
+
id: import_zod.z.string(),
|
|
248
|
+
type: import_zod.z.literal("social"),
|
|
249
|
+
hidden: import_zod.z.boolean().optional(),
|
|
250
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
251
|
+
mode: import_zod.z.enum(["horizontal", "vertical"]).optional(),
|
|
252
|
+
align: import_zod.z.enum(["left", "center", "right"]).optional(),
|
|
253
|
+
iconSize: import_zod.z.string().optional(),
|
|
254
|
+
iconPadding: import_zod.z.string().optional(),
|
|
255
|
+
borderRadius: import_zod.z.string().optional(),
|
|
256
|
+
// For round icons
|
|
257
|
+
links: import_zod.z.array(
|
|
258
|
+
import_zod.z.object({
|
|
259
|
+
platform: import_zod.z.enum(["facebook", "twitter", "instagram", "linkedin", "youtube", "pinterest", "github"]),
|
|
260
|
+
url: import_zod.z.string(),
|
|
261
|
+
color: import_zod.z.string().optional()
|
|
262
|
+
// Per-icon color override (uses platform default if not set)
|
|
263
|
+
})
|
|
264
|
+
)
|
|
265
|
+
});
|
|
266
|
+
var HeroBlockSchema = import_zod.z.object({
|
|
267
|
+
id: import_zod.z.string(),
|
|
268
|
+
type: import_zod.z.literal("hero"),
|
|
269
|
+
hidden: import_zod.z.boolean().optional(),
|
|
270
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
271
|
+
backgroundImage: import_zod.z.string().min(1),
|
|
272
|
+
backgroundHeight: import_zod.z.string().optional(),
|
|
273
|
+
backgroundWidth: import_zod.z.string().optional(),
|
|
274
|
+
backgroundColor: import_zod.z.string().optional(),
|
|
275
|
+
verticalAlign: import_zod.z.enum(["top", "middle", "bottom"]).optional(),
|
|
276
|
+
mode: import_zod.z.enum(["fluid-height", "fixed-height"]).optional()
|
|
277
|
+
});
|
|
278
|
+
var AccordionBlockSchema = import_zod.z.object({
|
|
279
|
+
id: import_zod.z.string(),
|
|
280
|
+
type: import_zod.z.literal("accordion"),
|
|
281
|
+
hidden: import_zod.z.boolean().optional(),
|
|
282
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
283
|
+
items: import_zod.z.array(
|
|
284
|
+
import_zod.z.object({
|
|
285
|
+
title: import_zod.z.string(),
|
|
286
|
+
content: import_zod.z.string()
|
|
287
|
+
})
|
|
288
|
+
),
|
|
289
|
+
iconPosition: import_zod.z.enum(["left", "right"]).optional(),
|
|
290
|
+
borderColor: import_zod.z.string().optional(),
|
|
291
|
+
fontFamily: import_zod.z.string().optional()
|
|
292
|
+
});
|
|
293
|
+
var RawBlockSchema = import_zod.z.object({
|
|
294
|
+
id: import_zod.z.string(),
|
|
295
|
+
type: import_zod.z.literal("raw"),
|
|
296
|
+
hidden: import_zod.z.boolean().optional(),
|
|
297
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
298
|
+
html: import_zod.z.string()
|
|
299
|
+
});
|
|
300
|
+
var NavbarBlockSchema = import_zod.z.object({
|
|
301
|
+
id: import_zod.z.string(),
|
|
302
|
+
type: import_zod.z.literal("navbar"),
|
|
303
|
+
hidden: import_zod.z.boolean().optional(),
|
|
304
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
305
|
+
links: import_zod.z.array(
|
|
306
|
+
import_zod.z.object({
|
|
307
|
+
label: import_zod.z.string(),
|
|
308
|
+
href: import_zod.z.string(),
|
|
309
|
+
color: import_zod.z.string().optional()
|
|
310
|
+
})
|
|
311
|
+
),
|
|
312
|
+
hamburger: import_zod.z.boolean().optional(),
|
|
313
|
+
baseUrl: import_zod.z.string().optional(),
|
|
314
|
+
align: import_zod.z.enum(["left", "center", "right"]).optional(),
|
|
315
|
+
icoColor: import_zod.z.string().optional(),
|
|
316
|
+
padding: SpacingSchema.optional()
|
|
317
|
+
});
|
|
318
|
+
var CarouselBlockSchema = import_zod.z.object({
|
|
319
|
+
id: import_zod.z.string(),
|
|
320
|
+
type: import_zod.z.literal("carousel"),
|
|
321
|
+
hidden: import_zod.z.boolean().optional(),
|
|
322
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
323
|
+
images: import_zod.z.array(
|
|
324
|
+
import_zod.z.object({
|
|
325
|
+
src: import_zod.z.string(),
|
|
326
|
+
alt: import_zod.z.string().optional(),
|
|
327
|
+
href: import_zod.z.string().optional(),
|
|
328
|
+
thumbnailSrc: import_zod.z.string().optional()
|
|
329
|
+
})
|
|
330
|
+
),
|
|
331
|
+
thumbnails: import_zod.z.enum(["visible", "hidden"]).optional(),
|
|
332
|
+
borderRadius: import_zod.z.string().optional(),
|
|
333
|
+
iconWidth: import_zod.z.string().optional(),
|
|
334
|
+
tbBorderRadius: import_zod.z.string().optional(),
|
|
335
|
+
padding: SpacingSchema.optional()
|
|
336
|
+
});
|
|
337
|
+
var TableBlockSchema = import_zod.z.object({
|
|
338
|
+
id: import_zod.z.string(),
|
|
339
|
+
type: import_zod.z.literal("table"),
|
|
340
|
+
hidden: import_zod.z.boolean().optional(),
|
|
341
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
342
|
+
headers: import_zod.z.array(import_zod.z.string()),
|
|
343
|
+
rows: import_zod.z.array(import_zod.z.array(import_zod.z.string())),
|
|
344
|
+
align: import_zod.z.enum(["left", "center", "right"]).optional(),
|
|
345
|
+
color: import_zod.z.string().optional(),
|
|
346
|
+
fontFamily: import_zod.z.string().optional(),
|
|
347
|
+
fontSize: import_zod.z.string().optional(),
|
|
348
|
+
cellpadding: import_zod.z.string().optional(),
|
|
349
|
+
cellspacing: import_zod.z.string().optional(),
|
|
350
|
+
border: import_zod.z.string().optional(),
|
|
351
|
+
padding: SpacingSchema.optional()
|
|
352
|
+
});
|
|
353
|
+
var BlockSchema = import_zod.z.discriminatedUnion("type", [
|
|
354
|
+
TextBlockSchema,
|
|
355
|
+
ImageBlockSchema,
|
|
356
|
+
ButtonBlockSchema,
|
|
357
|
+
DividerBlockSchema,
|
|
358
|
+
SpacerBlockSchema,
|
|
359
|
+
HeaderBlockSchema,
|
|
360
|
+
FooterBlockSchema,
|
|
361
|
+
SocialBlockSchema,
|
|
362
|
+
HeroBlockSchema,
|
|
363
|
+
AccordionBlockSchema,
|
|
364
|
+
RawBlockSchema,
|
|
365
|
+
NavbarBlockSchema,
|
|
366
|
+
CarouselBlockSchema,
|
|
367
|
+
TableBlockSchema
|
|
368
|
+
]);
|
|
369
|
+
var ColumnSchema = import_zod.z.object({
|
|
370
|
+
id: import_zod.z.string(),
|
|
371
|
+
width: import_zod.z.number().min(0).max(100).optional(),
|
|
372
|
+
backgroundColor: import_zod.z.string().optional(),
|
|
373
|
+
backgroundGradient: BackgroundGradientSchema.optional(),
|
|
374
|
+
padding: SpacingSchema.optional(),
|
|
375
|
+
verticalAlign: import_zod.z.enum(["top", "middle", "bottom"]).optional(),
|
|
376
|
+
// Vertical content alignment
|
|
377
|
+
hidden: import_zod.z.boolean().optional(),
|
|
378
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
379
|
+
blocks: import_zod.z.array(BlockSchema)
|
|
380
|
+
});
|
|
381
|
+
var sectionFields = {
|
|
382
|
+
id: import_zod.z.string(),
|
|
383
|
+
type: import_zod.z.literal("section"),
|
|
384
|
+
backgroundColor: import_zod.z.string().optional(),
|
|
385
|
+
backgroundImage: import_zod.z.string().optional(),
|
|
386
|
+
backgroundPosition: import_zod.z.string().optional(),
|
|
387
|
+
backgroundRepeat: import_zod.z.enum(["repeat", "no-repeat"]).optional(),
|
|
388
|
+
backgroundSize: import_zod.z.string().optional(),
|
|
389
|
+
backgroundGradient: BackgroundGradientSchema.optional(),
|
|
390
|
+
padding: SpacingSchema.optional(),
|
|
391
|
+
noStack: import_zod.z.boolean().optional(),
|
|
392
|
+
fullWidth: import_zod.z.boolean().optional(),
|
|
393
|
+
hidden: import_zod.z.boolean().optional(),
|
|
394
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
395
|
+
bodyRaw: import_zod.z.boolean().optional(),
|
|
396
|
+
columns: import_zod.z.array(ColumnSchema).min(1)
|
|
397
|
+
};
|
|
398
|
+
var SectionSchema = import_zod.z.object({
|
|
399
|
+
...sectionFields,
|
|
400
|
+
isWrapper: import_zod.z.undefined({ invalid_type_error: "isWrapper is a schema 1.0 field; a 1.1 document holds a wrapper instead" }).optional()
|
|
401
|
+
});
|
|
402
|
+
var SectionSchemaV1_0 = import_zod.z.object({
|
|
403
|
+
...sectionFields,
|
|
404
|
+
isWrapper: import_zod.z.boolean().optional()
|
|
405
|
+
});
|
|
406
|
+
var WrapperSchema = import_zod.z.object({
|
|
407
|
+
id: import_zod.z.string(),
|
|
408
|
+
type: import_zod.z.literal("wrapper"),
|
|
409
|
+
hidden: import_zod.z.boolean().optional(),
|
|
410
|
+
backgroundColor: import_zod.z.string().optional(),
|
|
411
|
+
backgroundImage: import_zod.z.string().optional(),
|
|
412
|
+
backgroundGradient: BackgroundGradientSchema.optional(),
|
|
413
|
+
backgroundPosition: import_zod.z.string().optional(),
|
|
414
|
+
backgroundRepeat: import_zod.z.enum(["repeat", "no-repeat"]).optional(),
|
|
415
|
+
backgroundSize: import_zod.z.string().optional(),
|
|
416
|
+
border: import_zod.z.string().optional(),
|
|
417
|
+
borderTop: import_zod.z.string().optional(),
|
|
418
|
+
borderRight: import_zod.z.string().optional(),
|
|
419
|
+
borderBottom: import_zod.z.string().optional(),
|
|
420
|
+
borderLeft: import_zod.z.string().optional(),
|
|
421
|
+
borderRadius: import_zod.z.string().optional(),
|
|
422
|
+
padding: SpacingSchema.optional(),
|
|
423
|
+
fullWidth: import_zod.z.boolean().optional(),
|
|
424
|
+
cssClass: import_zod.z.string().optional(),
|
|
425
|
+
gap: import_zod.z.string().regex(/^[0-9]+(\.[0-9]+)?px$/, "gap is a length in px, e.g. 16px").optional(),
|
|
426
|
+
textAlign: import_zod.z.enum(["left", "center", "right"]).optional(),
|
|
427
|
+
extraAttributes: ExtraAttributesSchema.optional(),
|
|
428
|
+
sections: import_zod.z.array(SectionSchema)
|
|
429
|
+
});
|
|
430
|
+
var TopLevelItemSchema = import_zod.z.discriminatedUnion("type", [SectionSchema, WrapperSchema]);
|
|
431
|
+
var EmailTemplateSchemaV1_0 = import_zod.z.object({
|
|
432
|
+
id: import_zod.z.string().optional(),
|
|
433
|
+
version: import_zod.z.literal("1.0"),
|
|
434
|
+
metadata: TemplateMetadataSchema,
|
|
435
|
+
sections: import_zod.z.array(SectionSchemaV1_0)
|
|
436
|
+
});
|
|
437
|
+
var EmailTemplateSchemaV1_1 = import_zod.z.object({
|
|
438
|
+
id: import_zod.z.string().optional(),
|
|
439
|
+
version: import_zod.z.literal("1.1"),
|
|
440
|
+
metadata: TemplateMetadataSchema,
|
|
441
|
+
sections: import_zod.z.array(TopLevelItemSchema)
|
|
442
|
+
});
|
|
443
|
+
var EmailTemplateSchema = import_zod.z.discriminatedUnion("version", [EmailTemplateSchemaV1_0, EmailTemplateSchemaV1_1]);
|
|
444
|
+
function validateTemplate(template) {
|
|
445
|
+
return EmailTemplateSchema.safeParse(template);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// src/schema/migrate.ts
|
|
449
|
+
var import_nanoid = require("nanoid");
|
|
450
|
+
var CURRENT_TEMPLATE_VERSION = "1.1";
|
|
451
|
+
var SUPPORTED_TEMPLATE_VERSIONS = ["1.0", CURRENT_TEMPLATE_VERSION];
|
|
452
|
+
var TemplateMigrationError = class extends Error {
|
|
453
|
+
constructor(code, message, options = {}) {
|
|
454
|
+
super(message);
|
|
455
|
+
this.name = "TemplateMigrationError";
|
|
456
|
+
this.code = code;
|
|
457
|
+
this.version = options.version;
|
|
458
|
+
this.issues = options.issues ?? [];
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
function isTemplateMigrationError(error) {
|
|
462
|
+
return error instanceof TemplateMigrationError;
|
|
463
|
+
}
|
|
464
|
+
function parseVersion(version) {
|
|
465
|
+
const match = /^(\d+)\.(\d+)$/.exec(version);
|
|
466
|
+
if (!match) return null;
|
|
467
|
+
return [Number(match[1]), Number(match[2])];
|
|
468
|
+
}
|
|
469
|
+
function compareVersions(a, b) {
|
|
470
|
+
return a[0] !== b[0] ? a[0] - b[0] : a[1] - b[1];
|
|
471
|
+
}
|
|
472
|
+
function migrateTemplate(doc) {
|
|
473
|
+
if (typeof doc !== "object" || doc === null || Array.isArray(doc)) {
|
|
474
|
+
throw new TemplateMigrationError(
|
|
475
|
+
"INVALID_INPUT",
|
|
476
|
+
`Expected a template document object, received ${doc === null ? "null" : Array.isArray(doc) ? "an array" : typeof doc}.`
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
const version = doc.version;
|
|
480
|
+
if (typeof version !== "string" || version.length === 0) {
|
|
481
|
+
throw new TemplateMigrationError(
|
|
482
|
+
"MISSING_VERSION",
|
|
483
|
+
'The template document has no "version" field, so its schema cannot be determined.'
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
const parsed = parseVersion(version);
|
|
487
|
+
if (!parsed) {
|
|
488
|
+
throw new TemplateMigrationError(
|
|
489
|
+
"UNSUPPORTED_VERSION",
|
|
490
|
+
`Template schema version "${version}" is not of the form "major.minor".`,
|
|
491
|
+
{ version }
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
const current = parseVersion(CURRENT_TEMPLATE_VERSION);
|
|
495
|
+
if (compareVersions(parsed, current) > 0) {
|
|
496
|
+
throw new TemplateMigrationError(
|
|
497
|
+
"NEWER_VERSION",
|
|
498
|
+
`Template schema version "${version}" is newer than this editor supports (${CURRENT_TEMPLATE_VERSION}). Upgrade the @marlinjai/email-editor packages to open it.`,
|
|
499
|
+
{ version }
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
if (!SUPPORTED_TEMPLATE_VERSIONS.includes(version)) {
|
|
503
|
+
throw new TemplateMigrationError(
|
|
504
|
+
"UNSUPPORTED_VERSION",
|
|
505
|
+
`Template schema version "${version}" is not supported and has no migration to ${CURRENT_TEMPLATE_VERSION}.`,
|
|
506
|
+
{ version }
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
let next = doc;
|
|
510
|
+
if (version === "1.0") {
|
|
511
|
+
assertValid(EmailTemplateSchemaV1_0, next, version);
|
|
512
|
+
next = migrateV1_0ToV1_1(next);
|
|
513
|
+
}
|
|
514
|
+
assertValid(EmailTemplateSchemaV1_1, next, version);
|
|
515
|
+
return next;
|
|
516
|
+
}
|
|
517
|
+
function assertValid(schema, doc, version) {
|
|
518
|
+
const result = schema.safeParse(doc);
|
|
519
|
+
if (result.success) return;
|
|
520
|
+
const issues = result.error.issues.map((issue) => ({ path: issue.path, message: issue.message }));
|
|
521
|
+
const first = issues[0];
|
|
522
|
+
throw new TemplateMigrationError(
|
|
523
|
+
"INVALID_DOCUMENT",
|
|
524
|
+
`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)` : ""}.`,
|
|
525
|
+
{ version, issues }
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
function migrateV1_0ToV1_1(doc) {
|
|
529
|
+
if (!doc.sections.some((s) => s.isWrapper === true && !s.bodyRaw)) {
|
|
530
|
+
return { ...doc, version: "1.1", sections: doc.sections.map(stripIsWrapper) };
|
|
531
|
+
}
|
|
532
|
+
const ids = /* @__PURE__ */ new Set();
|
|
533
|
+
for (const s of doc.sections) {
|
|
534
|
+
ids.add(s.id);
|
|
535
|
+
for (const c of s.columns ?? []) {
|
|
536
|
+
ids.add(c.id);
|
|
537
|
+
for (const b of c.blocks ?? []) ids.add(b.id);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
const freshId = (base) => {
|
|
541
|
+
let candidate = `${base}-inner`;
|
|
542
|
+
for (let n = 2; ids.has(candidate); n++) candidate = `${base}-inner-${n}`;
|
|
543
|
+
ids.add(candidate);
|
|
544
|
+
return candidate;
|
|
545
|
+
};
|
|
546
|
+
const sections = doc.sections.map((s) => {
|
|
547
|
+
if (s.isWrapper !== true || s.bodyRaw) return stripIsWrapper(s);
|
|
548
|
+
const { isWrapper: _flag, noStack: _noStack, ...rest } = s;
|
|
549
|
+
const wrapper = { id: s.id, type: "wrapper", sections: [{ id: freshId(s.id), type: "section", columns: s.columns }] };
|
|
550
|
+
if (rest.hidden !== void 0) wrapper.hidden = rest.hidden;
|
|
551
|
+
if (rest.backgroundColor !== void 0) wrapper.backgroundColor = rest.backgroundColor;
|
|
552
|
+
if (rest.backgroundImage !== void 0) wrapper.backgroundImage = rest.backgroundImage;
|
|
553
|
+
if (rest.backgroundGradient !== void 0) wrapper.backgroundGradient = rest.backgroundGradient;
|
|
554
|
+
if (rest.backgroundPosition !== void 0) wrapper.backgroundPosition = rest.backgroundPosition;
|
|
555
|
+
if (rest.backgroundRepeat !== void 0) wrapper.backgroundRepeat = rest.backgroundRepeat;
|
|
556
|
+
if (rest.backgroundSize !== void 0) wrapper.backgroundSize = rest.backgroundSize;
|
|
557
|
+
if (rest.fullWidth !== void 0) wrapper.fullWidth = rest.fullWidth;
|
|
558
|
+
if (rest.padding !== void 0) wrapper.padding = rest.padding;
|
|
559
|
+
if (rest.extraAttributes !== void 0) wrapper.extraAttributes = rest.extraAttributes;
|
|
560
|
+
return wrapper;
|
|
561
|
+
});
|
|
562
|
+
return { ...doc, version: "1.1", sections };
|
|
563
|
+
}
|
|
564
|
+
function stripIsWrapper(s) {
|
|
565
|
+
if (!("isWrapper" in s)) return s;
|
|
566
|
+
const { isWrapper: _flag, ...rest } = s;
|
|
567
|
+
return rest;
|
|
568
|
+
}
|
|
569
|
+
function withTemplateId(doc) {
|
|
570
|
+
if (typeof doc.id === "string" && doc.id.length > 0) return doc;
|
|
571
|
+
return { ...doc, id: (0, import_nanoid.nanoid)() };
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// src/registry/BlockRegistry.ts
|
|
575
|
+
var BlockRegistryImpl = class {
|
|
576
|
+
constructor() {
|
|
577
|
+
this.definitions = /* @__PURE__ */ new Map();
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Register a new block definition
|
|
581
|
+
*/
|
|
582
|
+
register(definition) {
|
|
583
|
+
if (this.definitions.has(definition.type)) {
|
|
584
|
+
console.warn(`Block type "${definition.type}" is already registered. Overwriting.`);
|
|
585
|
+
}
|
|
586
|
+
this.definitions.set(definition.type, definition);
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Unregister a block definition
|
|
590
|
+
*/
|
|
591
|
+
unregister(type) {
|
|
592
|
+
this.definitions.delete(type);
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Get a block definition by type
|
|
596
|
+
*/
|
|
597
|
+
get(type) {
|
|
598
|
+
return this.definitions.get(type);
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Get all registered block definitions
|
|
602
|
+
*/
|
|
603
|
+
getAll() {
|
|
604
|
+
return Array.from(this.definitions.values());
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Get blocks by category
|
|
608
|
+
*/
|
|
609
|
+
getByCategory(category) {
|
|
610
|
+
return this.getAll().filter((def) => def.category === category);
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Check if a block type is registered
|
|
614
|
+
*/
|
|
615
|
+
has(type) {
|
|
616
|
+
return this.definitions.has(type);
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
function createBlockRegistry() {
|
|
620
|
+
return new BlockRegistryImpl();
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// src/store/mst/models/BlockModel.ts
|
|
624
|
+
var import_mobx_state_tree = require("mobx-state-tree");
|
|
625
|
+
|
|
626
|
+
// src/store/mst/models/spacingSnapshot.ts
|
|
627
|
+
var SIDES = [
|
|
628
|
+
["top", "paddingTop"],
|
|
629
|
+
["right", "paddingRight"],
|
|
630
|
+
["bottom", "paddingBottom"],
|
|
631
|
+
["left", "paddingLeft"]
|
|
632
|
+
];
|
|
633
|
+
function paddingIn(snapshot) {
|
|
634
|
+
if (!snapshot || typeof snapshot !== "object") return snapshot;
|
|
635
|
+
const { padding, ...rest } = snapshot;
|
|
636
|
+
if (padding === void 0) return snapshot;
|
|
637
|
+
const flat = rest;
|
|
638
|
+
if (padding && typeof padding === "object") {
|
|
639
|
+
for (const [side, field] of SIDES) {
|
|
640
|
+
const value = padding[side];
|
|
641
|
+
if (flat[field] === void 0 && typeof value === "string" && value !== "") flat[field] = value;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return flat;
|
|
645
|
+
}
|
|
646
|
+
function paddingOut(snapshot) {
|
|
647
|
+
if (!snapshot || typeof snapshot !== "object") return snapshot;
|
|
648
|
+
const { paddingTop, paddingRight, paddingBottom, paddingLeft, ...rest } = snapshot;
|
|
649
|
+
const values = {
|
|
650
|
+
top: paddingTop,
|
|
651
|
+
right: paddingRight,
|
|
652
|
+
bottom: paddingBottom,
|
|
653
|
+
left: paddingLeft
|
|
654
|
+
};
|
|
655
|
+
const padding = {};
|
|
656
|
+
for (const [side] of SIDES) {
|
|
657
|
+
const value = values[side];
|
|
658
|
+
if (typeof value === "string" && value !== "") padding[side] = value;
|
|
659
|
+
}
|
|
660
|
+
return Object.keys(padding).length > 0 ? { ...rest, padding } : rest;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// src/store/mst/models/filledDefaults.ts
|
|
664
|
+
var same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
665
|
+
function fillDefaults(snapshot, defaults) {
|
|
666
|
+
if (!snapshot || typeof snapshot !== "object") return snapshot;
|
|
667
|
+
const s = { ...snapshot };
|
|
668
|
+
const filled = { ...s.filled ?? {} };
|
|
669
|
+
for (const [key, make] of Object.entries(defaults)) {
|
|
670
|
+
if (s[key] !== void 0) continue;
|
|
671
|
+
const value = make();
|
|
672
|
+
s[key] = value;
|
|
673
|
+
filled[key] = value;
|
|
674
|
+
}
|
|
675
|
+
if (Object.keys(filled).length > 0) s.filled = filled;
|
|
676
|
+
return s;
|
|
677
|
+
}
|
|
678
|
+
function dropFilled(snapshot) {
|
|
679
|
+
if (!snapshot || typeof snapshot !== "object") return snapshot;
|
|
680
|
+
const { filled, ...rest } = snapshot;
|
|
681
|
+
if (filled) {
|
|
682
|
+
for (const [key, value] of Object.entries(filled)) {
|
|
683
|
+
if (same(rest[key], value)) delete rest[key];
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
for (const key of Object.keys(rest)) if (rest[key] === void 0) delete rest[key];
|
|
687
|
+
return rest;
|
|
688
|
+
}
|
|
689
|
+
var empty = () => [];
|
|
690
|
+
var no = () => false;
|
|
691
|
+
var BLOCK_DEFAULTS = {
|
|
692
|
+
hidden: no,
|
|
693
|
+
content: () => "",
|
|
694
|
+
links: empty,
|
|
695
|
+
items: empty,
|
|
696
|
+
navLinks: empty,
|
|
697
|
+
hamburger: no,
|
|
698
|
+
images: empty,
|
|
699
|
+
headers: empty,
|
|
700
|
+
rows: empty,
|
|
701
|
+
locked: no
|
|
702
|
+
};
|
|
703
|
+
var COLUMN_DEFAULTS = {
|
|
704
|
+
width: () => 100,
|
|
705
|
+
hidden: no,
|
|
706
|
+
subColumns: empty
|
|
707
|
+
};
|
|
708
|
+
var SECTION_DEFAULTS = {
|
|
709
|
+
type: () => "section",
|
|
710
|
+
fullWidth: no,
|
|
711
|
+
noStack: no,
|
|
712
|
+
hidden: no
|
|
713
|
+
};
|
|
714
|
+
var WRAPPER_DEFAULTS = {
|
|
715
|
+
hidden: no,
|
|
716
|
+
fullWidth: no
|
|
717
|
+
};
|
|
718
|
+
function fillColumnWidths(section) {
|
|
719
|
+
if (!section || typeof section !== "object") return section;
|
|
720
|
+
const s = section;
|
|
721
|
+
if (!Array.isArray(s.columns) || s.columns.length === 0) return section;
|
|
722
|
+
if (s.columns.every((c) => c?.width !== void 0)) return section;
|
|
723
|
+
const share = 100 / s.columns.length;
|
|
724
|
+
return {
|
|
725
|
+
...s,
|
|
726
|
+
columns: s.columns.map((c) => {
|
|
727
|
+
const col = c;
|
|
728
|
+
if (!col || typeof col !== "object" || col.width !== void 0) return c;
|
|
729
|
+
return { ...col, width: share, filled: { ...col.filled ?? {}, width: share } };
|
|
730
|
+
})
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// src/store/mst/models/BlockModel.ts
|
|
735
|
+
var BlockType = /* @__PURE__ */ ((BlockType4) => {
|
|
736
|
+
BlockType4["TEXT"] = "text";
|
|
737
|
+
BlockType4["IMAGE"] = "image";
|
|
738
|
+
BlockType4["BUTTON"] = "button";
|
|
739
|
+
BlockType4["DIVIDER"] = "divider";
|
|
740
|
+
BlockType4["SPACER"] = "spacer";
|
|
741
|
+
BlockType4["SOCIAL"] = "social";
|
|
742
|
+
BlockType4["HERO"] = "hero";
|
|
743
|
+
BlockType4["ACCORDION"] = "accordion";
|
|
744
|
+
BlockType4["RAW"] = "raw";
|
|
745
|
+
BlockType4["NAVBAR"] = "navbar";
|
|
746
|
+
BlockType4["CAROUSEL"] = "carousel";
|
|
747
|
+
BlockType4["TABLE"] = "table";
|
|
748
|
+
BlockType4["HEADER"] = "header";
|
|
749
|
+
BlockType4["FOOTER"] = "footer";
|
|
750
|
+
return BlockType4;
|
|
751
|
+
})(BlockType || {});
|
|
752
|
+
var SocialLinkModel = import_mobx_state_tree.types.model("SocialLink", {
|
|
753
|
+
platform: import_mobx_state_tree.types.string,
|
|
754
|
+
url: import_mobx_state_tree.types.string,
|
|
755
|
+
color: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string)
|
|
756
|
+
});
|
|
757
|
+
var NavbarLinkModel = import_mobx_state_tree.types.model("NavbarLink", {
|
|
758
|
+
href: import_mobx_state_tree.types.string,
|
|
759
|
+
label: import_mobx_state_tree.types.string,
|
|
760
|
+
color: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string)
|
|
761
|
+
});
|
|
762
|
+
var CarouselImageModel = import_mobx_state_tree.types.model("CarouselImage", {
|
|
763
|
+
src: import_mobx_state_tree.types.string,
|
|
764
|
+
alt: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
765
|
+
href: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
766
|
+
thumbnailSrc: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string)
|
|
767
|
+
});
|
|
768
|
+
var AccordionItemModel = import_mobx_state_tree.types.model("AccordionItem", {
|
|
769
|
+
title: import_mobx_state_tree.types.string,
|
|
770
|
+
content: import_mobx_state_tree.types.string
|
|
771
|
+
});
|
|
772
|
+
var SpacingModel = import_mobx_state_tree.types.model("Spacing", {
|
|
773
|
+
top: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
774
|
+
right: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
775
|
+
bottom: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
776
|
+
left: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string)
|
|
777
|
+
});
|
|
778
|
+
var BlockModelBase = import_mobx_state_tree.types.model("Block", {
|
|
779
|
+
id: import_mobx_state_tree.types.identifier,
|
|
780
|
+
type: import_mobx_state_tree.types.enumeration("BlockType", Object.values(BlockType)),
|
|
781
|
+
hidden: import_mobx_state_tree.types.optional(import_mobx_state_tree.types.boolean, false),
|
|
782
|
+
// === Common Text Properties ===
|
|
783
|
+
content: import_mobx_state_tree.types.optional(import_mobx_state_tree.types.string, ""),
|
|
784
|
+
color: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
785
|
+
backgroundColor: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
786
|
+
fontSize: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
787
|
+
fontFamily: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
788
|
+
align: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.enumeration(["left", "center", "right", "justify"])),
|
|
789
|
+
lineHeight: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
790
|
+
// === Spacing ===
|
|
791
|
+
paddingTop: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
792
|
+
paddingRight: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
793
|
+
paddingBottom: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
794
|
+
paddingLeft: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
795
|
+
// === Image Properties ===
|
|
796
|
+
src: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
797
|
+
alt: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
798
|
+
width: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
799
|
+
height: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
800
|
+
// === Button Properties ===
|
|
801
|
+
href: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
802
|
+
label: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
803
|
+
borderRadius: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
804
|
+
border: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
805
|
+
innerPadding: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
806
|
+
// === Divider Properties ===
|
|
807
|
+
borderColor: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
808
|
+
borderWidth: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
809
|
+
borderStyle: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.enumeration(["solid", "dashed", "dotted"])),
|
|
810
|
+
// === Spacer Properties ===
|
|
811
|
+
// (uses height from image properties)
|
|
812
|
+
// === Social Properties ===
|
|
813
|
+
links: import_mobx_state_tree.types.optional(import_mobx_state_tree.types.array(SocialLinkModel), []),
|
|
814
|
+
iconSize: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
815
|
+
iconPadding: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
816
|
+
mode: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.enumeration(["horizontal", "vertical", "fixed-height", "fluid-height"])),
|
|
817
|
+
// === Hero Properties ===
|
|
818
|
+
backgroundImage: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
819
|
+
backgroundHeight: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
820
|
+
backgroundWidth: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
821
|
+
verticalAlign: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.enumeration(["top", "middle", "bottom"])),
|
|
822
|
+
// === Accordion Properties ===
|
|
823
|
+
items: import_mobx_state_tree.types.optional(import_mobx_state_tree.types.array(AccordionItemModel), []),
|
|
824
|
+
iconPosition: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.enumeration(["left", "right"])),
|
|
825
|
+
// === Raw Properties ===
|
|
826
|
+
html: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
827
|
+
// === Navbar Properties ===
|
|
828
|
+
navLinks: import_mobx_state_tree.types.optional(import_mobx_state_tree.types.array(NavbarLinkModel), []),
|
|
829
|
+
hamburger: import_mobx_state_tree.types.optional(import_mobx_state_tree.types.boolean, false),
|
|
830
|
+
baseUrl: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
831
|
+
icoColor: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
832
|
+
// === Carousel Properties ===
|
|
833
|
+
images: import_mobx_state_tree.types.optional(import_mobx_state_tree.types.array(CarouselImageModel), []),
|
|
834
|
+
thumbnails: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.enumeration(["visible", "hidden"])),
|
|
835
|
+
iconWidth: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
836
|
+
tbBorderRadius: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
837
|
+
// === Table Properties ===
|
|
838
|
+
headers: import_mobx_state_tree.types.optional(import_mobx_state_tree.types.array(import_mobx_state_tree.types.string), []),
|
|
839
|
+
rows: import_mobx_state_tree.types.optional(import_mobx_state_tree.types.array(import_mobx_state_tree.types.array(import_mobx_state_tree.types.string)), []),
|
|
840
|
+
cellpadding: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
841
|
+
cellspacing: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
|
|
842
|
+
// === Locked Blocks (Header/Footer) ===
|
|
843
|
+
locked: import_mobx_state_tree.types.optional(import_mobx_state_tree.types.boolean, false),
|
|
844
|
+
// === MJML attributes the inspector has no control for (kept from an import) ===
|
|
845
|
+
extraAttributes: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.frozen()),
|
|
846
|
+
/** Defaults the store filled when it opened the node; they go back out only if changed (see `filledDefaults.ts`). */
|
|
847
|
+
filled: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.frozen())
|
|
848
|
+
}).actions((self) => ({
|
|
849
|
+
/**
|
|
850
|
+
* Update any style property by name
|
|
851
|
+
*/
|
|
852
|
+
updateStyle(property, value) {
|
|
853
|
+
if (property in self) {
|
|
854
|
+
self[property] = value;
|
|
855
|
+
}
|
|
856
|
+
},
|
|
857
|
+
/**
|
|
858
|
+
* Update multiple properties at once
|
|
859
|
+
*/
|
|
860
|
+
updateProperties(updates) {
|
|
861
|
+
Object.entries(updates).forEach(([key, value]) => {
|
|
862
|
+
if (key in self) {
|
|
863
|
+
self[key] = value;
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
},
|
|
867
|
+
/**
|
|
868
|
+
* Set block content (for text blocks)
|
|
869
|
+
*/
|
|
870
|
+
setContent(content) {
|
|
871
|
+
self.content = content;
|
|
872
|
+
},
|
|
873
|
+
/**
|
|
874
|
+
* Toggle block visibility
|
|
875
|
+
*/
|
|
876
|
+
toggleHidden() {
|
|
877
|
+
self.hidden = !self.hidden;
|
|
878
|
+
},
|
|
879
|
+
/**
|
|
880
|
+
* Set padding values
|
|
881
|
+
*/
|
|
882
|
+
setPadding(padding) {
|
|
883
|
+
if (padding.top !== void 0) self.paddingTop = padding.top;
|
|
884
|
+
if (padding.right !== void 0) self.paddingRight = padding.right;
|
|
885
|
+
if (padding.bottom !== void 0) self.paddingBottom = padding.bottom;
|
|
886
|
+
if (padding.left !== void 0) self.paddingLeft = padding.left;
|
|
887
|
+
},
|
|
888
|
+
/**
|
|
889
|
+
* Add a social link
|
|
890
|
+
*/
|
|
891
|
+
addSocialLink(platform, url, color) {
|
|
892
|
+
self.links.push(SocialLinkModel.create({ platform, url, color }));
|
|
893
|
+
},
|
|
894
|
+
/**
|
|
895
|
+
* Remove a social link by index
|
|
896
|
+
*/
|
|
897
|
+
removeSocialLink(index) {
|
|
898
|
+
self.links.splice(index, 1);
|
|
899
|
+
},
|
|
900
|
+
/**
|
|
901
|
+
* Add a navbar link
|
|
902
|
+
*/
|
|
903
|
+
addNavbarLink(href, label, color) {
|
|
904
|
+
self.navLinks.push(NavbarLinkModel.create({ href, label, color }));
|
|
905
|
+
},
|
|
906
|
+
/**
|
|
907
|
+
* Remove a navbar link by index
|
|
908
|
+
*/
|
|
909
|
+
removeNavbarLink(index) {
|
|
910
|
+
self.navLinks.splice(index, 1);
|
|
911
|
+
},
|
|
912
|
+
/**
|
|
913
|
+
* Add a carousel image
|
|
914
|
+
*/
|
|
915
|
+
addCarouselImage(src, alt, href, thumbnailSrc) {
|
|
916
|
+
self.images.push(CarouselImageModel.create({ src, alt, href, thumbnailSrc }));
|
|
917
|
+
},
|
|
918
|
+
/**
|
|
919
|
+
* Remove a carousel image by index
|
|
920
|
+
*/
|
|
921
|
+
removeCarouselImage(index) {
|
|
922
|
+
self.images.splice(index, 1);
|
|
923
|
+
},
|
|
924
|
+
/**
|
|
925
|
+
* Add an accordion item
|
|
926
|
+
*/
|
|
927
|
+
addAccordionItem(title, content) {
|
|
928
|
+
self.items.push(AccordionItemModel.create({ title, content }));
|
|
929
|
+
},
|
|
930
|
+
/**
|
|
931
|
+
* Remove an accordion item by index
|
|
932
|
+
*/
|
|
933
|
+
removeAccordionItem(index) {
|
|
934
|
+
self.items.splice(index, 1);
|
|
935
|
+
},
|
|
936
|
+
/**
|
|
937
|
+
* Add a table row
|
|
938
|
+
*/
|
|
939
|
+
addTableRow(cells) {
|
|
940
|
+
self.rows.push(cells);
|
|
941
|
+
},
|
|
942
|
+
/**
|
|
943
|
+
* Remove a table row by index
|
|
944
|
+
*/
|
|
945
|
+
removeTableRow(index) {
|
|
946
|
+
self.rows.splice(index, 1);
|
|
947
|
+
}
|
|
948
|
+
})).views((self) => ({
|
|
949
|
+
/**
|
|
950
|
+
* Computed style object for React rendering
|
|
951
|
+
*/
|
|
952
|
+
get computedStyle() {
|
|
953
|
+
const style = {};
|
|
954
|
+
if (self.color) style.color = self.color;
|
|
955
|
+
if (self.backgroundColor) style.backgroundColor = self.backgroundColor;
|
|
956
|
+
if (self.fontSize) style.fontSize = self.fontSize;
|
|
957
|
+
if (self.fontFamily) style.fontFamily = self.fontFamily;
|
|
958
|
+
if (self.align) style.textAlign = self.align;
|
|
959
|
+
if (self.lineHeight) style.lineHeight = self.lineHeight;
|
|
960
|
+
if (self.paddingTop) style.paddingTop = self.paddingTop;
|
|
961
|
+
if (self.paddingRight) style.paddingRight = self.paddingRight;
|
|
962
|
+
if (self.paddingBottom) style.paddingBottom = self.paddingBottom;
|
|
963
|
+
if (self.paddingLeft) style.paddingLeft = self.paddingLeft;
|
|
964
|
+
if (self.borderRadius) style.borderRadius = self.borderRadius;
|
|
965
|
+
if (self.border) style.border = self.border;
|
|
966
|
+
return style;
|
|
967
|
+
},
|
|
968
|
+
/**
|
|
969
|
+
* Get padding as a single object
|
|
970
|
+
*/
|
|
971
|
+
get padding() {
|
|
972
|
+
return {
|
|
973
|
+
top: self.paddingTop || void 0,
|
|
974
|
+
right: self.paddingRight || void 0,
|
|
975
|
+
bottom: self.paddingBottom || void 0,
|
|
976
|
+
left: self.paddingLeft || void 0
|
|
977
|
+
};
|
|
978
|
+
},
|
|
979
|
+
/**
|
|
980
|
+
* Get padding as a CSS string (e.g., "10px 20px 10px 20px")
|
|
981
|
+
*/
|
|
982
|
+
get paddingString() {
|
|
983
|
+
const { paddingTop, paddingRight, paddingBottom, paddingLeft } = self;
|
|
984
|
+
if (!paddingTop && !paddingRight && !paddingBottom && !paddingLeft) {
|
|
985
|
+
return void 0;
|
|
986
|
+
}
|
|
987
|
+
return `${paddingTop || "0"} ${paddingRight || "0"} ${paddingBottom || "0"} ${paddingLeft || "0"}`;
|
|
988
|
+
},
|
|
989
|
+
/**
|
|
990
|
+
* MJML attributes for export
|
|
991
|
+
*/
|
|
992
|
+
get mjmlAttributes() {
|
|
993
|
+
const attrs = {};
|
|
994
|
+
if (self.color) attrs.color = self.color;
|
|
995
|
+
if (self.backgroundColor) attrs["background-color"] = self.backgroundColor;
|
|
996
|
+
if (self.fontSize) attrs["font-size"] = self.fontSize;
|
|
997
|
+
if (self.fontFamily) attrs["font-family"] = self.fontFamily;
|
|
998
|
+
if (self.align) attrs.align = self.align;
|
|
999
|
+
if (self.lineHeight) attrs["line-height"] = self.lineHeight;
|
|
1000
|
+
const paddingParts = [];
|
|
1001
|
+
if (self.paddingTop) paddingParts.push(`top:${self.paddingTop}`);
|
|
1002
|
+
if (self.paddingRight) paddingParts.push(`right:${self.paddingRight}`);
|
|
1003
|
+
if (self.paddingBottom) paddingParts.push(`bottom:${self.paddingBottom}`);
|
|
1004
|
+
if (self.paddingLeft) paddingParts.push(`left:${self.paddingLeft}`);
|
|
1005
|
+
if (paddingParts.length > 0) {
|
|
1006
|
+
const padding = `${self.paddingTop || "0"} ${self.paddingRight || "0"} ${self.paddingBottom || "0"} ${self.paddingLeft || "0"}`;
|
|
1007
|
+
attrs.padding = padding;
|
|
1008
|
+
}
|
|
1009
|
+
if (self.src) attrs.src = self.src;
|
|
1010
|
+
if (self.alt) attrs.alt = self.alt;
|
|
1011
|
+
if (self.width) attrs.width = self.width;
|
|
1012
|
+
if (self.height) attrs.height = self.height;
|
|
1013
|
+
if (self.href) attrs.href = self.href;
|
|
1014
|
+
if (self.borderRadius) attrs["border-radius"] = self.borderRadius;
|
|
1015
|
+
if (self.border) attrs.border = self.border;
|
|
1016
|
+
if (self.innerPadding) attrs["inner-padding"] = self.innerPadding;
|
|
1017
|
+
if (self.borderColor) attrs["border-color"] = self.borderColor;
|
|
1018
|
+
if (self.borderWidth) attrs["border-width"] = self.borderWidth;
|
|
1019
|
+
if (self.borderStyle) attrs["border-style"] = self.borderStyle;
|
|
1020
|
+
return attrs;
|
|
1021
|
+
},
|
|
1022
|
+
/**
|
|
1023
|
+
* Check if this is a text-based block
|
|
1024
|
+
*/
|
|
1025
|
+
get isTextBlock() {
|
|
1026
|
+
return self.type === "text" /* TEXT */;
|
|
1027
|
+
},
|
|
1028
|
+
/**
|
|
1029
|
+
* Check if this is an image block
|
|
1030
|
+
*/
|
|
1031
|
+
get isImageBlock() {
|
|
1032
|
+
return self.type === "image" /* IMAGE */;
|
|
1033
|
+
},
|
|
1034
|
+
/**
|
|
1035
|
+
* Check if this is a button block
|
|
1036
|
+
*/
|
|
1037
|
+
get isButtonBlock() {
|
|
1038
|
+
return self.type === "button" /* BUTTON */;
|
|
1039
|
+
},
|
|
1040
|
+
/**
|
|
1041
|
+
* Check if this block is locked (header/footer)
|
|
1042
|
+
*/
|
|
1043
|
+
get isLocked() {
|
|
1044
|
+
return self.locked || self.type === "header" /* HEADER */ || self.type === "footer" /* FOOTER */;
|
|
1045
|
+
},
|
|
1046
|
+
/**
|
|
1047
|
+
* Get display name for layers panel
|
|
1048
|
+
*/
|
|
1049
|
+
get displayName() {
|
|
1050
|
+
switch (self.type) {
|
|
1051
|
+
case "text" /* TEXT */:
|
|
1052
|
+
const text = self.content.replace(/<[^>]*>/g, "").trim();
|
|
1053
|
+
return text.length > 20 ? text.substring(0, 20) + "..." : text || "Text";
|
|
1054
|
+
case "image" /* IMAGE */:
|
|
1055
|
+
return self.alt || "Image";
|
|
1056
|
+
case "button" /* BUTTON */:
|
|
1057
|
+
return self.label || "Button";
|
|
1058
|
+
case "divider" /* DIVIDER */:
|
|
1059
|
+
return "Divider";
|
|
1060
|
+
case "spacer" /* SPACER */:
|
|
1061
|
+
return `Spacer (${self.height || "20px"})`;
|
|
1062
|
+
case "social" /* SOCIAL */:
|
|
1063
|
+
return "Social Links";
|
|
1064
|
+
case "hero" /* HERO */:
|
|
1065
|
+
return "Hero";
|
|
1066
|
+
case "accordion" /* ACCORDION */:
|
|
1067
|
+
return "Accordion";
|
|
1068
|
+
case "raw" /* RAW */:
|
|
1069
|
+
return "Custom HTML";
|
|
1070
|
+
case "navbar" /* NAVBAR */:
|
|
1071
|
+
return "Navigation";
|
|
1072
|
+
case "carousel" /* CAROUSEL */:
|
|
1073
|
+
return "Carousel";
|
|
1074
|
+
case "table" /* TABLE */:
|
|
1075
|
+
return "Table";
|
|
1076
|
+
case "header" /* HEADER */:
|
|
1077
|
+
return "Header";
|
|
1078
|
+
case "footer" /* FOOTER */:
|
|
1079
|
+
return "Footer";
|
|
1080
|
+
default:
|
|
1081
|
+
return "Block";
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}));
|
|
1085
|
+
var BlockModel = BlockModelBase.preProcessSnapshot((raw) => {
|
|
1086
|
+
let snapshot = paddingIn(raw);
|
|
1087
|
+
if (snapshot && snapshot.type === "navbar") {
|
|
1088
|
+
const { links, navLinks, ...rest } = snapshot;
|
|
1089
|
+
const stored = navLinks && navLinks.length > 0 ? navLinks : links ?? [];
|
|
1090
|
+
snapshot = { ...rest, navLinks: stored };
|
|
1091
|
+
}
|
|
1092
|
+
return fillDefaults(snapshot, BLOCK_DEFAULTS);
|
|
1093
|
+
}).postProcessSnapshot((raw) => {
|
|
1094
|
+
const snapshot = paddingOut(dropFilled(raw));
|
|
1095
|
+
if (snapshot.type !== "navbar") return snapshot;
|
|
1096
|
+
const { navLinks, links: _socialLinks, ...rest } = snapshot;
|
|
1097
|
+
return { ...rest, links: navLinks ?? [] };
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
// src/registry/blockCategories.ts
|
|
1101
|
+
var LEAF_BLOCK_TYPES = [
|
|
1102
|
+
"text" /* TEXT */,
|
|
1103
|
+
"image" /* IMAGE */,
|
|
1104
|
+
"button" /* BUTTON */,
|
|
1105
|
+
"divider" /* DIVIDER */,
|
|
1106
|
+
"spacer" /* SPACER */,
|
|
1107
|
+
"social" /* SOCIAL */
|
|
1108
|
+
];
|
|
1109
|
+
var CONTAINER_BLOCK_TYPES = [
|
|
1110
|
+
"hero" /* HERO */,
|
|
1111
|
+
"accordion" /* ACCORDION */,
|
|
1112
|
+
"raw" /* RAW */,
|
|
1113
|
+
"navbar" /* NAVBAR */,
|
|
1114
|
+
"carousel" /* CAROUSEL */,
|
|
1115
|
+
"table" /* TABLE */,
|
|
1116
|
+
"header" /* HEADER */,
|
|
1117
|
+
"footer" /* FOOTER */
|
|
1118
|
+
];
|
|
1119
|
+
function isLeafBlockType(t) {
|
|
1120
|
+
return LEAF_BLOCK_TYPES.includes(t);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// src/history/HistoryManager.ts
|
|
1124
|
+
var import_immer = require("immer");
|
|
1125
|
+
var HistoryManager = class {
|
|
1126
|
+
constructor(initialState, maxHistorySize = 50) {
|
|
1127
|
+
this.history = [];
|
|
1128
|
+
this.currentIndex = -1;
|
|
1129
|
+
this.maxHistorySize = maxHistorySize;
|
|
1130
|
+
this.history.push(initialState);
|
|
1131
|
+
this.currentIndex = 0;
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Get current state
|
|
1135
|
+
*/
|
|
1136
|
+
getCurrentState() {
|
|
1137
|
+
return this.history[this.currentIndex];
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Update state with a producer function (Immer)
|
|
1141
|
+
* Adds new state to history
|
|
1142
|
+
*/
|
|
1143
|
+
updateState(producer) {
|
|
1144
|
+
const newState = (0, import_immer.produce)(this.getCurrentState(), producer);
|
|
1145
|
+
if (this.currentIndex < this.history.length - 1) {
|
|
1146
|
+
this.history = this.history.slice(0, this.currentIndex + 1);
|
|
1147
|
+
}
|
|
1148
|
+
this.history.push(newState);
|
|
1149
|
+
if (this.history.length > this.maxHistorySize) {
|
|
1150
|
+
this.history = this.history.slice(-this.maxHistorySize);
|
|
1151
|
+
this.currentIndex = this.history.length - 1;
|
|
1152
|
+
} else {
|
|
1153
|
+
this.currentIndex++;
|
|
1154
|
+
}
|
|
1155
|
+
return newState;
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Replace current state without affecting history
|
|
1159
|
+
*/
|
|
1160
|
+
replaceState(newState) {
|
|
1161
|
+
this.history[this.currentIndex] = newState;
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Undo to previous state
|
|
1165
|
+
*/
|
|
1166
|
+
undo() {
|
|
1167
|
+
if (!this.canUndo()) {
|
|
1168
|
+
return null;
|
|
1169
|
+
}
|
|
1170
|
+
this.currentIndex--;
|
|
1171
|
+
return this.getCurrentState();
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Redo to next state
|
|
1175
|
+
*/
|
|
1176
|
+
redo() {
|
|
1177
|
+
if (!this.canRedo()) {
|
|
1178
|
+
return null;
|
|
1179
|
+
}
|
|
1180
|
+
this.currentIndex++;
|
|
1181
|
+
return this.getCurrentState();
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Check if undo is available
|
|
1185
|
+
*/
|
|
1186
|
+
canUndo() {
|
|
1187
|
+
return this.currentIndex > 0;
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Check if redo is available
|
|
1191
|
+
*/
|
|
1192
|
+
canRedo() {
|
|
1193
|
+
return this.currentIndex < this.history.length - 1;
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Clear all history and set new initial state
|
|
1197
|
+
*/
|
|
1198
|
+
reset(initialState) {
|
|
1199
|
+
this.history = [initialState];
|
|
1200
|
+
this.currentIndex = 0;
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Get history size
|
|
1204
|
+
*/
|
|
1205
|
+
getHistorySize() {
|
|
1206
|
+
return this.history.length;
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Get current index
|
|
1210
|
+
*/
|
|
1211
|
+
getCurrentIndex() {
|
|
1212
|
+
return this.currentIndex;
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
function createHistoryManager(initialState, maxHistorySize = 50) {
|
|
1216
|
+
return new HistoryManager(initialState, maxHistorySize);
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
// src/selection/SelectionManager.ts
|
|
1220
|
+
var SelectionManager = class {
|
|
1221
|
+
constructor() {
|
|
1222
|
+
this.state = {
|
|
1223
|
+
blockId: null,
|
|
1224
|
+
sectionId: null
|
|
1225
|
+
};
|
|
1226
|
+
this.listeners = [];
|
|
1227
|
+
}
|
|
1228
|
+
/**
|
|
1229
|
+
* Get current selection
|
|
1230
|
+
*/
|
|
1231
|
+
getSelection() {
|
|
1232
|
+
return { ...this.state };
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Select a block
|
|
1236
|
+
*/
|
|
1237
|
+
selectBlock(blockId, sectionId) {
|
|
1238
|
+
this.state = { blockId, sectionId };
|
|
1239
|
+
this.notify();
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Select a section
|
|
1243
|
+
*/
|
|
1244
|
+
selectSection(sectionId) {
|
|
1245
|
+
this.state = { blockId: null, sectionId };
|
|
1246
|
+
this.notify();
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Clear selection
|
|
1250
|
+
*/
|
|
1251
|
+
clearSelection() {
|
|
1252
|
+
this.state = { blockId: null, sectionId: null };
|
|
1253
|
+
this.notify();
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Subscribe to selection changes
|
|
1257
|
+
*/
|
|
1258
|
+
subscribe(listener) {
|
|
1259
|
+
this.listeners.push(listener);
|
|
1260
|
+
return () => {
|
|
1261
|
+
this.listeners = this.listeners.filter((l) => l !== listener);
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Notify all listeners
|
|
1266
|
+
*/
|
|
1267
|
+
notify() {
|
|
1268
|
+
this.listeners.forEach((listener) => listener(this.state));
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1271
|
+
function createSelectionManager() {
|
|
1272
|
+
return new SelectionManager();
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
// src/templates/registry.ts
|
|
1276
|
+
var PrebuiltTemplateRegistryImpl = class {
|
|
1277
|
+
constructor() {
|
|
1278
|
+
this.templates = /* @__PURE__ */ new Map();
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Register a pre-built template
|
|
1282
|
+
*/
|
|
1283
|
+
register(template) {
|
|
1284
|
+
this.templates.set(template.id, template);
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Get a template by ID
|
|
1288
|
+
*/
|
|
1289
|
+
get(id) {
|
|
1290
|
+
return this.templates.get(id);
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Get all registered templates
|
|
1294
|
+
*/
|
|
1295
|
+
getAll() {
|
|
1296
|
+
return Array.from(this.templates.values());
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Get templates filtered by category
|
|
1300
|
+
*/
|
|
1301
|
+
getByCategory(category) {
|
|
1302
|
+
return this.getAll().filter((t) => t.category === category);
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
function createPrebuiltTemplateRegistry() {
|
|
1306
|
+
return new PrebuiltTemplateRegistryImpl();
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
// src/store/mst/models/ColumnModel.ts
|
|
1310
|
+
var import_mobx_state_tree3 = require("mobx-state-tree");
|
|
1311
|
+
var import_nanoid3 = require("nanoid");
|
|
1312
|
+
|
|
1313
|
+
// src/store/mst/models/SubColumnModel.ts
|
|
1314
|
+
var import_mobx_state_tree2 = require("mobx-state-tree");
|
|
1315
|
+
var import_nanoid2 = require("nanoid");
|
|
1316
|
+
var SubColumnModel = import_mobx_state_tree2.types.model("SubColumn", {
|
|
1317
|
+
id: import_mobx_state_tree2.types.identifier,
|
|
1318
|
+
width: import_mobx_state_tree2.types.optional(import_mobx_state_tree2.types.number, 50),
|
|
1319
|
+
backgroundColor: import_mobx_state_tree2.types.maybe(import_mobx_state_tree2.types.string),
|
|
1320
|
+
backgroundGradient: import_mobx_state_tree2.types.maybe(import_mobx_state_tree2.types.frozen()),
|
|
1321
|
+
verticalAlign: import_mobx_state_tree2.types.maybe(import_mobx_state_tree2.types.enumeration(["top", "middle", "bottom"])),
|
|
1322
|
+
paddingTop: import_mobx_state_tree2.types.maybe(import_mobx_state_tree2.types.string),
|
|
1323
|
+
paddingRight: import_mobx_state_tree2.types.maybe(import_mobx_state_tree2.types.string),
|
|
1324
|
+
paddingBottom: import_mobx_state_tree2.types.maybe(import_mobx_state_tree2.types.string),
|
|
1325
|
+
paddingLeft: import_mobx_state_tree2.types.maybe(import_mobx_state_tree2.types.string),
|
|
1326
|
+
blocks: import_mobx_state_tree2.types.array(BlockModel)
|
|
1327
|
+
}).actions((self) => ({
|
|
1328
|
+
addBlock(block, index) {
|
|
1329
|
+
const type = block.type;
|
|
1330
|
+
if (!isLeafBlockType(type)) {
|
|
1331
|
+
throw new Error(`SubColumn only accepts leaf blocks; got "${type}"`);
|
|
1332
|
+
}
|
|
1333
|
+
const blockToAdd = (0, import_mobx_state_tree2.isStateTreeNode)(block) ? (0, import_mobx_state_tree2.detach)(block) : BlockModel.create(block);
|
|
1334
|
+
if (index !== void 0 && index >= 0 && index <= self.blocks.length) {
|
|
1335
|
+
self.blocks.splice(index, 0, blockToAdd);
|
|
1336
|
+
} else {
|
|
1337
|
+
self.blocks.push(blockToAdd);
|
|
1338
|
+
}
|
|
1339
|
+
return blockToAdd;
|
|
1340
|
+
},
|
|
1341
|
+
removeBlock(blockId) {
|
|
1342
|
+
const block = self.blocks.find((b) => b.id === blockId);
|
|
1343
|
+
if (block) {
|
|
1344
|
+
(0, import_mobx_state_tree2.destroy)(block);
|
|
1345
|
+
return true;
|
|
1346
|
+
}
|
|
1347
|
+
return false;
|
|
1348
|
+
},
|
|
1349
|
+
moveBlock(fromIndex, toIndex) {
|
|
1350
|
+
if (fromIndex < 0 || fromIndex >= self.blocks.length) return false;
|
|
1351
|
+
if (toIndex < 0 || toIndex > self.blocks.length) return false;
|
|
1352
|
+
if (fromIndex === toIndex) return false;
|
|
1353
|
+
const [block] = self.blocks.splice(fromIndex, 1);
|
|
1354
|
+
const adj = toIndex > fromIndex ? toIndex - 1 : toIndex;
|
|
1355
|
+
self.blocks.splice(adj, 0, block);
|
|
1356
|
+
return true;
|
|
1357
|
+
},
|
|
1358
|
+
detachBlock(blockId) {
|
|
1359
|
+
const block = self.blocks.find((b) => b.id === blockId);
|
|
1360
|
+
return block ? (0, import_mobx_state_tree2.detach)(block) : void 0;
|
|
1361
|
+
},
|
|
1362
|
+
setWidth(width) {
|
|
1363
|
+
self.width = Math.max(0, Math.min(100, width));
|
|
1364
|
+
},
|
|
1365
|
+
setPadding(p) {
|
|
1366
|
+
if (p.top !== void 0) self.paddingTop = p.top;
|
|
1367
|
+
if (p.right !== void 0) self.paddingRight = p.right;
|
|
1368
|
+
if (p.bottom !== void 0) self.paddingBottom = p.bottom;
|
|
1369
|
+
if (p.left !== void 0) self.paddingLeft = p.left;
|
|
1370
|
+
},
|
|
1371
|
+
updateProperties(updates) {
|
|
1372
|
+
Object.entries(updates).forEach(([k, v]) => {
|
|
1373
|
+
if (k in self) self[k] = v;
|
|
1374
|
+
});
|
|
1375
|
+
},
|
|
1376
|
+
clearBlocks() {
|
|
1377
|
+
self.blocks.forEach((b) => (0, import_mobx_state_tree2.destroy)(b));
|
|
1378
|
+
self.blocks.clear();
|
|
1379
|
+
}
|
|
1380
|
+
})).views((self) => ({
|
|
1381
|
+
get isEmpty() {
|
|
1382
|
+
return self.blocks.length === 0;
|
|
1383
|
+
},
|
|
1384
|
+
get computedStyle() {
|
|
1385
|
+
const style = { width: `${self.width}%` };
|
|
1386
|
+
if (self.backgroundGradient) {
|
|
1387
|
+
const css = buildGradientCSS(self.backgroundGradient);
|
|
1388
|
+
if (css) style.backgroundImage = css;
|
|
1389
|
+
}
|
|
1390
|
+
if (self.backgroundColor) style.backgroundColor = self.backgroundColor;
|
|
1391
|
+
if (self.verticalAlign) style.verticalAlign = self.verticalAlign;
|
|
1392
|
+
if (self.paddingTop) style.paddingTop = self.paddingTop;
|
|
1393
|
+
if (self.paddingRight) style.paddingRight = self.paddingRight;
|
|
1394
|
+
if (self.paddingBottom) style.paddingBottom = self.paddingBottom;
|
|
1395
|
+
if (self.paddingLeft) style.paddingLeft = self.paddingLeft;
|
|
1396
|
+
return style;
|
|
1397
|
+
}
|
|
1398
|
+
}));
|
|
1399
|
+
function createSubColumn(opts = {}) {
|
|
1400
|
+
return {
|
|
1401
|
+
id: opts.id ?? (0, import_nanoid2.nanoid)(),
|
|
1402
|
+
width: opts.width ?? 50,
|
|
1403
|
+
blocks: opts.blocks ?? []
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
// src/store/mst/models/ColumnModel.ts
|
|
1408
|
+
var ColumnModelBase = import_mobx_state_tree3.types.model("Column", {
|
|
1409
|
+
id: import_mobx_state_tree3.types.identifier,
|
|
1410
|
+
width: import_mobx_state_tree3.types.optional(import_mobx_state_tree3.types.number, 100),
|
|
1411
|
+
// percentage (e.g., 50 for 50%)
|
|
1412
|
+
backgroundColor: import_mobx_state_tree3.types.maybe(import_mobx_state_tree3.types.string),
|
|
1413
|
+
backgroundGradient: import_mobx_state_tree3.types.maybe(import_mobx_state_tree3.types.frozen()),
|
|
1414
|
+
verticalAlign: import_mobx_state_tree3.types.maybe(import_mobx_state_tree3.types.enumeration(["top", "middle", "bottom"])),
|
|
1415
|
+
paddingTop: import_mobx_state_tree3.types.maybe(import_mobx_state_tree3.types.string),
|
|
1416
|
+
paddingRight: import_mobx_state_tree3.types.maybe(import_mobx_state_tree3.types.string),
|
|
1417
|
+
paddingBottom: import_mobx_state_tree3.types.maybe(import_mobx_state_tree3.types.string),
|
|
1418
|
+
paddingLeft: import_mobx_state_tree3.types.maybe(import_mobx_state_tree3.types.string),
|
|
1419
|
+
hidden: import_mobx_state_tree3.types.optional(import_mobx_state_tree3.types.boolean, false),
|
|
1420
|
+
blocks: import_mobx_state_tree3.types.array(BlockModel),
|
|
1421
|
+
/**
|
|
1422
|
+
* Optional sub-columns for depth-2 nesting.
|
|
1423
|
+
* Invariant: blocks XOR subColumns. Both can be empty, but never both populated.
|
|
1424
|
+
*
|
|
1425
|
+
* `types.late` is used to break TypeScript's deep inference, which otherwise
|
|
1426
|
+
* makes the inferred RootStore type exceed the compiler's serialization limit.
|
|
1427
|
+
*/
|
|
1428
|
+
subColumns: import_mobx_state_tree3.types.optional(import_mobx_state_tree3.types.array(import_mobx_state_tree3.types.late(() => SubColumnModel)), []),
|
|
1429
|
+
/** MJML attributes the inspector has no control for (kept from an import). */
|
|
1430
|
+
extraAttributes: import_mobx_state_tree3.types.maybe(import_mobx_state_tree3.types.frozen()),
|
|
1431
|
+
/** Defaults the store filled when it opened the node; they go back out only if changed (see `filledDefaults.ts`). */
|
|
1432
|
+
filled: import_mobx_state_tree3.types.maybe(import_mobx_state_tree3.types.frozen())
|
|
1433
|
+
}).preProcessSnapshot((snapshot) => {
|
|
1434
|
+
if (snapshot && Array.isArray(snapshot.blocks) && snapshot.blocks.length > 0 && Array.isArray(snapshot.subColumns) && snapshot.subColumns.length > 0) {
|
|
1435
|
+
throw new Error(
|
|
1436
|
+
"Invalid Column snapshot: cannot have both blocks and subColumns populated"
|
|
1437
|
+
);
|
|
1438
|
+
}
|
|
1439
|
+
return snapshot;
|
|
1440
|
+
}).actions((self) => ({
|
|
1441
|
+
/**
|
|
1442
|
+
* Add a block to this column
|
|
1443
|
+
*/
|
|
1444
|
+
addBlock(block, index) {
|
|
1445
|
+
if (self.subColumns.length > 0) {
|
|
1446
|
+
throw new Error("Cannot add block to a group column; merge sub-columns first");
|
|
1447
|
+
}
|
|
1448
|
+
const blockToAdd = (0, import_mobx_state_tree3.isStateTreeNode)(block) ? (0, import_mobx_state_tree3.detach)(block) : BlockModel.create(block);
|
|
1449
|
+
if (index !== void 0 && index >= 0 && index <= self.blocks.length) {
|
|
1450
|
+
self.blocks.splice(index, 0, blockToAdd);
|
|
1451
|
+
} else {
|
|
1452
|
+
self.blocks.push(blockToAdd);
|
|
1453
|
+
}
|
|
1454
|
+
return blockToAdd;
|
|
1455
|
+
},
|
|
1456
|
+
/**
|
|
1457
|
+
* Remove a block by ID
|
|
1458
|
+
*/
|
|
1459
|
+
removeBlock(blockId) {
|
|
1460
|
+
const block = self.blocks.find((b) => b.id === blockId);
|
|
1461
|
+
if (block) {
|
|
1462
|
+
(0, import_mobx_state_tree3.destroy)(block);
|
|
1463
|
+
return true;
|
|
1464
|
+
}
|
|
1465
|
+
return false;
|
|
1466
|
+
},
|
|
1467
|
+
/**
|
|
1468
|
+
* Move a block within this column
|
|
1469
|
+
*/
|
|
1470
|
+
moveBlock(fromIndex, toIndex) {
|
|
1471
|
+
if (fromIndex < 0 || fromIndex >= self.blocks.length) return false;
|
|
1472
|
+
if (toIndex < 0 || toIndex > self.blocks.length) return false;
|
|
1473
|
+
if (fromIndex === toIndex) return false;
|
|
1474
|
+
const [block] = self.blocks.splice(fromIndex, 1);
|
|
1475
|
+
const adjustedToIndex = toIndex > fromIndex ? toIndex - 1 : toIndex;
|
|
1476
|
+
self.blocks.splice(adjustedToIndex, 0, block);
|
|
1477
|
+
return true;
|
|
1478
|
+
},
|
|
1479
|
+
/**
|
|
1480
|
+
* Detach a block (remove without destroying, for moving to another column)
|
|
1481
|
+
*/
|
|
1482
|
+
detachBlock(blockId) {
|
|
1483
|
+
const block = self.blocks.find((b) => b.id === blockId);
|
|
1484
|
+
if (block) {
|
|
1485
|
+
return (0, import_mobx_state_tree3.detach)(block);
|
|
1486
|
+
}
|
|
1487
|
+
return void 0;
|
|
1488
|
+
},
|
|
1489
|
+
/**
|
|
1490
|
+
* Update column properties
|
|
1491
|
+
*/
|
|
1492
|
+
updateProperties(updates) {
|
|
1493
|
+
Object.entries(updates).forEach(([key, value]) => {
|
|
1494
|
+
if (key in self) {
|
|
1495
|
+
self[key] = value;
|
|
1496
|
+
}
|
|
1497
|
+
});
|
|
1498
|
+
},
|
|
1499
|
+
/**
|
|
1500
|
+
* Set padding
|
|
1501
|
+
*/
|
|
1502
|
+
setPadding(padding) {
|
|
1503
|
+
if (padding.top !== void 0) self.paddingTop = padding.top;
|
|
1504
|
+
if (padding.right !== void 0) self.paddingRight = padding.right;
|
|
1505
|
+
if (padding.bottom !== void 0) self.paddingBottom = padding.bottom;
|
|
1506
|
+
if (padding.left !== void 0) self.paddingLeft = padding.left;
|
|
1507
|
+
},
|
|
1508
|
+
/**
|
|
1509
|
+
* Toggle column visibility
|
|
1510
|
+
*/
|
|
1511
|
+
toggleHidden() {
|
|
1512
|
+
self.hidden = !self.hidden;
|
|
1513
|
+
},
|
|
1514
|
+
/**
|
|
1515
|
+
* Set column width
|
|
1516
|
+
*/
|
|
1517
|
+
setWidth(width) {
|
|
1518
|
+
self.width = Math.max(0, Math.min(100, width));
|
|
1519
|
+
},
|
|
1520
|
+
/**
|
|
1521
|
+
* Clear all blocks
|
|
1522
|
+
*/
|
|
1523
|
+
clearBlocks() {
|
|
1524
|
+
self.blocks.forEach((block) => (0, import_mobx_state_tree3.destroy)(block));
|
|
1525
|
+
self.blocks.clear();
|
|
1526
|
+
},
|
|
1527
|
+
/**
|
|
1528
|
+
* Convert this leaf column into a group of N sub-columns (2-4).
|
|
1529
|
+
* Existing blocks migrate into the first sub-column.
|
|
1530
|
+
*/
|
|
1531
|
+
splitIntoSubColumns(count) {
|
|
1532
|
+
if (count < 2 || count > 4) {
|
|
1533
|
+
throw new Error(`splitIntoSubColumns requires count in [2..4], got ${count}`);
|
|
1534
|
+
}
|
|
1535
|
+
const firstBlocks = self.blocks.map((b) => (0, import_mobx_state_tree3.detach)(b));
|
|
1536
|
+
self.blocks.clear();
|
|
1537
|
+
self.subColumns.clear();
|
|
1538
|
+
const base = Math.floor(100 / count * 100) / 100;
|
|
1539
|
+
for (let i = 0; i < count; i++) {
|
|
1540
|
+
const isLast = i === count - 1;
|
|
1541
|
+
const w = isLast ? Math.round((100 - base * (count - 1)) * 100) / 100 : base;
|
|
1542
|
+
self.subColumns.push(
|
|
1543
|
+
SubColumnModel.create(createSubColumn({
|
|
1544
|
+
width: w,
|
|
1545
|
+
blocks: i === 0 ? firstBlocks : []
|
|
1546
|
+
}))
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1549
|
+
},
|
|
1550
|
+
/**
|
|
1551
|
+
* Merge all sub-column blocks back into self.blocks (concatenated in order).
|
|
1552
|
+
*/
|
|
1553
|
+
mergeSubColumns() {
|
|
1554
|
+
const collected = [];
|
|
1555
|
+
for (const sc of self.subColumns) {
|
|
1556
|
+
while (sc.blocks.length > 0) {
|
|
1557
|
+
const b = (0, import_mobx_state_tree3.detach)(sc.blocks[0]);
|
|
1558
|
+
collected.push(b);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
self.subColumns.clear();
|
|
1562
|
+
for (const b of collected) {
|
|
1563
|
+
self.blocks.push(b);
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
})).views((self) => ({
|
|
1567
|
+
/**
|
|
1568
|
+
* Whether this column is a leaf (holds blocks) or a group (holds sub-columns).
|
|
1569
|
+
*/
|
|
1570
|
+
get kind() {
|
|
1571
|
+
return self.subColumns.length > 0 ? "group" : "leaf";
|
|
1572
|
+
},
|
|
1573
|
+
/**
|
|
1574
|
+
* Find a block by ID
|
|
1575
|
+
*/
|
|
1576
|
+
getBlockById(blockId) {
|
|
1577
|
+
return self.blocks.find((b) => b.id === blockId);
|
|
1578
|
+
},
|
|
1579
|
+
/**
|
|
1580
|
+
* Get block index
|
|
1581
|
+
*/
|
|
1582
|
+
getBlockIndex(blockId) {
|
|
1583
|
+
return self.blocks.findIndex((b) => b.id === blockId);
|
|
1584
|
+
},
|
|
1585
|
+
/**
|
|
1586
|
+
* Get visible blocks only
|
|
1587
|
+
*/
|
|
1588
|
+
get visibleBlocks() {
|
|
1589
|
+
return self.blocks.filter((b) => !b.hidden);
|
|
1590
|
+
},
|
|
1591
|
+
/**
|
|
1592
|
+
* Count of blocks
|
|
1593
|
+
*/
|
|
1594
|
+
get blockCount() {
|
|
1595
|
+
return self.blocks.length;
|
|
1596
|
+
},
|
|
1597
|
+
/**
|
|
1598
|
+
* Check if column is empty
|
|
1599
|
+
*/
|
|
1600
|
+
get isEmpty() {
|
|
1601
|
+
return self.blocks.length === 0;
|
|
1602
|
+
},
|
|
1603
|
+
/**
|
|
1604
|
+
* Get padding as object
|
|
1605
|
+
*/
|
|
1606
|
+
get padding() {
|
|
1607
|
+
return {
|
|
1608
|
+
top: self.paddingTop || void 0,
|
|
1609
|
+
right: self.paddingRight || void 0,
|
|
1610
|
+
bottom: self.paddingBottom || void 0,
|
|
1611
|
+
left: self.paddingLeft || void 0
|
|
1612
|
+
};
|
|
1613
|
+
},
|
|
1614
|
+
/**
|
|
1615
|
+
* Get padding as CSS string
|
|
1616
|
+
*/
|
|
1617
|
+
get paddingString() {
|
|
1618
|
+
const { paddingTop, paddingRight, paddingBottom, paddingLeft } = self;
|
|
1619
|
+
if (!paddingTop && !paddingRight && !paddingBottom && !paddingLeft) {
|
|
1620
|
+
return void 0;
|
|
1621
|
+
}
|
|
1622
|
+
return `${paddingTop || "0"} ${paddingRight || "0"} ${paddingBottom || "0"} ${paddingLeft || "0"}`;
|
|
1623
|
+
},
|
|
1624
|
+
/**
|
|
1625
|
+
* Computed style for rendering
|
|
1626
|
+
*/
|
|
1627
|
+
get computedStyle() {
|
|
1628
|
+
const style = {
|
|
1629
|
+
width: `${self.width}%`
|
|
1630
|
+
};
|
|
1631
|
+
if (self.backgroundGradient) {
|
|
1632
|
+
const css = buildGradientCSS(self.backgroundGradient);
|
|
1633
|
+
if (css) style.backgroundImage = css;
|
|
1634
|
+
}
|
|
1635
|
+
if (self.backgroundColor) style.backgroundColor = self.backgroundColor;
|
|
1636
|
+
if (self.verticalAlign) style.verticalAlign = self.verticalAlign;
|
|
1637
|
+
if (self.paddingTop) style.paddingTop = self.paddingTop;
|
|
1638
|
+
if (self.paddingRight) style.paddingRight = self.paddingRight;
|
|
1639
|
+
if (self.paddingBottom) style.paddingBottom = self.paddingBottom;
|
|
1640
|
+
if (self.paddingLeft) style.paddingLeft = self.paddingLeft;
|
|
1641
|
+
return style;
|
|
1642
|
+
},
|
|
1643
|
+
/**
|
|
1644
|
+
* MJML attributes for export
|
|
1645
|
+
*/
|
|
1646
|
+
get mjmlAttributes() {
|
|
1647
|
+
const attrs = {};
|
|
1648
|
+
if (self.width !== 100) attrs.width = `${self.width}%`;
|
|
1649
|
+
if (self.backgroundColor) attrs["background-color"] = self.backgroundColor;
|
|
1650
|
+
if (self.verticalAlign) attrs["vertical-align"] = self.verticalAlign;
|
|
1651
|
+
const hasPadding = self.paddingTop || self.paddingRight || self.paddingBottom || self.paddingLeft;
|
|
1652
|
+
if (hasPadding) {
|
|
1653
|
+
const padding = `${self.paddingTop || "0"} ${self.paddingRight || "0"} ${self.paddingBottom || "0"} ${self.paddingLeft || "0"}`;
|
|
1654
|
+
attrs.padding = padding;
|
|
1655
|
+
}
|
|
1656
|
+
return attrs;
|
|
1657
|
+
}
|
|
1658
|
+
}));
|
|
1659
|
+
var ColumnModel = ColumnModelBase.preProcessSnapshot(
|
|
1660
|
+
(snapshot) => fillDefaults(paddingIn(snapshot), COLUMN_DEFAULTS)
|
|
1661
|
+
).postProcessSnapshot((snapshot) => paddingOut(dropFilled(snapshot)));
|
|
1662
|
+
function createColumn(options = {}) {
|
|
1663
|
+
return {
|
|
1664
|
+
id: options.id || (0, import_nanoid3.nanoid)(),
|
|
1665
|
+
width: options.width || 100,
|
|
1666
|
+
blocks: options.blocks || []
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
// src/store/mst/models/SectionModel.ts
|
|
1671
|
+
var import_mobx_state_tree4 = require("mobx-state-tree");
|
|
1672
|
+
var import_nanoid4 = require("nanoid");
|
|
1673
|
+
var SectionModelBase = import_mobx_state_tree4.types.model("Section", {
|
|
1674
|
+
id: import_mobx_state_tree4.types.identifier,
|
|
1675
|
+
type: import_mobx_state_tree4.types.optional(import_mobx_state_tree4.types.literal("section"), "section"),
|
|
1676
|
+
// Background
|
|
1677
|
+
backgroundColor: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.string),
|
|
1678
|
+
backgroundImage: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.string),
|
|
1679
|
+
backgroundGradient: import_mobx_state_tree4.types.maybe(
|
|
1680
|
+
import_mobx_state_tree4.types.model("BackgroundGradient", {
|
|
1681
|
+
type: import_mobx_state_tree4.types.enumeration(["linear", "radial"]),
|
|
1682
|
+
angle: import_mobx_state_tree4.types.number,
|
|
1683
|
+
stops: import_mobx_state_tree4.types.array(
|
|
1684
|
+
import_mobx_state_tree4.types.model("GradientStop", {
|
|
1685
|
+
color: import_mobx_state_tree4.types.string,
|
|
1686
|
+
position: import_mobx_state_tree4.types.number
|
|
1687
|
+
})
|
|
1688
|
+
)
|
|
1689
|
+
})
|
|
1690
|
+
),
|
|
1691
|
+
backgroundPosition: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.string),
|
|
1692
|
+
backgroundRepeat: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.enumeration(["repeat", "no-repeat"])),
|
|
1693
|
+
backgroundSize: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.string),
|
|
1694
|
+
// Layout
|
|
1695
|
+
fullWidth: import_mobx_state_tree4.types.optional(import_mobx_state_tree4.types.boolean, false),
|
|
1696
|
+
noStack: import_mobx_state_tree4.types.optional(import_mobx_state_tree4.types.boolean, false),
|
|
1697
|
+
// mj-group behavior
|
|
1698
|
+
// Visibility
|
|
1699
|
+
hidden: import_mobx_state_tree4.types.optional(import_mobx_state_tree4.types.boolean, false),
|
|
1700
|
+
// Padding
|
|
1701
|
+
paddingTop: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.string),
|
|
1702
|
+
paddingRight: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.string),
|
|
1703
|
+
paddingBottom: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.string),
|
|
1704
|
+
paddingLeft: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.string),
|
|
1705
|
+
// Columns
|
|
1706
|
+
columns: import_mobx_state_tree4.types.array(ColumnModel),
|
|
1707
|
+
/**
|
|
1708
|
+
* Emit the section's raw blocks straight into its parent (mj-body or
|
|
1709
|
+
* mj-wrapper) instead of wrapping them in an mj-section (set by the MJML
|
|
1710
|
+
* import for markup that sat there). Ignored once the section holds any
|
|
1711
|
+
* other block.
|
|
1712
|
+
*/
|
|
1713
|
+
bodyRaw: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.boolean),
|
|
1714
|
+
/** MJML attributes the inspector has no control for (kept from an import). */
|
|
1715
|
+
extraAttributes: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.frozen()),
|
|
1716
|
+
/** Defaults the store filled when it opened the node; they go back out only if changed (see `filledDefaults.ts`). */
|
|
1717
|
+
filled: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.frozen())
|
|
1718
|
+
}).actions((self) => ({
|
|
1719
|
+
/**
|
|
1720
|
+
* Add a column to this section
|
|
1721
|
+
*/
|
|
1722
|
+
addColumn(column, index) {
|
|
1723
|
+
const columnToAdd = ColumnModel.is(column) ? (0, import_mobx_state_tree4.detach)(column) : ColumnModel.create(column);
|
|
1724
|
+
if (index !== void 0 && index >= 0 && index <= self.columns.length) {
|
|
1725
|
+
self.columns.splice(index, 0, columnToAdd);
|
|
1726
|
+
} else {
|
|
1727
|
+
self.columns.push(columnToAdd);
|
|
1728
|
+
}
|
|
1729
|
+
this.rebalanceColumnWidths();
|
|
1730
|
+
return columnToAdd;
|
|
1731
|
+
},
|
|
1732
|
+
/**
|
|
1733
|
+
* Remove a column by ID
|
|
1734
|
+
*/
|
|
1735
|
+
removeColumn(columnId) {
|
|
1736
|
+
const column = self.columns.find((c) => c.id === columnId);
|
|
1737
|
+
if (column && self.columns.length > 1) {
|
|
1738
|
+
(0, import_mobx_state_tree4.destroy)(column);
|
|
1739
|
+
this.rebalanceColumnWidths();
|
|
1740
|
+
return true;
|
|
1741
|
+
}
|
|
1742
|
+
return false;
|
|
1743
|
+
},
|
|
1744
|
+
/**
|
|
1745
|
+
* Set the number of columns (1-4)
|
|
1746
|
+
*/
|
|
1747
|
+
setColumnCount(count) {
|
|
1748
|
+
const currentCount = self.columns.length;
|
|
1749
|
+
if (count === currentCount) return;
|
|
1750
|
+
if (count > currentCount) {
|
|
1751
|
+
for (let i = currentCount; i < count; i++) {
|
|
1752
|
+
self.columns.push(ColumnModel.create(createColumn()));
|
|
1753
|
+
}
|
|
1754
|
+
} else {
|
|
1755
|
+
const blocksToMove = [];
|
|
1756
|
+
for (let i = count; i < currentCount; i++) {
|
|
1757
|
+
const column = self.columns[i];
|
|
1758
|
+
column.blocks.forEach((block) => {
|
|
1759
|
+
blocksToMove.push((0, import_mobx_state_tree4.detach)(block));
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
while (self.columns.length > count) {
|
|
1763
|
+
const column = self.columns[self.columns.length - 1];
|
|
1764
|
+
(0, import_mobx_state_tree4.destroy)(column);
|
|
1765
|
+
}
|
|
1766
|
+
blocksToMove.forEach((block) => {
|
|
1767
|
+
self.columns[0].blocks.push(block);
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
this.rebalanceColumnWidths();
|
|
1771
|
+
},
|
|
1772
|
+
/**
|
|
1773
|
+
* Rebalance column widths to equal distribution
|
|
1774
|
+
*/
|
|
1775
|
+
rebalanceColumnWidths() {
|
|
1776
|
+
const columnCount = self.columns.length;
|
|
1777
|
+
if (columnCount === 0) return;
|
|
1778
|
+
const equalWidth = Math.floor(100 / columnCount);
|
|
1779
|
+
self.columns.forEach((column, index) => {
|
|
1780
|
+
if (index === columnCount - 1) {
|
|
1781
|
+
column.setWidth(100 - equalWidth * (columnCount - 1));
|
|
1782
|
+
} else {
|
|
1783
|
+
column.setWidth(equalWidth);
|
|
1784
|
+
}
|
|
1785
|
+
});
|
|
1786
|
+
},
|
|
1787
|
+
/**
|
|
1788
|
+
* Update section properties
|
|
1789
|
+
*/
|
|
1790
|
+
updateProperties(updates) {
|
|
1791
|
+
Object.entries(updates).forEach(([key, value]) => {
|
|
1792
|
+
if (key in self) {
|
|
1793
|
+
self[key] = value;
|
|
1794
|
+
}
|
|
1795
|
+
});
|
|
1796
|
+
},
|
|
1797
|
+
/**
|
|
1798
|
+
* Set padding
|
|
1799
|
+
*/
|
|
1800
|
+
setPadding(padding) {
|
|
1801
|
+
if (padding.top !== void 0) self.paddingTop = padding.top;
|
|
1802
|
+
if (padding.right !== void 0) self.paddingRight = padding.right;
|
|
1803
|
+
if (padding.bottom !== void 0) self.paddingBottom = padding.bottom;
|
|
1804
|
+
if (padding.left !== void 0) self.paddingLeft = padding.left;
|
|
1805
|
+
},
|
|
1806
|
+
/**
|
|
1807
|
+
* Toggle section visibility
|
|
1808
|
+
*/
|
|
1809
|
+
toggleHidden() {
|
|
1810
|
+
self.hidden = !self.hidden;
|
|
1811
|
+
},
|
|
1812
|
+
/**
|
|
1813
|
+
* Toggle full width
|
|
1814
|
+
*/
|
|
1815
|
+
toggleFullWidth() {
|
|
1816
|
+
self.fullWidth = !self.fullWidth;
|
|
1817
|
+
},
|
|
1818
|
+
/**
|
|
1819
|
+
* Toggle no-stack (mj-group)
|
|
1820
|
+
*/
|
|
1821
|
+
toggleNoStack() {
|
|
1822
|
+
self.noStack = !self.noStack;
|
|
1823
|
+
},
|
|
1824
|
+
/**
|
|
1825
|
+
* Move a block from one column to another within this section
|
|
1826
|
+
*/
|
|
1827
|
+
moveBlockBetweenColumns(blockId, sourceColumnId, targetColumnId, targetIndex) {
|
|
1828
|
+
const sourceColumn = self.columns.find((c) => c.id === sourceColumnId);
|
|
1829
|
+
const targetColumn = self.columns.find((c) => c.id === targetColumnId);
|
|
1830
|
+
if (!sourceColumn || !targetColumn) return false;
|
|
1831
|
+
const block = sourceColumn.detachBlock(blockId);
|
|
1832
|
+
if (!block) return false;
|
|
1833
|
+
targetColumn.addBlock(block, targetIndex);
|
|
1834
|
+
return true;
|
|
1835
|
+
}
|
|
1836
|
+
})).views((self) => ({
|
|
1837
|
+
/**
|
|
1838
|
+
* Find a column by ID
|
|
1839
|
+
*/
|
|
1840
|
+
getColumnById(columnId) {
|
|
1841
|
+
return self.columns.find((c) => c.id === columnId);
|
|
1842
|
+
},
|
|
1843
|
+
/**
|
|
1844
|
+
* Find a block by ID (searches all columns)
|
|
1845
|
+
*/
|
|
1846
|
+
findBlockById(blockId) {
|
|
1847
|
+
for (const column of self.columns) {
|
|
1848
|
+
const block = column.getBlockById(blockId);
|
|
1849
|
+
if (block) return block;
|
|
1850
|
+
}
|
|
1851
|
+
return void 0;
|
|
1852
|
+
},
|
|
1853
|
+
/**
|
|
1854
|
+
* Find which column contains a block
|
|
1855
|
+
*/
|
|
1856
|
+
findColumnByBlockId(blockId) {
|
|
1857
|
+
for (const column of self.columns) {
|
|
1858
|
+
if (column.getBlockById(blockId)) {
|
|
1859
|
+
return column;
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
return void 0;
|
|
1863
|
+
},
|
|
1864
|
+
/**
|
|
1865
|
+
* Get column index
|
|
1866
|
+
*/
|
|
1867
|
+
getColumnIndex(columnId) {
|
|
1868
|
+
return self.columns.findIndex((c) => c.id === columnId);
|
|
1869
|
+
},
|
|
1870
|
+
/**
|
|
1871
|
+
* Get visible columns
|
|
1872
|
+
*/
|
|
1873
|
+
get visibleColumns() {
|
|
1874
|
+
return self.columns.filter((c) => !c.hidden);
|
|
1875
|
+
},
|
|
1876
|
+
/**
|
|
1877
|
+
* Column count
|
|
1878
|
+
*/
|
|
1879
|
+
get columnCount() {
|
|
1880
|
+
return self.columns.length;
|
|
1881
|
+
},
|
|
1882
|
+
/**
|
|
1883
|
+
* Total block count across all columns
|
|
1884
|
+
*/
|
|
1885
|
+
get totalBlockCount() {
|
|
1886
|
+
return self.columns.reduce((sum, col) => sum + col.blockCount, 0);
|
|
1887
|
+
},
|
|
1888
|
+
/**
|
|
1889
|
+
* Check if section is empty (all columns are empty)
|
|
1890
|
+
*/
|
|
1891
|
+
get isEmpty() {
|
|
1892
|
+
return self.columns.every((c) => c.isEmpty);
|
|
1893
|
+
},
|
|
1894
|
+
/**
|
|
1895
|
+
* Get padding as object
|
|
1896
|
+
*/
|
|
1897
|
+
get padding() {
|
|
1898
|
+
return {
|
|
1899
|
+
top: self.paddingTop || void 0,
|
|
1900
|
+
right: self.paddingRight || void 0,
|
|
1901
|
+
bottom: self.paddingBottom || void 0,
|
|
1902
|
+
left: self.paddingLeft || void 0
|
|
1903
|
+
};
|
|
1904
|
+
},
|
|
1905
|
+
/**
|
|
1906
|
+
* Get padding as CSS string
|
|
1907
|
+
*/
|
|
1908
|
+
get paddingString() {
|
|
1909
|
+
const { paddingTop, paddingRight, paddingBottom, paddingLeft } = self;
|
|
1910
|
+
if (!paddingTop && !paddingRight && !paddingBottom && !paddingLeft) {
|
|
1911
|
+
return void 0;
|
|
1912
|
+
}
|
|
1913
|
+
return `${paddingTop || "0"} ${paddingRight || "0"} ${paddingBottom || "0"} ${paddingLeft || "0"}`;
|
|
1914
|
+
},
|
|
1915
|
+
/**
|
|
1916
|
+
* Computed style for rendering
|
|
1917
|
+
*/
|
|
1918
|
+
get computedStyle() {
|
|
1919
|
+
const style = {};
|
|
1920
|
+
if (self.backgroundColor) style.backgroundColor = self.backgroundColor;
|
|
1921
|
+
const gradientCSS = self.backgroundGradient ? buildGradientCSS(self.backgroundGradient) : void 0;
|
|
1922
|
+
if (gradientCSS) {
|
|
1923
|
+
style.backgroundImage = gradientCSS;
|
|
1924
|
+
} else if (self.backgroundImage) {
|
|
1925
|
+
style.backgroundImage = `url(${self.backgroundImage})`;
|
|
1926
|
+
if (self.backgroundPosition) style.backgroundPosition = self.backgroundPosition;
|
|
1927
|
+
if (self.backgroundRepeat) style.backgroundRepeat = self.backgroundRepeat;
|
|
1928
|
+
if (self.backgroundSize) style.backgroundSize = self.backgroundSize;
|
|
1929
|
+
}
|
|
1930
|
+
if (self.paddingTop) style.paddingTop = self.paddingTop;
|
|
1931
|
+
if (self.paddingRight) style.paddingRight = self.paddingRight;
|
|
1932
|
+
if (self.paddingBottom) style.paddingBottom = self.paddingBottom;
|
|
1933
|
+
if (self.paddingLeft) style.paddingLeft = self.paddingLeft;
|
|
1934
|
+
return style;
|
|
1935
|
+
},
|
|
1936
|
+
/**
|
|
1937
|
+
* MJML attributes for export
|
|
1938
|
+
*/
|
|
1939
|
+
get mjmlAttributes() {
|
|
1940
|
+
const attrs = {};
|
|
1941
|
+
if (self.backgroundColor) attrs["background-color"] = self.backgroundColor;
|
|
1942
|
+
if (self.backgroundImage) attrs["background-url"] = self.backgroundImage;
|
|
1943
|
+
if (self.backgroundPosition) attrs["background-position"] = self.backgroundPosition;
|
|
1944
|
+
if (self.backgroundRepeat) attrs["background-repeat"] = self.backgroundRepeat;
|
|
1945
|
+
if (self.backgroundSize) attrs["background-size"] = self.backgroundSize;
|
|
1946
|
+
if (self.fullWidth) attrs["full-width"] = "full-width";
|
|
1947
|
+
const hasPadding = self.paddingTop || self.paddingRight || self.paddingBottom || self.paddingLeft;
|
|
1948
|
+
if (hasPadding) {
|
|
1949
|
+
const padding = `${self.paddingTop || "0"} ${self.paddingRight || "0"} ${self.paddingBottom || "0"} ${self.paddingLeft || "0"}`;
|
|
1950
|
+
attrs.padding = padding;
|
|
1951
|
+
}
|
|
1952
|
+
return attrs;
|
|
1953
|
+
},
|
|
1954
|
+
/**
|
|
1955
|
+
* How wide the section's visible columns are together, in percent, as MJML
|
|
1956
|
+
* lays them out: each column's width, where a column opened without one
|
|
1957
|
+
* holds MJML's share (100 / columns, whatever its siblings say). Over 100,
|
|
1958
|
+
* the last column wraps below the others on desktop.
|
|
1959
|
+
*/
|
|
1960
|
+
get columnWidthTotal() {
|
|
1961
|
+
const total = self.columns.filter((c) => !c.hidden).reduce((sum, c) => sum + c.width, 0);
|
|
1962
|
+
return Math.round(total * 100) / 100;
|
|
1963
|
+
},
|
|
1964
|
+
/** Whether the columns overflow the section ({@link columnWidthTotal} over 100). */
|
|
1965
|
+
get columnsOverflow() {
|
|
1966
|
+
return this.columnWidthTotal > 100;
|
|
1967
|
+
},
|
|
1968
|
+
/**
|
|
1969
|
+
* Display name for layers panel
|
|
1970
|
+
*/
|
|
1971
|
+
get displayName() {
|
|
1972
|
+
const count = self.columns.length;
|
|
1973
|
+
if (count === 1) {
|
|
1974
|
+
return "Full Width Section";
|
|
1975
|
+
}
|
|
1976
|
+
return `${count}-Column Section`;
|
|
1977
|
+
}
|
|
1978
|
+
}));
|
|
1979
|
+
var SectionModel = SectionModelBase.preProcessSnapshot(
|
|
1980
|
+
(snapshot) => fillDefaults(fillColumnWidths(paddingIn(snapshot)), SECTION_DEFAULTS)
|
|
1981
|
+
).postProcessSnapshot((snapshot) => paddingOut(dropFilled(snapshot)));
|
|
1982
|
+
function createSection(options = {}) {
|
|
1983
|
+
const columnCount = options.columnCount || 1;
|
|
1984
|
+
const columnWidth = Math.floor(100 / columnCount);
|
|
1985
|
+
const columns = [];
|
|
1986
|
+
for (let i = 0; i < columnCount; i++) {
|
|
1987
|
+
columns.push(createColumn({
|
|
1988
|
+
width: i === columnCount - 1 ? 100 - columnWidth * (columnCount - 1) : columnWidth
|
|
1989
|
+
}));
|
|
1990
|
+
}
|
|
1991
|
+
return {
|
|
1992
|
+
id: options.id || (0, import_nanoid4.nanoid)(),
|
|
1993
|
+
type: "section",
|
|
1994
|
+
backgroundColor: options.backgroundColor,
|
|
1995
|
+
columns
|
|
1996
|
+
};
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
// src/store/mst/models/WrapperModel.ts
|
|
2000
|
+
var import_mobx_state_tree5 = require("mobx-state-tree");
|
|
2001
|
+
var import_nanoid5 = require("nanoid");
|
|
2002
|
+
var MJML_WRAPPER_DEFAULT_PADDING = { top: "20px", right: "0px", bottom: "20px", left: "0px" };
|
|
2003
|
+
var WrapperModelBase = import_mobx_state_tree5.types.model("Wrapper", {
|
|
2004
|
+
id: import_mobx_state_tree5.types.identifier,
|
|
2005
|
+
type: import_mobx_state_tree5.types.literal("wrapper"),
|
|
2006
|
+
hidden: import_mobx_state_tree5.types.optional(import_mobx_state_tree5.types.boolean, false),
|
|
2007
|
+
// Background
|
|
2008
|
+
backgroundColor: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
|
|
2009
|
+
backgroundImage: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
|
|
2010
|
+
backgroundGradient: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.frozen()),
|
|
2011
|
+
backgroundPosition: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
|
|
2012
|
+
backgroundRepeat: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.enumeration(["repeat", "no-repeat"])),
|
|
2013
|
+
backgroundSize: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
|
|
2014
|
+
// Border
|
|
2015
|
+
border: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
|
|
2016
|
+
borderTop: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
|
|
2017
|
+
borderRight: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
|
|
2018
|
+
borderBottom: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
|
|
2019
|
+
borderLeft: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
|
|
2020
|
+
borderRadius: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
|
|
2021
|
+
// Padding (the schema's `padding` object, flat in the store; see `spacingSnapshot.ts`)
|
|
2022
|
+
paddingTop: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
|
|
2023
|
+
paddingRight: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
|
|
2024
|
+
paddingBottom: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
|
|
2025
|
+
paddingLeft: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
|
|
2026
|
+
// Layout
|
|
2027
|
+
fullWidth: import_mobx_state_tree5.types.optional(import_mobx_state_tree5.types.boolean, false),
|
|
2028
|
+
cssClass: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
|
|
2029
|
+
gap: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
|
|
2030
|
+
textAlign: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.enumeration(["left", "center", "right"])),
|
|
2031
|
+
sections: import_mobx_state_tree5.types.array(SectionModel),
|
|
2032
|
+
/** MJML attributes the inspector has no control for (kept from an import). */
|
|
2033
|
+
extraAttributes: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.frozen()),
|
|
2034
|
+
/** Defaults the store filled when it opened the node; they go back out only if changed (see `filledDefaults.ts`). */
|
|
2035
|
+
filled: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.frozen())
|
|
2036
|
+
}).actions((self) => ({
|
|
2037
|
+
/** Set any of the inspector's fields; `undefined` clears one. */
|
|
2038
|
+
updateProperties(updates) {
|
|
2039
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
2040
|
+
if (key in self) self[key] = value;
|
|
2041
|
+
}
|
|
2042
|
+
},
|
|
2043
|
+
toggleHidden() {
|
|
2044
|
+
self.hidden = !self.hidden;
|
|
2045
|
+
},
|
|
2046
|
+
toggleFullWidth() {
|
|
2047
|
+
self.fullWidth = !self.fullWidth;
|
|
2048
|
+
}
|
|
2049
|
+
})).views((self) => ({
|
|
2050
|
+
get isEmpty() {
|
|
2051
|
+
return self.sections.length === 0 || self.sections.every((s) => s.isEmpty);
|
|
2052
|
+
},
|
|
2053
|
+
get displayName() {
|
|
2054
|
+
const n = self.sections.length;
|
|
2055
|
+
return n === 0 ? "Container (empty)" : `Container, ${n} ${n === 1 ? "section" : "sections"}`;
|
|
2056
|
+
},
|
|
2057
|
+
/**
|
|
2058
|
+
* The padding the mail gets: MJML's `20px 0` when none is set. As soon as
|
|
2059
|
+
* one side is set, the compiler writes all four and an unset side is 0.
|
|
2060
|
+
*/
|
|
2061
|
+
get effectivePadding() {
|
|
2062
|
+
const { paddingTop, paddingRight, paddingBottom, paddingLeft } = self;
|
|
2063
|
+
if (!paddingTop && !paddingRight && !paddingBottom && !paddingLeft) return { ...MJML_WRAPPER_DEFAULT_PADDING };
|
|
2064
|
+
return { top: paddingTop || "0px", right: paddingRight || "0px", bottom: paddingBottom || "0px", left: paddingLeft || "0px" };
|
|
2065
|
+
},
|
|
2066
|
+
/** The wrapper's box on the canvas, styled as MJML renders it. */
|
|
2067
|
+
get computedStyle() {
|
|
2068
|
+
const style = {};
|
|
2069
|
+
const gradientCSS = self.backgroundGradient ? buildGradientCSS(self.backgroundGradient) : void 0;
|
|
2070
|
+
if (gradientCSS) {
|
|
2071
|
+
style.backgroundColor = self.backgroundGradient?.stops[0]?.color;
|
|
2072
|
+
style.backgroundImage = gradientCSS;
|
|
2073
|
+
} else {
|
|
2074
|
+
if (self.backgroundColor) style.backgroundColor = self.backgroundColor;
|
|
2075
|
+
if (self.backgroundImage) {
|
|
2076
|
+
style.backgroundImage = `url(${self.backgroundImage})`;
|
|
2077
|
+
style.backgroundPosition = self.backgroundPosition || "top center";
|
|
2078
|
+
style.backgroundSize = self.backgroundSize || "auto";
|
|
2079
|
+
style.backgroundRepeat = self.backgroundRepeat || "repeat";
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
if (self.border) style.border = self.border;
|
|
2083
|
+
if (self.borderTop) style.borderTop = self.borderTop;
|
|
2084
|
+
if (self.borderRight) style.borderRight = self.borderRight;
|
|
2085
|
+
if (self.borderBottom) style.borderBottom = self.borderBottom;
|
|
2086
|
+
if (self.borderLeft) style.borderLeft = self.borderLeft;
|
|
2087
|
+
if (self.borderRadius) {
|
|
2088
|
+
style.borderRadius = self.borderRadius;
|
|
2089
|
+
style.overflow = "hidden";
|
|
2090
|
+
}
|
|
2091
|
+
const p = this.effectivePadding;
|
|
2092
|
+
style.paddingTop = p.top;
|
|
2093
|
+
style.paddingRight = p.right;
|
|
2094
|
+
style.paddingBottom = p.bottom;
|
|
2095
|
+
style.paddingLeft = p.left;
|
|
2096
|
+
if (self.textAlign) style.textAlign = self.textAlign;
|
|
2097
|
+
return style;
|
|
2098
|
+
}
|
|
2099
|
+
}));
|
|
2100
|
+
var WrapperModel = WrapperModelBase.preProcessSnapshot(
|
|
2101
|
+
(snapshot) => fillDefaults(paddingIn(snapshot), WRAPPER_DEFAULTS)
|
|
2102
|
+
).postProcessSnapshot((snapshot) => paddingOut(dropFilled(snapshot)));
|
|
2103
|
+
function createWrapper(options = {}) {
|
|
2104
|
+
return {
|
|
2105
|
+
id: options.id || (0, import_nanoid5.nanoid)(),
|
|
2106
|
+
type: "wrapper",
|
|
2107
|
+
sections: options.sections ?? [createSection()]
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
// src/store/mst/models/TemplateModel.ts
|
|
2112
|
+
var import_mobx_state_tree6 = require("mobx-state-tree");
|
|
2113
|
+
var import_nanoid6 = require("nanoid");
|
|
2114
|
+
var TopLevelItemModel = import_mobx_state_tree6.types.union(
|
|
2115
|
+
{
|
|
2116
|
+
dispatcher: (snapshot) => snapshot?.type === "wrapper" ? WrapperModel : SectionModel
|
|
2117
|
+
},
|
|
2118
|
+
SectionModel,
|
|
2119
|
+
WrapperModel
|
|
2120
|
+
);
|
|
2121
|
+
function isWrapperInstance(item) {
|
|
2122
|
+
return item?.type === "wrapper";
|
|
2123
|
+
}
|
|
2124
|
+
function cloneSectionSnapshot(snapshot) {
|
|
2125
|
+
const cloneBlocks = (blocks) => (blocks ?? []).map((b) => ({ ...b, id: (0, import_nanoid6.nanoid)() }));
|
|
2126
|
+
return {
|
|
2127
|
+
...snapshot,
|
|
2128
|
+
id: (0, import_nanoid6.nanoid)(),
|
|
2129
|
+
columns: (snapshot.columns ?? []).map((col) => ({
|
|
2130
|
+
...col,
|
|
2131
|
+
id: (0, import_nanoid6.nanoid)(),
|
|
2132
|
+
blocks: cloneBlocks(col.blocks),
|
|
2133
|
+
...col.subColumns ? { subColumns: col.subColumns.map((sc) => ({ ...sc, id: (0, import_nanoid6.nanoid)(), blocks: cloneBlocks(sc.blocks) })) } : {}
|
|
2134
|
+
}))
|
|
2135
|
+
};
|
|
2136
|
+
}
|
|
2137
|
+
var FontDefinitionModel = import_mobx_state_tree6.types.model("FontDefinition", {
|
|
2138
|
+
name: import_mobx_state_tree6.types.string,
|
|
2139
|
+
href: import_mobx_state_tree6.types.string
|
|
2140
|
+
});
|
|
2141
|
+
var ThemeColorModel = import_mobx_state_tree6.types.model("ThemeColor", {
|
|
2142
|
+
name: import_mobx_state_tree6.types.string,
|
|
2143
|
+
value: import_mobx_state_tree6.types.string
|
|
2144
|
+
}).actions((self) => ({
|
|
2145
|
+
setValue(value) {
|
|
2146
|
+
self.value = value;
|
|
2147
|
+
},
|
|
2148
|
+
setName(name) {
|
|
2149
|
+
self.name = name;
|
|
2150
|
+
}
|
|
2151
|
+
}));
|
|
2152
|
+
var DEFAULT_THEME_COLORS = [
|
|
2153
|
+
{ name: "Primary", value: "#944923" },
|
|
2154
|
+
{ name: "Secondary", value: "#ffffff" },
|
|
2155
|
+
{ name: "Text", value: "#333333" },
|
|
2156
|
+
{ name: "Background", value: "#f5f5f5" }
|
|
2157
|
+
];
|
|
2158
|
+
var METADATA_DEFAULTS = {
|
|
2159
|
+
title: () => "Untitled Template",
|
|
2160
|
+
subject: () => "",
|
|
2161
|
+
previewText: () => "",
|
|
2162
|
+
createdAt: () => Date.now(),
|
|
2163
|
+
updatedAt: () => Date.now(),
|
|
2164
|
+
fonts: () => [],
|
|
2165
|
+
themeColors: () => DEFAULT_THEME_COLORS.map((c) => ({ ...c }))
|
|
2166
|
+
};
|
|
2167
|
+
var TemplateMetadataModel = import_mobx_state_tree6.types.model("TemplateMetadata", {
|
|
2168
|
+
title: import_mobx_state_tree6.types.optional(import_mobx_state_tree6.types.string, "Untitled Template"),
|
|
2169
|
+
subject: import_mobx_state_tree6.types.optional(import_mobx_state_tree6.types.string, ""),
|
|
2170
|
+
previewText: import_mobx_state_tree6.types.optional(import_mobx_state_tree6.types.string, ""),
|
|
2171
|
+
createdAt: import_mobx_state_tree6.types.optional(import_mobx_state_tree6.types.Date, () => /* @__PURE__ */ new Date()),
|
|
2172
|
+
updatedAt: import_mobx_state_tree6.types.optional(import_mobx_state_tree6.types.Date, () => /* @__PURE__ */ new Date()),
|
|
2173
|
+
fonts: import_mobx_state_tree6.types.optional(import_mobx_state_tree6.types.array(FontDefinitionModel), []),
|
|
2174
|
+
themeColors: import_mobx_state_tree6.types.optional(import_mobx_state_tree6.types.array(ThemeColorModel), DEFAULT_THEME_COLORS),
|
|
2175
|
+
breakpoint: import_mobx_state_tree6.types.maybe(import_mobx_state_tree6.types.string),
|
|
2176
|
+
customCSS: import_mobx_state_tree6.types.maybe(import_mobx_state_tree6.types.string),
|
|
2177
|
+
inlineCSS: import_mobx_state_tree6.types.maybe(import_mobx_state_tree6.types.string),
|
|
2178
|
+
/** The MJML head and body settings of an imported document (see `MjmlHead` in the schema). */
|
|
2179
|
+
mjmlHead: import_mobx_state_tree6.types.maybe(import_mobx_state_tree6.types.frozen()),
|
|
2180
|
+
/** Defaults the store filled when it opened the node; they go back out only if changed (see `filledDefaults.ts`). */
|
|
2181
|
+
filled: import_mobx_state_tree6.types.maybe(import_mobx_state_tree6.types.frozen()),
|
|
2182
|
+
/** Dates a stored document wrote as ISO strings, so they go back out as written. */
|
|
2183
|
+
dateStrings: import_mobx_state_tree6.types.maybe(import_mobx_state_tree6.types.frozen())
|
|
2184
|
+
}).actions((self) => ({
|
|
2185
|
+
update(updates) {
|
|
2186
|
+
Object.entries(updates).forEach(([key, value]) => {
|
|
2187
|
+
if (key in self && value !== void 0) {
|
|
2188
|
+
self[key] = value;
|
|
2189
|
+
}
|
|
2190
|
+
});
|
|
2191
|
+
self.updatedAt = /* @__PURE__ */ new Date();
|
|
2192
|
+
},
|
|
2193
|
+
addFont(name, href) {
|
|
2194
|
+
self.fonts.push(FontDefinitionModel.create({ name, href }));
|
|
2195
|
+
self.updatedAt = /* @__PURE__ */ new Date();
|
|
2196
|
+
},
|
|
2197
|
+
removeFont(name) {
|
|
2198
|
+
const index = self.fonts.findIndex((f) => f.name === name);
|
|
2199
|
+
if (index !== -1) {
|
|
2200
|
+
self.fonts.splice(index, 1);
|
|
2201
|
+
self.updatedAt = /* @__PURE__ */ new Date();
|
|
2202
|
+
}
|
|
2203
|
+
},
|
|
2204
|
+
addThemeColor(name, value) {
|
|
2205
|
+
self.themeColors.push(ThemeColorModel.create({ name, value }));
|
|
2206
|
+
self.updatedAt = /* @__PURE__ */ new Date();
|
|
2207
|
+
},
|
|
2208
|
+
updateThemeColor(name, value) {
|
|
2209
|
+
const color = self.themeColors.find((c) => c.name === name);
|
|
2210
|
+
if (color) {
|
|
2211
|
+
color.setValue(value);
|
|
2212
|
+
self.updatedAt = /* @__PURE__ */ new Date();
|
|
2213
|
+
}
|
|
2214
|
+
},
|
|
2215
|
+
removeThemeColor(name) {
|
|
2216
|
+
const index = self.themeColors.findIndex((c) => c.name === name);
|
|
2217
|
+
if (index !== -1) {
|
|
2218
|
+
self.themeColors.splice(index, 1);
|
|
2219
|
+
self.updatedAt = /* @__PURE__ */ new Date();
|
|
2220
|
+
}
|
|
2221
|
+
},
|
|
2222
|
+
touch() {
|
|
2223
|
+
self.updatedAt = /* @__PURE__ */ new Date();
|
|
2224
|
+
}
|
|
2225
|
+
})).preProcessSnapshot((snapshot) => {
|
|
2226
|
+
if (!snapshot) return snapshot;
|
|
2227
|
+
const dateStrings = {};
|
|
2228
|
+
const toDate = (key, value) => {
|
|
2229
|
+
if (typeof value !== "string") return value;
|
|
2230
|
+
const ms = Date.parse(value);
|
|
2231
|
+
if (Number.isNaN(ms)) return void 0;
|
|
2232
|
+
dateStrings[key] = value;
|
|
2233
|
+
return ms;
|
|
2234
|
+
};
|
|
2235
|
+
const parsed = {
|
|
2236
|
+
...snapshot,
|
|
2237
|
+
createdAt: toDate("createdAt", snapshot.createdAt),
|
|
2238
|
+
updatedAt: toDate("updatedAt", snapshot.updatedAt),
|
|
2239
|
+
...Object.keys(dateStrings).length > 0 ? { dateStrings } : {}
|
|
2240
|
+
};
|
|
2241
|
+
return fillDefaults(parsed, METADATA_DEFAULTS);
|
|
2242
|
+
}).postProcessSnapshot((snapshot) => {
|
|
2243
|
+
const { dateStrings, ...rest } = dropFilled(snapshot);
|
|
2244
|
+
const out = rest;
|
|
2245
|
+
for (const [key, written] of Object.entries(dateStrings ?? {})) {
|
|
2246
|
+
if (out[key] === Date.parse(written)) out[key] = written;
|
|
2247
|
+
}
|
|
2248
|
+
return out;
|
|
2249
|
+
});
|
|
2250
|
+
var TemplateModel = import_mobx_state_tree6.types.model("Template", {
|
|
2251
|
+
id: import_mobx_state_tree6.types.optional(import_mobx_state_tree6.types.string, () => (0, import_nanoid6.nanoid)()),
|
|
2252
|
+
version: import_mobx_state_tree6.types.optional(import_mobx_state_tree6.types.string, CURRENT_TEMPLATE_VERSION),
|
|
2253
|
+
metadata: import_mobx_state_tree6.types.optional(TemplateMetadataModel, {}),
|
|
2254
|
+
/** The top level, in order: sections and wrappers (see `TopLevelItemModel`). */
|
|
2255
|
+
sections: import_mobx_state_tree6.types.array(TopLevelItemModel)
|
|
2256
|
+
}).preProcessSnapshot((snapshot) => {
|
|
2257
|
+
if (!snapshot || snapshot.version !== "1.0" || !Array.isArray(snapshot.sections)) return snapshot;
|
|
2258
|
+
return migrateV1_0ToV1_1(snapshot);
|
|
2259
|
+
}).views((self) => ({
|
|
2260
|
+
/** Every section in document order, the ones inside wrappers included. */
|
|
2261
|
+
get allSections() {
|
|
2262
|
+
const out = [];
|
|
2263
|
+
for (const item of self.sections) {
|
|
2264
|
+
if (isWrapperInstance(item)) out.push(...item.sections);
|
|
2265
|
+
else out.push(item);
|
|
2266
|
+
}
|
|
2267
|
+
return out;
|
|
2268
|
+
},
|
|
2269
|
+
/** The top-level wrappers, in order. */
|
|
2270
|
+
get wrappers() {
|
|
2271
|
+
return self.sections.filter(isWrapperInstance);
|
|
2272
|
+
}
|
|
2273
|
+
})).views((self) => ({
|
|
2274
|
+
getSectionById(sectionId) {
|
|
2275
|
+
return self.allSections.find((s) => s.id === sectionId);
|
|
2276
|
+
},
|
|
2277
|
+
getWrapperById(wrapperId) {
|
|
2278
|
+
return self.wrappers.find((w) => w.id === wrapperId);
|
|
2279
|
+
},
|
|
2280
|
+
/** The wrapper a section sits in; undefined for a top-level section (or an unknown id). */
|
|
2281
|
+
findWrapperBySectionId(sectionId) {
|
|
2282
|
+
return self.wrappers.find((w) => w.sections.some((s) => s.id === sectionId));
|
|
2283
|
+
},
|
|
2284
|
+
/** The index of a top-level item (section or wrapper); -1 when it is not at the top level. */
|
|
2285
|
+
getSectionIndex(itemId) {
|
|
2286
|
+
return self.sections.findIndex((s) => s.id === itemId);
|
|
2287
|
+
}
|
|
2288
|
+
})).actions((self) => {
|
|
2289
|
+
const locate = (sectionId) => {
|
|
2290
|
+
const top = self.sections.findIndex((s) => s.id === sectionId && !isWrapperInstance(s));
|
|
2291
|
+
if (top !== -1) return { list: self.sections, index: top };
|
|
2292
|
+
for (const wrapper of self.wrappers) {
|
|
2293
|
+
const index = wrapper.sections.findIndex((s) => s.id === sectionId);
|
|
2294
|
+
if (index !== -1) return { list: wrapper.sections, index, wrapper };
|
|
2295
|
+
}
|
|
2296
|
+
return void 0;
|
|
2297
|
+
};
|
|
2298
|
+
return {
|
|
2299
|
+
/**
|
|
2300
|
+
* Add a section at the top level, or into a wrapper when `wrapperId`
|
|
2301
|
+
* names one. `index` is the position in that list; the end when left out.
|
|
2302
|
+
*/
|
|
2303
|
+
addSection(section, index, wrapperId) {
|
|
2304
|
+
const sectionToAdd = SectionModel.create(section);
|
|
2305
|
+
const wrapper = wrapperId ? self.getWrapperById(wrapperId) : void 0;
|
|
2306
|
+
if (wrapperId && !wrapper) throw new Error(`No wrapper with id "${wrapperId}"`);
|
|
2307
|
+
const list = wrapper ? wrapper.sections : self.sections;
|
|
2308
|
+
if (index !== void 0 && index >= 0 && index <= list.length) list.splice(index, 0, sectionToAdd);
|
|
2309
|
+
else list.push(sectionToAdd);
|
|
2310
|
+
self.metadata.touch();
|
|
2311
|
+
return sectionToAdd;
|
|
2312
|
+
},
|
|
2313
|
+
/** Add a wrapper at the top level (by default around one empty section). */
|
|
2314
|
+
addWrapper(wrapper = createWrapper(), index) {
|
|
2315
|
+
const wrapperToAdd = WrapperModel.create(wrapper);
|
|
2316
|
+
if (index !== void 0 && index >= 0 && index <= self.sections.length) self.sections.splice(index, 0, wrapperToAdd);
|
|
2317
|
+
else self.sections.push(wrapperToAdd);
|
|
2318
|
+
self.metadata.touch();
|
|
2319
|
+
return wrapperToAdd;
|
|
2320
|
+
},
|
|
2321
|
+
/** Remove a section, wherever it is (a wrapper that loses its last section stays, empty). */
|
|
2322
|
+
removeSection(sectionId) {
|
|
2323
|
+
const at = locate(sectionId);
|
|
2324
|
+
if (!at) return false;
|
|
2325
|
+
(0, import_mobx_state_tree6.destroy)(at.list[at.index]);
|
|
2326
|
+
self.metadata.touch();
|
|
2327
|
+
return true;
|
|
2328
|
+
},
|
|
2329
|
+
/**
|
|
2330
|
+
* Move a top-level item (a section or a wrapper) to another position
|
|
2331
|
+
* of the top level.
|
|
2332
|
+
*/
|
|
2333
|
+
moveSection(itemId, toIndex) {
|
|
2334
|
+
const fromIndex = self.sections.findIndex((s) => s.id === itemId);
|
|
2335
|
+
if (fromIndex === -1 || toIndex < 0 || toIndex >= self.sections.length) return false;
|
|
2336
|
+
if (fromIndex === toIndex) return false;
|
|
2337
|
+
const item = (0, import_mobx_state_tree6.detach)(self.sections[fromIndex]);
|
|
2338
|
+
self.sections.splice(toIndex, 0, item);
|
|
2339
|
+
self.metadata.touch();
|
|
2340
|
+
return true;
|
|
2341
|
+
},
|
|
2342
|
+
/**
|
|
2343
|
+
* Move a section into a wrapper, out of one, between two, or within one.
|
|
2344
|
+
* `wrapperId: null` targets the top level. `index` is the position in
|
|
2345
|
+
* the target list as it is after the section left its old place.
|
|
2346
|
+
*/
|
|
2347
|
+
moveSectionTo(sectionId, target) {
|
|
2348
|
+
const at = locate(sectionId);
|
|
2349
|
+
if (!at) return false;
|
|
2350
|
+
const wrapper = target.wrapperId ? self.getWrapperById(target.wrapperId) : void 0;
|
|
2351
|
+
if (target.wrapperId && !wrapper) return false;
|
|
2352
|
+
const sameList = (at.wrapper?.id ?? null) === (target.wrapperId ?? null);
|
|
2353
|
+
if (sameList && at.index === target.index) return false;
|
|
2354
|
+
const section = (0, import_mobx_state_tree6.detach)(at.list[at.index]);
|
|
2355
|
+
const list = wrapper ? wrapper.sections : self.sections;
|
|
2356
|
+
const index = Math.max(0, Math.min(target.index, list.length));
|
|
2357
|
+
list.splice(index, 0, section);
|
|
2358
|
+
self.metadata.touch();
|
|
2359
|
+
return true;
|
|
2360
|
+
},
|
|
2361
|
+
/** Put a top-level section into a new wrapper, in its place. Returns the wrapper. */
|
|
2362
|
+
wrapSection(sectionId) {
|
|
2363
|
+
const index = self.sections.findIndex((s) => s.id === sectionId && !isWrapperInstance(s));
|
|
2364
|
+
if (index === -1) return void 0;
|
|
2365
|
+
const section = (0, import_mobx_state_tree6.detach)(self.sections[index]);
|
|
2366
|
+
const wrapper = WrapperModel.create({ id: (0, import_nanoid6.nanoid)(), type: "wrapper", sections: [] });
|
|
2367
|
+
self.sections.splice(index, 0, wrapper);
|
|
2368
|
+
wrapper.sections.push(section);
|
|
2369
|
+
self.metadata.touch();
|
|
2370
|
+
return wrapper;
|
|
2371
|
+
},
|
|
2372
|
+
/** Take a wrapper's sections out to the top level, in its place, and remove the wrapper. Returns the sections' ids. */
|
|
2373
|
+
unwrap(wrapperId) {
|
|
2374
|
+
const index = self.sections.findIndex((s) => s.id === wrapperId && isWrapperInstance(s));
|
|
2375
|
+
if (index === -1) return [];
|
|
2376
|
+
const wrapper = self.sections[index];
|
|
2377
|
+
const sections = wrapper.sections.slice().map((s) => (0, import_mobx_state_tree6.detach)(s));
|
|
2378
|
+
(0, import_mobx_state_tree6.destroy)(wrapper);
|
|
2379
|
+
self.sections.splice(index, 0, ...sections);
|
|
2380
|
+
self.metadata.touch();
|
|
2381
|
+
return sections.map((s) => s.id);
|
|
2382
|
+
},
|
|
2383
|
+
/**
|
|
2384
|
+
* Remove a wrapper. With `keepSections`, its sections stay, in its place
|
|
2385
|
+
* (the same as {@link unwrap}); otherwise they go with it.
|
|
2386
|
+
*/
|
|
2387
|
+
removeWrapper(wrapperId, options) {
|
|
2388
|
+
const wrapper = self.getWrapperById(wrapperId);
|
|
2389
|
+
if (!wrapper) return false;
|
|
2390
|
+
if (options.keepSections) {
|
|
2391
|
+
this.unwrap(wrapperId);
|
|
2392
|
+
} else {
|
|
2393
|
+
(0, import_mobx_state_tree6.destroy)(wrapper);
|
|
2394
|
+
self.metadata.touch();
|
|
2395
|
+
}
|
|
2396
|
+
return true;
|
|
2397
|
+
},
|
|
2398
|
+
/** Duplicate a section right after itself, in the same wrapper (or at the top level). */
|
|
2399
|
+
duplicateSection(sectionId) {
|
|
2400
|
+
const at = locate(sectionId);
|
|
2401
|
+
if (!at) return void 0;
|
|
2402
|
+
const newSection = SectionModel.create(cloneSectionSnapshot((0, import_mobx_state_tree6.getSnapshot)(at.list[at.index])));
|
|
2403
|
+
at.list.splice(at.index + 1, 0, newSection);
|
|
2404
|
+
self.metadata.touch();
|
|
2405
|
+
return newSection;
|
|
2406
|
+
},
|
|
2407
|
+
/** Duplicate a wrapper and everything inside it, right after itself. */
|
|
2408
|
+
duplicateWrapper(wrapperId) {
|
|
2409
|
+
const index = self.sections.findIndex((s) => s.id === wrapperId && isWrapperInstance(s));
|
|
2410
|
+
if (index === -1) return void 0;
|
|
2411
|
+
const snapshot = (0, import_mobx_state_tree6.getSnapshot)(self.sections[index]);
|
|
2412
|
+
const copy = WrapperModel.create({
|
|
2413
|
+
...snapshot,
|
|
2414
|
+
id: (0, import_nanoid6.nanoid)(),
|
|
2415
|
+
sections: snapshot.sections.map((s) => cloneSectionSnapshot(s))
|
|
2416
|
+
});
|
|
2417
|
+
self.sections.splice(index + 1, 0, copy);
|
|
2418
|
+
self.metadata.touch();
|
|
2419
|
+
return copy;
|
|
2420
|
+
},
|
|
2421
|
+
insertBlock(columnId, block, index) {
|
|
2422
|
+
for (const section of self.allSections) {
|
|
2423
|
+
const column = section.getColumnById(columnId);
|
|
2424
|
+
if (column) {
|
|
2425
|
+
self.metadata.touch();
|
|
2426
|
+
const newBlock = BlockModel.create(block);
|
|
2427
|
+
if (index !== void 0 && index >= 0 && index <= column.blocks.length) {
|
|
2428
|
+
column.blocks.splice(index, 0, newBlock);
|
|
2429
|
+
} else {
|
|
2430
|
+
column.blocks.push(newBlock);
|
|
2431
|
+
}
|
|
2432
|
+
return newBlock;
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
return void 0;
|
|
2436
|
+
},
|
|
2437
|
+
moveBlock(blockId, targetColumnId, targetIndex) {
|
|
2438
|
+
let sourceColumn;
|
|
2439
|
+
let block;
|
|
2440
|
+
for (const section of self.allSections) {
|
|
2441
|
+
const col = section.findColumnByBlockId(blockId);
|
|
2442
|
+
if (col) {
|
|
2443
|
+
sourceColumn = col;
|
|
2444
|
+
block = col.getBlockById(blockId);
|
|
2445
|
+
break;
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
if (!sourceColumn || !block) return false;
|
|
2449
|
+
let targetColumn;
|
|
2450
|
+
for (const section of self.allSections) {
|
|
2451
|
+
const col = section.getColumnById(targetColumnId);
|
|
2452
|
+
if (col) {
|
|
2453
|
+
targetColumn = col;
|
|
2454
|
+
break;
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
if (!targetColumn) return false;
|
|
2458
|
+
if (sourceColumn.id === targetColumn.id) {
|
|
2459
|
+
const fromIndex = sourceColumn.getBlockIndex(blockId);
|
|
2460
|
+
sourceColumn.moveBlock(fromIndex, targetIndex);
|
|
2461
|
+
} else {
|
|
2462
|
+
const detachedBlock = sourceColumn.detachBlock(blockId);
|
|
2463
|
+
if (detachedBlock) {
|
|
2464
|
+
if (targetIndex >= 0 && targetIndex <= targetColumn.blocks.length) {
|
|
2465
|
+
targetColumn.blocks.splice(targetIndex, 0, detachedBlock);
|
|
2466
|
+
} else {
|
|
2467
|
+
targetColumn.blocks.push(detachedBlock);
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
self.metadata.touch();
|
|
2472
|
+
return true;
|
|
2473
|
+
},
|
|
2474
|
+
deleteBlock(blockId) {
|
|
2475
|
+
for (const section of self.allSections) {
|
|
2476
|
+
for (const column of section.columns) {
|
|
2477
|
+
if (column.removeBlock(blockId)) {
|
|
2478
|
+
self.metadata.touch();
|
|
2479
|
+
return true;
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
return false;
|
|
2484
|
+
},
|
|
2485
|
+
updateMetadata(updates) {
|
|
2486
|
+
self.metadata.update(updates);
|
|
2487
|
+
},
|
|
2488
|
+
clear() {
|
|
2489
|
+
self.sections.forEach((s) => (0, import_mobx_state_tree6.destroy)(s));
|
|
2490
|
+
self.sections.clear();
|
|
2491
|
+
self.metadata.touch();
|
|
2492
|
+
}
|
|
2493
|
+
};
|
|
2494
|
+
}).views((self) => ({
|
|
2495
|
+
findColumnById(columnId) {
|
|
2496
|
+
for (const section of self.allSections) {
|
|
2497
|
+
const column = section.getColumnById(columnId);
|
|
2498
|
+
if (column) return column;
|
|
2499
|
+
}
|
|
2500
|
+
return void 0;
|
|
2501
|
+
},
|
|
2502
|
+
findBlockById(blockId) {
|
|
2503
|
+
for (const section of self.allSections) {
|
|
2504
|
+
const block = section.findBlockById(blockId);
|
|
2505
|
+
if (block) return block;
|
|
2506
|
+
}
|
|
2507
|
+
return void 0;
|
|
2508
|
+
},
|
|
2509
|
+
findSectionByBlockId(blockId) {
|
|
2510
|
+
for (const section of self.allSections) {
|
|
2511
|
+
if (section.findBlockById(blockId)) {
|
|
2512
|
+
return section;
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
return void 0;
|
|
2516
|
+
},
|
|
2517
|
+
findColumnByBlockId(blockId) {
|
|
2518
|
+
for (const section of self.allSections) {
|
|
2519
|
+
const column = section.findColumnByBlockId(blockId);
|
|
2520
|
+
if (column) return column;
|
|
2521
|
+
}
|
|
2522
|
+
return void 0;
|
|
2523
|
+
},
|
|
2524
|
+
/** Visible top-level items (a hidden wrapper hides everything inside it). */
|
|
2525
|
+
get visibleSections() {
|
|
2526
|
+
return self.sections.filter((s) => !s.hidden);
|
|
2527
|
+
},
|
|
2528
|
+
/** How many sections the document has, the ones inside wrappers included. */
|
|
2529
|
+
get sectionCount() {
|
|
2530
|
+
return self.allSections.length;
|
|
2531
|
+
},
|
|
2532
|
+
get totalBlockCount() {
|
|
2533
|
+
return self.allSections.reduce((sum, section) => sum + section.totalBlockCount, 0);
|
|
2534
|
+
},
|
|
2535
|
+
get isEmpty() {
|
|
2536
|
+
return self.allSections.every((s) => s.isEmpty);
|
|
2537
|
+
},
|
|
2538
|
+
getBlocksByType(type) {
|
|
2539
|
+
const blocks = [];
|
|
2540
|
+
for (const section of self.allSections) {
|
|
2541
|
+
for (const column of section.columns) {
|
|
2542
|
+
for (const block of column.blocks) {
|
|
2543
|
+
if (block.type === type) {
|
|
2544
|
+
blocks.push(block);
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
return blocks;
|
|
2550
|
+
},
|
|
2551
|
+
get allBlocks() {
|
|
2552
|
+
const blocks = [];
|
|
2553
|
+
for (const section of self.allSections) {
|
|
2554
|
+
for (const column of section.columns) {
|
|
2555
|
+
blocks.push(...column.blocks);
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
return blocks;
|
|
2559
|
+
}
|
|
2560
|
+
}));
|
|
2561
|
+
function createTemplate(options = {}) {
|
|
2562
|
+
return {
|
|
2563
|
+
id: options.id || (0, import_nanoid6.nanoid)(),
|
|
2564
|
+
version: CURRENT_TEMPLATE_VERSION,
|
|
2565
|
+
metadata: {
|
|
2566
|
+
title: options.title || "Untitled Template",
|
|
2567
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
2568
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2569
|
+
},
|
|
2570
|
+
sections: options.sections || []
|
|
2571
|
+
};
|
|
2572
|
+
}
|
|
2573
|
+
function createTemplateWithDefaultSection(options = {}) {
|
|
2574
|
+
return {
|
|
2575
|
+
id: options.id || (0, import_nanoid6.nanoid)(),
|
|
2576
|
+
version: CURRENT_TEMPLATE_VERSION,
|
|
2577
|
+
metadata: {
|
|
2578
|
+
title: options.title || "Untitled Template",
|
|
2579
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
2580
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2581
|
+
},
|
|
2582
|
+
sections: [createSection()]
|
|
2583
|
+
};
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
// src/store/mst/EditorUIStore.ts
|
|
2587
|
+
var import_mobx_state_tree7 = require("mobx-state-tree");
|
|
2588
|
+
var EditorUIStore = import_mobx_state_tree7.types.model("EditorUI", {
|
|
2589
|
+
// === Selection State ===
|
|
2590
|
+
selectedBlockId: import_mobx_state_tree7.types.maybe(import_mobx_state_tree7.types.string),
|
|
2591
|
+
selectedSectionId: import_mobx_state_tree7.types.maybe(import_mobx_state_tree7.types.string),
|
|
2592
|
+
selectedColumnId: import_mobx_state_tree7.types.maybe(import_mobx_state_tree7.types.string),
|
|
2593
|
+
selectedSubColumnId: import_mobx_state_tree7.types.maybe(import_mobx_state_tree7.types.string),
|
|
2594
|
+
/** A wrapper (container around sections), one level out from sections. */
|
|
2595
|
+
selectedWrapperId: import_mobx_state_tree7.types.maybe(import_mobx_state_tree7.types.string),
|
|
2596
|
+
// === Panel State ===
|
|
2597
|
+
activeTab: import_mobx_state_tree7.types.optional(
|
|
2598
|
+
import_mobx_state_tree7.types.enumeration(["elements", "layout", "layers", "settings", "prebuilt", "saved"]),
|
|
2599
|
+
"elements"
|
|
2600
|
+
),
|
|
2601
|
+
showLeftPanel: import_mobx_state_tree7.types.optional(import_mobx_state_tree7.types.boolean, true),
|
|
2602
|
+
showRightPanel: import_mobx_state_tree7.types.optional(import_mobx_state_tree7.types.boolean, true),
|
|
2603
|
+
// === View Mode ===
|
|
2604
|
+
previewDevice: import_mobx_state_tree7.types.optional(
|
|
2605
|
+
import_mobx_state_tree7.types.enumeration(["desktop", "mobile"]),
|
|
2606
|
+
"desktop"
|
|
2607
|
+
),
|
|
2608
|
+
zoomLevel: import_mobx_state_tree7.types.optional(import_mobx_state_tree7.types.number, 1)
|
|
2609
|
+
}).volatile(() => ({
|
|
2610
|
+
// === Transient State (not persisted) ===
|
|
2611
|
+
hoverBlockId: void 0,
|
|
2612
|
+
hoverSectionId: void 0,
|
|
2613
|
+
hoverColumnId: void 0,
|
|
2614
|
+
hoverSubColumnId: void 0,
|
|
2615
|
+
hoverWrapperId: void 0,
|
|
2616
|
+
/** The wrapper whose delete the editor is asking about (keep its sections, or delete everything). */
|
|
2617
|
+
pendingWrapperDeleteId: void 0,
|
|
2618
|
+
// Drag state
|
|
2619
|
+
isDragging: false,
|
|
2620
|
+
dragData: void 0,
|
|
2621
|
+
dropIntent: void 0,
|
|
2622
|
+
// Resize state
|
|
2623
|
+
isResizing: false,
|
|
2624
|
+
resizeState: void 0,
|
|
2625
|
+
// Inline editing state
|
|
2626
|
+
isInlineEditing: false,
|
|
2627
|
+
inlineEditBlockId: void 0
|
|
2628
|
+
})).actions((self) => ({
|
|
2629
|
+
// === Selection Actions ===
|
|
2630
|
+
/**
|
|
2631
|
+
* Select a block
|
|
2632
|
+
*/
|
|
2633
|
+
selectBlock(blockId) {
|
|
2634
|
+
self.selectedBlockId = blockId || void 0;
|
|
2635
|
+
self.selectedSectionId = void 0;
|
|
2636
|
+
self.selectedColumnId = void 0;
|
|
2637
|
+
self.selectedSubColumnId = void 0;
|
|
2638
|
+
self.selectedWrapperId = void 0;
|
|
2639
|
+
},
|
|
2640
|
+
/**
|
|
2641
|
+
* Select a section
|
|
2642
|
+
*/
|
|
2643
|
+
selectSection(sectionId) {
|
|
2644
|
+
self.selectedSectionId = sectionId || void 0;
|
|
2645
|
+
self.selectedBlockId = void 0;
|
|
2646
|
+
self.selectedColumnId = void 0;
|
|
2647
|
+
self.selectedSubColumnId = void 0;
|
|
2648
|
+
self.selectedWrapperId = void 0;
|
|
2649
|
+
},
|
|
2650
|
+
/**
|
|
2651
|
+
* Select a column
|
|
2652
|
+
*/
|
|
2653
|
+
selectColumn(columnId) {
|
|
2654
|
+
self.selectedColumnId = columnId || void 0;
|
|
2655
|
+
self.selectedBlockId = void 0;
|
|
2656
|
+
self.selectedSectionId = void 0;
|
|
2657
|
+
self.selectedSubColumnId = void 0;
|
|
2658
|
+
self.selectedWrapperId = void 0;
|
|
2659
|
+
},
|
|
2660
|
+
/**
|
|
2661
|
+
* Select a sub-column (depth-2 nested column).
|
|
2662
|
+
* Mutually exclusive with the other selection levels.
|
|
2663
|
+
*/
|
|
2664
|
+
selectSubColumn(subColumnId) {
|
|
2665
|
+
self.selectedSubColumnId = subColumnId || void 0;
|
|
2666
|
+
self.selectedBlockId = void 0;
|
|
2667
|
+
self.selectedSectionId = void 0;
|
|
2668
|
+
self.selectedColumnId = void 0;
|
|
2669
|
+
self.selectedWrapperId = void 0;
|
|
2670
|
+
},
|
|
2671
|
+
/**
|
|
2672
|
+
* Select a wrapper (the container around sections).
|
|
2673
|
+
* Mutually exclusive with the other selection levels.
|
|
2674
|
+
*/
|
|
2675
|
+
selectWrapper(wrapperId) {
|
|
2676
|
+
self.selectedWrapperId = wrapperId || void 0;
|
|
2677
|
+
self.selectedBlockId = void 0;
|
|
2678
|
+
self.selectedSectionId = void 0;
|
|
2679
|
+
self.selectedColumnId = void 0;
|
|
2680
|
+
self.selectedSubColumnId = void 0;
|
|
2681
|
+
},
|
|
2682
|
+
/**
|
|
2683
|
+
* Clear all selection
|
|
2684
|
+
*/
|
|
2685
|
+
clearSelection() {
|
|
2686
|
+
self.selectedBlockId = void 0;
|
|
2687
|
+
self.selectedSectionId = void 0;
|
|
2688
|
+
self.selectedColumnId = void 0;
|
|
2689
|
+
self.selectedSubColumnId = void 0;
|
|
2690
|
+
self.selectedWrapperId = void 0;
|
|
2691
|
+
},
|
|
2692
|
+
// === Hover Actions ===
|
|
2693
|
+
/**
|
|
2694
|
+
* Set hover block
|
|
2695
|
+
*/
|
|
2696
|
+
setHoverBlock(blockId) {
|
|
2697
|
+
self.hoverBlockId = blockId;
|
|
2698
|
+
},
|
|
2699
|
+
/**
|
|
2700
|
+
* Set hover section
|
|
2701
|
+
*/
|
|
2702
|
+
setHoverSection(sectionId) {
|
|
2703
|
+
self.hoverSectionId = sectionId;
|
|
2704
|
+
},
|
|
2705
|
+
/**
|
|
2706
|
+
* Set hover column
|
|
2707
|
+
*/
|
|
2708
|
+
setHoverColumn(columnId) {
|
|
2709
|
+
self.hoverColumnId = columnId;
|
|
2710
|
+
},
|
|
2711
|
+
/**
|
|
2712
|
+
* Set hover sub-column (depth-2 nested)
|
|
2713
|
+
*/
|
|
2714
|
+
setHoverSubColumn(subColumnId) {
|
|
2715
|
+
self.hoverSubColumnId = subColumnId;
|
|
2716
|
+
},
|
|
2717
|
+
/**
|
|
2718
|
+
* Set hover wrapper
|
|
2719
|
+
*/
|
|
2720
|
+
setHoverWrapper(wrapperId) {
|
|
2721
|
+
self.hoverWrapperId = wrapperId;
|
|
2722
|
+
},
|
|
2723
|
+
// === Wrapper delete dialog ===
|
|
2724
|
+
/** Ask whether to keep a wrapper's sections or delete everything (the editor shows its own dialog). */
|
|
2725
|
+
requestWrapperDelete(wrapperId) {
|
|
2726
|
+
self.pendingWrapperDeleteId = wrapperId;
|
|
2727
|
+
},
|
|
2728
|
+
cancelWrapperDelete() {
|
|
2729
|
+
self.pendingWrapperDeleteId = void 0;
|
|
2730
|
+
},
|
|
2731
|
+
/**
|
|
2732
|
+
* Clear all hover states
|
|
2733
|
+
*/
|
|
2734
|
+
clearHover() {
|
|
2735
|
+
self.hoverBlockId = void 0;
|
|
2736
|
+
self.hoverSectionId = void 0;
|
|
2737
|
+
self.hoverColumnId = void 0;
|
|
2738
|
+
self.hoverSubColumnId = void 0;
|
|
2739
|
+
self.hoverWrapperId = void 0;
|
|
2740
|
+
},
|
|
2741
|
+
// === Drag Actions ===
|
|
2742
|
+
/**
|
|
2743
|
+
* Start a drag operation for an existing block
|
|
2744
|
+
*/
|
|
2745
|
+
startBlockDrag(blockId, sourceColumnId, sourceIndex) {
|
|
2746
|
+
self.isDragging = true;
|
|
2747
|
+
self.dragData = {
|
|
2748
|
+
blockId,
|
|
2749
|
+
sourceColumnId,
|
|
2750
|
+
sourceIndex,
|
|
2751
|
+
isNewBlock: false
|
|
2752
|
+
};
|
|
2753
|
+
},
|
|
2754
|
+
/**
|
|
2755
|
+
* Start a drag operation for a new block from the toolbar
|
|
2756
|
+
*/
|
|
2757
|
+
startNewBlockDrag(blockType) {
|
|
2758
|
+
self.isDragging = true;
|
|
2759
|
+
self.dragData = {
|
|
2760
|
+
blockType,
|
|
2761
|
+
isNewBlock: true
|
|
2762
|
+
};
|
|
2763
|
+
},
|
|
2764
|
+
/**
|
|
2765
|
+
* Update the drop intent during drag
|
|
2766
|
+
*/
|
|
2767
|
+
setDropIntent(intent) {
|
|
2768
|
+
self.dropIntent = intent;
|
|
2769
|
+
},
|
|
2770
|
+
/**
|
|
2771
|
+
* End the drag operation
|
|
2772
|
+
*/
|
|
2773
|
+
endDrag() {
|
|
2774
|
+
self.isDragging = false;
|
|
2775
|
+
self.dragData = void 0;
|
|
2776
|
+
self.dropIntent = void 0;
|
|
2777
|
+
},
|
|
2778
|
+
// === Resize Actions ===
|
|
2779
|
+
/**
|
|
2780
|
+
* Start a resize operation
|
|
2781
|
+
*/
|
|
2782
|
+
startResize(targetId, handleType, originalValue) {
|
|
2783
|
+
self.isResizing = true;
|
|
2784
|
+
self.resizeState = {
|
|
2785
|
+
targetId,
|
|
2786
|
+
handleType,
|
|
2787
|
+
originalValue,
|
|
2788
|
+
previewValue: originalValue
|
|
2789
|
+
};
|
|
2790
|
+
},
|
|
2791
|
+
/**
|
|
2792
|
+
* Update resize preview value
|
|
2793
|
+
*/
|
|
2794
|
+
updateResizePreview(value) {
|
|
2795
|
+
if (self.resizeState) {
|
|
2796
|
+
self.resizeState.previewValue = value;
|
|
2797
|
+
}
|
|
2798
|
+
},
|
|
2799
|
+
/**
|
|
2800
|
+
* End resize operation (commit or cancel)
|
|
2801
|
+
*/
|
|
2802
|
+
endResize() {
|
|
2803
|
+
self.isResizing = false;
|
|
2804
|
+
self.resizeState = void 0;
|
|
2805
|
+
},
|
|
2806
|
+
// === Inline Editing Actions ===
|
|
2807
|
+
/**
|
|
2808
|
+
* Start inline editing for a block
|
|
2809
|
+
*/
|
|
2810
|
+
startInlineEdit(blockId) {
|
|
2811
|
+
self.isInlineEditing = true;
|
|
2812
|
+
self.inlineEditBlockId = blockId;
|
|
2813
|
+
self.selectedBlockId = blockId;
|
|
2814
|
+
},
|
|
2815
|
+
/**
|
|
2816
|
+
* End inline editing
|
|
2817
|
+
*/
|
|
2818
|
+
endInlineEdit() {
|
|
2819
|
+
self.isInlineEditing = false;
|
|
2820
|
+
self.inlineEditBlockId = void 0;
|
|
2821
|
+
},
|
|
2822
|
+
// === Panel Actions ===
|
|
2823
|
+
/**
|
|
2824
|
+
* Set the active sidebar tab
|
|
2825
|
+
*/
|
|
2826
|
+
setActiveTab(tab) {
|
|
2827
|
+
self.activeTab = tab;
|
|
2828
|
+
},
|
|
2829
|
+
/**
|
|
2830
|
+
* Toggle left panel visibility
|
|
2831
|
+
*/
|
|
2832
|
+
toggleLeftPanel() {
|
|
2833
|
+
self.showLeftPanel = !self.showLeftPanel;
|
|
2834
|
+
},
|
|
2835
|
+
/**
|
|
2836
|
+
* Toggle right panel visibility
|
|
2837
|
+
*/
|
|
2838
|
+
toggleRightPanel() {
|
|
2839
|
+
self.showRightPanel = !self.showRightPanel;
|
|
2840
|
+
},
|
|
2841
|
+
/**
|
|
2842
|
+
* Set left panel visibility
|
|
2843
|
+
*/
|
|
2844
|
+
setLeftPanelVisible(visible) {
|
|
2845
|
+
self.showLeftPanel = visible;
|
|
2846
|
+
},
|
|
2847
|
+
/**
|
|
2848
|
+
* Set right panel visibility
|
|
2849
|
+
*/
|
|
2850
|
+
setRightPanelVisible(visible) {
|
|
2851
|
+
self.showRightPanel = visible;
|
|
2852
|
+
},
|
|
2853
|
+
// === View Actions ===
|
|
2854
|
+
/**
|
|
2855
|
+
* Set preview device
|
|
2856
|
+
*/
|
|
2857
|
+
setPreviewDevice(device) {
|
|
2858
|
+
self.previewDevice = device;
|
|
2859
|
+
},
|
|
2860
|
+
/**
|
|
2861
|
+
* Toggle preview device between desktop and mobile
|
|
2862
|
+
*/
|
|
2863
|
+
togglePreviewDevice() {
|
|
2864
|
+
self.previewDevice = self.previewDevice === "desktop" ? "mobile" : "desktop";
|
|
2865
|
+
},
|
|
2866
|
+
/**
|
|
2867
|
+
* Set zoom level
|
|
2868
|
+
*/
|
|
2869
|
+
setZoomLevel(zoom) {
|
|
2870
|
+
self.zoomLevel = Math.max(0.25, Math.min(2, zoom));
|
|
2871
|
+
},
|
|
2872
|
+
/**
|
|
2873
|
+
* Zoom in
|
|
2874
|
+
*/
|
|
2875
|
+
zoomIn() {
|
|
2876
|
+
self.zoomLevel = Math.min(2, self.zoomLevel + 0.1);
|
|
2877
|
+
},
|
|
2878
|
+
/**
|
|
2879
|
+
* Zoom out
|
|
2880
|
+
*/
|
|
2881
|
+
zoomOut() {
|
|
2882
|
+
self.zoomLevel = Math.max(0.25, self.zoomLevel - 0.1);
|
|
2883
|
+
},
|
|
2884
|
+
/**
|
|
2885
|
+
* Reset zoom to 100%
|
|
2886
|
+
*/
|
|
2887
|
+
resetZoom() {
|
|
2888
|
+
self.zoomLevel = 1;
|
|
2889
|
+
}
|
|
2890
|
+
})).views((self) => ({
|
|
2891
|
+
/**
|
|
2892
|
+
* Get the current selection type
|
|
2893
|
+
*/
|
|
2894
|
+
get selectionType() {
|
|
2895
|
+
if (self.selectedBlockId) return "block";
|
|
2896
|
+
if (self.selectedSectionId) return "section";
|
|
2897
|
+
if (self.selectedColumnId) return "column";
|
|
2898
|
+
if (self.selectedSubColumnId) return "subColumn";
|
|
2899
|
+
if (self.selectedWrapperId) return "wrapper";
|
|
2900
|
+
return null;
|
|
2901
|
+
},
|
|
2902
|
+
/**
|
|
2903
|
+
* Check if anything is selected
|
|
2904
|
+
*/
|
|
2905
|
+
get hasSelection() {
|
|
2906
|
+
return !!(self.selectedBlockId || self.selectedSectionId || self.selectedColumnId || self.selectedSubColumnId || self.selectedWrapperId);
|
|
2907
|
+
},
|
|
2908
|
+
/**
|
|
2909
|
+
* Check if a specific block is selected
|
|
2910
|
+
*/
|
|
2911
|
+
isBlockSelected(blockId) {
|
|
2912
|
+
return self.selectedBlockId === blockId;
|
|
2913
|
+
},
|
|
2914
|
+
/**
|
|
2915
|
+
* Check if a specific section is selected
|
|
2916
|
+
*/
|
|
2917
|
+
isSectionSelected(sectionId) {
|
|
2918
|
+
return self.selectedSectionId === sectionId;
|
|
2919
|
+
},
|
|
2920
|
+
/**
|
|
2921
|
+
* Check if a specific column is selected
|
|
2922
|
+
*/
|
|
2923
|
+
isColumnSelected(columnId) {
|
|
2924
|
+
return self.selectedColumnId === columnId;
|
|
2925
|
+
},
|
|
2926
|
+
/**
|
|
2927
|
+
* Check if a specific wrapper is selected
|
|
2928
|
+
*/
|
|
2929
|
+
isWrapperSelected(wrapperId) {
|
|
2930
|
+
return self.selectedWrapperId === wrapperId;
|
|
2931
|
+
},
|
|
2932
|
+
/**
|
|
2933
|
+
* Check if a specific block is hovered
|
|
2934
|
+
*/
|
|
2935
|
+
isBlockHovered(blockId) {
|
|
2936
|
+
return self.hoverBlockId === blockId;
|
|
2937
|
+
},
|
|
2938
|
+
/**
|
|
2939
|
+
* Check if we're dragging an existing block
|
|
2940
|
+
*/
|
|
2941
|
+
get isDraggingExistingBlock() {
|
|
2942
|
+
return self.isDragging && !!self.dragData && !self.dragData.isNewBlock;
|
|
2943
|
+
},
|
|
2944
|
+
/**
|
|
2945
|
+
* Check if we're dragging a new block from toolbar
|
|
2946
|
+
*/
|
|
2947
|
+
get isDraggingNewBlock() {
|
|
2948
|
+
return self.isDragging && !!self.dragData && !!self.dragData.isNewBlock;
|
|
2949
|
+
},
|
|
2950
|
+
/**
|
|
2951
|
+
* Get the currently dragged block type (for new blocks)
|
|
2952
|
+
*/
|
|
2953
|
+
get draggedBlockType() {
|
|
2954
|
+
return self.dragData?.blockType;
|
|
2955
|
+
},
|
|
2956
|
+
/**
|
|
2957
|
+
* Get the currently dragged block ID (for existing blocks)
|
|
2958
|
+
*/
|
|
2959
|
+
get draggedBlockId() {
|
|
2960
|
+
return self.dragData?.blockId;
|
|
2961
|
+
},
|
|
2962
|
+
/**
|
|
2963
|
+
* Get preview width based on device
|
|
2964
|
+
*/
|
|
2965
|
+
get previewWidth() {
|
|
2966
|
+
return self.previewDevice === "desktop" ? 600 : 375;
|
|
2967
|
+
},
|
|
2968
|
+
/**
|
|
2969
|
+
* Check if in mobile preview mode
|
|
2970
|
+
*/
|
|
2971
|
+
get isMobilePreview() {
|
|
2972
|
+
return self.previewDevice === "mobile";
|
|
2973
|
+
},
|
|
2974
|
+
/**
|
|
2975
|
+
* Check if in desktop preview mode
|
|
2976
|
+
*/
|
|
2977
|
+
get isDesktopPreview() {
|
|
2978
|
+
return self.previewDevice === "desktop";
|
|
2979
|
+
},
|
|
2980
|
+
/**
|
|
2981
|
+
* Get zoom percentage
|
|
2982
|
+
*/
|
|
2983
|
+
get zoomPercentage() {
|
|
2984
|
+
return Math.round(self.zoomLevel * 100);
|
|
2985
|
+
}
|
|
2986
|
+
}));
|
|
2987
|
+
|
|
2988
|
+
// src/store/mst/RootStore.ts
|
|
2989
|
+
var import_mobx_state_tree8 = require("mobx-state-tree");
|
|
2990
|
+
var RootStore = import_mobx_state_tree8.types.model("RootStore", {
|
|
2991
|
+
template: TemplateModel,
|
|
2992
|
+
editorUI: EditorUIStore
|
|
2993
|
+
}).volatile(() => ({
|
|
2994
|
+
// History state for undo/redo. Typed as `unknown` to keep the inferred
|
|
2995
|
+
// RootStore type small enough for TypeScript's serialization limit;
|
|
2996
|
+
// history snapshots are roundtripped through MST validation anyway.
|
|
2997
|
+
history: [],
|
|
2998
|
+
historyIndex: -1,
|
|
2999
|
+
maxHistory: 50,
|
|
3000
|
+
isUndoRedo: false,
|
|
3001
|
+
/**
|
|
3002
|
+
* The snapshot undo or redo just applied. The snapshot listener runs after
|
|
3003
|
+
* the outermost action (undo itself) has finished, so it must recognise
|
|
3004
|
+
* this one and not record it as a new step: that would cut off the redo
|
|
3005
|
+
* future.
|
|
3006
|
+
*/
|
|
3007
|
+
appliedFromHistory: void 0
|
|
3008
|
+
})).actions((self) => ({
|
|
3009
|
+
/**
|
|
3010
|
+
* Initialize history with current state
|
|
3011
|
+
*/
|
|
3012
|
+
initHistory() {
|
|
3013
|
+
const snapshot = (0, import_mobx_state_tree8.getSnapshot)(self.template);
|
|
3014
|
+
self.history = [snapshot];
|
|
3015
|
+
self.historyIndex = 0;
|
|
3016
|
+
},
|
|
3017
|
+
/**
|
|
3018
|
+
* Record current state to history (called after mutations)
|
|
3019
|
+
*/
|
|
3020
|
+
recordHistory() {
|
|
3021
|
+
if (self.isUndoRedo) return;
|
|
3022
|
+
const snapshot = (0, import_mobx_state_tree8.getSnapshot)(self.template);
|
|
3023
|
+
if (self.appliedFromHistory !== void 0) {
|
|
3024
|
+
const applied = self.appliedFromHistory;
|
|
3025
|
+
self.appliedFromHistory = void 0;
|
|
3026
|
+
if (JSON.stringify(applied) === JSON.stringify(snapshot)) return;
|
|
3027
|
+
}
|
|
3028
|
+
if (self.historyIndex < self.history.length - 1) {
|
|
3029
|
+
self.history = self.history.slice(0, self.historyIndex + 1);
|
|
3030
|
+
}
|
|
3031
|
+
self.history.push(snapshot);
|
|
3032
|
+
if (self.history.length > self.maxHistory) {
|
|
3033
|
+
self.history = self.history.slice(self.history.length - self.maxHistory);
|
|
3034
|
+
}
|
|
3035
|
+
self.historyIndex = self.history.length - 1;
|
|
3036
|
+
},
|
|
3037
|
+
/**
|
|
3038
|
+
* Undo to previous state
|
|
3039
|
+
*/
|
|
3040
|
+
undo() {
|
|
3041
|
+
if (self.historyIndex <= 0) return false;
|
|
3042
|
+
self.isUndoRedo = true;
|
|
3043
|
+
self.historyIndex--;
|
|
3044
|
+
(0, import_mobx_state_tree8.applySnapshot)(self.template, self.history[self.historyIndex]);
|
|
3045
|
+
self.appliedFromHistory = (0, import_mobx_state_tree8.getSnapshot)(self.template);
|
|
3046
|
+
self.isUndoRedo = false;
|
|
3047
|
+
this.validateSelection();
|
|
3048
|
+
return true;
|
|
3049
|
+
},
|
|
3050
|
+
/**
|
|
3051
|
+
* Redo to next state
|
|
3052
|
+
*/
|
|
3053
|
+
redo() {
|
|
3054
|
+
if (self.historyIndex >= self.history.length - 1) return false;
|
|
3055
|
+
self.isUndoRedo = true;
|
|
3056
|
+
self.historyIndex++;
|
|
3057
|
+
(0, import_mobx_state_tree8.applySnapshot)(self.template, self.history[self.historyIndex]);
|
|
3058
|
+
self.appliedFromHistory = (0, import_mobx_state_tree8.getSnapshot)(self.template);
|
|
3059
|
+
self.isUndoRedo = false;
|
|
3060
|
+
this.validateSelection();
|
|
3061
|
+
return true;
|
|
3062
|
+
},
|
|
3063
|
+
/**
|
|
3064
|
+
* Clear history
|
|
3065
|
+
*/
|
|
3066
|
+
clearHistory() {
|
|
3067
|
+
const snapshot = (0, import_mobx_state_tree8.getSnapshot)(self.template);
|
|
3068
|
+
self.history = [snapshot];
|
|
3069
|
+
self.historyIndex = 0;
|
|
3070
|
+
},
|
|
3071
|
+
/**
|
|
3072
|
+
* Validate and clear selection if items no longer exist
|
|
3073
|
+
*/
|
|
3074
|
+
validateSelection() {
|
|
3075
|
+
if (self.editorUI.selectedBlockId && !self.template.findBlockById(self.editorUI.selectedBlockId)) {
|
|
3076
|
+
self.editorUI.clearSelection();
|
|
3077
|
+
}
|
|
3078
|
+
if (self.editorUI.selectedSectionId && !self.template.getSectionById(self.editorUI.selectedSectionId)) {
|
|
3079
|
+
self.editorUI.clearSelection();
|
|
3080
|
+
}
|
|
3081
|
+
if (self.editorUI.selectedColumnId && !self.template.findColumnById(self.editorUI.selectedColumnId)) {
|
|
3082
|
+
self.editorUI.clearSelection();
|
|
3083
|
+
}
|
|
3084
|
+
if (self.editorUI.selectedWrapperId && !self.template.getWrapperById(self.editorUI.selectedWrapperId)) {
|
|
3085
|
+
self.editorUI.clearSelection();
|
|
3086
|
+
}
|
|
3087
|
+
if (self.editorUI.selectedSubColumnId && !this.findSubColumn(self.editorUI.selectedSubColumnId)) {
|
|
3088
|
+
self.editorUI.clearSelection();
|
|
3089
|
+
}
|
|
3090
|
+
if (self.editorUI.pendingWrapperDeleteId && !self.template.getWrapperById(self.editorUI.pendingWrapperDeleteId)) {
|
|
3091
|
+
self.editorUI.cancelWrapperDelete();
|
|
3092
|
+
}
|
|
3093
|
+
},
|
|
3094
|
+
findSubColumn(id) {
|
|
3095
|
+
for (const section of self.template.allSections) {
|
|
3096
|
+
for (const column of section.columns) {
|
|
3097
|
+
const sc = (column.subColumns ?? []).find((s) => s.id === id);
|
|
3098
|
+
if (sc) return sc;
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
return void 0;
|
|
3102
|
+
},
|
|
3103
|
+
/**
|
|
3104
|
+
* Load a new template
|
|
3105
|
+
*/
|
|
3106
|
+
loadTemplate(templateData) {
|
|
3107
|
+
(0, import_mobx_state_tree8.applySnapshot)(self.template, templateData);
|
|
3108
|
+
self.editorUI.clearSelection();
|
|
3109
|
+
this.clearHistory();
|
|
3110
|
+
},
|
|
3111
|
+
/**
|
|
3112
|
+
* Reset to a new empty template
|
|
3113
|
+
*/
|
|
3114
|
+
resetTemplate(title) {
|
|
3115
|
+
const newTemplate = createTemplateWithDefaultSection({ title });
|
|
3116
|
+
(0, import_mobx_state_tree8.applySnapshot)(self.template, newTemplate);
|
|
3117
|
+
self.editorUI.clearSelection();
|
|
3118
|
+
this.clearHistory();
|
|
3119
|
+
}
|
|
3120
|
+
})).views((self) => ({
|
|
3121
|
+
/**
|
|
3122
|
+
* Check if undo is available
|
|
3123
|
+
*/
|
|
3124
|
+
get canUndo() {
|
|
3125
|
+
return self.historyIndex > 0;
|
|
3126
|
+
},
|
|
3127
|
+
/**
|
|
3128
|
+
* Check if redo is available
|
|
3129
|
+
*/
|
|
3130
|
+
get canRedo() {
|
|
3131
|
+
return self.historyIndex < self.history.length - 1;
|
|
3132
|
+
},
|
|
3133
|
+
/**
|
|
3134
|
+
* Get the currently selected block. Explicit return types here
|
|
3135
|
+
* (and on the other selectors below) keep the inferred RootStore
|
|
3136
|
+
* type small enough for TypeScript's serialization limit.
|
|
3137
|
+
*/
|
|
3138
|
+
get selectedBlock() {
|
|
3139
|
+
if (!self.editorUI.selectedBlockId) return void 0;
|
|
3140
|
+
return self.template.findBlockById(self.editorUI.selectedBlockId);
|
|
3141
|
+
},
|
|
3142
|
+
/**
|
|
3143
|
+
* Get the currently selected section
|
|
3144
|
+
*/
|
|
3145
|
+
get selectedSection() {
|
|
3146
|
+
if (!self.editorUI.selectedSectionId) return void 0;
|
|
3147
|
+
return self.template.getSectionById(self.editorUI.selectedSectionId);
|
|
3148
|
+
},
|
|
3149
|
+
/**
|
|
3150
|
+
* Get the currently selected column
|
|
3151
|
+
*/
|
|
3152
|
+
get selectedColumn() {
|
|
3153
|
+
if (!self.editorUI.selectedColumnId) return void 0;
|
|
3154
|
+
return self.template.findColumnById(self.editorUI.selectedColumnId);
|
|
3155
|
+
},
|
|
3156
|
+
/**
|
|
3157
|
+
* Get the currently selected sub-column (depth-2 nested).
|
|
3158
|
+
*/
|
|
3159
|
+
get selectedSubColumn() {
|
|
3160
|
+
const id = self.editorUI.selectedSubColumnId;
|
|
3161
|
+
if (!id) return void 0;
|
|
3162
|
+
for (const section of self.template.allSections) {
|
|
3163
|
+
for (const column of section.columns) {
|
|
3164
|
+
const sc = (column.subColumns ?? []).find((s) => s.id === id);
|
|
3165
|
+
if (sc) return sc;
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
return void 0;
|
|
3169
|
+
},
|
|
3170
|
+
/**
|
|
3171
|
+
* Get the currently selected wrapper
|
|
3172
|
+
*/
|
|
3173
|
+
get selectedWrapper() {
|
|
3174
|
+
if (!self.editorUI.selectedWrapperId) return void 0;
|
|
3175
|
+
return self.template.getWrapperById(self.editorUI.selectedWrapperId);
|
|
3176
|
+
},
|
|
3177
|
+
/**
|
|
3178
|
+
* Get history size info
|
|
3179
|
+
*/
|
|
3180
|
+
get historyInfo() {
|
|
3181
|
+
return {
|
|
3182
|
+
current: self.historyIndex + 1,
|
|
3183
|
+
total: self.history.length,
|
|
3184
|
+
canUndo: self.historyIndex > 0,
|
|
3185
|
+
canRedo: self.historyIndex < self.history.length - 1
|
|
3186
|
+
};
|
|
3187
|
+
}
|
|
3188
|
+
}));
|
|
3189
|
+
function createRootStore(options = {}) {
|
|
3190
|
+
const { template: initialTemplate, onChange, onChangeDebounce = 300 } = options;
|
|
3191
|
+
const store = RootStore.create({
|
|
3192
|
+
template: initialTemplate || createTemplateWithDefaultSection(),
|
|
3193
|
+
editorUI: {}
|
|
3194
|
+
});
|
|
3195
|
+
store.initHistory();
|
|
3196
|
+
if (onChange) {
|
|
3197
|
+
let timeoutId;
|
|
3198
|
+
(0, import_mobx_state_tree8.onSnapshot)(store.template, (snapshot) => {
|
|
3199
|
+
store.recordHistory();
|
|
3200
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
3201
|
+
timeoutId = setTimeout(() => {
|
|
3202
|
+
onChange(snapshot);
|
|
3203
|
+
}, onChangeDebounce);
|
|
3204
|
+
});
|
|
3205
|
+
} else {
|
|
3206
|
+
(0, import_mobx_state_tree8.onSnapshot)(store.template, () => {
|
|
3207
|
+
store.recordHistory();
|
|
3208
|
+
});
|
|
3209
|
+
}
|
|
3210
|
+
return store;
|
|
3211
|
+
}
|
|
3212
|
+
function createEmptyStore() {
|
|
3213
|
+
return createRootStore({
|
|
3214
|
+
template: createTemplate()
|
|
3215
|
+
});
|
|
3216
|
+
}
|
|
3217
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3218
|
+
0 && (module.exports = {
|
|
3219
|
+
AccordionBlockSchema,
|
|
3220
|
+
AccordionItemModel,
|
|
3221
|
+
BackgroundGradientSchema,
|
|
3222
|
+
BlockModel,
|
|
3223
|
+
BlockRegistryImpl,
|
|
3224
|
+
BlockSchema,
|
|
3225
|
+
BlockType,
|
|
3226
|
+
ButtonBlockSchema,
|
|
3227
|
+
CONTAINER_BLOCK_TYPES,
|
|
3228
|
+
CURRENT_TEMPLATE_VERSION,
|
|
3229
|
+
CarouselBlockSchema,
|
|
3230
|
+
CarouselImageModel,
|
|
3231
|
+
ColumnModel,
|
|
3232
|
+
ColumnSchema,
|
|
3233
|
+
CustomFontSchema,
|
|
3234
|
+
DividerBlockSchema,
|
|
3235
|
+
EditorUIStore,
|
|
3236
|
+
EmailTemplateSchema,
|
|
3237
|
+
EmailTemplateSchemaV1_0,
|
|
3238
|
+
EmailTemplateSchemaV1_1,
|
|
3239
|
+
ExtraAttributesSchema,
|
|
3240
|
+
FontDefinitionModel,
|
|
3241
|
+
FooterBlockSchema,
|
|
3242
|
+
GradientStopSchema,
|
|
3243
|
+
HeaderBlockSchema,
|
|
3244
|
+
HeroBlockSchema,
|
|
3245
|
+
HistoryManager,
|
|
3246
|
+
ImageBlockSchema,
|
|
3247
|
+
LEAF_BLOCK_TYPES,
|
|
3248
|
+
MJML_WRAPPER_DEFAULT_PADDING,
|
|
3249
|
+
MjmlHeadSchema,
|
|
3250
|
+
NavbarBlockSchema,
|
|
3251
|
+
NavbarLinkModel,
|
|
3252
|
+
RawBlockSchema,
|
|
3253
|
+
RootStore,
|
|
3254
|
+
SUPPORTED_TEMPLATE_VERSIONS,
|
|
3255
|
+
SectionModel,
|
|
3256
|
+
SectionSchema,
|
|
3257
|
+
SectionSchemaV1_0,
|
|
3258
|
+
SelectionManager,
|
|
3259
|
+
SocialBlockSchema,
|
|
3260
|
+
SocialLinkModel,
|
|
3261
|
+
SpacerBlockSchema,
|
|
3262
|
+
SpacingModel,
|
|
3263
|
+
SpacingSchema,
|
|
3264
|
+
SubColumnModel,
|
|
3265
|
+
TableBlockSchema,
|
|
3266
|
+
TemplateMetadataModel,
|
|
3267
|
+
TemplateMetadataSchema,
|
|
3268
|
+
TemplateMigrationError,
|
|
3269
|
+
TemplateModel,
|
|
3270
|
+
TextBlockSchema,
|
|
3271
|
+
ThemeColorModel,
|
|
3272
|
+
TopLevelItemModel,
|
|
3273
|
+
TopLevelItemSchema,
|
|
3274
|
+
WrapperModel,
|
|
3275
|
+
WrapperSchema,
|
|
3276
|
+
allSections,
|
|
3277
|
+
buildGradientCSS,
|
|
3278
|
+
cloneSectionSnapshot,
|
|
3279
|
+
createBlockRegistry,
|
|
3280
|
+
createColumn,
|
|
3281
|
+
createEmptyStore,
|
|
3282
|
+
createHistoryManager,
|
|
3283
|
+
createPrebuiltTemplateRegistry,
|
|
3284
|
+
createRootStore,
|
|
3285
|
+
createSection,
|
|
3286
|
+
createSelectionManager,
|
|
3287
|
+
createSubColumn,
|
|
3288
|
+
createTemplate,
|
|
3289
|
+
createTemplateWithDefaultSection,
|
|
3290
|
+
createWrapper,
|
|
3291
|
+
isLeafBlockType,
|
|
3292
|
+
isTemplateMigrationError,
|
|
3293
|
+
isWrapper,
|
|
3294
|
+
isWrapperInstance,
|
|
3295
|
+
migrateTemplate,
|
|
3296
|
+
migrateV1_0ToV1_1,
|
|
3297
|
+
validateTemplate,
|
|
3298
|
+
withTemplateId
|
|
3299
|
+
});
|