payload-plugin-newsletter 0.16.10 → 0.17.0
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 +28 -0
- package/dist/collections.cjs +73 -31
- package/dist/collections.cjs.map +1 -1
- package/dist/collections.js +73 -31
- package/dist/collections.js.map +1 -1
- package/dist/components.cjs +295 -275
- package/dist/components.cjs.map +1 -1
- package/dist/components.js +305 -285
- package/dist/components.js.map +1 -1
- package/dist/index.cjs +118 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +118 -33
- package/dist/index.js.map +1 -1
- package/dist/types.d.cts +7 -0
- package/dist/types.d.ts +7 -0
- package/dist/utils.cjs +64 -28
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +1 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +64 -28
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3174,62 +3174,73 @@ async function convertToEmailSafeHtml(editorState, options) {
|
|
|
3174
3174
|
if (!editorState) {
|
|
3175
3175
|
return "";
|
|
3176
3176
|
}
|
|
3177
|
-
const rawHtml = await lexicalToEmailHtml(editorState, options?.mediaUrl);
|
|
3177
|
+
const rawHtml = await lexicalToEmailHtml(editorState, options?.mediaUrl, options?.customBlockConverter);
|
|
3178
3178
|
const sanitizedHtml = DOMPurify2.sanitize(rawHtml, EMAIL_SAFE_CONFIG);
|
|
3179
3179
|
if (options?.wrapInTemplate) {
|
|
3180
3180
|
return wrapInEmailTemplate(sanitizedHtml, options.preheader);
|
|
3181
3181
|
}
|
|
3182
3182
|
return sanitizedHtml;
|
|
3183
3183
|
}
|
|
3184
|
-
async function lexicalToEmailHtml(editorState, mediaUrl) {
|
|
3184
|
+
async function lexicalToEmailHtml(editorState, mediaUrl, customBlockConverter) {
|
|
3185
3185
|
const { root } = editorState;
|
|
3186
3186
|
if (!root || !root.children) {
|
|
3187
3187
|
return "";
|
|
3188
3188
|
}
|
|
3189
|
-
const
|
|
3190
|
-
|
|
3189
|
+
const htmlParts = await Promise.all(
|
|
3190
|
+
root.children.map((node) => convertNode(node, mediaUrl, customBlockConverter))
|
|
3191
|
+
);
|
|
3192
|
+
return htmlParts.join("");
|
|
3191
3193
|
}
|
|
3192
|
-
function convertNode(node, mediaUrl) {
|
|
3194
|
+
async function convertNode(node, mediaUrl, customBlockConverter) {
|
|
3193
3195
|
switch (node.type) {
|
|
3194
3196
|
case "paragraph":
|
|
3195
|
-
return convertParagraph(node, mediaUrl);
|
|
3197
|
+
return convertParagraph(node, mediaUrl, customBlockConverter);
|
|
3196
3198
|
case "heading":
|
|
3197
|
-
return convertHeading(node, mediaUrl);
|
|
3199
|
+
return convertHeading(node, mediaUrl, customBlockConverter);
|
|
3198
3200
|
case "list":
|
|
3199
|
-
return convertList(node, mediaUrl);
|
|
3201
|
+
return convertList(node, mediaUrl, customBlockConverter);
|
|
3200
3202
|
case "listitem":
|
|
3201
|
-
return convertListItem(node, mediaUrl);
|
|
3203
|
+
return convertListItem(node, mediaUrl, customBlockConverter);
|
|
3202
3204
|
case "blockquote":
|
|
3203
|
-
return convertBlockquote(node, mediaUrl);
|
|
3205
|
+
return convertBlockquote(node, mediaUrl, customBlockConverter);
|
|
3204
3206
|
case "text":
|
|
3205
3207
|
return convertText(node);
|
|
3206
3208
|
case "link":
|
|
3207
|
-
return convertLink(node, mediaUrl);
|
|
3209
|
+
return convertLink(node, mediaUrl, customBlockConverter);
|
|
3208
3210
|
case "linebreak":
|
|
3209
3211
|
return "<br>";
|
|
3210
3212
|
case "upload":
|
|
3211
3213
|
return convertUpload(node, mediaUrl);
|
|
3212
3214
|
case "block":
|
|
3213
|
-
return convertBlock(node, mediaUrl);
|
|
3215
|
+
return await convertBlock(node, mediaUrl, customBlockConverter);
|
|
3214
3216
|
default:
|
|
3215
3217
|
if (node.children) {
|
|
3216
|
-
|
|
3218
|
+
const childParts = await Promise.all(
|
|
3219
|
+
node.children.map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
3220
|
+
);
|
|
3221
|
+
return childParts.join("");
|
|
3217
3222
|
}
|
|
3218
3223
|
return "";
|
|
3219
3224
|
}
|
|
3220
3225
|
}
|
|
3221
|
-
function convertParagraph(node, mediaUrl) {
|
|
3226
|
+
async function convertParagraph(node, mediaUrl, customBlockConverter) {
|
|
3222
3227
|
const align = getAlignment(node.format);
|
|
3223
|
-
const
|
|
3228
|
+
const childParts = await Promise.all(
|
|
3229
|
+
(node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
3230
|
+
);
|
|
3231
|
+
const children = childParts.join("");
|
|
3224
3232
|
if (!children.trim()) {
|
|
3225
3233
|
return '<p style="margin: 0 0 16px 0; min-height: 1em;"> </p>';
|
|
3226
3234
|
}
|
|
3227
3235
|
return `<p style="margin: 0 0 16px 0; text-align: ${align};">${children}</p>`;
|
|
3228
3236
|
}
|
|
3229
|
-
function convertHeading(node, mediaUrl) {
|
|
3237
|
+
async function convertHeading(node, mediaUrl, customBlockConverter) {
|
|
3230
3238
|
const tag = node.tag || "h1";
|
|
3231
3239
|
const align = getAlignment(node.format);
|
|
3232
|
-
const
|
|
3240
|
+
const childParts = await Promise.all(
|
|
3241
|
+
(node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
3242
|
+
);
|
|
3243
|
+
const children = childParts.join("");
|
|
3233
3244
|
const styles2 = {
|
|
3234
3245
|
h1: "font-size: 32px; font-weight: 700; margin: 0 0 24px 0; line-height: 1.2;",
|
|
3235
3246
|
h2: "font-size: 24px; font-weight: 600; margin: 0 0 16px 0; line-height: 1.3;",
|
|
@@ -3238,18 +3249,27 @@ function convertHeading(node, mediaUrl) {
|
|
|
3238
3249
|
const style = `${styles2[tag] || styles2.h3} text-align: ${align};`;
|
|
3239
3250
|
return `<${tag} style="${style}">${children}</${tag}>`;
|
|
3240
3251
|
}
|
|
3241
|
-
function convertList(node, mediaUrl) {
|
|
3252
|
+
async function convertList(node, mediaUrl, customBlockConverter) {
|
|
3242
3253
|
const tag = node.listType === "number" ? "ol" : "ul";
|
|
3243
|
-
const
|
|
3254
|
+
const childParts = await Promise.all(
|
|
3255
|
+
(node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
3256
|
+
);
|
|
3257
|
+
const children = childParts.join("");
|
|
3244
3258
|
const style = tag === "ul" ? "margin: 0 0 16px 0; padding-left: 24px; list-style-type: disc;" : "margin: 0 0 16px 0; padding-left: 24px; list-style-type: decimal;";
|
|
3245
3259
|
return `<${tag} style="${style}">${children}</${tag}>`;
|
|
3246
3260
|
}
|
|
3247
|
-
function convertListItem(node, mediaUrl) {
|
|
3248
|
-
const
|
|
3261
|
+
async function convertListItem(node, mediaUrl, customBlockConverter) {
|
|
3262
|
+
const childParts = await Promise.all(
|
|
3263
|
+
(node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
3264
|
+
);
|
|
3265
|
+
const children = childParts.join("");
|
|
3249
3266
|
return `<li style="margin: 0 0 8px 0;">${children}</li>`;
|
|
3250
3267
|
}
|
|
3251
|
-
function convertBlockquote(node, mediaUrl) {
|
|
3252
|
-
const
|
|
3268
|
+
async function convertBlockquote(node, mediaUrl, customBlockConverter) {
|
|
3269
|
+
const childParts = await Promise.all(
|
|
3270
|
+
(node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
3271
|
+
);
|
|
3272
|
+
const children = childParts.join("");
|
|
3253
3273
|
const style = "margin: 0 0 16px 0; padding-left: 16px; border-left: 4px solid #e5e7eb; color: #6b7280;";
|
|
3254
3274
|
return `<blockquote style="${style}">${children}</blockquote>`;
|
|
3255
3275
|
}
|
|
@@ -3269,8 +3289,11 @@ function convertText(node) {
|
|
|
3269
3289
|
}
|
|
3270
3290
|
return text;
|
|
3271
3291
|
}
|
|
3272
|
-
function convertLink(node, mediaUrl) {
|
|
3273
|
-
const
|
|
3292
|
+
async function convertLink(node, mediaUrl, customBlockConverter) {
|
|
3293
|
+
const childParts = await Promise.all(
|
|
3294
|
+
(node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
3295
|
+
);
|
|
3296
|
+
const children = childParts.join("");
|
|
3274
3297
|
const url = node.fields?.url || "#";
|
|
3275
3298
|
const newTab = node.fields?.newTab ?? false;
|
|
3276
3299
|
const targetAttr = newTab ? ' target="_blank"' : "";
|
|
@@ -3301,8 +3324,18 @@ function convertUpload(node, mediaUrl) {
|
|
|
3301
3324
|
}
|
|
3302
3325
|
return `<div style="margin: 0 0 16px 0; text-align: center;">${imgHtml}</div>`;
|
|
3303
3326
|
}
|
|
3304
|
-
function convertBlock(node, mediaUrl) {
|
|
3305
|
-
const blockType = node.fields?.blockName;
|
|
3327
|
+
async function convertBlock(node, mediaUrl, customBlockConverter) {
|
|
3328
|
+
const blockType = node.fields?.blockName || node.blockName;
|
|
3329
|
+
if (customBlockConverter) {
|
|
3330
|
+
try {
|
|
3331
|
+
const customHtml = await customBlockConverter(node, mediaUrl);
|
|
3332
|
+
if (customHtml) {
|
|
3333
|
+
return customHtml;
|
|
3334
|
+
}
|
|
3335
|
+
} catch (error) {
|
|
3336
|
+
console.error(`Custom block converter error for ${blockType}:`, error);
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3306
3339
|
switch (blockType) {
|
|
3307
3340
|
case "button":
|
|
3308
3341
|
return convertButtonBlock(node.fields);
|
|
@@ -3310,7 +3343,10 @@ function convertBlock(node, mediaUrl) {
|
|
|
3310
3343
|
return convertDividerBlock(node.fields);
|
|
3311
3344
|
default:
|
|
3312
3345
|
if (node.children) {
|
|
3313
|
-
|
|
3346
|
+
const childParts = await Promise.all(
|
|
3347
|
+
node.children.map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
3348
|
+
);
|
|
3349
|
+
return childParts.join("");
|
|
3314
3350
|
}
|
|
3315
3351
|
return "";
|
|
3316
3352
|
}
|
|
@@ -3437,7 +3473,8 @@ var createTestBroadcastEndpoint = (config, collectionSlug) => {
|
|
|
3437
3473
|
}
|
|
3438
3474
|
const htmlContent = await convertToEmailSafeHtml(broadcast.content, {
|
|
3439
3475
|
wrapInTemplate: true,
|
|
3440
|
-
preheader: broadcast.preheader
|
|
3476
|
+
preheader: broadcast.preheader,
|
|
3477
|
+
customBlockConverter: config.customizations?.broadcasts?.customBlockConverter
|
|
3441
3478
|
});
|
|
3442
3479
|
const emailService = req.payload.newsletterEmailService;
|
|
3443
3480
|
if (!emailService) {
|
|
@@ -3475,6 +3512,47 @@ var createTestBroadcastEndpoint = (config, collectionSlug) => {
|
|
|
3475
3512
|
};
|
|
3476
3513
|
};
|
|
3477
3514
|
|
|
3515
|
+
// src/endpoints/broadcasts/preview.ts
|
|
3516
|
+
var createBroadcastPreviewEndpoint = (config, collectionSlug) => {
|
|
3517
|
+
return {
|
|
3518
|
+
path: `/api/${collectionSlug}/preview`,
|
|
3519
|
+
method: "post",
|
|
3520
|
+
handler: async (req) => {
|
|
3521
|
+
try {
|
|
3522
|
+
const data = await (req.json?.() || Promise.resolve({}));
|
|
3523
|
+
const { content, preheader, subject } = data;
|
|
3524
|
+
if (!content) {
|
|
3525
|
+
return Response.json({
|
|
3526
|
+
success: false,
|
|
3527
|
+
error: "Content is required for preview"
|
|
3528
|
+
}, { status: 400 });
|
|
3529
|
+
}
|
|
3530
|
+
const mediaUrl = req.payload.config.serverURL ? `${req.payload.config.serverURL}/api/media` : "/api/media";
|
|
3531
|
+
const htmlContent = await convertToEmailSafeHtml(content, {
|
|
3532
|
+
wrapInTemplate: true,
|
|
3533
|
+
preheader,
|
|
3534
|
+
mediaUrl,
|
|
3535
|
+
customBlockConverter: config.customizations?.broadcasts?.customBlockConverter
|
|
3536
|
+
});
|
|
3537
|
+
return Response.json({
|
|
3538
|
+
success: true,
|
|
3539
|
+
preview: {
|
|
3540
|
+
subject: subject || "Preview",
|
|
3541
|
+
preheader: preheader || "",
|
|
3542
|
+
html: htmlContent
|
|
3543
|
+
}
|
|
3544
|
+
});
|
|
3545
|
+
} catch (error) {
|
|
3546
|
+
console.error("Failed to generate email preview:", error);
|
|
3547
|
+
return Response.json({
|
|
3548
|
+
success: false,
|
|
3549
|
+
error: "Failed to generate email preview"
|
|
3550
|
+
}, { status: 500 });
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
};
|
|
3554
|
+
};
|
|
3555
|
+
|
|
3478
3556
|
// src/endpoints/broadcasts/index.ts
|
|
3479
3557
|
var createBroadcastManagementEndpoints = (config) => {
|
|
3480
3558
|
if (!config.features?.newsletterManagement?.enabled) {
|
|
@@ -3484,7 +3562,8 @@ var createBroadcastManagementEndpoints = (config) => {
|
|
|
3484
3562
|
return [
|
|
3485
3563
|
createSendBroadcastEndpoint(config, collectionSlug),
|
|
3486
3564
|
createScheduleBroadcastEndpoint(config, collectionSlug),
|
|
3487
|
-
createTestBroadcastEndpoint(config, collectionSlug)
|
|
3565
|
+
createTestBroadcastEndpoint(config, collectionSlug),
|
|
3566
|
+
createBroadcastPreviewEndpoint(config, collectionSlug)
|
|
3488
3567
|
];
|
|
3489
3568
|
};
|
|
3490
3569
|
|
|
@@ -4410,7 +4489,9 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
4410
4489
|
const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
|
|
4411
4490
|
const provider = new BroadcastApiProvider2(providerConfig);
|
|
4412
4491
|
req.payload.logger.info("Converting content to HTML...");
|
|
4413
|
-
const htmlContent = await convertToEmailSafeHtml(doc.contentSection?.content
|
|
4492
|
+
const htmlContent = await convertToEmailSafeHtml(doc.contentSection?.content, {
|
|
4493
|
+
customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
|
|
4494
|
+
});
|
|
4414
4495
|
if (!htmlContent || htmlContent.trim() === "") {
|
|
4415
4496
|
req.payload.logger.info("Skipping provider sync - content is empty after conversion");
|
|
4416
4497
|
return doc;
|
|
@@ -4507,7 +4588,9 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
4507
4588
|
return doc;
|
|
4508
4589
|
}
|
|
4509
4590
|
req.payload.logger.info("Creating broadcast in provider (deferred from initial create)...");
|
|
4510
|
-
const htmlContent = await convertToEmailSafeHtml(doc.contentSection?.content
|
|
4591
|
+
const htmlContent = await convertToEmailSafeHtml(doc.contentSection?.content, {
|
|
4592
|
+
customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
|
|
4593
|
+
});
|
|
4511
4594
|
if (!htmlContent || htmlContent.trim() === "") {
|
|
4512
4595
|
req.payload.logger.info("Skipping provider sync - content is empty after conversion");
|
|
4513
4596
|
return doc;
|
|
@@ -4566,7 +4649,9 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
4566
4649
|
updates.preheader = doc.contentSection?.preheader;
|
|
4567
4650
|
}
|
|
4568
4651
|
if (JSON.stringify(doc.contentSection?.content) !== JSON.stringify(previousDoc?.contentSection?.content)) {
|
|
4569
|
-
updates.content = await convertToEmailSafeHtml(doc.contentSection?.content
|
|
4652
|
+
updates.content = await convertToEmailSafeHtml(doc.contentSection?.content, {
|
|
4653
|
+
customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
|
|
4654
|
+
});
|
|
4570
4655
|
}
|
|
4571
4656
|
if (doc.settings?.trackOpens !== previousDoc?.settings?.trackOpens) {
|
|
4572
4657
|
updates.trackOpens = doc.settings.trackOpens;
|