payload-plugin-newsletter 0.16.10 → 0.17.1
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 +36 -0
- package/dist/collections.cjs +73 -31
- package/dist/collections.cjs.map +1 -1
- package/dist/collections.js +73 -31
- package/dist/collections.js.map +1 -1
- package/dist/components.cjs +295 -275
- package/dist/components.cjs.map +1 -1
- package/dist/components.js +305 -285
- package/dist/components.js.map +1 -1
- package/dist/index.cjs +118 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +118 -33
- package/dist/index.js.map +1 -1
- package/dist/types.d.cts +7 -0
- package/dist/types.d.ts +7 -0
- package/dist/utils.cjs +64 -28
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +1 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +64 -28
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
## [0.17.1] - 2025-07-29
|
|
2
|
+
|
|
3
|
+
### Fixed
|
|
4
|
+
- **Email Preview Endpoint Path** - Fixed incorrect path for broadcast preview endpoint
|
|
5
|
+
- Removed extra `/api` prefix from preview endpoint path
|
|
6
|
+
- Preview endpoint now correctly registers at `/{collectionSlug}/preview`
|
|
7
|
+
- Fixes 404 error when accessing email preview from the admin UI
|
|
8
|
+
|
|
9
|
+
## [0.17.0] - 2025-07-29
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **Custom Block Email Converter Support** - Added support for custom block email conversion in broadcasts
|
|
13
|
+
- New `customBlockConverter` option in `BroadcastCustomizations` interface
|
|
14
|
+
- Allows users to provide their own email conversion logic for custom Lexical blocks
|
|
15
|
+
- Converter receives block node and media URL, returns email-safe HTML
|
|
16
|
+
- Supports async operations for fetching external data during conversion
|
|
17
|
+
|
|
18
|
+
- **Server-Side Email Preview Generation** - Implemented server-side email preview for accurate rendering
|
|
19
|
+
- New `/api/broadcasts/preview` endpoint for generating email previews
|
|
20
|
+
- Updated BroadcastInlinePreview component to use server-side preview
|
|
21
|
+
- Ensures preview exactly matches what will be sent via email
|
|
22
|
+
- Custom block converters work in both preview and sent emails
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- **Email Conversion Functions Now Async** - All email conversion functions are now async to support custom converters
|
|
26
|
+
- `convertToEmailSafeHtml` and all internal converters are now async
|
|
27
|
+
- Maintains backward compatibility - existing code continues to work
|
|
28
|
+
- Enables custom converters to perform async operations like API calls
|
|
29
|
+
|
|
30
|
+
### Technical
|
|
31
|
+
- Updated `convertNode`, `convertParagraph`, `convertHeading`, etc. to be async functions
|
|
32
|
+
- Added `customBlockConverter` parameter throughout the email conversion pipeline
|
|
33
|
+
- Custom converter is called first, falls back to default handling if it returns empty
|
|
34
|
+
- Error handling for custom converter failures with graceful fallback
|
|
35
|
+
- Preview endpoint uses same conversion logic as email sending for consistency
|
|
36
|
+
|
|
1
37
|
## [0.16.10] - 2025-01-29
|
|
2
38
|
|
|
3
39
|
### Fixed
|
package/dist/collections.cjs
CHANGED
|
@@ -963,62 +963,73 @@ async function convertToEmailSafeHtml(editorState, options) {
|
|
|
963
963
|
if (!editorState) {
|
|
964
964
|
return "";
|
|
965
965
|
}
|
|
966
|
-
const rawHtml = await lexicalToEmailHtml(editorState, options?.mediaUrl);
|
|
966
|
+
const rawHtml = await lexicalToEmailHtml(editorState, options?.mediaUrl, options?.customBlockConverter);
|
|
967
967
|
const sanitizedHtml = import_isomorphic_dompurify.default.sanitize(rawHtml, EMAIL_SAFE_CONFIG);
|
|
968
968
|
if (options?.wrapInTemplate) {
|
|
969
969
|
return wrapInEmailTemplate(sanitizedHtml, options.preheader);
|
|
970
970
|
}
|
|
971
971
|
return sanitizedHtml;
|
|
972
972
|
}
|
|
973
|
-
async function lexicalToEmailHtml(editorState, mediaUrl) {
|
|
973
|
+
async function lexicalToEmailHtml(editorState, mediaUrl, customBlockConverter) {
|
|
974
974
|
const { root } = editorState;
|
|
975
975
|
if (!root || !root.children) {
|
|
976
976
|
return "";
|
|
977
977
|
}
|
|
978
|
-
const
|
|
979
|
-
|
|
978
|
+
const htmlParts = await Promise.all(
|
|
979
|
+
root.children.map((node) => convertNode(node, mediaUrl, customBlockConverter))
|
|
980
|
+
);
|
|
981
|
+
return htmlParts.join("");
|
|
980
982
|
}
|
|
981
|
-
function convertNode(node, mediaUrl) {
|
|
983
|
+
async function convertNode(node, mediaUrl, customBlockConverter) {
|
|
982
984
|
switch (node.type) {
|
|
983
985
|
case "paragraph":
|
|
984
|
-
return convertParagraph(node, mediaUrl);
|
|
986
|
+
return convertParagraph(node, mediaUrl, customBlockConverter);
|
|
985
987
|
case "heading":
|
|
986
|
-
return convertHeading(node, mediaUrl);
|
|
988
|
+
return convertHeading(node, mediaUrl, customBlockConverter);
|
|
987
989
|
case "list":
|
|
988
|
-
return convertList(node, mediaUrl);
|
|
990
|
+
return convertList(node, mediaUrl, customBlockConverter);
|
|
989
991
|
case "listitem":
|
|
990
|
-
return convertListItem(node, mediaUrl);
|
|
992
|
+
return convertListItem(node, mediaUrl, customBlockConverter);
|
|
991
993
|
case "blockquote":
|
|
992
|
-
return convertBlockquote(node, mediaUrl);
|
|
994
|
+
return convertBlockquote(node, mediaUrl, customBlockConverter);
|
|
993
995
|
case "text":
|
|
994
996
|
return convertText(node);
|
|
995
997
|
case "link":
|
|
996
|
-
return convertLink(node, mediaUrl);
|
|
998
|
+
return convertLink(node, mediaUrl, customBlockConverter);
|
|
997
999
|
case "linebreak":
|
|
998
1000
|
return "<br>";
|
|
999
1001
|
case "upload":
|
|
1000
1002
|
return convertUpload(node, mediaUrl);
|
|
1001
1003
|
case "block":
|
|
1002
|
-
return convertBlock(node, mediaUrl);
|
|
1004
|
+
return await convertBlock(node, mediaUrl, customBlockConverter);
|
|
1003
1005
|
default:
|
|
1004
1006
|
if (node.children) {
|
|
1005
|
-
|
|
1007
|
+
const childParts = await Promise.all(
|
|
1008
|
+
node.children.map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
1009
|
+
);
|
|
1010
|
+
return childParts.join("");
|
|
1006
1011
|
}
|
|
1007
1012
|
return "";
|
|
1008
1013
|
}
|
|
1009
1014
|
}
|
|
1010
|
-
function convertParagraph(node, mediaUrl) {
|
|
1015
|
+
async function convertParagraph(node, mediaUrl, customBlockConverter) {
|
|
1011
1016
|
const align = getAlignment(node.format);
|
|
1012
|
-
const
|
|
1017
|
+
const childParts = await Promise.all(
|
|
1018
|
+
(node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
1019
|
+
);
|
|
1020
|
+
const children = childParts.join("");
|
|
1013
1021
|
if (!children.trim()) {
|
|
1014
1022
|
return '<p style="margin: 0 0 16px 0; min-height: 1em;"> </p>';
|
|
1015
1023
|
}
|
|
1016
1024
|
return `<p style="margin: 0 0 16px 0; text-align: ${align};">${children}</p>`;
|
|
1017
1025
|
}
|
|
1018
|
-
function convertHeading(node, mediaUrl) {
|
|
1026
|
+
async function convertHeading(node, mediaUrl, customBlockConverter) {
|
|
1019
1027
|
const tag = node.tag || "h1";
|
|
1020
1028
|
const align = getAlignment(node.format);
|
|
1021
|
-
const
|
|
1029
|
+
const childParts = await Promise.all(
|
|
1030
|
+
(node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
1031
|
+
);
|
|
1032
|
+
const children = childParts.join("");
|
|
1022
1033
|
const styles2 = {
|
|
1023
1034
|
h1: "font-size: 32px; font-weight: 700; margin: 0 0 24px 0; line-height: 1.2;",
|
|
1024
1035
|
h2: "font-size: 24px; font-weight: 600; margin: 0 0 16px 0; line-height: 1.3;",
|
|
@@ -1027,18 +1038,27 @@ function convertHeading(node, mediaUrl) {
|
|
|
1027
1038
|
const style = `${styles2[tag] || styles2.h3} text-align: ${align};`;
|
|
1028
1039
|
return `<${tag} style="${style}">${children}</${tag}>`;
|
|
1029
1040
|
}
|
|
1030
|
-
function convertList(node, mediaUrl) {
|
|
1041
|
+
async function convertList(node, mediaUrl, customBlockConverter) {
|
|
1031
1042
|
const tag = node.listType === "number" ? "ol" : "ul";
|
|
1032
|
-
const
|
|
1043
|
+
const childParts = await Promise.all(
|
|
1044
|
+
(node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
1045
|
+
);
|
|
1046
|
+
const children = childParts.join("");
|
|
1033
1047
|
const style = tag === "ul" ? "margin: 0 0 16px 0; padding-left: 24px; list-style-type: disc;" : "margin: 0 0 16px 0; padding-left: 24px; list-style-type: decimal;";
|
|
1034
1048
|
return `<${tag} style="${style}">${children}</${tag}>`;
|
|
1035
1049
|
}
|
|
1036
|
-
function convertListItem(node, mediaUrl) {
|
|
1037
|
-
const
|
|
1050
|
+
async function convertListItem(node, mediaUrl, customBlockConverter) {
|
|
1051
|
+
const childParts = await Promise.all(
|
|
1052
|
+
(node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
1053
|
+
);
|
|
1054
|
+
const children = childParts.join("");
|
|
1038
1055
|
return `<li style="margin: 0 0 8px 0;">${children}</li>`;
|
|
1039
1056
|
}
|
|
1040
|
-
function convertBlockquote(node, mediaUrl) {
|
|
1041
|
-
const
|
|
1057
|
+
async function convertBlockquote(node, mediaUrl, customBlockConverter) {
|
|
1058
|
+
const childParts = await Promise.all(
|
|
1059
|
+
(node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
1060
|
+
);
|
|
1061
|
+
const children = childParts.join("");
|
|
1042
1062
|
const style = "margin: 0 0 16px 0; padding-left: 16px; border-left: 4px solid #e5e7eb; color: #6b7280;";
|
|
1043
1063
|
return `<blockquote style="${style}">${children}</blockquote>`;
|
|
1044
1064
|
}
|
|
@@ -1058,8 +1078,11 @@ function convertText(node) {
|
|
|
1058
1078
|
}
|
|
1059
1079
|
return text;
|
|
1060
1080
|
}
|
|
1061
|
-
function convertLink(node, mediaUrl) {
|
|
1062
|
-
const
|
|
1081
|
+
async function convertLink(node, mediaUrl, customBlockConverter) {
|
|
1082
|
+
const childParts = await Promise.all(
|
|
1083
|
+
(node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
1084
|
+
);
|
|
1085
|
+
const children = childParts.join("");
|
|
1063
1086
|
const url = node.fields?.url || "#";
|
|
1064
1087
|
const newTab = node.fields?.newTab ?? false;
|
|
1065
1088
|
const targetAttr = newTab ? ' target="_blank"' : "";
|
|
@@ -1090,8 +1113,18 @@ function convertUpload(node, mediaUrl) {
|
|
|
1090
1113
|
}
|
|
1091
1114
|
return `<div style="margin: 0 0 16px 0; text-align: center;">${imgHtml}</div>`;
|
|
1092
1115
|
}
|
|
1093
|
-
function convertBlock(node, mediaUrl) {
|
|
1094
|
-
const blockType = node.fields?.blockName;
|
|
1116
|
+
async function convertBlock(node, mediaUrl, customBlockConverter) {
|
|
1117
|
+
const blockType = node.fields?.blockName || node.blockName;
|
|
1118
|
+
if (customBlockConverter) {
|
|
1119
|
+
try {
|
|
1120
|
+
const customHtml = await customBlockConverter(node, mediaUrl);
|
|
1121
|
+
if (customHtml) {
|
|
1122
|
+
return customHtml;
|
|
1123
|
+
}
|
|
1124
|
+
} catch (error) {
|
|
1125
|
+
console.error(`Custom block converter error for ${blockType}:`, error);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1095
1128
|
switch (blockType) {
|
|
1096
1129
|
case "button":
|
|
1097
1130
|
return convertButtonBlock(node.fields);
|
|
@@ -1099,7 +1132,10 @@ function convertBlock(node, mediaUrl) {
|
|
|
1099
1132
|
return convertDividerBlock(node.fields);
|
|
1100
1133
|
default:
|
|
1101
1134
|
if (node.children) {
|
|
1102
|
-
|
|
1135
|
+
const childParts = await Promise.all(
|
|
1136
|
+
node.children.map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
1137
|
+
);
|
|
1138
|
+
return childParts.join("");
|
|
1103
1139
|
}
|
|
1104
1140
|
return "";
|
|
1105
1141
|
}
|
|
@@ -1473,7 +1509,9 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1473
1509
|
const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
|
|
1474
1510
|
const provider = new BroadcastApiProvider2(providerConfig);
|
|
1475
1511
|
req.payload.logger.info("Converting content to HTML...");
|
|
1476
|
-
const htmlContent = await convertToEmailSafeHtml(doc.contentSection?.content
|
|
1512
|
+
const htmlContent = await convertToEmailSafeHtml(doc.contentSection?.content, {
|
|
1513
|
+
customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
|
|
1514
|
+
});
|
|
1477
1515
|
if (!htmlContent || htmlContent.trim() === "") {
|
|
1478
1516
|
req.payload.logger.info("Skipping provider sync - content is empty after conversion");
|
|
1479
1517
|
return doc;
|
|
@@ -1570,7 +1608,9 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1570
1608
|
return doc;
|
|
1571
1609
|
}
|
|
1572
1610
|
req.payload.logger.info("Creating broadcast in provider (deferred from initial create)...");
|
|
1573
|
-
const htmlContent = await convertToEmailSafeHtml(doc.contentSection?.content
|
|
1611
|
+
const htmlContent = await convertToEmailSafeHtml(doc.contentSection?.content, {
|
|
1612
|
+
customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
|
|
1613
|
+
});
|
|
1574
1614
|
if (!htmlContent || htmlContent.trim() === "") {
|
|
1575
1615
|
req.payload.logger.info("Skipping provider sync - content is empty after conversion");
|
|
1576
1616
|
return doc;
|
|
@@ -1629,7 +1669,9 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1629
1669
|
updates.preheader = doc.contentSection?.preheader;
|
|
1630
1670
|
}
|
|
1631
1671
|
if (JSON.stringify(doc.contentSection?.content) !== JSON.stringify(previousDoc?.contentSection?.content)) {
|
|
1632
|
-
updates.content = await convertToEmailSafeHtml(doc.contentSection?.content
|
|
1672
|
+
updates.content = await convertToEmailSafeHtml(doc.contentSection?.content, {
|
|
1673
|
+
customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
|
|
1674
|
+
});
|
|
1633
1675
|
}
|
|
1634
1676
|
if (doc.settings?.trackOpens !== previousDoc?.settings?.trackOpens) {
|
|
1635
1677
|
updates.trackOpens = doc.settings.trackOpens;
|