payload-plugin-newsletter 0.25.8 → 0.25.11

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,25 @@
1
+ ## [0.25.11] - 2025-12-11
2
+
3
+ ### Fixed
4
+ - Fixed LinkFeature configuration causing URLs to disappear in the Lexical editor link drawer
5
+ - The custom `fields` array was replacing Payload's default fields, removing the crucial `linkType` field
6
+ - Now uses default LinkFeature fields which include all required fields for the drawer UI to work correctly
7
+ - Fixed TypeScript errors with Payload logger API (pino signature requires object first, then message)
8
+ - Fixed `relationTo` type errors where `string | string[]` was passed to `findByID`
9
+
10
+ ## [0.25.9] - 2025-08-19
11
+
12
+ ### Fixed
13
+ - Fixed providerId/externalId not being saved during broadcast creation
14
+ - Moved provider creation from create to update operation
15
+ - Provider broadcast is now created on first update when subject and content are present
16
+ - Ensures IDs are properly persisted to the database
17
+ - Prevents issues with Payload's create operation not persisting afterChange modifications
18
+
19
+ ### Changed
20
+ - Broadcasts are now created in Payload first, then synced to provider on first meaningful update
21
+ - More robust approach that works with Payload's hook lifecycle
22
+
1
23
  ## [0.25.8] - 2025-08-19
2
24
 
3
25
  ### Fixed
@@ -754,25 +754,8 @@ var createEmailSafeFeatures = (additionalBlocks) => {
754
754
  (0, import_richtext_lexical.ItalicFeature)(),
755
755
  (0, import_richtext_lexical.UnderlineFeature)(),
756
756
  (0, import_richtext_lexical.StrikethroughFeature)(),
757
- // Links with enhanced configuration
758
- (0, import_richtext_lexical.LinkFeature)({
759
- fields: [
760
- {
761
- name: "url",
762
- type: "text",
763
- required: true,
764
- admin: {
765
- description: "Enter the full URL (including https://)"
766
- }
767
- },
768
- {
769
- name: "newTab",
770
- type: "checkbox",
771
- label: "Open in new tab",
772
- defaultValue: false
773
- }
774
- ]
775
- }),
757
+ // Links - use default fields to ensure drawer UI works correctly
758
+ (0, import_richtext_lexical.LinkFeature)(),
776
759
  // Lists
777
760
  (0, import_richtext_lexical.OrderedListFeature)(),
778
761
  (0, import_richtext_lexical.UnorderedListFeature)(),
@@ -828,25 +811,8 @@ var createEmailLexicalEditor = (customBlocks = []) => {
828
811
  (0, import_richtext_lexical.ItalicFeature)(),
829
812
  (0, import_richtext_lexical.UnderlineFeature)(),
830
813
  (0, import_richtext_lexical.StrikethroughFeature)(),
831
- // Links with enhanced configuration
832
- (0, import_richtext_lexical.LinkFeature)({
833
- fields: [
834
- {
835
- name: "url",
836
- type: "text",
837
- required: true,
838
- admin: {
839
- description: "Enter the full URL (including https://)"
840
- }
841
- },
842
- {
843
- name: "newTab",
844
- type: "checkbox",
845
- label: "Open in new tab",
846
- defaultValue: false
847
- }
848
- ]
849
- }),
814
+ // Links - use default fields to ensure drawer UI works correctly
815
+ (0, import_richtext_lexical.LinkFeature)(),
850
816
  // Lists
851
817
  (0, import_richtext_lexical.OrderedListFeature)(),
852
818
  (0, import_richtext_lexical.UnorderedListFeature)(),
@@ -1414,7 +1380,7 @@ async function getBroadcastConfig(req, pluginConfig) {
1414
1380
  }
1415
1381
  return pluginConfig.providers?.broadcast || null;
1416
1382
  } catch (error) {
1417
- req.payload.logger.error("Failed to get broadcast config from settings:", error);
1383
+ req.payload.logger.error({ error: String(error) }, "Failed to get broadcast config from settings");
1418
1384
  return pluginConfig.providers?.broadcast || null;
1419
1385
  }
1420
1386
  }
