payload-plugin-newsletter 0.20.2 → 0.20.3
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 +27 -0
- package/dist/admin.js +74 -1762
- package/package.json +3 -2
- package/dist/admin.d.ts +0 -706
package/dist/admin.js
CHANGED
|
@@ -1,1792 +1,104 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
"use client";
|
|
3
3
|
|
|
4
|
-
// src/components/
|
|
5
|
-
import { useState, useCallback } from "react";
|
|
6
|
-
import { useFormFields } from "@payloadcms/ui";
|
|
7
|
-
|
|
8
|
-
// src/components/Broadcasts/PreviewControls.tsx
|
|
4
|
+
// src/admin/components/BroadcastInlinePreview.tsx
|
|
9
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
-
var
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
isLoading = false
|
|
6
|
+
var BroadcastInlinePreview = ({
|
|
7
|
+
field: _field,
|
|
8
|
+
data: _data,
|
|
9
|
+
..._props
|
|
15
10
|
}) => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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);
|
|
104
|
-
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(
|
|
11
|
+
return /* @__PURE__ */ jsx("div", { className: "broadcast-preview", children: /* @__PURE__ */ jsxs("div", { style: { padding: "1rem", border: "1px solid #e0e0e0", borderRadius: "4px" }, children: [
|
|
12
|
+
/* @__PURE__ */ jsx("h3", { children: "Email Preview" }),
|
|
13
|
+
/* @__PURE__ */ jsx("p", { children: "This is a simplified preview component for the admin bundle." }),
|
|
14
|
+
/* @__PURE__ */ jsx("p", { children: "Full preview functionality will be available in the complete admin interface." })
|
|
15
|
+
] }) });
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// src/admin/components/StatusBadge.tsx
|
|
19
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
20
|
+
var StatusBadge = (props) => {
|
|
21
|
+
const status = props.cellData || "draft";
|
|
22
|
+
const getStatusColor = (status2) => {
|
|
23
|
+
switch (status2) {
|
|
24
|
+
case "sent":
|
|
25
|
+
return "#22c55e";
|
|
26
|
+
case "scheduled":
|
|
27
|
+
return "#3b82f6";
|
|
28
|
+
case "draft":
|
|
29
|
+
return "#6b7280";
|
|
30
|
+
case "failed":
|
|
31
|
+
return "#ef4444";
|
|
32
|
+
default:
|
|
33
|
+
return "#6b7280";
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const getStatusLabel = (status2) => {
|
|
37
|
+
switch (status2) {
|
|
38
|
+
case "sent":
|
|
39
|
+
return "Sent";
|
|
40
|
+
case "scheduled":
|
|
41
|
+
return "Scheduled";
|
|
42
|
+
case "draft":
|
|
43
|
+
return "Draft";
|
|
44
|
+
case "failed":
|
|
45
|
+
return "Failed";
|
|
46
|
+
default:
|
|
47
|
+
return status2;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
return /* @__PURE__ */ jsx2(
|
|
317
51
|
"span",
|
|
318
52
|
{
|
|
319
53
|
style: {
|
|
320
|
-
display: "inline-
|
|
321
|
-
|
|
322
|
-
padding: "2px 10px",
|
|
54
|
+
display: "inline-block",
|
|
55
|
+
padding: "4px 8px",
|
|
323
56
|
borderRadius: "12px",
|
|
324
57
|
fontSize: "12px",
|
|
325
58
|
fontWeight: "500",
|
|
326
|
-
color:
|
|
327
|
-
backgroundColor:
|
|
59
|
+
color: "#fff",
|
|
60
|
+
backgroundColor: getStatusColor(status),
|
|
61
|
+
textTransform: "capitalize"
|
|
328
62
|
},
|
|
329
|
-
children:
|
|
63
|
+
children: getStatusLabel(status)
|
|
330
64
|
}
|
|
331
65
|
);
|
|
332
66
|
};
|
|
333
67
|
|
|
334
|
-
// src/components/
|
|
335
|
-
import {
|
|
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
|
-
};
|
|
68
|
+
// src/admin/components/EmailPreview.tsx
|
|
69
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
927
70
|
var EmailPreview = ({
|
|
928
71
|
content,
|
|
929
72
|
subject,
|
|
930
|
-
preheader
|
|
931
|
-
mode = "desktop",
|
|
932
|
-
onValidation,
|
|
933
|
-
pluginConfig: propPluginConfig
|
|
73
|
+
preheader
|
|
934
74
|
}) => {
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
const iframeRef = useRef(null);
|
|
941
|
-
useEffect(() => {
|
|
942
|
-
const convertContent = async () => {
|
|
943
|
-
if (!content) {
|
|
944
|
-
setHtml("");
|
|
945
|
-
return;
|
|
946
|
-
}
|
|
947
|
-
setLoading(true);
|
|
948
|
-
try {
|
|
949
|
-
const emailPreviewConfig = pluginConfig?.customizations?.broadcasts?.emailPreview;
|
|
950
|
-
const emailHtml = await convertToEmailSafeHtml(content, {
|
|
951
|
-
wrapInTemplate: emailPreviewConfig?.wrapInTemplate ?? true,
|
|
952
|
-
preheader,
|
|
953
|
-
subject,
|
|
954
|
-
customWrapper: emailPreviewConfig?.customWrapper,
|
|
955
|
-
customBlockConverter: pluginConfig?.customizations?.broadcasts?.customBlockConverter
|
|
956
|
-
});
|
|
957
|
-
const personalizedHtml = replacePersonalizationTags(emailHtml, SAMPLE_DATA);
|
|
958
|
-
const previewHtml = addEmailHeader(personalizedHtml, {
|
|
959
|
-
subject,
|
|
960
|
-
from: "Newsletter <noreply@example.com>",
|
|
961
|
-
to: SAMPLE_DATA["subscriber.email"]
|
|
962
|
-
});
|
|
963
|
-
setHtml(previewHtml);
|
|
964
|
-
const validation = validateEmailHtml(emailHtml);
|
|
965
|
-
setValidationResult(validation);
|
|
966
|
-
onValidation?.(validation);
|
|
967
|
-
} catch (error) {
|
|
968
|
-
console.error("Failed to convert content to HTML:", error);
|
|
969
|
-
setHtml("<p>Error converting content to HTML</p>");
|
|
970
|
-
} finally {
|
|
971
|
-
setLoading(false);
|
|
972
|
-
}
|
|
973
|
-
};
|
|
974
|
-
convertContent();
|
|
975
|
-
}, [content, subject, preheader, onValidation, pluginConfig]);
|
|
976
|
-
useEffect(() => {
|
|
977
|
-
if (iframeRef.current && html) {
|
|
978
|
-
const doc = iframeRef.current.contentDocument;
|
|
979
|
-
if (doc) {
|
|
980
|
-
doc.open();
|
|
981
|
-
doc.write(html);
|
|
982
|
-
doc.close();
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
}, [html]);
|
|
986
|
-
const viewport = VIEWPORT_SIZES[mode];
|
|
987
|
-
return /* @__PURE__ */ jsxs3("div", { style: { height: "100%", display: "flex", flexDirection: "column" }, children: [
|
|
988
|
-
validationResult && (validationResult.errors.length > 0 || validationResult.warnings.length > 0) && /* @__PURE__ */ jsxs3("div", { style: { padding: "16px", borderBottom: "1px solid #e5e7eb" }, children: [
|
|
989
|
-
validationResult.errors.length > 0 && /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "12px" }, children: [
|
|
990
|
-
/* @__PURE__ */ jsxs3("h4", { style: { color: "#dc2626", margin: "0 0 8px 0", fontSize: "14px" }, children: [
|
|
991
|
-
"Errors (",
|
|
992
|
-
validationResult.errors.length,
|
|
993
|
-
")"
|
|
994
|
-
] }),
|
|
995
|
-
/* @__PURE__ */ jsx5("ul", { style: { margin: 0, paddingLeft: "20px", fontSize: "13px", color: "#dc2626" }, children: validationResult.errors.map((error, index) => /* @__PURE__ */ jsx5("li", { children: error }, index)) })
|
|
996
|
-
] }),
|
|
997
|
-
validationResult.warnings.length > 0 && /* @__PURE__ */ jsxs3("div", { children: [
|
|
998
|
-
/* @__PURE__ */ jsxs3("h4", { style: { color: "#d97706", margin: "0 0 8px 0", fontSize: "14px" }, children: [
|
|
999
|
-
"Warnings (",
|
|
1000
|
-
validationResult.warnings.length,
|
|
1001
|
-
")"
|
|
1002
|
-
] }),
|
|
1003
|
-
/* @__PURE__ */ jsx5("ul", { style: { margin: 0, paddingLeft: "20px", fontSize: "13px", color: "#d97706" }, children: validationResult.warnings.map((warning, index) => /* @__PURE__ */ jsx5("li", { children: warning }, index)) })
|
|
1004
|
-
] })
|
|
75
|
+
return /* @__PURE__ */ jsxs2("div", { className: "email-preview", style: { padding: "1rem" }, children: [
|
|
76
|
+
/* @__PURE__ */ jsxs2("div", { style: { marginBottom: "1rem" }, children: [
|
|
77
|
+
/* @__PURE__ */ jsx3("strong", { children: "Subject:" }),
|
|
78
|
+
" ",
|
|
79
|
+
subject || "No subject"
|
|
1005
80
|
] }),
|
|
1006
|
-
/* @__PURE__ */
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
backgroundColor: "
|
|
1016
|
-
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
|
|
1017
|
-
borderRadius: "8px",
|
|
1018
|
-
overflow: "hidden",
|
|
1019
|
-
transform: `scale(${viewport.scale})`,
|
|
1020
|
-
transformOrigin: "top center"
|
|
1021
|
-
}, children: /* @__PURE__ */ jsx5(
|
|
1022
|
-
"iframe",
|
|
1023
|
-
{
|
|
1024
|
-
ref: iframeRef,
|
|
1025
|
-
title: "Email Preview",
|
|
1026
|
-
style: {
|
|
1027
|
-
width: `${viewport.width}px`,
|
|
1028
|
-
height: "800px",
|
|
1029
|
-
border: "none",
|
|
1030
|
-
display: "block"
|
|
1031
|
-
},
|
|
1032
|
-
sandbox: "allow-same-origin"
|
|
1033
|
-
}
|
|
1034
|
-
) }) : /* @__PURE__ */ jsx5("div", { style: { textAlign: "center", color: "#6b7280" }, children: /* @__PURE__ */ jsx5("p", { children: "Start typing to see the email preview" }) }) }),
|
|
1035
|
-
validationResult && /* @__PURE__ */ jsxs3("div", { style: {
|
|
1036
|
-
padding: "12px 16px",
|
|
1037
|
-
borderTop: "1px solid #e5e7eb",
|
|
1038
|
-
fontSize: "13px",
|
|
1039
|
-
color: "#6b7280",
|
|
1040
|
-
display: "flex",
|
|
1041
|
-
gap: "24px"
|
|
81
|
+
preheader && /* @__PURE__ */ jsxs2("div", { style: { marginBottom: "1rem", color: "#666" }, children: [
|
|
82
|
+
/* @__PURE__ */ jsx3("strong", { children: "Preheader:" }),
|
|
83
|
+
" ",
|
|
84
|
+
preheader
|
|
85
|
+
] }),
|
|
86
|
+
/* @__PURE__ */ jsxs2("div", { style: {
|
|
87
|
+
border: "1px solid #e0e0e0",
|
|
88
|
+
borderRadius: "4px",
|
|
89
|
+
padding: "1rem",
|
|
90
|
+
backgroundColor: "#f9f9f9"
|
|
1042
91
|
}, children: [
|
|
1043
|
-
/* @__PURE__ */
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
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"
|
|
92
|
+
/* @__PURE__ */ jsx3("div", { children: "Email content will be rendered here" }),
|
|
93
|
+
content && /* @__PURE__ */ jsxs2("div", { style: { marginTop: "1rem", fontSize: "14px", color: "#666" }, children: [
|
|
94
|
+
"Content type: ",
|
|
95
|
+
typeof content
|
|
1059
96
|
] })
|
|
1060
97
|
] })
|
|
1061
98
|
] });
|
|
1062
99
|
};
|
|
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
|
-
|
|
1079
|
-
// src/components/Broadcasts/BroadcastEditor.tsx
|
|
1080
|
-
import { useState as useState3, useCallback as useCallback2 } from "react";
|
|
1081
|
-
import { useField, useFormFields as useFormFields2 } from "@payloadcms/ui";
|
|
1082
|
-
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1083
|
-
var BroadcastEditor = (props) => {
|
|
1084
|
-
const { value } = useField({ path: props.path });
|
|
1085
|
-
const [showPreview, setShowPreview] = useState3(true);
|
|
1086
|
-
const [previewMode, setPreviewMode] = useState3("desktop");
|
|
1087
|
-
const [isValid, setIsValid] = useState3(true);
|
|
1088
|
-
const [validationSummary, setValidationSummary] = useState3("");
|
|
1089
|
-
const fields = useFormFields2(([fields2]) => ({
|
|
1090
|
-
subject: fields2["subject"],
|
|
1091
|
-
preheader: fields2["contentSection.preheader"]
|
|
1092
|
-
}));
|
|
1093
|
-
const handleValidation = useCallback2((result) => {
|
|
1094
|
-
setIsValid(result.valid);
|
|
1095
|
-
const errorCount = result.errors.length;
|
|
1096
|
-
const warningCount = result.warnings.length;
|
|
1097
|
-
if (errorCount > 0) {
|
|
1098
|
-
setValidationSummary(`${errorCount} error${errorCount !== 1 ? "s" : ""}, ${warningCount} warning${warningCount !== 1 ? "s" : ""}`);
|
|
1099
|
-
} else if (warningCount > 0) {
|
|
1100
|
-
setValidationSummary(`${warningCount} warning${warningCount !== 1 ? "s" : ""}`);
|
|
1101
|
-
} else {
|
|
1102
|
-
setValidationSummary("");
|
|
1103
|
-
}
|
|
1104
|
-
}, []);
|
|
1105
|
-
const handleTestEmail = async () => {
|
|
1106
|
-
const pathParts = window.location.pathname.split("/");
|
|
1107
|
-
const broadcastId = pathParts[pathParts.length - 1];
|
|
1108
|
-
if (!broadcastId || broadcastId === "create") {
|
|
1109
|
-
alert("Please save the broadcast before sending a test email");
|
|
1110
|
-
return;
|
|
1111
|
-
}
|
|
1112
|
-
try {
|
|
1113
|
-
const response = await fetch(`/api/broadcasts/${broadcastId}/test`, {
|
|
1114
|
-
method: "POST",
|
|
1115
|
-
headers: {
|
|
1116
|
-
"Content-Type": "application/json"
|
|
1117
|
-
}
|
|
1118
|
-
});
|
|
1119
|
-
if (!response.ok) {
|
|
1120
|
-
const data = await response.json();
|
|
1121
|
-
throw new Error(data.error || "Failed to send test email");
|
|
1122
|
-
}
|
|
1123
|
-
alert("Test email sent successfully! Check your inbox.");
|
|
1124
|
-
} catch (error) {
|
|
1125
|
-
alert(error instanceof Error ? error.message : "Failed to send test email");
|
|
1126
|
-
}
|
|
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
|
-
] });
|
|
1235
|
-
};
|
|
1236
|
-
|
|
1237
|
-
// src/components/Broadcasts/EmailPreviewField.tsx
|
|
1238
|
-
import { useState as useState4 } from "react";
|
|
1239
|
-
import { useFormFields as useFormFields3 } from "@payloadcms/ui";
|
|
1240
|
-
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1241
|
-
var EmailPreviewField = () => {
|
|
1242
|
-
const [previewMode, setPreviewMode] = useState4("desktop");
|
|
1243
|
-
const [isValid, setIsValid] = useState4(true);
|
|
1244
|
-
const [validationSummary, setValidationSummary] = useState4("");
|
|
1245
|
-
const pluginConfig = usePluginConfigOptional();
|
|
1246
|
-
const fields = useFormFields3(([fields2]) => ({
|
|
1247
|
-
content: fields2["contentSection.content"],
|
|
1248
|
-
subject: fields2["subject"],
|
|
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
|
-
)
|
|
1364
|
-
] }),
|
|
1365
|
-
/* @__PURE__ */ jsx7("div", { style: { height: "600px" }, children: /* @__PURE__ */ jsx7(
|
|
1366
|
-
EmailPreview,
|
|
1367
|
-
{
|
|
1368
|
-
content: fields.content?.value || null,
|
|
1369
|
-
subject: fields.subject?.value || "Email Subject",
|
|
1370
|
-
preheader: fields.preheader?.value,
|
|
1371
|
-
mode: previewMode,
|
|
1372
|
-
onValidation: handleValidation,
|
|
1373
|
-
pluginConfig: pluginConfig || void 0
|
|
1374
|
-
}
|
|
1375
|
-
) })
|
|
1376
|
-
] });
|
|
1377
|
-
};
|
|
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
100
|
export {
|
|
1780
|
-
BroadcastEditor,
|
|
1781
101
|
BroadcastInlinePreview,
|
|
1782
|
-
BroadcastPreviewField,
|
|
1783
102
|
EmailPreview,
|
|
1784
|
-
|
|
1785
|
-
PluginConfigProvider,
|
|
1786
|
-
StatusBadge,
|
|
1787
|
-
createBroadcastInlinePreviewField,
|
|
1788
|
-
createBroadcastPreviewField,
|
|
1789
|
-
createEmailContentField,
|
|
1790
|
-
usePluginConfig,
|
|
1791
|
-
usePluginConfigOptional2 as usePluginConfigOptional
|
|
103
|
+
StatusBadge
|
|
1792
104
|
};
|