payload-plugin-newsletter 0.25.9 → 0.25.12
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 +24 -0
- package/dist/collections.cjs +89 -95
- package/dist/collections.cjs.map +1 -1
- package/dist/collections.js +89 -95
- package/dist/collections.js.map +1 -1
- package/dist/fields.cjs +4 -38
- package/dist/fields.cjs.map +1 -1
- package/dist/fields.js +4 -38
- package/dist/fields.js.map +1 -1
- package/dist/server.js +89 -95
- package/dist/utils.cjs +2 -2
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.js +2 -2
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
## [0.25.12] - 2026-01-02
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
- New exported utilities for external preview generation:
|
|
5
|
+
- `generateBroadcastPreviewHtml()` - Generate preview HTML from broadcast content
|
|
6
|
+
- `populateMediaFields()` - Resolve Media IDs to full media objects with URLs
|
|
7
|
+
- `convertToEmailSafeHtml()` - Convert Lexical content to email-safe HTML
|
|
8
|
+
- `PreviewOptions` and `PreviewResult` types for TypeScript support
|
|
9
|
+
- Extracted media population logic to `src/utils/mediaPopulation.ts` for cleaner code organization
|
|
10
|
+
- Created `src/utils/preview.ts` with high-level preview generation utility
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Refactored `src/endpoints/broadcasts/preview.ts` to use new utility files
|
|
14
|
+
- Improved TypeScript types for media population functions
|
|
15
|
+
|
|
16
|
+
## [0.25.11] - 2025-12-11
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- Fixed LinkFeature configuration causing URLs to disappear in the Lexical editor link drawer
|
|
20
|
+
- The custom `fields` array was replacing Payload's default fields, removing the crucial `linkType` field
|
|
21
|
+
- Now uses default LinkFeature fields which include all required fields for the drawer UI to work correctly
|
|
22
|
+
- Fixed TypeScript errors with Payload logger API (pino signature requires object first, then message)
|
|
23
|
+
- Fixed `relationTo` type errors where `string | string[]` was passed to `findByID`
|
|
24
|
+
|
|
1
25
|
## [0.25.9] - 2025-08-19
|
|
2
26
|
|
|
3
27
|
### Fixed
|
package/dist/collections.cjs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
}
|
|
@@ -1792,70 +1758,88 @@ var createTestBroadcastEndpoint = (config, collectionSlug) => {
|
|
|
1792
1758
|
};
|
|
1793
1759
|
};
|
|
1794
1760
|
|
|
1795
|
-
// src/
|
|
1761
|
+
// src/utils/mediaPopulation.ts
|
|
1796
1762
|
async function populateMediaFields(content, payload, config) {
|
|
1797
1763
|
if (!content || typeof content !== "object") return content;
|
|
1798
|
-
|
|
1799
|
-
|
|
1764
|
+
const typedContent = content;
|
|
1765
|
+
if (typedContent.root?.children) {
|
|
1766
|
+
for (const child of typedContent.root.children) {
|
|
1800
1767
|
await populateBlockMediaFields(child, payload, config);
|
|
1801
1768
|
}
|
|
1802
1769
|
}
|
|
1803
1770
|
return content;
|
|
1804
1771
|
}
|
|
1805
1772
|
async function populateBlockMediaFields(node, payload, config) {
|
|
1806
|
-
if (node
|
|
1807
|
-
|
|
1773
|
+
if (!node || typeof node !== "object") return;
|
|
1774
|
+
const typedNode = node;
|
|
1775
|
+
if (typedNode.type === "block" && typedNode.fields) {
|
|
1776
|
+
const blockType = typedNode.fields.blockType || typedNode.fields.blockName;
|
|
1808
1777
|
const customBlocks = config.customizations?.broadcasts?.customBlocks || [];
|
|
1809
1778
|
const blockConfig = customBlocks.find((b) => b.slug === blockType);
|
|
1810
1779
|
if (blockConfig && blockConfig.fields) {
|
|
1811
1780
|
for (const field of blockConfig.fields) {
|
|
1812
|
-
if (field.type === "upload" && field.relationTo &&
|
|
1813
|
-
const fieldValue =
|
|
1781
|
+
if (field.type === "upload" && field.relationTo && typedNode.fields[field.name]) {
|
|
1782
|
+
const fieldValue = typedNode.fields[field.name];
|
|
1783
|
+
const collectionName = Array.isArray(field.relationTo) ? field.relationTo[0] : field.relationTo;
|
|
1814
1784
|
if (typeof fieldValue === "string" && fieldValue.match(/^[a-f0-9]{24}$/i)) {
|
|
1815
1785
|
try {
|
|
1816
1786
|
const media = await payload.findByID({
|
|
1817
|
-
collection:
|
|
1787
|
+
collection: collectionName,
|
|
1818
1788
|
id: fieldValue,
|
|
1819
1789
|
depth: 0
|
|
1820
1790
|
});
|
|
1821
1791
|
if (media) {
|
|
1822
|
-
|
|
1823
|
-
payload.logger?.info(
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1792
|
+
typedNode.fields[field.name] = media;
|
|
1793
|
+
payload.logger?.info(
|
|
1794
|
+
{
|
|
1795
|
+
mediaId: fieldValue,
|
|
1796
|
+
mediaUrl: media.url,
|
|
1797
|
+
filename: media.filename
|
|
1798
|
+
},
|
|
1799
|
+
`Populated ${field.name} for block ${blockType}`
|
|
1800
|
+
);
|
|
1828
1801
|
}
|
|
1829
1802
|
} catch (error) {
|
|
1830
|
-
payload.logger?.error(
|
|
1803
|
+
payload.logger?.error(
|
|
1804
|
+
{ error: String(error) },
|
|
1805
|
+
`Failed to populate ${field.name} for block ${blockType}`
|
|
1806
|
+
);
|
|
1831
1807
|
}
|
|
1832
1808
|
}
|
|
1833
1809
|
}
|
|
1834
1810
|
if (field.type === "array" && field.fields) {
|
|
1835
|
-
const arrayValue =
|
|
1811
|
+
const arrayValue = typedNode.fields[field.name];
|
|
1836
1812
|
if (Array.isArray(arrayValue)) {
|
|
1837
1813
|
for (const arrayItem of arrayValue) {
|
|
1838
1814
|
if (arrayItem && typeof arrayItem === "object") {
|
|
1815
|
+
const typedArrayItem = arrayItem;
|
|
1839
1816
|
for (const arrayField of field.fields) {
|
|
1840
|
-
if (arrayField.type === "upload" && arrayField.relationTo &&
|
|
1841
|
-
const arrayFieldValue =
|
|
1817
|
+
if (arrayField.type === "upload" && arrayField.relationTo && typedArrayItem[arrayField.name]) {
|
|
1818
|
+
const arrayFieldValue = typedArrayItem[arrayField.name];
|
|
1819
|
+
const arrayCollectionName = Array.isArray(arrayField.relationTo) ? arrayField.relationTo[0] : arrayField.relationTo;
|
|
1842
1820
|
if (typeof arrayFieldValue === "string" && arrayFieldValue.match(/^[a-f0-9]{24}$/i)) {
|
|
1843
1821
|
try {
|
|
1844
1822
|
const media = await payload.findByID({
|
|
1845
|
-
collection:
|
|
1823
|
+
collection: arrayCollectionName,
|
|
1846
1824
|
id: arrayFieldValue,
|
|
1847
1825
|
depth: 0
|
|
1848
1826
|
});
|
|
1849
1827
|
if (media) {
|
|
1850
|
-
|
|
1851
|
-
payload.logger?.info(
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1828
|
+
typedArrayItem[arrayField.name] = media;
|
|
1829
|
+
payload.logger?.info(
|
|
1830
|
+
{
|
|
1831
|
+
mediaId: arrayFieldValue,
|
|
1832
|
+
mediaUrl: media.url,
|
|
1833
|
+
filename: media.filename
|
|
1834
|
+
},
|
|
1835
|
+
`Populated array ${arrayField.name} for block ${blockType}`
|
|
1836
|
+
);
|
|
1856
1837
|
}
|
|
1857
1838
|
} catch (error) {
|
|
1858
|
-
payload.logger?.error(
|
|
1839
|
+
payload.logger?.error(
|
|
1840
|
+
{ error: String(error) },
|
|
1841
|
+
`Failed to populate array ${arrayField.name} for block ${blockType}`
|
|
1842
|
+
);
|
|
1859
1843
|
}
|
|
1860
1844
|
}
|
|
1861
1845
|
}
|
|
@@ -1864,23 +1848,24 @@ async function populateBlockMediaFields(node, payload, config) {
|
|
|
1864
1848
|
}
|
|
1865
1849
|
}
|
|
1866
1850
|
}
|
|
1867
|
-
if (field.type === "richText" &&
|
|
1868
|
-
await populateRichTextUploads(
|
|
1851
|
+
if (field.type === "richText" && typedNode.fields[field.name]) {
|
|
1852
|
+
await populateRichTextUploads(typedNode.fields[field.name], payload);
|
|
1869
1853
|
payload.logger?.info(`Processed rich text field ${field.name} for upload nodes`);
|
|
1870
1854
|
}
|
|
1871
1855
|
}
|
|
1872
1856
|
}
|
|
1873
1857
|
}
|
|
1874
|
-
if (
|
|
1875
|
-
for (const child of
|
|
1858
|
+
if (typedNode.children) {
|
|
1859
|
+
for (const child of typedNode.children) {
|
|
1876
1860
|
await populateBlockMediaFields(child, payload, config);
|
|
1877
1861
|
}
|
|
1878
1862
|
}
|
|
1879
1863
|
}
|
|
1880
1864
|
async function populateRichTextUploads(content, payload) {
|
|
1881
1865
|
if (!content || typeof content !== "object") return;
|
|
1882
|
-
|
|
1883
|
-
|
|
1866
|
+
const typedContent = content;
|
|
1867
|
+
if (typedContent.root?.children) {
|
|
1868
|
+
await processNodeArray(typedContent.root.children);
|
|
1884
1869
|
}
|
|
1885
1870
|
if (Array.isArray(content)) {
|
|
1886
1871
|
await processNodeArray(content);
|
|
@@ -1890,33 +1875,42 @@ async function populateRichTextUploads(content, payload) {
|
|
|
1890
1875
|
}
|
|
1891
1876
|
async function processNode(node) {
|
|
1892
1877
|
if (!node || typeof node !== "object") return;
|
|
1893
|
-
|
|
1878
|
+
const typedNode = node;
|
|
1879
|
+
if (typedNode.type === "upload" && typedNode.relationTo === "media" && typeof typedNode.value === "string" && typedNode.value.match(/^[a-f0-9]{24}$/i)) {
|
|
1894
1880
|
try {
|
|
1895
1881
|
const media = await payload.findByID({
|
|
1896
1882
|
collection: "media",
|
|
1897
|
-
id:
|
|
1883
|
+
id: typedNode.value,
|
|
1898
1884
|
depth: 0
|
|
1899
1885
|
});
|
|
1900
1886
|
if (media) {
|
|
1901
|
-
|
|
1902
|
-
payload.logger?.info(
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1887
|
+
typedNode.value = media;
|
|
1888
|
+
payload.logger?.info(
|
|
1889
|
+
{
|
|
1890
|
+
mediaId: typedNode.value,
|
|
1891
|
+
mediaUrl: media.url,
|
|
1892
|
+
filename: media.filename
|
|
1893
|
+
},
|
|
1894
|
+
"Populated rich text upload node"
|
|
1895
|
+
);
|
|
1907
1896
|
}
|
|
1908
1897
|
} catch (error) {
|
|
1909
|
-
payload.logger?.error(
|
|
1898
|
+
payload.logger?.error(
|
|
1899
|
+
{ error: String(error) },
|
|
1900
|
+
`Failed to populate rich text upload ${typedNode.value}`
|
|
1901
|
+
);
|
|
1910
1902
|
}
|
|
1911
1903
|
}
|
|
1912
|
-
if (
|
|
1913
|
-
await processNodeArray(
|
|
1904
|
+
if (typedNode.children && Array.isArray(typedNode.children)) {
|
|
1905
|
+
await processNodeArray(typedNode.children);
|
|
1914
1906
|
}
|
|
1915
|
-
if (
|
|
1916
|
-
await processNodeArray(
|
|
1907
|
+
if (typedNode.root?.children && Array.isArray(typedNode.root.children)) {
|
|
1908
|
+
await processNodeArray(typedNode.root.children);
|
|
1917
1909
|
}
|
|
1918
1910
|
}
|
|
1919
1911
|
}
|
|
1912
|
+
|
|
1913
|
+
// src/endpoints/broadcasts/preview.ts
|
|
1920
1914
|
var createBroadcastPreviewEndpoint = (config, _collectionSlug) => {
|
|
1921
1915
|
return {
|
|
1922
1916
|
path: "/preview",
|
|
@@ -2310,7 +2304,7 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
2310
2304
|
return doc;
|
|
2311
2305
|
}
|
|
2312
2306
|
if (operation === "update") {
|
|
2313
|
-
req.payload.logger.info(
|
|
2307
|
+
req.payload.logger.info({
|
|
2314
2308
|
operation,
|
|
2315
2309
|
hasProviderId: !!doc.providerId,
|
|
2316
2310
|
hasExternalId: !!doc.externalId,
|
|
@@ -2318,7 +2312,7 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
2318
2312
|
publishStatus: doc._status,
|
|
2319
2313
|
hasSubject: !!doc.subject,
|
|
2320
2314
|
hasContent: !!doc.contentSection?.content
|
|
2321
|
-
});
|
|
2315
|
+
}, "Broadcast afterChange update hook triggered");
|
|
2322
2316
|
try {
|
|
2323
2317
|
const providerConfig = await getBroadcastConfig(req, pluginConfig);
|
|
2324
2318
|
if (!providerConfig || !providerConfig.token) {
|
|
@@ -2405,10 +2399,10 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
2405
2399
|
if (JSON.stringify(doc.audienceIds) !== JSON.stringify(previousDoc?.audienceIds)) {
|
|
2406
2400
|
updates.audienceIds = doc.audienceIds?.map((a) => a.audienceId);
|
|
2407
2401
|
}
|
|
2408
|
-
req.payload.logger.info(
|
|
2402
|
+
req.payload.logger.info({
|
|
2409
2403
|
providerId: doc.providerId,
|
|
2410
2404
|
updates
|
|
2411
|
-
});
|
|
2405
|
+
}, "Syncing broadcast updates to provider");
|
|
2412
2406
|
await provider.update(doc.providerId, updates);
|
|
2413
2407
|
req.payload.logger.info(`Broadcast ${doc.id} synced to provider successfully`);
|
|
2414
2408
|
} else {
|
|
@@ -2430,18 +2424,18 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
2430
2424
|
...error.statusText
|
|
2431
2425
|
});
|
|
2432
2426
|
} else if (typeof error === "string") {
|
|
2433
|
-
req.payload.logger.error("Error is a string
|
|
2427
|
+
req.payload.logger.error({ errorValue: error }, "Error is a string");
|
|
2434
2428
|
} else if (error && typeof error === "object") {
|
|
2435
|
-
req.payload.logger.error(
|
|
2429
|
+
req.payload.logger.error({ errorValue: JSON.stringify(error, null, 2) }, "Error is an object");
|
|
2436
2430
|
} else {
|
|
2437
|
-
req.payload.logger.error("Unknown error type
|
|
2431
|
+
req.payload.logger.error({ errorType: typeof error }, "Unknown error type");
|
|
2438
2432
|
}
|
|
2439
|
-
req.payload.logger.error(
|
|
2433
|
+
req.payload.logger.error({
|
|
2440
2434
|
id: doc.id,
|
|
2441
2435
|
subject: doc.subject,
|
|
2442
2436
|
hasContent: !!doc.contentSection?.content,
|
|
2443
2437
|
contentType: doc.contentSection?.content ? typeof doc.contentSection.content : "none"
|
|
2444
|
-
});
|
|
2438
|
+
}, "Failed broadcast document (update operation)");
|
|
2445
2439
|
}
|
|
2446
2440
|
}
|
|
2447
2441
|
return doc;
|
|
@@ -2487,7 +2481,7 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
2487
2481
|
...error.details
|
|
2488
2482
|
});
|
|
2489
2483
|
} else {
|
|
2490
|
-
req.payload.logger.error(`Failed to send broadcast ${doc.id}
|
|
2484
|
+
req.payload.logger.error({ error: String(error) }, `Failed to send broadcast ${doc.id}`);
|
|
2491
2485
|
}
|
|
2492
2486
|
await req.payload.update({
|
|
2493
2487
|
collection: "broadcasts",
|
|
@@ -2530,7 +2524,7 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
2530
2524
|
...error.details
|
|
2531
2525
|
});
|
|
2532
2526
|
} else {
|
|
2533
|
-
req.payload.logger.error("Failed to delete broadcast from provider
|
|
2527
|
+
req.payload.logger.error({ error: String(error) }, "Failed to delete broadcast from provider");
|
|
2534
2528
|
}
|
|
2535
2529
|
}
|
|
2536
2530
|
return doc;
|