payload-plugin-newsletter 0.14.3 → 0.15.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/fields.js CHANGED
@@ -17,143 +17,152 @@ import {
17
17
  InlineToolbarFeature,
18
18
  lexicalEditor
19
19
  } from "@payloadcms/richtext-lexical";
20
- var emailSafeFeatures = [
21
- // Toolbars
22
- FixedToolbarFeature(),
23
- // Fixed toolbar at the top
24
- InlineToolbarFeature(),
25
- // Floating toolbar when text is selected
26
- // Basic text formatting
27
- BoldFeature(),
28
- ItalicFeature(),
29
- UnderlineFeature(),
30
- StrikethroughFeature(),
31
- // Links with enhanced configuration
32
- LinkFeature({
33
- fields: [
34
- {
35
- name: "url",
36
- type: "text",
37
- required: true,
38
- admin: {
39
- description: "Enter the full URL (including https://)"
20
+ var createEmailSafeFeatures = (additionalBlocks) => {
21
+ const baseBlocks = [
22
+ {
23
+ slug: "button",
24
+ fields: [
25
+ {
26
+ name: "text",
27
+ type: "text",
28
+ label: "Button Text",
29
+ required: true
30
+ },
31
+ {
32
+ name: "url",
33
+ type: "text",
34
+ label: "Button URL",
35
+ required: true,
36
+ admin: {
37
+ description: "Enter the full URL (including https://)"
38
+ }
39
+ },
40
+ {
41
+ name: "style",
42
+ type: "select",
43
+ label: "Button Style",
44
+ defaultValue: "primary",
45
+ options: [
46
+ { label: "Primary", value: "primary" },
47
+ { label: "Secondary", value: "secondary" },
48
+ { label: "Outline", value: "outline" }
49
+ ]
40
50
  }
41
- },
42
- {
43
- name: "newTab",
44
- type: "checkbox",
45
- label: "Open in new tab",
46
- defaultValue: false
51
+ ],
52
+ interfaceName: "EmailButton",
53
+ labels: {
54
+ singular: "Button",
55
+ plural: "Buttons"
47
56
  }
48
- ]
49
- }),
50
- // Lists
51
- OrderedListFeature(),
52
- UnorderedListFeature(),
53
- // Headings - limited to h1, h2, h3 for email compatibility
54
- HeadingFeature({
55
- enabledHeadingSizes: ["h1", "h2", "h3"]
56
- }),
57
- // Basic paragraph and alignment
58
- ParagraphFeature(),
59
- AlignFeature(),
60
- // Blockquotes
61
- BlockquoteFeature(),
62
- // Upload feature for images
63
- UploadFeature({
64
- collections: {
65
- media: {
66
- fields: [
67
- {
68
- name: "caption",
69
- type: "text",
70
- admin: {
71
- description: "Optional caption for the image"
72
- }
73
- },
74
- {
75
- name: "altText",
76
- type: "text",
77
- label: "Alt Text",
78
- required: true,
79
- admin: {
80
- description: "Alternative text for accessibility and when image cannot be displayed"
81
- }
82
- }
83
- ]
57
+ },
58
+ {
59
+ slug: "divider",
60
+ fields: [
61
+ {
62
+ name: "style",
63
+ type: "select",
64
+ label: "Divider Style",
65
+ defaultValue: "solid",
66
+ options: [
67
+ { label: "Solid", value: "solid" },
68
+ { label: "Dashed", value: "dashed" },
69
+ { label: "Dotted", value: "dotted" }
70
+ ]
71
+ }
72
+ ],
73
+ interfaceName: "EmailDivider",
74
+ labels: {
75
+ singular: "Divider",
76
+ plural: "Dividers"
84
77
  }
85
78
  }
86
- }),
87
- // Custom blocks for email-specific content
88
- BlocksFeature({
89
- blocks: [
90
- {
91
- slug: "button",
92
- fields: [
93
- {
94
- name: "text",
95
- type: "text",
96
- label: "Button Text",
97
- required: true
98
- },
99
- {
100
- name: "url",
101
- type: "text",
102
- label: "Button URL",
103
- required: true,
104
- admin: {
105
- description: "Enter the full URL (including https://)"
106
- }
107
- },
108
- {
109
- name: "style",
110
- type: "select",
111
- label: "Button Style",
112
- defaultValue: "primary",
113
- options: [
114
- { label: "Primary", value: "primary" },
115
- { label: "Secondary", value: "secondary" },
116
- { label: "Outline", value: "outline" }
117
- ]
79
+ ];
80
+ const allBlocks = [
81
+ ...baseBlocks,
82
+ ...additionalBlocks || []
83
+ ];
84
+ return [
85
+ // Toolbars
86
+ FixedToolbarFeature(),
87
+ // Fixed toolbar at the top
88
+ InlineToolbarFeature(),
89
+ // Floating toolbar when text is selected
90
+ // Basic text formatting
91
+ BoldFeature(),
92
+ ItalicFeature(),
93
+ UnderlineFeature(),
94
+ StrikethroughFeature(),
95
+ // Links with enhanced configuration
96
+ LinkFeature({
97
+ fields: [
98
+ {
99
+ name: "url",
100
+ type: "text",
101
+ required: true,
102
+ admin: {
103
+ description: "Enter the full URL (including https://)"
118
104
  }
119
- ],
120
- interfaceName: "EmailButton",
121
- labels: {
122
- singular: "Button",
123
- plural: "Buttons"
105
+ },
106
+ {
107
+ name: "newTab",
108
+ type: "checkbox",
109
+ label: "Open in new tab",
110
+ defaultValue: false
124
111
  }
125
- },
126
- {
127
- slug: "divider",
128
- fields: [
129
- {
130
- name: "style",
131
- type: "select",
132
- label: "Divider Style",
133
- defaultValue: "solid",
134
- options: [
135
- { label: "Solid", value: "solid" },
136
- { label: "Dashed", value: "dashed" },
137
- { label: "Dotted", value: "dotted" }
138
- ]
139
- }
140
- ],
141
- interfaceName: "EmailDivider",
142
- labels: {
143
- singular: "Divider",
144
- plural: "Dividers"
112
+ ]
113
+ }),
114
+ // Lists
115
+ OrderedListFeature(),
116
+ UnorderedListFeature(),
117
+ // Headings - limited to h1, h2, h3 for email compatibility
118
+ HeadingFeature({
119
+ enabledHeadingSizes: ["h1", "h2", "h3"]
120
+ }),
121
+ // Basic paragraph and alignment
122
+ ParagraphFeature(),
123
+ AlignFeature(),
124
+ // Blockquotes
125
+ BlockquoteFeature(),
126
+ // Upload feature for images
127
+ UploadFeature({
128
+ collections: {
129
+ media: {
130
+ fields: [
131
+ {
132
+ name: "caption",
133
+ type: "text",
134
+ admin: {
135
+ description: "Optional caption for the image"
136
+ }
137
+ },
138
+ {
139
+ name: "altText",
140
+ type: "text",
141
+ label: "Alt Text",
142
+ required: true,
143
+ admin: {
144
+ description: "Alternative text for accessibility and when image cannot be displayed"
145
+ }
146
+ }
147
+ ]
145
148
  }
146
149
  }
147
- ]
148
- })
149
- ];
150
+ }),
151
+ // Custom blocks for email-specific content
152
+ BlocksFeature({
153
+ blocks: allBlocks
154
+ })
155
+ ];
156
+ };
157
+ var emailSafeFeatures = createEmailSafeFeatures();
150
158
  var createEmailContentField = (overrides) => {
159
+ const features = createEmailSafeFeatures(overrides?.additionalBlocks);
151
160
  return {
152
161
  name: "content",
153
162
  type: "richText",
154
163
  required: true,
155
164
  editor: lexicalEditor({
156
- features: emailSafeFeatures
165
+ features
157
166
  }),
158
167
  admin: {
159
168
  description: "Email content with limited formatting for compatibility",
@@ -162,8 +171,193 @@ var createEmailContentField = (overrides) => {
162
171
  ...overrides
163
172
  };
164
173
  };
174
+
175
+ // src/fields/broadcastInlinePreview.ts
176
+ var createBroadcastInlinePreviewField = () => {
177
+ return {
178
+ name: "broadcastInlinePreview",
179
+ type: "ui",
180
+ admin: {
181
+ components: {
182
+ Field: "payload-plugin-newsletter/components#BroadcastInlinePreview"
183
+ }
184
+ }
185
+ };
186
+ };
187
+
188
+ // src/fields/newsletterScheduling.ts
189
+ function createNewsletterSchedulingFields(config) {
190
+ const groupName = config.features?.newsletterScheduling?.fields?.groupName || "newsletterScheduling";
191
+ const contentField = config.features?.newsletterScheduling?.fields?.contentField || "content";
192
+ const createMarkdownField = config.features?.newsletterScheduling?.fields?.createMarkdownField !== false;
193
+ const fields = [
194
+ {
195
+ name: groupName,
196
+ type: "group",
197
+ label: "Newsletter Scheduling",
198
+ admin: {
199
+ condition: (data, { user }) => user?.collection === "users"
200
+ // Only show for admin users
201
+ },
202
+ fields: [
203
+ {
204
+ name: "scheduled",
205
+ type: "checkbox",
206
+ label: "Schedule for Newsletter",
207
+ defaultValue: false,
208
+ admin: {
209
+ description: "Schedule this content to be sent as a newsletter"
210
+ }
211
+ },
212
+ {
213
+ name: "scheduledDate",
214
+ type: "date",
215
+ label: "Send Date",
216
+ required: true,
217
+ admin: {
218
+ date: {
219
+ pickerAppearance: "dayAndTime"
220
+ },
221
+ condition: (data) => data?.[groupName]?.scheduled,
222
+ description: "When to send this newsletter"
223
+ }
224
+ },
225
+ {
226
+ name: "sentDate",
227
+ type: "date",
228
+ label: "Sent Date",
229
+ admin: {
230
+ readOnly: true,
231
+ condition: (data) => data?.[groupName]?.sendStatus === "sent",
232
+ description: "When this newsletter was sent"
233
+ }
234
+ },
235
+ {
236
+ name: "sendStatus",
237
+ type: "select",
238
+ label: "Status",
239
+ options: [
240
+ { label: "Draft", value: "draft" },
241
+ { label: "Scheduled", value: "scheduled" },
242
+ { label: "Sending", value: "sending" },
243
+ { label: "Sent", value: "sent" },
244
+ { label: "Failed", value: "failed" }
245
+ ],
246
+ defaultValue: "draft",
247
+ admin: {
248
+ readOnly: true,
249
+ description: "Current send status"
250
+ }
251
+ },
252
+ {
253
+ name: "emailSubject",
254
+ type: "text",
255
+ label: "Email Subject",
256
+ required: true,
257
+ admin: {
258
+ condition: (data) => data?.[groupName]?.scheduled,
259
+ description: "Subject line for the newsletter email"
260
+ }
261
+ },
262
+ {
263
+ name: "preheader",
264
+ type: "text",
265
+ label: "Email Preheader",
266
+ admin: {
267
+ condition: (data) => data?.[groupName]?.scheduled,
268
+ description: "Preview text that appears after the subject line"
269
+ }
270
+ },
271
+ {
272
+ name: "segments",
273
+ type: "select",
274
+ label: "Target Segments",
275
+ hasMany: true,
276
+ options: [
277
+ { label: "All Subscribers", value: "all" },
278
+ ...config.i18n?.locales?.map((locale) => ({
279
+ label: `${locale.toUpperCase()} Subscribers`,
280
+ value: locale
281
+ })) || []
282
+ ],
283
+ defaultValue: ["all"],
284
+ admin: {
285
+ condition: (data) => data?.[groupName]?.scheduled,
286
+ description: "Which subscriber segments to send to"
287
+ }
288
+ },
289
+ {
290
+ name: "testEmails",
291
+ type: "array",
292
+ label: "Test Email Recipients",
293
+ admin: {
294
+ condition: (data) => data?.[groupName]?.scheduled && data?.[groupName]?.sendStatus === "draft",
295
+ description: "Send test emails before scheduling"
296
+ },
297
+ fields: [
298
+ {
299
+ name: "email",
300
+ type: "email",
301
+ required: true
302
+ }
303
+ ]
304
+ }
305
+ ]
306
+ }
307
+ ];
308
+ if (createMarkdownField) {
309
+ fields.push(createMarkdownFieldInternal({
310
+ name: `${contentField}Markdown`,
311
+ richTextField: contentField,
312
+ label: "Email Content (Markdown)",
313
+ admin: {
314
+ position: "sidebar",
315
+ condition: (data) => Boolean(data?.[contentField] && data?.[groupName]?.scheduled),
316
+ description: "Markdown version for email rendering",
317
+ readOnly: true
318
+ }
319
+ }));
320
+ }
321
+ return fields;
322
+ }
323
+ function createMarkdownFieldInternal(config) {
324
+ return {
325
+ name: config.name,
326
+ type: "textarea",
327
+ label: config.label || "Markdown",
328
+ admin: {
329
+ ...config.admin,
330
+ description: config.admin?.description || "Auto-generated from rich text content"
331
+ },
332
+ hooks: {
333
+ afterRead: [
334
+ async ({ data }) => {
335
+ if (data?.[config.richTextField]) {
336
+ try {
337
+ const { convertLexicalToMarkdown } = await import("@payloadcms/richtext-lexical");
338
+ return convertLexicalToMarkdown({
339
+ data: data[config.richTextField]
340
+ });
341
+ } catch {
342
+ return "";
343
+ }
344
+ }
345
+ return "";
346
+ }
347
+ ],
348
+ beforeChange: [
349
+ () => {
350
+ return null;
351
+ }
352
+ ]
353
+ }
354
+ };
355
+ }
165
356
  export {
357
+ createBroadcastInlinePreviewField,
166
358
  createEmailContentField,
359
+ createEmailSafeFeatures,
360
+ createNewsletterSchedulingFields,
167
361
  emailSafeFeatures
168
362
  };
169
363
  //# sourceMappingURL=fields.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/fields/emailContent.ts"],"sourcesContent":["import { \n BoldFeature,\n ItalicFeature,\n UnderlineFeature,\n StrikethroughFeature,\n LinkFeature,\n OrderedListFeature,\n UnorderedListFeature,\n HeadingFeature,\n ParagraphFeature,\n AlignFeature,\n BlockquoteFeature,\n BlocksFeature,\n UploadFeature,\n FixedToolbarFeature,\n InlineToolbarFeature,\n lexicalEditor,\n} from '@payloadcms/richtext-lexical'\nimport type { RichTextField } from 'payload'\n\n/**\n * Email-safe features for Lexical editor\n * Only includes features that render consistently across email clients\n */\n// Using any[] here because Payload's FeatureProviderServer type is complex\n// and varies between versions. The features are properly typed by Payload internally.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const emailSafeFeatures: any[] = [\n // Toolbars\n FixedToolbarFeature(), // Fixed toolbar at the top\n InlineToolbarFeature(), // Floating toolbar when text is selected\n \n // Basic text formatting\n BoldFeature(),\n ItalicFeature(),\n UnderlineFeature(),\n StrikethroughFeature(),\n \n // Links with enhanced configuration\n LinkFeature({\n fields: [\n {\n name: 'url',\n type: 'text',\n required: true,\n admin: {\n description: 'Enter the full URL (including https://)',\n },\n },\n {\n name: 'newTab',\n type: 'checkbox',\n label: 'Open in new tab',\n defaultValue: false,\n },\n ],\n }),\n \n // Lists\n OrderedListFeature(),\n UnorderedListFeature(),\n \n // Headings - limited to h1, h2, h3 for email compatibility\n HeadingFeature({\n enabledHeadingSizes: ['h1', 'h2', 'h3'],\n }),\n \n // Basic paragraph and alignment\n ParagraphFeature(),\n AlignFeature(),\n \n // Blockquotes\n BlockquoteFeature(),\n \n // Upload feature for images\n UploadFeature({\n collections: {\n media: {\n fields: [\n {\n name: 'caption',\n type: 'text',\n admin: {\n description: 'Optional caption for the image',\n },\n },\n {\n name: 'altText',\n type: 'text',\n label: 'Alt Text',\n required: true,\n admin: {\n description: 'Alternative text for accessibility and when image cannot be displayed',\n },\n },\n ],\n },\n },\n }),\n \n // Custom blocks for email-specific content\n BlocksFeature({\n blocks: [\n {\n slug: 'button',\n fields: [\n {\n name: 'text',\n type: 'text',\n label: 'Button Text',\n required: true,\n },\n {\n name: 'url',\n type: 'text',\n label: 'Button URL',\n required: true,\n admin: {\n description: 'Enter the full URL (including https://)',\n },\n },\n {\n name: 'style',\n type: 'select',\n label: 'Button Style',\n defaultValue: 'primary',\n options: [\n { label: 'Primary', value: 'primary' },\n { label: 'Secondary', value: 'secondary' },\n { label: 'Outline', value: 'outline' },\n ],\n },\n ],\n interfaceName: 'EmailButton',\n labels: {\n singular: 'Button',\n plural: 'Buttons',\n },\n },\n {\n slug: 'divider',\n fields: [\n {\n name: 'style',\n type: 'select',\n label: 'Divider Style',\n defaultValue: 'solid',\n options: [\n { label: 'Solid', value: 'solid' },\n { label: 'Dashed', value: 'dashed' },\n { label: 'Dotted', value: 'dotted' },\n ],\n },\n ],\n interfaceName: 'EmailDivider',\n labels: {\n singular: 'Divider',\n plural: 'Dividers',\n },\n },\n ],\n }),\n]\n\n/**\n * Creates an email-safe rich text field configuration\n */\nexport const createEmailContentField = (overrides?: Partial<RichTextField>): RichTextField => {\n return {\n name: 'content',\n type: 'richText',\n required: true,\n editor: lexicalEditor({\n features: emailSafeFeatures,\n }),\n admin: {\n description: 'Email content with limited formatting for compatibility',\n ...overrides?.admin,\n },\n ...overrides,\n }\n}"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/fields/emailContent.ts","../src/fields/broadcastInlinePreview.ts","../src/fields/newsletterScheduling.ts"],"sourcesContent":["import { \n BoldFeature,\n ItalicFeature,\n UnderlineFeature,\n StrikethroughFeature,\n LinkFeature,\n OrderedListFeature,\n UnorderedListFeature,\n HeadingFeature,\n ParagraphFeature,\n AlignFeature,\n BlockquoteFeature,\n BlocksFeature,\n UploadFeature,\n FixedToolbarFeature,\n InlineToolbarFeature,\n lexicalEditor,\n} from '@payloadcms/richtext-lexical'\nimport type { RichTextField, Block } from 'payload'\n\n/**\n * Creates email-safe features for Lexical editor with optional additional blocks\n * Only includes features that render consistently across email clients\n */\n// Using any[] here because Payload's FeatureProviderServer type is complex\n// and varies between versions. The features are properly typed by Payload internally.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const createEmailSafeFeatures = (additionalBlocks?: Block[]): any[] => {\n // Base email-safe blocks\n const baseBlocks = [\n {\n slug: 'button',\n fields: [\n {\n name: 'text',\n type: 'text',\n label: 'Button Text',\n required: true,\n },\n {\n name: 'url',\n type: 'text',\n label: 'Button URL',\n required: true,\n admin: {\n description: 'Enter the full URL (including https://)',\n },\n },\n {\n name: 'style',\n type: 'select',\n label: 'Button Style',\n defaultValue: 'primary',\n options: [\n { label: 'Primary', value: 'primary' },\n { label: 'Secondary', value: 'secondary' },\n { label: 'Outline', value: 'outline' },\n ],\n },\n ],\n interfaceName: 'EmailButton',\n labels: {\n singular: 'Button',\n plural: 'Buttons',\n },\n },\n {\n slug: 'divider',\n fields: [\n {\n name: 'style',\n type: 'select',\n label: 'Divider Style',\n defaultValue: 'solid',\n options: [\n { label: 'Solid', value: 'solid' },\n { label: 'Dashed', value: 'dashed' },\n { label: 'Dotted', value: 'dotted' },\n ],\n },\n ],\n interfaceName: 'EmailDivider',\n labels: {\n singular: 'Divider',\n plural: 'Dividers',\n },\n },\n ] as Block[]\n\n // Merge additional blocks if provided\n const allBlocks = [\n ...baseBlocks,\n ...(additionalBlocks || [])\n ]\n\n return [\n // Toolbars\n FixedToolbarFeature(), // Fixed toolbar at the top\n InlineToolbarFeature(), // Floating toolbar when text is selected\n \n // Basic text formatting\n BoldFeature(),\n ItalicFeature(),\n UnderlineFeature(),\n StrikethroughFeature(),\n \n // Links with enhanced configuration\n LinkFeature({\n fields: [\n {\n name: 'url',\n type: 'text',\n required: true,\n admin: {\n description: 'Enter the full URL (including https://)',\n },\n },\n {\n name: 'newTab',\n type: 'checkbox',\n label: 'Open in new tab',\n defaultValue: false,\n },\n ],\n }),\n \n // Lists\n OrderedListFeature(),\n UnorderedListFeature(),\n \n // Headings - limited to h1, h2, h3 for email compatibility\n HeadingFeature({\n enabledHeadingSizes: ['h1', 'h2', 'h3'],\n }),\n \n // Basic paragraph and alignment\n ParagraphFeature(),\n AlignFeature(),\n \n // Blockquotes\n BlockquoteFeature(),\n \n // Upload feature for images\n UploadFeature({\n collections: {\n media: {\n fields: [\n {\n name: 'caption',\n type: 'text',\n admin: {\n description: 'Optional caption for the image',\n },\n },\n {\n name: 'altText',\n type: 'text',\n label: 'Alt Text',\n required: true,\n admin: {\n description: 'Alternative text for accessibility and when image cannot be displayed',\n },\n },\n ],\n },\n },\n }),\n \n // Custom blocks for email-specific content\n BlocksFeature({\n blocks: allBlocks,\n }),\n ]\n}\n\n/**\n * Legacy export for backward compatibility\n */\nexport const emailSafeFeatures = createEmailSafeFeatures()\n\n/**\n * Creates an email-safe rich text field configuration\n */\nexport const createEmailContentField = (\n overrides?: Partial<RichTextField> & {\n additionalBlocks?: Block[]\n }\n): RichTextField => {\n // Create features array with blocks\n const features = createEmailSafeFeatures(overrides?.additionalBlocks)\n\n return {\n name: 'content',\n type: 'richText',\n required: true,\n editor: lexicalEditor({\n features,\n }),\n admin: {\n description: 'Email content with limited formatting for compatibility',\n ...overrides?.admin,\n },\n ...overrides,\n }\n}","import type { Field } from 'payload'\n\nexport const createBroadcastInlinePreviewField = (): Field => {\n return {\n name: 'broadcastInlinePreview',\n type: 'ui',\n admin: {\n components: {\n Field: 'payload-plugin-newsletter/components#BroadcastInlinePreview',\n },\n },\n }\n}","import type { Field } from 'payload'\nimport type { NewsletterPluginConfig } from '../types'\n\nexport function createNewsletterSchedulingFields(\n config: NewsletterPluginConfig\n): Field[] {\n const groupName = config.features?.newsletterScheduling?.fields?.groupName || 'newsletterScheduling'\n const contentField = config.features?.newsletterScheduling?.fields?.contentField || 'content'\n const createMarkdownField = config.features?.newsletterScheduling?.fields?.createMarkdownField !== false\n\n const fields: Field[] = [\n {\n name: groupName,\n type: 'group',\n label: 'Newsletter Scheduling',\n admin: {\n condition: (data, { user }) => user?.collection === 'users', // Only show for admin users\n },\n fields: [\n {\n name: 'scheduled',\n type: 'checkbox',\n label: 'Schedule for Newsletter',\n defaultValue: false,\n admin: {\n description: 'Schedule this content to be sent as a newsletter',\n },\n },\n {\n name: 'scheduledDate',\n type: 'date',\n label: 'Send Date',\n required: true,\n admin: {\n date: {\n pickerAppearance: 'dayAndTime',\n },\n condition: (data) => data?.[groupName]?.scheduled,\n description: 'When to send this newsletter',\n },\n },\n {\n name: 'sentDate',\n type: 'date',\n label: 'Sent Date',\n admin: {\n readOnly: true,\n condition: (data) => data?.[groupName]?.sendStatus === 'sent',\n description: 'When this newsletter was sent',\n },\n },\n {\n name: 'sendStatus',\n type: 'select',\n label: 'Status',\n options: [\n { label: 'Draft', value: 'draft' },\n { label: 'Scheduled', value: 'scheduled' },\n { label: 'Sending', value: 'sending' },\n { label: 'Sent', value: 'sent' },\n { label: 'Failed', value: 'failed' },\n ],\n defaultValue: 'draft',\n admin: {\n readOnly: true,\n description: 'Current send status',\n },\n },\n {\n name: 'emailSubject',\n type: 'text',\n label: 'Email Subject',\n required: true,\n admin: {\n condition: (data) => data?.[groupName]?.scheduled,\n description: 'Subject line for the newsletter email',\n },\n },\n {\n name: 'preheader',\n type: 'text',\n label: 'Email Preheader',\n admin: {\n condition: (data) => data?.[groupName]?.scheduled,\n description: 'Preview text that appears after the subject line',\n },\n },\n {\n name: 'segments',\n type: 'select',\n label: 'Target Segments',\n hasMany: true,\n options: [\n { label: 'All Subscribers', value: 'all' },\n ...(config.i18n?.locales?.map(locale => ({\n label: `${locale.toUpperCase()} Subscribers`,\n value: locale,\n })) || []),\n ],\n defaultValue: ['all'],\n admin: {\n condition: (data) => data?.[groupName]?.scheduled,\n description: 'Which subscriber segments to send to',\n },\n },\n {\n name: 'testEmails',\n type: 'array',\n label: 'Test Email Recipients',\n admin: {\n condition: (data) => data?.[groupName]?.scheduled && data?.[groupName]?.sendStatus === 'draft',\n description: 'Send test emails before scheduling',\n },\n fields: [\n {\n name: 'email',\n type: 'email',\n required: true,\n },\n ],\n },\n ],\n },\n ]\n\n // Add markdown companion field if requested\n if (createMarkdownField) {\n fields.push(createMarkdownFieldInternal({\n name: `${contentField}Markdown`,\n richTextField: contentField,\n label: 'Email Content (Markdown)',\n admin: {\n position: 'sidebar',\n condition: (data: any) => Boolean(data?.[contentField] && data?.[groupName]?.scheduled),\n description: 'Markdown version for email rendering',\n readOnly: true,\n },\n }))\n }\n\n return fields\n}\n\n/**\n * Create a markdown companion field for rich text\n * This creates a virtual field that converts rich text to markdown\n */\nfunction createMarkdownFieldInternal(config: {\n name: string\n richTextField: string\n label?: string\n admin?: any\n}): Field {\n return {\n name: config.name,\n type: 'textarea',\n label: config.label || 'Markdown',\n admin: {\n ...config.admin,\n description: config.admin?.description || 'Auto-generated from rich text content',\n },\n hooks: {\n afterRead: [\n async ({ data }) => {\n // Convert rich text to markdown on read\n if (data?.[config.richTextField]) {\n try {\n const { convertLexicalToMarkdown } = await import('@payloadcms/richtext-lexical')\n return convertLexicalToMarkdown({\n data: data[config.richTextField],\n } as any)\n } catch {\n return ''\n }\n }\n return ''\n },\n ],\n beforeChange: [\n () => {\n // Don't save markdown to database\n return null\n },\n ],\n },\n }\n}"],"mappings":"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","names":[]}