payload-plugin-newsletter 0.15.0 → 0.15.1

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 CHANGED
@@ -1,3 +1,31 @@
1
+ ## [0.15.1] - 2025-07-27
2
+
3
+ ### Fixed
4
+ - **Email-Compatible Block Editor** - Resolved Next.js serialization errors with custom blocks
5
+ - Custom blocks are now processed server-side using Lexical's proven BlocksFeature pattern
6
+ - Prevents "Functions cannot be passed directly to Client Components" errors
7
+ - Maintains full email compatibility while enabling custom block functionality
8
+ - **Block Validation System** - Added validation utilities for email compatibility
9
+ - `validateEmailBlocks()` warns about potentially incompatible block types
10
+ - `createEmailSafeBlocks()` processes blocks for email-safe configurations
11
+ - Automatic detection of complex field types that may not render in email clients
12
+
13
+ ### Improved
14
+ - **Server-Side Block Processing** - Enhanced `createEmailLexicalEditor()` function
15
+ - Processes custom blocks into Lexical editor configuration before client serialization
16
+ - Clean separation between email-compatible and web-only content blocks
17
+ - Better performance through pre-configured editor instances
18
+ - **Enhanced Documentation** - Updated extension points guide with new approach
19
+ - Examples showing both legacy and new server-side processing methods
20
+ - Block validation utilities documentation
21
+ - Email compatibility best practices
22
+
23
+ ### Technical
24
+ - Added `createEmailLexicalEditor()` for server-side editor configuration
25
+ - Enhanced `createEmailContentField()` to accept pre-configured editors
26
+ - New utility exports: `validateEmailBlocks`, `createEmailSafeBlocks`
27
+ - Improved TypeScript support for custom block configurations
28
+
1
29
  ## [0.15.0] - 2025-07-27
2
30
 
3
31
  ### Added
