payload-plugin-newsletter 0.17.3 → 0.18.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/CHANGELOG.md CHANGED
@@ -1,3 +1,51 @@
1
+ ## [0.18.0] - 2025-07-30
2
+
3
+ ### Added
4
+ - **Media Field Population in Custom Blocks** - Fixed critical issue where media fields in custom blocks weren't populated during email conversion
5
+ - Added automatic media field population in preview endpoint before email conversion
6
+ - Media fields now receive full media objects with URLs instead of just ID strings
7
+ - Added `populateFields` option to `BroadcastCustomizations` interface for configurable field population
8
+ - Added recursive population support for array fields containing upload fields
9
+ - Comprehensive logging for media population success and failures
10
+ - Both email previews and sent emails now properly populate media relationships
11
+
12
+ ### Enhanced
13
+ - **Preview Endpoint** - Significantly improved custom block support
14
+ - Preview endpoint now populates media fields before calling custom block converters
15
+ - Custom blocks with images now display correctly in email previews
16
+ - Added detailed logging for debugging media population issues
17
+
18
+ - **Broadcast Sync Logic** - Applied media population to all broadcast operations
19
+ - Create operation now populates media fields before provider sync
20
+ - Update operation populates media fields when content changes
21
+ - Deferred create operation (empty → content) now handles media population
22
+ - Ensures consistency between previews and actual sent emails
23
+
24
+ ### Technical
25
+ - **New Helper Functions**
26
+ - `populateMediaFields()` - Recursively finds and populates upload fields in Lexical content
27
+ - `populateBlockMediaFields()` - Handles individual block media field population
28
+ - Support for both direct upload fields and upload fields within arrays
29
+ - Automatic detection of upload fields based on custom block configuration
30
+ - MongoDB ObjectId pattern matching for field population decisions
31
+
32
+ ### Types
33
+ - **Enhanced BroadcastCustomizations Interface**
34
+ - Added `populateFields` option with string array or function signature
35
+ - Comprehensive documentation with usage examples
36
+ - Support for block-type-specific field population logic
37
+
38
+ ### Breaking Changes
39
+ - None - all changes are backward compatible and additive
40
+
41
+ ## [0.17.4] - 2025-07-30
42
+
43
+ ### Fixed
44
+ - **Resolved Lint Errors** - Fixed CI/CD build failures caused by lint errors
45
+ - Removed unused imports from `endpoints/broadcasts/index.ts`
46
+ - Prefixed unused `collectionSlug` parameter with underscore in `preview.ts`
47
+ - Ensures clean build and successful npm publish
48
+
1
49
  ## [0.17.3] - 2025-07-30
2
50
 
3
51
  ### Fixed
@@ -1625,7 +1625,87 @@ var createTestBroadcastEndpoint = (config, collectionSlug) => {
1625
1625
  };
1626
1626
 
1627
1627
  // src/endpoints/broadcasts/preview.ts
