UNPKG

payload-plugin-newsletter

Version:

Complete newsletter management plugin for Payload CMS with subscriber management, magic link authentication, and email service integration

563 lines (557 loc) 16.7 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/exports/fields.ts var fields_exports = {}; __export(fields_exports, { createBroadcastInlinePreviewField: () => createBroadcastInlinePreviewField, createEmailContentField: () => createEmailContentField, createEmailLexicalEditor: () => createEmailLexicalEditor, createEmailSafeBlocks: () => createEmailSafeBlocks, createEmailSafeFeatures: () => createEmailSafeFeatures, createNewsletterSchedulingFields: () => createNewsletterSchedulingFields, emailSafeFeatures: () => emailSafeFeatures, validateEmailBlocks: () => validateEmailBlocks }); module.exports = __toCommonJS(fields_exports); // src/fields/emailContent.ts var import_richtext_lexical = require("@payloadcms/richtext-lexical"); // src/utils/blockValidation.ts var EMAIL_INCOMPATIBLE_TYPES = [ "chart", "dataTable", "interactive", "streamable", "video", "iframe", "form", "carousel", "tabs", "accordion", "map" ]; var validateEmailBlocks = (blocks) => { blocks.forEach((block) => { if (EMAIL_INCOMPATIBLE_TYPES.includes(block.slug)) { console.warn(`\u26A0\uFE0F Block "${block.slug}" may not be email-compatible. Consider creating an email-specific version.`); } const hasComplexFields = block.fields?.some((field) => { const complexTypes = ["code", "json", "richText", "blocks", "array"]; return complexTypes.includes(field.type); }); if (hasComplexFields) { console.warn(`\u26A0\uFE0F Block "${block.slug}" contains complex field types that may not render consistently in email clients.`); } }); }; var createEmailSafeBlocks = (customBlocks = []) => { validateEmailBlocks(customBlocks); const baseBlocks = [ { slug: "button", fields: [ { name: "text", type: "text", label: "Button Text", required: true }, { name: "url", type: "text", label: "Button URL", required: true, admin: { description: "Enter the full URL (including https://)" } }, { name: "style", type: "select", label: "Button Style", defaultValue: "primary", options: [ { label: "Primary", value: "primary" }, { label: "Secondary", value: "secondary" }, { label: "Outline", value: "outline" } ] } ], interfaceName: "EmailButton", labels: { singular: "Button", plural: "Buttons" } }, { slug: "divider", fields: [ { name: "style", type: "select", label: "Divider Style", defaultValue: "solid", options: [ { label: "Solid", value: "solid" }, { label: "Dashed", value: "dashed" }, { label: "Dotted", value: "dotted" } ] } ], interfaceName: "EmailDivider", labels: { singular: "Divider", plural: "Dividers" } } ]; return [ ...baseBlocks, ...customBlocks ]; }; // src/fields/emailContent.ts var createEmailSafeFeatures = (additionalBlocks) => { const baseBlocks = [ { slug: "button", fields: [ { name: "text", type: "text", label: "Button Text", required: true }, { name: "url", type: "text", label: "Button URL", required: true, admin: { description: "Enter the full URL (including https://)" } }, { name: "style", type: "select", label: "Button Style", defaultValue: "primary", options: [ { label: "Primary", value: "primary" }, { label: "Secondary", value: "secondary" }, { label: "Outline", value: "outline" } ] } ], interfaceName: "EmailButton", labels: { singular: "Button", plural: "Buttons" } }, { slug: "divider", fields: [ { name: "style", type: "select", label: "Divider Style", defaultValue: "solid", options: [ { label: "Solid", value: "solid" }, { label: "Dashed", value: "dashed" }, { label: "Dotted", value: "dotted" } ] } ], interfaceName: "EmailDivider", labels: { singular: "Divider", plural: "Dividers" } } ]; const allBlocks = [ ...baseBlocks, ...additionalBlocks || [] ]; return [ // Toolbars (0, import_richtext_lexical.FixedToolbarFeature)(), // Fixed toolbar at the top (0, import_richtext_lexical.InlineToolbarFeature)(), // Floating toolbar when text is selected // Basic text formatting (0, import_richtext_lexical.BoldFeature)(), (0, import_richtext_lexical.ItalicFeature)(), (0, import_richtext_lexical.UnderlineFeature)(), (0, import_richtext_lexical.StrikethroughFeature)(), // Links with enhanced configuration (0, import_richtext_lexical.LinkFeature)({ fields: [ { name: "url", type: "text", required: true, admin: { description: "Enter the full URL (including https://)" } }, { name: "newTab", type: "checkbox", label: "Open in new tab", defaultValue: false } ] }), // Lists (0, import_richtext_lexical.OrderedListFeature)(), (0, import_richtext_lexical.UnorderedListFeature)(), // Headings - limited to h1, h2, h3 for email compatibility (0, import_richtext_lexical.HeadingFeature)({ enabledHeadingSizes: ["h1", "h2", "h3"] }), // Basic paragraph and alignment (0, import_richtext_lexical.ParagraphFeature)(), (0, import_richtext_lexical.AlignFeature)(), // Blockquotes (0, import_richtext_lexical.BlockquoteFeature)(), // Upload feature for images (0, import_richtext_lexical.UploadFeature)({ collections: { media: { fields: [ { name: "caption", type: "text", admin: { description: "Optional caption for the image" } }, { name: "altText", type: "text", label: "Alt Text", required: true, admin: { description: "Alternative text for accessibility and when image cannot be displayed" } } ] } } }), // Custom blocks for email-specific content (0, import_richtext_lexical.BlocksFeature)({ blocks: allBlocks }) ]; }; var createEmailLexicalEditor = (customBlocks = []) => { const emailSafeBlocks = createEmailSafeBlocks(customBlocks); return (0, import_richtext_lexical.lexicalEditor)({ features: [ // Toolbars (0, import_richtext_lexical.FixedToolbarFeature)(), (0, import_richtext_lexical.InlineToolbarFeature)(), // Basic text formatting (0, import_richtext_lexical.BoldFeature)(), (0, import_richtext_lexical.ItalicFeature)(), (0, import_richtext_lexical.UnderlineFeature)(), (0, import_richtext_lexical.StrikethroughFeature)(), // Links with enhanced configuration (0, import_richtext_lexical.LinkFeature)({ fields: [ { name: "url", type: "text", required: true, admin: { description: "Enter the full URL (including https://)" } }, { name: "newTab", type: "checkbox", label: "Open in new tab", defaultValue: false } ] }), // Lists (0, import_richtext_lexical.OrderedListFeature)(), (0, import_richtext_lexical.UnorderedListFeature)(), // Headings - limited to h1, h2, h3 for email compatibility (0, import_richtext_lexical.HeadingFeature)({ enabledHeadingSizes: ["h1", "h2", "h3"] }), // Basic paragraph and alignment (0, import_richtext_lexical.ParagraphFeature)(), (0, import_richtext_lexical.AlignFeature)(), // Blockquotes (0, import_richtext_lexical.BlockquoteFeature)(), // Upload feature for images (0, import_richtext_lexical.UploadFeature)({ collections: { media: { fields: [ { name: "caption", type: "text", admin: { description: "Optional caption for the image" } }, { name: "altText", type: "text", label: "Alt Text", required: true, admin: { description: "Alternative text for accessibility and when image cannot be displayed" } } ] } } }), // Email-safe blocks (processed server-side) (0, import_richtext_lexical.BlocksFeature)({ blocks: emailSafeBlocks }) ] }); }; var emailSafeFeatures = createEmailSafeFeatures(); var createEmailContentField = (overrides) => { const editor = overrides?.editor || createEmailLexicalEditor(overrides?.additionalBlocks); return { name: "content", type: "richText", required: true, editor, admin: { description: "Email content with limited formatting for compatibility", ...overrides?.admin }, ...overrides }; }; // src/fields/broadcastInlinePreview.ts var createBroadcastInlinePreviewField = () => { return { name: "broadcastInlinePreview", type: "ui", admin: { components: { Field: "payload-plugin-newsletter/components#BroadcastInlinePreview" } } }; }; // src/fields/newsletterScheduling.ts function createNewsletterSchedulingFields(config) { const groupName = config.features?.newsletterScheduling?.fields?.groupName || "newsletterScheduling"; const contentField = config.features?.newsletterScheduling?.fields?.contentField || "content"; const createMarkdownField = config.features?.newsletterScheduling?.fields?.createMarkdownField !== false; const fields = [ { name: groupName, type: "group", label: "Newsletter Scheduling", admin: { condition: (data, { user }) => user?.collection === "users" // Only show for admin users }, fields: [ { name: "scheduled", type: "checkbox", label: "Schedule for Newsletter", defaultValue: false, admin: { description: "Schedule this content to be sent as a newsletter" } }, { name: "scheduledDate", type: "date", label: "Send Date", required: true, admin: { date: { pickerAppearance: "dayAndTime" }, condition: (data) => data?.[groupName]?.scheduled, description: "When to send this newsletter" } }, { name: "sentDate", type: "date", label: "Sent Date", admin: { readOnly: true, condition: (data) => data?.[groupName]?.sendStatus === "sent", description: "When this newsletter was sent" } }, { name: "sendStatus", type: "select", label: "Status", options: [ { label: "Draft", value: "draft" }, { label: "Scheduled", value: "scheduled" }, { label: "Sending", value: "sending" }, { label: "Sent", value: "sent" }, { label: "Failed", value: "failed" } ], defaultValue: "draft", admin: { readOnly: true, description: "Current send status" } }, { name: "emailSubject", type: "text", label: "Email Subject", required: true, admin: { condition: (data) => data?.[groupName]?.scheduled, description: "Subject line for the newsletter email" } }, { name: "preheader", type: "text", label: "Email Preheader", admin: { condition: (data) => data?.[groupName]?.scheduled, description: "Preview text that appears after the subject line" } }, { name: "segments", type: "select", label: "Target Segments", hasMany: true, options: [ { label: "All Subscribers", value: "all" }, ...config.i18n?.locales?.map((locale) => ({ label: `${locale.toUpperCase()} Subscribers`, value: locale })) || [] ], defaultValue: ["all"], admin: { condition: (data) => data?.[groupName]?.scheduled, description: "Which subscriber segments to send to" } }, { name: "testEmails", type: "array", label: "Test Email Recipients", admin: { condition: (data) => data?.[groupName]?.scheduled && data?.[groupName]?.sendStatus === "draft", description: "Send test emails before scheduling" }, fields: [ { name: "email", type: "email", required: true } ] } ] } ]; if (createMarkdownField) { fields.push(createMarkdownFieldInternal({ name: `${contentField}Markdown`, richTextField: contentField, label: "Email Content (Markdown)", admin: { position: "sidebar", condition: (data) => Boolean(data?.[contentField] && data?.[groupName]?.scheduled), description: "Markdown version for email rendering", readOnly: true } })); } return fields; } function createMarkdownFieldInternal(config) { return { name: config.name, type: "textarea", label: config.label || "Markdown", admin: { ...config.admin, description: config.admin?.description || "Auto-generated from rich text content" }, hooks: { afterRead: [ async ({ data }) => { if (data?.[config.richTextField]) { try { const { convertLexicalToMarkdown } = await import("@payloadcms/richtext-lexical"); return convertLexicalToMarkdown({ data: data[config.richTextField] }); } catch { return ""; } } return ""; } ], beforeChange: [ () => { return null; } ] } }; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { createBroadcastInlinePreviewField, createEmailContentField, createEmailLexicalEditor, createEmailSafeBlocks, createEmailSafeFeatures, createNewsletterSchedulingFields, emailSafeFeatures, validateEmailBlocks }); //# sourceMappingURL=fields.cjs.map