@emabuild/core 0.0.2 → 0.0.4
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 +410 -0
- package/dist/dnd/drag-manager.d.ts +13 -14
- package/dist/dnd/drag-manager.d.ts.map +1 -1
- package/dist/dnd/drop-indicator.d.ts +24 -0
- package/dist/dnd/drop-indicator.d.ts.map +1 -0
- package/dist/dnd/shadow-dom-utils.d.ts +29 -0
- package/dist/dnd/shadow-dom-utils.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{mail-editor-CoB2C5CT.js → mail-editor-D0FbEUZu.js} +677 -870
- package/dist/mail-editor-D0FbEUZu.js.map +1 -0
- package/dist/mail-editor.d.ts +3 -3
- package/dist/mail-editor.d.ts.map +1 -1
- package/dist/mail-editor.js +1 -1
- package/dist/properties/property-panel.d.ts +4 -9
- package/dist/properties/property-panel.d.ts.map +1 -1
- package/dist/properties/widgets/alignment-widget.d.ts +4 -0
- package/dist/properties/widgets/alignment-widget.d.ts.map +1 -0
- package/dist/properties/widgets/color-picker-widget.d.ts +4 -0
- package/dist/properties/widgets/color-picker-widget.d.ts.map +1 -0
- package/dist/properties/widgets/dropdown-widget.d.ts +4 -0
- package/dist/properties/widgets/dropdown-widget.d.ts.map +1 -0
- package/dist/properties/widgets/index.d.ts +8 -0
- package/dist/properties/widgets/index.d.ts.map +1 -0
- package/dist/properties/widgets/padding-widget.d.ts +4 -0
- package/dist/properties/widgets/padding-widget.d.ts.map +1 -0
- package/dist/properties/widgets/text-input-widget.d.ts +4 -0
- package/dist/properties/widgets/text-input-widget.d.ts.map +1 -0
- package/dist/properties/widgets/textarea-widget.d.ts +4 -0
- package/dist/properties/widgets/textarea-widget.d.ts.map +1 -0
- package/dist/properties/widgets/toggle-widget.d.ts +4 -0
- package/dist/properties/widgets/toggle-widget.d.ts.map +1 -0
- package/dist/sidebar/body-settings.d.ts +15 -0
- package/dist/sidebar/body-settings.d.ts.map +1 -0
- package/dist/sidebar/editor-sidebar.d.ts +0 -3
- package/dist/sidebar/editor-sidebar.d.ts.map +1 -1
- package/dist/state/design-factory.d.ts +9 -0
- package/dist/state/design-factory.d.ts.map +1 -0
- package/dist/state/design-lookup.d.ts +14 -0
- package/dist/state/design-lookup.d.ts.map +1 -0
- package/dist/state/editor-store.d.ts +27 -10
- package/dist/state/editor-store.d.ts.map +1 -1
- package/dist/state/history-manager.d.ts +20 -0
- package/dist/state/history-manager.d.ts.map +1 -0
- package/dist/tools/built-in/button-tool.d.ts.map +1 -1
- package/dist/tools/built-in/divider-tool.d.ts.map +1 -1
- package/dist/tools/built-in/form-tool.d.ts.map +1 -1
- package/dist/tools/built-in/heading-tool.d.ts.map +1 -1
- package/dist/tools/built-in/html-tool.d.ts.map +1 -1
- package/dist/tools/built-in/image-tool.d.ts.map +1 -1
- package/dist/tools/built-in/menu-tool.d.ts.map +1 -1
- package/dist/tools/built-in/paragraph-tool.d.ts.map +1 -1
- package/dist/tools/built-in/social-tool.d.ts.map +1 -1
- package/dist/tools/built-in/table-tool.d.ts.map +1 -1
- package/dist/tools/built-in/text-tool.d.ts.map +1 -1
- package/dist/tools/built-in/timer-tool.d.ts.map +1 -1
- package/dist/tools/built-in/video-tool.d.ts.map +1 -1
- package/dist/tools/helpers/email-html.d.ts +55 -0
- package/dist/tools/helpers/email-html.d.ts.map +1 -0
- package/dist/tools/helpers/index.d.ts +5 -0
- package/dist/tools/helpers/index.d.ts.map +1 -0
- package/dist/tools/helpers/types.d.ts +36 -0
- package/dist/tools/helpers/types.d.ts.map +1 -0
- package/dist/tools/helpers/value-extractor.d.ts +31 -0
- package/dist/tools/helpers/value-extractor.d.ts.map +1 -0
- package/dist/utils/event-emitter.d.ts +32 -4
- package/dist/utils/event-emitter.d.ts.map +1 -1
- package/dist/utils/id-generator.d.ts +17 -3
- package/dist/utils/id-generator.d.ts.map +1 -1
- package/package.json +16 -4
- package/dist/mail-editor-CoB2C5CT.js.map +0 -1
- package/dist/utils/deep-clone.d.ts +0 -2
- package/dist/utils/deep-clone.d.ts.map +0 -1
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { html, css, LitElement } from "lit";
|
|
2
2
|
import { property, customElement } from "lit/decorators.js";
|
|
3
3
|
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
|
4
|
-
function deepClone(obj) {
|
|
5
|
-
return structuredClone(obj);
|
|
6
|
-
}
|
|
7
4
|
function createCounterManager() {
|
|
8
5
|
const counters = {};
|
|
9
6
|
return {
|
|
@@ -11,7 +8,9 @@ function createCounterManager() {
|
|
|
11
8
|
return { ...counters };
|
|
12
9
|
},
|
|
13
10
|
setCounters(c) {
|
|
14
|
-
Object.keys(counters)
|
|
11
|
+
for (const key of Object.keys(counters)) {
|
|
12
|
+
delete counters[key];
|
|
13
|
+
}
|
|
15
14
|
Object.assign(counters, c);
|
|
16
15
|
},
|
|
17
16
|
next(prefix) {
|
|
@@ -25,24 +24,28 @@ class EventEmitter {
|
|
|
25
24
|
constructor() {
|
|
26
25
|
this.listeners = /* @__PURE__ */ new Map();
|
|
27
26
|
}
|
|
27
|
+
/** Register a listener for an event */
|
|
28
28
|
on(event, listener) {
|
|
29
29
|
if (!this.listeners.has(event)) {
|
|
30
30
|
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
31
31
|
}
|
|
32
32
|
this.listeners.get(event).add(listener);
|
|
33
33
|
}
|
|
34
|
+
/** Remove a specific listener */
|
|
34
35
|
off(event, listener) {
|
|
35
36
|
this.listeners.get(event)?.delete(listener);
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
+
/** Emit an event with a payload. Errors in listeners are caught and logged. */
|
|
39
|
+
emit(event, payload) {
|
|
38
40
|
this.listeners.get(event)?.forEach((fn) => {
|
|
39
41
|
try {
|
|
40
|
-
fn(
|
|
42
|
+
fn(payload);
|
|
41
43
|
} catch (e) {
|
|
42
|
-
console.error(`[
|
|
44
|
+
console.error(`[emabuild] Error in "${event}" listener:`, e);
|
|
43
45
|
}
|
|
44
46
|
});
|
|
45
47
|
}
|
|
48
|
+
/** Remove all listeners, optionally scoped to a single event */
|
|
46
49
|
removeAllListeners(event) {
|
|
47
50
|
if (event) {
|
|
48
51
|
this.listeners.delete(event);
|
|
@@ -51,6 +54,48 @@ class EventEmitter {
|
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
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
|
+
}
|
|
54
99
|
function createEmptyDesign() {
|
|
55
100
|
return {
|
|
56
101
|
counters: { u_row: 0, u_column: 0 },
|
|
@@ -96,13 +141,98 @@ function createEmptyDesign() {
|
|
|
96
141
|
schemaVersion: 16
|
|
97
142
|
};
|
|
98
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
|
+
}
|
|
99
231
|
class EditorStore {
|
|
100
232
|
constructor() {
|
|
101
|
-
this.
|
|
102
|
-
this.redoStack = [];
|
|
103
|
-
this.maxHistory = 50;
|
|
104
|
-
this.subscribers = /* @__PURE__ */ new Set();
|
|
233
|
+
this.history = new HistoryManager();
|
|
105
234
|
this.counterManager = createCounterManager();
|
|
235
|
+
this.subscribers = /* @__PURE__ */ new Set();
|
|
106
236
|
this.events = new EventEmitter();
|
|
107
237
|
this._selectedId = null;
|
|
108
238
|
this._hoveredId = null;
|
|
@@ -110,9 +240,8 @@ class EditorStore {
|
|
|
110
240
|
this._activeTab = "content";
|
|
111
241
|
this.design = createEmptyDesign();
|
|
112
242
|
}
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
// ----------------------------------------------------------
|
|
243
|
+
// ── Subscriptions ──────────────────────────────────────────
|
|
244
|
+
/** Subscribe to all state changes. Returns an unsubscribe function. */
|
|
116
245
|
subscribe(fn) {
|
|
117
246
|
this.subscribers.add(fn);
|
|
118
247
|
return () => this.subscribers.delete(fn);
|
|
@@ -120,18 +249,20 @@ class EditorStore {
|
|
|
120
249
|
notify() {
|
|
121
250
|
this.subscribers.forEach((fn) => fn());
|
|
122
251
|
}
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
// ----------------------------------------------------------
|
|
252
|
+
// ── Getters ────────────────────────────────────────────────
|
|
253
|
+
/** Get the full design document */
|
|
126
254
|
getDesign() {
|
|
127
255
|
return this.design;
|
|
128
256
|
}
|
|
257
|
+
/** Get the design body */
|
|
129
258
|
getBody() {
|
|
130
259
|
return this.design.body;
|
|
131
260
|
}
|
|
261
|
+
/** Get all rows */
|
|
132
262
|
getRows() {
|
|
133
263
|
return this.design.body.rows;
|
|
134
264
|
}
|
|
265
|
+
/** Get body-level values (background, fonts, etc.) */
|
|
135
266
|
getBodyValues() {
|
|
136
267
|
return this.design.body.values;
|
|
137
268
|
}
|
|
@@ -148,54 +279,39 @@ class EditorStore {
|
|
|
148
279
|
return this._activeTab;
|
|
149
280
|
}
|
|
150
281
|
get canUndo() {
|
|
151
|
-
return this.
|
|
282
|
+
return this.history.canUndo;
|
|
152
283
|
}
|
|
153
284
|
get canRedo() {
|
|
154
|
-
return this.
|
|
285
|
+
return this.history.canRedo;
|
|
155
286
|
}
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
// ----------------------------------------------------------
|
|
287
|
+
// ── Design Loading ─────────────────────────────────────────
|
|
288
|
+
/** Load a design document, resetting history and selection */
|
|
159
289
|
loadDesign(design) {
|
|
160
|
-
this.design =
|
|
290
|
+
this.design = structuredClone(design);
|
|
161
291
|
this.counterManager.setCounters(this.design.counters);
|
|
162
|
-
this.
|
|
163
|
-
this.redoStack = [];
|
|
292
|
+
this.history.clear();
|
|
164
293
|
this._selectedId = null;
|
|
165
294
|
this.notify();
|
|
166
295
|
this.events.emit("design:loaded", { design: this.design });
|
|
167
296
|
}
|
|
168
|
-
//
|
|
169
|
-
// History (undo/redo)
|
|
170
|
-
// ----------------------------------------------------------
|
|
171
|
-
pushHistory() {
|
|
172
|
-
this.undoStack.push(deepClone(this.design));
|
|
173
|
-
if (this.undoStack.length > this.maxHistory) {
|
|
174
|
-
this.undoStack.shift();
|
|
175
|
-
}
|
|
176
|
-
this.redoStack = [];
|
|
177
|
-
}
|
|
297
|
+
// ── Undo / Redo ────────────────────────────────────────────
|
|
178
298
|
undo() {
|
|
179
|
-
const prev = this.
|
|
299
|
+
const prev = this.history.undo(this.design);
|
|
180
300
|
if (!prev) return;
|
|
181
|
-
this.redoStack.push(deepClone(this.design));
|
|
182
301
|
this.design = prev;
|
|
183
302
|
this.counterManager.setCounters(this.design.counters);
|
|
184
303
|
this.notify();
|
|
185
304
|
this.emitUpdate("content_updated");
|
|
186
305
|
}
|
|
187
306
|
redo() {
|
|
188
|
-
const next = this.
|
|
307
|
+
const next = this.history.redo(this.design);
|
|
189
308
|
if (!next) return;
|
|
190
|
-
this.undoStack.push(deepClone(this.design));
|
|
191
309
|
this.design = next;
|
|
192
310
|
this.counterManager.setCounters(this.design.counters);
|
|
193
311
|
this.notify();
|
|
194
312
|
this.emitUpdate("content_updated");
|
|
195
313
|
}
|
|
196
|
-
//
|
|
197
|
-
// Selection / UI state
|
|
198
|
-
// ----------------------------------------------------------
|
|
314
|
+
// ── Selection / UI State ───────────────────────────────────
|
|
199
315
|
select(id) {
|
|
200
316
|
this._selectedId = id;
|
|
201
317
|
this.notify();
|
|
@@ -212,45 +328,46 @@ class EditorStore {
|
|
|
212
328
|
this._activeTab = tab;
|
|
213
329
|
this.notify();
|
|
214
330
|
}
|
|
215
|
-
//
|
|
216
|
-
|
|
217
|
-
// ----------------------------------------------------------
|
|
331
|
+
// ── Row Operations ─────────────────────────────────────────
|
|
332
|
+
/** Add a row at the given index (or at the end) */
|
|
218
333
|
addRow(row, index) {
|
|
219
|
-
this.
|
|
334
|
+
this.history.push(this.design);
|
|
220
335
|
const rows = this.design.body.rows;
|
|
336
|
+
const cloned = structuredClone(row);
|
|
221
337
|
if (index !== void 0 && index >= 0 && index <= rows.length) {
|
|
222
|
-
rows.splice(index, 0,
|
|
338
|
+
rows.splice(index, 0, cloned);
|
|
223
339
|
} else {
|
|
224
|
-
rows.push(
|
|
340
|
+
rows.push(cloned);
|
|
225
341
|
}
|
|
226
342
|
this.syncCounters();
|
|
227
343
|
this.notify();
|
|
228
|
-
this.emitUpdate("row_added",
|
|
344
|
+
this.emitUpdate("row_added", cloned);
|
|
229
345
|
}
|
|
346
|
+
/** Remove a row by ID */
|
|
230
347
|
removeRow(rowId) {
|
|
231
|
-
this.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
this.emitUpdate("row_removed");
|
|
239
|
-
}
|
|
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");
|
|
240
355
|
}
|
|
356
|
+
/** Move a row from one index to another */
|
|
241
357
|
moveRow(fromIndex, toIndex) {
|
|
242
|
-
this.
|
|
358
|
+
this.history.push(this.design);
|
|
243
359
|
const rows = this.design.body.rows;
|
|
244
360
|
const [row] = rows.splice(fromIndex, 1);
|
|
245
361
|
rows.splice(toIndex, 0, row);
|
|
246
362
|
this.notify();
|
|
247
363
|
this.emitUpdate("row_reordered");
|
|
248
364
|
}
|
|
365
|
+
/** Duplicate a row, assigning fresh IDs to all nested elements */
|
|
249
366
|
duplicateRow(rowId) {
|
|
250
|
-
const row = this.
|
|
367
|
+
const row = findRow(this.design, rowId);
|
|
251
368
|
if (!row) return;
|
|
252
|
-
this.
|
|
253
|
-
const cloned =
|
|
369
|
+
this.history.push(this.design);
|
|
370
|
+
const cloned = structuredClone(row);
|
|
254
371
|
const rowNum = this.counterManager.next("u_row");
|
|
255
372
|
cloned.id = `u_row_${rowNum}`;
|
|
256
373
|
cloned.values._meta = { htmlID: cloned.id, htmlClassNames: "u_row" };
|
|
@@ -264,43 +381,42 @@ class EditorStore {
|
|
|
264
381
|
content.values._meta = { htmlID: content.id, htmlClassNames: `u_content_${content.type}` };
|
|
265
382
|
}
|
|
266
383
|
}
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
rows.splice(idx + 1, 0, cloned);
|
|
384
|
+
const idx = getRowIndex(this.design, rowId);
|
|
385
|
+
this.design.body.rows.splice(idx + 1, 0, cloned);
|
|
270
386
|
this.syncCounters();
|
|
271
387
|
this.notify();
|
|
272
388
|
this.emitUpdate("row_added", cloned);
|
|
273
389
|
}
|
|
390
|
+
/** Get the index of a row */
|
|
274
391
|
getRowIndex(rowId) {
|
|
275
|
-
return this.design
|
|
392
|
+
return getRowIndex(this.design, rowId);
|
|
276
393
|
}
|
|
394
|
+
/** Update row-level values */
|
|
277
395
|
updateRowValues(rowId, patch) {
|
|
278
|
-
const row = this.
|
|
396
|
+
const row = findRow(this.design, rowId);
|
|
279
397
|
if (!row) return;
|
|
280
|
-
this.
|
|
398
|
+
this.history.push(this.design);
|
|
281
399
|
Object.assign(row.values, patch);
|
|
282
400
|
this.notify();
|
|
283
401
|
this.emitUpdate("content_updated");
|
|
284
402
|
}
|
|
285
|
-
//
|
|
286
|
-
|
|
287
|
-
// ----------------------------------------------------------
|
|
403
|
+
// ── Column Operations ──────────────────────────────────────
|
|
404
|
+
/** Update column-level values */
|
|
288
405
|
updateColumnValues(columnId, patch) {
|
|
289
|
-
const col = this.
|
|
406
|
+
const col = findColumn(this.design, columnId);
|
|
290
407
|
if (!col) return;
|
|
291
|
-
this.
|
|
408
|
+
this.history.push(this.design);
|
|
292
409
|
Object.assign(col.values, patch);
|
|
293
410
|
this.notify();
|
|
294
411
|
this.emitUpdate("content_updated");
|
|
295
412
|
}
|
|
296
|
-
//
|
|
297
|
-
|
|
298
|
-
// ----------------------------------------------------------
|
|
413
|
+
// ── Content Operations ─────────────────────────────────────
|
|
414
|
+
/** Add content to a column at the given index */
|
|
299
415
|
addContent(columnId, content, index) {
|
|
300
|
-
const col = this.
|
|
416
|
+
const col = findColumn(this.design, columnId);
|
|
301
417
|
if (!col) return;
|
|
302
|
-
this.
|
|
303
|
-
const cloned =
|
|
418
|
+
this.history.push(this.design);
|
|
419
|
+
const cloned = structuredClone(content);
|
|
304
420
|
if (index !== void 0 && index >= 0 && index <= col.contents.length) {
|
|
305
421
|
col.contents.splice(index, 0, cloned);
|
|
306
422
|
} else {
|
|
@@ -310,12 +426,13 @@ class EditorStore {
|
|
|
310
426
|
this.notify();
|
|
311
427
|
this.emitUpdate("content_added", cloned);
|
|
312
428
|
}
|
|
429
|
+
/** Remove a content block by ID */
|
|
313
430
|
removeContent(contentId) {
|
|
314
431
|
for (const row of this.design.body.rows) {
|
|
315
432
|
for (const col of row.columns) {
|
|
316
433
|
const idx = col.contents.findIndex((c) => c.id === contentId);
|
|
317
434
|
if (idx !== -1) {
|
|
318
|
-
this.
|
|
435
|
+
this.history.push(this.design);
|
|
319
436
|
col.contents.splice(idx, 1);
|
|
320
437
|
if (this._selectedId === contentId) this._selectedId = null;
|
|
321
438
|
this.notify();
|
|
@@ -325,49 +442,47 @@ class EditorStore {
|
|
|
325
442
|
}
|
|
326
443
|
}
|
|
327
444
|
}
|
|
445
|
+
/** Update content values by ID */
|
|
328
446
|
updateContentValues(contentId, patch) {
|
|
329
|
-
const content = this.
|
|
447
|
+
const content = findContent(this.design, contentId);
|
|
330
448
|
if (!content) return;
|
|
331
|
-
this.
|
|
449
|
+
this.history.push(this.design);
|
|
332
450
|
Object.assign(content.values, patch);
|
|
333
451
|
this.notify();
|
|
334
452
|
this.emitUpdate("content_updated");
|
|
335
453
|
}
|
|
454
|
+
/** Move a content block to a different column at a given index */
|
|
336
455
|
moveContent(contentId, targetColumnId, targetIndex) {
|
|
337
|
-
const content = this.
|
|
456
|
+
const content = findContent(this.design, contentId);
|
|
338
457
|
if (!content) return;
|
|
458
|
+
this.history.push(this.design);
|
|
339
459
|
for (const row of this.design.body.rows) {
|
|
340
460
|
for (const col of row.columns) {
|
|
341
461
|
const idx = col.contents.findIndex((c) => c.id === contentId);
|
|
342
462
|
if (idx !== -1) {
|
|
343
|
-
this.pushHistory();
|
|
344
463
|
col.contents.splice(idx, 1);
|
|
345
464
|
break;
|
|
346
465
|
}
|
|
347
466
|
}
|
|
348
467
|
}
|
|
349
|
-
const targetCol = this.
|
|
350
|
-
if (targetCol)
|
|
351
|
-
targetCol.contents.splice(targetIndex, 0, content);
|
|
352
|
-
}
|
|
468
|
+
const targetCol = findColumn(this.design, targetColumnId);
|
|
469
|
+
if (targetCol) targetCol.contents.splice(targetIndex, 0, content);
|
|
353
470
|
this.notify();
|
|
354
471
|
this.emitUpdate("content_reordered");
|
|
355
472
|
}
|
|
473
|
+
/** Duplicate a content block, inserting the copy right after the original */
|
|
356
474
|
duplicateContent(contentId) {
|
|
357
|
-
const content = this.
|
|
475
|
+
const content = findContent(this.design, contentId);
|
|
358
476
|
if (!content) return;
|
|
359
477
|
for (const row of this.design.body.rows) {
|
|
360
478
|
for (const col of row.columns) {
|
|
361
479
|
const idx = col.contents.findIndex((c) => c.id === contentId);
|
|
362
480
|
if (idx !== -1) {
|
|
363
|
-
|
|
481
|
+
this.history.push(this.design);
|
|
482
|
+
const cloned = structuredClone(content);
|
|
364
483
|
const counter = this.counterManager.next(`u_content_${content.type}`);
|
|
365
484
|
cloned.id = `u_content_${content.type}_${counter}`;
|
|
366
|
-
cloned.values._meta = {
|
|
367
|
-
htmlID: cloned.id,
|
|
368
|
-
htmlClassNames: `u_content_${content.type}`
|
|
369
|
-
};
|
|
370
|
-
this.pushHistory();
|
|
485
|
+
cloned.values._meta = { htmlID: cloned.id, htmlClassNames: `u_content_${content.type}` };
|
|
371
486
|
col.contents.splice(idx + 1, 0, cloned);
|
|
372
487
|
this.syncCounters();
|
|
373
488
|
this.notify();
|
|
@@ -377,109 +492,44 @@ class EditorStore {
|
|
|
377
492
|
}
|
|
378
493
|
}
|
|
379
494
|
}
|
|
380
|
-
//
|
|
381
|
-
|
|
382
|
-
// ----------------------------------------------------------
|
|
495
|
+
// ── Body Values ────────────────────────────────────────────
|
|
496
|
+
/** Update body-level values (background, fonts, etc.) */
|
|
383
497
|
updateBodyValues(patch) {
|
|
384
|
-
this.
|
|
498
|
+
this.history.push(this.design);
|
|
385
499
|
Object.assign(this.design.body.values, patch);
|
|
386
500
|
this.notify();
|
|
387
501
|
this.emitUpdate("body_updated");
|
|
388
502
|
}
|
|
389
|
-
//
|
|
390
|
-
// Lookup helpers
|
|
391
|
-
// ----------------------------------------------------------
|
|
503
|
+
// ── Lookups (delegate to design-lookup) ────────────────────
|
|
392
504
|
findRow(rowId) {
|
|
393
|
-
return this.design
|
|
505
|
+
return findRow(this.design, rowId);
|
|
394
506
|
}
|
|
395
507
|
findColumn(columnId) {
|
|
396
|
-
|
|
397
|
-
const col = row.columns.find((c) => c.id === columnId);
|
|
398
|
-
if (col) return col;
|
|
399
|
-
}
|
|
400
|
-
return void 0;
|
|
508
|
+
return findColumn(this.design, columnId);
|
|
401
509
|
}
|
|
402
510
|
findContent(contentId) {
|
|
403
|
-
|
|
404
|
-
for (const col of row.columns) {
|
|
405
|
-
const content = col.contents.find((c) => c.id === contentId);
|
|
406
|
-
if (content) return content;
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
return void 0;
|
|
511
|
+
return findContent(this.design, contentId);
|
|
410
512
|
}
|
|
411
513
|
findParentColumn(contentId) {
|
|
412
|
-
|
|
413
|
-
for (const col of row.columns) {
|
|
414
|
-
if (col.contents.some((c) => c.id === contentId)) return col;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
return void 0;
|
|
514
|
+
return findParentColumn(this.design, contentId);
|
|
418
515
|
}
|
|
419
516
|
findParentRow(columnId) {
|
|
420
|
-
|
|
421
|
-
if (row.columns.some((c) => c.id === columnId)) return row;
|
|
422
|
-
}
|
|
423
|
-
return void 0;
|
|
517
|
+
return findParentRow(this.design, columnId);
|
|
424
518
|
}
|
|
425
|
-
//
|
|
426
|
-
|
|
427
|
-
// ----------------------------------------------------------
|
|
428
|
-
/** Creates a new row with the given column layout */
|
|
519
|
+
// ── Factory Methods (delegate to design-factory) ───────────
|
|
520
|
+
/** Create a new row with the given column layout */
|
|
429
521
|
createRow(cellProportions) {
|
|
430
|
-
const
|
|
431
|
-
const columns = cellProportions.map((_, i) => {
|
|
432
|
-
const colNum = this.counterManager.next("u_column");
|
|
433
|
-
return {
|
|
434
|
-
id: `u_column_${colNum}`,
|
|
435
|
-
contents: [],
|
|
436
|
-
values: {
|
|
437
|
-
backgroundColor: "",
|
|
438
|
-
padding: "0px",
|
|
439
|
-
border: {},
|
|
440
|
-
borderRadius: "0px",
|
|
441
|
-
_meta: { htmlID: `u_column_${colNum}`, htmlClassNames: "u_column" }
|
|
442
|
-
}
|
|
443
|
-
};
|
|
444
|
-
});
|
|
522
|
+
const row = createRow(this.counterManager, cellProportions);
|
|
445
523
|
this.syncCounters();
|
|
446
|
-
return
|
|
447
|
-
id: `u_row_${rowNum}`,
|
|
448
|
-
cells: cellProportions,
|
|
449
|
-
columns,
|
|
450
|
-
values: {
|
|
451
|
-
displayCondition: null,
|
|
452
|
-
columns: false,
|
|
453
|
-
backgroundColor: "",
|
|
454
|
-
columnsBackgroundColor: "",
|
|
455
|
-
backgroundImage: { url: "", fullWidth: true, repeat: false, center: true, cover: false },
|
|
456
|
-
padding: "0px",
|
|
457
|
-
anchor: "",
|
|
458
|
-
hideDesktop: false,
|
|
459
|
-
hideMobile: false,
|
|
460
|
-
_meta: { htmlID: `u_row_${rowNum}`, htmlClassNames: "u_row" }
|
|
461
|
-
}
|
|
462
|
-
};
|
|
524
|
+
return row;
|
|
463
525
|
}
|
|
464
|
-
/**
|
|
526
|
+
/** Create a new content block for the given tool type */
|
|
465
527
|
createContent(type, values = {}) {
|
|
466
|
-
const
|
|
467
|
-
const id = `u_content_${type}_${counter}`;
|
|
528
|
+
const content = createContent(this.counterManager, type, values);
|
|
468
529
|
this.syncCounters();
|
|
469
|
-
return
|
|
470
|
-
id,
|
|
471
|
-
type,
|
|
472
|
-
values: {
|
|
473
|
-
containerPadding: "10px",
|
|
474
|
-
anchor: "",
|
|
475
|
-
hideDesktop: false,
|
|
476
|
-
hideMobile: false,
|
|
477
|
-
displayCondition: null,
|
|
478
|
-
_meta: { htmlID: id, htmlClassNames: `u_content_${type}` },
|
|
479
|
-
...values
|
|
480
|
-
}
|
|
481
|
-
};
|
|
530
|
+
return content;
|
|
482
531
|
}
|
|
532
|
+
// ── Private Helpers ────────────────────────────────────────
|
|
483
533
|
syncCounters() {
|
|
484
534
|
this.design.counters = this.counterManager.getCounters();
|
|
485
535
|
}
|
|
@@ -530,14 +580,72 @@ const dragState = {
|
|
|
530
580
|
this.draggingContentId = null;
|
|
531
581
|
}
|
|
532
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
|
+
}
|
|
533
643
|
class DragManager {
|
|
534
644
|
constructor(store, toolRegistry, root) {
|
|
535
645
|
this.currentDrop = null;
|
|
536
|
-
this.
|
|
646
|
+
this.contentIndicator = null;
|
|
537
647
|
this.rowIndicator = null;
|
|
538
|
-
this.
|
|
539
|
-
};
|
|
540
|
-
this.handleDragOver = (e) => {
|
|
648
|
+
this.onDragOver = (e) => {
|
|
541
649
|
const types = e.dataTransfer?.types || [];
|
|
542
650
|
const isToolDrag = types.includes("application/maileditor-tool");
|
|
543
651
|
const isLayoutDrag = types.includes("application/maileditor-layout");
|
|
@@ -546,76 +654,42 @@ class DragManager {
|
|
|
546
654
|
e.preventDefault();
|
|
547
655
|
e.dataTransfer.dropEffect = isToolDrag || isLayoutDrag ? "copy" : "move";
|
|
548
656
|
if (isLayoutDrag) {
|
|
549
|
-
|
|
550
|
-
this.
|
|
551
|
-
|
|
552
|
-
this.currentDrop = drop;
|
|
553
|
-
this.showRowIndicator(drop);
|
|
554
|
-
} else {
|
|
555
|
-
this.hideRowIndicator();
|
|
556
|
-
this.currentDrop = null;
|
|
557
|
-
}
|
|
657
|
+
this.currentDrop = this.findRowDropTarget(e.clientY);
|
|
658
|
+
hideIndicator(this.contentIndicator);
|
|
659
|
+
this.showRowIndicator();
|
|
558
660
|
} else {
|
|
559
|
-
|
|
560
|
-
this.
|
|
561
|
-
|
|
562
|
-
this.currentDrop = drop;
|
|
563
|
-
this.showContentIndicator(drop);
|
|
564
|
-
} else {
|
|
565
|
-
this.hideIndicator();
|
|
566
|
-
this.currentDrop = null;
|
|
567
|
-
}
|
|
661
|
+
this.currentDrop = this.findContentDropTarget(e.clientX, e.clientY);
|
|
662
|
+
hideIndicator(this.rowIndicator);
|
|
663
|
+
this.showContentIndicator();
|
|
568
664
|
}
|
|
569
665
|
};
|
|
570
|
-
this.
|
|
666
|
+
this.onDrop = (e) => {
|
|
571
667
|
e.preventDefault();
|
|
572
|
-
this.
|
|
573
|
-
this.hideRowIndicator();
|
|
668
|
+
this.hideAllIndicators();
|
|
574
669
|
const layoutData = e.dataTransfer?.getData("application/maileditor-layout");
|
|
575
670
|
if (layoutData) {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
const rowIndex = this.currentDrop?.type === "row" ? this.currentDrop.rowIndex : void 0;
|
|
579
|
-
this.store.addRow(row, rowIndex);
|
|
580
|
-
this.reset();
|
|
581
|
-
return;
|
|
671
|
+
this.handleLayoutDrop(JSON.parse(layoutData));
|
|
672
|
+
return this.reset();
|
|
582
673
|
}
|
|
583
674
|
const toolName = e.dataTransfer?.getData("application/maileditor-tool");
|
|
584
675
|
if (toolName) {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
const content = this.store.createContent(toolName, defaults);
|
|
588
|
-
this.store.addContent(this.currentDrop.columnId, content, this.currentDrop.contentIndex);
|
|
589
|
-
this.store.select(content.id);
|
|
590
|
-
} else {
|
|
591
|
-
const row = this.store.createRow([1]);
|
|
592
|
-
this.store.addRow(row);
|
|
593
|
-
const defaults = this.toolRegistry.getDefaultValues(toolName);
|
|
594
|
-
const content = this.store.createContent(toolName, defaults);
|
|
595
|
-
this.store.addContent(row.columns[0].id, content);
|
|
596
|
-
this.store.select(content.id);
|
|
597
|
-
}
|
|
598
|
-
this.reset();
|
|
599
|
-
return;
|
|
676
|
+
this.handleToolDrop(toolName);
|
|
677
|
+
return this.reset();
|
|
600
678
|
}
|
|
601
679
|
const contentId = e.dataTransfer?.getData("application/maileditor-content") || dragState.draggingContentId;
|
|
602
|
-
if (contentId
|
|
603
|
-
this.
|
|
604
|
-
this.store.select(contentId);
|
|
680
|
+
if (contentId) {
|
|
681
|
+
this.handleContentDrop(contentId);
|
|
605
682
|
}
|
|
606
683
|
this.reset();
|
|
607
|
-
dragState.reset();
|
|
608
684
|
};
|
|
609
|
-
this.
|
|
610
|
-
this.
|
|
611
|
-
this.hideRowIndicator();
|
|
685
|
+
this.onDragEnd = () => {
|
|
686
|
+
this.hideAllIndicators();
|
|
612
687
|
this.reset();
|
|
613
688
|
};
|
|
614
|
-
this.
|
|
689
|
+
this.onDragLeave = (e) => {
|
|
615
690
|
const related = e.relatedTarget;
|
|
616
691
|
if (!related || !this.root.contains(related)) {
|
|
617
|
-
this.
|
|
618
|
-
this.hideRowIndicator();
|
|
692
|
+
this.hideAllIndicators();
|
|
619
693
|
this.currentDrop = null;
|
|
620
694
|
}
|
|
621
695
|
};
|
|
@@ -623,56 +697,61 @@ class DragManager {
|
|
|
623
697
|
this.toolRegistry = toolRegistry;
|
|
624
698
|
this.root = root;
|
|
625
699
|
}
|
|
700
|
+
/** Attach all drag event listeners to the shadow root */
|
|
626
701
|
attach() {
|
|
627
|
-
this.root.addEventListener("dragover", this.
|
|
628
|
-
this.root.addEventListener("drop", this.
|
|
629
|
-
this.root.addEventListener("dragend", this.
|
|
630
|
-
this.root.addEventListener("dragleave", this.
|
|
631
|
-
this.
|
|
632
|
-
this.
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
createIndicator(color) {
|
|
636
|
-
const el = document.createElement("div");
|
|
637
|
-
Object.assign(el.style, {
|
|
638
|
-
position: "absolute",
|
|
639
|
-
left: "0",
|
|
640
|
-
right: "0",
|
|
641
|
-
height: "3px",
|
|
642
|
-
background: color,
|
|
643
|
-
borderRadius: "2px",
|
|
644
|
-
pointerEvents: "none",
|
|
645
|
-
zIndex: "1000",
|
|
646
|
-
display: "none",
|
|
647
|
-
boxShadow: `0 0 6px ${color}80`
|
|
648
|
-
});
|
|
649
|
-
return el;
|
|
650
|
-
}
|
|
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 */
|
|
651
710
|
detach() {
|
|
652
|
-
this.root.removeEventListener("dragover", this.
|
|
653
|
-
this.root.removeEventListener("drop", this.
|
|
654
|
-
this.root.removeEventListener("dragend", this.
|
|
655
|
-
this.root.removeEventListener("dragleave", this.
|
|
656
|
-
this.
|
|
657
|
-
this.indicator?.remove();
|
|
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();
|
|
658
716
|
this.rowIndicator?.remove();
|
|
659
717
|
}
|
|
660
|
-
//
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
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) {
|
|
664
747
|
const canvas = this.root.querySelector("me-editor-canvas");
|
|
665
748
|
if (!canvas?.shadowRoot) return null;
|
|
666
749
|
const rows = Array.from(canvas.shadowRoot.querySelectorAll("me-row-renderer"));
|
|
667
|
-
if (rows.length === 0) {
|
|
668
|
-
|
|
669
|
-
}
|
|
670
|
-
const firstRect = rows[0].getBoundingClientRect();
|
|
671
|
-
let bestDist = Math.abs(clientY - firstRect.top);
|
|
672
|
-
let bestTarget = { type: "row", rowIndex: 0, y: firstRect.top };
|
|
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 };
|
|
673
753
|
for (let i = 0; i < rows.length; i++) {
|
|
674
|
-
const
|
|
675
|
-
const y = rect.bottom;
|
|
754
|
+
const y = rows[i].getBoundingClientRect().bottom;
|
|
676
755
|
const dist = Math.abs(clientY - y);
|
|
677
756
|
if (dist < bestDist) {
|
|
678
757
|
bestDist = dist;
|
|
@@ -681,21 +760,16 @@ class DragManager {
|
|
|
681
760
|
}
|
|
682
761
|
return bestTarget;
|
|
683
762
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
// ----------------------------------------------------------
|
|
687
|
-
findContentDropPosition(clientX, clientY) {
|
|
688
|
-
const columns = this.queryShadowAll(this.root, "me-column-renderer");
|
|
763
|
+
findContentDropTarget(clientX, clientY) {
|
|
764
|
+
const columns = queryShadowAll(this.root, "me-column-renderer");
|
|
689
765
|
let bestTarget = null;
|
|
690
766
|
let bestDist = Infinity;
|
|
691
767
|
for (const colEl of columns) {
|
|
692
768
|
const columnId = colEl.dataset.columnId;
|
|
693
|
-
if (!columnId) continue;
|
|
694
|
-
const colShadow = colEl.shadowRoot;
|
|
695
|
-
if (!colShadow) continue;
|
|
769
|
+
if (!columnId || !colEl.shadowRoot) continue;
|
|
696
770
|
const colRect = colEl.getBoundingClientRect();
|
|
697
771
|
if (clientX < colRect.left || clientX > colRect.right) continue;
|
|
698
|
-
const contentEls = Array.from(
|
|
772
|
+
const contentEls = Array.from(colEl.shadowRoot.querySelectorAll("me-content-renderer"));
|
|
699
773
|
if (contentEls.length === 0) {
|
|
700
774
|
const dist2 = Math.abs(clientY - (colRect.top + colRect.height / 2));
|
|
701
775
|
if (dist2 < bestDist) {
|
|
@@ -704,11 +778,11 @@ class DragManager {
|
|
|
704
778
|
}
|
|
705
779
|
continue;
|
|
706
780
|
}
|
|
707
|
-
const
|
|
708
|
-
let dist = Math.abs(clientY -
|
|
781
|
+
const firstY = contentEls[0].getBoundingClientRect().top;
|
|
782
|
+
let dist = Math.abs(clientY - firstY);
|
|
709
783
|
if (dist < bestDist) {
|
|
710
784
|
bestDist = dist;
|
|
711
|
-
bestTarget = { type: "content", columnId, contentIndex: 0, y:
|
|
785
|
+
bestTarget = { type: "content", columnId, contentIndex: 0, y: firstY };
|
|
712
786
|
}
|
|
713
787
|
for (let i = 0; i < contentEls.length; i++) {
|
|
714
788
|
const rect = contentEls[i].getBoundingClientRect();
|
|
@@ -723,113 +797,72 @@ class DragManager {
|
|
|
723
797
|
}
|
|
724
798
|
return bestTarget;
|
|
725
799
|
}
|
|
726
|
-
//
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
const columns = this.queryShadowAll(this.root, "me-column-renderer");
|
|
732
|
-
const colEl = columns.find((c) => c.dataset.columnId === drop.columnId);
|
|
733
|
-
if (!colEl?.shadowRoot) return;
|
|
734
|
-
if (this.indicator.parentNode !== colEl.shadowRoot) {
|
|
735
|
-
this.indicator.remove();
|
|
736
|
-
colEl.shadowRoot.appendChild(this.indicator);
|
|
800
|
+
// ── Indicator Positioning ──────────────────────────────────
|
|
801
|
+
showContentIndicator() {
|
|
802
|
+
if (!this.contentIndicator || !this.currentDrop?.columnId) {
|
|
803
|
+
hideIndicator(this.contentIndicator);
|
|
804
|
+
return;
|
|
737
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;
|
|
738
809
|
const contentEls = Array.from(colEl.shadowRoot.querySelectorAll("me-content-renderer"));
|
|
739
|
-
|
|
740
|
-
let indicatorY;
|
|
741
|
-
if (contentEls.length === 0 || drop.contentIndex === 0) {
|
|
742
|
-
indicatorY = 0;
|
|
743
|
-
} else if (drop.contentIndex >= contentEls.length) {
|
|
744
|
-
const lastRect = contentEls[contentEls.length - 1].getBoundingClientRect();
|
|
745
|
-
indicatorY = lastRect.bottom - colRect.top;
|
|
746
|
-
} else {
|
|
747
|
-
const elRect = contentEls[drop.contentIndex].getBoundingClientRect();
|
|
748
|
-
indicatorY = elRect.top - colRect.top;
|
|
749
|
-
}
|
|
750
|
-
Object.assign(this.indicator.style, {
|
|
751
|
-
display: "block",
|
|
752
|
-
top: `${indicatorY}px`,
|
|
753
|
-
left: "4px",
|
|
754
|
-
right: "4px",
|
|
755
|
-
width: "auto"
|
|
756
|
-
});
|
|
810
|
+
positionIndicator(this.contentIndicator, colEl.shadowRoot, contentEls, this.currentDrop.contentIndex ?? 0, "4px");
|
|
757
811
|
}
|
|
758
|
-
showRowIndicator(
|
|
759
|
-
if (!this.rowIndicator)
|
|
812
|
+
showRowIndicator() {
|
|
813
|
+
if (!this.rowIndicator || !this.currentDrop) {
|
|
814
|
+
hideIndicator(this.rowIndicator);
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
760
817
|
const canvas = this.root.querySelector("me-editor-canvas");
|
|
761
|
-
|
|
762
|
-
const canvasBody = canvas.shadowRoot.querySelector(".canvas-body");
|
|
818
|
+
const canvasBody = canvas?.shadowRoot?.querySelector(".canvas-body");
|
|
763
819
|
if (!canvasBody) return;
|
|
764
|
-
if (this.rowIndicator.parentNode !== canvasBody) {
|
|
765
|
-
this.rowIndicator.remove();
|
|
766
|
-
canvasBody.appendChild(this.rowIndicator);
|
|
767
|
-
}
|
|
768
820
|
const rows = Array.from(canvas.shadowRoot.querySelectorAll("me-row-renderer"));
|
|
769
|
-
|
|
770
|
-
let indicatorY;
|
|
771
|
-
if (rows.length === 0 || drop.rowIndex === 0) {
|
|
772
|
-
indicatorY = 0;
|
|
773
|
-
} else if (drop.rowIndex >= rows.length) {
|
|
774
|
-
const lastRect = rows[rows.length - 1].getBoundingClientRect();
|
|
775
|
-
indicatorY = lastRect.bottom - bodyRect.top;
|
|
776
|
-
} else {
|
|
777
|
-
const elRect = rows[drop.rowIndex].getBoundingClientRect();
|
|
778
|
-
indicatorY = elRect.top - bodyRect.top;
|
|
779
|
-
}
|
|
780
|
-
Object.assign(this.rowIndicator.style, {
|
|
781
|
-
display: "block",
|
|
782
|
-
top: `${indicatorY}px`,
|
|
783
|
-
left: "0",
|
|
784
|
-
right: "0",
|
|
785
|
-
width: "auto"
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
hideIndicator() {
|
|
789
|
-
if (this.indicator) this.indicator.style.display = "none";
|
|
821
|
+
positionIndicator(this.rowIndicator, canvasBody, rows, this.currentDrop.rowIndex ?? 0, "0");
|
|
790
822
|
}
|
|
791
|
-
|
|
792
|
-
|
|
823
|
+
hideAllIndicators() {
|
|
824
|
+
hideIndicator(this.contentIndicator);
|
|
825
|
+
hideIndicator(this.rowIndicator);
|
|
793
826
|
}
|
|
794
827
|
reset() {
|
|
795
828
|
this.currentDrop = null;
|
|
796
|
-
|
|
797
|
-
this.hideRowIndicator();
|
|
829
|
+
dragState.reset();
|
|
798
830
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
for (const child of Array.from(children)) {
|
|
812
|
-
const el = child;
|
|
813
|
-
callback(el);
|
|
814
|
-
if (el.shadowRoot) this.walkShadowDom(el.shadowRoot, callback);
|
|
815
|
-
if (el.children?.length) this.walkShadowDom(el, callback);
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
findContentElement(el) {
|
|
819
|
-
while (el) {
|
|
820
|
-
if (el.dataset?.contentId) return el;
|
|
821
|
-
if (el.tagName?.toLowerCase() === "me-content-renderer") return el;
|
|
822
|
-
if (el.parentElement) {
|
|
823
|
-
el = el.parentElement;
|
|
824
|
-
} else if (el.getRootNode().host) {
|
|
825
|
-
el = el.getRootNode().host;
|
|
826
|
-
} else {
|
|
827
|
-
break;
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
return null;
|
|
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;
|
|
831
843
|
}
|
|
832
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
|
+
}
|
|
833
866
|
const textTool = {
|
|
834
867
|
name: "text",
|
|
835
868
|
label: "Text",
|
|
@@ -852,11 +885,7 @@ const textTool = {
|
|
|
852
885
|
options: {
|
|
853
886
|
color: { label: "Text Color", defaultValue: "#000000", widget: "color_picker" },
|
|
854
887
|
backgroundColor: { label: "Background Color", defaultValue: "", widget: "color_picker" },
|
|
855
|
-
textAlign: {
|
|
856
|
-
label: "Text Align",
|
|
857
|
-
defaultValue: "left",
|
|
858
|
-
widget: "alignment"
|
|
859
|
-
},
|
|
888
|
+
textAlign: { label: "Text Align", defaultValue: "left", widget: "alignment" },
|
|
860
889
|
lineHeight: {
|
|
861
890
|
label: "Line Height",
|
|
862
891
|
defaultValue: "140%",
|
|
@@ -896,37 +925,28 @@ const textTool = {
|
|
|
896
925
|
textAlign: "left"
|
|
897
926
|
},
|
|
898
927
|
renderer: {
|
|
899
|
-
renderEditor(values
|
|
900
|
-
const padding = values
|
|
901
|
-
const bgColor = values
|
|
902
|
-
const color = values
|
|
903
|
-
const lineHeight = values
|
|
904
|
-
const textContent = values
|
|
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");
|
|
905
934
|
return html`
|
|
906
935
|
<div style="padding:${padding};background-color:${bgColor};color:${color};line-height:${lineHeight};word-break:break-word;">
|
|
907
936
|
${unsafeHTML(textContent)}
|
|
908
937
|
</div>
|
|
909
938
|
`;
|
|
910
939
|
},
|
|
911
|
-
renderHtml(values
|
|
912
|
-
const padding = values
|
|
913
|
-
const bgColor = values
|
|
914
|
-
const color = values
|
|
915
|
-
const lineHeight = values
|
|
916
|
-
const
|
|
917
|
-
const
|
|
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");
|
|
918
947
|
const bgStyle = bgColor ? `background-color:${bgColor};` : "";
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
<tr>
|
|
922
|
-
<td style="overflow-wrap:break-word;word-break:break-word;padding:${padding};${bgStyle}font-family:arial,helvetica,sans-serif;" align="left">
|
|
923
|
-
<div style="font-size:14px;color:${color};line-height:${lineHeight};text-align:${textAlign};word-wrap:break-word;">
|
|
924
|
-
${textContent}
|
|
925
|
-
</div>
|
|
926
|
-
</td>
|
|
927
|
-
</tr>
|
|
928
|
-
</tbody>
|
|
929
|
-
</table>`;
|
|
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 });
|
|
930
950
|
}
|
|
931
951
|
}
|
|
932
952
|
};
|
|
@@ -940,23 +960,17 @@ const headingTool = {
|
|
|
940
960
|
text: {
|
|
941
961
|
title: "Heading",
|
|
942
962
|
options: {
|
|
943
|
-
text: {
|
|
944
|
-
label: "Text",
|
|
945
|
-
defaultValue: "Heading",
|
|
946
|
-
widget: "text"
|
|
947
|
-
},
|
|
963
|
+
text: { label: "Text", defaultValue: "Heading", widget: "text" },
|
|
948
964
|
headingType: {
|
|
949
965
|
label: "Heading Type",
|
|
950
966
|
defaultValue: "h1",
|
|
951
967
|
widget: "dropdown",
|
|
952
|
-
widgetParams: {
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
]
|
|
959
|
-
}
|
|
968
|
+
widgetParams: { options: [
|
|
969
|
+
{ label: "H1", value: "h1" },
|
|
970
|
+
{ label: "H2", value: "h2" },
|
|
971
|
+
{ label: "H3", value: "h3" },
|
|
972
|
+
{ label: "H4", value: "h4" }
|
|
973
|
+
] }
|
|
960
974
|
}
|
|
961
975
|
}
|
|
962
976
|
},
|
|
@@ -1037,35 +1051,32 @@ const headingTool = {
|
|
|
1037
1051
|
containerPadding: "10px"
|
|
1038
1052
|
},
|
|
1039
1053
|
renderer: {
|
|
1040
|
-
renderEditor(values
|
|
1041
|
-
const padding = values
|
|
1042
|
-
const fontSize = values
|
|
1043
|
-
const color = values
|
|
1044
|
-
const textAlign = values
|
|
1045
|
-
const fontWeight = values
|
|
1046
|
-
const lineHeight = values
|
|
1047
|
-
const text = values
|
|
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");
|
|
1048
1062
|
return html`
|
|
1049
1063
|
<div style="padding:${padding};font-size:${fontSize};color:${color};text-align:${textAlign};font-weight:${fontWeight};line-height:${lineHeight};">
|
|
1050
1064
|
${text}
|
|
1051
1065
|
</div>
|
|
1052
1066
|
`;
|
|
1053
1067
|
},
|
|
1054
|
-
renderHtml(values
|
|
1055
|
-
const padding = values
|
|
1056
|
-
const fontSize = values
|
|
1057
|
-
const color = values
|
|
1058
|
-
const textAlign = values
|
|
1059
|
-
const fontWeight = values
|
|
1060
|
-
const lineHeight = values
|
|
1061
|
-
const letterSpacing = values
|
|
1062
|
-
const tag = values
|
|
1063
|
-
const text = values
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
<${tag} style="margin:0;font-size:${fontSize};color:${color};text-align:${textAlign};font-weight:${fontWeight};line-height:${lineHeight};letter-spacing:${letterSpacing};">${text}</${tag}>
|
|
1067
|
-
</td></tr></tbody>
|
|
1068
|
-
</table>`;
|
|
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 });
|
|
1069
1080
|
}
|
|
1070
1081
|
}
|
|
1071
1082
|
};
|
|
@@ -1090,9 +1101,9 @@ const paragraphTool = {
|
|
|
1090
1101
|
title: "Style",
|
|
1091
1102
|
options: {
|
|
1092
1103
|
color: { label: "Text Color", defaultValue: "#374151", widget: "color_picker" },
|
|
1104
|
+
textAlign: { label: "Text Align", defaultValue: "left", widget: "alignment" },
|
|
1093
1105
|
lineHeight: { label: "Line Height", defaultValue: "160%", widget: "text" },
|
|
1094
|
-
letterSpacing: { label: "Letter Spacing", defaultValue: "normal", widget: "text" }
|
|
1095
|
-
textAlign: { label: "Text Align", defaultValue: "left", widget: "alignment" }
|
|
1106
|
+
letterSpacing: { label: "Letter Spacing", defaultValue: "normal", widget: "text" }
|
|
1096
1107
|
}
|
|
1097
1108
|
},
|
|
1098
1109
|
spacing: {
|
|
@@ -1103,7 +1114,7 @@ const paragraphTool = {
|
|
|
1103
1114
|
}
|
|
1104
1115
|
},
|
|
1105
1116
|
defaultValues: {
|
|
1106
|
-
text: '<p style="font-size:14px;line-height:1.6;">Lorem ipsum dolor sit amet, consectetur adipiscing elit
|
|
1117
|
+
text: '<p style="font-size:14px;line-height:1.6;">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>',
|
|
1107
1118
|
color: "#374151",
|
|
1108
1119
|
lineHeight: "160%",
|
|
1109
1120
|
letterSpacing: "normal",
|
|
@@ -1112,24 +1123,19 @@ const paragraphTool = {
|
|
|
1112
1123
|
},
|
|
1113
1124
|
renderer: {
|
|
1114
1125
|
renderEditor(values) {
|
|
1115
|
-
const padding = values
|
|
1116
|
-
const color = values
|
|
1117
|
-
const lineHeight = values
|
|
1118
|
-
|
|
1119
|
-
return html`<div style="padding:${padding};color:${color};line-height:${lineHeight};word-break:break-word;">${unsafeHTML(text)}</div>`;
|
|
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>`;
|
|
1120
1130
|
},
|
|
1121
1131
|
renderHtml(values) {
|
|
1122
|
-
const padding = values
|
|
1123
|
-
const color = values
|
|
1124
|
-
const lineHeight = values
|
|
1125
|
-
const
|
|
1126
|
-
const
|
|
1127
|
-
const
|
|
1128
|
-
return
|
|
1129
|
-
<tbody><tr><td style="overflow-wrap:break-word;word-break:break-word;padding:${padding};font-family:arial,helvetica,sans-serif;" align="left">
|
|
1130
|
-
<div style="font-size:14px;color:${color};line-height:${lineHeight};text-align:${textAlign};letter-spacing:${letterSpacing};word-wrap:break-word;">${text}</div>
|
|
1131
|
-
</td></tr></tbody>
|
|
1132
|
-
</table>`;
|
|
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 });
|
|
1133
1139
|
}
|
|
1134
1140
|
}
|
|
1135
1141
|
};
|
|
@@ -1170,9 +1176,7 @@ const imageTool = {
|
|
|
1170
1176
|
},
|
|
1171
1177
|
spacing: {
|
|
1172
1178
|
title: "Spacing",
|
|
1173
|
-
options: {
|
|
1174
|
-
containerPadding: { label: "Padding", defaultValue: "10px", widget: "padding" }
|
|
1175
|
-
}
|
|
1179
|
+
options: { containerPadding: { label: "Padding", defaultValue: "10px", widget: "padding" } }
|
|
1176
1180
|
},
|
|
1177
1181
|
general: {
|
|
1178
1182
|
title: "General",
|
|
@@ -1195,46 +1199,32 @@ const imageTool = {
|
|
|
1195
1199
|
containerPadding: "10px"
|
|
1196
1200
|
},
|
|
1197
1201
|
renderer: {
|
|
1198
|
-
renderEditor(values
|
|
1199
|
-
const padding = values
|
|
1200
|
-
const src = values
|
|
1201
|
-
const alt = values
|
|
1202
|
-
const width = values
|
|
1203
|
-
const borderRadius = values
|
|
1204
|
-
const align = values
|
|
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");
|
|
1205
1209
|
if (!src) {
|
|
1206
|
-
return html
|
|
1207
|
-
<div style="padding:${padding};text-align:${align};">
|
|
1208
|
-
<div style="background:#f1f5f9;border:2px dashed #cbd5e1;border-radius:8px;padding:40px 20px;text-align:center;color:#94a3b8;font-family:sans-serif;font-size:13px;">
|
|
1209
|
-
No image set. Enter a URL in the property panel.
|
|
1210
|
-
</div>
|
|
1211
|
-
</div>
|
|
1212
|
-
`;
|
|
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>`;
|
|
1213
1211
|
}
|
|
1214
|
-
return html
|
|
1215
|
-
<div style="padding:${padding};text-align:${align};">
|
|
1216
|
-
<img src=${src} alt=${alt} style="display:inline-block;max-width:100%;width:${width};border-radius:${borderRadius};border:0;" />
|
|
1217
|
-
</div>
|
|
1218
|
-
`;
|
|
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>`;
|
|
1219
1213
|
},
|
|
1220
1214
|
renderHtml(values, ctx) {
|
|
1221
|
-
const padding = values
|
|
1222
|
-
const src = values
|
|
1223
|
-
const alt = values
|
|
1224
|
-
const href = values
|
|
1225
|
-
const target = values
|
|
1226
|
-
const width = values
|
|
1227
|
-
const borderRadius = values
|
|
1228
|
-
const align = values
|
|
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");
|
|
1229
1223
|
const widthPx = width === "100%" ? ctx.columnWidth : parseInt(width);
|
|
1230
|
-
const
|
|
1231
|
-
const imgTag = `<img align="${align}" border="0" src="${src}" alt="${alt}" title="${alt}" style="outline:none;text-decoration:none
|
|
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}" />`;
|
|
1232
1226
|
const content = href ? `<a href="${href}" target="${target}" style="text-decoration:none;">${imgTag}</a>` : imgTag;
|
|
1233
|
-
return
|
|
1234
|
-
<tbody><tr><td style="overflow-wrap:break-word;word-break:break-word;padding:${padding};font-family:arial,helvetica,sans-serif;" align="${align}">
|
|
1235
|
-
${content}
|
|
1236
|
-
</td></tr></tbody>
|
|
1237
|
-
</table>`;
|
|
1227
|
+
return emailTableCell(content, { padding, align });
|
|
1238
1228
|
}
|
|
1239
1229
|
}
|
|
1240
1230
|
};
|
|
@@ -1275,10 +1265,7 @@ const buttonTool = {
|
|
|
1275
1265
|
label: "Font Weight",
|
|
1276
1266
|
defaultValue: "700",
|
|
1277
1267
|
widget: "dropdown",
|
|
1278
|
-
widgetParams: { options: [
|
|
1279
|
-
{ label: "Normal", value: "400" },
|
|
1280
|
-
{ label: "Bold", value: "700" }
|
|
1281
|
-
] }
|
|
1268
|
+
widgetParams: { options: [{ label: "Normal", value: "400" }, { label: "Bold", value: "700" }] }
|
|
1282
1269
|
},
|
|
1283
1270
|
borderRadius: { label: "Border Radius", defaultValue: "4px", widget: "text" },
|
|
1284
1271
|
buttonWidth: {
|
|
@@ -1299,9 +1286,7 @@ const buttonTool = {
|
|
|
1299
1286
|
},
|
|
1300
1287
|
spacing: {
|
|
1301
1288
|
title: "Spacing",
|
|
1302
|
-
options: {
|
|
1303
|
-
containerPadding: { label: "Padding", defaultValue: "10px", widget: "padding" }
|
|
1304
|
-
}
|
|
1289
|
+
options: { containerPadding: { label: "Padding", defaultValue: "10px", widget: "padding" } }
|
|
1305
1290
|
},
|
|
1306
1291
|
general: {
|
|
1307
1292
|
title: "General",
|
|
@@ -1329,64 +1314,52 @@ const buttonTool = {
|
|
|
1329
1314
|
containerPadding: "10px"
|
|
1330
1315
|
},
|
|
1331
1316
|
renderer: {
|
|
1332
|
-
renderEditor(values
|
|
1333
|
-
const padding = values
|
|
1334
|
-
const
|
|
1335
|
-
const
|
|
1336
|
-
const fontSize = values
|
|
1337
|
-
const fontWeight = values
|
|
1338
|
-
const
|
|
1339
|
-
const
|
|
1340
|
-
const text = values
|
|
1341
|
-
const
|
|
1342
|
-
const
|
|
1343
|
-
const
|
|
1344
|
-
const
|
|
1345
|
-
const borderStyle =
|
|
1346
|
-
const widthStyle =
|
|
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};`;
|
|
1347
1332
|
return html`
|
|
1348
|
-
<div style="padding:${padding};text-align:${
|
|
1349
|
-
<a style="${widthStyle}background-color:${
|
|
1350
|
-
${text}
|
|
1351
|
-
</a>
|
|
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>
|
|
1352
1335
|
</div>
|
|
1353
1336
|
`;
|
|
1354
1337
|
},
|
|
1355
|
-
renderHtml(values
|
|
1356
|
-
const padding = values
|
|
1357
|
-
const
|
|
1358
|
-
const
|
|
1359
|
-
const fontSize = values
|
|
1360
|
-
const fontWeight = values
|
|
1361
|
-
const
|
|
1362
|
-
const
|
|
1363
|
-
const text = values
|
|
1364
|
-
const
|
|
1365
|
-
const href = values
|
|
1366
|
-
const target = values
|
|
1367
|
-
const
|
|
1368
|
-
const
|
|
1369
|
-
const borderStyle =
|
|
1370
|
-
const vml =
|
|
1371
|
-
|
|
1372
|
-
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="${href}" style="height:auto;v-text-anchor:middle;width:auto;" arcsize="${Math.round(parseInt(borderRadius) / 20 * 100)}%" stroke="f" fillcolor="${bgColor}">
|
|
1373
|
-
<w:anchorlock/>
|
|
1374
|
-
<center style="color:${textColor};font-family:arial,helvetica,sans-serif;font-size:${fontSize};font-weight:${fontWeight};">${text}</center>
|
|
1375
|
-
</v:roundrect>
|
|
1376
|
-
<![endif]-->
|
|
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}
|
|
1377
1355
|
<!--[if !mso]><!-->` : "<!--[if !mso]><!-->";
|
|
1378
|
-
const
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
${
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
</a>
|
|
1386
|
-
${vmlEnd}
|
|
1387
|
-
</div>
|
|
1388
|
-
</td></tr></tbody>
|
|
1389
|
-
</table>`;
|
|
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 });
|
|
1390
1363
|
}
|
|
1391
1364
|
}
|
|
1392
1365
|
};
|
|
@@ -1418,9 +1391,7 @@ const dividerTool = {
|
|
|
1418
1391
|
},
|
|
1419
1392
|
spacing: {
|
|
1420
1393
|
title: "Spacing",
|
|
1421
|
-
options: {
|
|
1422
|
-
containerPadding: { label: "Padding", defaultValue: "10px", widget: "padding" }
|
|
1423
|
-
}
|
|
1394
|
+
options: { containerPadding: { label: "Padding", defaultValue: "10px", widget: "padding" } }
|
|
1424
1395
|
},
|
|
1425
1396
|
general: {
|
|
1426
1397
|
title: "General",
|
|
@@ -1439,30 +1410,17 @@ const dividerTool = {
|
|
|
1439
1410
|
},
|
|
1440
1411
|
renderer: {
|
|
1441
1412
|
renderEditor(values) {
|
|
1442
|
-
const padding = values
|
|
1443
|
-
const width = values
|
|
1444
|
-
const
|
|
1445
|
-
|
|
1446
|
-
const bc = values.borderTopColor || "#cccccc";
|
|
1447
|
-
return html`
|
|
1448
|
-
<div style="padding:${padding};">
|
|
1449
|
-
<div style="border-top:${bw} ${bs} ${bc};width:${width};margin:0 auto;"></div>
|
|
1450
|
-
</div>
|
|
1451
|
-
`;
|
|
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>`;
|
|
1452
1417
|
},
|
|
1453
1418
|
renderHtml(values) {
|
|
1454
|
-
const padding = values
|
|
1455
|
-
const width = values
|
|
1456
|
-
const
|
|
1457
|
-
const
|
|
1458
|
-
|
|
1459
|
-
return `<table role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
|
1460
|
-
<tbody><tr><td style="overflow-wrap:break-word;word-break:break-word;padding:${padding};font-family:arial,helvetica,sans-serif;" align="center">
|
|
1461
|
-
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="${width}" style="border-collapse:collapse;table-layout:fixed;border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;vertical-align:top;border-top:${bw} ${bs} ${bc};-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;">
|
|
1462
|
-
<tbody><tr><td style="font-size:0;line-height:0;"> </td></tr></tbody>
|
|
1463
|
-
</table>
|
|
1464
|
-
</td></tr></tbody>
|
|
1465
|
-
</table>`;
|
|
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" });
|
|
1466
1424
|
}
|
|
1467
1425
|
}
|
|
1468
1426
|
};
|
|
@@ -1485,9 +1443,7 @@ const htmlTool = {
|
|
|
1485
1443
|
},
|
|
1486
1444
|
spacing: {
|
|
1487
1445
|
title: "Spacing",
|
|
1488
|
-
options: {
|
|
1489
|
-
containerPadding: { label: "Padding", defaultValue: "10px", widget: "text" }
|
|
1490
|
-
}
|
|
1446
|
+
options: { containerPadding: { label: "Padding", defaultValue: "10px", widget: "text" } }
|
|
1491
1447
|
},
|
|
1492
1448
|
general: {
|
|
1493
1449
|
title: "General",
|
|
@@ -1503,22 +1459,14 @@ const htmlTool = {
|
|
|
1503
1459
|
},
|
|
1504
1460
|
renderer: {
|
|
1505
1461
|
renderEditor(values) {
|
|
1506
|
-
|
|
1507
|
-
const content = values.html || "";
|
|
1508
|
-
return html`<div style="padding:${padding};">${unsafeHTML(content)}</div>`;
|
|
1462
|
+
return html`<div style="padding:${str(values, "containerPadding", "10px")};">${unsafeHTML(str(values, "html"))}</div>`;
|
|
1509
1463
|
},
|
|
1510
1464
|
renderHtml(values) {
|
|
1511
|
-
|
|
1512
|
-
const content = values.html || "";
|
|
1513
|
-
return `<table role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
|
1514
|
-
<tbody><tr><td style="overflow-wrap:break-word;word-break:break-word;padding:${padding};font-family:arial,helvetica,sans-serif;" align="left">
|
|
1515
|
-
${content}
|
|
1516
|
-
</td></tr></tbody>
|
|
1517
|
-
</table>`;
|
|
1465
|
+
return emailTableCell(str(values, "html"), { padding: str(values, "containerPadding", "10px") });
|
|
1518
1466
|
}
|
|
1519
1467
|
}
|
|
1520
1468
|
};
|
|
1521
|
-
const
|
|
1469
|
+
const DEFAULT_ICONS = [
|
|
1522
1470
|
{ name: "Facebook", url: "https://facebook.com/", icon: "f", color: "#1877F2" },
|
|
1523
1471
|
{ name: "Twitter", url: "https://twitter.com/", icon: "𝕏", color: "#000000" },
|
|
1524
1472
|
{ name: "Instagram", url: "https://instagram.com/", icon: "📷", color: "#E4405F" },
|
|
@@ -1534,26 +1482,22 @@ const socialTool = {
|
|
|
1534
1482
|
icons: {
|
|
1535
1483
|
title: "Social Icons",
|
|
1536
1484
|
options: {
|
|
1537
|
-
icons: { label: "Icons (JSON)", defaultValue: JSON.stringify(
|
|
1485
|
+
icons: { label: "Icons (JSON)", defaultValue: JSON.stringify(DEFAULT_ICONS), widget: "rich_text" },
|
|
1538
1486
|
iconSize: { label: "Icon Size", defaultValue: "32px", widget: "text" },
|
|
1539
1487
|
iconSpacing: { label: "Spacing", defaultValue: "8px", widget: "text" }
|
|
1540
1488
|
}
|
|
1541
1489
|
},
|
|
1542
1490
|
style: {
|
|
1543
1491
|
title: "Style",
|
|
1544
|
-
options: {
|
|
1545
|
-
textAlign: { label: "Align", defaultValue: "center", widget: "text" }
|
|
1546
|
-
}
|
|
1492
|
+
options: { textAlign: { label: "Align", defaultValue: "center", widget: "alignment" } }
|
|
1547
1493
|
},
|
|
1548
1494
|
spacing: {
|
|
1549
1495
|
title: "Spacing",
|
|
1550
|
-
options: {
|
|
1551
|
-
containerPadding: { label: "Padding", defaultValue: "10px", widget: "text" }
|
|
1552
|
-
}
|
|
1496
|
+
options: { containerPadding: { label: "Padding", defaultValue: "10px", widget: "padding" } }
|
|
1553
1497
|
}
|
|
1554
1498
|
},
|
|
1555
1499
|
defaultValues: {
|
|
1556
|
-
icons: JSON.stringify(
|
|
1500
|
+
icons: JSON.stringify(DEFAULT_ICONS),
|
|
1557
1501
|
iconSize: "32px",
|
|
1558
1502
|
iconSpacing: "8px",
|
|
1559
1503
|
textAlign: "center",
|
|
@@ -1561,55 +1505,34 @@ const socialTool = {
|
|
|
1561
1505
|
},
|
|
1562
1506
|
renderer: {
|
|
1563
1507
|
renderEditor(values) {
|
|
1564
|
-
const padding = values
|
|
1565
|
-
const
|
|
1566
|
-
const iconSize = values
|
|
1567
|
-
const spacing = values
|
|
1568
|
-
|
|
1569
|
-
try {
|
|
1570
|
-
icons = JSON.parse(values.icons);
|
|
1571
|
-
} catch {
|
|
1572
|
-
}
|
|
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);
|
|
1573
1513
|
return html`
|
|
1574
|
-
<div style="padding:${padding};text-align:${
|
|
1575
|
-
${icons.map(
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
`
|
|
1579
|
-
)}
|
|
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
|
+
`)}
|
|
1580
1518
|
</div>
|
|
1581
1519
|
`;
|
|
1582
1520
|
},
|
|
1583
1521
|
renderHtml(values) {
|
|
1584
|
-
const padding = values
|
|
1585
|
-
const
|
|
1586
|
-
const iconSize =
|
|
1587
|
-
const spacing = values
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
} catch {
|
|
1592
|
-
}
|
|
1593
|
-
const iconsHtml = icons.map(
|
|
1594
|
-
(s) => `<td align="center" valign="middle" style="padding:0 ${spacing};">
|
|
1595
|
-
<a href="${s.url}" target="_blank" style="text-decoration:none;">
|
|
1596
|
-
<table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr>
|
|
1597
|
-
<td width="${iconSize}" height="${iconSize}" align="center" valign="middle" style="width:${iconSize}px;height:${iconSize}px;background:${s.color};border-radius:50%;color:#ffffff;font-size:14px;font-weight:bold;font-family:arial,sans-serif;">
|
|
1598
|
-
${s.icon}
|
|
1599
|
-
</td>
|
|
1600
|
-
</tr></table>
|
|
1601
|
-
</a>
|
|
1602
|
-
</td>`
|
|
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>`
|
|
1603
1529
|
).join("\n");
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
<table role="presentation" cellpadding="0" cellspacing="0" border="0" align="${textAlign}"><tr>${iconsHtml}</tr></table>
|
|
1607
|
-
</td></tr></tbody>
|
|
1608
|
-
</table>`;
|
|
1530
|
+
const inner = `<table role="presentation" cellpadding="0" cellspacing="0" border="0" align="${align}"><tr>${cells}</tr></table>`;
|
|
1531
|
+
return emailTableCell(inner, { padding, align });
|
|
1609
1532
|
}
|
|
1610
1533
|
}
|
|
1611
1534
|
};
|
|
1612
|
-
const
|
|
1535
|
+
const DEFAULT_ITEMS = [
|
|
1613
1536
|
{ text: "Home", href: "#" },
|
|
1614
1537
|
{ text: "About", href: "#" },
|
|
1615
1538
|
{ text: "Contact", href: "#" }
|
|
@@ -1623,14 +1546,12 @@ const menuTool = {
|
|
|
1623
1546
|
options: {
|
|
1624
1547
|
menu: {
|
|
1625
1548
|
title: "Menu",
|
|
1626
|
-
options: {
|
|
1627
|
-
items: { label: "Items (JSON)", defaultValue: JSON.stringify(defaultItems), widget: "rich_text" }
|
|
1628
|
-
}
|
|
1549
|
+
options: { items: { label: "Items (JSON)", defaultValue: JSON.stringify(DEFAULT_ITEMS), widget: "rich_text" } }
|
|
1629
1550
|
},
|
|
1630
1551
|
style: {
|
|
1631
1552
|
title: "Style",
|
|
1632
1553
|
options: {
|
|
1633
|
-
textAlign: { label: "Align", defaultValue: "center", widget: "
|
|
1554
|
+
textAlign: { label: "Align", defaultValue: "center", widget: "alignment" },
|
|
1634
1555
|
fontSize: { label: "Font Size", defaultValue: "14px", widget: "text" },
|
|
1635
1556
|
color: { label: "Text Color", defaultValue: "#333333", widget: "color_picker" },
|
|
1636
1557
|
separator: { label: "Separator", defaultValue: "|", widget: "text" },
|
|
@@ -1639,13 +1560,11 @@ const menuTool = {
|
|
|
1639
1560
|
},
|
|
1640
1561
|
spacing: {
|
|
1641
1562
|
title: "Spacing",
|
|
1642
|
-
options: {
|
|
1643
|
-
containerPadding: { label: "Padding", defaultValue: "10px", widget: "text" }
|
|
1644
|
-
}
|
|
1563
|
+
options: { containerPadding: { label: "Padding", defaultValue: "10px", widget: "padding" } }
|
|
1645
1564
|
}
|
|
1646
1565
|
},
|
|
1647
1566
|
defaultValues: {
|
|
1648
|
-
items: JSON.stringify(
|
|
1567
|
+
items: JSON.stringify(DEFAULT_ITEMS),
|
|
1649
1568
|
textAlign: "center",
|
|
1650
1569
|
fontSize: "14px",
|
|
1651
1570
|
color: "#333333",
|
|
@@ -1655,47 +1574,33 @@ const menuTool = {
|
|
|
1655
1574
|
},
|
|
1656
1575
|
renderer: {
|
|
1657
1576
|
renderEditor(values) {
|
|
1658
|
-
const padding = values
|
|
1659
|
-
const
|
|
1660
|
-
const fontSize = values
|
|
1661
|
-
const color = values
|
|
1662
|
-
const sep = values
|
|
1663
|
-
const sepColor = values
|
|
1664
|
-
|
|
1665
|
-
try {
|
|
1666
|
-
items = JSON.parse(values.items);
|
|
1667
|
-
} catch {
|
|
1668
|
-
}
|
|
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);
|
|
1669
1584
|
return html`
|
|
1670
|
-
<div style="padding:${padding};text-align:${
|
|
1671
|
-
${items.map(
|
|
1672
|
-
|
|
1673
|
-
<a href=${item.href} style="color:${color};text-decoration:none;">${item.text}</a>`
|
|
1674
|
-
)}
|
|
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>`)}
|
|
1675
1588
|
</div>
|
|
1676
1589
|
`;
|
|
1677
1590
|
},
|
|
1678
1591
|
renderHtml(values) {
|
|
1679
|
-
const padding = values
|
|
1680
|
-
const
|
|
1681
|
-
const fontSize = values
|
|
1682
|
-
const color = values
|
|
1683
|
-
const sep = values
|
|
1684
|
-
const sepColor = values
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
items = JSON.parse(values.items);
|
|
1688
|
-
} catch {
|
|
1689
|
-
}
|
|
1690
|
-
const linksHtml = items.map((item, i) => {
|
|
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) => {
|
|
1691
1600
|
const prefix = i > 0 ? `<span style="color:${sepColor};padding:0 8px;">${sep}</span>` : "";
|
|
1692
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>`;
|
|
1693
1602
|
}).join("");
|
|
1694
|
-
return `<
|
|
1695
|
-
<tbody><tr><td style="overflow-wrap:break-word;word-break:break-word;padding:${padding};font-family:arial,helvetica,sans-serif;" align="${textAlign}">
|
|
1696
|
-
<div style="text-align:${textAlign};">${linksHtml}</div>
|
|
1697
|
-
</td></tr></tbody>
|
|
1698
|
-
</table>`;
|
|
1603
|
+
return emailTableCell(`<div style="text-align:${align};">${links}</div>`, { padding, align });
|
|
1699
1604
|
}
|
|
1700
1605
|
}
|
|
1701
1606
|
};
|
|
@@ -1720,15 +1625,11 @@ const videoTool = {
|
|
|
1720
1625
|
},
|
|
1721
1626
|
style: {
|
|
1722
1627
|
title: "Style",
|
|
1723
|
-
options: {
|
|
1724
|
-
textAlign: { label: "Align", defaultValue: "center", widget: "text" }
|
|
1725
|
-
}
|
|
1628
|
+
options: { textAlign: { label: "Align", defaultValue: "center", widget: "alignment" } }
|
|
1726
1629
|
},
|
|
1727
1630
|
spacing: {
|
|
1728
1631
|
title: "Spacing",
|
|
1729
|
-
options: {
|
|
1730
|
-
containerPadding: { label: "Padding", defaultValue: "10px", widget: "text" }
|
|
1731
|
-
}
|
|
1632
|
+
options: { containerPadding: { label: "Padding", defaultValue: "10px", widget: "padding" } }
|
|
1732
1633
|
}
|
|
1733
1634
|
},
|
|
1734
1635
|
defaultValues: {
|
|
@@ -1740,46 +1641,23 @@ const videoTool = {
|
|
|
1740
1641
|
},
|
|
1741
1642
|
renderer: {
|
|
1742
1643
|
renderEditor(values) {
|
|
1743
|
-
const padding = values
|
|
1744
|
-
const url = values
|
|
1745
|
-
const thumbnail = values
|
|
1746
|
-
const
|
|
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");
|
|
1747
1648
|
if (!thumbnail) {
|
|
1748
|
-
return html
|
|
1749
|
-
<div style="padding:${padding};text-align:${textAlign};">
|
|
1750
|
-
<div style="background:#0f172a;border-radius:8px;padding:40px;text-align:center;color:white;font-family:sans-serif;position:relative;">
|
|
1751
|
-
<div style="font-size:48px;opacity:0.8;">▶</div>
|
|
1752
|
-
<div style="font-size:12px;margin-top:8px;opacity:0.6;">${url || "Enter video URL"}</div>
|
|
1753
|
-
</div>
|
|
1754
|
-
</div>
|
|
1755
|
-
`;
|
|
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>`;
|
|
1756
1650
|
}
|
|
1757
|
-
return html
|
|
1758
|
-
<div style="padding:${padding};text-align:${textAlign};">
|
|
1759
|
-
<div style="position:relative;display:inline-block;max-width:100%;cursor:pointer;">
|
|
1760
|
-
<img src=${thumbnail} alt="Video thumbnail" style="display:block;max-width:100%;border-radius:4px;" />
|
|
1761
|
-
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;">
|
|
1762
|
-
<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>
|
|
1763
|
-
</div>
|
|
1764
|
-
</div>
|
|
1765
|
-
</div>
|
|
1766
|
-
`;
|
|
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>`;
|
|
1767
1652
|
},
|
|
1768
1653
|
renderHtml(values, ctx) {
|
|
1769
|
-
const padding = values
|
|
1770
|
-
const url = values
|
|
1771
|
-
const thumbnail = values
|
|
1772
|
-
const alt = values
|
|
1773
|
-
const
|
|
1774
|
-
const
|
|
1775
|
-
|
|
1776
|
-
return `<table role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
|
1777
|
-
<tbody><tr><td style="overflow-wrap:break-word;word-break:break-word;padding:${padding};font-family:arial,helvetica,sans-serif;" align="${textAlign}">
|
|
1778
|
-
<a href="${url}" target="_blank" style="text-decoration:none;">
|
|
1779
|
-
${imgTag}
|
|
1780
|
-
</a>
|
|
1781
|
-
</td></tr></tbody>
|
|
1782
|
-
</table>`;
|
|
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 });
|
|
1783
1661
|
}
|
|
1784
1662
|
}
|
|
1785
1663
|
};
|
|
@@ -1800,7 +1678,7 @@ const timerTool = {
|
|
|
1800
1678
|
style: {
|
|
1801
1679
|
title: "Style",
|
|
1802
1680
|
options: {
|
|
1803
|
-
textAlign: { label: "Align", defaultValue: "center", widget: "
|
|
1681
|
+
textAlign: { label: "Align", defaultValue: "center", widget: "alignment" },
|
|
1804
1682
|
backgroundColor: { label: "Background", defaultValue: "#1f2937", widget: "color_picker" },
|
|
1805
1683
|
textColor: { label: "Text Color", defaultValue: "#ffffff", widget: "color_picker" },
|
|
1806
1684
|
fontSize: { label: "Font Size", defaultValue: "32px", widget: "text" }
|
|
@@ -1808,9 +1686,7 @@ const timerTool = {
|
|
|
1808
1686
|
},
|
|
1809
1687
|
spacing: {
|
|
1810
1688
|
title: "Spacing",
|
|
1811
|
-
options: {
|
|
1812
|
-
containerPadding: { label: "Padding", defaultValue: "10px", widget: "text" }
|
|
1813
|
-
}
|
|
1689
|
+
options: { containerPadding: { label: "Padding", defaultValue: "10px", widget: "padding" } }
|
|
1814
1690
|
}
|
|
1815
1691
|
},
|
|
1816
1692
|
defaultValues: {
|
|
@@ -1824,39 +1700,25 @@ const timerTool = {
|
|
|
1824
1700
|
},
|
|
1825
1701
|
renderer: {
|
|
1826
1702
|
renderEditor(values) {
|
|
1827
|
-
const padding = values
|
|
1828
|
-
const bg = values
|
|
1829
|
-
const color = values
|
|
1830
|
-
const fontSize = values
|
|
1831
|
-
const
|
|
1832
|
-
return html
|
|
1833
|
-
<div style="padding:${padding};">
|
|
1834
|
-
<div style="background:${bg};color:${color};font-size:${fontSize};text-align:${textAlign};padding:20px;border-radius:4px;font-family:monospace;font-weight:bold;letter-spacing:4px;">
|
|
1835
|
-
00 : 00 : 00 : 00
|
|
1836
|
-
<div style="font-size:11px;letter-spacing:8px;opacity:0.6;margin-top:4px;">DAYS HRS MIN SEC</div>
|
|
1837
|
-
</div>
|
|
1838
|
-
</div>
|
|
1839
|
-
`;
|
|
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>`;
|
|
1840
1709
|
},
|
|
1841
1710
|
renderHtml(values) {
|
|
1842
|
-
const padding = values
|
|
1843
|
-
const bg = values
|
|
1844
|
-
const color = values
|
|
1845
|
-
const fontSize = values
|
|
1846
|
-
const
|
|
1847
|
-
|
|
1848
|
-
return
|
|
1849
|
-
<tbody><tr><td style="overflow-wrap:break-word;word-break:break-word;padding:${padding};font-family:arial,helvetica,sans-serif;" align="${textAlign}">
|
|
1850
|
-
<div style="background-color:${bg};color:${color};font-size:${fontSize};text-align:${textAlign};padding:20px;border-radius:4px;font-family:'Courier New',monospace;font-weight:bold;letter-spacing:4px;">
|
|
1851
|
-
<div>00 : 00 : 00 : 00</div>
|
|
1852
|
-
<div style="font-size:11px;letter-spacing:8px;opacity:0.6;margin-top:4px;">DAYS HRS MIN SEC</div>
|
|
1853
|
-
</div>
|
|
1854
|
-
</td></tr></tbody>
|
|
1855
|
-
</table>`;
|
|
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 });
|
|
1856
1718
|
}
|
|
1857
1719
|
}
|
|
1858
1720
|
};
|
|
1859
|
-
const
|
|
1721
|
+
const DEFAULT_DATA = [
|
|
1860
1722
|
["Header 1", "Header 2", "Header 3"],
|
|
1861
1723
|
["Cell 1", "Cell 2", "Cell 3"],
|
|
1862
1724
|
["Cell 4", "Cell 5", "Cell 6"]
|
|
@@ -1870,9 +1732,7 @@ const tableTool = {
|
|
|
1870
1732
|
options: {
|
|
1871
1733
|
table: {
|
|
1872
1734
|
title: "Table",
|
|
1873
|
-
options: {
|
|
1874
|
-
tableData: { label: "Table Data (JSON)", defaultValue: JSON.stringify(defaultTableData), widget: "rich_text" }
|
|
1875
|
-
}
|
|
1735
|
+
options: { tableData: { label: "Table Data (JSON)", defaultValue: JSON.stringify(DEFAULT_DATA), widget: "rich_text" } }
|
|
1876
1736
|
},
|
|
1877
1737
|
style: {
|
|
1878
1738
|
title: "Style",
|
|
@@ -1886,13 +1746,11 @@ const tableTool = {
|
|
|
1886
1746
|
},
|
|
1887
1747
|
spacing: {
|
|
1888
1748
|
title: "Spacing",
|
|
1889
|
-
options: {
|
|
1890
|
-
containerPadding: { label: "Padding", defaultValue: "10px", widget: "text" }
|
|
1891
|
-
}
|
|
1749
|
+
options: { containerPadding: { label: "Padding", defaultValue: "10px", widget: "padding" } }
|
|
1892
1750
|
}
|
|
1893
1751
|
},
|
|
1894
1752
|
defaultValues: {
|
|
1895
|
-
tableData: JSON.stringify(
|
|
1753
|
+
tableData: JSON.stringify(DEFAULT_DATA),
|
|
1896
1754
|
headerBg: "#f3f4f6",
|
|
1897
1755
|
headerColor: "#111827",
|
|
1898
1756
|
borderColor: "#e5e7eb",
|
|
@@ -1902,69 +1760,42 @@ const tableTool = {
|
|
|
1902
1760
|
},
|
|
1903
1761
|
renderer: {
|
|
1904
1762
|
renderEditor(values) {
|
|
1905
|
-
const padding = values
|
|
1906
|
-
const
|
|
1907
|
-
const
|
|
1908
|
-
const
|
|
1909
|
-
const
|
|
1910
|
-
const
|
|
1911
|
-
|
|
1912
|
-
try {
|
|
1913
|
-
data = JSON.parse(values.tableData);
|
|
1914
|
-
} catch {
|
|
1915
|
-
}
|
|
1916
|
-
const headerRow = data[0] || [];
|
|
1917
|
-
const bodyRows = data.slice(1);
|
|
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);
|
|
1918
1770
|
return html`
|
|
1919
1771
|
<div style="padding:${padding};overflow-x:auto;">
|
|
1920
|
-
<table style="width:100%;border-collapse:collapse;font-size:${
|
|
1921
|
-
<thead>
|
|
1922
|
-
|
|
1923
|
-
${headerRow.map((cell) => html`<th style="padding:${cellPadding};background:${headerBg};color:${headerColor};border:1px solid ${borderColor};text-align:left;font-weight:600;">${cell}</th>`)}
|
|
1924
|
-
</tr>
|
|
1925
|
-
</thead>
|
|
1926
|
-
<tbody>
|
|
1927
|
-
${bodyRows.map((row) => html`
|
|
1928
|
-
<tr>
|
|
1929
|
-
${row.map((cell) => html`<td style="padding:${cellPadding};border:1px solid ${borderColor};">${cell}</td>`)}
|
|
1930
|
-
</tr>
|
|
1931
|
-
`)}
|
|
1932
|
-
</tbody>
|
|
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>
|
|
1933
1775
|
</table>
|
|
1934
1776
|
</div>
|
|
1935
1777
|
`;
|
|
1936
1778
|
},
|
|
1937
1779
|
renderHtml(values) {
|
|
1938
|
-
const padding = values
|
|
1939
|
-
const
|
|
1940
|
-
const
|
|
1941
|
-
const
|
|
1942
|
-
const
|
|
1943
|
-
const
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
}
|
|
1948
|
-
}
|
|
1949
|
-
|
|
1950
|
-
const bodyRows = data.slice(1);
|
|
1951
|
-
const headerHtml = headerRow.map(
|
|
1952
|
-
(c) => `<th style="padding:${cellPadding};background-color:${headerBg};color:${headerColor};border:1px solid ${borderColor};text-align:left;font-weight:600;font-family:arial,helvetica,sans-serif;font-size:${fontSize};">${c}</th>`
|
|
1953
|
-
).join("");
|
|
1954
|
-
const bodyHtml = bodyRows.map(
|
|
1955
|
-
(row) => `<tr>${row.map((c) => `<td style="padding:${cellPadding};border:1px solid ${borderColor};font-family:arial,helvetica,sans-serif;font-size:${fontSize};">${c}</td>`).join("")}</tr>`
|
|
1956
|
-
).join("");
|
|
1957
|
-
return `<table role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
|
1958
|
-
<tbody><tr><td style="overflow-wrap:break-word;word-break:break-word;padding:${padding};font-family:arial,helvetica,sans-serif;" align="left">
|
|
1959
|
-
<table cellpadding="0" cellspacing="0" width="100%" border="0" style="border-collapse:collapse;">
|
|
1960
|
-
<thead><tr>${headerHtml}</tr></thead>
|
|
1961
|
-
<tbody>${bodyHtml}</tbody>
|
|
1962
|
-
</table>
|
|
1963
|
-
</td></tr></tbody>
|
|
1964
|
-
</table>`;
|
|
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 });
|
|
1965
1792
|
}
|
|
1966
1793
|
}
|
|
1967
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
|
+
];
|
|
1968
1799
|
const formTool = {
|
|
1969
1800
|
name: "form",
|
|
1970
1801
|
label: "Form",
|
|
@@ -1978,10 +1809,7 @@ const formTool = {
|
|
|
1978
1809
|
actionUrl: { label: "Action URL", defaultValue: "#", widget: "text" },
|
|
1979
1810
|
method: { label: "Method", defaultValue: "POST", widget: "text" },
|
|
1980
1811
|
submitText: { label: "Submit Text", defaultValue: "Submit", widget: "text" },
|
|
1981
|
-
fields: { label: "Fields (JSON)", defaultValue: JSON.stringify(
|
|
1982
|
-
{ label: "Name", name: "name", type: "text", placeholder: "Your name" },
|
|
1983
|
-
{ label: "Email", name: "email", type: "email", placeholder: "your@email.com" }
|
|
1984
|
-
]), widget: "rich_text" }
|
|
1812
|
+
fields: { label: "Fields (JSON)", defaultValue: JSON.stringify(DEFAULT_FIELDS), widget: "rich_text" }
|
|
1985
1813
|
}
|
|
1986
1814
|
},
|
|
1987
1815
|
style: {
|
|
@@ -1993,34 +1821,25 @@ const formTool = {
|
|
|
1993
1821
|
},
|
|
1994
1822
|
spacing: {
|
|
1995
1823
|
title: "Spacing",
|
|
1996
|
-
options: {
|
|
1997
|
-
containerPadding: { label: "Padding", defaultValue: "10px", widget: "text" }
|
|
1998
|
-
}
|
|
1824
|
+
options: { containerPadding: { label: "Padding", defaultValue: "10px", widget: "padding" } }
|
|
1999
1825
|
}
|
|
2000
1826
|
},
|
|
2001
1827
|
defaultValues: {
|
|
2002
1828
|
actionUrl: "#",
|
|
2003
1829
|
method: "POST",
|
|
2004
1830
|
submitText: "Submit",
|
|
2005
|
-
fields: JSON.stringify(
|
|
2006
|
-
{ label: "Name", name: "name", type: "text", placeholder: "Your name" },
|
|
2007
|
-
{ label: "Email", name: "email", type: "email", placeholder: "your@email.com" }
|
|
2008
|
-
]),
|
|
1831
|
+
fields: JSON.stringify(DEFAULT_FIELDS),
|
|
2009
1832
|
buttonBg: "#3b82f6",
|
|
2010
1833
|
buttonColor: "#ffffff",
|
|
2011
1834
|
containerPadding: "10px"
|
|
2012
1835
|
},
|
|
2013
1836
|
renderer: {
|
|
2014
1837
|
renderEditor(values) {
|
|
2015
|
-
const padding = values
|
|
2016
|
-
const submitText = values
|
|
2017
|
-
const
|
|
2018
|
-
const
|
|
2019
|
-
|
|
2020
|
-
try {
|
|
2021
|
-
fields = JSON.parse(values.fields);
|
|
2022
|
-
} catch {
|
|
2023
|
-
}
|
|
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);
|
|
2024
1843
|
return html`
|
|
2025
1844
|
<div style="padding:${padding};font-family:arial,sans-serif;">
|
|
2026
1845
|
${fields.map((f) => html`
|
|
@@ -2029,36 +1848,24 @@ const formTool = {
|
|
|
2029
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;" />
|
|
2030
1849
|
</div>
|
|
2031
1850
|
`)}
|
|
2032
|
-
<button style="background:${
|
|
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>
|
|
2033
1852
|
</div>
|
|
2034
1853
|
`;
|
|
2035
1854
|
},
|
|
2036
1855
|
renderHtml(values) {
|
|
2037
|
-
const padding = values
|
|
2038
|
-
const actionUrl = values
|
|
2039
|
-
const method = values
|
|
2040
|
-
const submitText = values
|
|
2041
|
-
const
|
|
2042
|
-
const
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
fields = JSON.parse(values.fields);
|
|
2046
|
-
} catch {
|
|
2047
|
-
}
|
|
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;";
|
|
2048
1864
|
const fieldsHtml = fields.map(
|
|
2049
|
-
(f) => `<div style="margin-bottom:12px;"
|
|
2050
|
-
<label style="display:block;font-size:13px;color:#374151;margin-bottom:4px;font-weight:500;font-family:arial,helvetica,sans-serif;">${f.label}</label>
|
|
2051
|
-
<input type="${f.type || "text"}" name="${f.name}" placeholder="${f.placeholder || ""}" style="width:100%;padding:8px 12px;border:1px solid #d1d5db;border-radius:4px;font-size:14px;box-sizing:border-box;font-family:arial,helvetica,sans-serif;" />
|
|
2052
|
-
</div>`
|
|
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>`
|
|
2053
1866
|
).join("");
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
<form action="${actionUrl}" method="${method}">
|
|
2057
|
-
${fieldsHtml}
|
|
2058
|
-
<button type="submit" style="background-color:${buttonBg};color:${buttonColor};border:none;padding:10px 24px;border-radius:4px;font-size:14px;font-weight:600;cursor:pointer;font-family:arial,helvetica,sans-serif;">${submitText}</button>
|
|
2059
|
-
</form>
|
|
2060
|
-
</td></tr></tbody>
|
|
2061
|
-
</table>`;
|
|
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 });
|
|
2062
1869
|
}
|
|
2063
1870
|
}
|
|
2064
1871
|
};
|
|
@@ -2328,7 +2135,7 @@ let MailEditorElement = class extends LitElement {
|
|
|
2328
2135
|
this.removeEventListener("keydown", this._handleKeydown);
|
|
2329
2136
|
}
|
|
2330
2137
|
// ----------------------------------------------------------
|
|
2331
|
-
// Public API —
|
|
2138
|
+
// Public API — public API
|
|
2332
2139
|
// ----------------------------------------------------------
|
|
2333
2140
|
loadDesign(design) {
|
|
2334
2141
|
this.store.loadDesign(design);
|
|
@@ -2435,4 +2242,4 @@ export {
|
|
|
2435
2242
|
MailEditorElement as M,
|
|
2436
2243
|
ToolRegistry as T
|
|
2437
2244
|
};
|
|
2438
|
-
//# sourceMappingURL=mail-editor-
|
|
2245
|
+
//# sourceMappingURL=mail-editor-D0FbEUZu.js.map
|