package/README.md CHANGED
@@ -727,7 +727,7 @@ newsletterPlugin({
727
727
 
728
728
  ### Extending the Broadcasts Collection (v0.15.0+)
729
729
 
730
- You can extend the Broadcasts collection with additional fields and custom blocks:
730
+ You can extend the Broadcasts collection with additional fields and custom email-compatible blocks:
731
731
 
732
732
  ```typescript
733
733
  import type { Block } from 'payload'
@@ -753,7 +753,7 @@ newsletterPlugin({
753
753
  admin: { position: 'sidebar' }
754
754
  }
755
755
  ],
756
- customBlocks: [customBlock],
756
+ customBlocks: [customBlock], // Processed server-side for email compatibility
757
757
  fieldOverrides: {
758
758
  content: (defaultField) => ({
759
759
  ...defaultField,
@@ -768,6 +768,8 @@ newsletterPlugin({
768
768
  })
769
769
  ```
770
770
 
771
+ **Note**: Custom blocks are processed server-side to ensure email compatibility and prevent Next.js serialization errors.
772
+
771
773
  For complete extensibility documentation, see the [Extension Points Guide](./docs/architecture/extension-points.md).
772
774
 
773
775
  ## Troubleshooting
@@ -542,6 +542,103 @@ init_types();
542
542
 
543
543
  // src/fields/emailContent.ts
544
544
  var import_richtext_lexical = require("@payloadcms/richtext-lexical");
545
+
546
+ // src/utils/blockValidation.ts
547
+ var EMAIL_INCOMPATIBLE_TYPES = [
548
+ "chart",
549
+ "dataTable",
550
+ "interactive",
551
+ "streamable",
552
+ "video",
553
+ "iframe",
554
+ "form",
555
+ "carousel",
556
+ "tabs",
557
+ "accordion",
558
+ "map"
559
+ ];
560
+ var validateEmailBlocks = (blocks) => {
561
+ blocks.forEach((block) => {
562
+ if (EMAIL_INCOMPATIBLE_TYPES.includes(block.slug)) {
563
+ console.warn(`\u26A0\uFE0F Block "${block.slug}" may not be email-compatible. Consider creating an email-specific version.`);
564
+ }
565
+ const hasComplexFields = block.fields?.some((field) => {
566
+ const complexTypes = ["code", "json", "richText", "blocks", "array"];
567
+ return complexTypes.includes(field.type);
568
+ });
569
+ if (hasComplexFields) {
570
+ console.warn(`\u26A0\uFE0F Block "${block.slug}" contains complex field types that may not render consistently in email clients.`);
571
+ }
572
+ });
573
+ };
574
+ var createEmailSafeBlocks = (customBlocks = []) => {
575
+ validateEmailBlocks(customBlocks);
576
+ const baseBlocks = [
577
+ {
578
+ slug: "button",
579
+ fields: [
580
+ {
581
+ name: "text",
582
+ type: "text",
583
+ label: "Button Text",
584
+ required: true
585
+ },
586
+ {
587
+ name: "url",
588
+ type: "text",
589
+ label: "Button URL",
590
+ required: true,
591
+ admin: {
592
+ description: "Enter the full URL (including https://)"
593
+ }
594
+ },
595
+ {
596
+ name: "style",
597
+ type: "select",
598
+ label: "Button Style",
599
+ defaultValue: "primary",
600
+ options: [
601
+ { label: "Primary", value: "primary" },
602
+ { label: "Secondary", value: "secondary" },
603
+ { label: "Outline", value: "outline" }
604
+ ]
605
+ }
606
+ ],
607
+ interfaceName: "EmailButton",
608
+ labels: {
609
+ singular: "Button",
610
+ plural: "Buttons"
611
+ }
612
+ },
613
+ {
614
+ slug: "divider",
615
+ fields: [
616
+ {
617
+ name: "style",
618
+ type: "select",
619
+ label: "Divider Style",
620
+ defaultValue: "solid",
621
+ options: [
622
+ { label: "Solid", value: "solid" },
623
+ { label: "Dashed", value: "dashed" },
624
+ { label: "Dotted", value: "dotted" }
625
+ ]
626
+ }
627
+ ],
628
+ interfaceName: "EmailDivider",
629
+ labels: {
630
+ singular: "Divider",
631
+ plural: "Dividers"
632
+ }
633
+ }
634
+ ];
635
+ return [
636
+ ...baseBlocks,
637
+ ...customBlocks
638
+ ];
639
+ };
640
+
641
+ // src/fields/emailContent.ts
545
642
  var createEmailSafeFeatures = (additionalBlocks) => {
546
643
  const baseBlocks = [
547
644
  {
@@ -679,16 +776,89 @@ var createEmailSafeFeatures = (additionalBlocks) => {
679
776
  })
680
777
  ];
681
778
  };
779
+ var createEmailLexicalEditor = (customBlocks = []) => {
780
+ const emailSafeBlocks = createEmailSafeBlocks(customBlocks);
781
+ return (0, import_richtext_lexical.lexicalEditor)({
782
+ features: [
783
+ // Toolbars
784
+ (0, import_richtext_lexical.FixedToolbarFeature)(),
785
+ (0, import_richtext_lexical.InlineToolbarFeature)(),
786
+ // Basic text formatting
787
+ (0, import_richtext_lexical.BoldFeature)(),
788
+ (0, import_richtext_lexical.ItalicFeature)(),
789
+ (0, import_richtext_lexical.UnderlineFeature)(),
790
+ (0, import_richtext_lexical.StrikethroughFeature)(),
791
+ // Links with enhanced configuration
792
+ (0, import_richtext_lexical.LinkFeature)({
793
+ fields: [
794
+ {
795
+ name: "url",
796
+ type: "text",
797
+ required: true,
798
+ admin: {
799
+ description: "Enter the full URL (including https://)"
800
+ }
801
+ },
802
+ {
803
+ name: "newTab",
804
+ type: "checkbox",
805
+ label: "Open in new tab",
806
+ defaultValue: false
807
+ }
808
+ ]
809
+ }),
810
+ // Lists
811
+ (0, import_richtext_lexical.OrderedListFeature)(),
812
+ (0, import_richtext_lexical.UnorderedListFeature)(),
813
+ // Headings - limited to h1, h2, h3 for email compatibility
814
+ (0, import_richtext_lexical.HeadingFeature)({
815
+ enabledHeadingSizes: ["h1", "h2", "h3"]
816
+ }),
817
+ // Basic paragraph and alignment
818
+ (0, import_richtext_lexical.ParagraphFeature)(),
819
+ (0, import_richtext_lexical.AlignFeature)(),
820
+ // Blockquotes
821
+ (0, import_richtext_lexical.BlockquoteFeature)(),
822
+ // Upload feature for images
823
+ (0, import_richtext_lexical.UploadFeature)({
824
+ collections: {
825
+ media: {
826
+ fields: [
827
+ {
828
+ name: "caption",
829
+ type: "text",
830
+ admin: {
831
+ description: "Optional caption for the image"
832
+ }
833
+ },
834
+ {
835
+ name: "altText",
836
+ type: "text",
837
+ label: "Alt Text",
838
+ required: true,
839
+ admin: {
840
+ description: "Alternative text for accessibility and when image cannot be displayed"
841
+ }
842
+ }
843
+ ]
844
+ }
845
+ }
846
+ }),
847
+ // Email-safe blocks (processed server-side)
848
+ (0, import_richtext_lexical.BlocksFeature)({
849
+ blocks: emailSafeBlocks
850
+ })
851
+ ]
852
+ });
853
+ };
682
854
  var emailSafeFeatures = createEmailSafeFeatures();
683
855
  var createEmailContentField = (overrides) => {
684
- const features = createEmailSafeFeatures(overrides?.additionalBlocks);
856
+ const editor = overrides?.editor || createEmailLexicalEditor(overrides?.additionalBlocks);
685
857
  return {
686
858
  name: "content",
687
859
  type: "richText",
688
860
  required: true,
689
- editor: (0, import_richtext_lexical.lexicalEditor)({
690
- features
691
- }),
861
+ editor,
692
862
  admin: {
693
863
  description: "Email content with limited formatting for compatibility",
694
864
  ...overrides?.admin
@@ -1040,13 +1210,15 @@ var createBroadcastsCollection = (pluginConfig) => {
1040
1210
  }
1041
1211
  },
1042
1212
  // Apply content field customization if provided
1043
- customizations?.fieldOverrides?.content ? customizations.fieldOverrides.content(createEmailContentField({
1044
- admin: { description: "Email content" },
1045
- additionalBlocks: customizations.customBlocks
1046
- })) : createEmailContentField({
1047
- admin: { description: "Email content" },
1048
- additionalBlocks: customizations?.customBlocks
1049
- })
1213
+ // Process blocks server-side to avoid client serialization issues
1214
+ (() => {
1215
+ const emailEditor = createEmailLexicalEditor(customizations?.customBlocks);
1216
+ const baseField = createEmailContentField({
1217
+ admin: { description: "Email content" },
1218
+ editor: emailEditor
1219
+ });
1220
+ return customizations?.fieldOverrides?.content ? customizations.fieldOverrides.content(baseField) : baseField;
1221
+ })()
1050
1222
  ]
1051
1223
  },
1052
1224
  {