@@ -1811,23 +1777,24 @@ async function populateBlockMediaFields(node, payload, config) {
1811
1777
  for (const field of blockConfig.fields) {
1812
1778
  if (field.type === "upload" && field.relationTo && node.fields[field.name]) {
1813
1779
  const fieldValue = node.fields[field.name];
1780
+ const collectionName = Array.isArray(field.relationTo) ? field.relationTo[0] : field.relationTo;
1814
1781
  if (typeof fieldValue === "string" && fieldValue.match(/^[a-f0-9]{24}$/i)) {
1815
1782
  try {
1816
1783
  const media = await payload.findByID({
1817
- collection: field.relationTo,
1784
+ collection: collectionName,
1818
1785
  id: fieldValue,
1819
1786
  depth: 0
1820
1787
  });
1821
1788
  if (media) {
1822
1789
  node.fields[field.name] = media;
1823
- payload.logger?.info(`Populated ${field.name} for block ${blockType}:`, {
1790
+ payload.logger?.info({
1824
1791
  mediaId: fieldValue,
1825
1792
  mediaUrl: media.url,
1826
1793
  filename: media.filename
1827
- });
1794
+ }, `Populated ${field.name} for block ${blockType}`);
1828
1795
  }
1829
1796
  } catch (error) {
1830
- payload.logger?.error(`Failed to populate ${field.name} for block ${blockType}:`, error);
1797
+ payload.logger?.error({ error: String(error) }, `Failed to populate ${field.name} for block ${blockType}`);
1831
1798
  }
1832
1799
  }
1833
1800
  }
@@ -1839,23 +1806,24 @@ async function populateBlockMediaFields(node, payload, config) {
1839
1806
  for (const arrayField of field.fields) {
1840
1807
  if (arrayField.type === "upload" && arrayField.relationTo && arrayItem[arrayField.name]) {
1841
1808
  const arrayFieldValue = arrayItem[arrayField.name];
1809
+ const arrayCollectionName = Array.isArray(arrayField.relationTo) ? arrayField.relationTo[0] : arrayField.relationTo;
1842
1810
  if (typeof arrayFieldValue === "string" && arrayFieldValue.match(/^[a-f0-9]{24}$/i)) {
1843
1811
  try {
1844
1812
  const media = await payload.findByID({
1845
- collection: arrayField.relationTo,
1813
+ collection: arrayCollectionName,
1846
1814
  id: arrayFieldValue,
1847
1815
  depth: 0
1848
1816
  });
1849
1817
  if (media) {
1850
1818
  arrayItem[arrayField.name] = media;
1851
- payload.logger?.info(`Populated array ${arrayField.name} for block ${blockType}:`, {
1819
+ payload.logger?.info({
1852
1820
  mediaId: arrayFieldValue,
1853
1821
  mediaUrl: media.url,
1854
1822
  filename: media.filename
1855
- });
1823
+ }, `Populated array ${arrayField.name} for block ${blockType}`);
1856
1824
  }
1857
1825
  } catch (error) {
1858
- payload.logger?.error(`Failed to populate array ${arrayField.name} for block ${blockType}:`, error);
1826
+ payload.logger?.error({ error: String(error) }, `Failed to populate array ${arrayField.name} for block ${blockType}`);
1859
1827
  }
1860
1828
  }
1861
1829
  }
@@ -1899,14 +1867,14 @@ async function populateRichTextUploads(content, payload) {
1899
1867
  });
1900
1868
  if (media) {
1901
1869
  node.value = media;
1902
- payload.logger?.info(`Populated rich text upload node:`, {
1870
+ payload.logger?.info({
1903
1871
  mediaId: node.value,
1904
1872
  mediaUrl: media.url,
1905
1873
  filename: media.filename
1906
- });
1874
+ }, "Populated rich text upload node");
1907
1875
  }
1908
1876
  } catch (error) {
1909
- payload.logger?.error(`Failed to populate rich text upload ${node.value}:`, error);
1877
+ payload.logger?.error({ error: String(error) }, `Failed to populate rich text upload ${node.value}`);
1910
1878
  }
1911
1879
  }
1912
1880
  if (node.children && Array.isArray(node.children)) {
@@ -2306,93 +2274,19 @@ var createBroadcastsCollection = (pluginConfig) => {
2306
2274
  async ({ doc, operation, req, previousDoc }) => {
2307
2275
  if (!hasProviders) return doc;
2308
2276
  if (operation === "create") {
2309
- try {
2310
- req.payload.logger.info("Broadcast afterChange CREATE hook (sync) - doc info:", {
2311
- docId: doc.id,
2312
- docIdType: typeof doc.id,
2313
- hasDoc: !!doc,
2314
- operation,
2315
- status: doc._status,
2316
- hasExternalId: !!doc.externalId,
2317
- hasProviderId: !!doc.providerId
2318
- });
2319
- if (doc.externalId || doc.providerId) {
2320
- req.payload.logger.info("Broadcast already has provider IDs, skipping creation", {
2321
- externalId: doc.externalId,
2322
- providerId: doc.providerId
2323
- });
2324
- return doc;
2325
- }
2326
- const providerConfig = await getBroadcastConfig(req, pluginConfig);
2327
- if (!providerConfig || !providerConfig.token) {
2328
- req.payload.logger.error("Broadcast provider not configured in Newsletter Settings or environment variables");
2329
- return doc;
2330
- }
2331
- const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
2332
- const provider = new BroadcastApiProvider2(providerConfig);
2333
- const subject = doc.subject || `Draft Broadcast ${(/* @__PURE__ */ new Date()).toISOString()}`;
2334
- const htmlContent = doc.contentSection?.content ? await convertToEmailSafeHtml(
2335
- await populateMediaFields(doc.contentSection.content, req.payload, pluginConfig),
2336
- {
2337
- wrapInTemplate: pluginConfig.customizations?.broadcasts?.emailPreview?.wrapInTemplate ?? true,
2338
- customWrapper: pluginConfig.customizations?.broadcasts?.emailPreview?.customWrapper,
2339
- preheader: doc.contentSection?.preheader,
2340
- subject,
2341
- documentData: doc,
2342
- customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
2343
- }
2344
- ) : "<p>Draft content - to be updated</p>";
2345
- const createData = {
2346
- name: subject,
2347
- // Use subject as name
2348
- subject,
2349
- preheader: doc.contentSection?.preheader || "",
2350
- content: htmlContent,
2351
- trackOpens: doc.settings?.trackOpens ?? true,
2352
- trackClicks: doc.settings?.trackClicks ?? true,
2353
- replyTo: doc.settings?.replyTo || providerConfig.replyTo,
2354
- audienceIds: doc.audienceIds?.map((a) => a.audienceId) || []
2355
- };
2356
- req.payload.logger.info("Creating broadcast in provider with minimal data to establish association", {
2357
- subject: createData.subject,
2358
- hasActualContent: !!doc.contentSection?.content
2359
- });
2360
- const providerBroadcast = await provider.create(createData);
2361
- req.payload.logger.info("Provider broadcast created:", {
2362
- providerBroadcastId: providerBroadcast.id,
2363
- providerBroadcastIdType: typeof providerBroadcast.id,
2364
- docId: doc.id,
2365
- docIdType: typeof doc.id
2366
- });
2367
- req.payload.logger.info(`Broadcast ${doc.id} created in provider with ID ${providerBroadcast.id}`);
2368
- return {
2369
- ...doc,
2370
- providerId: providerBroadcast.id,
2371
- externalId: providerBroadcast.id,
2372
- // Include externalId in return value
2373
- providerData: providerBroadcast.providerData
2374
- };
2375
- } catch (error) {
2376
- req.payload.logger.error("Failed to create broadcast in provider during initial creation:");
2377
- if (error instanceof Error) {
2378
- req.payload.logger.error("Error details:", {
2379
- message: error.message,
2380
- stack: error.stack,
2381
- name: error.name
2382
- });
2383
- } else {
2384
- req.payload.logger.error("Raw error:", error);
2385
- }
2386
- return doc;
2387
- }
2277
+ req.payload.logger.info("Broadcast created in Payload, provider sync will happen on first update with content");
2278
+ return doc;
2388
2279
  }
2389
2280
  if (operation === "update") {
2390
- req.payload.logger.info("Broadcast afterChange update hook triggered", {
2281
+ req.payload.logger.info({
2391
2282
  operation,
2392
2283
  hasProviderId: !!doc.providerId,
2284
+ hasExternalId: !!doc.externalId,
2393
2285
  sendStatus: doc.sendStatus,
2394
- publishStatus: doc._status
2395
- });
2286
+ publishStatus: doc._status,
2287
+ hasSubject: !!doc.subject,
2288
+ hasContent: !!doc.contentSection?.content
2289
+ }, "Broadcast afterChange update hook triggered");
2396
2290
  try {
2397
2291
  const providerConfig = await getBroadcastConfig(req, pluginConfig);
2398
2292
  if (!providerConfig || !providerConfig.token) {
@@ -2401,8 +2295,40 @@ var createBroadcastsCollection = (pluginConfig) => {
2401
2295
  }
2402
2296
  const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
2403
2297
  const provider = new BroadcastApiProvider2(providerConfig);
2298
+ if (!doc.providerId && !doc.externalId && doc.subject && doc.contentSection?.content) {
2299
+ req.payload.logger.info("Creating broadcast in provider on first update with content");
2300
+ const htmlContent = await convertToEmailSafeHtml(
2301
+ await populateMediaFields(doc.contentSection.content, req.payload, pluginConfig),
2302
+ {
2303
+ wrapInTemplate: pluginConfig.customizations?.broadcasts?.emailPreview?.wrapInTemplate ?? true,
2304
+ customWrapper: pluginConfig.customizations?.broadcasts?.emailPreview?.customWrapper,
2305
+ preheader: doc.contentSection?.preheader,
2306
+ subject: doc.subject,
2307
+ documentData: doc,
2308
+ customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
2309
+ }
2310
+ );
2311
+ const createData = {
2312
+ name: doc.subject,
2313
+ subject: doc.subject,
2314
+ preheader: doc.contentSection?.preheader || "",
2315
+ content: htmlContent,
2316
+ trackOpens: doc.settings?.trackOpens ?? true,
2317
+ trackClicks: doc.settings?.trackClicks ?? true,
2318
+ replyTo: doc.settings?.replyTo || providerConfig.replyTo,
2319
+ audienceIds: doc.audienceIds?.map((a) => a.audienceId) || []
2320
+ };
2321
+ const providerBroadcast = await provider.create(createData);
2322
+ req.payload.logger.info(`Broadcast ${doc.id} created in provider with ID ${providerBroadcast.id}`);
2323
+ return {
2324
+ ...doc,
2325
+ providerId: providerBroadcast.id,
2326
+ externalId: providerBroadcast.id,
2327
+ providerData: providerBroadcast.providerData
2328
+ };
2329
+ }
2404
2330
  if (!doc.providerId) {
2405
- req.payload.logger.warn(`Broadcast ${doc.id} has no providerId - provider sync skipped. This shouldn't happen with immediate creation.`);
2331
+ req.payload.logger.info(`Broadcast ${doc.id} has no providerId and insufficient content for creation - skipping sync`);
2406
2332
  return doc;
2407
2333
  }
2408
2334
  if (doc.providerId) {
@@ -2447,10 +2373,10 @@ var createBroadcastsCollection = (pluginConfig) => {
2447
2373
  if (JSON.stringify(doc.audienceIds) !== JSON.stringify(previousDoc?.audienceIds)) {
2448
2374
  updates.audienceIds = doc.audienceIds?.map((a) => a.audienceId);
2449
2375
  }
2450
- req.payload.logger.info("Syncing broadcast updates to provider", {
2376
+ req.payload.logger.info({
2451
2377
  providerId: doc.providerId,
2452
2378
  updates
2453
- });
2379
+ }, "Syncing broadcast updates to provider");
2454
2380
  await provider.update(doc.providerId, updates);
2455
2381
  req.payload.logger.info(`Broadcast ${doc.id} synced to provider successfully`);
2456
2382
  } else {
@@ -2472,18 +2398,18 @@ var createBroadcastsCollection = (pluginConfig) => {
2472
2398
  ...error.statusText
2473
2399
  });
2474
2400
  } else if (typeof error === "string") {
2475
- req.payload.logger.error("Error is a string:", error);
2401
+ req.payload.logger.error({ errorValue: error }, "Error is a string");
2476
2402
  } else if (error && typeof error === "object") {
2477
- req.payload.logger.error("Error is an object:", JSON.stringify(error, null, 2));
2403
+ req.payload.logger.error({ errorValue: JSON.stringify(error, null, 2) }, "Error is an object");
2478
2404
  } else {
2479
- req.payload.logger.error("Unknown error type:", typeof error);
2405
+ req.payload.logger.error({ errorType: typeof error }, "Unknown error type");
2480
2406
  }
2481
- req.payload.logger.error("Failed broadcast document (update operation):", {
2407
+ req.payload.logger.error({
2482
2408
  id: doc.id,
2483
2409
  subject: doc.subject,
2484
2410
  hasContent: !!doc.contentSection?.content,
2485
2411
  contentType: doc.contentSection?.content ? typeof doc.contentSection.content : "none"
2486
- });
2412
+ }, "Failed broadcast document (update operation)");
2487
2413
  }
2488
2414
  }
2489
2415
  return doc;
@@ -2529,7 +2455,7 @@ var createBroadcastsCollection = (pluginConfig) => {
2529
2455
  ...error.details
2530
2456
  });
2531
2457
  } else {
2532
- req.payload.logger.error(`Failed to send broadcast ${doc.id}:`, error);
2458
+ req.payload.logger.error({ error: String(error) }, `Failed to send broadcast ${doc.id}`);
2533
2459
  }
2534
2460
  await req.payload.update({
2535
2461
  collection: "broadcasts",
@@ -2572,7 +2498,7 @@ var createBroadcastsCollection = (pluginConfig) => {
2572
2498
  ...error.details
2573
2499
  });
2574
2500
  } else {
2575
- req.payload.logger.error("Failed to delete broadcast from provider:", error);
2501
+ req.payload.logger.error({ error: String(error) }, "Failed to delete broadcast from provider");
2576
2502
  }
2577
2503
  }
2578
2504
  return doc;