payload-plugin-newsletter 0.17.4 → 0.19.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 +103 -0
- package/dist/collections.cjs +256 -23
- package/dist/collections.cjs.map +1 -1
- package/dist/collections.js +256 -23
- package/dist/collections.js.map +1 -1
- package/dist/components.cjs +166 -18
- package/dist/components.cjs.map +1 -1
- package/dist/components.js +166 -18
- package/dist/components.js.map +1 -1
- package/dist/index.cjs +256 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +256 -23
- package/dist/index.js.map +1 -1
- package/dist/types.d.cts +18 -0
- package/dist/types.d.ts +18 -0
- package/dist/utils.cjs +166 -18
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +2 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +166 -18
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
package/dist/collections.js
CHANGED
|
@@ -1016,9 +1016,9 @@ async function convertParagraph(node, mediaUrl, customBlockConverter) {
|
|
|
1016
1016
|
);
|
|
1017
1017
|
const children = childParts.join("");
|
|
1018
1018
|
if (!children.trim()) {
|
|
1019
|
-
return '<p style="margin: 0 0 16px 0; min-height: 1em;"> </p>';
|
|
1019
|
+
return '<p class="mobile-margin-bottom-16" style="margin: 0 0 16px 0; min-height: 1em;"> </p>';
|
|
1020
1020
|
}
|
|
1021
|
-
return `<p style="margin: 0 0 16px 0; text-align: ${align};">${children}</p>`;
|
|
1021
|
+
return `<p class="mobile-margin-bottom-16" style="margin: 0 0 16px 0; text-align: ${align}; font-size: 16px; line-height: 1.5;">${children}</p>`;
|
|
1022
1022
|
}
|
|
1023
1023
|
async function convertHeading(node, mediaUrl, customBlockConverter) {
|
|
1024
1024
|
const tag = node.tag || "h1";
|
|
@@ -1032,8 +1032,14 @@ async function convertHeading(node, mediaUrl, customBlockConverter) {
|
|
|
1032
1032
|
h2: "font-size: 24px; font-weight: 600; margin: 0 0 16px 0; line-height: 1.3;",
|
|
1033
1033
|
h3: "font-size: 20px; font-weight: 600; margin: 0 0 12px 0; line-height: 1.4;"
|
|
1034
1034
|
};
|
|
1035
|
+
const mobileClasses = {
|
|
1036
|
+
h1: "mobile-font-size-24",
|
|
1037
|
+
h2: "mobile-font-size-20",
|
|
1038
|
+
h3: "mobile-font-size-16"
|
|
1039
|
+
};
|
|
1035
1040
|
const style = `${styles2[tag] || styles2.h3} text-align: ${align};`;
|
|
1036
|
-
|
|
1041
|
+
const mobileClass = mobileClasses[tag] || mobileClasses.h3;
|
|
1042
|
+
return `<${tag} class="${mobileClass}" style="${style}">${children}</${tag}>`;
|
|
1037
1043
|
}
|
|
1038
1044
|
async function convertList(node, mediaUrl, customBlockConverter) {
|
|
1039
1045
|
const tag = node.listType === "number" ? "ol" : "ul";
|
|
@@ -1041,8 +1047,8 @@ async function convertList(node, mediaUrl, customBlockConverter) {
|
|
|
1041
1047
|
(node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
|
|
1042
1048
|
);
|
|
1043
1049
|
const children = childParts.join("");
|
|
1044
|
-
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;";
|
|
1045
|
-
return `<${tag} style="${style}">${children}</${tag}>`;
|
|
1050
|
+
const style = tag === "ul" ? "margin: 0 0 16px 0; padding-left: 24px; list-style-type: disc; font-size: 16px; line-height: 1.5;" : "margin: 0 0 16px 0; padding-left: 24px; list-style-type: decimal; font-size: 16px; line-height: 1.5;";
|
|
1051
|
+
return `<${tag} class="mobile-margin-bottom-16" style="${style}">${children}</${tag}>`;
|
|
1046
1052
|
}
|
|
1047
1053
|
async function convertListItem(node, mediaUrl, customBlockConverter) {
|
|
1048
1054
|
const childParts = await Promise.all(
|
|
@@ -1099,16 +1105,16 @@ function convertUpload(node, mediaUrl) {
|
|
|
1099
1105
|
}
|
|
1100
1106
|
const alt = node.fields?.altText || upload.alt || "";
|
|
1101
1107
|
const caption = node.fields?.caption || "";
|
|
1102
|
-
const imgHtml = `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}" style="max-width: 100%; height: auto; display: block; margin: 0 auto;" />`;
|
|
1108
|
+
const imgHtml = `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}" class="mobile-width-100" style="max-width: 100%; height: auto; display: block; margin: 0 auto; border-radius: 6px;" />`;
|
|
1103
1109
|
if (caption) {
|
|
1104
1110
|
return `
|
|
1105
|
-
<div style="margin: 0 0 16px 0; text-align: center;">
|
|
1111
|
+
<div style="margin: 0 0 16px 0; text-align: center;" class="mobile-margin-bottom-16">
|
|
1106
1112
|
${imgHtml}
|
|
1107
|
-
<p style="margin: 8px 0 0 0; font-size: 14px; color: #6b7280; font-style: italic;">${escapeHtml(caption)}</p>
|
|
1113
|
+
<p style="margin: 8px 0 0 0; font-size: 14px; color: #6b7280; font-style: italic; text-align: center;" class="mobile-font-size-14">${escapeHtml(caption)}</p>
|
|
1108
1114
|
</div>
|
|
1109
1115
|
`;
|
|
1110
1116
|
}
|
|
1111
|
-
return `<div style="margin: 0 0 16px 0; text-align: center;">${imgHtml}</div>`;
|
|
1117
|
+
return `<div style="margin: 0 0 16px 0; text-align: center;" class="mobile-margin-bottom-16">${imgHtml}</div>`;
|
|
1112
1118
|
}
|
|
1113
1119
|
async function convertBlock(node, mediaUrl, customBlockConverter) {
|
|
1114
1120
|
const blockType = node.fields?.blockName || node.blockName;
|
|
@@ -1181,11 +1187,14 @@ function escapeHtml(text) {
|
|
|
1181
1187
|
}
|
|
1182
1188
|
function wrapInEmailTemplate(content, preheader) {
|
|
1183
1189
|
return `<!DOCTYPE html>
|
|
1184
|
-
<html lang="en">
|
|
1190
|
+
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
|
1185
1191
|
<head>
|
|
1186
1192
|
<meta charset="UTF-8">
|
|
1187
1193
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1188
|
-
<
|
|
1194
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
1195
|
+
<meta name="x-apple-disable-message-reformatting">
|
|
1196
|
+
<title>Newsletter</title>
|
|
1197
|
+
|
|
1189
1198
|
<!--[if mso]>
|
|
1190
1199
|
<noscript>
|
|
1191
1200
|
<xml>
|
|
@@ -1195,16 +1204,155 @@ function wrapInEmailTemplate(content, preheader) {
|
|
|
1195
1204
|
</xml>
|
|
1196
1205
|
</noscript>
|
|
1197
1206
|
<![endif]-->
|
|
1207
|
+
|
|
1208
|
+
<style>
|
|
1209
|
+
/* Reset and base styles */
|
|
1210
|
+
* {
|
|
1211
|
+
-webkit-text-size-adjust: 100%;
|
|
1212
|
+
-ms-text-size-adjust: 100%;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
body {
|
|
1216
|
+
margin: 0 !important;
|
|
1217
|
+
padding: 0 !important;
|
|
1218
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
|
|
1219
|
+
font-size: 16px;
|
|
1220
|
+
line-height: 1.5;
|
|
1221
|
+
color: #1A1A1A;
|
|
1222
|
+
background-color: #f8f9fa;
|
|
1223
|
+
-webkit-font-smoothing: antialiased;
|
|
1224
|
+
-moz-osx-font-smoothing: grayscale;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
table {
|
|
1228
|
+
border-spacing: 0 !important;
|
|
1229
|
+
border-collapse: collapse !important;
|
|
1230
|
+
table-layout: fixed !important;
|
|
1231
|
+
margin: 0 auto !important;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
table table table {
|
|
1235
|
+
table-layout: auto;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
img {
|
|
1239
|
+
-ms-interpolation-mode: bicubic;
|
|
1240
|
+
max-width: 100%;
|
|
1241
|
+
height: auto;
|
|
1242
|
+
border: 0;
|
|
1243
|
+
outline: none;
|
|
1244
|
+
text-decoration: none;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
/* Responsive styles */
|
|
1248
|
+
@media only screen and (max-width: 640px) {
|
|
1249
|
+
.mobile-hide {
|
|
1250
|
+
display: none !important;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
.mobile-center {
|
|
1254
|
+
text-align: center !important;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
.mobile-width-100 {
|
|
1258
|
+
width: 100% !important;
|
|
1259
|
+
max-width: 100% !important;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
.mobile-padding {
|
|
1263
|
+
padding: 20px !important;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
.mobile-padding-sm {
|
|
1267
|
+
padding: 16px !important;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
.mobile-font-size-14 {
|
|
1271
|
+
font-size: 14px !important;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
.mobile-font-size-16 {
|
|
1275
|
+
font-size: 16px !important;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
.mobile-font-size-20 {
|
|
1279
|
+
font-size: 20px !important;
|
|
1280
|
+
line-height: 1.3 !important;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
.mobile-font-size-24 {
|
|
1284
|
+
font-size: 24px !important;
|
|
1285
|
+
line-height: 1.2 !important;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
/* Stack sections on mobile */
|
|
1289
|
+
.mobile-stack {
|
|
1290
|
+
display: block !important;
|
|
1291
|
+
width: 100% !important;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
/* Mobile-specific spacing */
|
|
1295
|
+
.mobile-margin-bottom-16 {
|
|
1296
|
+
margin-bottom: 16px !important;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
.mobile-margin-bottom-20 {
|
|
1300
|
+
margin-bottom: 20px !important;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
/* Dark mode support */
|
|
1305
|
+
@media (prefers-color-scheme: dark) {
|
|
1306
|
+
.dark-mode-bg {
|
|
1307
|
+
background-color: #1a1a1a !important;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
.dark-mode-text {
|
|
1311
|
+
color: #ffffff !important;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
.dark-mode-border {
|
|
1315
|
+
border-color: #333333 !important;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
/* Outlook-specific fixes */
|
|
1320
|
+
<!--[if mso]>
|
|
1321
|
+
<style>
|
|
1322
|
+
table {
|
|
1323
|
+
border-collapse: collapse;
|
|
1324
|
+
border-spacing: 0;
|
|
1325
|
+
border: none;
|
|
1326
|
+
margin: 0;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
div, p {
|
|
1330
|
+
margin: 0;
|
|
1331
|
+
}
|
|
1332
|
+
</style>
|
|
1333
|
+
<![endif]-->
|
|
1334
|
+
</style>
|
|
1198
1335
|
</head>
|
|
1199
|
-
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 16px; line-height: 1.5; color: #
|
|
1200
|
-
${preheader ?
|
|
1201
|
-
|
|
1336
|
+
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 16px; line-height: 1.5; color: #1A1A1A; background-color: #f8f9fa;">
|
|
1337
|
+
${preheader ? `
|
|
1338
|
+
<!-- Preheader text -->
|
|
1339
|
+
<div style="display: none; max-height: 0; overflow: hidden; font-size: 1px; line-height: 1px; color: transparent;">
|
|
1340
|
+
${escapeHtml(preheader)}
|
|
1341
|
+
</div>
|
|
1342
|
+
` : ""}
|
|
1343
|
+
|
|
1344
|
+
<!-- Main container -->
|
|
1345
|
+
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin: 0; padding: 0; background-color: #f8f9fa;">
|
|
1202
1346
|
<tr>
|
|
1203
|
-
<td align="center" style="padding: 20px
|
|
1204
|
-
|
|
1347
|
+
<td align="center" style="padding: 20px 10px;">
|
|
1348
|
+
<!-- Email wrapper -->
|
|
1349
|
+
<table role="presentation" cellpadding="0" cellspacing="0" width="600" class="mobile-width-100" style="margin: 0 auto; max-width: 600px;">
|
|
1205
1350
|
<tr>
|
|
1206
|
-
<td style="padding:
|
|
1207
|
-
|
|
1351
|
+
<td class="mobile-padding" style="padding: 0;">
|
|
1352
|
+
<!-- Content area with light background -->
|
|
1353
|
+
<div style="background-color: #ffffff; padding: 40px 30px; border-radius: 8px;" class="mobile-padding">
|
|
1354
|
+
${content}
|
|
1355
|
+
</div>
|
|
1208
1356
|
</td>
|
|
1209
1357
|
</tr>
|
|
1210
1358
|
</table>
|
|
@@ -1612,6 +1760,86 @@ var createTestBroadcastEndpoint = (config, collectionSlug) => {
|
|
|
1612
1760
|
};
|
|
1613
1761
|
|
|
1614
1762
|
// src/endpoints/broadcasts/preview.ts
|
|
1763
|
+
async function populateMediaFields(content, payload, config) {
|
|
1764
|
+
if (!content || typeof content !== "object") return content;
|
|
1765
|
+
if (content.root?.children) {
|
|
1766
|
+
for (const child of content.root.children) {
|
|
1767
|
+
await populateBlockMediaFields(child, payload, config);
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
return content;
|
|
1771
|
+
}
|
|
1772
|
+
async function populateBlockMediaFields(node, payload, config) {
|
|
1773
|
+
if (node.type === "block" && node.fields) {
|
|
1774
|
+
const blockType = node.fields.blockType || node.fields.blockName;
|
|
1775
|
+
const customBlocks = config.customizations?.broadcasts?.customBlocks || [];
|
|
1776
|
+
const blockConfig = customBlocks.find((b) => b.slug === blockType);
|
|
1777
|
+
if (blockConfig && blockConfig.fields) {
|
|
1778
|
+
for (const field of blockConfig.fields) {
|
|
1779
|
+
if (field.type === "upload" && field.relationTo && node.fields[field.name]) {
|
|
1780
|
+
const fieldValue = node.fields[field.name];
|
|
1781
|
+
if (typeof fieldValue === "string" && fieldValue.match(/^[a-f0-9]{24}$/i)) {
|
|
1782
|
+
try {
|
|
1783
|
+
const media = await payload.findByID({
|
|
1784
|
+
collection: field.relationTo,
|
|
1785
|
+
id: fieldValue,
|
|
1786
|
+
depth: 0
|
|
1787
|
+
});
|
|
1788
|
+
if (media) {
|
|
1789
|
+
node.fields[field.name] = media;
|
|
1790
|
+
payload.logger?.info(`Populated ${field.name} for block ${blockType}:`, {
|
|
1791
|
+
mediaId: fieldValue,
|
|
1792
|
+
mediaUrl: media.url,
|
|
1793
|
+
filename: media.filename
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
} catch (error) {
|
|
1797
|
+
payload.logger?.error(`Failed to populate ${field.name} for block ${blockType}:`, error);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
if (field.type === "array" && field.fields) {
|
|
1802
|
+
const arrayValue = node.fields[field.name];
|
|
1803
|
+
if (Array.isArray(arrayValue)) {
|
|
1804
|
+
for (const arrayItem of arrayValue) {
|
|
1805
|
+
if (arrayItem && typeof arrayItem === "object") {
|
|
1806
|
+
for (const arrayField of field.fields) {
|
|
1807
|
+
if (arrayField.type === "upload" && arrayField.relationTo && arrayItem[arrayField.name]) {
|
|
1808
|
+
const arrayFieldValue = arrayItem[arrayField.name];
|
|
1809
|
+
if (typeof arrayFieldValue === "string" && arrayFieldValue.match(/^[a-f0-9]{24}$/i)) {
|
|
1810
|
+
try {
|
|
1811
|
+
const media = await payload.findByID({
|
|
1812
|
+
collection: arrayField.relationTo,
|
|
1813
|
+
id: arrayFieldValue,
|
|
1814
|
+
depth: 0
|
|
1815
|
+
});
|
|
1816
|
+
if (media) {
|
|
1817
|
+
arrayItem[arrayField.name] = media;
|
|
1818
|
+
payload.logger?.info(`Populated array ${arrayField.name} for block ${blockType}:`, {
|
|
1819
|
+
mediaId: arrayFieldValue,
|
|
1820
|
+
mediaUrl: media.url,
|
|
1821
|
+
filename: media.filename
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1824
|
+
} catch (error) {
|
|
1825
|
+
payload.logger?.error(`Failed to populate array ${arrayField.name} for block ${blockType}:`, error);
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
if (node.children) {
|
|
1838
|
+
for (const child of node.children) {
|
|
1839
|
+
await populateBlockMediaFields(child, payload, config);
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1615
1843
|
var createBroadcastPreviewEndpoint = (config, _collectionSlug) => {
|
|
1616
1844
|
return {
|
|
1617
1845
|
path: "/preview",
|
|
@@ -1627,7 +1855,9 @@ var createBroadcastPreviewEndpoint = (config, _collectionSlug) => {
|
|
|
1627
1855
|
}, { status: 400 });
|
|
1628
1856
|
}
|
|
1629
1857
|
const mediaUrl = req.payload.config.serverURL ? `${req.payload.config.serverURL}/api/media` : "/api/media";
|
|
1630
|
-
|
|
1858
|
+
req.payload.logger?.info("Populating media fields for email preview...");
|
|
1859
|
+
const populatedContent = await populateMediaFields(content, req.payload, config);
|
|
1860
|
+
const htmlContent = await convertToEmailSafeHtml(populatedContent, {
|
|
1631
1861
|
wrapInTemplate: true,
|
|
1632
1862
|
preheader,
|
|
1633
1863
|
mediaUrl,
|
|
@@ -1926,8 +2156,9 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
1926
2156
|
}
|
|
1927
2157
|
const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
|
|
1928
2158
|
const provider = new BroadcastApiProvider2(providerConfig);
|
|
1929
|
-
req.payload.logger.info("
|
|
1930
|
-
const
|
|
2159
|
+
req.payload.logger.info("Populating media fields and converting content to HTML...");
|
|
2160
|
+
const populatedContent = await populateMediaFields(doc.contentSection?.content, req.payload, pluginConfig);
|
|
2161
|
+
const htmlContent = await convertToEmailSafeHtml(populatedContent, {
|
|
1931
2162
|
customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
|
|
1932
2163
|
});
|
|
1933
2164
|
if (!htmlContent || htmlContent.trim() === "") {
|
|
@@ -2026,7 +2257,8 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
2026
2257
|
return doc;
|
|
2027
2258
|
}
|
|
2028
2259
|
req.payload.logger.info("Creating broadcast in provider (deferred from initial create)...");
|
|
2029
|
-
const
|
|
2260
|
+
const populatedContent = await populateMediaFields(doc.contentSection?.content, req.payload, pluginConfig);
|
|
2261
|
+
const htmlContent = await convertToEmailSafeHtml(populatedContent, {
|
|
2030
2262
|
customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
|
|
2031
2263
|
});
|
|
2032
2264
|
if (!htmlContent || htmlContent.trim() === "") {
|
|
@@ -2087,7 +2319,8 @@ var createBroadcastsCollection = (pluginConfig) => {
|
|
|
2087
2319
|
updates.preheader = doc.contentSection?.preheader;
|
|
2088
2320
|
}
|
|
2089
2321
|
if (JSON.stringify(doc.contentSection?.content) !== JSON.stringify(previousDoc?.contentSection?.content)) {
|
|
2090
|
-
|
|
2322
|
+
const populatedContent = await populateMediaFields(doc.contentSection?.content, req.payload, pluginConfig);
|
|
2323
|
+
updates.content = await convertToEmailSafeHtml(populatedContent, {
|
|
2091
2324
|
customBlockConverter: pluginConfig.customizations?.broadcasts?.customBlockConverter
|
|
2092
2325
|
});
|
|
2093
2326
|
}
|