1628
- var createBroadcastPreviewEndpoint = (config, collectionSlug) => {
1628
+ async function populateMediaFields(content, payload, config) {
1629
+ if (!content || typeof content !== "object") return content;
1630
+ if (content.root?.children) {
1631
+ for (const child of content.root.children) {
1632
+ await populateBlockMediaFields(child, payload, config);
1633
+ }
1634
+ }
1635
+ return content;
1636
+ }
1637
+ async function populateBlockMediaFields(node, payload, config) {
1638
+ if (node.type === "block" && node.fields) {
1639
+ const blockType = node.fields.blockType || node.fields.blockName;
1640
+ const customBlocks = config.customizations?.broadcasts?.customBlocks || [];
1641
+ const blockConfig = customBlocks.find((b) => b.slug === blockType);
1642
+ if (blockConfig && blockConfig.fields) {
1643
+ for (const field of blockConfig.fields) {
1644
+ if (field.type === "upload" && field.relationTo && node.fields[field.name]) {
1645
+ const fieldValue = node.fields[field.name];
1646
+ if (typeof fieldValue === "string" && fieldValue.match(/^[a-f0-9]{24}$/i)) {
1647
+ try {
1648
+ const media = await payload.findByID({
1649
+ collection: field.relationTo,
1650
+ id: fieldValue,
1651
+ depth: 0
1652
+ });
1653
+ if (media) {
1654
+ node.fields[field.name] = media;
1655
+ payload.logger?.info(`Populated ${field.name} for block ${blockType}:`, {
1656
+ mediaId: fieldValue,
1657
+ mediaUrl: media.url,
1658
+ filename: media.filename
1659
+ });
1660
+ }
1661
+ } catch (error) {
1662
+ payload.logger?.error(`Failed to populate ${field.name} for block ${blockType}:`, error);
1663
+ }
1664
+ }
1665
+ }
1666
+ if (field.type === "array" && field.fields) {
1667
+ const arrayValue = node.fields[field.name];
1668
+ if (Array.isArray(arrayValue)) {
1669
+ for (const arrayItem of arrayValue) {
1670
+ if (arrayItem && typeof arrayItem === "object") {
1671
+ for (const arrayField of field.fields) {
1672
+ if (arrayField.type === "upload" && arrayField.relationTo && arrayItem[arrayField.name]) {
1673
+ const arrayFieldValue = arrayItem[arrayField.name];
1674
+ if (typeof arrayFieldValue === "string" && arrayFieldValue.match(/^[a-f0-9]{24}$/i)) {
1675
+ try {
1676
+ const media = await payload.findByID({
1677
+ collection: arrayField.relationTo,
1678
+ id: arrayFieldValue,
1679
+ depth: 0
1680
+ });
1681
+ if (media) {
1682
+ arrayItem[arrayField.name] = media;
1683
+ payload.logger?.info(`Populated array ${arrayField.name} for block ${blockType}:`, {
1684
+ mediaId: arrayFieldValue,
1685
+ mediaUrl: media.url,
1686
+ filename: media.filename
1687
+ });
1688
+ }
1689
+ } catch (error) {
1690
+ payload.logger?.error(`Failed to populate array ${arrayField.name} for block ${blockType}:`, error);
1691
+ }
1692
+ }
1693
+ }
1694
+ }
1695
+ }
1696
+ }
1697
+ }
1698
+ }
1699
+ }
1700
+ }
1701
+ }
1702
+ if (node.children) {
1703
+ for (const child of node.children) {
1704
+ await populateBlockMediaFields(child, payload, config);
1705
+ }
1706
+ }
1707
+ }
1708
+ var createBroadcastPreviewEndpoint = (config, _collectionSlug) => {
1629
1709
  return {
1630
1710
  path: "/preview",
1631
1711
  method: "post",
@@ -1640,7 +1720,9 @@ var createBroadcastPreviewEndpoint = (config, collectionSlug) => {
1640
1720
  }, { status: 400 });
1641
1721
  }
1642
1722
  const mediaUrl = req.payload.config.serverURL ? `${req.payload.config.serverURL}/api/media` : "/api/media";
1643
- const htmlContent = await convertToEmailSafeHtml(content, {
1723
+ req.payload.logger?.info("Populating media fields for email preview...");
1724
+ const populatedContent = await populateMediaFields(content, req.payload, config);
1725
+ const htmlContent = await convertToEmailSafeHtml(populatedContent, {
1644
1726
  wrapInTemplate: true,
1645
1727
  preheader,
1646
1728
  mediaUrl,
@@ -1939,8 +2021,9 @@ var createBroadcastsCollection = (pluginConfig) => {
1939
2021
  }
1940
2022
  const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
1941
2023
  const provider = new BroadcastApiProvider2(providerConfig);
1942
- req.payload.logger.info("Converting content to HTML...");
1943
- const htmlContent = await convertToEmailSafeHtml(doc.contentSection?.content, {
2024
+ req.payload.logger.info("Populating media fields and converting content to HTML...");
2025
+ const populatedContent = await populateMediaFields(doc.contentSection?.content, req.payload, pluginConfig);
2026
+ const htmlContent = await convertToEmailSafeHtml(populatedContent, {
1944
2027
  customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
1945
2028
  });
1946
2029
  if (!htmlContent || htmlContent.trim() === "") {
@@ -2039,7 +2122,8 @@ var createBroadcastsCollection = (pluginConfig) => {
2039
2122
  return doc;
2040
2123
  }
2041
2124
  req.payload.logger.info("Creating broadcast in provider (deferred from initial create)...");
2042
- const htmlContent = await convertToEmailSafeHtml(doc.contentSection?.content, {
2125
+ const populatedContent = await populateMediaFields(doc.contentSection?.content, req.payload, pluginConfig);
2126
+ const htmlContent = await convertToEmailSafeHtml(populatedContent, {
2043
2127
  customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
2044
2128
  });
2045
2129
  if (!htmlContent || htmlContent.trim() === "") {
@@ -2100,7 +2184,8 @@ var createBroadcastsCollection = (pluginConfig) => {
2100
2184
  updates.preheader = doc.contentSection?.preheader;
2101
2185
  }
2102
2186
  if (JSON.stringify(doc.contentSection?.content) !== JSON.stringify(previousDoc?.contentSection?.content)) {
2103
- updates.content = await convertToEmailSafeHtml(doc.contentSection?.content, {
2187
+ const populatedContent = await populateMediaFields(doc.contentSection?.content, req.payload, pluginConfig);
2188
+ updates.content = await convertToEmailSafeHtml(populatedContent, {
2104
2189
  customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
2105
2190
  });
2106
2191
  }