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