@emabuild/core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/canvas/column-renderer.d.ts +15 -0
- package/dist/canvas/column-renderer.d.ts.map +1 -0
- package/dist/canvas/content-renderer.d.ts +23 -0
- package/dist/canvas/content-renderer.d.ts.map +1 -0
- package/dist/canvas/editor-canvas.d.ts +14 -0
- package/dist/canvas/editor-canvas.d.ts.map +1 -0
- package/dist/canvas/row-renderer.d.ts +14 -0
- package/dist/canvas/row-renderer.d.ts.map +1 -0
- package/dist/dnd/drag-manager.d.ts +30 -0
- package/dist/dnd/drag-manager.d.ts.map +1 -0
- package/dist/dnd/drag-state.d.ts +8 -0
- package/dist/dnd/drag-state.d.ts.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/mail-editor-DzI7SmSe.js +2286 -0
- package/dist/mail-editor-DzI7SmSe.js.map +1 -0
- package/dist/mail-editor.d.ts +36 -0
- package/dist/mail-editor.d.ts.map +1 -0
- package/dist/mail-editor.js +7 -0
- package/dist/mail-editor.js.map +1 -0
- package/dist/properties/property-panel.d.ts +19 -0
- package/dist/properties/property-panel.d.ts.map +1 -0
- package/dist/sidebar/editor-sidebar.d.ts +19 -0
- package/dist/sidebar/editor-sidebar.d.ts.map +1 -0
- package/dist/state/editor-store.d.ts +60 -0
- package/dist/state/editor-store.d.ts.map +1 -0
- package/dist/tools/built-in/button-tool.d.ts +3 -0
- package/dist/tools/built-in/button-tool.d.ts.map +1 -0
- package/dist/tools/built-in/divider-tool.d.ts +3 -0
- package/dist/tools/built-in/divider-tool.d.ts.map +1 -0
- package/dist/tools/built-in/form-tool.d.ts +3 -0
- package/dist/tools/built-in/form-tool.d.ts.map +1 -0
- package/dist/tools/built-in/heading-tool.d.ts +3 -0
- package/dist/tools/built-in/heading-tool.d.ts.map +1 -0
- package/dist/tools/built-in/html-tool.d.ts +3 -0
- package/dist/tools/built-in/html-tool.d.ts.map +1 -0
- package/dist/tools/built-in/image-tool.d.ts +3 -0
- package/dist/tools/built-in/image-tool.d.ts.map +1 -0
- package/dist/tools/built-in/index.d.ts +14 -0
- package/dist/tools/built-in/index.d.ts.map +1 -0
- package/dist/tools/built-in/menu-tool.d.ts +3 -0
- package/dist/tools/built-in/menu-tool.d.ts.map +1 -0
- package/dist/tools/built-in/paragraph-tool.d.ts +3 -0
- package/dist/tools/built-in/paragraph-tool.d.ts.map +1 -0
- package/dist/tools/built-in/social-tool.d.ts +3 -0
- package/dist/tools/built-in/social-tool.d.ts.map +1 -0
- package/dist/tools/built-in/table-tool.d.ts +3 -0
- package/dist/tools/built-in/table-tool.d.ts.map +1 -0
- package/dist/tools/built-in/text-tool.d.ts +3 -0
- package/dist/tools/built-in/text-tool.d.ts.map +1 -0
- package/dist/tools/built-in/timer-tool.d.ts +3 -0
- package/dist/tools/built-in/timer-tool.d.ts.map +1 -0
- package/dist/tools/built-in/video-tool.d.ts +3 -0
- package/dist/tools/built-in/video-tool.d.ts.map +1 -0
- package/dist/tools/tool-registry.d.ts +18 -0
- package/dist/tools/tool-registry.d.ts.map +1 -0
- package/dist/utils/deep-clone.d.ts +2 -0
- package/dist/utils/deep-clone.d.ts.map +1 -0
- package/dist/utils/event-emitter.d.ts +10 -0
- package/dist/utils/event-emitter.d.ts.map +1 -0
- package/dist/utils/id-generator.d.ts +7 -0
- package/dist/utils/id-generator.d.ts.map +1 -0
- package/dist/utils/store-subscriber.d.ts +9 -0
- package/dist/utils/store-subscriber.d.ts.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,2286 @@
|
|
|
1
|
+
import { html, css, LitElement } from "lit";
|
|
2
|
+
import { property, customElement } from "lit/decorators.js";
|
|
3
|
+
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
|
4
|
+
function deepClone(obj) {
|
|
5
|
+
return structuredClone(obj);
|
|
6
|
+
}
|
|
7
|
+
function createCounterManager() {
|
|
8
|
+
const counters = {};
|
|
9
|
+
return {
|
|
10
|
+
getCounters() {
|
|
11
|
+
return { ...counters };
|
|
12
|
+
},
|
|
13
|
+
setCounters(c) {
|
|
14
|
+
Object.keys(counters).forEach((k) => delete counters[k]);
|
|
15
|
+
Object.assign(counters, c);
|
|
16
|
+
},
|
|
17
|
+
next(prefix) {
|
|
18
|
+
const current = counters[prefix] ?? 0;
|
|
19
|
+
counters[prefix] = current + 1;
|
|
20
|
+
return counters[prefix];
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
class EventEmitter {
|
|
25
|
+
constructor() {
|
|
26
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
27
|
+
}
|
|
28
|
+
on(event, listener) {
|
|
29
|
+
if (!this.listeners.has(event)) {
|
|
30
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
31
|
+
}
|
|
32
|
+
this.listeners.get(event).add(listener);
|
|
33
|
+
}
|
|
34
|
+
off(event, listener) {
|
|
35
|
+
this.listeners.get(event)?.delete(listener);
|
|
36
|
+
}
|
|
37
|
+
emit(event, ...args) {
|
|
38
|
+
this.listeners.get(event)?.forEach((fn) => {
|
|
39
|
+
try {
|
|
40
|
+
fn(...args);
|
|
41
|
+
} catch (e) {
|
|
42
|
+
console.error(`[maileditor] Error in "${event}" listener:`, e);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
removeAllListeners(event) {
|
|
47
|
+
if (event) {
|
|
48
|
+
this.listeners.delete(event);
|
|
49
|
+
} else {
|
|
50
|
+
this.listeners.clear();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function createEmptyDesign() {
|
|
55
|
+
return {
|
|
56
|
+
counters: { u_row: 0, u_column: 0 },
|
|
57
|
+
body: {
|
|
58
|
+
id: "u_body",
|
|
59
|
+
rows: [],
|
|
60
|
+
headers: [],
|
|
61
|
+
footers: [],
|
|
62
|
+
values: {
|
|
63
|
+
backgroundColor: "#e7e7e7",
|
|
64
|
+
contentAlign: "center",
|
|
65
|
+
contentVerticalAlign: "center",
|
|
66
|
+
contentWidth: "600px",
|
|
67
|
+
fontFamily: { label: "Arial", value: "arial,helvetica,sans-serif" },
|
|
68
|
+
textColor: "#000000",
|
|
69
|
+
linkStyle: {
|
|
70
|
+
body: true,
|
|
71
|
+
linkColor: "#0000ee",
|
|
72
|
+
linkHoverColor: "#0000ee",
|
|
73
|
+
linkUnderline: true,
|
|
74
|
+
linkHoverUnderline: true
|
|
75
|
+
},
|
|
76
|
+
preheaderText: "",
|
|
77
|
+
popupPosition: "center",
|
|
78
|
+
popupWidth: "600px",
|
|
79
|
+
popupHeight: "auto",
|
|
80
|
+
borderRadius: "10px",
|
|
81
|
+
popupBackgroundColor: "#FFFFFF",
|
|
82
|
+
popupBackgroundImage: { url: "", fullWidth: true, repeat: "no-repeat", center: true, cover: true },
|
|
83
|
+
popupOverlay_backgroundColor: "rgba(0, 0, 0, 0.1)",
|
|
84
|
+
popupCloseButton_position: "top-right",
|
|
85
|
+
popupCloseButton_backgroundColor: "#DDDDDD",
|
|
86
|
+
popupCloseButton_iconColor: "#000000",
|
|
87
|
+
popupCloseButton_borderRadius: "0px",
|
|
88
|
+
popupCloseButton_margin: "0px",
|
|
89
|
+
popupCloseButton_action: {
|
|
90
|
+
name: "close_popup",
|
|
91
|
+
attrs: { onClick: "document.querySelector('.u-popup-container').style.display = 'none';" }
|
|
92
|
+
},
|
|
93
|
+
_meta: { htmlID: "u_body", htmlClassNames: "u_body" }
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
schemaVersion: 16
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
class EditorStore {
|
|
100
|
+
constructor() {
|
|
101
|
+
this.undoStack = [];
|
|
102
|
+
this.redoStack = [];
|
|
103
|
+
this.maxHistory = 50;
|
|
104
|
+
this.subscribers = /* @__PURE__ */ new Set();
|
|
105
|
+
this.counterManager = createCounterManager();
|
|
106
|
+
this.events = new EventEmitter();
|
|
107
|
+
this._selectedId = null;
|
|
108
|
+
this._hoveredId = null;
|
|
109
|
+
this._viewMode = "desktop";
|
|
110
|
+
this._activeTab = "content";
|
|
111
|
+
this.design = createEmptyDesign();
|
|
112
|
+
}
|
|
113
|
+
// ----------------------------------------------------------
|
|
114
|
+
// Subscriptions (for Lit reactive controllers)
|
|
115
|
+
// ----------------------------------------------------------
|
|
116
|
+
subscribe(fn) {
|
|
117
|
+
this.subscribers.add(fn);
|
|
118
|
+
return () => this.subscribers.delete(fn);
|
|
119
|
+
}
|
|
120
|
+
notify() {
|
|
121
|
+
this.subscribers.forEach((fn) => fn());
|
|
122
|
+
}
|
|
123
|
+
// ----------------------------------------------------------
|
|
124
|
+
// Getters
|
|
125
|
+
// ----------------------------------------------------------
|
|
126
|
+
getDesign() {
|
|
127
|
+
return this.design;
|
|
128
|
+
}
|
|
129
|
+
getBody() {
|
|
130
|
+
return this.design.body;
|
|
131
|
+
}
|
|
132
|
+
getRows() {
|
|
133
|
+
return this.design.body.rows;
|
|
134
|
+
}
|
|
135
|
+
getBodyValues() {
|
|
136
|
+
return this.design.body.values;
|
|
137
|
+
}
|
|
138
|
+
get selectedId() {
|
|
139
|
+
return this._selectedId;
|
|
140
|
+
}
|
|
141
|
+
get hoveredId() {
|
|
142
|
+
return this._hoveredId;
|
|
143
|
+
}
|
|
144
|
+
get viewMode() {
|
|
145
|
+
return this._viewMode;
|
|
146
|
+
}
|
|
147
|
+
get activeTab() {
|
|
148
|
+
return this._activeTab;
|
|
149
|
+
}
|
|
150
|
+
get canUndo() {
|
|
151
|
+
return this.undoStack.length > 0;
|
|
152
|
+
}
|
|
153
|
+
get canRedo() {
|
|
154
|
+
return this.redoStack.length > 0;
|
|
155
|
+
}
|
|
156
|
+
// ----------------------------------------------------------
|
|
157
|
+
// Design loading
|
|
158
|
+
// ----------------------------------------------------------
|
|
159
|
+
loadDesign(design) {
|
|
160
|
+
this.design = deepClone(design);
|
|
161
|
+
this.counterManager.setCounters(this.design.counters);
|
|
162
|
+
this.undoStack = [];
|
|
163
|
+
this.redoStack = [];
|
|
164
|
+
this._selectedId = null;
|
|
165
|
+
this.notify();
|
|
166
|
+
this.events.emit("design:loaded", { design: this.design });
|
|
167
|
+
}
|
|
168
|
+
// ----------------------------------------------------------
|
|
169
|
+
// History (undo/redo)
|
|
170
|
+
// ----------------------------------------------------------
|
|
171
|
+
pushHistory() {
|
|
172
|
+
this.undoStack.push(deepClone(this.design));
|
|
173
|
+
if (this.undoStack.length > this.maxHistory) {
|
|
174
|
+
this.undoStack.shift();
|
|
175
|
+
}
|
|
176
|
+
this.redoStack = [];
|
|
177
|
+
}
|
|
178
|
+
undo() {
|
|
179
|
+
const prev = this.undoStack.pop();
|
|
180
|
+
if (!prev) return;
|
|
181
|
+
this.redoStack.push(deepClone(this.design));
|
|
182
|
+
this.design = prev;
|
|
183
|
+
this.counterManager.setCounters(this.design.counters);
|
|
184
|
+
this.notify();
|
|
185
|
+
this.emitUpdate("content_updated");
|
|
186
|
+
}
|
|
187
|
+
redo() {
|
|
188
|
+
const next = this.redoStack.pop();
|
|
189
|
+
if (!next) return;
|
|
190
|
+
this.undoStack.push(deepClone(this.design));
|
|
191
|
+
this.design = next;
|
|
192
|
+
this.counterManager.setCounters(this.design.counters);
|
|
193
|
+
this.notify();
|
|
194
|
+
this.emitUpdate("content_updated");
|
|
195
|
+
}
|
|
196
|
+
// ----------------------------------------------------------
|
|
197
|
+
// Selection / UI state
|
|
198
|
+
// ----------------------------------------------------------
|
|
199
|
+
select(id) {
|
|
200
|
+
this._selectedId = id;
|
|
201
|
+
this.notify();
|
|
202
|
+
}
|
|
203
|
+
hover(id) {
|
|
204
|
+
this._hoveredId = id;
|
|
205
|
+
this.notify();
|
|
206
|
+
}
|
|
207
|
+
setViewMode(mode) {
|
|
208
|
+
this._viewMode = mode;
|
|
209
|
+
this.notify();
|
|
210
|
+
}
|
|
211
|
+
setActiveTab(tab) {
|
|
212
|
+
this._activeTab = tab;
|
|
213
|
+
this.notify();
|
|
214
|
+
}
|
|
215
|
+
// ----------------------------------------------------------
|
|
216
|
+
// Row operations
|
|
217
|
+
// ----------------------------------------------------------
|
|
218
|
+
addRow(row, index) {
|
|
219
|
+
this.pushHistory();
|
|
220
|
+
const rows = this.design.body.rows;
|
|
221
|
+
if (index !== void 0 && index >= 0 && index <= rows.length) {
|
|
222
|
+
rows.splice(index, 0, deepClone(row));
|
|
223
|
+
} else {
|
|
224
|
+
rows.push(deepClone(row));
|
|
225
|
+
}
|
|
226
|
+
this.syncCounters();
|
|
227
|
+
this.notify();
|
|
228
|
+
this.emitUpdate("row_added", row);
|
|
229
|
+
}
|
|
230
|
+
removeRow(rowId) {
|
|
231
|
+
this.pushHistory();
|
|
232
|
+
const rows = this.design.body.rows;
|
|
233
|
+
const idx = rows.findIndex((r) => r.id === rowId);
|
|
234
|
+
if (idx !== -1) {
|
|
235
|
+
rows.splice(idx, 1);
|
|
236
|
+
if (this._selectedId === rowId) this._selectedId = null;
|
|
237
|
+
this.notify();
|
|
238
|
+
this.emitUpdate("row_removed");
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
moveRow(fromIndex, toIndex) {
|
|
242
|
+
this.pushHistory();
|
|
243
|
+
const rows = this.design.body.rows;
|
|
244
|
+
const [row] = rows.splice(fromIndex, 1);
|
|
245
|
+
rows.splice(toIndex, 0, row);
|
|
246
|
+
this.notify();
|
|
247
|
+
this.emitUpdate("row_reordered");
|
|
248
|
+
}
|
|
249
|
+
updateRowValues(rowId, patch) {
|
|
250
|
+
const row = this.findRow(rowId);
|
|
251
|
+
if (!row) return;
|
|
252
|
+
this.pushHistory();
|
|
253
|
+
Object.assign(row.values, patch);
|
|
254
|
+
this.notify();
|
|
255
|
+
this.emitUpdate("content_updated");
|
|
256
|
+
}
|
|
257
|
+
// ----------------------------------------------------------
|
|
258
|
+
// Column operations
|
|
259
|
+
// ----------------------------------------------------------
|
|
260
|
+
updateColumnValues(columnId, patch) {
|
|
261
|
+
const col = this.findColumn(columnId);
|
|
262
|
+
if (!col) return;
|
|
263
|
+
this.pushHistory();
|
|
264
|
+
Object.assign(col.values, patch);
|
|
265
|
+
this.notify();
|
|
266
|
+
this.emitUpdate("content_updated");
|
|
267
|
+
}
|
|
268
|
+
// ----------------------------------------------------------
|
|
269
|
+
// Content operations
|
|
270
|
+
// ----------------------------------------------------------
|
|
271
|
+
addContent(columnId, content, index) {
|
|
272
|
+
const col = this.findColumn(columnId);
|
|
273
|
+
if (!col) return;
|
|
274
|
+
this.pushHistory();
|
|
275
|
+
const cloned = deepClone(content);
|
|
276
|
+
if (index !== void 0 && index >= 0 && index <= col.contents.length) {
|
|
277
|
+
col.contents.splice(index, 0, cloned);
|
|
278
|
+
} else {
|
|
279
|
+
col.contents.push(cloned);
|
|
280
|
+
}
|
|
281
|
+
this.syncCounters();
|
|
282
|
+
this.notify();
|
|
283
|
+
this.emitUpdate("content_added", cloned);
|
|
284
|
+
}
|
|
285
|
+
removeContent(contentId) {
|
|
286
|
+
for (const row of this.design.body.rows) {
|
|
287
|
+
for (const col of row.columns) {
|
|
288
|
+
const idx = col.contents.findIndex((c) => c.id === contentId);
|
|
289
|
+
if (idx !== -1) {
|
|
290
|
+
this.pushHistory();
|
|
291
|
+
col.contents.splice(idx, 1);
|
|
292
|
+
if (this._selectedId === contentId) this._selectedId = null;
|
|
293
|
+
this.notify();
|
|
294
|
+
this.emitUpdate("content_removed");
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
updateContentValues(contentId, patch) {
|
|
301
|
+
const content = this.findContent(contentId);
|
|
302
|
+
if (!content) return;
|
|
303
|
+
this.pushHistory();
|
|
304
|
+
Object.assign(content.values, patch);
|
|
305
|
+
this.notify();
|
|
306
|
+
this.emitUpdate("content_updated");
|
|
307
|
+
}
|
|
308
|
+
moveContent(contentId, targetColumnId, targetIndex) {
|
|
309
|
+
const content = this.findContent(contentId);
|
|
310
|
+
if (!content) return;
|
|
311
|
+
for (const row of this.design.body.rows) {
|
|
312
|
+
for (const col of row.columns) {
|
|
313
|
+
const idx = col.contents.findIndex((c) => c.id === contentId);
|
|
314
|
+
if (idx !== -1) {
|
|
315
|
+
this.pushHistory();
|
|
316
|
+
col.contents.splice(idx, 1);
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const targetCol = this.findColumn(targetColumnId);
|
|
322
|
+
if (targetCol) {
|
|
323
|
+
targetCol.contents.splice(targetIndex, 0, content);
|
|
324
|
+
}
|
|
325
|
+
this.notify();
|
|
326
|
+
this.emitUpdate("content_reordered");
|
|
327
|
+
}
|
|
328
|
+
duplicateContent(contentId) {
|
|
329
|
+
const content = this.findContent(contentId);
|
|
330
|
+
if (!content) return;
|
|
331
|
+
for (const row of this.design.body.rows) {
|
|
332
|
+
for (const col of row.columns) {
|
|
333
|
+
const idx = col.contents.findIndex((c) => c.id === contentId);
|
|
334
|
+
if (idx !== -1) {
|
|
335
|
+
const cloned = deepClone(content);
|
|
336
|
+
const counter = this.counterManager.next(`u_content_${content.type}`);
|
|
337
|
+
cloned.id = `u_content_${content.type}_${counter}`;
|
|
338
|
+
cloned.values._meta = {
|
|
339
|
+
htmlID: cloned.id,
|
|
340
|
+
htmlClassNames: `u_content_${content.type}`
|
|
341
|
+
};
|
|
342
|
+
this.pushHistory();
|
|
343
|
+
col.contents.splice(idx + 1, 0, cloned);
|
|
344
|
+
this.syncCounters();
|
|
345
|
+
this.notify();
|
|
346
|
+
this.emitUpdate("content_added", cloned);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// ----------------------------------------------------------
|
|
353
|
+
// Body values
|
|
354
|
+
// ----------------------------------------------------------
|
|
355
|
+
updateBodyValues(patch) {
|
|
356
|
+
this.pushHistory();
|
|
357
|
+
Object.assign(this.design.body.values, patch);
|
|
358
|
+
this.notify();
|
|
359
|
+
this.emitUpdate("body_updated");
|
|
360
|
+
}
|
|
361
|
+
// ----------------------------------------------------------
|
|
362
|
+
// Lookup helpers
|
|
363
|
+
// ----------------------------------------------------------
|
|
364
|
+
findRow(rowId) {
|
|
365
|
+
return this.design.body.rows.find((r) => r.id === rowId);
|
|
366
|
+
}
|
|
367
|
+
findColumn(columnId) {
|
|
368
|
+
for (const row of this.design.body.rows) {
|
|
369
|
+
const col = row.columns.find((c) => c.id === columnId);
|
|
370
|
+
if (col) return col;
|
|
371
|
+
}
|
|
372
|
+
return void 0;
|
|
373
|
+
}
|
|
374
|
+
findContent(contentId) {
|
|
375
|
+
for (const row of this.design.body.rows) {
|
|
376
|
+
for (const col of row.columns) {
|
|
377
|
+
const content = col.contents.find((c) => c.id === contentId);
|
|
378
|
+
if (content) return content;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return void 0;
|
|
382
|
+
}
|
|
383
|
+
findParentColumn(contentId) {
|
|
384
|
+
for (const row of this.design.body.rows) {
|
|
385
|
+
for (const col of row.columns) {
|
|
386
|
+
if (col.contents.some((c) => c.id === contentId)) return col;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return void 0;
|
|
390
|
+
}
|
|
391
|
+
findParentRow(columnId) {
|
|
392
|
+
for (const row of this.design.body.rows) {
|
|
393
|
+
if (row.columns.some((c) => c.id === columnId)) return row;
|
|
394
|
+
}
|
|
395
|
+
return void 0;
|
|
396
|
+
}
|
|
397
|
+
// ----------------------------------------------------------
|
|
398
|
+
// Helpers
|
|
399
|
+
// ----------------------------------------------------------
|
|
400
|
+
/** Creates a new row with the given column layout */
|
|
401
|
+
createRow(cellProportions) {
|
|
402
|
+
const rowNum = this.counterManager.next("u_row");
|
|
403
|
+
const columns = cellProportions.map((_, i) => {
|
|
404
|
+
const colNum = this.counterManager.next("u_column");
|
|
405
|
+
return {
|
|
406
|
+
id: `u_column_${colNum}`,
|
|
407
|
+
contents: [],
|
|
408
|
+
values: {
|
|
409
|
+
backgroundColor: "",
|
|
410
|
+
padding: "0px",
|
|
411
|
+
border: {},
|
|
412
|
+
borderRadius: "0px",
|
|
413
|
+
_meta: { htmlID: `u_column_${colNum}`, htmlClassNames: "u_column" }
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
});
|
|
417
|
+
this.syncCounters();
|
|
418
|
+
return {
|
|
419
|
+
id: `u_row_${rowNum}`,
|
|
420
|
+
cells: cellProportions,
|
|
421
|
+
columns,
|
|
422
|
+
values: {
|
|
423
|
+
displayCondition: null,
|
|
424
|
+
columns: false,
|
|
425
|
+
backgroundColor: "",
|
|
426
|
+
columnsBackgroundColor: "",
|
|
427
|
+
backgroundImage: { url: "", fullWidth: true, repeat: false, center: true, cover: false },
|
|
428
|
+
padding: "0px",
|
|
429
|
+
anchor: "",
|
|
430
|
+
hideDesktop: false,
|
|
431
|
+
hideMobile: false,
|
|
432
|
+
_meta: { htmlID: `u_row_${rowNum}`, htmlClassNames: "u_row" }
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
/** Creates a new content block for the given tool type */
|
|
437
|
+
createContent(type, values = {}) {
|
|
438
|
+
const counter = this.counterManager.next(`u_content_${type}`);
|
|
439
|
+
const id = `u_content_${type}_${counter}`;
|
|
440
|
+
this.syncCounters();
|
|
441
|
+
return {
|
|
442
|
+
id,
|
|
443
|
+
type,
|
|
444
|
+
values: {
|
|
445
|
+
containerPadding: "10px",
|
|
446
|
+
anchor: "",
|
|
447
|
+
hideDesktop: false,
|
|
448
|
+
hideMobile: false,
|
|
449
|
+
displayCondition: null,
|
|
450
|
+
_meta: { htmlID: id, htmlClassNames: `u_content_${type}` },
|
|
451
|
+
...values
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
syncCounters() {
|
|
456
|
+
this.design.counters = this.counterManager.getCounters();
|
|
457
|
+
}
|
|
458
|
+
emitUpdate(type, item) {
|
|
459
|
+
this.events.emit("design:updated", { type, item });
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
class ToolRegistry {
|
|
463
|
+
constructor() {
|
|
464
|
+
this.tools = /* @__PURE__ */ new Map();
|
|
465
|
+
}
|
|
466
|
+
register(tool) {
|
|
467
|
+
this.tools.set(tool.name, tool);
|
|
468
|
+
}
|
|
469
|
+
get(name) {
|
|
470
|
+
return this.tools.get(name);
|
|
471
|
+
}
|
|
472
|
+
getAll() {
|
|
473
|
+
return Array.from(this.tools.values()).sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
|
|
474
|
+
}
|
|
475
|
+
has(name) {
|
|
476
|
+
return this.tools.has(name);
|
|
477
|
+
}
|
|
478
|
+
getDefaultValues(name) {
|
|
479
|
+
const tool = this.tools.get(name);
|
|
480
|
+
if (!tool) return {};
|
|
481
|
+
const defaults = { ...tool.defaultValues };
|
|
482
|
+
for (const group of Object.values(tool.options)) {
|
|
483
|
+
for (const [key, prop] of Object.entries(group.options)) {
|
|
484
|
+
if (!(key in defaults)) {
|
|
485
|
+
defaults[key] = prop.defaultValue;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return defaults;
|
|
490
|
+
}
|
|
491
|
+
getPropertyGroups(name) {
|
|
492
|
+
return this.tools.get(name)?.options ?? {};
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
const dragState = {
|
|
496
|
+
/** ID of the content currently being dragged (null if not dragging content) */
|
|
497
|
+
draggingContentId: null,
|
|
498
|
+
startContentDrag(contentId) {
|
|
499
|
+
this.draggingContentId = contentId;
|
|
500
|
+
},
|
|
501
|
+
reset() {
|
|
502
|
+
this.draggingContentId = null;
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
class DragManager {
|
|
506
|
+
constructor(store, toolRegistry, root) {
|
|
507
|
+
this.currentDrop = null;
|
|
508
|
+
this.indicator = null;
|
|
509
|
+
this.rowIndicator = null;
|
|
510
|
+
this.handleDragStart = (_e) => {
|
|
511
|
+
};
|
|
512
|
+
this.handleDragOver = (e) => {
|
|
513
|
+
const types = e.dataTransfer?.types || [];
|
|
514
|
+
const isToolDrag = types.includes("application/maileditor-tool");
|
|
515
|
+
const isLayoutDrag = types.includes("application/maileditor-layout");
|
|
516
|
+
const isContentDrag = types.includes("application/maileditor-content") || !!dragState.draggingContentId;
|
|
517
|
+
if (!isToolDrag && !isContentDrag && !isLayoutDrag) return;
|
|
518
|
+
e.preventDefault();
|
|
519
|
+
e.dataTransfer.dropEffect = isToolDrag || isLayoutDrag ? "copy" : "move";
|
|
520
|
+
if (isLayoutDrag) {
|
|
521
|
+
const drop = this.findRowDropPosition(e.clientY);
|
|
522
|
+
this.hideIndicator();
|
|
523
|
+
if (drop) {
|
|
524
|
+
this.currentDrop = drop;
|
|
525
|
+
this.showRowIndicator(drop);
|
|
526
|
+
} else {
|
|
527
|
+
this.hideRowIndicator();
|
|
528
|
+
this.currentDrop = null;
|
|
529
|
+
}
|
|
530
|
+
} else {
|
|
531
|
+
const drop = this.findContentDropPosition(e.clientX, e.clientY);
|
|
532
|
+
this.hideRowIndicator();
|
|
533
|
+
if (drop) {
|
|
534
|
+
this.currentDrop = drop;
|
|
535
|
+
this.showContentIndicator(drop);
|
|
536
|
+
} else {
|
|
537
|
+
this.hideIndicator();
|
|
538
|
+
this.currentDrop = null;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
this.handleDrop = (e) => {
|
|
543
|
+
e.preventDefault();
|
|
544
|
+
this.hideIndicator();
|
|
545
|
+
this.hideRowIndicator();
|
|
546
|
+
const layoutData = e.dataTransfer?.getData("application/maileditor-layout");
|
|
547
|
+
if (layoutData) {
|
|
548
|
+
const cells = JSON.parse(layoutData);
|
|
549
|
+
const row = this.store.createRow(cells);
|
|
550
|
+
const rowIndex = this.currentDrop?.type === "row" ? this.currentDrop.rowIndex : void 0;
|
|
551
|
+
this.store.addRow(row, rowIndex);
|
|
552
|
+
this.reset();
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const toolName = e.dataTransfer?.getData("application/maileditor-tool");
|
|
556
|
+
if (toolName) {
|
|
557
|
+
if (this.currentDrop?.type === "content" && this.currentDrop.columnId) {
|
|
558
|
+
const defaults = this.toolRegistry.getDefaultValues(toolName);
|
|
559
|
+
const content = this.store.createContent(toolName, defaults);
|
|
560
|
+
this.store.addContent(this.currentDrop.columnId, content, this.currentDrop.contentIndex);
|
|
561
|
+
this.store.select(content.id);
|
|
562
|
+
} else {
|
|
563
|
+
const row = this.store.createRow([1]);
|
|
564
|
+
this.store.addRow(row);
|
|
565
|
+
const defaults = this.toolRegistry.getDefaultValues(toolName);
|
|
566
|
+
const content = this.store.createContent(toolName, defaults);
|
|
567
|
+
this.store.addContent(row.columns[0].id, content);
|
|
568
|
+
this.store.select(content.id);
|
|
569
|
+
}
|
|
570
|
+
this.reset();
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
const contentId = e.dataTransfer?.getData("application/maileditor-content") || dragState.draggingContentId;
|
|
574
|
+
if (contentId && this.currentDrop?.type === "content" && this.currentDrop.columnId) {
|
|
575
|
+
this.store.moveContent(contentId, this.currentDrop.columnId, this.currentDrop.contentIndex);
|
|
576
|
+
this.store.select(contentId);
|
|
577
|
+
}
|
|
578
|
+
this.reset();
|
|
579
|
+
dragState.reset();
|
|
580
|
+
};
|
|
581
|
+
this.handleDragEnd = () => {
|
|
582
|
+
this.hideIndicator();
|
|
583
|
+
this.hideRowIndicator();
|
|
584
|
+
this.reset();
|
|
585
|
+
};
|
|
586
|
+
this.handleDragLeave = (e) => {
|
|
587
|
+
const related = e.relatedTarget;
|
|
588
|
+
if (!related || !this.root.contains(related)) {
|
|
589
|
+
this.hideIndicator();
|
|
590
|
+
this.hideRowIndicator();
|
|
591
|
+
this.currentDrop = null;
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
this.store = store;
|
|
595
|
+
this.toolRegistry = toolRegistry;
|
|
596
|
+
this.root = root;
|
|
597
|
+
}
|
|
598
|
+
attach() {
|
|
599
|
+
this.root.addEventListener("dragover", this.handleDragOver);
|
|
600
|
+
this.root.addEventListener("drop", this.handleDrop);
|
|
601
|
+
this.root.addEventListener("dragend", this.handleDragEnd);
|
|
602
|
+
this.root.addEventListener("dragleave", this.handleDragLeave);
|
|
603
|
+
this.root.addEventListener("dragstart", this.handleDragStart);
|
|
604
|
+
this.indicator = this.createIndicator("#3b82f6");
|
|
605
|
+
this.rowIndicator = this.createIndicator("#8b5cf6");
|
|
606
|
+
}
|
|
607
|
+
createIndicator(color) {
|
|
608
|
+
const el = document.createElement("div");
|
|
609
|
+
Object.assign(el.style, {
|
|
610
|
+
position: "absolute",
|
|
611
|
+
left: "0",
|
|
612
|
+
right: "0",
|
|
613
|
+
height: "3px",
|
|
614
|
+
background: color,
|
|
615
|
+
borderRadius: "2px",
|
|
616
|
+
pointerEvents: "none",
|
|
617
|
+
zIndex: "1000",
|
|
618
|
+
display: "none",
|
|
619
|
+
boxShadow: `0 0 6px ${color}80`
|
|
620
|
+
});
|
|
621
|
+
return el;
|
|
622
|
+
}
|
|
623
|
+
detach() {
|
|
624
|
+
this.root.removeEventListener("dragover", this.handleDragOver);
|
|
625
|
+
this.root.removeEventListener("drop", this.handleDrop);
|
|
626
|
+
this.root.removeEventListener("dragend", this.handleDragEnd);
|
|
627
|
+
this.root.removeEventListener("dragleave", this.handleDragLeave);
|
|
628
|
+
this.root.removeEventListener("dragstart", this.handleDragStart);
|
|
629
|
+
this.indicator?.remove();
|
|
630
|
+
this.rowIndicator?.remove();
|
|
631
|
+
}
|
|
632
|
+
// ----------------------------------------------------------
|
|
633
|
+
// Find row drop position (between rows)
|
|
634
|
+
// ----------------------------------------------------------
|
|
635
|
+
findRowDropPosition(clientY) {
|
|
636
|
+
const canvas = this.root.querySelector("me-editor-canvas");
|
|
637
|
+
if (!canvas?.shadowRoot) return null;
|
|
638
|
+
const rows = Array.from(canvas.shadowRoot.querySelectorAll("me-row-renderer"));
|
|
639
|
+
if (rows.length === 0) {
|
|
640
|
+
return { type: "row", rowIndex: 0, y: 0 };
|
|
641
|
+
}
|
|
642
|
+
const firstRect = rows[0].getBoundingClientRect();
|
|
643
|
+
let bestDist = Math.abs(clientY - firstRect.top);
|
|
644
|
+
let bestTarget = { type: "row", rowIndex: 0, y: firstRect.top };
|
|
645
|
+
for (let i = 0; i < rows.length; i++) {
|
|
646
|
+
const rect = rows[i].getBoundingClientRect();
|
|
647
|
+
const y = rect.bottom;
|
|
648
|
+
const dist = Math.abs(clientY - y);
|
|
649
|
+
if (dist < bestDist) {
|
|
650
|
+
bestDist = dist;
|
|
651
|
+
bestTarget = { type: "row", rowIndex: i + 1, y };
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return bestTarget;
|
|
655
|
+
}
|
|
656
|
+
// ----------------------------------------------------------
|
|
657
|
+
// Find content drop position (inside columns)
|
|
658
|
+
// ----------------------------------------------------------
|
|
659
|
+
findContentDropPosition(clientX, clientY) {
|
|
660
|
+
const columns = this.queryShadowAll(this.root, "me-column-renderer");
|
|
661
|
+
let bestTarget = null;
|
|
662
|
+
let bestDist = Infinity;
|
|
663
|
+
for (const colEl of columns) {
|
|
664
|
+
const columnId = colEl.dataset.columnId;
|
|
665
|
+
if (!columnId) continue;
|
|
666
|
+
const colShadow = colEl.shadowRoot;
|
|
667
|
+
if (!colShadow) continue;
|
|
668
|
+
const colRect = colEl.getBoundingClientRect();
|
|
669
|
+
if (clientX < colRect.left || clientX > colRect.right) continue;
|
|
670
|
+
const contentEls = Array.from(colShadow.querySelectorAll("me-content-renderer"));
|
|
671
|
+
if (contentEls.length === 0) {
|
|
672
|
+
const dist2 = Math.abs(clientY - (colRect.top + colRect.height / 2));
|
|
673
|
+
if (dist2 < bestDist) {
|
|
674
|
+
bestDist = dist2;
|
|
675
|
+
bestTarget = { type: "content", columnId, contentIndex: 0, y: colRect.top + colRect.height / 2 };
|
|
676
|
+
}
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
const firstRect = contentEls[0].getBoundingClientRect();
|
|
680
|
+
let dist = Math.abs(clientY - firstRect.top);
|
|
681
|
+
if (dist < bestDist) {
|
|
682
|
+
bestDist = dist;
|
|
683
|
+
bestTarget = { type: "content", columnId, contentIndex: 0, y: firstRect.top };
|
|
684
|
+
}
|
|
685
|
+
for (let i = 0; i < contentEls.length; i++) {
|
|
686
|
+
const rect = contentEls[i].getBoundingClientRect();
|
|
687
|
+
const nextRect = contentEls[i + 1]?.getBoundingClientRect();
|
|
688
|
+
const y = nextRect ? (rect.bottom + nextRect.top) / 2 : rect.bottom;
|
|
689
|
+
dist = Math.abs(clientY - y);
|
|
690
|
+
if (dist < bestDist) {
|
|
691
|
+
bestDist = dist;
|
|
692
|
+
bestTarget = { type: "content", columnId, contentIndex: i + 1, y };
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return bestTarget;
|
|
697
|
+
}
|
|
698
|
+
// ----------------------------------------------------------
|
|
699
|
+
// Indicator positioning
|
|
700
|
+
// ----------------------------------------------------------
|
|
701
|
+
showContentIndicator(drop) {
|
|
702
|
+
if (!this.indicator || !drop.columnId) return;
|
|
703
|
+
const columns = this.queryShadowAll(this.root, "me-column-renderer");
|
|
704
|
+
const colEl = columns.find((c) => c.dataset.columnId === drop.columnId);
|
|
705
|
+
if (!colEl?.shadowRoot) return;
|
|
706
|
+
if (this.indicator.parentNode !== colEl.shadowRoot) {
|
|
707
|
+
this.indicator.remove();
|
|
708
|
+
colEl.shadowRoot.appendChild(this.indicator);
|
|
709
|
+
}
|
|
710
|
+
const contentEls = Array.from(colEl.shadowRoot.querySelectorAll("me-content-renderer"));
|
|
711
|
+
const colRect = colEl.getBoundingClientRect();
|
|
712
|
+
let indicatorY;
|
|
713
|
+
if (contentEls.length === 0 || drop.contentIndex === 0) {
|
|
714
|
+
indicatorY = 0;
|
|
715
|
+
} else if (drop.contentIndex >= contentEls.length) {
|
|
716
|
+
const lastRect = contentEls[contentEls.length - 1].getBoundingClientRect();
|
|
717
|
+
indicatorY = lastRect.bottom - colRect.top;
|
|
718
|
+
} else {
|
|
719
|
+
const elRect = contentEls[drop.contentIndex].getBoundingClientRect();
|
|
720
|
+
indicatorY = elRect.top - colRect.top;
|
|
721
|
+
}
|
|
722
|
+
Object.assign(this.indicator.style, {
|
|
723
|
+
display: "block",
|
|
724
|
+
top: `${indicatorY}px`,
|
|
725
|
+
left: "4px",
|
|
726
|
+
right: "4px",
|
|
727
|
+
width: "auto"
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
showRowIndicator(drop) {
|
|
731
|
+
if (!this.rowIndicator) return;
|
|
732
|
+
const canvas = this.root.querySelector("me-editor-canvas");
|
|
733
|
+
if (!canvas?.shadowRoot) return;
|
|
734
|
+
const canvasBody = canvas.shadowRoot.querySelector(".canvas-body");
|
|
735
|
+
if (!canvasBody) return;
|
|
736
|
+
if (this.rowIndicator.parentNode !== canvasBody) {
|
|
737
|
+
this.rowIndicator.remove();
|
|
738
|
+
canvasBody.appendChild(this.rowIndicator);
|
|
739
|
+
}
|
|
740
|
+
const rows = Array.from(canvas.shadowRoot.querySelectorAll("me-row-renderer"));
|
|
741
|
+
const bodyRect = canvasBody.getBoundingClientRect();
|
|
742
|
+
let indicatorY;
|
|
743
|
+
if (rows.length === 0 || drop.rowIndex === 0) {
|
|
744
|
+
indicatorY = 0;
|
|
745
|
+
} else if (drop.rowIndex >= rows.length) {
|
|
746
|
+
const lastRect = rows[rows.length - 1].getBoundingClientRect();
|
|
747
|
+
indicatorY = lastRect.bottom - bodyRect.top;
|
|
748
|
+
} else {
|
|
749
|
+
const elRect = rows[drop.rowIndex].getBoundingClientRect();
|
|
750
|
+
indicatorY = elRect.top - bodyRect.top;
|
|
751
|
+
}
|
|
752
|
+
Object.assign(this.rowIndicator.style, {
|
|
753
|
+
display: "block",
|
|
754
|
+
top: `${indicatorY}px`,
|
|
755
|
+
left: "0",
|
|
756
|
+
right: "0",
|
|
757
|
+
width: "auto"
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
hideIndicator() {
|
|
761
|
+
if (this.indicator) this.indicator.style.display = "none";
|
|
762
|
+
}
|
|
763
|
+
hideRowIndicator() {
|
|
764
|
+
if (this.rowIndicator) this.rowIndicator.style.display = "none";
|
|
765
|
+
}
|
|
766
|
+
reset() {
|
|
767
|
+
this.currentDrop = null;
|
|
768
|
+
this.hideIndicator();
|
|
769
|
+
this.hideRowIndicator();
|
|
770
|
+
}
|
|
771
|
+
// ----------------------------------------------------------
|
|
772
|
+
// Shadow DOM traversal
|
|
773
|
+
// ----------------------------------------------------------
|
|
774
|
+
queryShadowAll(root, selector) {
|
|
775
|
+
const results = [];
|
|
776
|
+
this.walkShadowDom(root, (el) => {
|
|
777
|
+
if (el.matches?.(selector)) results.push(el);
|
|
778
|
+
});
|
|
779
|
+
return results;
|
|
780
|
+
}
|
|
781
|
+
walkShadowDom(root, callback) {
|
|
782
|
+
const children = root instanceof ShadowRoot ? root.children : root.children;
|
|
783
|
+
for (const child of Array.from(children)) {
|
|
784
|
+
const el = child;
|
|
785
|
+
callback(el);
|
|
786
|
+
if (el.shadowRoot) this.walkShadowDom(el.shadowRoot, callback);
|
|
787
|
+
if (el.children?.length) this.walkShadowDom(el, callback);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
findContentElement(el) {
|
|
791
|
+
while (el) {
|
|
792
|
+
if (el.dataset?.contentId) return el;
|
|
793
|
+
if (el.tagName?.toLowerCase() === "me-content-renderer") return el;
|
|
794
|
+
if (el.parentElement) {
|
|
795
|
+
el = el.parentElement;
|
|
796
|
+
} else if (el.getRootNode().host) {
|
|
797
|
+
el = el.getRootNode().host;
|
|
798
|
+
} else {
|
|
799
|
+
break;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
const textTool = {
|
|
806
|
+
name: "text",
|
|
807
|
+
label: "Text",
|
|
808
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/></svg>`,
|
|
809
|
+
supportedDisplayModes: ["email", "web"],
|
|
810
|
+
position: 1,
|
|
811
|
+
options: {
|
|
812
|
+
text: {
|
|
813
|
+
title: "Text",
|
|
814
|
+
options: {
|
|
815
|
+
text: {
|
|
816
|
+
label: "Text",
|
|
817
|
+
defaultValue: '<p style="font-size: 14px;">This is a new text block. Change the text.</p>',
|
|
818
|
+
widget: "rich_text"
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
},
|
|
822
|
+
style: {
|
|
823
|
+
title: "Style",
|
|
824
|
+
options: {
|
|
825
|
+
color: { label: "Text Color", defaultValue: "#000000", widget: "color_picker" },
|
|
826
|
+
backgroundColor: { label: "Background Color", defaultValue: "", widget: "color_picker" },
|
|
827
|
+
lineHeight: { label: "Line Height", defaultValue: "140%", widget: "text" },
|
|
828
|
+
linkStyle: { label: "Link Style", defaultValue: void 0, widget: "link_style" }
|
|
829
|
+
}
|
|
830
|
+
},
|
|
831
|
+
spacing: {
|
|
832
|
+
title: "Spacing",
|
|
833
|
+
options: {
|
|
834
|
+
containerPadding: { label: "Padding", defaultValue: "10px", widget: "padding" }
|
|
835
|
+
}
|
|
836
|
+
},
|
|
837
|
+
general: {
|
|
838
|
+
title: "General",
|
|
839
|
+
options: {
|
|
840
|
+
anchor: { label: "Anchor", defaultValue: "", widget: "text" },
|
|
841
|
+
hideDesktop: { label: "Hide on Desktop", defaultValue: false, widget: "toggle" },
|
|
842
|
+
hideMobile: { label: "Hide on Mobile", defaultValue: false, widget: "toggle" }
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
defaultValues: {
|
|
847
|
+
text: '<p style="font-size: 14px;">This is a new text block. Change the text.</p>',
|
|
848
|
+
color: "#000000",
|
|
849
|
+
backgroundColor: "",
|
|
850
|
+
lineHeight: "140%",
|
|
851
|
+
containerPadding: "10px",
|
|
852
|
+
textAlign: "left"
|
|
853
|
+
},
|
|
854
|
+
renderer: {
|
|
855
|
+
renderEditor(values, context) {
|
|
856
|
+
const padding = values.containerPadding || "10px";
|
|
857
|
+
const bgColor = values.backgroundColor || "transparent";
|
|
858
|
+
const color = values.color || "inherit";
|
|
859
|
+
const lineHeight = values.lineHeight || "140%";
|
|
860
|
+
const textContent = values.text || "";
|
|
861
|
+
return html`
|
|
862
|
+
<div style="padding:${padding};background-color:${bgColor};color:${color};line-height:${lineHeight};word-break:break-word;">
|
|
863
|
+
${unsafeHTML(textContent)}
|
|
864
|
+
</div>
|
|
865
|
+
`;
|
|
866
|
+
},
|
|
867
|
+
renderHtml(values, context) {
|
|
868
|
+
const padding = values.containerPadding || "10px";
|
|
869
|
+
const bgColor = values.backgroundColor || "";
|
|
870
|
+
const color = values.color || "#000000";
|
|
871
|
+
const lineHeight = values.lineHeight || "140%";
|
|
872
|
+
const textContent = values.text || "";
|
|
873
|
+
const textAlign = values.textAlign || "left";
|
|
874
|
+
const bgStyle = bgColor ? `background-color:${bgColor};` : "";
|
|
875
|
+
return `<table role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
|
876
|
+
<tbody>
|
|
877
|
+
<tr>
|
|
878
|
+
<td style="overflow-wrap:break-word;word-break:break-word;padding:${padding};${bgStyle}font-family:arial,helvetica,sans-serif;" align="left">
|
|
879
|
+
<div style="font-size:14px;color:${color};line-height:${lineHeight};text-align:${textAlign};word-wrap:break-word;">
|
|
880
|
+
${textContent}
|
|
881
|
+
</div>
|
|
882
|
+
</td>
|
|
883
|
+
</tr>
|
|
884
|
+
</tbody>
|
|
885
|
+
</table>`;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
};
|
|
889
|
+
const headingTool = {
|
|
890
|
+
name: "heading",
|
|
891
|
+
label: "Heading",
|
|
892
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 12h12"/><path d="M6 4v16"/><path d="M18 4v16"/></svg>`,
|
|
893
|
+
supportedDisplayModes: ["email", "web"],
|
|
894
|
+
position: 2,
|
|
895
|
+
options: {
|
|
896
|
+
text: {
|
|
897
|
+
title: "Heading",
|
|
898
|
+
options: {
|
|
899
|
+
text: {
|
|
900
|
+
label: "Text",
|
|
901
|
+
defaultValue: "Heading",
|
|
902
|
+
widget: "text"
|
|
903
|
+
},
|
|
904
|
+
headingType: {
|
|
905
|
+
label: "Heading Type",
|
|
906
|
+
defaultValue: "h1",
|
|
907
|
+
widget: "dropdown",
|
|
908
|
+
widgetParams: {
|
|
909
|
+
options: [
|
|
910
|
+
{ label: "H1", value: "h1" },
|
|
911
|
+
{ label: "H2", value: "h2" },
|
|
912
|
+
{ label: "H3", value: "h3" },
|
|
913
|
+
{ label: "H4", value: "h4" }
|
|
914
|
+
]
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
},
|
|
919
|
+
style: {
|
|
920
|
+
title: "Style",
|
|
921
|
+
options: {
|
|
922
|
+
fontSize: { label: "Font Size", defaultValue: "22px", widget: "text" },
|
|
923
|
+
color: { label: "Text Color", defaultValue: "#000000", widget: "color_picker" },
|
|
924
|
+
textAlign: { label: "Text Align", defaultValue: "left", widget: "text" },
|
|
925
|
+
fontWeight: { label: "Font Weight", defaultValue: "700", widget: "text" },
|
|
926
|
+
lineHeight: { label: "Line Height", defaultValue: "140%", widget: "text" },
|
|
927
|
+
letterSpacing: { label: "Letter Spacing", defaultValue: "normal", widget: "text" }
|
|
928
|
+
}
|
|
929
|
+
},
|
|
930
|
+
spacing: {
|
|
931
|
+
title: "Spacing",
|
|
932
|
+
options: {
|
|
933
|
+
containerPadding: { label: "Padding", defaultValue: "10px", widget: "text" }
|
|
934
|
+
}
|
|
935
|
+
},
|
|
936
|
+
general: {
|
|
937
|
+
title: "General",
|
|
938
|
+
options: {
|
|
939
|
+
anchor: { label: "Anchor", defaultValue: "", widget: "text" },
|
|
940
|
+
hideDesktop: { label: "Hide on Desktop", defaultValue: false, widget: "toggle" },
|
|
941
|
+
hideMobile: { label: "Hide on Mobile", defaultValue: false, widget: "toggle" }
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
},
|
|
945
|
+
defaultValues: {
|
|
946
|
+
text: "Heading",
|
|
947
|
+
headingType: "h1",
|
|
948
|
+
fontSize: "22px",
|
|
949
|
+
color: "#000000",
|
|
950
|
+
textAlign: "left",
|
|
951
|
+
fontWeight: "700",
|
|
952
|
+
lineHeight: "140%",
|
|
953
|
+
letterSpacing: "normal",
|
|
954
|
+
containerPadding: "10px"
|
|
955
|
+
},
|
|
956
|
+
renderer: {
|
|
957
|
+
renderEditor(values, ctx) {
|
|
958
|
+
const padding = values.containerPadding || "10px";
|
|
959
|
+
const fontSize = values.fontSize || "22px";
|
|
960
|
+
const color = values.color || "#000000";
|
|
961
|
+
const textAlign = values.textAlign || "left";
|
|
962
|
+
const fontWeight = values.fontWeight || "700";
|
|
963
|
+
const lineHeight = values.lineHeight || "140%";
|
|
964
|
+
const text = values.text || "Heading";
|
|
965
|
+
return html`
|
|
966
|
+
<div style="padding:${padding};font-size:${fontSize};color:${color};text-align:${textAlign};font-weight:${fontWeight};line-height:${lineHeight};">
|
|
967
|
+
${text}
|
|
968
|
+
</div>
|
|
969
|
+
`;
|
|
970
|
+
},
|
|
971
|
+
renderHtml(values, ctx) {
|
|
972
|
+
const padding = values.containerPadding || "10px";
|
|
973
|
+
const fontSize = values.fontSize || "22px";
|
|
974
|
+
const color = values.color || "#000000";
|
|
975
|
+
const textAlign = values.textAlign || "left";
|
|
976
|
+
const fontWeight = values.fontWeight || "700";
|
|
977
|
+
const lineHeight = values.lineHeight || "140%";
|
|
978
|
+
const letterSpacing = values.letterSpacing || "normal";
|
|
979
|
+
const tag = values.headingType || "h1";
|
|
980
|
+
const text = values.text || "Heading";
|
|
981
|
+
return `<table role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
|
982
|
+
<tbody><tr><td style="overflow-wrap:break-word;word-break:break-word;padding:${padding};font-family:arial,helvetica,sans-serif;" align="left">
|
|
983
|
+
<${tag} style="margin:0;font-size:${fontSize};color:${color};text-align:${textAlign};font-weight:${fontWeight};line-height:${lineHeight};letter-spacing:${letterSpacing};">${text}</${tag}>
|
|
984
|
+
</td></tr></tbody>
|
|
985
|
+
</table>`;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
const paragraphTool = {
|
|
990
|
+
name: "paragraph",
|
|
991
|
+
label: "Paragraph",
|
|
992
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/><line x1="3" y1="14" x2="17" y2="14"/></svg>`,
|
|
993
|
+
supportedDisplayModes: ["email", "web"],
|
|
994
|
+
position: 3,
|
|
995
|
+
options: {
|
|
996
|
+
text: {
|
|
997
|
+
title: "Paragraph",
|
|
998
|
+
options: {
|
|
999
|
+
text: {
|
|
1000
|
+
label: "Text",
|
|
1001
|
+
defaultValue: '<p style="font-size:14px;line-height:1.6;">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>',
|
|
1002
|
+
widget: "rich_text"
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
},
|
|
1006
|
+
style: {
|
|
1007
|
+
title: "Style",
|
|
1008
|
+
options: {
|
|
1009
|
+
color: { label: "Text Color", defaultValue: "#374151", widget: "color_picker" },
|
|
1010
|
+
lineHeight: { label: "Line Height", defaultValue: "160%", widget: "text" },
|
|
1011
|
+
letterSpacing: { label: "Letter Spacing", defaultValue: "normal", widget: "text" },
|
|
1012
|
+
textAlign: { label: "Text Align", defaultValue: "left", widget: "text" }
|
|
1013
|
+
}
|
|
1014
|
+
},
|
|
1015
|
+
spacing: {
|
|
1016
|
+
title: "Spacing",
|
|
1017
|
+
options: {
|
|
1018
|
+
containerPadding: { label: "Padding", defaultValue: "10px", widget: "text" }
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
},
|
|
1022
|
+
defaultValues: {
|
|
1023
|
+
text: '<p style="font-size:14px;line-height:1.6;">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>',
|
|
1024
|
+
color: "#374151",
|
|
1025
|
+
lineHeight: "160%",
|
|
1026
|
+
letterSpacing: "normal",
|
|
1027
|
+
textAlign: "left",
|
|
1028
|
+
containerPadding: "10px"
|
|
1029
|
+
},
|
|
1030
|
+
renderer: {
|
|
1031
|
+
renderEditor(values) {
|
|
1032
|
+
const padding = values.containerPadding || "10px";
|
|
1033
|
+
const color = values.color || "#374151";
|
|
1034
|
+
const lineHeight = values.lineHeight || "160%";
|
|
1035
|
+
const text = values.text || "";
|
|
1036
|
+
return html`<div style="padding:${padding};color:${color};line-height:${lineHeight};word-break:break-word;">${unsafeHTML(text)}</div>`;
|
|
1037
|
+
},
|
|
1038
|
+
renderHtml(values) {
|
|
1039
|
+
const padding = values.containerPadding || "10px";
|
|
1040
|
+
const color = values.color || "#374151";
|
|
1041
|
+
const lineHeight = values.lineHeight || "160%";
|
|
1042
|
+
const letterSpacing = values.letterSpacing || "normal";
|
|
1043
|
+
const textAlign = values.textAlign || "left";
|
|
1044
|
+
const text = values.text || "";
|
|
1045
|
+
return `<table role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
|
1046
|
+
<tbody><tr><td style="overflow-wrap:break-word;word-break:break-word;padding:${padding};font-family:arial,helvetica,sans-serif;" align="left">
|
|
1047
|
+
<div style="font-size:14px;color:${color};line-height:${lineHeight};text-align:${textAlign};letter-spacing:${letterSpacing};word-wrap:break-word;">${text}</div>
|
|
1048
|
+
</td></tr></tbody>
|
|
1049
|
+
</table>`;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
const imageTool = {
|
|
1054
|
+
name: "image",
|
|
1055
|
+
label: "Image",
|
|
1056
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>`,
|
|
1057
|
+
supportedDisplayModes: ["email", "web"],
|
|
1058
|
+
position: 3,
|
|
1059
|
+
options: {
|
|
1060
|
+
image: {
|
|
1061
|
+
title: "Image",
|
|
1062
|
+
options: {
|
|
1063
|
+
src: { label: "Image URL", defaultValue: "", widget: "text" },
|
|
1064
|
+
alt: { label: "Alt Text", defaultValue: "", widget: "text" },
|
|
1065
|
+
href: { label: "Link URL", defaultValue: "", widget: "text" },
|
|
1066
|
+
target: { label: "Link Target", defaultValue: "_blank", widget: "text" }
|
|
1067
|
+
}
|
|
1068
|
+
},
|
|
1069
|
+
style: {
|
|
1070
|
+
title: "Style",
|
|
1071
|
+
options: {
|
|
1072
|
+
width: { label: "Width", defaultValue: "100%", widget: "text" },
|
|
1073
|
+
maxWidth: { label: "Max Width", defaultValue: "100%", widget: "text" },
|
|
1074
|
+
align: { label: "Align", defaultValue: "center", widget: "text" },
|
|
1075
|
+
borderRadius: { label: "Border Radius", defaultValue: "0px", widget: "text" }
|
|
1076
|
+
}
|
|
1077
|
+
},
|
|
1078
|
+
spacing: {
|
|
1079
|
+
title: "Spacing",
|
|
1080
|
+
options: {
|
|
1081
|
+
containerPadding: { label: "Padding", defaultValue: "10px", widget: "text" }
|
|
1082
|
+
}
|
|
1083
|
+
},
|
|
1084
|
+
general: {
|
|
1085
|
+
title: "General",
|
|
1086
|
+
options: {
|
|
1087
|
+
anchor: { label: "Anchor", defaultValue: "", widget: "text" },
|
|
1088
|
+
hideDesktop: { label: "Hide on Desktop", defaultValue: false, widget: "toggle" },
|
|
1089
|
+
hideMobile: { label: "Hide on Mobile", defaultValue: false, widget: "toggle" }
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
},
|
|
1093
|
+
defaultValues: {
|
|
1094
|
+
src: "https://placehold.co/600x200/e2e8f0/64748b?text=Drop+Image+Here",
|
|
1095
|
+
alt: "Image",
|
|
1096
|
+
href: "",
|
|
1097
|
+
target: "_blank",
|
|
1098
|
+
width: "100%",
|
|
1099
|
+
maxWidth: "100%",
|
|
1100
|
+
align: "center",
|
|
1101
|
+
borderRadius: "0px",
|
|
1102
|
+
containerPadding: "10px"
|
|
1103
|
+
},
|
|
1104
|
+
renderer: {
|
|
1105
|
+
renderEditor(values, ctx) {
|
|
1106
|
+
const padding = values.containerPadding || "10px";
|
|
1107
|
+
const src = values.src || "";
|
|
1108
|
+
const alt = values.alt || "";
|
|
1109
|
+
const width = values.width || "100%";
|
|
1110
|
+
const borderRadius = values.borderRadius || "0px";
|
|
1111
|
+
const align = values.align || "center";
|
|
1112
|
+
if (!src) {
|
|
1113
|
+
return html`
|
|
1114
|
+
<div style="padding:${padding};text-align:${align};">
|
|
1115
|
+
<div style="background:#f1f5f9;border:2px dashed #cbd5e1;border-radius:8px;padding:40px 20px;text-align:center;color:#94a3b8;font-family:sans-serif;font-size:13px;">
|
|
1116
|
+
No image set. Enter a URL in the property panel.
|
|
1117
|
+
</div>
|
|
1118
|
+
</div>
|
|
1119
|
+
`;
|
|
1120
|
+
}
|
|
1121
|
+
return html`
|
|
1122
|
+
<div style="padding:${padding};text-align:${align};">
|
|
1123
|
+
<img src=${src} alt=${alt} style="display:inline-block;max-width:100%;width:${width};border-radius:${borderRadius};border:0;" />
|
|
1124
|
+
</div>
|
|
1125
|
+
`;
|
|
1126
|
+
},
|
|
1127
|
+
renderHtml(values, ctx) {
|
|
1128
|
+
const padding = values.containerPadding || "10px";
|
|
1129
|
+
const src = values.src || "";
|
|
1130
|
+
const alt = values.alt || "";
|
|
1131
|
+
const href = values.href || "";
|
|
1132
|
+
const target = values.target || "_blank";
|
|
1133
|
+
const width = values.width || "100%";
|
|
1134
|
+
const borderRadius = values.borderRadius || "0px";
|
|
1135
|
+
const align = values.align || "center";
|
|
1136
|
+
const widthPx = width === "100%" ? ctx.columnWidth : parseInt(width);
|
|
1137
|
+
const borderStyle = borderRadius !== "0px" ? `border-radius:${borderRadius};` : "";
|
|
1138
|
+
const imgTag = `<img align="${align}" border="0" src="${src}" alt="${alt}" title="${alt}" style="outline:none;text-decoration:none;-ms-interpolation-mode:bicubic;clear:both;display:inline-block!important;border:none;height:auto;float:none;width:${width};max-width:${widthPx}px;${borderStyle}" width="${widthPx}" />`;
|
|
1139
|
+
const content = href ? `<a href="${href}" target="${target}" style="text-decoration:none;">${imgTag}</a>` : imgTag;
|
|
1140
|
+
return `<table role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
|
1141
|
+
<tbody><tr><td style="overflow-wrap:break-word;word-break:break-word;padding:${padding};font-family:arial,helvetica,sans-serif;" align="${align}">
|
|
1142
|
+
${content}
|
|
1143
|
+
</td></tr></tbody>
|
|
1144
|
+
</table>`;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
const buttonTool = {
|
|
1149
|
+
name: "button",
|
|
1150
|
+
label: "Button",
|
|
1151
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="7" width="20" height="10" rx="2"/><path d="M12 7v10"/><path d="m8 12 4-3 4 3"/></svg>`,
|
|
1152
|
+
supportedDisplayModes: ["email", "web"],
|
|
1153
|
+
position: 4,
|
|
1154
|
+
options: {
|
|
1155
|
+
button: {
|
|
1156
|
+
title: "Button",
|
|
1157
|
+
options: {
|
|
1158
|
+
text: { label: "Button Text", defaultValue: "Click Me", widget: "text" },
|
|
1159
|
+
href: { label: "Link URL", defaultValue: "#", widget: "text" },
|
|
1160
|
+
target: { label: "Target", defaultValue: "_blank", widget: "text" }
|
|
1161
|
+
}
|
|
1162
|
+
},
|
|
1163
|
+
style: {
|
|
1164
|
+
title: "Style",
|
|
1165
|
+
options: {
|
|
1166
|
+
backgroundColor: { label: "Button Color", defaultValue: "#3b82f6", widget: "color_picker" },
|
|
1167
|
+
textColor: { label: "Text Color", defaultValue: "#ffffff", widget: "color_picker" },
|
|
1168
|
+
fontSize: { label: "Font Size", defaultValue: "14px", widget: "text" },
|
|
1169
|
+
fontWeight: { label: "Font Weight", defaultValue: "700", widget: "text" },
|
|
1170
|
+
borderRadius: { label: "Border Radius", defaultValue: "4px", widget: "text" },
|
|
1171
|
+
buttonWidth: { label: "Width", defaultValue: "auto", widget: "text" },
|
|
1172
|
+
textAlign: { label: "Align", defaultValue: "center", widget: "text" },
|
|
1173
|
+
buttonPadding: { label: "Button Padding", defaultValue: "10px 20px", widget: "text" },
|
|
1174
|
+
borderColor: { label: "Border Color", defaultValue: "", widget: "color_picker" },
|
|
1175
|
+
borderWidth: { label: "Border Width", defaultValue: "0px", widget: "text" }
|
|
1176
|
+
}
|
|
1177
|
+
},
|
|
1178
|
+
spacing: {
|
|
1179
|
+
title: "Spacing",
|
|
1180
|
+
options: {
|
|
1181
|
+
containerPadding: { label: "Padding", defaultValue: "10px", widget: "text" }
|
|
1182
|
+
}
|
|
1183
|
+
},
|
|
1184
|
+
general: {
|
|
1185
|
+
title: "General",
|
|
1186
|
+
options: {
|
|
1187
|
+
anchor: { label: "Anchor", defaultValue: "", widget: "text" },
|
|
1188
|
+
hideDesktop: { label: "Hide on Desktop", defaultValue: false, widget: "toggle" },
|
|
1189
|
+
hideMobile: { label: "Hide on Mobile", defaultValue: false, widget: "toggle" }
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
},
|
|
1193
|
+
defaultValues: {
|
|
1194
|
+
text: "Click Me",
|
|
1195
|
+
href: "#",
|
|
1196
|
+
target: "_blank",
|
|
1197
|
+
backgroundColor: "#3b82f6",
|
|
1198
|
+
textColor: "#ffffff",
|
|
1199
|
+
fontSize: "14px",
|
|
1200
|
+
fontWeight: "700",
|
|
1201
|
+
borderRadius: "4px",
|
|
1202
|
+
buttonWidth: "auto",
|
|
1203
|
+
textAlign: "center",
|
|
1204
|
+
buttonPadding: "10px 20px",
|
|
1205
|
+
borderColor: "",
|
|
1206
|
+
borderWidth: "0px",
|
|
1207
|
+
containerPadding: "10px"
|
|
1208
|
+
},
|
|
1209
|
+
renderer: {
|
|
1210
|
+
renderEditor(values, ctx) {
|
|
1211
|
+
const padding = values.containerPadding || "10px";
|
|
1212
|
+
const bgColor = values.backgroundColor || "#3b82f6";
|
|
1213
|
+
const textColor = values.textColor || "#ffffff";
|
|
1214
|
+
const fontSize = values.fontSize || "14px";
|
|
1215
|
+
const fontWeight = values.fontWeight || "700";
|
|
1216
|
+
const borderRadius = values.borderRadius || "4px";
|
|
1217
|
+
const buttonPadding = values.buttonPadding || "10px 20px";
|
|
1218
|
+
const text = values.text || "Click Me";
|
|
1219
|
+
const textAlign = values.textAlign || "center";
|
|
1220
|
+
const buttonWidth = values.buttonWidth || "auto";
|
|
1221
|
+
const borderColor = values.borderColor || bgColor;
|
|
1222
|
+
const borderWidth = values.borderWidth || "0px";
|
|
1223
|
+
const borderStyle = borderWidth !== "0px" ? `border:${borderWidth} solid ${borderColor};` : "border:none;";
|
|
1224
|
+
const widthStyle = buttonWidth === "auto" ? "display:inline-block;" : `display:block;width:${buttonWidth};`;
|
|
1225
|
+
return html`
|
|
1226
|
+
<div style="padding:${padding};text-align:${textAlign};">
|
|
1227
|
+
<a style="${widthStyle}background-color:${bgColor};color:${textColor};font-size:${fontSize};font-weight:${fontWeight};border-radius:${borderRadius};padding:${buttonPadding};text-decoration:none;text-align:center;${borderStyle}cursor:pointer;font-family:arial,helvetica,sans-serif;box-sizing:border-box;">
|
|
1228
|
+
${text}
|
|
1229
|
+
</a>
|
|
1230
|
+
</div>
|
|
1231
|
+
`;
|
|
1232
|
+
},
|
|
1233
|
+
renderHtml(values, ctx) {
|
|
1234
|
+
const padding = values.containerPadding || "10px";
|
|
1235
|
+
const bgColor = values.backgroundColor || "#3b82f6";
|
|
1236
|
+
const textColor = values.textColor || "#ffffff";
|
|
1237
|
+
const fontSize = values.fontSize || "14px";
|
|
1238
|
+
const fontWeight = values.fontWeight || "700";
|
|
1239
|
+
const borderRadius = values.borderRadius || "4px";
|
|
1240
|
+
const buttonPadding = values.buttonPadding || "10px 20px";
|
|
1241
|
+
const text = values.text || "Click Me";
|
|
1242
|
+
const textAlign = values.textAlign || "center";
|
|
1243
|
+
const href = values.href || "#";
|
|
1244
|
+
const target = values.target || "_blank";
|
|
1245
|
+
const borderColor = values.borderColor || bgColor;
|
|
1246
|
+
const borderWidth = values.borderWidth || "0px";
|
|
1247
|
+
const borderStyle = borderWidth !== "0px" ? `border:${borderWidth} solid ${borderColor};` : "border:none;";
|
|
1248
|
+
const vml = parseInt(borderRadius) > 0 ? `
|
|
1249
|
+
<!--[if mso]>
|
|
1250
|
+
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="${href}" style="height:auto;v-text-anchor:middle;width:auto;" arcsize="${Math.round(parseInt(borderRadius) / 20 * 100)}%" stroke="f" fillcolor="${bgColor}">
|
|
1251
|
+
<w:anchorlock/>
|
|
1252
|
+
<center style="color:${textColor};font-family:arial,helvetica,sans-serif;font-size:${fontSize};font-weight:${fontWeight};">${text}</center>
|
|
1253
|
+
</v:roundrect>
|
|
1254
|
+
<![endif]-->
|
|
1255
|
+
<!--[if !mso]><!-->` : "<!--[if !mso]><!-->";
|
|
1256
|
+
const vmlEnd = parseInt(borderRadius) > 0 ? "<!--<![endif]-->" : "<!--<![endif]-->";
|
|
1257
|
+
return `<table role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
|
1258
|
+
<tbody><tr><td style="overflow-wrap:break-word;word-break:break-word;padding:${padding};font-family:arial,helvetica,sans-serif;" align="${textAlign}">
|
|
1259
|
+
<div align="${textAlign}">
|
|
1260
|
+
${vml}
|
|
1261
|
+
<a href="${href}" target="${target}" style="box-sizing:border-box;display:inline-block;text-decoration:none;-webkit-text-size-adjust:none;text-align:center;color:${textColor};background-color:${bgColor};border-radius:${borderRadius};-webkit-border-radius:${borderRadius};-moz-border-radius:${borderRadius};font-size:${fontSize};font-weight:${fontWeight};padding:${buttonPadding};font-family:arial,helvetica,sans-serif;${borderStyle}mso-border-alt:none;word-break:keep-all;">
|
|
1262
|
+
<span style="line-height:120%;">${text}</span>
|
|
1263
|
+
</a>
|
|
1264
|
+
${vmlEnd}
|
|
1265
|
+
</div>
|
|
1266
|
+
</td></tr></tbody>
|
|
1267
|
+
</table>`;
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1271
|
+
const dividerTool = {
|
|
1272
|
+
name: "divider",
|
|
1273
|
+
label: "Divider",
|
|
1274
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="2" y1="12" x2="22" y2="12"/></svg>`,
|
|
1275
|
+
supportedDisplayModes: ["email", "web"],
|
|
1276
|
+
position: 5,
|
|
1277
|
+
options: {
|
|
1278
|
+
style: {
|
|
1279
|
+
title: "Style",
|
|
1280
|
+
options: {
|
|
1281
|
+
borderTopWidth: { label: "Width", defaultValue: "1px", widget: "text" },
|
|
1282
|
+
borderTopStyle: { label: "Style", defaultValue: "solid", widget: "text" },
|
|
1283
|
+
borderTopColor: { label: "Color", defaultValue: "#cccccc", widget: "color_picker" },
|
|
1284
|
+
width: { label: "Line Width", defaultValue: "100%", widget: "text" }
|
|
1285
|
+
}
|
|
1286
|
+
},
|
|
1287
|
+
spacing: {
|
|
1288
|
+
title: "Spacing",
|
|
1289
|
+
options: {
|
|
1290
|
+
containerPadding: { label: "Padding", defaultValue: "10px", widget: "text" }
|
|
1291
|
+
}
|
|
1292
|
+
},
|
|
1293
|
+
general: {
|
|
1294
|
+
title: "General",
|
|
1295
|
+
options: {
|
|
1296
|
+
hideDesktop: { label: "Hide on Desktop", defaultValue: false, widget: "toggle" },
|
|
1297
|
+
hideMobile: { label: "Hide on Mobile", defaultValue: false, widget: "toggle" }
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
},
|
|
1301
|
+
defaultValues: {
|
|
1302
|
+
borderTopWidth: "1px",
|
|
1303
|
+
borderTopStyle: "solid",
|
|
1304
|
+
borderTopColor: "#cccccc",
|
|
1305
|
+
width: "100%",
|
|
1306
|
+
containerPadding: "10px"
|
|
1307
|
+
},
|
|
1308
|
+
renderer: {
|
|
1309
|
+
renderEditor(values) {
|
|
1310
|
+
const padding = values.containerPadding || "10px";
|
|
1311
|
+
const width = values.width || "100%";
|
|
1312
|
+
const bw = values.borderTopWidth || "1px";
|
|
1313
|
+
const bs = values.borderTopStyle || "solid";
|
|
1314
|
+
const bc = values.borderTopColor || "#cccccc";
|
|
1315
|
+
return html`
|
|
1316
|
+
<div style="padding:${padding};">
|
|
1317
|
+
<div style="border-top:${bw} ${bs} ${bc};width:${width};margin:0 auto;"></div>
|
|
1318
|
+
</div>
|
|
1319
|
+
`;
|
|
1320
|
+
},
|
|
1321
|
+
renderHtml(values) {
|
|
1322
|
+
const padding = values.containerPadding || "10px";
|
|
1323
|
+
const width = values.width || "100%";
|
|
1324
|
+
const bw = values.borderTopWidth || "1px";
|
|
1325
|
+
const bs = values.borderTopStyle || "solid";
|
|
1326
|
+
const bc = values.borderTopColor || "#cccccc";
|
|
1327
|
+
return `<table role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
|
1328
|
+
<tbody><tr><td style="overflow-wrap:break-word;word-break:break-word;padding:${padding};font-family:arial,helvetica,sans-serif;" align="center">
|
|
1329
|
+
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="${width}" style="border-collapse:collapse;table-layout:fixed;border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;vertical-align:top;border-top:${bw} ${bs} ${bc};-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;">
|
|
1330
|
+
<tbody><tr><td style="font-size:0;line-height:0;"> </td></tr></tbody>
|
|
1331
|
+
</table>
|
|
1332
|
+
</td></tr></tbody>
|
|
1333
|
+
</table>`;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
};
|
|
1337
|
+
const htmlTool = {
|
|
1338
|
+
name: "html",
|
|
1339
|
+
label: "HTML",
|
|
1340
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>`,
|
|
1341
|
+
supportedDisplayModes: ["email", "web"],
|
|
1342
|
+
position: 6,
|
|
1343
|
+
options: {
|
|
1344
|
+
html: {
|
|
1345
|
+
title: "HTML",
|
|
1346
|
+
options: {
|
|
1347
|
+
html: {
|
|
1348
|
+
label: "Custom HTML",
|
|
1349
|
+
defaultValue: '<div style="padding:20px;text-align:center;color:#999;">Custom HTML Block</div>',
|
|
1350
|
+
widget: "rich_text"
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
},
|
|
1354
|
+
spacing: {
|
|
1355
|
+
title: "Spacing",
|
|
1356
|
+
options: {
|
|
1357
|
+
containerPadding: { label: "Padding", defaultValue: "10px", widget: "text" }
|
|
1358
|
+
}
|
|
1359
|
+
},
|
|
1360
|
+
general: {
|
|
1361
|
+
title: "General",
|
|
1362
|
+
options: {
|
|
1363
|
+
hideDesktop: { label: "Hide on Desktop", defaultValue: false, widget: "toggle" },
|
|
1364
|
+
hideMobile: { label: "Hide on Mobile", defaultValue: false, widget: "toggle" }
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
},
|
|
1368
|
+
defaultValues: {
|
|
1369
|
+
html: '<div style="padding:20px;text-align:center;color:#999;">Custom HTML Block</div>',
|
|
1370
|
+
containerPadding: "10px"
|
|
1371
|
+
},
|
|
1372
|
+
renderer: {
|
|
1373
|
+
renderEditor(values) {
|
|
1374
|
+
const padding = values.containerPadding || "10px";
|
|
1375
|
+
const content = values.html || "";
|
|
1376
|
+
return html`<div style="padding:${padding};">${unsafeHTML(content)}</div>`;
|
|
1377
|
+
},
|
|
1378
|
+
renderHtml(values) {
|
|
1379
|
+
const padding = values.containerPadding || "10px";
|
|
1380
|
+
const content = values.html || "";
|
|
1381
|
+
return `<table role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
|
1382
|
+
<tbody><tr><td style="overflow-wrap:break-word;word-break:break-word;padding:${padding};font-family:arial,helvetica,sans-serif;" align="left">
|
|
1383
|
+
${content}
|
|
1384
|
+
</td></tr></tbody>
|
|
1385
|
+
</table>`;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
};
|
|
1389
|
+
const defaultSocials = [
|
|
1390
|
+
{ name: "Facebook", url: "https://facebook.com/", icon: "f", color: "#1877F2" },
|
|
1391
|
+
{ name: "Twitter", url: "https://twitter.com/", icon: "𝕏", color: "#000000" },
|
|
1392
|
+
{ name: "Instagram", url: "https://instagram.com/", icon: "📷", color: "#E4405F" },
|
|
1393
|
+
{ name: "LinkedIn", url: "https://linkedin.com/", icon: "in", color: "#0A66C2" }
|
|
1394
|
+
];
|
|
1395
|
+
const socialTool = {
|
|
1396
|
+
name: "social",
|
|
1397
|
+
label: "Social",
|
|
1398
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>`,
|
|
1399
|
+
supportedDisplayModes: ["email", "web"],
|
|
1400
|
+
position: 8,
|
|
1401
|
+
options: {
|
|
1402
|
+
icons: {
|
|
1403
|
+
title: "Social Icons",
|
|
1404
|
+
options: {
|
|
1405
|
+
icons: { label: "Icons (JSON)", defaultValue: JSON.stringify(defaultSocials), widget: "rich_text" },
|
|
1406
|
+
iconSize: { label: "Icon Size", defaultValue: "32px", widget: "text" },
|
|
1407
|
+
iconSpacing: { label: "Spacing", defaultValue: "8px", widget: "text" }
|
|
1408
|
+
}
|
|
1409
|
+
},
|
|
1410
|
+
style: {
|
|
1411
|
+
title: "Style",
|
|
1412
|
+
options: {
|
|
1413
|
+
textAlign: { label: "Align", defaultValue: "center", widget: "text" }
|
|
1414
|
+
}
|
|
1415
|
+
},
|
|
1416
|
+
spacing: {
|
|
1417
|
+
title: "Spacing",
|
|
1418
|
+
options: {
|
|
1419
|
+
containerPadding: { label: "Padding", defaultValue: "10px", widget: "text" }
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
},
|
|
1423
|
+
defaultValues: {
|
|
1424
|
+
icons: JSON.stringify(defaultSocials),
|
|
1425
|
+
iconSize: "32px",
|
|
1426
|
+
iconSpacing: "8px",
|
|
1427
|
+
textAlign: "center",
|
|
1428
|
+
containerPadding: "10px"
|
|
1429
|
+
},
|
|
1430
|
+
renderer: {
|
|
1431
|
+
renderEditor(values) {
|
|
1432
|
+
const padding = values.containerPadding || "10px";
|
|
1433
|
+
const textAlign = values.textAlign || "center";
|
|
1434
|
+
const iconSize = values.iconSize || "32px";
|
|
1435
|
+
const spacing = values.iconSpacing || "8px";
|
|
1436
|
+
let icons = defaultSocials;
|
|
1437
|
+
try {
|
|
1438
|
+
icons = JSON.parse(values.icons);
|
|
1439
|
+
} catch {
|
|
1440
|
+
}
|
|
1441
|
+
return html`
|
|
1442
|
+
<div style="padding:${padding};text-align:${textAlign};">
|
|
1443
|
+
${icons.map(
|
|
1444
|
+
(s) => html`
|
|
1445
|
+
<a href=${s.url} target="_blank" style="display:inline-block;width:${iconSize};height:${iconSize};line-height:${iconSize};text-align:center;background:${s.color};color:white;border-radius:50%;text-decoration:none;font-size:14px;font-weight:bold;margin:0 ${spacing};font-family:arial,sans-serif;vertical-align:middle;">${s.icon}</a>
|
|
1446
|
+
`
|
|
1447
|
+
)}
|
|
1448
|
+
</div>
|
|
1449
|
+
`;
|
|
1450
|
+
},
|
|
1451
|
+
renderHtml(values) {
|
|
1452
|
+
const padding = values.containerPadding || "10px";
|
|
1453
|
+
const textAlign = values.textAlign || "center";
|
|
1454
|
+
const iconSize = parseInt(values.iconSize || "32");
|
|
1455
|
+
const spacing = values.iconSpacing || "8px";
|
|
1456
|
+
let icons = defaultSocials;
|
|
1457
|
+
try {
|
|
1458
|
+
icons = JSON.parse(values.icons);
|
|
1459
|
+
} catch {
|
|
1460
|
+
}
|
|
1461
|
+
const iconsHtml = icons.map(
|
|
1462
|
+
(s) => `<td align="center" valign="middle" style="padding:0 ${spacing};">
|
|
1463
|
+
<a href="${s.url}" target="_blank" style="text-decoration:none;">
|
|
1464
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr>
|
|
1465
|
+
<td width="${iconSize}" height="${iconSize}" align="center" valign="middle" style="width:${iconSize}px;height:${iconSize}px;background:${s.color};border-radius:50%;color:#ffffff;font-size:14px;font-weight:bold;font-family:arial,sans-serif;">
|
|
1466
|
+
${s.icon}
|
|
1467
|
+
</td>
|
|
1468
|
+
</tr></table>
|
|
1469
|
+
</a>
|
|
1470
|
+
</td>`
|
|
1471
|
+
).join("\n");
|
|
1472
|
+
return `<table role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
|
1473
|
+
<tbody><tr><td style="overflow-wrap:break-word;word-break:break-word;padding:${padding};font-family:arial,helvetica,sans-serif;" align="${textAlign}">
|
|
1474
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0" align="${textAlign}"><tr>${iconsHtml}</tr></table>
|
|
1475
|
+
</td></tr></tbody>
|
|
1476
|
+
</table>`;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
};
|
|
1480
|
+
const defaultItems = [
|
|
1481
|
+
{ text: "Home", href: "#" },
|
|
1482
|
+
{ text: "About", href: "#" },
|
|
1483
|
+
{ text: "Contact", href: "#" }
|
|
1484
|
+
];
|
|
1485
|
+
const menuTool = {
|
|
1486
|
+
name: "menu",
|
|
1487
|
+
label: "Menu",
|
|
1488
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="20" y2="18"/></svg>`,
|
|
1489
|
+
supportedDisplayModes: ["email", "web"],
|
|
1490
|
+
position: 9,
|
|
1491
|
+
options: {
|
|
1492
|
+
menu: {
|
|
1493
|
+
title: "Menu",
|
|
1494
|
+
options: {
|
|
1495
|
+
items: { label: "Items (JSON)", defaultValue: JSON.stringify(defaultItems), widget: "rich_text" }
|
|
1496
|
+
}
|
|
1497
|
+
},
|
|
1498
|
+
style: {
|
|
1499
|
+
title: "Style",
|
|
1500
|
+
options: {
|
|
1501
|
+
textAlign: { label: "Align", defaultValue: "center", widget: "text" },
|
|
1502
|
+
fontSize: { label: "Font Size", defaultValue: "14px", widget: "text" },
|
|
1503
|
+
color: { label: "Text Color", defaultValue: "#333333", widget: "color_picker" },
|
|
1504
|
+
separator: { label: "Separator", defaultValue: "|", widget: "text" },
|
|
1505
|
+
separatorColor: { label: "Separator Color", defaultValue: "#cccccc", widget: "color_picker" }
|
|
1506
|
+
}
|
|
1507
|
+
},
|
|
1508
|
+
spacing: {
|
|
1509
|
+
title: "Spacing",
|
|
1510
|
+
options: {
|
|
1511
|
+
containerPadding: { label: "Padding", defaultValue: "10px", widget: "text" }
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
},
|
|
1515
|
+
defaultValues: {
|
|
1516
|
+
items: JSON.stringify(defaultItems),
|
|
1517
|
+
textAlign: "center",
|
|
1518
|
+
fontSize: "14px",
|
|
1519
|
+
color: "#333333",
|
|
1520
|
+
separator: "|",
|
|
1521
|
+
separatorColor: "#cccccc",
|
|
1522
|
+
containerPadding: "10px"
|
|
1523
|
+
},
|
|
1524
|
+
renderer: {
|
|
1525
|
+
renderEditor(values) {
|
|
1526
|
+
const padding = values.containerPadding || "10px";
|
|
1527
|
+
const textAlign = values.textAlign || "center";
|
|
1528
|
+
const fontSize = values.fontSize || "14px";
|
|
1529
|
+
const color = values.color || "#333333";
|
|
1530
|
+
const sep = values.separator || "|";
|
|
1531
|
+
const sepColor = values.separatorColor || "#cccccc";
|
|
1532
|
+
let items = defaultItems;
|
|
1533
|
+
try {
|
|
1534
|
+
items = JSON.parse(values.items);
|
|
1535
|
+
} catch {
|
|
1536
|
+
}
|
|
1537
|
+
return html`
|
|
1538
|
+
<div style="padding:${padding};text-align:${textAlign};font-size:${fontSize};font-family:arial,sans-serif;">
|
|
1539
|
+
${items.map(
|
|
1540
|
+
(item, i) => html`${i > 0 ? html`<span style="color:${sepColor};padding:0 8px;">${sep}</span>` : ""}
|
|
1541
|
+
<a href=${item.href} style="color:${color};text-decoration:none;">${item.text}</a>`
|
|
1542
|
+
)}
|
|
1543
|
+
</div>
|
|
1544
|
+
`;
|
|
1545
|
+
},
|
|
1546
|
+
renderHtml(values) {
|
|
1547
|
+
const padding = values.containerPadding || "10px";
|
|
1548
|
+
const textAlign = values.textAlign || "center";
|
|
1549
|
+
const fontSize = values.fontSize || "14px";
|
|
1550
|
+
const color = values.color || "#333333";
|
|
1551
|
+
const sep = values.separator || "|";
|
|
1552
|
+
const sepColor = values.separatorColor || "#cccccc";
|
|
1553
|
+
let items = defaultItems;
|
|
1554
|
+
try {
|
|
1555
|
+
items = JSON.parse(values.items);
|
|
1556
|
+
} catch {
|
|
1557
|
+
}
|
|
1558
|
+
const linksHtml = items.map((item, i) => {
|
|
1559
|
+
const prefix = i > 0 ? `<span style="color:${sepColor};padding:0 8px;">${sep}</span>` : "";
|
|
1560
|
+
return `${prefix}<a href="${item.href}" target="_blank" style="color:${color};text-decoration:none;font-family:arial,helvetica,sans-serif;font-size:${fontSize};">${item.text}</a>`;
|
|
1561
|
+
}).join("");
|
|
1562
|
+
return `<table role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
|
1563
|
+
<tbody><tr><td style="overflow-wrap:break-word;word-break:break-word;padding:${padding};font-family:arial,helvetica,sans-serif;" align="${textAlign}">
|
|
1564
|
+
<div style="text-align:${textAlign};">${linksHtml}</div>
|
|
1565
|
+
</td></tr></tbody>
|
|
1566
|
+
</table>`;
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
};
|
|
1570
|
+
function getYouTubeThumbnail(url) {
|
|
1571
|
+
const match = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})/);
|
|
1572
|
+
return match ? `https://img.youtube.com/vi/${match[1]}/maxresdefault.jpg` : null;
|
|
1573
|
+
}
|
|
1574
|
+
const videoTool = {
|
|
1575
|
+
name: "video",
|
|
1576
|
+
label: "Video",
|
|
1577
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>`,
|
|
1578
|
+
supportedDisplayModes: ["email", "web"],
|
|
1579
|
+
position: 10,
|
|
1580
|
+
options: {
|
|
1581
|
+
video: {
|
|
1582
|
+
title: "Video",
|
|
1583
|
+
options: {
|
|
1584
|
+
url: { label: "Video URL", defaultValue: "https://www.youtube.com/watch?v=dQw4w9WgXcQ", widget: "text" },
|
|
1585
|
+
thumbnailUrl: { label: "Thumbnail URL (auto)", defaultValue: "", widget: "text" },
|
|
1586
|
+
alt: { label: "Alt Text", defaultValue: "Video", widget: "text" }
|
|
1587
|
+
}
|
|
1588
|
+
},
|
|
1589
|
+
style: {
|
|
1590
|
+
title: "Style",
|
|
1591
|
+
options: {
|
|
1592
|
+
textAlign: { label: "Align", defaultValue: "center", widget: "text" }
|
|
1593
|
+
}
|
|
1594
|
+
},
|
|
1595
|
+
spacing: {
|
|
1596
|
+
title: "Spacing",
|
|
1597
|
+
options: {
|
|
1598
|
+
containerPadding: { label: "Padding", defaultValue: "10px", widget: "text" }
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
},
|
|
1602
|
+
defaultValues: {
|
|
1603
|
+
url: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
|
1604
|
+
thumbnailUrl: "",
|
|
1605
|
+
alt: "Video",
|
|
1606
|
+
textAlign: "center",
|
|
1607
|
+
containerPadding: "10px"
|
|
1608
|
+
},
|
|
1609
|
+
renderer: {
|
|
1610
|
+
renderEditor(values) {
|
|
1611
|
+
const padding = values.containerPadding || "10px";
|
|
1612
|
+
const url = values.url || "";
|
|
1613
|
+
const thumbnail = values.thumbnailUrl || getYouTubeThumbnail(url) || "";
|
|
1614
|
+
const textAlign = values.textAlign || "center";
|
|
1615
|
+
if (!thumbnail) {
|
|
1616
|
+
return html`
|
|
1617
|
+
<div style="padding:${padding};text-align:${textAlign};">
|
|
1618
|
+
<div style="background:#0f172a;border-radius:8px;padding:40px;text-align:center;color:white;font-family:sans-serif;position:relative;">
|
|
1619
|
+
<div style="font-size:48px;opacity:0.8;">▶</div>
|
|
1620
|
+
<div style="font-size:12px;margin-top:8px;opacity:0.6;">${url || "Enter video URL"}</div>
|
|
1621
|
+
</div>
|
|
1622
|
+
</div>
|
|
1623
|
+
`;
|
|
1624
|
+
}
|
|
1625
|
+
return html`
|
|
1626
|
+
<div style="padding:${padding};text-align:${textAlign};">
|
|
1627
|
+
<div style="position:relative;display:inline-block;max-width:100%;cursor:pointer;">
|
|
1628
|
+
<img src=${thumbnail} alt="Video thumbnail" style="display:block;max-width:100%;border-radius:4px;" />
|
|
1629
|
+
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;">
|
|
1630
|
+
<div style="width:60px;height:60px;background:rgba(0,0,0,0.7);border-radius:50%;display:flex;align-items:center;justify-content:center;color:white;font-size:24px;">▶</div>
|
|
1631
|
+
</div>
|
|
1632
|
+
</div>
|
|
1633
|
+
</div>
|
|
1634
|
+
`;
|
|
1635
|
+
},
|
|
1636
|
+
renderHtml(values, ctx) {
|
|
1637
|
+
const padding = values.containerPadding || "10px";
|
|
1638
|
+
const url = values.url || "#";
|
|
1639
|
+
const thumbnail = values.thumbnailUrl || getYouTubeThumbnail(url) || "";
|
|
1640
|
+
const alt = values.alt || "Video";
|
|
1641
|
+
const textAlign = values.textAlign || "center";
|
|
1642
|
+
const widthPx = ctx.columnWidth;
|
|
1643
|
+
const imgTag = thumbnail ? `<img src="${thumbnail}" alt="${alt}" width="${widthPx}" style="display:block;max-width:100%;width:${widthPx}px;border:0;" />` : `<div style="background:#0f172a;padding:40px;text-align:center;color:white;font-family:arial,sans-serif;font-size:16px;">▶ Watch Video</div>`;
|
|
1644
|
+
return `<table role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
|
1645
|
+
<tbody><tr><td style="overflow-wrap:break-word;word-break:break-word;padding:${padding};font-family:arial,helvetica,sans-serif;" align="${textAlign}">
|
|
1646
|
+
<a href="${url}" target="_blank" style="text-decoration:none;">
|
|
1647
|
+
${imgTag}
|
|
1648
|
+
</a>
|
|
1649
|
+
</td></tr></tbody>
|
|
1650
|
+
</table>`;
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
};
|
|
1654
|
+
const timerTool = {
|
|
1655
|
+
name: "timer",
|
|
1656
|
+
label: "Timer",
|
|
1657
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>`,
|
|
1658
|
+
supportedDisplayModes: ["email", "web"],
|
|
1659
|
+
position: 11,
|
|
1660
|
+
options: {
|
|
1661
|
+
timer: {
|
|
1662
|
+
title: "Timer",
|
|
1663
|
+
options: {
|
|
1664
|
+
endDate: { label: "End Date (ISO)", defaultValue: new Date(Date.now() + 7 * 864e5).toISOString().split("T")[0], widget: "text" },
|
|
1665
|
+
expiredMessage: { label: "Expired Message", defaultValue: "This offer has expired", widget: "text" }
|
|
1666
|
+
}
|
|
1667
|
+
},
|
|
1668
|
+
style: {
|
|
1669
|
+
title: "Style",
|
|
1670
|
+
options: {
|
|
1671
|
+
textAlign: { label: "Align", defaultValue: "center", widget: "text" },
|
|
1672
|
+
backgroundColor: { label: "Background", defaultValue: "#1f2937", widget: "color_picker" },
|
|
1673
|
+
textColor: { label: "Text Color", defaultValue: "#ffffff", widget: "color_picker" },
|
|
1674
|
+
fontSize: { label: "Font Size", defaultValue: "32px", widget: "text" }
|
|
1675
|
+
}
|
|
1676
|
+
},
|
|
1677
|
+
spacing: {
|
|
1678
|
+
title: "Spacing",
|
|
1679
|
+
options: {
|
|
1680
|
+
containerPadding: { label: "Padding", defaultValue: "10px", widget: "text" }
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
},
|
|
1684
|
+
defaultValues: {
|
|
1685
|
+
endDate: new Date(Date.now() + 7 * 864e5).toISOString().split("T")[0],
|
|
1686
|
+
expiredMessage: "This offer has expired",
|
|
1687
|
+
textAlign: "center",
|
|
1688
|
+
backgroundColor: "#1f2937",
|
|
1689
|
+
textColor: "#ffffff",
|
|
1690
|
+
fontSize: "32px",
|
|
1691
|
+
containerPadding: "10px"
|
|
1692
|
+
},
|
|
1693
|
+
renderer: {
|
|
1694
|
+
renderEditor(values) {
|
|
1695
|
+
const padding = values.containerPadding || "10px";
|
|
1696
|
+
const bg = values.backgroundColor || "#1f2937";
|
|
1697
|
+
const color = values.textColor || "#ffffff";
|
|
1698
|
+
const fontSize = values.fontSize || "32px";
|
|
1699
|
+
const textAlign = values.textAlign || "center";
|
|
1700
|
+
return html`
|
|
1701
|
+
<div style="padding:${padding};">
|
|
1702
|
+
<div style="background:${bg};color:${color};font-size:${fontSize};text-align:${textAlign};padding:20px;border-radius:4px;font-family:monospace;font-weight:bold;letter-spacing:4px;">
|
|
1703
|
+
00 : 00 : 00 : 00
|
|
1704
|
+
<div style="font-size:11px;letter-spacing:8px;opacity:0.6;margin-top:4px;">DAYS HRS MIN SEC</div>
|
|
1705
|
+
</div>
|
|
1706
|
+
</div>
|
|
1707
|
+
`;
|
|
1708
|
+
},
|
|
1709
|
+
renderHtml(values) {
|
|
1710
|
+
const padding = values.containerPadding || "10px";
|
|
1711
|
+
const bg = values.backgroundColor || "#1f2937";
|
|
1712
|
+
const color = values.textColor || "#ffffff";
|
|
1713
|
+
const fontSize = values.fontSize || "32px";
|
|
1714
|
+
const textAlign = values.textAlign || "center";
|
|
1715
|
+
values.endDate || "";
|
|
1716
|
+
return `<table role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
|
1717
|
+
<tbody><tr><td style="overflow-wrap:break-word;word-break:break-word;padding:${padding};font-family:arial,helvetica,sans-serif;" align="${textAlign}">
|
|
1718
|
+
<div style="background-color:${bg};color:${color};font-size:${fontSize};text-align:${textAlign};padding:20px;border-radius:4px;font-family:'Courier New',monospace;font-weight:bold;letter-spacing:4px;">
|
|
1719
|
+
<div>00 : 00 : 00 : 00</div>
|
|
1720
|
+
<div style="font-size:11px;letter-spacing:8px;opacity:0.6;margin-top:4px;">DAYS HRS MIN SEC</div>
|
|
1721
|
+
</div>
|
|
1722
|
+
</td></tr></tbody>
|
|
1723
|
+
</table>`;
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
};
|
|
1727
|
+
const defaultTableData = [
|
|
1728
|
+
["Header 1", "Header 2", "Header 3"],
|
|
1729
|
+
["Cell 1", "Cell 2", "Cell 3"],
|
|
1730
|
+
["Cell 4", "Cell 5", "Cell 6"]
|
|
1731
|
+
];
|
|
1732
|
+
const tableTool = {
|
|
1733
|
+
name: "table",
|
|
1734
|
+
label: "Table",
|
|
1735
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M3 15h18"/><path d="M9 3v18"/><path d="M15 3v18"/></svg>`,
|
|
1736
|
+
supportedDisplayModes: ["email", "web"],
|
|
1737
|
+
position: 12,
|
|
1738
|
+
options: {
|
|
1739
|
+
table: {
|
|
1740
|
+
title: "Table",
|
|
1741
|
+
options: {
|
|
1742
|
+
tableData: { label: "Table Data (JSON)", defaultValue: JSON.stringify(defaultTableData), widget: "rich_text" }
|
|
1743
|
+
}
|
|
1744
|
+
},
|
|
1745
|
+
style: {
|
|
1746
|
+
title: "Style",
|
|
1747
|
+
options: {
|
|
1748
|
+
headerBg: { label: "Header Background", defaultValue: "#f3f4f6", widget: "color_picker" },
|
|
1749
|
+
headerColor: { label: "Header Text Color", defaultValue: "#111827", widget: "color_picker" },
|
|
1750
|
+
borderColor: { label: "Border Color", defaultValue: "#e5e7eb", widget: "color_picker" },
|
|
1751
|
+
cellPadding: { label: "Cell Padding", defaultValue: "8px 12px", widget: "text" },
|
|
1752
|
+
fontSize: { label: "Font Size", defaultValue: "14px", widget: "text" }
|
|
1753
|
+
}
|
|
1754
|
+
},
|
|
1755
|
+
spacing: {
|
|
1756
|
+
title: "Spacing",
|
|
1757
|
+
options: {
|
|
1758
|
+
containerPadding: { label: "Padding", defaultValue: "10px", widget: "text" }
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
},
|
|
1762
|
+
defaultValues: {
|
|
1763
|
+
tableData: JSON.stringify(defaultTableData),
|
|
1764
|
+
headerBg: "#f3f4f6",
|
|
1765
|
+
headerColor: "#111827",
|
|
1766
|
+
borderColor: "#e5e7eb",
|
|
1767
|
+
cellPadding: "8px 12px",
|
|
1768
|
+
fontSize: "14px",
|
|
1769
|
+
containerPadding: "10px"
|
|
1770
|
+
},
|
|
1771
|
+
renderer: {
|
|
1772
|
+
renderEditor(values) {
|
|
1773
|
+
const padding = values.containerPadding || "10px";
|
|
1774
|
+
const headerBg = values.headerBg || "#f3f4f6";
|
|
1775
|
+
const headerColor = values.headerColor || "#111827";
|
|
1776
|
+
const borderColor = values.borderColor || "#e5e7eb";
|
|
1777
|
+
const cellPadding = values.cellPadding || "8px 12px";
|
|
1778
|
+
const fontSize = values.fontSize || "14px";
|
|
1779
|
+
let data = defaultTableData;
|
|
1780
|
+
try {
|
|
1781
|
+
data = JSON.parse(values.tableData);
|
|
1782
|
+
} catch {
|
|
1783
|
+
}
|
|
1784
|
+
const headerRow = data[0] || [];
|
|
1785
|
+
const bodyRows = data.slice(1);
|
|
1786
|
+
return html`
|
|
1787
|
+
<div style="padding:${padding};overflow-x:auto;">
|
|
1788
|
+
<table style="width:100%;border-collapse:collapse;font-size:${fontSize};font-family:arial,sans-serif;">
|
|
1789
|
+
<thead>
|
|
1790
|
+
<tr>
|
|
1791
|
+
${headerRow.map((cell) => html`<th style="padding:${cellPadding};background:${headerBg};color:${headerColor};border:1px solid ${borderColor};text-align:left;font-weight:600;">${cell}</th>`)}
|
|
1792
|
+
</tr>
|
|
1793
|
+
</thead>
|
|
1794
|
+
<tbody>
|
|
1795
|
+
${bodyRows.map((row) => html`
|
|
1796
|
+
<tr>
|
|
1797
|
+
${row.map((cell) => html`<td style="padding:${cellPadding};border:1px solid ${borderColor};">${cell}</td>`)}
|
|
1798
|
+
</tr>
|
|
1799
|
+
`)}
|
|
1800
|
+
</tbody>
|
|
1801
|
+
</table>
|
|
1802
|
+
</div>
|
|
1803
|
+
`;
|
|
1804
|
+
},
|
|
1805
|
+
renderHtml(values) {
|
|
1806
|
+
const padding = values.containerPadding || "10px";
|
|
1807
|
+
const headerBg = values.headerBg || "#f3f4f6";
|
|
1808
|
+
const headerColor = values.headerColor || "#111827";
|
|
1809
|
+
const borderColor = values.borderColor || "#e5e7eb";
|
|
1810
|
+
const cellPadding = values.cellPadding || "8px 12px";
|
|
1811
|
+
const fontSize = values.fontSize || "14px";
|
|
1812
|
+
let data = defaultTableData;
|
|
1813
|
+
try {
|
|
1814
|
+
data = JSON.parse(values.tableData);
|
|
1815
|
+
} catch {
|
|
1816
|
+
}
|
|
1817
|
+
const headerRow = data[0] || [];
|
|
1818
|
+
const bodyRows = data.slice(1);
|
|
1819
|
+
const headerHtml = headerRow.map(
|
|
1820
|
+
(c) => `<th style="padding:${cellPadding};background-color:${headerBg};color:${headerColor};border:1px solid ${borderColor};text-align:left;font-weight:600;font-family:arial,helvetica,sans-serif;font-size:${fontSize};">${c}</th>`
|
|
1821
|
+
).join("");
|
|
1822
|
+
const bodyHtml = bodyRows.map(
|
|
1823
|
+
(row) => `<tr>${row.map((c) => `<td style="padding:${cellPadding};border:1px solid ${borderColor};font-family:arial,helvetica,sans-serif;font-size:${fontSize};">${c}</td>`).join("")}</tr>`
|
|
1824
|
+
).join("");
|
|
1825
|
+
return `<table role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
|
1826
|
+
<tbody><tr><td style="overflow-wrap:break-word;word-break:break-word;padding:${padding};font-family:arial,helvetica,sans-serif;" align="left">
|
|
1827
|
+
<table cellpadding="0" cellspacing="0" width="100%" border="0" style="border-collapse:collapse;">
|
|
1828
|
+
<thead><tr>${headerHtml}</tr></thead>
|
|
1829
|
+
<tbody>${bodyHtml}</tbody>
|
|
1830
|
+
</table>
|
|
1831
|
+
</td></tr></tbody>
|
|
1832
|
+
</table>`;
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
};
|
|
1836
|
+
const formTool = {
|
|
1837
|
+
name: "form",
|
|
1838
|
+
label: "Form",
|
|
1839
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M7 7h10"/><path d="M7 12h10"/><path d="M7 17h6"/></svg>`,
|
|
1840
|
+
supportedDisplayModes: ["web"],
|
|
1841
|
+
position: 13,
|
|
1842
|
+
options: {
|
|
1843
|
+
form: {
|
|
1844
|
+
title: "Form",
|
|
1845
|
+
options: {
|
|
1846
|
+
actionUrl: { label: "Action URL", defaultValue: "#", widget: "text" },
|
|
1847
|
+
method: { label: "Method", defaultValue: "POST", widget: "text" },
|
|
1848
|
+
submitText: { label: "Submit Text", defaultValue: "Submit", widget: "text" },
|
|
1849
|
+
fields: { label: "Fields (JSON)", defaultValue: JSON.stringify([
|
|
1850
|
+
{ label: "Name", name: "name", type: "text", placeholder: "Your name" },
|
|
1851
|
+
{ label: "Email", name: "email", type: "email", placeholder: "your@email.com" }
|
|
1852
|
+
]), widget: "rich_text" }
|
|
1853
|
+
}
|
|
1854
|
+
},
|
|
1855
|
+
style: {
|
|
1856
|
+
title: "Style",
|
|
1857
|
+
options: {
|
|
1858
|
+
buttonBg: { label: "Button Color", defaultValue: "#3b82f6", widget: "color_picker" },
|
|
1859
|
+
buttonColor: { label: "Button Text", defaultValue: "#ffffff", widget: "color_picker" }
|
|
1860
|
+
}
|
|
1861
|
+
},
|
|
1862
|
+
spacing: {
|
|
1863
|
+
title: "Spacing",
|
|
1864
|
+
options: {
|
|
1865
|
+
containerPadding: { label: "Padding", defaultValue: "10px", widget: "text" }
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
},
|
|
1869
|
+
defaultValues: {
|
|
1870
|
+
actionUrl: "#",
|
|
1871
|
+
method: "POST",
|
|
1872
|
+
submitText: "Submit",
|
|
1873
|
+
fields: JSON.stringify([
|
|
1874
|
+
{ label: "Name", name: "name", type: "text", placeholder: "Your name" },
|
|
1875
|
+
{ label: "Email", name: "email", type: "email", placeholder: "your@email.com" }
|
|
1876
|
+
]),
|
|
1877
|
+
buttonBg: "#3b82f6",
|
|
1878
|
+
buttonColor: "#ffffff",
|
|
1879
|
+
containerPadding: "10px"
|
|
1880
|
+
},
|
|
1881
|
+
renderer: {
|
|
1882
|
+
renderEditor(values) {
|
|
1883
|
+
const padding = values.containerPadding || "10px";
|
|
1884
|
+
const submitText = values.submitText || "Submit";
|
|
1885
|
+
const buttonBg = values.buttonBg || "#3b82f6";
|
|
1886
|
+
const buttonColor = values.buttonColor || "#ffffff";
|
|
1887
|
+
let fields = [];
|
|
1888
|
+
try {
|
|
1889
|
+
fields = JSON.parse(values.fields);
|
|
1890
|
+
} catch {
|
|
1891
|
+
}
|
|
1892
|
+
return html`
|
|
1893
|
+
<div style="padding:${padding};font-family:arial,sans-serif;">
|
|
1894
|
+
${fields.map((f) => html`
|
|
1895
|
+
<div style="margin-bottom:12px;">
|
|
1896
|
+
<label style="display:block;font-size:13px;color:#374151;margin-bottom:4px;font-weight:500;">${f.label}</label>
|
|
1897
|
+
<input type=${f.type || "text"} placeholder=${f.placeholder || ""} style="width:100%;padding:8px 12px;border:1px solid #d1d5db;border-radius:4px;font-size:14px;box-sizing:border-box;" />
|
|
1898
|
+
</div>
|
|
1899
|
+
`)}
|
|
1900
|
+
<button style="background:${buttonBg};color:${buttonColor};border:none;padding:10px 24px;border-radius:4px;font-size:14px;font-weight:600;cursor:pointer;">${submitText}</button>
|
|
1901
|
+
</div>
|
|
1902
|
+
`;
|
|
1903
|
+
},
|
|
1904
|
+
renderHtml(values) {
|
|
1905
|
+
const padding = values.containerPadding || "10px";
|
|
1906
|
+
const actionUrl = values.actionUrl || "#";
|
|
1907
|
+
const method = values.method || "POST";
|
|
1908
|
+
const submitText = values.submitText || "Submit";
|
|
1909
|
+
const buttonBg = values.buttonBg || "#3b82f6";
|
|
1910
|
+
const buttonColor = values.buttonColor || "#ffffff";
|
|
1911
|
+
let fields = [];
|
|
1912
|
+
try {
|
|
1913
|
+
fields = JSON.parse(values.fields);
|
|
1914
|
+
} catch {
|
|
1915
|
+
}
|
|
1916
|
+
const fieldsHtml = fields.map(
|
|
1917
|
+
(f) => `<div style="margin-bottom:12px;">
|
|
1918
|
+
<label style="display:block;font-size:13px;color:#374151;margin-bottom:4px;font-weight:500;font-family:arial,helvetica,sans-serif;">${f.label}</label>
|
|
1919
|
+
<input type="${f.type || "text"}" name="${f.name}" placeholder="${f.placeholder || ""}" style="width:100%;padding:8px 12px;border:1px solid #d1d5db;border-radius:4px;font-size:14px;box-sizing:border-box;font-family:arial,helvetica,sans-serif;" />
|
|
1920
|
+
</div>`
|
|
1921
|
+
).join("");
|
|
1922
|
+
return `<table role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
|
1923
|
+
<tbody><tr><td style="overflow-wrap:break-word;word-break:break-word;padding:${padding};font-family:arial,helvetica,sans-serif;" align="left">
|
|
1924
|
+
<form action="${actionUrl}" method="${method}">
|
|
1925
|
+
${fieldsHtml}
|
|
1926
|
+
<button type="submit" style="background-color:${buttonBg};color:${buttonColor};border:none;padding:10px 24px;border-radius:4px;font-size:14px;font-weight:600;cursor:pointer;font-family:arial,helvetica,sans-serif;">${submitText}</button>
|
|
1927
|
+
</form>
|
|
1928
|
+
</td></tr></tbody>
|
|
1929
|
+
</table>`;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
};
|
|
1933
|
+
function wrapInDocumentShell(bodyHtml, cssBlock, bodyValues) {
|
|
1934
|
+
const bgColor = bodyValues.backgroundColor || "#e7e7e7";
|
|
1935
|
+
const contentWidth = bodyValues.contentWidth || "600px";
|
|
1936
|
+
const fontFamily = bodyValues.fontFamily?.value || "arial,helvetica,sans-serif";
|
|
1937
|
+
const textColor = bodyValues.textColor || "#000000";
|
|
1938
|
+
const preheaderText = bodyValues.preheaderText || "";
|
|
1939
|
+
const preheader = preheaderText ? `<div style="display:none;font-size:1px;color:${bgColor};line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;">${preheaderText}${"‌ ".repeat(80)}</div>` : "";
|
|
1940
|
+
return `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
1941
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
|
1942
|
+
<head>
|
|
1943
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
1944
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1945
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
1946
|
+
<meta name="x-apple-disable-message-reformatting">
|
|
1947
|
+
<meta name="format-detection" content="telephone=no,address=no,email=no,date=no,url=no">
|
|
1948
|
+
<meta name="color-scheme" content="light dark">
|
|
1949
|
+
<meta name="supported-color-schemes" content="light dark">
|
|
1950
|
+
<title></title>
|
|
1951
|
+
<!--[if mso]>
|
|
1952
|
+
<noscript><xml>
|
|
1953
|
+
<o:OfficeDocumentSettings>
|
|
1954
|
+
<o:AllowPNG/><o:PixelsPerInch>96</o:PixelsPerInch>
|
|
1955
|
+
</o:OfficeDocumentSettings>
|
|
1956
|
+
</xml></noscript>
|
|
1957
|
+
<style type="text/css">
|
|
1958
|
+
table, td, th { font-family: ${fontFamily} !important; }
|
|
1959
|
+
</style>
|
|
1960
|
+
<![endif]-->
|
|
1961
|
+
<!--[if !mso]><!-->
|
|
1962
|
+
<style type="text/css">
|
|
1963
|
+
${cssBlock}
|
|
1964
|
+
</style>
|
|
1965
|
+
<!--<![endif]-->
|
|
1966
|
+
<style type="text/css">
|
|
1967
|
+
body { margin: 0; padding: 0; }
|
|
1968
|
+
table, tr, td { vertical-align: top; border-collapse: collapse; }
|
|
1969
|
+
p { margin: 0; }
|
|
1970
|
+
.ie-container table, .mso-container table { table-layout: fixed; }
|
|
1971
|
+
* { line-height: inherit; }
|
|
1972
|
+
a[x-apple-data-detectors='true'] { color: inherit !important; text-decoration: none !important; }
|
|
1973
|
+
</style>
|
|
1974
|
+
</head>
|
|
1975
|
+
<body class="clean-body u_body" style="margin:0;padding:0;-webkit-text-size-adjust:100%;background-color:${bgColor};color:${textColor};">
|
|
1976
|
+
${preheader}
|
|
1977
|
+
<table id="u_body" style="border-collapse:collapse;table-layout:fixed;border-spacing:0;mso-table-lspace:0pt;mso-table-rspace:0pt;vertical-align:top;min-width:320px;margin:0 auto;background-color:${bgColor};width:100%;" cellpadding="0" cellspacing="0" border="0">
|
|
1978
|
+
<tbody>
|
|
1979
|
+
<tr style="vertical-align:top;">
|
|
1980
|
+
<td style="word-break:break-word;border-collapse:collapse !important;vertical-align:top;">
|
|
1981
|
+
<!--[if (mso)|(IE)]><table width="${parseInt(contentWidth)}" align="center" cellpadding="0" cellspacing="0" border="0"><tr><td><![endif]-->
|
|
1982
|
+
${bodyHtml}
|
|
1983
|
+
<!--[if (mso)|(IE)]></td></tr></table><![endif]-->
|
|
1984
|
+
</td>
|
|
1985
|
+
</tr>
|
|
1986
|
+
</tbody>
|
|
1987
|
+
</table>
|
|
1988
|
+
</body>
|
|
1989
|
+
</html>`;
|
|
1990
|
+
}
|
|
1991
|
+
function renderRow(row, bodyValues, toolRenderers) {
|
|
1992
|
+
const contentWidth = parseInt(bodyValues.contentWidth || "600");
|
|
1993
|
+
const bgColor = row.values.backgroundColor || "";
|
|
1994
|
+
const colsBgColor = row.values.columnsBackgroundColor || "";
|
|
1995
|
+
const padding = row.values.padding || "0px";
|
|
1996
|
+
const totalCells = row.cells.reduce((a, b) => a + b, 0);
|
|
1997
|
+
const bgStyle = bgColor ? `background-color:${bgColor};` : "";
|
|
1998
|
+
const bgImage = row.values.backgroundImage?.url ? `background-image:url('${row.values.backgroundImage.url}');background-repeat:${row.values.backgroundImage.repeat ? "repeat" : "no-repeat"};background-position:center top;background-size:${row.values.backgroundImage.cover ? "cover" : "auto"};` : "";
|
|
1999
|
+
const columnsHtml = row.columns.map((col, i) => {
|
|
2000
|
+
const colWidthPx = Math.round(row.cells[i] / totalCells * contentWidth);
|
|
2001
|
+
return renderColumn(col, colWidthPx, colsBgColor, bodyValues, toolRenderers);
|
|
2002
|
+
});
|
|
2003
|
+
const needsGhostTable = row.columns.length > 1;
|
|
2004
|
+
let innerHtml;
|
|
2005
|
+
if (needsGhostTable) {
|
|
2006
|
+
const ghostCols = row.columns.map((col, i) => {
|
|
2007
|
+
const colWidthPx = Math.round(row.cells[i] / totalCells * contentWidth);
|
|
2008
|
+
const colHtml = renderColumn(col, colWidthPx, colsBgColor, bodyValues, toolRenderers);
|
|
2009
|
+
return `<!--[if (mso)|(IE)]><td align="center" width="${colWidthPx}" style="width:${colWidthPx}px;padding:0px;border:none;" valign="top"><![endif]-->
|
|
2010
|
+
${colHtml}
|
|
2011
|
+
<!--[if (mso)|(IE)]></td><![endif]-->`;
|
|
2012
|
+
});
|
|
2013
|
+
innerHtml = `<!--[if (mso)|(IE)]><table role="presentation" width="${contentWidth}" cellpadding="0" cellspacing="0" border="0"><tr>${ghostCols.join("\n")}</tr></table><![endif]-->
|
|
2014
|
+
|
|
2015
|
+
<!--[if !mso]><!-->
|
|
2016
|
+
<div style="max-width:${contentWidth}px;margin:0 auto;">
|
|
2017
|
+
${columnsHtml.join("\n")}
|
|
2018
|
+
</div>
|
|
2019
|
+
<!--<![endif]-->`;
|
|
2020
|
+
} else {
|
|
2021
|
+
innerHtml = columnsHtml.join("\n");
|
|
2022
|
+
}
|
|
2023
|
+
const hideDesktop = row.values.hideDesktop ? " u_hide_desktop" : "";
|
|
2024
|
+
const hideMobile = row.values.hideMobile ? " u_hide_mobile" : "";
|
|
2025
|
+
return `<div class="u_row${hideDesktop}${hideMobile}" style="padding:${padding};${bgStyle}${bgImage}">
|
|
2026
|
+
<div style="margin:0 auto;min-width:320px;max-width:${contentWidth}px;overflow-wrap:break-word;word-wrap:break-word;word-break:break-word;background-color:transparent;">
|
|
2027
|
+
<div style="border-collapse:collapse;display:table;width:100%;height:100%;background-color:transparent;">
|
|
2028
|
+
${innerHtml}
|
|
2029
|
+
</div>
|
|
2030
|
+
</div>
|
|
2031
|
+
</div>`;
|
|
2032
|
+
}
|
|
2033
|
+
function renderColumn(col, widthPx, colsBgColor, bodyValues, toolRenderers) {
|
|
2034
|
+
const bgColor = col.values.backgroundColor || colsBgColor || "";
|
|
2035
|
+
const padding = col.values.padding || "0px";
|
|
2036
|
+
const borderRadius = col.values.borderRadius || "0px";
|
|
2037
|
+
const bgStyle = bgColor ? `background-color:${bgColor};` : "";
|
|
2038
|
+
const contentsHtml = col.contents.map((content) => {
|
|
2039
|
+
const renderer = toolRenderers.get(content.type);
|
|
2040
|
+
if (!renderer) return `<!-- unknown tool: ${content.type} -->`;
|
|
2041
|
+
const ctx = {
|
|
2042
|
+
columnWidth: widthPx,
|
|
2043
|
+
displayMode: "email",
|
|
2044
|
+
contentWidth: parseInt(bodyValues.contentWidth || "600"),
|
|
2045
|
+
bodyValues
|
|
2046
|
+
};
|
|
2047
|
+
return renderer(content.values, ctx);
|
|
2048
|
+
}).join("\n");
|
|
2049
|
+
return `<div class="u_column" style="max-width:${widthPx}px;min-width:${Math.min(widthPx, 320)}px;display:table-cell;vertical-align:top;">
|
|
2050
|
+
<div style="height:100%;width:100% !important;border-radius:${borderRadius};-webkit-border-radius:${borderRadius};${bgStyle}">
|
|
2051
|
+
<div style="box-sizing:border-box;height:100%;padding:${padding};border:none;border-radius:${borderRadius};-webkit-border-radius:${borderRadius};">
|
|
2052
|
+
${contentsHtml || '<!--[if (!mso)&(!IE)]><!--><div style="height:0;min-height:1px;font-size:0;"> </div><!--<![endif]-->'}
|
|
2053
|
+
</div>
|
|
2054
|
+
</div>
|
|
2055
|
+
</div>`;
|
|
2056
|
+
}
|
|
2057
|
+
function getResponsiveCss(contentWidth) {
|
|
2058
|
+
return `
|
|
2059
|
+
@media only screen and (min-width: ${contentWidth + 20}px) {
|
|
2060
|
+
.u_row .u_column { display: table-cell; }
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
@media only screen and (max-width: ${contentWidth + 20}px) {
|
|
2064
|
+
.u_row .u_column {
|
|
2065
|
+
display: block !important;
|
|
2066
|
+
width: 100% !important;
|
|
2067
|
+
min-width: 320px !important;
|
|
2068
|
+
max-width: 100% !important;
|
|
2069
|
+
}
|
|
2070
|
+
.u_row {
|
|
2071
|
+
width: 100% !important;
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
@media only screen and (max-width: 620px) {
|
|
2076
|
+
.u_row-container {
|
|
2077
|
+
max-width: 100% !important;
|
|
2078
|
+
padding-left: 0 !important;
|
|
2079
|
+
padding-right: 0 !important;
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
@media (prefers-color-scheme: dark) {
|
|
2084
|
+
/* Dark mode overrides — add per-client rules as needed */
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
/* Outlook dark mode */
|
|
2088
|
+
[data-ogsb] body,
|
|
2089
|
+
[data-ogsb] table,
|
|
2090
|
+
[data-ogsb] td {
|
|
2091
|
+
/* Preserve original colors */
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
.u_hide_desktop { display: block !important; }
|
|
2095
|
+
.u_hide_mobile { display: block !important; }
|
|
2096
|
+
|
|
2097
|
+
@media only screen and (max-width: ${contentWidth + 20}px) {
|
|
2098
|
+
.u_hide_desktop { display: block !important; }
|
|
2099
|
+
.u_hide_mobile { display: none !important; }
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
@media only screen and (min-width: ${contentWidth + 21}px) {
|
|
2103
|
+
.u_hide_desktop { display: none !important; }
|
|
2104
|
+
.u_hide_mobile { display: block !important; }
|
|
2105
|
+
}`;
|
|
2106
|
+
}
|
|
2107
|
+
function renderDesignToHtml(design, toolRenderers, options) {
|
|
2108
|
+
const bodyValues = design.body.values;
|
|
2109
|
+
const contentWidth = parseInt(bodyValues.contentWidth || "600");
|
|
2110
|
+
const rowsHtml = design.body.rows.map((row) => renderRow(row, bodyValues, toolRenderers)).join("\n");
|
|
2111
|
+
const cssBlock = getResponsiveCss(contentWidth);
|
|
2112
|
+
let fullHtml = wrapInDocumentShell(rowsHtml, cssBlock, bodyValues);
|
|
2113
|
+
if (options?.mergeTags) {
|
|
2114
|
+
for (const [tag, value] of Object.entries(options.mergeTags)) {
|
|
2115
|
+
fullHtml = fullHtml.replaceAll(`{{${tag}}}`, value);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
const bodyMatch = fullHtml.match(/<body[^>]*>([\s\S]*)<\/body>/i);
|
|
2119
|
+
const cssMatch = fullHtml.match(/<style[^>]*>([\s\S]*?)<\/style>/gi);
|
|
2120
|
+
const fontsUsed = [];
|
|
2121
|
+
if (bodyValues.fontFamily?.url) {
|
|
2122
|
+
fontsUsed.push(bodyValues.fontFamily.url);
|
|
2123
|
+
}
|
|
2124
|
+
return {
|
|
2125
|
+
design: structuredClone(design),
|
|
2126
|
+
html: fullHtml,
|
|
2127
|
+
chunks: {
|
|
2128
|
+
body: bodyMatch?.[1] ?? rowsHtml,
|
|
2129
|
+
css: cssMatch?.map((s) => s.replace(/<\/?style[^>]*>/gi, "")).join("\n") ?? cssBlock,
|
|
2130
|
+
fonts: fontsUsed,
|
|
2131
|
+
js: ""
|
|
2132
|
+
}
|
|
2133
|
+
};
|
|
2134
|
+
}
|
|
2135
|
+
var __defProp = Object.defineProperty;
|
|
2136
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
2137
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
2138
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
2139
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
2140
|
+
if (decorator = decorators[i])
|
|
2141
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
2142
|
+
if (kind && result) __defProp(target, key, result);
|
|
2143
|
+
return result;
|
|
2144
|
+
};
|
|
2145
|
+
let MailEditorElement = class extends LitElement {
|
|
2146
|
+
constructor() {
|
|
2147
|
+
super(...arguments);
|
|
2148
|
+
this.options = {};
|
|
2149
|
+
this.store = new EditorStore();
|
|
2150
|
+
this.toolRegistry = new ToolRegistry();
|
|
2151
|
+
this.dragManager = null;
|
|
2152
|
+
this.callbacks = /* @__PURE__ */ new Map();
|
|
2153
|
+
this.unsubscribe = null;
|
|
2154
|
+
}
|
|
2155
|
+
connectedCallback() {
|
|
2156
|
+
super.connectedCallback();
|
|
2157
|
+
this.registerBuiltInTools();
|
|
2158
|
+
this.applyOptions();
|
|
2159
|
+
}
|
|
2160
|
+
firstUpdated() {
|
|
2161
|
+
this.dragManager = new DragManager(this.store, this.toolRegistry, this.shadowRoot);
|
|
2162
|
+
this.dragManager.attach();
|
|
2163
|
+
this.unsubscribe = this.store.subscribe(() => this.requestUpdate());
|
|
2164
|
+
this.store.events.on("design:loaded", (detail) => {
|
|
2165
|
+
this.dispatchEvent(new CustomEvent("design:loaded", { detail, bubbles: true, composed: true }));
|
|
2166
|
+
});
|
|
2167
|
+
this.store.events.on("design:updated", (detail) => {
|
|
2168
|
+
this.dispatchEvent(new CustomEvent("design:updated", { detail, bubbles: true, composed: true }));
|
|
2169
|
+
});
|
|
2170
|
+
this.dispatchEvent(new CustomEvent("editor:ready", { bubbles: true, composed: true }));
|
|
2171
|
+
}
|
|
2172
|
+
disconnectedCallback() {
|
|
2173
|
+
super.disconnectedCallback();
|
|
2174
|
+
this.dragManager?.detach();
|
|
2175
|
+
this.unsubscribe?.();
|
|
2176
|
+
this.store.events.removeAllListeners();
|
|
2177
|
+
}
|
|
2178
|
+
// ----------------------------------------------------------
|
|
2179
|
+
// Public API — mirrors Unlayer
|
|
2180
|
+
// ----------------------------------------------------------
|
|
2181
|
+
loadDesign(design) {
|
|
2182
|
+
this.store.loadDesign(design);
|
|
2183
|
+
}
|
|
2184
|
+
saveDesign(callback) {
|
|
2185
|
+
callback(structuredClone(this.store.getDesign()));
|
|
2186
|
+
}
|
|
2187
|
+
exportHtml(callback, options) {
|
|
2188
|
+
const design = this.store.getDesign();
|
|
2189
|
+
const toolRenderers = /* @__PURE__ */ new Map();
|
|
2190
|
+
for (const tool of this.toolRegistry.getAll()) {
|
|
2191
|
+
toolRenderers.set(tool.name, (values, ctx) => tool.renderer.renderHtml(values, ctx));
|
|
2192
|
+
}
|
|
2193
|
+
const result = renderDesignToHtml(design, toolRenderers, options);
|
|
2194
|
+
callback(result);
|
|
2195
|
+
}
|
|
2196
|
+
async exportHtmlAsync(options) {
|
|
2197
|
+
return new Promise((resolve) => this.exportHtml(resolve, options));
|
|
2198
|
+
}
|
|
2199
|
+
registerTool(definition) {
|
|
2200
|
+
this.toolRegistry.register(definition);
|
|
2201
|
+
this.requestUpdate();
|
|
2202
|
+
}
|
|
2203
|
+
registerPropertyEditor(_name, _editor) {
|
|
2204
|
+
}
|
|
2205
|
+
registerTab(_tab) {
|
|
2206
|
+
}
|
|
2207
|
+
registerCallback(type, callback) {
|
|
2208
|
+
this.callbacks.set(type, callback);
|
|
2209
|
+
}
|
|
2210
|
+
setMergeTags(_tags) {
|
|
2211
|
+
}
|
|
2212
|
+
undo() {
|
|
2213
|
+
this.store.undo();
|
|
2214
|
+
}
|
|
2215
|
+
redo() {
|
|
2216
|
+
this.store.redo();
|
|
2217
|
+
}
|
|
2218
|
+
setBodyValues(values) {
|
|
2219
|
+
this.store.updateBodyValues(values);
|
|
2220
|
+
}
|
|
2221
|
+
// ----------------------------------------------------------
|
|
2222
|
+
// Internal
|
|
2223
|
+
// ----------------------------------------------------------
|
|
2224
|
+
registerBuiltInTools() {
|
|
2225
|
+
this.toolRegistry.register(textTool);
|
|
2226
|
+
this.toolRegistry.register(headingTool);
|
|
2227
|
+
this.toolRegistry.register(paragraphTool);
|
|
2228
|
+
this.toolRegistry.register(imageTool);
|
|
2229
|
+
this.toolRegistry.register(buttonTool);
|
|
2230
|
+
this.toolRegistry.register(dividerTool);
|
|
2231
|
+
this.toolRegistry.register(htmlTool);
|
|
2232
|
+
this.toolRegistry.register(socialTool);
|
|
2233
|
+
this.toolRegistry.register(menuTool);
|
|
2234
|
+
this.toolRegistry.register(videoTool);
|
|
2235
|
+
this.toolRegistry.register(timerTool);
|
|
2236
|
+
this.toolRegistry.register(tableTool);
|
|
2237
|
+
this.toolRegistry.register(formTool);
|
|
2238
|
+
}
|
|
2239
|
+
applyOptions() {
|
|
2240
|
+
if (this.options.design) {
|
|
2241
|
+
this.store.loadDesign(this.options.design);
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
render() {
|
|
2245
|
+
return html`
|
|
2246
|
+
<me-editor-sidebar
|
|
2247
|
+
.store=${this.store}
|
|
2248
|
+
.toolRegistry=${this.toolRegistry}
|
|
2249
|
+
></me-editor-sidebar>
|
|
2250
|
+
<me-editor-canvas
|
|
2251
|
+
.store=${this.store}
|
|
2252
|
+
.toolRegistry=${this.toolRegistry}
|
|
2253
|
+
></me-editor-canvas>
|
|
2254
|
+
<me-property-panel
|
|
2255
|
+
.store=${this.store}
|
|
2256
|
+
.toolRegistry=${this.toolRegistry}
|
|
2257
|
+
></me-property-panel>
|
|
2258
|
+
`;
|
|
2259
|
+
}
|
|
2260
|
+
};
|
|
2261
|
+
MailEditorElement.styles = css`
|
|
2262
|
+
:host {
|
|
2263
|
+
display: flex;
|
|
2264
|
+
width: 100%;
|
|
2265
|
+
height: 100%;
|
|
2266
|
+
min-height: 500px;
|
|
2267
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
2268
|
+
color: #111827;
|
|
2269
|
+
box-sizing: border-box;
|
|
2270
|
+
overflow: hidden;
|
|
2271
|
+
position: relative;
|
|
2272
|
+
}
|
|
2273
|
+
* { box-sizing: border-box; }
|
|
2274
|
+
`;
|
|
2275
|
+
__decorateClass([
|
|
2276
|
+
property({ type: Object })
|
|
2277
|
+
], MailEditorElement.prototype, "options", 2);
|
|
2278
|
+
MailEditorElement = __decorateClass([
|
|
2279
|
+
customElement("mail-editor")
|
|
2280
|
+
], MailEditorElement);
|
|
2281
|
+
export {
|
|
2282
|
+
EditorStore as E,
|
|
2283
|
+
MailEditorElement as M,
|
|
2284
|
+
ToolRegistry as T
|
|
2285
|
+
};
|
|
2286
|
+
//# sourceMappingURL=mail-editor-DzI7SmSe.js.map
|