payload-plugin-newsletter 0.15.0 → 0.16.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 +72 -0
- package/README.md +48 -2
- package/dist/collections.cjs +232 -24
- package/dist/collections.cjs.map +1 -1
- package/dist/collections.js +232 -24
- package/dist/collections.js.map +1 -1
- package/dist/components.cjs +0 -438
- package/dist/components.cjs.map +1 -1
- package/dist/components.d.cts +1 -3
- package/dist/components.d.ts +1 -3
- package/dist/components.js +0 -437
- package/dist/components.js.map +1 -1
- package/dist/fields.cjs +182 -6
- package/dist/fields.cjs.map +1 -1
- package/dist/fields.d.cts +16 -1
- package/dist/fields.d.ts +16 -1
- package/dist/fields.js +178 -5
- package/dist/fields.js.map +1 -1
- package/dist/index.cjs +232 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +232 -24
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/collections.js
CHANGED
|
@@ -529,6 +529,103 @@ import {
|
|
|
529
529
|
InlineToolbarFeature,
|
|
530
530
|
lexicalEditor
|
|
531
531
|
} from "@payloadcms/richtext-lexical";
|
|
532
|
+
|
|
533
|
+
// src/utils/blockValidation.ts
|
|
534
|
+
var EMAIL_INCOMPATIBLE_TYPES = [
|
|
535
|
+
"chart",
|
|
536
|
+
"dataTable",
|
|
537
|
+
"interactive",
|
|
538
|
+
"streamable",
|
|
539
|
+
"video",
|
|
540
|
+
"iframe",
|
|
541
|
+
"form",
|
|
542
|
+
"carousel",
|
|
543
|
+
"tabs",
|
|
544
|
+
"accordion",
|
|
545
|
+
"map"
|
|
546
|
+
];
|
|
547
|
+
var validateEmailBlocks = (blocks) => {
|
|
548
|
+
blocks.forEach((block) => {
|
|
549
|
+
if (EMAIL_INCOMPATIBLE_TYPES.includes(block.slug)) {
|
|
550
|
+
console.warn(`\u26A0\uFE0F Block "${block.slug}" may not be email-compatible. Consider creating an email-specific version.`);
|
|
551
|
+
}
|
|
552
|
+
const hasComplexFields = block.fields?.some((field) => {
|
|
553
|
+
const complexTypes = ["code", "json", "richText", "blocks", "array"];
|
|
554
|
+
return complexTypes.includes(field.type);
|
|
555
|
+
});
|
|
556
|
+
if (hasComplexFields) {
|
|
557
|
+
console.warn(`\u26A0\uFE0F Block "${block.slug}" contains complex field types that may not render consistently in email clients.`);
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
};
|
|
561
|
+
var createEmailSafeBlocks = (customBlocks = []) => {
|
|
562
|
+
validateEmailBlocks(customBlocks);
|
|
563
|
+
const baseBlocks = [
|
|
564
|
+
{
|
|
565
|
+
slug: "button",
|
|
566
|
+
fields: [
|
|
567
|
+
{
|
|
568
|
+
name: "text",
|
|
569
|
+
type: "text",
|
|
570
|
+
label: "Button Text",
|
|
571
|
+
required: true
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
name: "url",
|
|
575
|
+
type: "text",
|
|
576
|
+
label: "Button URL",
|
|
577
|
+
required: true,
|
|
578
|
+
admin: {
|
|
579
|
+
description: "Enter the full URL (including https://)"
|
|
580
|
+
}
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
name: "style",
|
|
584
|
+
type: "select",
|
|
585
|
+
label: "Button Style",
|
|
586
|
+
defaultValue: "primary",
|
|
587
|
+
options: [
|
|
588
|
+
{ label: "Primary", value: "primary" },
|
|
589
|
+
{ label: "Secondary", value: "secondary" },
|
|
590
|
+
{ label: "Outline", value: "outline" }
|
|
591
|
+
]
|
|
592
|
+
}
|
|
593
|
+
],
|
|
594
|
+
interfaceName: "EmailButton",
|
|
595
|
+
labels: {
|
|
596
|
+
singular: "Button",
|
|
597
|
+
plural: "Buttons"
|
|
598
|
+
}
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
slug: "divider",
|
|
602
|
+
fields: [
|
|
603
|
+
{
|
|
604
|
+
name: "style",
|
|
605
|
+
type: "select",
|
|
606
|
+
label: "Divider Style",
|
|
607
|
+
defaultValue: "solid",
|
|
608
|
+
options: [
|
|
609
|
+
{ label: "Solid", value: "solid" },
|
|
610
|
+
{ label: "Dashed", value: "dashed" },
|
|
611
|
+
{ label: "Dotted", value: "dotted" }
|
|
612
|
+
]
|
|
613
|
+
}
|
|
614
|
+
],
|
|
615
|
+
interfaceName: "EmailDivider",
|
|
616
|
+
labels: {
|
|
617
|
+
singular: "Divider",
|
|
618
|
+
plural: "Dividers"
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
];
|
|
622
|
+
return [
|
|
623
|
+
...baseBlocks,
|
|
624
|
+
...customBlocks
|
|
625
|
+
];
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
// src/fields/emailContent.ts
|
|
532
629
|
var createEmailSafeFeatures = (additionalBlocks) => {
|
|
533
630
|
const baseBlocks = [
|
|
534
631
|
{
|
|
@@ -666,16 +763,89 @@ var createEmailSafeFeatures = (additionalBlocks) => {
|
|
|
666
763
|
})
|
|
667
764
|
];
|
|
668
765
|
};
|
|
766
|
+
var createEmailLexicalEditor = (customBlocks = []) => {
|
|
767
|
+
const emailSafeBlocks = createEmailSafeBlocks(customBlocks);
|
|
768
|
+
return lexicalEditor({
|
|
769
|
+
features: [
|
|
770
|
+
// Toolbars
|
|
771
|
+
FixedToolbarFeature(),
|
|
772
|
+
InlineToolbarFeature(),
|
|
773
|
+
// Basic text formatting
|
|
774
|
+
BoldFeature(),
|
|
775
|
+
ItalicFeature(),
|
|
776
|
+
UnderlineFeature(),
|
|
777
|
+
StrikethroughFeature(),
|
|
778
|
+
// Links with enhanced configuration
|
|
779
|
+
LinkFeature({
|
|
780
|
+
fields: [
|
|
781
|
+
{
|
|
782
|
+
name: "url",
|
|
783
|
+
type: "text",
|
|
784
|
+
required: true,
|
|
785
|
+
admin: {
|
|
786
|
+
description: "Enter the full URL (including https://)"
|
|
787
|
+
}
|
|
788
|
+
},
|
|
789
|
+
{
|
|
790
|
+
name: "newTab",
|
|
791
|
+
type: "checkbox",
|
|
792
|
+
label: "Open in new tab",
|
|
793
|
+
defaultValue: false
|
|
794
|
+
}
|
|
795
|
+
]
|
|
796
|
+
}),
|
|
797
|
+
// Lists
|
|
798
|
+
OrderedListFeature(),
|
|
799
|
+
UnorderedListFeature(),
|
|
800
|
+
// Headings - limited to h1, h2, h3 for email compatibility
|
|
801
|
+
HeadingFeature({
|
|
802
|
+
enabledHeadingSizes: ["h1", "h2", "h3"]
|
|
803
|
+
}),
|
|
804
|
+
// Basic paragraph and alignment
|
|
805
|
+
ParagraphFeature(),
|
|
806
|
+
AlignFeature(),
|
|
807
|
+
// Blockquotes
|
|
808
|
+
BlockquoteFeature(),
|
|
809
|
+
// Upload feature for images
|
|
810
|
+
UploadFeature({
|
|
811
|
+
collections: {
|
|
812
|
+
media: {
|
|
813
|
+
fields: [
|
|
814
|
+
{
|
|
815
|
+
name: "caption",
|
|
816
|
+
type: "text",
|
|
817
|
+
admin: {
|
|
818
|
+
description: "Optional caption for the image"
|
|
819
|
+
}
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
name: "altText",
|
|
823
|
+
type: "text",
|
|
824
|
+
label: "Alt Text",
|
|
825
|
+
required: true,
|
|
826
|
+
admin: {
|
|
827
|
+
description: "Alternative text for accessibility and when image cannot be displayed"
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
]
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}),
|
|
834
|
+
// Email-safe blocks (processed server-side)
|
|
835
|
+
BlocksFeature({
|
|
836
|
+
blocks: emailSafeBlocks
|
|
837
|
+
})
|
|
838
|
+
]
|
|
839
|
+
});
|
|
840
|
+
};
|
|
669
841
|
var emailSafeFeatures = createEmailSafeFeatures();
|
|
670
842
|
var createEmailContentField = (overrides) => {
|
|
671
|
-
const
|
|
843
|
+
const editor = overrides?.editor || createEmailLexicalEditor(overrides?.additionalBlocks);
|
|
672
844
|
return {
|
|
673
845
|
name: "content",
|
|
674
846
|
type: "richText",
|
|
675
847
|
required: true,
|
|
676
|
-
editor
|
|
677
|
-
features
|
|
678
|
-
}),
|
|
848
|
+
editor,
|
|
679
849
|
admin: {
|
|
680
850
|
description: "Email content with limited formatting for compatibility",
|
|
681
851
|
...overrides?.admin
|
|
@@ -985,6 +1155,12 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
985
1155
|
const customizations = pluginConfig.customizations?.broadcasts;
|
|
986
1156
|
return {
|
|
987
1157
|
slug: "broadcasts",
|
|
1158
|
+
versions: {
|
|
1159
|
+
drafts: {
|
|
1160
|
+
autosave: true,
|
|
1161
|
+
schedulePublish: true
|
|
1162
|
+
}
|
|
1163
|
+
},
|
|
988
1164
|
labels: {
|
|
989
1165
|
singular: "Broadcast",
|
|
990
1166
|
plural: "Broadcasts"
|
|
@@ -992,7 +1168,7 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
992
1168
|
admin: {
|
|
993
1169
|
useAsTitle: "subject",
|
|
994
1170
|
description: "Individual email campaigns sent to subscribers",
|
|
995
|
-
defaultColumns: ["subject", "
|
|
1171
|
+
defaultColumns: ["subject", "_status", "status", "sentAt", "recipientCount"]
|
|
996
1172
|
},
|
|
997
1173
|
fields: [
|
|
998
1174
|
{
|
|
@@ -1027,13 +1203,15 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1027
1203
|
}
|
|
1028
1204
|
},
|
|
1029
1205
|
// Apply content field customization if provided
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1206
|
+
// Process blocks server-side to avoid client serialization issues
|
|
1207
|
+
(() => {
|
|
1208
|
+
const emailEditor = createEmailLexicalEditor(customizations?.customBlocks);
|
|
1209
|
+
const baseField = createEmailContentField({
|
|
1210
|
+
admin: { description: "Email content" },
|
|
1211
|
+
editor: emailEditor
|
|
1212
|
+
});
|
|
1213
|
+
return customizations?.fieldOverrides?.content ? customizations.fieldOverrides.content(baseField) : baseField;
|
|
1214
|
+
})()
|
|
1037
1215
|
]
|
|
1038
1216
|
},
|
|
1039
1217
|
{
|
|
@@ -1203,18 +1381,6 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1203
1381
|
condition: () => false
|
|
1204
1382
|
// Hidden by default
|
|
1205
1383
|
}
|
|
1206
|
-
},
|
|
1207
|
-
// UI Field for custom actions in list view
|
|
1208
|
-
{
|
|
1209
|
-
name: "actions",
|
|
1210
|
-
type: "ui",
|
|
1211
|
-
admin: {
|
|
1212
|
-
components: {
|
|
1213
|
-
Cell: "payload-plugin-newsletter/components#ActionsCell",
|
|
1214
|
-
Field: "payload-plugin-newsletter/components#EmptyField"
|
|
1215
|
-
},
|
|
1216
|
-
disableListColumn: false
|
|
1217
|
-
}
|
|
1218
1384
|
}
|
|
1219
1385
|
],
|
|
1220
1386
|
hooks: {
|
|
@@ -1328,6 +1494,48 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1328
1494
|
req.payload.logger.error("Failed to delete broadcast from provider:", error);
|
|
1329
1495
|
}
|
|
1330
1496
|
return doc;
|
|
1497
|
+
},
|
|
1498
|
+
// Hook to send when published
|
|
1499
|
+
async ({ doc, req }) => {
|
|
1500
|
+
if (doc._status === "published" && doc.providerId) {
|
|
1501
|
+
if (doc.status === "sent" || doc.status === "sending") {
|
|
1502
|
+
return doc;
|
|
1503
|
+
}
|
|
1504
|
+
try {
|
|
1505
|
+
const broadcastConfig = pluginConfig.providers?.broadcast;
|
|
1506
|
+
const resendConfig = pluginConfig.providers?.resend;
|
|
1507
|
+
if (!broadcastConfig && !resendConfig) {
|
|
1508
|
+
req.payload.logger.error("No provider configured for sending");
|
|
1509
|
+
return doc;
|
|
1510
|
+
}
|
|
1511
|
+
if (broadcastConfig) {
|
|
1512
|
+
const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
|
|
1513
|
+
const provider = new BroadcastApiProvider2(broadcastConfig);
|
|
1514
|
+
await provider.send(doc.providerId);
|
|
1515
|
+
}
|
|
1516
|
+
await req.payload.update({
|
|
1517
|
+
collection: "broadcasts",
|
|
1518
|
+
id: doc.id,
|
|
1519
|
+
data: {
|
|
1520
|
+
status: "sending" /* SENDING */,
|
|
1521
|
+
sentAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1522
|
+
},
|
|
1523
|
+
req
|
|
1524
|
+
});
|
|
1525
|
+
req.payload.logger.info(`Broadcast ${doc.id} sent successfully`);
|
|
1526
|
+
} catch (error) {
|
|
1527
|
+
req.payload.logger.error(`Failed to send broadcast ${doc.id}:`, error);
|
|
1528
|
+
await req.payload.update({
|
|
1529
|
+
collection: "broadcasts",
|
|
1530
|
+
id: doc.id,
|
|
1531
|
+
data: {
|
|
1532
|
+
status: "failed" /* FAILED */
|
|
1533
|
+
},
|
|
1534
|
+
req
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
return doc;
|
|
1331
1539
|
}
|
|
1332
1540
|
]
|
|
1333
1541
|
}
|