payload-plugin-newsletter 0.18.0 → 0.20.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/dist/index.d.cts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { PayloadRequest, Config } from 'payload';
2
- import { BroadcastProvider, NewsletterPluginConfig } from './types.cjs';
2
+ import { NewsletterPluginConfig, BroadcastProvider } from './types.cjs';
3
+ import React from 'react';
3
4
 
4
5
  interface NextApiRequest {
5
6
  cookies?: {
@@ -66,6 +67,17 @@ declare const requireAuth: <P extends {
66
67
  */
67
68
  declare const isAuthenticated: (req: PayloadRequest | NextApiRequest, secret: string) => boolean;
68
69
 
70
+ declare const PluginConfigProvider: React.FC<{
71
+ config: NewsletterPluginConfig;
72
+ children: React.ReactNode;
73
+ }>;
74
+ declare const usePluginConfig: () => NewsletterPluginConfig;
75
+ /**
76
+ * Hook to safely access plugin config without throwing if context is not available
77
+ * This is useful for components that might be used outside of the plugin context
78
+ */
79
+ declare const usePluginConfigOptional: () => NewsletterPluginConfig | null;
80
+
69
81
  declare module 'payload' {
70
82
  interface BasePayload {
71
83
  newsletterEmailService?: any;
@@ -75,4 +87,4 @@ declare module 'payload' {
75
87
  }
76
88
  declare const newsletterPlugin: (pluginConfig: NewsletterPluginConfig) => (incomingConfig: Config) => Config;
77
89
 
78
- export { newsletterPlugin as default, getServerSideAuth, getTokenFromRequest, isAuthenticated, newsletterPlugin, requireAuth, verifyToken };
90
+ export { PluginConfigProvider, newsletterPlugin as default, getServerSideAuth, getTokenFromRequest, isAuthenticated, newsletterPlugin, requireAuth, usePluginConfig, usePluginConfigOptional, verifyToken };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { PayloadRequest, Config } from 'payload';
2
- import { BroadcastProvider, NewsletterPluginConfig } from './types.js';
2
+ import { NewsletterPluginConfig, BroadcastProvider } from './types.js';
3
+ import React from 'react';
3
4
 
4
5
  interface NextApiRequest {
5
6
  cookies?: {
@@ -66,6 +67,17 @@ declare const requireAuth: <P extends {
66
67
  */
67
68
  declare const isAuthenticated: (req: PayloadRequest | NextApiRequest, secret: string) => boolean;
68
69
 
70
+ declare const PluginConfigProvider: React.FC<{
71
+ config: NewsletterPluginConfig;
72
+ children: React.ReactNode;
73
+ }>;
74
+ declare const usePluginConfig: () => NewsletterPluginConfig;
75
+ /**
76
+ * Hook to safely access plugin config without throwing if context is not available
77
+ * This is useful for components that might be used outside of the plugin context
78
+ */
79
+ declare const usePluginConfigOptional: () => NewsletterPluginConfig | null;
80
+
69
81
  declare module 'payload' {
70
82
  interface BasePayload {
71
83
  newsletterEmailService?: any;
@@ -75,4 +87,4 @@ declare module 'payload' {
75
87
  }
76
88
  declare const newsletterPlugin: (pluginConfig: NewsletterPluginConfig) => (incomingConfig: Config) => Config;
77
89
 
78
- export { newsletterPlugin as default, getServerSideAuth, getTokenFromRequest, isAuthenticated, newsletterPlugin, requireAuth, verifyToken };
90
+ export { PluginConfigProvider, newsletterPlugin as default, getServerSideAuth, getTokenFromRequest, isAuthenticated, newsletterPlugin, requireAuth, usePluginConfig, usePluginConfigOptional, verifyToken };
package/dist/index.js CHANGED
@@ -3584,6 +3584,12 @@ async function convertToEmailSafeHtml(editorState, options) {
3584
3584
  const rawHtml = await lexicalToEmailHtml(editorState, options?.mediaUrl, options?.customBlockConverter);
3585
3585
  const sanitizedHtml = DOMPurify2.sanitize(rawHtml, EMAIL_SAFE_CONFIG);
3586
3586
  if (options?.wrapInTemplate) {
3587
+ if (options.customWrapper) {
3588
+ return await Promise.resolve(options.customWrapper(sanitizedHtml, {
3589
+ preheader: options.preheader,
3590
+ subject: options.subject
3591
+ }));
3592
+ }
3587
3593
  return wrapInEmailTemplate(sanitizedHtml, options.preheader);
3588
3594
  }
3589
3595
  return sanitizedHtml;
@@ -3637,9 +3643,9 @@ async function convertParagraph(node, mediaUrl, customBlockConverter) {
3637
3643
  );
3638
3644
  const children = childParts.join("");
3639
3645
  if (!children.trim()) {
3640
- return '<p style="margin: 0 0 16px 0; min-height: 1em;">&nbsp;</p>';
3646
+ return '<p class="mobile-margin-bottom-16" style="margin: 0 0 16px 0; min-height: 1em;">&nbsp;</p>';
3641
3647
  }
3642
- return `<p style="margin: 0 0 16px 0; text-align: ${align};">${children}</p>`;
3648
+ 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>`;
3643
3649
  }
3644
3650
  async function convertHeading(node, mediaUrl, customBlockConverter) {
3645
3651
  const tag = node.tag || "h1";
@@ -3653,8 +3659,14 @@ async function convertHeading(node, mediaUrl, customBlockConverter) {
3653
3659
  h2: "font-size: 24px; font-weight: 600; margin: 0 0 16px 0; line-height: 1.3;",
3654
3660
  h3: "font-size: 20px; font-weight: 600; margin: 0 0 12px 0; line-height: 1.4;"
3655
3661
  };
3662
+ const mobileClasses = {
3663
+ h1: "mobile-font-size-24",
3664
+ h2: "mobile-font-size-20",
3665
+ h3: "mobile-font-size-16"
3666
+ };
3656
3667
  const style = `${styles2[tag] || styles2.h3} text-align: ${align};`;
3657
- return `<${tag} style="${style}">${children}</${tag}>`;
3668
+ const mobileClass = mobileClasses[tag] || mobileClasses.h3;
3669
+ return `<${tag} class="${mobileClass}" style="${style}">${children}</${tag}>`;
3658
3670
  }
3659
3671
  async function convertList(node, mediaUrl, customBlockConverter) {
3660
3672
  const tag = node.listType === "number" ? "ol" : "ul";
@@ -3662,8 +3674,8 @@ async function convertList(node, mediaUrl, customBlockConverter) {
3662
3674
  (node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
3663
3675
  );
3664
3676
  const children = childParts.join("");
3665
- 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;";
3666
- return `<${tag} style="${style}">${children}</${tag}>`;
3677
+ 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;";
3678
+ return `<${tag} class="mobile-margin-bottom-16" style="${style}">${children}</${tag}>`;
3667
3679
  }
3668
3680
  async function convertListItem(node, mediaUrl, customBlockConverter) {
3669
3681
  const childParts = await Promise.all(
@@ -3720,16 +3732,16 @@ function convertUpload(node, mediaUrl) {
3720
3732
  }
3721
3733
  const alt = node.fields?.altText || upload.alt || "";
3722
3734
  const caption = node.fields?.caption || "";
3723
- const imgHtml = `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}" style="max-width: 100%; height: auto; display: block; margin: 0 auto;" />`;
3735
+ 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;" />`;
3724
3736
  if (caption) {
3725
3737
  return `
3726
- <div style="margin: 0 0 16px 0; text-align: center;">
3738
+ <div style="margin: 0 0 16px 0; text-align: center;" class="mobile-margin-bottom-16">
3727
3739
  ${imgHtml}
3728
- <p style="margin: 8px 0 0 0; font-size: 14px; color: #6b7280; font-style: italic;">${escapeHtml(caption)}</p>
3740
+ <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>
3729
3741
  </div>
3730
3742
  `;
3731
3743
  }
3732
- return `<div style="margin: 0 0 16px 0; text-align: center;">${imgHtml}</div>`;
3744
+ return `<div style="margin: 0 0 16px 0; text-align: center;" class="mobile-margin-bottom-16">${imgHtml}</div>`;
3733
3745
  }
3734
3746
  async function convertBlock(node, mediaUrl, customBlockConverter) {
3735
3747
  const blockType = node.fields?.blockName || node.blockName;
@@ -3802,11 +3814,14 @@ function escapeHtml(text) {
3802
3814
  }
3803
3815
  function wrapInEmailTemplate(content, preheader) {
3804
3816
  return `<!DOCTYPE html>
3805
- <html lang="en">
3817
+ <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">
3806
3818
  <head>
3807
3819
  <meta charset="UTF-8">
3808
3820
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
3809
- <title>Email</title>
3821
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
3822
+ <meta name="x-apple-disable-message-reformatting">
3823
+ <title>Newsletter</title>
3824
+
3810
3825
  <!--[if mso]>
3811
3826
  <noscript>
3812
3827
  <xml>
@@ -3816,16 +3831,155 @@ function wrapInEmailTemplate(content, preheader) {
3816
3831
  </xml>
3817
3832
  </noscript>
3818
3833
  <![endif]-->
3834
+
3835
+ <style>
3836
+ /* Reset and base styles */
3837
+ * {
3838
+ -webkit-text-size-adjust: 100%;
3839
+ -ms-text-size-adjust: 100%;
3840
+ }
3841
+
3842
+ body {
3843
+ margin: 0 !important;
3844
+ padding: 0 !important;
3845
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
3846
+ font-size: 16px;
3847
+ line-height: 1.5;
3848
+ color: #1A1A1A;
3849
+ background-color: #f8f9fa;
3850
+ -webkit-font-smoothing: antialiased;
3851
+ -moz-osx-font-smoothing: grayscale;
3852
+ }
3853
+
3854
+ table {
3855
+ border-spacing: 0 !important;
3856
+ border-collapse: collapse !important;
3857
+ table-layout: fixed !important;
3858
+ margin: 0 auto !important;
3859
+ }
3860
+
3861
+ table table table {
3862
+ table-layout: auto;
3863
+ }
3864
+
3865
+ img {
3866
+ -ms-interpolation-mode: bicubic;
3867
+ max-width: 100%;
3868
+ height: auto;
3869
+ border: 0;
3870
+ outline: none;
3871
+ text-decoration: none;
3872
+ }
3873
+
3874
+ /* Responsive styles */
3875
+ @media only screen and (max-width: 640px) {
3876
+ .mobile-hide {
3877
+ display: none !important;
3878
+ }
3879
+
3880
+ .mobile-center {
3881
+ text-align: center !important;
3882
+ }
3883
+
3884
+ .mobile-width-100 {
3885
+ width: 100% !important;
3886
+ max-width: 100% !important;
3887
+ }
3888
+
3889
+ .mobile-padding {
3890
+ padding: 20px !important;
3891
+ }
3892
+
3893
+ .mobile-padding-sm {
3894
+ padding: 16px !important;
3895
+ }
3896
+
3897
+ .mobile-font-size-14 {
3898
+ font-size: 14px !important;
3899
+ }
3900
+
3901
+ .mobile-font-size-16 {
3902
+ font-size: 16px !important;
3903
+ }
3904
+
3905
+ .mobile-font-size-20 {
3906
+ font-size: 20px !important;
3907
+ line-height: 1.3 !important;
3908
+ }
3909
+
3910
+ .mobile-font-size-24 {
3911
+ font-size: 24px !important;
3912
+ line-height: 1.2 !important;
3913
+ }
3914
+
3915
+ /* Stack sections on mobile */
3916
+ .mobile-stack {
3917
+ display: block !important;
3918
+ width: 100% !important;
3919
+ }
3920
+
3921
+ /* Mobile-specific spacing */
3922
+ .mobile-margin-bottom-16 {
3923
+ margin-bottom: 16px !important;
3924
+ }
3925
+
3926
+ .mobile-margin-bottom-20 {
3927
+ margin-bottom: 20px !important;
3928
+ }
3929
+ }
3930
+
3931
+ /* Dark mode support */
3932
+ @media (prefers-color-scheme: dark) {
3933
+ .dark-mode-bg {
3934
+ background-color: #1a1a1a !important;
3935
+ }
3936
+
3937
+ .dark-mode-text {
3938
+ color: #ffffff !important;
3939
+ }
3940
+
3941
+ .dark-mode-border {
3942
+ border-color: #333333 !important;
3943
+ }
3944
+ }
3945
+
3946
+ /* Outlook-specific fixes */
3947
+ <!--[if mso]>
3948
+ <style>
3949
+ table {
3950
+ border-collapse: collapse;
3951
+ border-spacing: 0;
3952
+ border: none;
3953
+ margin: 0;
3954
+ }
3955
+
3956
+ div, p {
3957
+ margin: 0;
3958
+ }
3959
+ </style>
3960
+ <![endif]-->
3961
+ </style>
3819
3962
  </head>
3820
- <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: #333333; background-color: #f3f4f6;">
3821
- ${preheader ? `<div style="display: none; max-height: 0; overflow: hidden;">${escapeHtml(preheader)}</div>` : ""}
3822
- <table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin: 0; padding: 0;">
3963
+ <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;">
3964
+ ${preheader ? `
3965
+ <!-- Preheader text -->
3966
+ <div style="display: none; max-height: 0; overflow: hidden; font-size: 1px; line-height: 1px; color: transparent;">
3967
+ ${escapeHtml(preheader)}
3968
+ </div>
3969
+ ` : ""}
3970
+
3971
+ <!-- Main container -->
3972
+ <table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin: 0; padding: 0; background-color: #f8f9fa;">
3823
3973
  <tr>
3824
- <td align="center" style="padding: 20px 0;">
3825
- <table role="presentation" cellpadding="0" cellspacing="0" width="600" style="margin: 0 auto; background-color: #ffffff; border-radius: 8px; overflow: hidden;">
3974
+ <td align="center" style="padding: 20px 10px;">
3975
+ <!-- Email wrapper -->
3976
+ <table role="presentation" cellpadding="0" cellspacing="0" width="600" class="mobile-width-100" style="margin: 0 auto; max-width: 600px;">
3826
3977
  <tr>
3827
- <td style="padding: 40px 30px;">
3828
- ${content}
3978
+ <td class="mobile-padding" style="padding: 0;">
3979
+ <!-- Content area with light background -->
3980
+ <div style="background-color: #ffffff; padding: 40px 30px; border-radius: 8px;" class="mobile-padding">
3981
+ ${content}
3982
+ </div>
3829
3983
  </td>
3830
3984
  </tr>
3831
3985
  </table>
@@ -4269,11 +4423,14 @@ var createBroadcastPreviewEndpoint = (config, _collectionSlug) => {
4269
4423
  const mediaUrl = req.payload.config.serverURL ? `${req.payload.config.serverURL}/api/media` : "/api/media";
4270
4424
  req.payload.logger?.info("Populating media fields for email preview...");
4271
4425
  const populatedContent = await populateMediaFields(content, req.payload, config);
4426
+ const emailPreviewConfig = config.customizations?.broadcasts?.emailPreview;
4272
4427
  const htmlContent = await convertToEmailSafeHtml(populatedContent, {
4273
- wrapInTemplate: true,
4428
+ wrapInTemplate: emailPreviewConfig?.wrapInTemplate ?? true,
4274
4429
  preheader,
4430
+ subject,
4275
4431
  mediaUrl,
4276
- customBlockConverter: config.customizations?.broadcasts?.customBlockConverter
4432
+ customBlockConverter: config.customizations?.broadcasts?.customBlockConverter,
4433
+ customWrapper: emailPreviewConfig?.customWrapper
4277
4434
  });
4278
4435
  return Response.json({
4279
4436
  success: true,
@@ -5177,6 +5334,24 @@ var isAuthenticated = (req, secret) => {
5177
5334
  return !!decoded;
5178
5335
  };
5179
5336
 
5337
+ // src/contexts/PluginConfigContext.tsx
5338
+ import { createContext, useContext } from "react";
5339
+ import { jsx as jsx5 } from "react/jsx-runtime";
5340
+ var PluginConfigContext = createContext(null);
5341
+ var PluginConfigProvider = ({ config, children }) => {
5342
+ return /* @__PURE__ */ jsx5(PluginConfigContext.Provider, { value: config, children });
5343
+ };
5344
+ var usePluginConfig = () => {
5345
+ const config = useContext(PluginConfigContext);
5346
+ if (!config) {
5347
+ throw new Error("usePluginConfig must be used within PluginConfigProvider");
5348
+ }
5349
+ return config;
5350
+ };
5351
+ var usePluginConfigOptional = () => {
5352
+ return useContext(PluginConfigContext);
5353
+ };
5354
+
5180
5355
  // src/index.ts
5181
5356
  var newsletterPlugin = (pluginConfig) => (incomingConfig) => {
5182
5357
  const config = {
@@ -5353,12 +5528,15 @@ var newsletterPlugin = (pluginConfig) => (incomingConfig) => {
5353
5528
  return modifiedConfig;
5354
5529
  };
5355
5530
  export {
5531
+ PluginConfigProvider,
5356
5532
  newsletterPlugin as default,
5357
5533
  getServerSideAuth,
5358
5534
  getTokenFromRequest,
5359
5535
  isAuthenticated,
5360
5536
  newsletterPlugin,
5361
5537
  requireAuth,
5538
+ usePluginConfig,
5539
+ usePluginConfigOptional,
5362
5540
  verifyToken
5363
5541
  };
5364
5542
  //# sourceMappingURL=index.js.map