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
JavaScript
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
;