payload-plugin-newsletter 0.20.2 → 0.20.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/CHANGELOG.md +71 -0
- package/dist/admin.js +199 -1750
- package/dist/collections.cjs +1 -0
- package/dist/collections.cjs.map +1 -1
- package/dist/collections.js +1 -0
- package/dist/collections.js.map +1 -1
- package/dist/server.js +1 -0
- package/package.json +3 -2
- package/dist/admin.d.ts +0 -706
package/dist/admin.js
CHANGED
|
@@ -1,1792 +1,241 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
"use client";
|
|
3
3
|
|
|
4
|
-
// src/components/
|
|
5
|
-
import { useState,
|
|
6
|
-
import { useFormFields } from "@payloadcms/ui";
|
|
7
|
-
|
|
8
|
-
// src/components/Broadcasts/PreviewControls.tsx
|
|
4
|
+
// src/admin/components/BroadcastInlinePreview.tsx
|
|
5
|
+
import { useState, useEffect, useRef } from "react";
|
|
9
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
-
var
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
var BroadcastInlinePreview = ({
|
|
8
|
+
data,
|
|
9
|
+
field: _field,
|
|
10
|
+
path: _path,
|
|
11
|
+
schemaPath: _schemaPath,
|
|
12
|
+
..._props
|
|
15
13
|
}) => {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
alignItems: "center",
|
|
19
|
-
justifyContent: "space-between",
|
|
20
|
-
padding: "1rem",
|
|
21
|
-
background: "white",
|
|
22
|
-
borderBottom: "1px solid #e5e7eb"
|
|
23
|
-
};
|
|
24
|
-
const updateButtonStyle = {
|
|
25
|
-
padding: "0.5rem 1rem",
|
|
26
|
-
background: "#10b981",
|
|
27
|
-
color: "white",
|
|
28
|
-
border: "none",
|
|
29
|
-
borderRadius: "4px",
|
|
30
|
-
cursor: isLoading ? "not-allowed" : "pointer",
|
|
31
|
-
fontSize: "14px",
|
|
32
|
-
fontWeight: 500,
|
|
33
|
-
opacity: isLoading ? 0.6 : 1
|
|
34
|
-
};
|
|
35
|
-
const deviceSelectorStyle = {
|
|
36
|
-
display: "flex",
|
|
37
|
-
gap: "0.5rem"
|
|
38
|
-
};
|
|
39
|
-
const deviceButtonStyle = (isActive) => ({
|
|
40
|
-
display: "flex",
|
|
41
|
-
alignItems: "center",
|
|
42
|
-
gap: "0.5rem",
|
|
43
|
-
padding: "0.5rem 0.75rem",
|
|
44
|
-
background: isActive ? "#1f2937" : "white",
|
|
45
|
-
color: isActive ? "white" : "#374151",
|
|
46
|
-
border: `1px solid ${isActive ? "#1f2937" : "#e5e7eb"}`,
|
|
47
|
-
borderRadius: "4px",
|
|
48
|
-
cursor: "pointer",
|
|
49
|
-
fontSize: "14px"
|
|
50
|
-
});
|
|
51
|
-
return /* @__PURE__ */ jsxs("div", { style: controlsStyle, children: [
|
|
52
|
-
/* @__PURE__ */ jsx(
|
|
53
|
-
"button",
|
|
54
|
-
{
|
|
55
|
-
style: updateButtonStyle,
|
|
56
|
-
onClick: onUpdate,
|
|
57
|
-
disabled: isLoading,
|
|
58
|
-
children: isLoading ? "Updating..." : "Update Preview"
|
|
59
|
-
}
|
|
60
|
-
),
|
|
61
|
-
/* @__PURE__ */ jsxs("div", { style: deviceSelectorStyle, children: [
|
|
62
|
-
/* @__PURE__ */ jsxs(
|
|
63
|
-
"button",
|
|
64
|
-
{
|
|
65
|
-
style: deviceButtonStyle(device === "desktop"),
|
|
66
|
-
onClick: () => onDeviceChange("desktop"),
|
|
67
|
-
"aria-label": "Desktop view",
|
|
68
|
-
children: [
|
|
69
|
-
/* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
70
|
-
/* @__PURE__ */ jsx("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2", ry: "2" }),
|
|
71
|
-
/* @__PURE__ */ jsx("line", { x1: "8", y1: "21", x2: "16", y2: "21" }),
|
|
72
|
-
/* @__PURE__ */ jsx("line", { x1: "12", y1: "17", x2: "12", y2: "21" })
|
|
73
|
-
] }),
|
|
74
|
-
"Desktop"
|
|
75
|
-
]
|
|
76
|
-
}
|
|
77
|
-
),
|
|
78
|
-
/* @__PURE__ */ jsxs(
|
|
79
|
-
"button",
|
|
80
|
-
{
|
|
81
|
-
style: deviceButtonStyle(device === "mobile"),
|
|
82
|
-
onClick: () => onDeviceChange("mobile"),
|
|
83
|
-
"aria-label": "Mobile view",
|
|
84
|
-
children: [
|
|
85
|
-
/* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
86
|
-
/* @__PURE__ */ jsx("rect", { x: "5", y: "2", width: "14", height: "20", rx: "2", ry: "2" }),
|
|
87
|
-
/* @__PURE__ */ jsx("line", { x1: "12", y1: "18", x2: "12", y2: "18" })
|
|
88
|
-
] }),
|
|
89
|
-
"Mobile"
|
|
90
|
-
]
|
|
91
|
-
}
|
|
92
|
-
)
|
|
93
|
-
] })
|
|
94
|
-
] });
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// src/components/Broadcasts/BroadcastInlinePreview.tsx
|
|
98
|
-
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
99
|
-
var BroadcastInlinePreview = () => {
|
|
100
|
-
const [device, setDevice] = useState("desktop");
|
|
101
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
102
|
-
const [showPreview, setShowPreview] = useState(false);
|
|
103
|
-
const [previewHtml, setPreviewHtml] = useState(null);
|
|
14
|
+
const [previewHtml, setPreviewHtml] = useState("");
|
|
15
|
+
const [loading, setLoading] = useState(false);
|
|
104
16
|
const [error, setError] = useState(null);
|
|
105
|
-
const fields = useFormFields(([fields2]) => ({
|
|
106
|
-
subject: fields2["subject"]?.value,
|
|
107
|
-
preheader: fields2["contentSection.preheader"]?.value,
|
|
108
|
-
content: fields2["contentSection.content"]?.value
|
|
109
|
-
}));
|
|
110
|
-
const updatePreview = useCallback(async () => {
|
|
111
|
-
if (!fields.content) {
|
|
112
|
-
setError(new Error("Please add some content before previewing"));
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
setIsLoading(true);
|
|
116
|
-
setError(null);
|
|
117
|
-
try {
|
|
118
|
-
const response = await fetch("/api/broadcasts/preview", {
|
|
119
|
-
method: "POST",
|
|
120
|
-
headers: {
|
|
121
|
-
"Content-Type": "application/json"
|
|
122
|
-
},
|
|
123
|
-
body: JSON.stringify({
|
|
124
|
-
content: fields.content,
|
|
125
|
-
preheader: fields.preheader,
|
|
126
|
-
subject: fields.subject
|
|
127
|
-
})
|
|
128
|
-
});
|
|
129
|
-
const data = await response.json();
|
|
130
|
-
if (!response.ok || !data.success) {
|
|
131
|
-
throw new Error(data.error || "Failed to generate preview");
|
|
132
|
-
}
|
|
133
|
-
setPreviewHtml(data.preview.html);
|
|
134
|
-
setShowPreview(true);
|
|
135
|
-
} catch (err) {
|
|
136
|
-
setError(err);
|
|
137
|
-
console.error("Failed to update preview:", err);
|
|
138
|
-
} finally {
|
|
139
|
-
setIsLoading(false);
|
|
140
|
-
}
|
|
141
|
-
}, [fields]);
|
|
142
|
-
const containerStyle = {
|
|
143
|
-
border: "1px solid #e5e7eb",
|
|
144
|
-
borderRadius: "8px",
|
|
145
|
-
overflow: "hidden",
|
|
146
|
-
height: "100%",
|
|
147
|
-
display: "flex",
|
|
148
|
-
flexDirection: "column"
|
|
149
|
-
};
|
|
150
|
-
const headerStyle = {
|
|
151
|
-
display: "flex",
|
|
152
|
-
alignItems: "center",
|
|
153
|
-
justifyContent: "space-between",
|
|
154
|
-
padding: "1rem",
|
|
155
|
-
background: "#f9fafb",
|
|
156
|
-
borderBottom: "1px solid #e5e7eb"
|
|
157
|
-
};
|
|
158
|
-
const titleStyle = {
|
|
159
|
-
fontSize: "16px",
|
|
160
|
-
fontWeight: 600,
|
|
161
|
-
color: "#1f2937",
|
|
162
|
-
margin: 0
|
|
163
|
-
};
|
|
164
|
-
const previewContainerStyle = {
|
|
165
|
-
flex: 1,
|
|
166
|
-
display: "flex",
|
|
167
|
-
flexDirection: "column",
|
|
168
|
-
background: "#f3f4f6",
|
|
169
|
-
overflow: "hidden"
|
|
170
|
-
};
|
|
171
|
-
const errorStyle = {
|
|
172
|
-
padding: "2rem",
|
|
173
|
-
textAlign: "center"
|
|
174
|
-
};
|
|
175
|
-
const toggleButtonStyle = {
|
|
176
|
-
padding: "0.5rem 1rem",
|
|
177
|
-
background: showPreview ? "#ef4444" : "#3b82f6",
|
|
178
|
-
color: "white",
|
|
179
|
-
border: "none",
|
|
180
|
-
borderRadius: "4px",
|
|
181
|
-
cursor: "pointer",
|
|
182
|
-
fontSize: "14px",
|
|
183
|
-
fontWeight: 500
|
|
184
|
-
};
|
|
185
|
-
return /* @__PURE__ */ jsxs2("div", { style: containerStyle, children: [
|
|
186
|
-
/* @__PURE__ */ jsxs2("div", { style: headerStyle, children: [
|
|
187
|
-
/* @__PURE__ */ jsx2("h3", { style: titleStyle, children: "Email Preview" }),
|
|
188
|
-
/* @__PURE__ */ jsx2(
|
|
189
|
-
"button",
|
|
190
|
-
{
|
|
191
|
-
onClick: () => showPreview ? setShowPreview(false) : updatePreview(),
|
|
192
|
-
style: toggleButtonStyle,
|
|
193
|
-
disabled: isLoading,
|
|
194
|
-
children: isLoading ? "Loading..." : showPreview ? "Hide Preview" : "Show Preview"
|
|
195
|
-
}
|
|
196
|
-
)
|
|
197
|
-
] }),
|
|
198
|
-
showPreview && /* @__PURE__ */ jsx2("div", { style: previewContainerStyle, children: error ? /* @__PURE__ */ jsxs2("div", { style: errorStyle, children: [
|
|
199
|
-
/* @__PURE__ */ jsx2("p", { style: { color: "#ef4444", margin: "0 0 1rem" }, children: error.message }),
|
|
200
|
-
/* @__PURE__ */ jsx2(
|
|
201
|
-
"button",
|
|
202
|
-
{
|
|
203
|
-
onClick: updatePreview,
|
|
204
|
-
style: {
|
|
205
|
-
padding: "0.5rem 1rem",
|
|
206
|
-
background: "#3b82f6",
|
|
207
|
-
color: "white",
|
|
208
|
-
border: "none",
|
|
209
|
-
borderRadius: "4px",
|
|
210
|
-
cursor: "pointer"
|
|
211
|
-
},
|
|
212
|
-
children: "Retry"
|
|
213
|
-
}
|
|
214
|
-
)
|
|
215
|
-
] }) : previewHtml ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
216
|
-
/* @__PURE__ */ jsx2(
|
|
217
|
-
PreviewControls,
|
|
218
|
-
{
|
|
219
|
-
onUpdate: updatePreview,
|
|
220
|
-
device,
|
|
221
|
-
onDeviceChange: setDevice,
|
|
222
|
-
isLoading
|
|
223
|
-
}
|
|
224
|
-
),
|
|
225
|
-
/* @__PURE__ */ jsx2(
|
|
226
|
-
"div",
|
|
227
|
-
{
|
|
228
|
-
style: {
|
|
229
|
-
flex: 1,
|
|
230
|
-
padding: device === "mobile" ? "1rem" : "2rem",
|
|
231
|
-
display: "flex",
|
|
232
|
-
justifyContent: "center",
|
|
233
|
-
overflow: "auto"
|
|
234
|
-
},
|
|
235
|
-
children: /* @__PURE__ */ jsx2(
|
|
236
|
-
"div",
|
|
237
|
-
{
|
|
238
|
-
style: {
|
|
239
|
-
width: device === "mobile" ? "375px" : "600px",
|
|
240
|
-
maxWidth: "100%",
|
|
241
|
-
background: "white",
|
|
242
|
-
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
|
|
243
|
-
borderRadius: "8px",
|
|
244
|
-
overflow: "hidden"
|
|
245
|
-
},
|
|
246
|
-
children: /* @__PURE__ */ jsx2(
|
|
247
|
-
"iframe",
|
|
248
|
-
{
|
|
249
|
-
srcDoc: previewHtml,
|
|
250
|
-
style: {
|
|
251
|
-
width: "100%",
|
|
252
|
-
height: "100%",
|
|
253
|
-
minHeight: "600px",
|
|
254
|
-
border: "none"
|
|
255
|
-
},
|
|
256
|
-
title: "Email Preview"
|
|
257
|
-
}
|
|
258
|
-
)
|
|
259
|
-
}
|
|
260
|
-
)
|
|
261
|
-
}
|
|
262
|
-
)
|
|
263
|
-
] }) : null })
|
|
264
|
-
] });
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
// src/components/Broadcasts/StatusBadge.tsx
|
|
268
|
-
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
269
|
-
var statusConfig = {
|
|
270
|
-
["draft" /* DRAFT */]: {
|
|
271
|
-
label: "Draft",
|
|
272
|
-
color: "#6B7280",
|
|
273
|
-
// gray
|
|
274
|
-
backgroundColor: "#F3F4F6"
|
|
275
|
-
},
|
|
276
|
-
["scheduled" /* SCHEDULED */]: {
|
|
277
|
-
label: "Scheduled",
|
|
278
|
-
color: "#2563EB",
|
|
279
|
-
// blue
|
|
280
|
-
backgroundColor: "#DBEAFE"
|
|
281
|
-
},
|
|
282
|
-
["sending" /* SENDING */]: {
|
|
283
|
-
label: "Sending",
|
|
284
|
-
color: "#D97706",
|
|
285
|
-
// yellow/orange
|
|
286
|
-
backgroundColor: "#FEF3C7"
|
|
287
|
-
},
|
|
288
|
-
["sent" /* SENT */]: {
|
|
289
|
-
label: "Sent",
|
|
290
|
-
color: "#059669",
|
|
291
|
-
// green
|
|
292
|
-
backgroundColor: "#D1FAE5"
|
|
293
|
-
},
|
|
294
|
-
["failed" /* FAILED */]: {
|
|
295
|
-
label: "Failed",
|
|
296
|
-
color: "#DC2626",
|
|
297
|
-
// red
|
|
298
|
-
backgroundColor: "#FEE2E2"
|
|
299
|
-
},
|
|
300
|
-
["paused" /* PAUSED */]: {
|
|
301
|
-
label: "Paused",
|
|
302
|
-
color: "#9333EA",
|
|
303
|
-
// purple
|
|
304
|
-
backgroundColor: "#EDE9FE"
|
|
305
|
-
},
|
|
306
|
-
["canceled" /* CANCELED */]: {
|
|
307
|
-
label: "Canceled",
|
|
308
|
-
color: "#6B7280",
|
|
309
|
-
// gray
|
|
310
|
-
backgroundColor: "#F3F4F6"
|
|
311
|
-
}
|
|
312
|
-
};
|
|
313
|
-
var StatusBadge = ({ cellData }) => {
|
|
314
|
-
const status = cellData;
|
|
315
|
-
const config = statusConfig[status] || statusConfig["draft" /* DRAFT */];
|
|
316
|
-
return /* @__PURE__ */ jsx3(
|
|
317
|
-
"span",
|
|
318
|
-
{
|
|
319
|
-
style: {
|
|
320
|
-
display: "inline-flex",
|
|
321
|
-
alignItems: "center",
|
|
322
|
-
padding: "2px 10px",
|
|
323
|
-
borderRadius: "12px",
|
|
324
|
-
fontSize: "12px",
|
|
325
|
-
fontWeight: "500",
|
|
326
|
-
color: config.color,
|
|
327
|
-
backgroundColor: config.backgroundColor
|
|
328
|
-
},
|
|
329
|
-
children: config.label
|
|
330
|
-
}
|
|
331
|
-
);
|
|
332
|
-
};
|
|
333
|
-
|
|
334
|
-
// src/components/Broadcasts/EmailPreview.tsx
|
|
335
|
-
import { useState as useState2, useEffect, useRef } from "react";
|
|
336
|
-
|
|
337
|
-
// src/utils/emailSafeHtml.ts
|
|
338
|
-
import DOMPurify from "isomorphic-dompurify";
|
|
339
|
-
var EMAIL_SAFE_CONFIG = {
|
|
340
|
-
ALLOWED_TAGS: [
|
|
341
|
-
"p",
|
|
342
|
-
"br",
|
|
343
|
-
"strong",
|
|
344
|
-
"b",
|
|
345
|
-
"em",
|
|
346
|
-
"i",
|
|
347
|
-
"u",
|
|
348
|
-
"strike",
|
|
349
|
-
"s",
|
|
350
|
-
"span",
|
|
351
|
-
"a",
|
|
352
|
-
"h1",
|
|
353
|
-
"h2",
|
|
354
|
-
"h3",
|
|
355
|
-
"ul",
|
|
356
|
-
"ol",
|
|
357
|
-
"li",
|
|
358
|
-
"blockquote",
|
|
359
|
-
"hr",
|
|
360
|
-
"img",
|
|
361
|
-
"div",
|
|
362
|
-
"table",
|
|
363
|
-
"tr",
|
|
364
|
-
"td",
|
|
365
|
-
"th",
|
|
366
|
-
"tbody",
|
|
367
|
-
"thead"
|
|
368
|
-
],
|
|
369
|
-
ALLOWED_ATTR: ["href", "style", "target", "rel", "align", "src", "alt", "width", "height", "border", "cellpadding", "cellspacing"],
|
|
370
|
-
ALLOWED_STYLES: {
|
|
371
|
-
"*": [
|
|
372
|
-
"color",
|
|
373
|
-
"background-color",
|
|
374
|
-
"font-size",
|
|
375
|
-
"font-weight",
|
|
376
|
-
"font-style",
|
|
377
|
-
"text-decoration",
|
|
378
|
-
"text-align",
|
|
379
|
-
"margin",
|
|
380
|
-
"margin-top",
|
|
381
|
-
"margin-right",
|
|
382
|
-
"margin-bottom",
|
|
383
|
-
"margin-left",
|
|
384
|
-
"padding",
|
|
385
|
-
"padding-top",
|
|
386
|
-
"padding-right",
|
|
387
|
-
"padding-bottom",
|
|
388
|
-
"padding-left",
|
|
389
|
-
"line-height",
|
|
390
|
-
"border-left",
|
|
391
|
-
"border-left-width",
|
|
392
|
-
"border-left-style",
|
|
393
|
-
"border-left-color"
|
|
394
|
-
]
|
|
395
|
-
},
|
|
396
|
-
FORBID_TAGS: ["script", "style", "iframe", "object", "embed", "form", "input"],
|
|
397
|
-
FORBID_ATTR: ["class", "id", "onclick", "onload", "onerror"]
|
|
398
|
-
};
|
|
399
|
-
async function convertToEmailSafeHtml(editorState, options) {
|
|
400
|
-
if (!editorState) {
|
|
401
|
-
return "";
|
|
402
|
-
}
|
|
403
|
-
const rawHtml = await lexicalToEmailHtml(editorState, options?.mediaUrl, options?.customBlockConverter);
|
|
404
|
-
const sanitizedHtml = DOMPurify.sanitize(rawHtml, EMAIL_SAFE_CONFIG);
|
|
405
|
-
if (options?.wrapInTemplate) {
|
|
406
|
-
if (options.customWrapper) {
|
|
407
|
-
return await Promise.resolve(options.customWrapper(sanitizedHtml, {
|
|
408
|
-
preheader: options.preheader,
|
|
409
|
-
subject: options.subject
|
|
410
|
-
}));
|
|
411
|
-
}
|
|
412
|
-
return wrapInEmailTemplate(sanitizedHtml, options.preheader);
|
|
413
|
-
}
|
|
414
|
-
return sanitizedHtml;
|
|
415
|
-
}
|
|
416
|
-
async function lexicalToEmailHtml(editorState, mediaUrl, customBlockConverter) {
|
|
417
|
-
const { root } = editorState;
|
|
418
|
-
if (!root || !root.children) {
|
|
419
|
-
return "";
|
|
420
|
-
}
|
|
421
|
-
const htmlParts = await Promise.all(
|
|
422
|
-
root.children.map((node) => convertNode(node, mediaUrl, customBlockConverter))
|
|
423
|
-
);
|
|
424
|
-
return htmlParts.join("");
|
|
425
|
-
}
|
|
426
|
-
async function convertNode(node, mediaUrl, customBlockConverter) {
|
|
427
|
-
switch (node.type) {
|
|
428
|
-
case "paragraph":
|
|
429
|
-
return convertParagraph(node, mediaUrl, customBlockConverter);
|
|
430
|
-
case "heading":
|
|
431
|
-
return convertHeading(node, mediaUrl, customBlockConverter);
|
|
432
|
-
case "list":
|
|
433
|
-
return convertList(node, mediaUrl, customBlockConverter);
|
|
434
|
-
case "listitem":
|
|
435
|
-
return convertListItem(node, mediaUrl, customBlockConverter);
|
|
436
|
-
case "blockquote":
|
|
437
|
-
return convertBlockquote(node, mediaUrl, customBlockConverter);
|
|
438
|
-
case "text":
|
|
439
|
-
return convertText(node);
|
|
440
|
-
case "link":
|
|
441
|
-
return convertLink(node, mediaUrl, customBlockConverter);
|
|
442
|
-
case "linebreak":
|
|
443
|
-
return "<br>";
|
|
444
|
-
case "upload":
|
|
445
|
-
return convertUpload(node, mediaUrl);
|
|
446
|
-
case "block":
|
|
447
|
-
return await convertBlock(node, mediaUrl, customBlockConverter);
|
|
448
|
-
default:
|
|
449
|
-
if (node.children) {
|
|
450
|
-
const childParts = await Promise.all(
|
|
451
|
-
node.children.map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
452
|
-
);
|
|
453
|
-
return childParts.join("");
|
|
454
|
-
}
|
|
455
|
-
return "";
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
async function convertParagraph(node, mediaUrl, customBlockConverter) {
|
|
459
|
-
const align = getAlignment(node.format);
|
|
460
|
-
const childParts = await Promise.all(
|
|
461
|
-
(node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
462
|
-
);
|
|
463
|
-
const children = childParts.join("");
|
|
464
|
-
if (!children.trim()) {
|
|
465
|
-
return '<p class="mobile-margin-bottom-16" style="margin: 0 0 16px 0; min-height: 1em;"> </p>';
|
|
466
|
-
}
|
|
467
|
-
return `<p class="mobile-margin-bottom-16" style="margin: 0 0 16px 0; text-align: ${align}; font-size: 16px; line-height: 1.5;">${children}</p>`;
|
|
468
|
-
}
|
|
469
|
-
async function convertHeading(node, mediaUrl, customBlockConverter) {
|
|
470
|
-
const tag = node.tag || "h1";
|
|
471
|
-
const align = getAlignment(node.format);
|
|
472
|
-
const childParts = await Promise.all(
|
|
473
|
-
(node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
474
|
-
);
|
|
475
|
-
const children = childParts.join("");
|
|
476
|
-
const styles = {
|
|
477
|
-
h1: "font-size: 32px; font-weight: 700; margin: 0 0 24px 0; line-height: 1.2;",
|
|
478
|
-
h2: "font-size: 24px; font-weight: 600; margin: 0 0 16px 0; line-height: 1.3;",
|
|
479
|
-
h3: "font-size: 20px; font-weight: 600; margin: 0 0 12px 0; line-height: 1.4;"
|
|
480
|
-
};
|
|
481
|
-
const mobileClasses = {
|
|
482
|
-
h1: "mobile-font-size-24",
|
|
483
|
-
h2: "mobile-font-size-20",
|
|
484
|
-
h3: "mobile-font-size-16"
|
|
485
|
-
};
|
|
486
|
-
const style = `${styles[tag] || styles.h3} text-align: ${align};`;
|
|
487
|
-
const mobileClass = mobileClasses[tag] || mobileClasses.h3;
|
|
488
|
-
return `<${tag} class="${mobileClass}" style="${style}">${children}</${tag}>`;
|
|
489
|
-
}
|
|
490
|
-
async function convertList(node, mediaUrl, customBlockConverter) {
|
|
491
|
-
const tag = node.listType === "number" ? "ol" : "ul";
|
|
492
|
-
const childParts = await Promise.all(
|
|
493
|
-
(node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
494
|
-
);
|
|
495
|
-
const children = childParts.join("");
|
|
496
|
-
const style = tag === "ul" ? "margin: 0 0 16px 0; padding-left: 24px; list-style-type: disc; font-size: 16px; line-height: 1.5;" : "margin: 0 0 16px 0; padding-left: 24px; list-style-type: decimal; font-size: 16px; line-height: 1.5;";
|
|
497
|
-
return `<${tag} class="mobile-margin-bottom-16" style="${style}">${children}</${tag}>`;
|
|
498
|
-
}
|
|
499
|
-
async function convertListItem(node, mediaUrl, customBlockConverter) {
|
|
500
|
-
const childParts = await Promise.all(
|
|
501
|
-
(node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
502
|
-
);
|
|
503
|
-
const children = childParts.join("");
|
|
504
|
-
return `<li style="margin: 0 0 8px 0;">${children}</li>`;
|
|
505
|
-
}
|
|
506
|
-
async function convertBlockquote(node, mediaUrl, customBlockConverter) {
|
|
507
|
-
const childParts = await Promise.all(
|
|
508
|
-
(node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
509
|
-
);
|
|
510
|
-
const children = childParts.join("");
|
|
511
|
-
const style = "margin: 0 0 16px 0; padding-left: 16px; border-left: 4px solid #e5e7eb; color: #6b7280;";
|
|
512
|
-
return `<blockquote style="${style}">${children}</blockquote>`;
|
|
513
|
-
}
|
|
514
|
-
function convertText(node) {
|
|
515
|
-
let text = escapeHtml(node.text || "");
|
|
516
|
-
if (node.format & 1) {
|
|
517
|
-
text = `<strong>${text}</strong>`;
|
|
518
|
-
}
|
|
519
|
-
if (node.format & 2) {
|
|
520
|
-
text = `<em>${text}</em>`;
|
|
521
|
-
}
|
|
522
|
-
if (node.format & 8) {
|
|
523
|
-
text = `<u>${text}</u>`;
|
|
524
|
-
}
|
|
525
|
-
if (node.format & 4) {
|
|
526
|
-
text = `<strike>${text}</strike>`;
|
|
527
|
-
}
|
|
528
|
-
return text;
|
|
529
|
-
}
|
|
530
|
-
async function convertLink(node, mediaUrl, customBlockConverter) {
|
|
531
|
-
const childParts = await Promise.all(
|
|
532
|
-
(node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
533
|
-
);
|
|
534
|
-
const children = childParts.join("");
|
|
535
|
-
const url = node.fields?.url || "#";
|
|
536
|
-
const newTab = node.fields?.newTab ?? false;
|
|
537
|
-
const targetAttr = newTab ? ' target="_blank"' : "";
|
|
538
|
-
const relAttr = newTab ? ' rel="noopener noreferrer"' : "";
|
|
539
|
-
return `<a href="${escapeHtml(url)}"${targetAttr}${relAttr} style="color: #2563eb; text-decoration: underline;">${children}</a>`;
|
|
540
|
-
}
|
|
541
|
-
function convertUpload(node, mediaUrl) {
|
|
542
|
-
const upload = node.value;
|
|
543
|
-
if (!upload) return "";
|
|
544
|
-
let src = "";
|
|
545
|
-
if (typeof upload === "string") {
|
|
546
|
-
src = upload;
|
|
547
|
-
} else if (upload.url) {
|
|
548
|
-
src = upload.url;
|
|
549
|
-
} else if (upload.filename && mediaUrl) {
|
|
550
|
-
src = `${mediaUrl}/${upload.filename}`;
|
|
551
|
-
}
|
|
552
|
-
const alt = node.fields?.altText || upload.alt || "";
|
|
553
|
-
const caption = node.fields?.caption || "";
|
|
554
|
-
const imgHtml = `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}" class="mobile-width-100" style="max-width: 100%; height: auto; display: block; margin: 0 auto; border-radius: 6px;" />`;
|
|
555
|
-
if (caption) {
|
|
556
|
-
return `
|
|
557
|
-
<div style="margin: 0 0 16px 0; text-align: center;" class="mobile-margin-bottom-16">
|
|
558
|
-
${imgHtml}
|
|
559
|
-
<p style="margin: 8px 0 0 0; font-size: 14px; color: #6b7280; font-style: italic; text-align: center;" class="mobile-font-size-14">${escapeHtml(caption)}</p>
|
|
560
|
-
</div>
|
|
561
|
-
`;
|
|
562
|
-
}
|
|
563
|
-
return `<div style="margin: 0 0 16px 0; text-align: center;" class="mobile-margin-bottom-16">${imgHtml}</div>`;
|
|
564
|
-
}
|
|
565
|
-
async function convertBlock(node, mediaUrl, customBlockConverter) {
|
|
566
|
-
const blockType = node.fields?.blockName || node.blockName;
|
|
567
|
-
if (customBlockConverter) {
|
|
568
|
-
try {
|
|
569
|
-
const customHtml = await customBlockConverter(node, mediaUrl);
|
|
570
|
-
if (customHtml) {
|
|
571
|
-
return customHtml;
|
|
572
|
-
}
|
|
573
|
-
} catch (error) {
|
|
574
|
-
console.error(`Custom block converter error for ${blockType}:`, error);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
switch (blockType) {
|
|
578
|
-
case "button":
|
|
579
|
-
return convertButtonBlock(node.fields);
|
|
580
|
-
case "divider":
|
|
581
|
-
return convertDividerBlock(node.fields);
|
|
582
|
-
default:
|
|
583
|
-
if (node.children) {
|
|
584
|
-
const childParts = await Promise.all(
|
|
585
|
-
node.children.map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
586
|
-
);
|
|
587
|
-
return childParts.join("");
|
|
588
|
-
}
|
|
589
|
-
return "";
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
function convertButtonBlock(fields) {
|
|
593
|
-
const text = fields?.text || "Click here";
|
|
594
|
-
const url = fields?.url || "#";
|
|
595
|
-
const style = fields?.style || "primary";
|
|
596
|
-
const styles = {
|
|
597
|
-
primary: "background-color: #2563eb; color: #ffffff; border: 2px solid #2563eb;",
|
|
598
|
-
secondary: "background-color: #6b7280; color: #ffffff; border: 2px solid #6b7280;",
|
|
599
|
-
outline: "background-color: transparent; color: #2563eb; border: 2px solid #2563eb;"
|
|
600
|
-
};
|
|
601
|
-
const buttonStyle = `${styles[style] || styles.primary} display: inline-block; padding: 12px 24px; font-size: 16px; font-weight: 600; text-decoration: none; border-radius: 6px; text-align: center;`;
|
|
602
|
-
return `
|
|
603
|
-
<div style="margin: 0 0 16px 0; text-align: center;">
|
|
604
|
-
<a href="${escapeHtml(url)}" target="_blank" rel="noopener noreferrer" style="${buttonStyle}">${escapeHtml(text)}</a>
|
|
605
|
-
</div>
|
|
606
|
-
`;
|
|
607
|
-
}
|
|
608
|
-
function convertDividerBlock(fields) {
|
|
609
|
-
const style = fields?.style || "solid";
|
|
610
|
-
const styles = {
|
|
611
|
-
solid: "border-top: 1px solid #e5e7eb;",
|
|
612
|
-
dashed: "border-top: 1px dashed #e5e7eb;",
|
|
613
|
-
dotted: "border-top: 1px dotted #e5e7eb;"
|
|
614
|
-
};
|
|
615
|
-
return `<hr style="${styles[style] || styles.solid} margin: 24px 0; border-bottom: none; border-left: none; border-right: none;" />`;
|
|
616
|
-
}
|
|
617
|
-
function getAlignment(format) {
|
|
618
|
-
if (!format) return "left";
|
|
619
|
-
if (format & 2) return "center";
|
|
620
|
-
if (format & 3) return "right";
|
|
621
|
-
if (format & 4) return "justify";
|
|
622
|
-
return "left";
|
|
623
|
-
}
|
|
624
|
-
function escapeHtml(text) {
|
|
625
|
-
const map = {
|
|
626
|
-
"&": "&",
|
|
627
|
-
"<": "<",
|
|
628
|
-
">": ">",
|
|
629
|
-
'"': """,
|
|
630
|
-
"'": "'"
|
|
631
|
-
};
|
|
632
|
-
return text.replace(/[&<>"']/g, (m) => map[m]);
|
|
633
|
-
}
|
|
634
|
-
function wrapInEmailTemplate(content, preheader) {
|
|
635
|
-
return `<!DOCTYPE html>
|
|
636
|
-
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
|
637
|
-
<head>
|
|
638
|
-
<meta charset="UTF-8">
|
|
639
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
640
|
-
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
641
|
-
<meta name="x-apple-disable-message-reformatting">
|
|
642
|
-
<title>Newsletter</title>
|
|
643
|
-
|
|
644
|
-
<!--[if mso]>
|
|
645
|
-
<noscript>
|
|
646
|
-
<xml>
|
|
647
|
-
<o:OfficeDocumentSettings>
|
|
648
|
-
<o:PixelsPerInch>96</o:PixelsPerInch>
|
|
649
|
-
</o:OfficeDocumentSettings>
|
|
650
|
-
</xml>
|
|
651
|
-
</noscript>
|
|
652
|
-
<![endif]-->
|
|
653
|
-
|
|
654
|
-
<style>
|
|
655
|
-
/* Reset and base styles */
|
|
656
|
-
* {
|
|
657
|
-
-webkit-text-size-adjust: 100%;
|
|
658
|
-
-ms-text-size-adjust: 100%;
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
body {
|
|
662
|
-
margin: 0 !important;
|
|
663
|
-
padding: 0 !important;
|
|
664
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
|
|
665
|
-
font-size: 16px;
|
|
666
|
-
line-height: 1.5;
|
|
667
|
-
color: #1A1A1A;
|
|
668
|
-
background-color: #f8f9fa;
|
|
669
|
-
-webkit-font-smoothing: antialiased;
|
|
670
|
-
-moz-osx-font-smoothing: grayscale;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
table {
|
|
674
|
-
border-spacing: 0 !important;
|
|
675
|
-
border-collapse: collapse !important;
|
|
676
|
-
table-layout: fixed !important;
|
|
677
|
-
margin: 0 auto !important;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
table table table {
|
|
681
|
-
table-layout: auto;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
img {
|
|
685
|
-
-ms-interpolation-mode: bicubic;
|
|
686
|
-
max-width: 100%;
|
|
687
|
-
height: auto;
|
|
688
|
-
border: 0;
|
|
689
|
-
outline: none;
|
|
690
|
-
text-decoration: none;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
/* Responsive styles */
|
|
694
|
-
@media only screen and (max-width: 640px) {
|
|
695
|
-
.mobile-hide {
|
|
696
|
-
display: none !important;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
.mobile-center {
|
|
700
|
-
text-align: center !important;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
.mobile-width-100 {
|
|
704
|
-
width: 100% !important;
|
|
705
|
-
max-width: 100% !important;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
.mobile-padding {
|
|
709
|
-
padding: 20px !important;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
.mobile-padding-sm {
|
|
713
|
-
padding: 16px !important;
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
.mobile-font-size-14 {
|
|
717
|
-
font-size: 14px !important;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
.mobile-font-size-16 {
|
|
721
|
-
font-size: 16px !important;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
.mobile-font-size-20 {
|
|
725
|
-
font-size: 20px !important;
|
|
726
|
-
line-height: 1.3 !important;
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
.mobile-font-size-24 {
|
|
730
|
-
font-size: 24px !important;
|
|
731
|
-
line-height: 1.2 !important;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
/* Stack sections on mobile */
|
|
735
|
-
.mobile-stack {
|
|
736
|
-
display: block !important;
|
|
737
|
-
width: 100% !important;
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
/* Mobile-specific spacing */
|
|
741
|
-
.mobile-margin-bottom-16 {
|
|
742
|
-
margin-bottom: 16px !important;
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
.mobile-margin-bottom-20 {
|
|
746
|
-
margin-bottom: 20px !important;
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
/* Dark mode support */
|
|
751
|
-
@media (prefers-color-scheme: dark) {
|
|
752
|
-
.dark-mode-bg {
|
|
753
|
-
background-color: #1a1a1a !important;
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
.dark-mode-text {
|
|
757
|
-
color: #ffffff !important;
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
.dark-mode-border {
|
|
761
|
-
border-color: #333333 !important;
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
/* Outlook-specific fixes */
|
|
766
|
-
<!--[if mso]>
|
|
767
|
-
<style>
|
|
768
|
-
table {
|
|
769
|
-
border-collapse: collapse;
|
|
770
|
-
border-spacing: 0;
|
|
771
|
-
border: none;
|
|
772
|
-
margin: 0;
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
div, p {
|
|
776
|
-
margin: 0;
|
|
777
|
-
}
|
|
778
|
-
</style>
|
|
779
|
-
<![endif]-->
|
|
780
|
-
</style>
|
|
781
|
-
</head>
|
|
782
|
-
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 16px; line-height: 1.5; color: #1A1A1A; background-color: #f8f9fa;">
|
|
783
|
-
${preheader ? `
|
|
784
|
-
<!-- Preheader text -->
|
|
785
|
-
<div style="display: none; max-height: 0; overflow: hidden; font-size: 1px; line-height: 1px; color: transparent;">
|
|
786
|
-
${escapeHtml(preheader)}
|
|
787
|
-
</div>
|
|
788
|
-
` : ""}
|
|
789
|
-
|
|
790
|
-
<!-- Main container -->
|
|
791
|
-
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin: 0; padding: 0; background-color: #f8f9fa;">
|
|
792
|
-
<tr>
|
|
793
|
-
<td align="center" style="padding: 20px 10px;">
|
|
794
|
-
<!-- Email wrapper -->
|
|
795
|
-
<table role="presentation" cellpadding="0" cellspacing="0" width="600" class="mobile-width-100" style="margin: 0 auto; max-width: 600px;">
|
|
796
|
-
<tr>
|
|
797
|
-
<td class="mobile-padding" style="padding: 0;">
|
|
798
|
-
<!-- Content area with light background -->
|
|
799
|
-
<div style="background-color: #ffffff; padding: 40px 30px; border-radius: 8px;" class="mobile-padding">
|
|
800
|
-
${content}
|
|
801
|
-
</div>
|
|
802
|
-
</td>
|
|
803
|
-
</tr>
|
|
804
|
-
</table>
|
|
805
|
-
</td>
|
|
806
|
-
</tr>
|
|
807
|
-
</table>
|
|
808
|
-
</body>
|
|
809
|
-
</html>`;
|
|
810
|
-
}
|
|
811
|
-
function replacePersonalizationTags(html, sampleData) {
|
|
812
|
-
return html.replace(/\{\{([^}]+)\}\}/g, (match, tag) => {
|
|
813
|
-
const trimmedTag = tag.trim();
|
|
814
|
-
return sampleData[trimmedTag] || match;
|
|
815
|
-
});
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
// src/utils/validateEmailHtml.ts
|
|
819
|
-
function validateEmailHtml(html) {
|
|
820
|
-
const warnings = [];
|
|
821
|
-
const errors = [];
|
|
822
|
-
const sizeInBytes = new Blob([html]).size;
|
|
823
|
-
if (sizeInBytes > 102400) {
|
|
824
|
-
warnings.push(`Email size (${Math.round(sizeInBytes / 1024)}KB) exceeds Gmail's 102KB limit - email may be clipped`);
|
|
825
|
-
}
|
|
826
|
-
if (html.includes("position:") && (html.includes("position: absolute") || html.includes("position: fixed"))) {
|
|
827
|
-
errors.push("Absolute/fixed positioning is not supported in most email clients");
|
|
828
|
-
}
|
|
829
|
-
if (html.includes("display: flex") || html.includes("display: grid")) {
|
|
830
|
-
errors.push("Flexbox and Grid layouts are not supported in many email clients");
|
|
831
|
-
}
|
|
832
|
-
if (html.includes("@media")) {
|
|
833
|
-
warnings.push("Media queries may not work in all email clients");
|
|
834
|
-
}
|
|
835
|
-
const hasJavaScript = html.includes("<script") || html.includes("onclick") || html.includes("onload") || html.includes("javascript:");
|
|
836
|
-
if (hasJavaScript) {
|
|
837
|
-
errors.push("JavaScript is not supported in email and will be stripped by email clients");
|
|
838
|
-
}
|
|
839
|
-
const hasExternalStyles = html.includes("<link") && html.includes("stylesheet");
|
|
840
|
-
if (hasExternalStyles) {
|
|
841
|
-
errors.push("External stylesheets are not supported - use inline styles only");
|
|
842
|
-
}
|
|
843
|
-
if (html.includes("<form") || html.includes("<input") || html.includes("<button")) {
|
|
844
|
-
errors.push("Forms and form elements are not reliably supported in email");
|
|
845
|
-
}
|
|
846
|
-
const unsupportedTags = [
|
|
847
|
-
"video",
|
|
848
|
-
"audio",
|
|
849
|
-
"iframe",
|
|
850
|
-
"embed",
|
|
851
|
-
"object",
|
|
852
|
-
"canvas",
|
|
853
|
-
"svg"
|
|
854
|
-
];
|
|
855
|
-
for (const tag of unsupportedTags) {
|
|
856
|
-
if (html.includes(`<${tag}`)) {
|
|
857
|
-
errors.push(`<${tag}> tags are not supported in email`);
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
const imageCount = (html.match(/<img/g) || []).length;
|
|
861
|
-
const linkCount = (html.match(/<a/g) || []).length;
|
|
862
|
-
if (imageCount > 20) {
|
|
863
|
-
warnings.push(`High number of images (${imageCount}) may affect email performance`);
|
|
864
|
-
}
|
|
865
|
-
const imagesWithoutAlt = (html.match(/<img(?![^>]*\balt\s*=)[^>]*>/g) || []).length;
|
|
866
|
-
if (imagesWithoutAlt > 0) {
|
|
867
|
-
warnings.push(`${imagesWithoutAlt} image(s) missing alt text - important for accessibility`);
|
|
868
|
-
}
|
|
869
|
-
const linksWithoutTarget = (html.match(/<a(?![^>]*\btarget\s*=)[^>]*>/g) || []).length;
|
|
870
|
-
if (linksWithoutTarget > 0) {
|
|
871
|
-
warnings.push(`${linksWithoutTarget} link(s) missing target="_blank" attribute`);
|
|
872
|
-
}
|
|
873
|
-
if (html.includes("margin: auto") || html.includes("margin:auto")) {
|
|
874
|
-
warnings.push('margin: auto is not supported in Outlook - use align="center" or tables for centering');
|
|
875
|
-
}
|
|
876
|
-
if (html.includes("background-image")) {
|
|
877
|
-
warnings.push("Background images are not reliably supported - consider using <img> tags instead");
|
|
878
|
-
}
|
|
879
|
-
if (html.match(/\d+\s*(rem|em)/)) {
|
|
880
|
-
warnings.push("rem/em units may render inconsistently - use px for reliable sizing");
|
|
881
|
-
}
|
|
882
|
-
if (html.match(/margin[^:]*:\s*-\d+/)) {
|
|
883
|
-
errors.push("Negative margins are not supported in many email clients");
|
|
884
|
-
}
|
|
885
|
-
const personalizationTags = html.match(/\{\{([^}]+)\}\}/g) || [];
|
|
886
|
-
const validTags = ["subscriber.name", "subscriber.email", "subscriber.firstName", "subscriber.lastName"];
|
|
887
|
-
for (const tag of personalizationTags) {
|
|
888
|
-
const tagContent = tag.replace(/[{}]/g, "").trim();
|
|
889
|
-
if (!validTags.includes(tagContent)) {
|
|
890
|
-
warnings.push(`Unknown personalization tag: ${tag}`);
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
return {
|
|
894
|
-
valid: errors.length === 0,
|
|
895
|
-
warnings,
|
|
896
|
-
errors,
|
|
897
|
-
stats: {
|
|
898
|
-
sizeInBytes,
|
|
899
|
-
imageCount,
|
|
900
|
-
linkCount,
|
|
901
|
-
hasExternalStyles,
|
|
902
|
-
hasJavaScript
|
|
903
|
-
}
|
|
904
|
-
};
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
// src/contexts/PluginConfigContext.tsx
|
|
908
|
-
import { createContext, useContext } from "react";
|
|
909
|
-
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
910
|
-
var PluginConfigContext = createContext(null);
|
|
911
|
-
var usePluginConfigOptional = () => {
|
|
912
|
-
return useContext(PluginConfigContext);
|
|
913
|
-
};
|
|
914
|
-
|
|
915
|
-
// src/components/Broadcasts/EmailPreview.tsx
|
|
916
|
-
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
917
|
-
var SAMPLE_DATA = {
|
|
918
|
-
"subscriber.name": "John Doe",
|
|
919
|
-
"subscriber.firstName": "John",
|
|
920
|
-
"subscriber.lastName": "Doe",
|
|
921
|
-
"subscriber.email": "john.doe@example.com"
|
|
922
|
-
};
|
|
923
|
-
var VIEWPORT_SIZES = {
|
|
924
|
-
desktop: { width: 600, scale: 1 },
|
|
925
|
-
mobile: { width: 320, scale: 0.8 }
|
|
926
|
-
};
|
|
927
|
-
var EmailPreview = ({
|
|
928
|
-
content,
|
|
929
|
-
subject,
|
|
930
|
-
preheader,
|
|
931
|
-
mode = "desktop",
|
|
932
|
-
onValidation,
|
|
933
|
-
pluginConfig: propPluginConfig
|
|
934
|
-
}) => {
|
|
935
|
-
const contextPluginConfig = usePluginConfigOptional();
|
|
936
|
-
const pluginConfig = propPluginConfig || contextPluginConfig;
|
|
937
|
-
const [html, setHtml] = useState2("");
|
|
938
|
-
const [loading, setLoading] = useState2(false);
|
|
939
|
-
const [validationResult, setValidationResult] = useState2(null);
|
|
940
17
|
const iframeRef = useRef(null);
|
|
941
18
|
useEffect(() => {
|
|
942
|
-
const
|
|
943
|
-
if (!content) {
|
|
944
|
-
|
|
19
|
+
const fetchPreview = async () => {
|
|
20
|
+
if (!data?.content || !data.content.root?.children?.length) {
|
|
21
|
+
setPreviewHtml("");
|
|
945
22
|
return;
|
|
946
23
|
}
|
|
947
24
|
setLoading(true);
|
|
25
|
+
setError(null);
|
|
948
26
|
try {
|
|
949
|
-
const
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
to: SAMPLE_DATA["subscriber.email"]
|
|
27
|
+
const response = await fetch("/api/broadcasts/preview", {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: {
|
|
30
|
+
"Content-Type": "application/json"
|
|
31
|
+
},
|
|
32
|
+
credentials: "same-origin",
|
|
33
|
+
// Include cookies for auth
|
|
34
|
+
body: JSON.stringify({
|
|
35
|
+
content: data.content,
|
|
36
|
+
subject: data.subject || "",
|
|
37
|
+
preheader: data.preheader || ""
|
|
38
|
+
})
|
|
962
39
|
});
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
const errorData = await response.json().catch(() => ({}));
|
|
42
|
+
throw new Error(errorData.error || `Failed to fetch preview: ${response.status}`);
|
|
43
|
+
}
|
|
44
|
+
const result = await response.json();
|
|
45
|
+
if (result.success && result.html) {
|
|
46
|
+
setPreviewHtml(result.html);
|
|
47
|
+
} else {
|
|
48
|
+
throw new Error(result.error || "No preview HTML returned");
|
|
49
|
+
}
|
|
50
|
+
} catch (err) {
|
|
51
|
+
console.error("Preview fetch error:", err);
|
|
52
|
+
setError(err instanceof Error ? err.message : "Failed to load preview");
|
|
970
53
|
} finally {
|
|
971
54
|
setLoading(false);
|
|
972
55
|
}
|
|
973
56
|
};
|
|
974
|
-
|
|
975
|
-
|
|
57
|
+
const timeoutId = setTimeout(fetchPreview, 500);
|
|
58
|
+
return () => clearTimeout(timeoutId);
|
|
59
|
+
}, [data?.content, data?.subject, data?.preheader]);
|
|
976
60
|
useEffect(() => {
|
|
977
|
-
if (iframeRef.current &&
|
|
978
|
-
const
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
61
|
+
if (iframeRef.current && previewHtml) {
|
|
62
|
+
const iframe = iframeRef.current;
|
|
63
|
+
const resizeIframe = () => {
|
|
64
|
+
try {
|
|
65
|
+
const body = iframe.contentDocument?.body;
|
|
66
|
+
if (body) {
|
|
67
|
+
iframe.style.height = `${body.scrollHeight + 40}px`;
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
iframe.style.height = "600px";
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
iframe.onload = resizeIframe;
|
|
74
|
+
const contentWindow = iframe.contentWindow;
|
|
75
|
+
if (contentWindow) {
|
|
76
|
+
contentWindow.addEventListener("resize", resizeIframe);
|
|
77
|
+
return () => contentWindow.removeEventListener("resize", resizeIframe);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}, [previewHtml]);
|
|
81
|
+
if (!data?.content) {
|
|
82
|
+
return /* @__PURE__ */ jsx("div", { style: {
|
|
83
|
+
padding: "2rem",
|
|
84
|
+
textAlign: "center",
|
|
85
|
+
color: "#666",
|
|
86
|
+
border: "1px dashed #ddd",
|
|
87
|
+
borderRadius: "4px",
|
|
88
|
+
backgroundColor: "#f9f9f9"
|
|
89
|
+
}, children: /* @__PURE__ */ jsx("p", { children: "Start adding content to see the email preview" }) });
|
|
90
|
+
}
|
|
91
|
+
if (loading) {
|
|
92
|
+
return /* @__PURE__ */ jsx("div", { style: {
|
|
93
|
+
padding: "2rem",
|
|
94
|
+
textAlign: "center",
|
|
95
|
+
border: "1px solid #e0e0e0",
|
|
96
|
+
borderRadius: "4px",
|
|
97
|
+
backgroundColor: "#f9f9f9"
|
|
98
|
+
}, children: /* @__PURE__ */ jsx("div", { style: { display: "inline-block" }, children: "Loading preview..." }) });
|
|
99
|
+
}
|
|
100
|
+
if (error) {
|
|
101
|
+
return /* @__PURE__ */ jsxs("div", { style: {
|
|
102
|
+
padding: "1rem",
|
|
103
|
+
border: "1px solid #fee",
|
|
104
|
+
borderRadius: "4px",
|
|
105
|
+
backgroundColor: "#fef2f2",
|
|
106
|
+
color: "#dc2626"
|
|
107
|
+
}, children: [
|
|
108
|
+
/* @__PURE__ */ jsx("strong", { children: "Preview Error:" }),
|
|
109
|
+
" ",
|
|
110
|
+
error
|
|
111
|
+
] });
|
|
112
|
+
}
|
|
113
|
+
return /* @__PURE__ */ jsxs("div", { className: "broadcast-preview", style: { marginTop: "1rem" }, children: [
|
|
114
|
+
/* @__PURE__ */ jsxs("div", { style: {
|
|
115
|
+
marginBottom: "0.5rem",
|
|
1008
116
|
display: "flex",
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
117
|
+
justifyContent: "space-between",
|
|
118
|
+
alignItems: "center"
|
|
119
|
+
}, children: [
|
|
120
|
+
/* @__PURE__ */ jsx("h3", { style: { margin: 0, fontSize: "1rem", fontWeight: "600" }, children: "Email Preview" }),
|
|
121
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: "0.875rem", color: "#666" }, children: data.subject && /* @__PURE__ */ jsxs("span", { children: [
|
|
122
|
+
"Subject: ",
|
|
123
|
+
data.subject
|
|
124
|
+
] }) })
|
|
125
|
+
] }),
|
|
126
|
+
/* @__PURE__ */ jsx("div", { style: {
|
|
127
|
+
border: "1px solid #e0e0e0",
|
|
128
|
+
borderRadius: "4px",
|
|
1018
129
|
overflow: "hidden",
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
}, children: /* @__PURE__ */ jsx5(
|
|
130
|
+
backgroundColor: "#fff"
|
|
131
|
+
}, children: /* @__PURE__ */ jsx(
|
|
1022
132
|
"iframe",
|
|
1023
133
|
{
|
|
1024
134
|
ref: iframeRef,
|
|
1025
|
-
|
|
135
|
+
srcDoc: previewHtml,
|
|
1026
136
|
style: {
|
|
1027
|
-
width:
|
|
1028
|
-
|
|
137
|
+
width: "100%",
|
|
138
|
+
minHeight: "400px",
|
|
1029
139
|
border: "none",
|
|
1030
140
|
display: "block"
|
|
1031
141
|
},
|
|
142
|
+
title: "Email Preview",
|
|
1032
143
|
sandbox: "allow-same-origin"
|
|
1033
144
|
}
|
|
1034
|
-
) })
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
gap: "24px"
|
|
1042
|
-
}, children: [
|
|
1043
|
-
/* @__PURE__ */ jsxs3("span", { children: [
|
|
1044
|
-
"Size: ",
|
|
1045
|
-
Math.round(validationResult.stats.sizeInBytes / 1024),
|
|
1046
|
-
"KB"
|
|
1047
|
-
] }),
|
|
1048
|
-
/* @__PURE__ */ jsxs3("span", { children: [
|
|
1049
|
-
"Links: ",
|
|
1050
|
-
validationResult.stats.linkCount
|
|
1051
|
-
] }),
|
|
1052
|
-
/* @__PURE__ */ jsxs3("span", { children: [
|
|
1053
|
-
"Images: ",
|
|
1054
|
-
validationResult.stats.imageCount
|
|
1055
|
-
] }),
|
|
1056
|
-
/* @__PURE__ */ jsxs3("span", { children: [
|
|
1057
|
-
"Viewport: ",
|
|
1058
|
-
mode === "desktop" ? "600px" : "320px"
|
|
1059
|
-
] })
|
|
1060
|
-
] })
|
|
145
|
+
) }),
|
|
146
|
+
/* @__PURE__ */ jsx("div", { style: {
|
|
147
|
+
marginTop: "0.5rem",
|
|
148
|
+
fontSize: "0.75rem",
|
|
149
|
+
color: "#666",
|
|
150
|
+
textAlign: "center"
|
|
151
|
+
}, children: "This preview shows how your email will appear when sent" })
|
|
1061
152
|
] });
|
|
1062
153
|
};
|
|
1063
|
-
function addEmailHeader(html, headers) {
|
|
1064
|
-
const headerHtml = `
|
|
1065
|
-
<div style="background-color: #f9fafb; border-bottom: 1px solid #e5e7eb; padding: 16px; font-family: monospace; font-size: 13px;">
|
|
1066
|
-
<div style="margin-bottom: 8px;"><strong>Subject:</strong> ${escapeHtml2(headers.subject)}</div>
|
|
1067
|
-
<div style="margin-bottom: 8px;"><strong>From:</strong> ${escapeHtml2(headers.from)}</div>
|
|
1068
|
-
<div><strong>To:</strong> ${escapeHtml2(headers.to)}</div>
|
|
1069
|
-
</div>
|
|
1070
|
-
`;
|
|
1071
|
-
return html.replace(/<body[^>]*>/, `$&${headerHtml}`);
|
|
1072
|
-
}
|
|
1073
|
-
function escapeHtml2(text) {
|
|
1074
|
-
const div = document.createElement("div");
|
|
1075
|
-
div.textContent = text;
|
|
1076
|
-
return div.innerHTML;
|
|
1077
|
-
}
|
|
1078
154
|
|
|
1079
|
-
// src/components/
|
|
1080
|
-
import {
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
alert(error instanceof Error ? error.message : "Failed to send test email");
|
|
155
|
+
// src/admin/components/StatusBadge.tsx
|
|
156
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
157
|
+
var StatusBadge = (props) => {
|
|
158
|
+
const status = props.cellData || "draft";
|
|
159
|
+
const getStatusColor = (status2) => {
|
|
160
|
+
switch (status2) {
|
|
161
|
+
case "sent":
|
|
162
|
+
return "#22c55e";
|
|
163
|
+
case "scheduled":
|
|
164
|
+
return "#3b82f6";
|
|
165
|
+
case "draft":
|
|
166
|
+
return "#6b7280";
|
|
167
|
+
case "failed":
|
|
168
|
+
return "#ef4444";
|
|
169
|
+
default:
|
|
170
|
+
return "#6b7280";
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
const getStatusLabel = (status2) => {
|
|
174
|
+
switch (status2) {
|
|
175
|
+
case "sent":
|
|
176
|
+
return "Sent";
|
|
177
|
+
case "scheduled":
|
|
178
|
+
return "Scheduled";
|
|
179
|
+
case "draft":
|
|
180
|
+
return "Draft";
|
|
181
|
+
case "failed":
|
|
182
|
+
return "Failed";
|
|
183
|
+
default:
|
|
184
|
+
return status2;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
return /* @__PURE__ */ jsx2(
|
|
188
|
+
"span",
|
|
189
|
+
{
|
|
190
|
+
style: {
|
|
191
|
+
display: "inline-block",
|
|
192
|
+
padding: "4px 8px",
|
|
193
|
+
borderRadius: "12px",
|
|
194
|
+
fontSize: "12px",
|
|
195
|
+
fontWeight: "500",
|
|
196
|
+
color: "#fff",
|
|
197
|
+
backgroundColor: getStatusColor(status),
|
|
198
|
+
textTransform: "capitalize"
|
|
199
|
+
},
|
|
200
|
+
children: getStatusLabel(status)
|
|
1126
201
|
}
|
|
1127
|
-
|
|
1128
|
-
return /* @__PURE__ */ jsxs4("div", { style: { height: "600px", display: "flex", flexDirection: "column" }, children: [
|
|
1129
|
-
/* @__PURE__ */ jsxs4("div", { style: {
|
|
1130
|
-
display: "flex",
|
|
1131
|
-
alignItems: "center",
|
|
1132
|
-
justifyContent: "space-between",
|
|
1133
|
-
padding: "12px 16px",
|
|
1134
|
-
borderBottom: "1px solid #e5e7eb",
|
|
1135
|
-
backgroundColor: "#f9fafb"
|
|
1136
|
-
}, children: [
|
|
1137
|
-
/* @__PURE__ */ jsxs4("div", { style: { display: "flex", alignItems: "center", gap: "16px" }, children: [
|
|
1138
|
-
/* @__PURE__ */ jsx6(
|
|
1139
|
-
"button",
|
|
1140
|
-
{
|
|
1141
|
-
type: "button",
|
|
1142
|
-
onClick: () => setShowPreview(!showPreview),
|
|
1143
|
-
style: {
|
|
1144
|
-
padding: "6px 12px",
|
|
1145
|
-
backgroundColor: showPreview ? "#3b82f6" : "#e5e7eb",
|
|
1146
|
-
color: showPreview ? "white" : "#374151",
|
|
1147
|
-
border: "none",
|
|
1148
|
-
borderRadius: "4px",
|
|
1149
|
-
fontSize: "14px",
|
|
1150
|
-
cursor: "pointer"
|
|
1151
|
-
},
|
|
1152
|
-
children: showPreview ? "Hide Preview" : "Show Preview"
|
|
1153
|
-
}
|
|
1154
|
-
),
|
|
1155
|
-
showPreview && /* @__PURE__ */ jsxs4("div", { style: { display: "flex", gap: "8px" }, children: [
|
|
1156
|
-
/* @__PURE__ */ jsx6(
|
|
1157
|
-
"button",
|
|
1158
|
-
{
|
|
1159
|
-
type: "button",
|
|
1160
|
-
onClick: () => setPreviewMode("desktop"),
|
|
1161
|
-
style: {
|
|
1162
|
-
padding: "6px 12px",
|
|
1163
|
-
backgroundColor: previewMode === "desktop" ? "#6366f1" : "#e5e7eb",
|
|
1164
|
-
color: previewMode === "desktop" ? "white" : "#374151",
|
|
1165
|
-
border: "none",
|
|
1166
|
-
borderRadius: "4px 0 0 4px",
|
|
1167
|
-
fontSize: "14px",
|
|
1168
|
-
cursor: "pointer"
|
|
1169
|
-
},
|
|
1170
|
-
children: "Desktop"
|
|
1171
|
-
}
|
|
1172
|
-
),
|
|
1173
|
-
/* @__PURE__ */ jsx6(
|
|
1174
|
-
"button",
|
|
1175
|
-
{
|
|
1176
|
-
type: "button",
|
|
1177
|
-
onClick: () => setPreviewMode("mobile"),
|
|
1178
|
-
style: {
|
|
1179
|
-
padding: "6px 12px",
|
|
1180
|
-
backgroundColor: previewMode === "mobile" ? "#6366f1" : "#e5e7eb",
|
|
1181
|
-
color: previewMode === "mobile" ? "white" : "#374151",
|
|
1182
|
-
border: "none",
|
|
1183
|
-
borderRadius: "0 4px 4px 0",
|
|
1184
|
-
fontSize: "14px",
|
|
1185
|
-
cursor: "pointer"
|
|
1186
|
-
},
|
|
1187
|
-
children: "Mobile"
|
|
1188
|
-
}
|
|
1189
|
-
)
|
|
1190
|
-
] }),
|
|
1191
|
-
showPreview && validationSummary && /* @__PURE__ */ jsx6("div", { style: {
|
|
1192
|
-
padding: "6px 12px",
|
|
1193
|
-
backgroundColor: isValid ? "#fef3c7" : "#fee2e2",
|
|
1194
|
-
color: isValid ? "#92400e" : "#991b1b",
|
|
1195
|
-
borderRadius: "4px",
|
|
1196
|
-
fontSize: "13px"
|
|
1197
|
-
}, children: validationSummary })
|
|
1198
|
-
] }),
|
|
1199
|
-
showPreview && /* @__PURE__ */ jsx6(
|
|
1200
|
-
"button",
|
|
1201
|
-
{
|
|
1202
|
-
type: "button",
|
|
1203
|
-
onClick: handleTestEmail,
|
|
1204
|
-
style: {
|
|
1205
|
-
padding: "6px 12px",
|
|
1206
|
-
backgroundColor: "#10b981",
|
|
1207
|
-
color: "white",
|
|
1208
|
-
border: "none",
|
|
1209
|
-
borderRadius: "4px",
|
|
1210
|
-
fontSize: "14px",
|
|
1211
|
-
cursor: "pointer"
|
|
1212
|
-
},
|
|
1213
|
-
children: "Send Test Email"
|
|
1214
|
-
}
|
|
1215
|
-
)
|
|
1216
|
-
] }),
|
|
1217
|
-
/* @__PURE__ */ jsxs4("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
|
|
1218
|
-
/* @__PURE__ */ jsx6("div", { style: {
|
|
1219
|
-
flex: showPreview ? "0 0 50%" : "1",
|
|
1220
|
-
overflow: "auto",
|
|
1221
|
-
borderRight: showPreview ? "1px solid #e5e7eb" : "none"
|
|
1222
|
-
}, children: /* @__PURE__ */ jsx6("div", { style: { padding: "16px" }, children: /* @__PURE__ */ jsx6("div", { className: "rich-text-lexical" }) }) }),
|
|
1223
|
-
showPreview && /* @__PURE__ */ jsx6("div", { style: { flex: "0 0 50%", overflow: "hidden" }, children: /* @__PURE__ */ jsx6(
|
|
1224
|
-
EmailPreview,
|
|
1225
|
-
{
|
|
1226
|
-
content: value,
|
|
1227
|
-
subject: fields.subject?.value || "Email Subject",
|
|
1228
|
-
preheader: fields.preheader?.value,
|
|
1229
|
-
mode: previewMode,
|
|
1230
|
-
onValidation: handleValidation
|
|
1231
|
-
}
|
|
1232
|
-
) })
|
|
1233
|
-
] })
|
|
1234
|
-
] });
|
|
202
|
+
);
|
|
1235
203
|
};
|
|
1236
204
|
|
|
1237
|
-
// src/components/
|
|
1238
|
-
import {
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
preheader: fields2["contentSection.preheader"],
|
|
1250
|
-
channel: fields2.channel
|
|
1251
|
-
}));
|
|
1252
|
-
const handleValidation = (result) => {
|
|
1253
|
-
setIsValid(result.valid);
|
|
1254
|
-
const errorCount = result.errors.length;
|
|
1255
|
-
const warningCount = result.warnings.length;
|
|
1256
|
-
if (errorCount > 0) {
|
|
1257
|
-
setValidationSummary(`${errorCount} error${errorCount !== 1 ? "s" : ""}, ${warningCount} warning${warningCount !== 1 ? "s" : ""}`);
|
|
1258
|
-
} else if (warningCount > 0) {
|
|
1259
|
-
setValidationSummary(`${warningCount} warning${warningCount !== 1 ? "s" : ""}`);
|
|
1260
|
-
} else {
|
|
1261
|
-
setValidationSummary("");
|
|
1262
|
-
}
|
|
1263
|
-
};
|
|
1264
|
-
const handleTestEmail = async () => {
|
|
1265
|
-
const pathParts = window.location.pathname.split("/");
|
|
1266
|
-
const broadcastId = pathParts[pathParts.length - 1];
|
|
1267
|
-
if (!broadcastId || broadcastId === "create") {
|
|
1268
|
-
alert("Please save the broadcast before sending a test email");
|
|
1269
|
-
return;
|
|
1270
|
-
}
|
|
1271
|
-
try {
|
|
1272
|
-
const response = await fetch(`/api/broadcasts/${broadcastId}/test`, {
|
|
1273
|
-
method: "POST",
|
|
1274
|
-
headers: {
|
|
1275
|
-
"Content-Type": "application/json"
|
|
1276
|
-
}
|
|
1277
|
-
});
|
|
1278
|
-
if (!response.ok) {
|
|
1279
|
-
const data = await response.json();
|
|
1280
|
-
throw new Error(data.error || "Failed to send test email");
|
|
1281
|
-
}
|
|
1282
|
-
alert("Test email sent successfully! Check your inbox.");
|
|
1283
|
-
} catch (error) {
|
|
1284
|
-
alert(error instanceof Error ? error.message : "Failed to send test email");
|
|
1285
|
-
}
|
|
1286
|
-
};
|
|
1287
|
-
return /* @__PURE__ */ jsxs5("div", { style: {
|
|
1288
|
-
marginTop: "24px",
|
|
1289
|
-
border: "1px solid #e5e7eb",
|
|
1290
|
-
borderRadius: "8px",
|
|
1291
|
-
overflow: "hidden"
|
|
1292
|
-
}, children: [
|
|
1293
|
-
/* @__PURE__ */ jsxs5("div", { style: {
|
|
1294
|
-
display: "flex",
|
|
1295
|
-
alignItems: "center",
|
|
1296
|
-
justifyContent: "space-between",
|
|
1297
|
-
padding: "12px 16px",
|
|
1298
|
-
borderBottom: "1px solid #e5e7eb",
|
|
1299
|
-
backgroundColor: "#f9fafb"
|
|
1300
|
-
}, children: [
|
|
1301
|
-
/* @__PURE__ */ jsxs5("div", { style: { display: "flex", alignItems: "center", gap: "16px" }, children: [
|
|
1302
|
-
/* @__PURE__ */ jsx7("h3", { style: { margin: 0, fontSize: "16px", fontWeight: 600 }, children: "Email Preview" }),
|
|
1303
|
-
/* @__PURE__ */ jsxs5("div", { style: { display: "flex", gap: "8px" }, children: [
|
|
1304
|
-
/* @__PURE__ */ jsx7(
|
|
1305
|
-
"button",
|
|
1306
|
-
{
|
|
1307
|
-
type: "button",
|
|
1308
|
-
onClick: () => setPreviewMode("desktop"),
|
|
1309
|
-
style: {
|
|
1310
|
-
padding: "6px 12px",
|
|
1311
|
-
backgroundColor: previewMode === "desktop" ? "#6366f1" : "#e5e7eb",
|
|
1312
|
-
color: previewMode === "desktop" ? "white" : "#374151",
|
|
1313
|
-
border: "none",
|
|
1314
|
-
borderRadius: "4px 0 0 4px",
|
|
1315
|
-
fontSize: "14px",
|
|
1316
|
-
cursor: "pointer"
|
|
1317
|
-
},
|
|
1318
|
-
children: "Desktop"
|
|
1319
|
-
}
|
|
1320
|
-
),
|
|
1321
|
-
/* @__PURE__ */ jsx7(
|
|
1322
|
-
"button",
|
|
1323
|
-
{
|
|
1324
|
-
type: "button",
|
|
1325
|
-
onClick: () => setPreviewMode("mobile"),
|
|
1326
|
-
style: {
|
|
1327
|
-
padding: "6px 12px",
|
|
1328
|
-
backgroundColor: previewMode === "mobile" ? "#6366f1" : "#e5e7eb",
|
|
1329
|
-
color: previewMode === "mobile" ? "white" : "#374151",
|
|
1330
|
-
border: "none",
|
|
1331
|
-
borderRadius: "0 4px 4px 0",
|
|
1332
|
-
fontSize: "14px",
|
|
1333
|
-
cursor: "pointer"
|
|
1334
|
-
},
|
|
1335
|
-
children: "Mobile"
|
|
1336
|
-
}
|
|
1337
|
-
)
|
|
1338
|
-
] }),
|
|
1339
|
-
validationSummary && /* @__PURE__ */ jsx7("div", { style: {
|
|
1340
|
-
padding: "6px 12px",
|
|
1341
|
-
backgroundColor: isValid ? "#fef3c7" : "#fee2e2",
|
|
1342
|
-
color: isValid ? "#92400e" : "#991b1b",
|
|
1343
|
-
borderRadius: "4px",
|
|
1344
|
-
fontSize: "13px"
|
|
1345
|
-
}, children: validationSummary })
|
|
1346
|
-
] }),
|
|
1347
|
-
/* @__PURE__ */ jsx7(
|
|
1348
|
-
"button",
|
|
1349
|
-
{
|
|
1350
|
-
type: "button",
|
|
1351
|
-
onClick: handleTestEmail,
|
|
1352
|
-
style: {
|
|
1353
|
-
padding: "6px 12px",
|
|
1354
|
-
backgroundColor: "#10b981",
|
|
1355
|
-
color: "white",
|
|
1356
|
-
border: "none",
|
|
1357
|
-
borderRadius: "4px",
|
|
1358
|
-
fontSize: "14px",
|
|
1359
|
-
cursor: "pointer"
|
|
1360
|
-
},
|
|
1361
|
-
children: "Send Test Email"
|
|
1362
|
-
}
|
|
1363
|
-
)
|
|
205
|
+
// src/admin/components/EmailPreview.tsx
|
|
206
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
207
|
+
var EmailPreview = ({
|
|
208
|
+
content,
|
|
209
|
+
subject,
|
|
210
|
+
preheader
|
|
211
|
+
}) => {
|
|
212
|
+
return /* @__PURE__ */ jsxs2("div", { className: "email-preview", style: { padding: "1rem" }, children: [
|
|
213
|
+
/* @__PURE__ */ jsxs2("div", { style: { marginBottom: "1rem" }, children: [
|
|
214
|
+
/* @__PURE__ */ jsx3("strong", { children: "Subject:" }),
|
|
215
|
+
" ",
|
|
216
|
+
subject || "No subject"
|
|
1364
217
|
] }),
|
|
1365
|
-
/* @__PURE__ */
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
218
|
+
preheader && /* @__PURE__ */ jsxs2("div", { style: { marginBottom: "1rem", color: "#666" }, children: [
|
|
219
|
+
/* @__PURE__ */ jsx3("strong", { children: "Preheader:" }),
|
|
220
|
+
" ",
|
|
221
|
+
preheader
|
|
222
|
+
] }),
|
|
223
|
+
/* @__PURE__ */ jsxs2("div", { style: {
|
|
224
|
+
border: "1px solid #e0e0e0",
|
|
225
|
+
borderRadius: "4px",
|
|
226
|
+
padding: "1rem",
|
|
227
|
+
backgroundColor: "#f9f9f9"
|
|
228
|
+
}, children: [
|
|
229
|
+
/* @__PURE__ */ jsx3("div", { children: "Email content will be rendered here" }),
|
|
230
|
+
content && /* @__PURE__ */ jsxs2("div", { style: { marginTop: "1rem", fontSize: "14px", color: "#666" }, children: [
|
|
231
|
+
"Content type: ",
|
|
232
|
+
typeof content
|
|
233
|
+
] })
|
|
234
|
+
] })
|
|
1376
235
|
] });
|
|
1377
236
|
};
|
|
1378
|
-
|
|
1379
|
-
// src/components/Broadcasts/BroadcastPreviewField.tsx
|
|
1380
|
-
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
1381
|
-
var BroadcastPreviewField = () => {
|
|
1382
|
-
return /* @__PURE__ */ jsx8("div", { style: {
|
|
1383
|
-
padding: "1rem",
|
|
1384
|
-
background: "#f9fafb",
|
|
1385
|
-
borderRadius: "4px",
|
|
1386
|
-
fontSize: "14px",
|
|
1387
|
-
color: "#6b7280"
|
|
1388
|
-
}, children: "Email preview is available inline below the content editor." });
|
|
1389
|
-
};
|
|
1390
|
-
|
|
1391
|
-
// src/contexts/ClientContext.tsx
|
|
1392
|
-
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
1393
|
-
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
1394
|
-
var PluginConfigContext2 = createContext2(null);
|
|
1395
|
-
var PluginConfigProvider = ({ config, children }) => {
|
|
1396
|
-
return /* @__PURE__ */ jsx9(PluginConfigContext2.Provider, { value: config, children });
|
|
1397
|
-
};
|
|
1398
|
-
var usePluginConfig = () => {
|
|
1399
|
-
const context = useContext2(PluginConfigContext2);
|
|
1400
|
-
if (!context) {
|
|
1401
|
-
throw new Error("usePluginConfig must be used within a PluginConfigProvider");
|
|
1402
|
-
}
|
|
1403
|
-
return context;
|
|
1404
|
-
};
|
|
1405
|
-
var usePluginConfigOptional2 = () => {
|
|
1406
|
-
return useContext2(PluginConfigContext2);
|
|
1407
|
-
};
|
|
1408
|
-
|
|
1409
|
-
// src/fields/broadcastInlinePreview.ts
|
|
1410
|
-
var createBroadcastInlinePreviewField = () => {
|
|
1411
|
-
return {
|
|
1412
|
-
name: "broadcastInlinePreview",
|
|
1413
|
-
type: "ui",
|
|
1414
|
-
admin: {
|
|
1415
|
-
components: {
|
|
1416
|
-
Field: "payload-plugin-newsletter/components#BroadcastInlinePreview"
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
};
|
|
1420
|
-
};
|
|
1421
|
-
|
|
1422
|
-
// src/fields/broadcastPreview.ts
|
|
1423
|
-
var createBroadcastPreviewField = () => {
|
|
1424
|
-
return {
|
|
1425
|
-
name: "broadcastPreview",
|
|
1426
|
-
type: "ui",
|
|
1427
|
-
admin: {
|
|
1428
|
-
components: {
|
|
1429
|
-
Field: "payload-plugin-newsletter/components#BroadcastPreviewField"
|
|
1430
|
-
},
|
|
1431
|
-
position: "sidebar"
|
|
1432
|
-
}
|
|
1433
|
-
};
|
|
1434
|
-
};
|
|
1435
|
-
|
|
1436
|
-
// src/fields/emailContent.ts
|
|
1437
|
-
import {
|
|
1438
|
-
BoldFeature,
|
|
1439
|
-
ItalicFeature,
|
|
1440
|
-
UnderlineFeature,
|
|
1441
|
-
StrikethroughFeature,
|
|
1442
|
-
LinkFeature,
|
|
1443
|
-
OrderedListFeature,
|
|
1444
|
-
UnorderedListFeature,
|
|
1445
|
-
HeadingFeature,
|
|
1446
|
-
ParagraphFeature,
|
|
1447
|
-
AlignFeature,
|
|
1448
|
-
BlockquoteFeature,
|
|
1449
|
-
BlocksFeature,
|
|
1450
|
-
UploadFeature,
|
|
1451
|
-
FixedToolbarFeature,
|
|
1452
|
-
InlineToolbarFeature,
|
|
1453
|
-
lexicalEditor
|
|
1454
|
-
} from "@payloadcms/richtext-lexical";
|
|
1455
|
-
|
|
1456
|
-
// src/utils/blockValidation.ts
|
|
1457
|
-
var EMAIL_INCOMPATIBLE_TYPES = [
|
|
1458
|
-
"chart",
|
|
1459
|
-
"dataTable",
|
|
1460
|
-
"interactive",
|
|
1461
|
-
"streamable",
|
|
1462
|
-
"video",
|
|
1463
|
-
"iframe",
|
|
1464
|
-
"form",
|
|
1465
|
-
"carousel",
|
|
1466
|
-
"tabs",
|
|
1467
|
-
"accordion",
|
|
1468
|
-
"map"
|
|
1469
|
-
];
|
|
1470
|
-
var validateEmailBlocks = (blocks) => {
|
|
1471
|
-
blocks.forEach((block) => {
|
|
1472
|
-
if (EMAIL_INCOMPATIBLE_TYPES.includes(block.slug)) {
|
|
1473
|
-
console.warn(`\u26A0\uFE0F Block "${block.slug}" may not be email-compatible. Consider creating an email-specific version.`);
|
|
1474
|
-
}
|
|
1475
|
-
const hasComplexFields = block.fields?.some((field) => {
|
|
1476
|
-
const complexTypes = ["code", "json", "richText", "blocks", "array"];
|
|
1477
|
-
return complexTypes.includes(field.type);
|
|
1478
|
-
});
|
|
1479
|
-
if (hasComplexFields) {
|
|
1480
|
-
console.warn(`\u26A0\uFE0F Block "${block.slug}" contains complex field types that may not render consistently in email clients.`);
|
|
1481
|
-
}
|
|
1482
|
-
});
|
|
1483
|
-
};
|
|
1484
|
-
var createEmailSafeBlocks = (customBlocks = []) => {
|
|
1485
|
-
validateEmailBlocks(customBlocks);
|
|
1486
|
-
const baseBlocks = [
|
|
1487
|
-
{
|
|
1488
|
-
slug: "button",
|
|
1489
|
-
fields: [
|
|
1490
|
-
{
|
|
1491
|
-
name: "text",
|
|
1492
|
-
type: "text",
|
|
1493
|
-
label: "Button Text",
|
|
1494
|
-
required: true
|
|
1495
|
-
},
|
|
1496
|
-
{
|
|
1497
|
-
name: "url",
|
|
1498
|
-
type: "text",
|
|
1499
|
-
label: "Button URL",
|
|
1500
|
-
required: true,
|
|
1501
|
-
admin: {
|
|
1502
|
-
description: "Enter the full URL (including https://)"
|
|
1503
|
-
}
|
|
1504
|
-
},
|
|
1505
|
-
{
|
|
1506
|
-
name: "style",
|
|
1507
|
-
type: "select",
|
|
1508
|
-
label: "Button Style",
|
|
1509
|
-
defaultValue: "primary",
|
|
1510
|
-
options: [
|
|
1511
|
-
{ label: "Primary", value: "primary" },
|
|
1512
|
-
{ label: "Secondary", value: "secondary" },
|
|
1513
|
-
{ label: "Outline", value: "outline" }
|
|
1514
|
-
]
|
|
1515
|
-
}
|
|
1516
|
-
],
|
|
1517
|
-
interfaceName: "EmailButton",
|
|
1518
|
-
labels: {
|
|
1519
|
-
singular: "Button",
|
|
1520
|
-
plural: "Buttons"
|
|
1521
|
-
}
|
|
1522
|
-
},
|
|
1523
|
-
{
|
|
1524
|
-
slug: "divider",
|
|
1525
|
-
fields: [
|
|
1526
|
-
{
|
|
1527
|
-
name: "style",
|
|
1528
|
-
type: "select",
|
|
1529
|
-
label: "Divider Style",
|
|
1530
|
-
defaultValue: "solid",
|
|
1531
|
-
options: [
|
|
1532
|
-
{ label: "Solid", value: "solid" },
|
|
1533
|
-
{ label: "Dashed", value: "dashed" },
|
|
1534
|
-
{ label: "Dotted", value: "dotted" }
|
|
1535
|
-
]
|
|
1536
|
-
}
|
|
1537
|
-
],
|
|
1538
|
-
interfaceName: "EmailDivider",
|
|
1539
|
-
labels: {
|
|
1540
|
-
singular: "Divider",
|
|
1541
|
-
plural: "Dividers"
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
];
|
|
1545
|
-
return [
|
|
1546
|
-
...baseBlocks,
|
|
1547
|
-
...customBlocks
|
|
1548
|
-
];
|
|
1549
|
-
};
|
|
1550
|
-
|
|
1551
|
-
// src/fields/emailContent.ts
|
|
1552
|
-
var createEmailSafeFeatures = (additionalBlocks) => {
|
|
1553
|
-
const baseBlocks = [
|
|
1554
|
-
{
|
|
1555
|
-
slug: "button",
|
|
1556
|
-
fields: [
|
|
1557
|
-
{
|
|
1558
|
-
name: "text",
|
|
1559
|
-
type: "text",
|
|
1560
|
-
label: "Button Text",
|
|
1561
|
-
required: true
|
|
1562
|
-
},
|
|
1563
|
-
{
|
|
1564
|
-
name: "url",
|
|
1565
|
-
type: "text",
|
|
1566
|
-
label: "Button URL",
|
|
1567
|
-
required: true,
|
|
1568
|
-
admin: {
|
|
1569
|
-
description: "Enter the full URL (including https://)"
|
|
1570
|
-
}
|
|
1571
|
-
},
|
|
1572
|
-
{
|
|
1573
|
-
name: "style",
|
|
1574
|
-
type: "select",
|
|
1575
|
-
label: "Button Style",
|
|
1576
|
-
defaultValue: "primary",
|
|
1577
|
-
options: [
|
|
1578
|
-
{ label: "Primary", value: "primary" },
|
|
1579
|
-
{ label: "Secondary", value: "secondary" },
|
|
1580
|
-
{ label: "Outline", value: "outline" }
|
|
1581
|
-
]
|
|
1582
|
-
}
|
|
1583
|
-
],
|
|
1584
|
-
interfaceName: "EmailButton",
|
|
1585
|
-
labels: {
|
|
1586
|
-
singular: "Button",
|
|
1587
|
-
plural: "Buttons"
|
|
1588
|
-
}
|
|
1589
|
-
},
|
|
1590
|
-
{
|
|
1591
|
-
slug: "divider",
|
|
1592
|
-
fields: [
|
|
1593
|
-
{
|
|
1594
|
-
name: "style",
|
|
1595
|
-
type: "select",
|
|
1596
|
-
label: "Divider Style",
|
|
1597
|
-
defaultValue: "solid",
|
|
1598
|
-
options: [
|
|
1599
|
-
{ label: "Solid", value: "solid" },
|
|
1600
|
-
{ label: "Dashed", value: "dashed" },
|
|
1601
|
-
{ label: "Dotted", value: "dotted" }
|
|
1602
|
-
]
|
|
1603
|
-
}
|
|
1604
|
-
],
|
|
1605
|
-
interfaceName: "EmailDivider",
|
|
1606
|
-
labels: {
|
|
1607
|
-
singular: "Divider",
|
|
1608
|
-
plural: "Dividers"
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
];
|
|
1612
|
-
const allBlocks = [
|
|
1613
|
-
...baseBlocks,
|
|
1614
|
-
...additionalBlocks || []
|
|
1615
|
-
];
|
|
1616
|
-
return [
|
|
1617
|
-
// Toolbars
|
|
1618
|
-
FixedToolbarFeature(),
|
|
1619
|
-
// Fixed toolbar at the top
|
|
1620
|
-
InlineToolbarFeature(),
|
|
1621
|
-
// Floating toolbar when text is selected
|
|
1622
|
-
// Basic text formatting
|
|
1623
|
-
BoldFeature(),
|
|
1624
|
-
ItalicFeature(),
|
|
1625
|
-
UnderlineFeature(),
|
|
1626
|
-
StrikethroughFeature(),
|
|
1627
|
-
// Links with enhanced configuration
|
|
1628
|
-
LinkFeature({
|
|
1629
|
-
fields: [
|
|
1630
|
-
{
|
|
1631
|
-
name: "url",
|
|
1632
|
-
type: "text",
|
|
1633
|
-
required: true,
|
|
1634
|
-
admin: {
|
|
1635
|
-
description: "Enter the full URL (including https://)"
|
|
1636
|
-
}
|
|
1637
|
-
},
|
|
1638
|
-
{
|
|
1639
|
-
name: "newTab",
|
|
1640
|
-
type: "checkbox",
|
|
1641
|
-
label: "Open in new tab",
|
|
1642
|
-
defaultValue: false
|
|
1643
|
-
}
|
|
1644
|
-
]
|
|
1645
|
-
}),
|
|
1646
|
-
// Lists
|
|
1647
|
-
OrderedListFeature(),
|
|
1648
|
-
UnorderedListFeature(),
|
|
1649
|
-
// Headings - limited to h1, h2, h3 for email compatibility
|
|
1650
|
-
HeadingFeature({
|
|
1651
|
-
enabledHeadingSizes: ["h1", "h2", "h3"]
|
|
1652
|
-
}),
|
|
1653
|
-
// Basic paragraph and alignment
|
|
1654
|
-
ParagraphFeature(),
|
|
1655
|
-
AlignFeature(),
|
|
1656
|
-
// Blockquotes
|
|
1657
|
-
BlockquoteFeature(),
|
|
1658
|
-
// Upload feature for images
|
|
1659
|
-
UploadFeature({
|
|
1660
|
-
collections: {
|
|
1661
|
-
media: {
|
|
1662
|
-
fields: [
|
|
1663
|
-
{
|
|
1664
|
-
name: "caption",
|
|
1665
|
-
type: "text",
|
|
1666
|
-
admin: {
|
|
1667
|
-
description: "Optional caption for the image"
|
|
1668
|
-
}
|
|
1669
|
-
},
|
|
1670
|
-
{
|
|
1671
|
-
name: "altText",
|
|
1672
|
-
type: "text",
|
|
1673
|
-
label: "Alt Text",
|
|
1674
|
-
required: true,
|
|
1675
|
-
admin: {
|
|
1676
|
-
description: "Alternative text for accessibility and when image cannot be displayed"
|
|
1677
|
-
}
|
|
1678
|
-
}
|
|
1679
|
-
]
|
|
1680
|
-
}
|
|
1681
|
-
}
|
|
1682
|
-
}),
|
|
1683
|
-
// Custom blocks for email-specific content
|
|
1684
|
-
BlocksFeature({
|
|
1685
|
-
blocks: allBlocks
|
|
1686
|
-
})
|
|
1687
|
-
];
|
|
1688
|
-
};
|
|
1689
|
-
var createEmailLexicalEditor = (customBlocks = []) => {
|
|
1690
|
-
const emailSafeBlocks = createEmailSafeBlocks(customBlocks);
|
|
1691
|
-
return lexicalEditor({
|
|
1692
|
-
features: [
|
|
1693
|
-
// Toolbars
|
|
1694
|
-
FixedToolbarFeature(),
|
|
1695
|
-
InlineToolbarFeature(),
|
|
1696
|
-
// Basic text formatting
|
|
1697
|
-
BoldFeature(),
|
|
1698
|
-
ItalicFeature(),
|
|
1699
|
-
UnderlineFeature(),
|
|
1700
|
-
StrikethroughFeature(),
|
|
1701
|
-
// Links with enhanced configuration
|
|
1702
|
-
LinkFeature({
|
|
1703
|
-
fields: [
|
|
1704
|
-
{
|
|
1705
|
-
name: "url",
|
|
1706
|
-
type: "text",
|
|
1707
|
-
required: true,
|
|
1708
|
-
admin: {
|
|
1709
|
-
description: "Enter the full URL (including https://)"
|
|
1710
|
-
}
|
|
1711
|
-
},
|
|
1712
|
-
{
|
|
1713
|
-
name: "newTab",
|
|
1714
|
-
type: "checkbox",
|
|
1715
|
-
label: "Open in new tab",
|
|
1716
|
-
defaultValue: false
|
|
1717
|
-
}
|
|
1718
|
-
]
|
|
1719
|
-
}),
|
|
1720
|
-
// Lists
|
|
1721
|
-
OrderedListFeature(),
|
|
1722
|
-
UnorderedListFeature(),
|
|
1723
|
-
// Headings - limited to h1, h2, h3 for email compatibility
|
|
1724
|
-
HeadingFeature({
|
|
1725
|
-
enabledHeadingSizes: ["h1", "h2", "h3"]
|
|
1726
|
-
}),
|
|
1727
|
-
// Basic paragraph and alignment
|
|
1728
|
-
ParagraphFeature(),
|
|
1729
|
-
AlignFeature(),
|
|
1730
|
-
// Blockquotes
|
|
1731
|
-
BlockquoteFeature(),
|
|
1732
|
-
// Upload feature for images
|
|
1733
|
-
UploadFeature({
|
|
1734
|
-
collections: {
|
|
1735
|
-
media: {
|
|
1736
|
-
fields: [
|
|
1737
|
-
{
|
|
1738
|
-
name: "caption",
|
|
1739
|
-
type: "text",
|
|
1740
|
-
admin: {
|
|
1741
|
-
description: "Optional caption for the image"
|
|
1742
|
-
}
|
|
1743
|
-
},
|
|
1744
|
-
{
|
|
1745
|
-
name: "altText",
|
|
1746
|
-
type: "text",
|
|
1747
|
-
label: "Alt Text",
|
|
1748
|
-
required: true,
|
|
1749
|
-
admin: {
|
|
1750
|
-
description: "Alternative text for accessibility and when image cannot be displayed"
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
]
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1756
|
-
}),
|
|
1757
|
-
// Email-safe blocks (processed server-side)
|
|
1758
|
-
BlocksFeature({
|
|
1759
|
-
blocks: emailSafeBlocks
|
|
1760
|
-
})
|
|
1761
|
-
]
|
|
1762
|
-
});
|
|
1763
|
-
};
|
|
1764
|
-
var emailSafeFeatures = createEmailSafeFeatures();
|
|
1765
|
-
var createEmailContentField = (overrides) => {
|
|
1766
|
-
const editor = overrides?.editor || createEmailLexicalEditor(overrides?.additionalBlocks);
|
|
1767
|
-
return {
|
|
1768
|
-
name: "content",
|
|
1769
|
-
type: "richText",
|
|
1770
|
-
required: true,
|
|
1771
|
-
editor,
|
|
1772
|
-
admin: {
|
|
1773
|
-
description: "Email content with limited formatting for compatibility",
|
|
1774
|
-
...overrides?.admin
|
|
1775
|
-
},
|
|
1776
|
-
...overrides
|
|
1777
|
-
};
|
|
1778
|
-
};
|
|
1779
237
|
export {
|
|
1780
|
-
BroadcastEditor,
|
|
1781
238
|
BroadcastInlinePreview,
|
|
1782
|
-
BroadcastPreviewField,
|
|
1783
239
|
EmailPreview,
|
|
1784
|
-
|
|
1785
|
-
PluginConfigProvider,
|
|
1786
|
-
StatusBadge,
|
|
1787
|
-
createBroadcastInlinePreviewField,
|
|
1788
|
-
createBroadcastPreviewField,
|
|
1789
|
-
createEmailContentField,
|
|
1790
|
-
usePluginConfig,
|
|
1791
|
-
usePluginConfigOptional2 as usePluginConfigOptional
|
|
240
|
+
StatusBadge
|
|
1792
241
|
};
|