@momentumcms/server-analog 0.5.1 → 0.5.3
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/index.cjs +485 -62
- package/index.js +484 -62
- package/package.json +7 -2
package/index.cjs
CHANGED
|
@@ -531,6 +531,383 @@ var init_src = __esm({
|
|
|
531
531
|
}
|
|
532
532
|
});
|
|
533
533
|
|
|
534
|
+
// libs/email/src/lib/utils/escape-html.ts
|
|
535
|
+
function escapeHtml2(unsafe) {
|
|
536
|
+
return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
537
|
+
}
|
|
538
|
+
var init_escape_html = __esm({
|
|
539
|
+
"libs/email/src/lib/utils/escape-html.ts"() {
|
|
540
|
+
"use strict";
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// libs/email/src/lib/utils/css-inliner.ts
|
|
545
|
+
function inlineCss(html) {
|
|
546
|
+
return (0, import_juice.default)(html, {
|
|
547
|
+
removeStyleTags: true,
|
|
548
|
+
preserveMediaQueries: true,
|
|
549
|
+
preserveFontFaces: true,
|
|
550
|
+
insertPreservedExtraCss: true
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
var import_juice;
|
|
554
|
+
var init_css_inliner = __esm({
|
|
555
|
+
"libs/email/src/lib/utils/css-inliner.ts"() {
|
|
556
|
+
"use strict";
|
|
557
|
+
import_juice = __toESM(require("juice"));
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
// libs/email/src/lib/utils/replace-variables.ts
|
|
562
|
+
function replaceVariables(text2, variables) {
|
|
563
|
+
return text2.replace(/\{\{(\w+)\}\}/g, (_, key) => variables[key] ?? "");
|
|
564
|
+
}
|
|
565
|
+
function replaceBlockVariables(blocks2, variables) {
|
|
566
|
+
return blocks2.map((block) => ({
|
|
567
|
+
...block,
|
|
568
|
+
data: replaceDataVariables(block.data, variables)
|
|
569
|
+
}));
|
|
570
|
+
}
|
|
571
|
+
function replaceDataVariables(data, variables) {
|
|
572
|
+
const result = {};
|
|
573
|
+
for (const [key, value] of Object.entries(data)) {
|
|
574
|
+
if (typeof value === "string") {
|
|
575
|
+
result[key] = replaceVariables(value, variables);
|
|
576
|
+
} else if (Array.isArray(value)) {
|
|
577
|
+
result[key] = value.map((item) => {
|
|
578
|
+
if (typeof item === "object" && item !== null && "blocks" in item) {
|
|
579
|
+
const col = item;
|
|
580
|
+
const nestedBlocks = Array.isArray(col["blocks"]) ? col["blocks"] : [];
|
|
581
|
+
return { ...col, blocks: replaceBlockVariables(nestedBlocks, variables) };
|
|
582
|
+
}
|
|
583
|
+
return item;
|
|
584
|
+
});
|
|
585
|
+
} else {
|
|
586
|
+
result[key] = value;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return result;
|
|
590
|
+
}
|
|
591
|
+
var init_replace_variables = __esm({
|
|
592
|
+
"libs/email/src/lib/utils/replace-variables.ts"() {
|
|
593
|
+
"use strict";
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
// libs/email/src/lib/utils/sanitize.ts
|
|
598
|
+
function sanitizeAlignment(value) {
|
|
599
|
+
return VALID_ALIGNMENTS.has(value) ? value : "left";
|
|
600
|
+
}
|
|
601
|
+
function sanitizeCssValue(value) {
|
|
602
|
+
return value.replace(/[;{}()"'<>\\]/g, "");
|
|
603
|
+
}
|
|
604
|
+
function sanitizeFontFamily(value) {
|
|
605
|
+
return value.replace(/[;{}()"<>\\]/g, "");
|
|
606
|
+
}
|
|
607
|
+
function sanitizeCssNumber(value, fallback) {
|
|
608
|
+
if (value === null || value === void 0)
|
|
609
|
+
return String(fallback);
|
|
610
|
+
const num = Number(value);
|
|
611
|
+
return Number.isFinite(num) && num >= 0 ? String(num) : String(fallback);
|
|
612
|
+
}
|
|
613
|
+
function sanitizeUrl(url) {
|
|
614
|
+
const trimmed = url.trim();
|
|
615
|
+
if (!trimmed || trimmed === "#")
|
|
616
|
+
return trimmed || "#";
|
|
617
|
+
try {
|
|
618
|
+
const parsed = new URL(trimmed);
|
|
619
|
+
return SAFE_URL_PROTOCOLS.has(parsed.protocol) ? trimmed : "#";
|
|
620
|
+
} catch {
|
|
621
|
+
if (trimmed.startsWith("/") || trimmed.startsWith("#"))
|
|
622
|
+
return trimmed;
|
|
623
|
+
return "#";
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
var VALID_ALIGNMENTS, SAFE_URL_PROTOCOLS;
|
|
627
|
+
var init_sanitize = __esm({
|
|
628
|
+
"libs/email/src/lib/utils/sanitize.ts"() {
|
|
629
|
+
"use strict";
|
|
630
|
+
VALID_ALIGNMENTS = /* @__PURE__ */ new Set(["left", "center", "right"]);
|
|
631
|
+
SAFE_URL_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:"]);
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
// libs/email/src/types.ts
|
|
636
|
+
var DEFAULT_EMAIL_THEME;
|
|
637
|
+
var init_types = __esm({
|
|
638
|
+
"libs/email/src/types.ts"() {
|
|
639
|
+
"use strict";
|
|
640
|
+
DEFAULT_EMAIL_THEME = {
|
|
641
|
+
primaryColor: "#18181b",
|
|
642
|
+
backgroundColor: "#f4f4f5",
|
|
643
|
+
textColor: "#3f3f46",
|
|
644
|
+
mutedColor: "#71717a",
|
|
645
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif",
|
|
646
|
+
borderRadius: "8px"
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
// libs/email/src/lib/render/render-blocks.ts
|
|
652
|
+
function renderEmailFromBlocks(template, options) {
|
|
653
|
+
const theme = { ...DEFAULT_EMAIL_THEME, ...template.theme };
|
|
654
|
+
const shouldInline = options?.inlineCss ?? true;
|
|
655
|
+
const blocks2 = options?.variables ? replaceBlockVariables(template.blocks, options.variables) : template.blocks;
|
|
656
|
+
const validBlocks = blocks2.filter((block) => {
|
|
657
|
+
if (!isValidBlock(block)) {
|
|
658
|
+
console.warn("[momentum:email] Skipping invalid email block:", block);
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
661
|
+
return true;
|
|
662
|
+
});
|
|
663
|
+
const blocksHtml = validBlocks.map((block) => renderBlock(block, theme, 0)).join("\n");
|
|
664
|
+
let html = wrapEmailDocument(blocksHtml, theme);
|
|
665
|
+
if (shouldInline) {
|
|
666
|
+
html = inlineCss(html);
|
|
667
|
+
}
|
|
668
|
+
return html;
|
|
669
|
+
}
|
|
670
|
+
function wrapEmailDocument(content, theme) {
|
|
671
|
+
const fontFamily = sanitizeFontFamily(theme.fontFamily);
|
|
672
|
+
const bgColor = sanitizeCssValue(theme.backgroundColor);
|
|
673
|
+
const borderRadius = sanitizeCssValue(theme.borderRadius);
|
|
674
|
+
return `<!DOCTYPE html>
|
|
675
|
+
<html lang="en">
|
|
676
|
+
<head>
|
|
677
|
+
<meta charset="UTF-8">
|
|
678
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
679
|
+
</head>
|
|
680
|
+
<body style="margin: 0; padding: 0; font-family: ${fontFamily}; background-color: ${bgColor}; line-height: 1.6;">
|
|
681
|
+
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background-color: ${bgColor};">
|
|
682
|
+
<tr>
|
|
683
|
+
<td style="padding: 40px 20px;">
|
|
684
|
+
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="max-width: 480px; margin: 0 auto; background-color: #ffffff; border-radius: ${borderRadius}; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
|
|
685
|
+
<tr>
|
|
686
|
+
<td style="padding: 40px;">
|
|
687
|
+
${content}
|
|
688
|
+
</td>
|
|
689
|
+
</tr>
|
|
690
|
+
</table>
|
|
691
|
+
</td>
|
|
692
|
+
</tr>
|
|
693
|
+
</table>
|
|
694
|
+
</body>
|
|
695
|
+
</html>`;
|
|
696
|
+
}
|
|
697
|
+
function isValidBlock(block) {
|
|
698
|
+
const rec = block;
|
|
699
|
+
return typeof block === "object" && block !== null && typeof rec["id"] === "string" && rec["id"].length > 0 && typeof rec["type"] === "string" && typeof rec["data"] === "object" && rec["data"] !== null;
|
|
700
|
+
}
|
|
701
|
+
function renderBlock(block, theme, depth) {
|
|
702
|
+
switch (block.type) {
|
|
703
|
+
case "header":
|
|
704
|
+
return renderHeaderBlock(block.data, theme);
|
|
705
|
+
case "text":
|
|
706
|
+
return renderTextBlock(block.data, theme);
|
|
707
|
+
case "button":
|
|
708
|
+
return renderButtonBlock(block.data, theme);
|
|
709
|
+
case "image":
|
|
710
|
+
return renderImageBlock(block.data);
|
|
711
|
+
case "divider":
|
|
712
|
+
return renderDividerBlock(block.data);
|
|
713
|
+
case "spacer":
|
|
714
|
+
return renderSpacerBlock(block.data);
|
|
715
|
+
case "columns":
|
|
716
|
+
if (depth >= MAX_BLOCK_DEPTH) {
|
|
717
|
+
console.warn("[momentum:email] Max nesting depth reached, skipping columns block");
|
|
718
|
+
return "";
|
|
719
|
+
}
|
|
720
|
+
return renderColumnsBlock(block.data, theme, depth);
|
|
721
|
+
case "footer":
|
|
722
|
+
return renderFooterBlock(block.data, theme);
|
|
723
|
+
default:
|
|
724
|
+
return `<!-- unknown block type: ${escapeHtml2(block.type)} -->`;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
function renderHeaderBlock(data, theme) {
|
|
728
|
+
const title = escapeHtml2(String(data["title"] ?? ""));
|
|
729
|
+
const subtitle = data["subtitle"] ? escapeHtml2(String(data["subtitle"])) : "";
|
|
730
|
+
const alignment = sanitizeAlignment(String(data["alignment"] ?? "left"));
|
|
731
|
+
return `<h1 style="margin: 0 0 8px; font-size: 24px; font-weight: 600; color: ${sanitizeCssValue(theme.textColor)}; text-align: ${alignment};">${title}</h1>${subtitle ? `<p style="margin: 0 0 16px; font-size: 16px; color: ${sanitizeCssValue(theme.mutedColor)}; text-align: ${alignment};">${subtitle}</p>` : ""}`;
|
|
732
|
+
}
|
|
733
|
+
function renderTextBlock(data, theme) {
|
|
734
|
+
const content = escapeHtml2(String(data["content"] ?? ""));
|
|
735
|
+
const fontSize = sanitizeCssNumber(data["fontSize"], 16);
|
|
736
|
+
const color = sanitizeCssValue(String(data["color"] ?? theme.textColor));
|
|
737
|
+
const alignment = sanitizeAlignment(String(data["alignment"] ?? "left"));
|
|
738
|
+
return `<p style="margin: 0 0 16px; font-size: ${fontSize}px; color: ${color}; text-align: ${alignment}; line-height: 1.6;">${content}</p>`;
|
|
739
|
+
}
|
|
740
|
+
function renderButtonBlock(data, theme) {
|
|
741
|
+
const label = escapeHtml2(String(data["label"] ?? "Click here"));
|
|
742
|
+
const href = escapeHtml2(sanitizeUrl(String(data["href"] ?? "#")));
|
|
743
|
+
const bgColor = sanitizeCssValue(String(data["backgroundColor"] ?? theme.primaryColor));
|
|
744
|
+
const color = sanitizeCssValue(String(data["color"] ?? "#ffffff"));
|
|
745
|
+
const alignment = sanitizeAlignment(String(data["alignment"] ?? "left"));
|
|
746
|
+
return `<table role="presentation" width="100%" cellspacing="0" cellpadding="0">
|
|
747
|
+
<tr>
|
|
748
|
+
<td style="padding: 0 0 16px;" align="${alignment}">
|
|
749
|
+
<a href="${href}" style="display: inline-block; padding: 12px 24px; background-color: ${bgColor}; color: ${color}; text-decoration: none; border-radius: 6px; font-weight: 500; font-size: 16px;">${label}</a>
|
|
750
|
+
</td>
|
|
751
|
+
</tr>
|
|
752
|
+
</table>`;
|
|
753
|
+
}
|
|
754
|
+
function renderImageBlock(data) {
|
|
755
|
+
const rawSrc = String(data["src"] ?? "").trim();
|
|
756
|
+
if (!rawSrc)
|
|
757
|
+
return "<!-- image block: no src configured -->";
|
|
758
|
+
const src = escapeHtml2(sanitizeUrl(rawSrc));
|
|
759
|
+
const alt = escapeHtml2(String(data["alt"] ?? ""));
|
|
760
|
+
const width = sanitizeCssValue(String(data["width"] ?? "100%"));
|
|
761
|
+
const img = `<img src="${src}" alt="${alt}" width="${width}" style="display: block; max-width: 100%; height: auto; border: 0;">`;
|
|
762
|
+
if (data["href"]) {
|
|
763
|
+
const href = escapeHtml2(sanitizeUrl(String(data["href"])));
|
|
764
|
+
return `<a href="${href}" style="display: block;">${img}</a>`;
|
|
765
|
+
}
|
|
766
|
+
return img;
|
|
767
|
+
}
|
|
768
|
+
function renderDividerBlock(data) {
|
|
769
|
+
const color = sanitizeCssValue(String(data["color"] ?? "#e4e4e7"));
|
|
770
|
+
const margin = sanitizeCssValue(String(data["margin"] ?? "24px 0"));
|
|
771
|
+
return `<hr style="border: none; border-top: 1px solid ${color}; margin: ${margin};">`;
|
|
772
|
+
}
|
|
773
|
+
function renderSpacerBlock(data) {
|
|
774
|
+
const height = sanitizeCssNumber(data["height"], 24);
|
|
775
|
+
return `<div style="height: ${height}px; line-height: ${height}px; font-size: 1px;"> </div>`;
|
|
776
|
+
}
|
|
777
|
+
function renderColumnsBlock(data, theme, depth) {
|
|
778
|
+
const rawColumns = data["columns"];
|
|
779
|
+
const columns = Array.isArray(rawColumns) ? rawColumns : [];
|
|
780
|
+
const width = Math.floor(100 / (columns.length || 1));
|
|
781
|
+
const tds = columns.map((col) => {
|
|
782
|
+
const colObj = (
|
|
783
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- narrowing unknown column objects
|
|
784
|
+
typeof col === "object" && col !== null ? col : {}
|
|
785
|
+
);
|
|
786
|
+
const rawBlocks = colObj["blocks"];
|
|
787
|
+
const colContent = (Array.isArray(rawBlocks) ? rawBlocks : []).filter(isValidBlock).map((b) => renderBlock(b, theme, depth + 1)).join("\n");
|
|
788
|
+
return `<td style="width: ${width}%; vertical-align: top; padding: 0 8px;">${colContent}</td>`;
|
|
789
|
+
}).join("\n");
|
|
790
|
+
return `<table role="presentation" width="100%" cellspacing="0" cellpadding="0"><tr>${tds}</tr></table>`;
|
|
791
|
+
}
|
|
792
|
+
function renderFooterBlock(data, theme) {
|
|
793
|
+
const text2 = escapeHtml2(String(data["text"] ?? ""));
|
|
794
|
+
const color = sanitizeCssValue(String(data["color"] ?? theme.mutedColor));
|
|
795
|
+
return `<p style="margin: 16px 0 0; font-size: 12px; color: ${color}; text-align: center;">${text2}</p>`;
|
|
796
|
+
}
|
|
797
|
+
var MAX_BLOCK_DEPTH;
|
|
798
|
+
var init_render_blocks = __esm({
|
|
799
|
+
"libs/email/src/lib/render/render-blocks.ts"() {
|
|
800
|
+
"use strict";
|
|
801
|
+
init_escape_html();
|
|
802
|
+
init_css_inliner();
|
|
803
|
+
init_replace_variables();
|
|
804
|
+
init_sanitize();
|
|
805
|
+
init_types();
|
|
806
|
+
MAX_BLOCK_DEPTH = 5;
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
// libs/email/src/lib/utils/blocks-to-plain-text.ts
|
|
811
|
+
function blocksToPlainText(blocks2, depth = 0) {
|
|
812
|
+
const lines = [];
|
|
813
|
+
for (const block of blocks2) {
|
|
814
|
+
const text2 = blockToText(block, depth);
|
|
815
|
+
if (text2) {
|
|
816
|
+
lines.push(text2);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
return lines.join("\n\n");
|
|
820
|
+
}
|
|
821
|
+
function blockToText(block, depth) {
|
|
822
|
+
switch (block.type) {
|
|
823
|
+
case "header":
|
|
824
|
+
return headerToText(block.data);
|
|
825
|
+
case "text":
|
|
826
|
+
return String(block.data["content"] ?? "");
|
|
827
|
+
case "button":
|
|
828
|
+
return buttonToText(block.data);
|
|
829
|
+
case "footer":
|
|
830
|
+
return String(block.data["text"] ?? "");
|
|
831
|
+
case "columns":
|
|
832
|
+
if (depth >= MAX_BLOCK_DEPTH2) {
|
|
833
|
+
console.warn("[momentum:email] Max nesting depth reached, skipping columns block");
|
|
834
|
+
return "";
|
|
835
|
+
}
|
|
836
|
+
return columnsToText(block.data, depth);
|
|
837
|
+
case "divider":
|
|
838
|
+
case "spacer":
|
|
839
|
+
case "image":
|
|
840
|
+
return "";
|
|
841
|
+
default:
|
|
842
|
+
return "";
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
function headerToText(data) {
|
|
846
|
+
const title = String(data["title"] ?? "");
|
|
847
|
+
const subtitle = data["subtitle"] ? String(data["subtitle"]) : "";
|
|
848
|
+
if (!title && !subtitle)
|
|
849
|
+
return "";
|
|
850
|
+
if (!subtitle)
|
|
851
|
+
return title;
|
|
852
|
+
return `${title}
|
|
853
|
+
${subtitle}`;
|
|
854
|
+
}
|
|
855
|
+
function buttonToText(data) {
|
|
856
|
+
const label = String(data["label"] ?? "");
|
|
857
|
+
const href = data["href"] ? String(data["href"]) : "";
|
|
858
|
+
if (!label)
|
|
859
|
+
return "";
|
|
860
|
+
if (!href)
|
|
861
|
+
return label;
|
|
862
|
+
return `${label}: ${href}`;
|
|
863
|
+
}
|
|
864
|
+
function columnsToText(data, depth) {
|
|
865
|
+
const columns = data["columns"];
|
|
866
|
+
if (!Array.isArray(columns))
|
|
867
|
+
return "";
|
|
868
|
+
const parts = [];
|
|
869
|
+
for (const col of columns) {
|
|
870
|
+
if (col && typeof col === "object" && Array.isArray(col.blocks)) {
|
|
871
|
+
const colText = blocksToPlainText(col.blocks, depth + 1);
|
|
872
|
+
if (colText) {
|
|
873
|
+
parts.push(colText);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return parts.join("\n\n");
|
|
878
|
+
}
|
|
879
|
+
var MAX_BLOCK_DEPTH2;
|
|
880
|
+
var init_blocks_to_plain_text = __esm({
|
|
881
|
+
"libs/email/src/lib/utils/blocks-to-plain-text.ts"() {
|
|
882
|
+
"use strict";
|
|
883
|
+
MAX_BLOCK_DEPTH2 = 5;
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
// libs/email/src/server.ts
|
|
888
|
+
var server_exports = {};
|
|
889
|
+
__export(server_exports, {
|
|
890
|
+
DEFAULT_EMAIL_THEME: () => DEFAULT_EMAIL_THEME,
|
|
891
|
+
blocksToPlainText: () => blocksToPlainText,
|
|
892
|
+
escapeHtml: () => escapeHtml2,
|
|
893
|
+
inlineCss: () => inlineCss,
|
|
894
|
+
isValidBlock: () => isValidBlock,
|
|
895
|
+
renderEmailFromBlocks: () => renderEmailFromBlocks,
|
|
896
|
+
replaceBlockVariables: () => replaceBlockVariables,
|
|
897
|
+
replaceVariables: () => replaceVariables
|
|
898
|
+
});
|
|
899
|
+
var init_server = __esm({
|
|
900
|
+
"libs/email/src/server.ts"() {
|
|
901
|
+
"use strict";
|
|
902
|
+
init_render_blocks();
|
|
903
|
+
init_replace_variables();
|
|
904
|
+
init_blocks_to_plain_text();
|
|
905
|
+
init_css_inliner();
|
|
906
|
+
init_escape_html();
|
|
907
|
+
init_types();
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
|
|
534
911
|
// libs/server-analog/src/index.ts
|
|
535
912
|
var src_exports2 = {};
|
|
536
913
|
__export(src_exports2, {
|
|
@@ -973,6 +1350,31 @@ function validateRowCount(name, label, count, minRows, maxRows, errors) {
|
|
|
973
1350
|
}
|
|
974
1351
|
|
|
975
1352
|
// libs/core/src/lib/collections/media.collection.ts
|
|
1353
|
+
var validateFocalPoint = (value) => {
|
|
1354
|
+
if (value === null || value === void 0)
|
|
1355
|
+
return true;
|
|
1356
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
1357
|
+
return "Focal point must be an object with x and y coordinates";
|
|
1358
|
+
}
|
|
1359
|
+
const fp = Object.fromEntries(Object.entries(value));
|
|
1360
|
+
if (!("x" in fp) || !("y" in fp)) {
|
|
1361
|
+
return "Focal point must have both x and y properties";
|
|
1362
|
+
}
|
|
1363
|
+
const { x, y } = fp;
|
|
1364
|
+
if (typeof x !== "number" || !Number.isFinite(x)) {
|
|
1365
|
+
return "Focal point x must be a finite number";
|
|
1366
|
+
}
|
|
1367
|
+
if (typeof y !== "number" || !Number.isFinite(y)) {
|
|
1368
|
+
return "Focal point y must be a finite number";
|
|
1369
|
+
}
|
|
1370
|
+
if (x < 0 || x > 1) {
|
|
1371
|
+
return `Focal point x must be between 0 and 1 (received ${x})`;
|
|
1372
|
+
}
|
|
1373
|
+
if (y < 0 || y > 1) {
|
|
1374
|
+
return `Focal point y must be between 0 and 1 (received ${y})`;
|
|
1375
|
+
}
|
|
1376
|
+
return true;
|
|
1377
|
+
};
|
|
976
1378
|
var MediaCollection = defineCollection({
|
|
977
1379
|
slug: "media",
|
|
978
1380
|
labels: {
|
|
@@ -1027,6 +1429,14 @@ var MediaCollection = defineCollection({
|
|
|
1027
1429
|
json("focalPoint", {
|
|
1028
1430
|
label: "Focal Point",
|
|
1029
1431
|
description: "Focal point coordinates for image cropping",
|
|
1432
|
+
validate: validateFocalPoint,
|
|
1433
|
+
admin: {
|
|
1434
|
+
hidden: true
|
|
1435
|
+
}
|
|
1436
|
+
}),
|
|
1437
|
+
json("sizes", {
|
|
1438
|
+
label: "Image Sizes",
|
|
1439
|
+
description: "Generated image size variants",
|
|
1030
1440
|
admin: {
|
|
1031
1441
|
hidden: true
|
|
1032
1442
|
}
|
|
@@ -1796,6 +2206,15 @@ function deepEqual(a, b) {
|
|
|
1796
2206
|
(key) => Object.prototype.hasOwnProperty.call(bRec, key) && deepEqual(aRec[key], bRec[key])
|
|
1797
2207
|
);
|
|
1798
2208
|
}
|
|
2209
|
+
function stripTransientKeys(data) {
|
|
2210
|
+
const result = {};
|
|
2211
|
+
for (const [key, value] of Object.entries(data)) {
|
|
2212
|
+
if (!key.startsWith("_")) {
|
|
2213
|
+
result[key] = value;
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
return result;
|
|
2217
|
+
}
|
|
1799
2218
|
function flattenWhereClause(where) {
|
|
1800
2219
|
if (!where)
|
|
1801
2220
|
return {};
|
|
@@ -1959,6 +2378,7 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
|
|
|
1959
2378
|
);
|
|
1960
2379
|
}
|
|
1961
2380
|
processedData = await this.runHooks("beforeChange", processedData, "create");
|
|
2381
|
+
processedData = stripTransientKeys(processedData);
|
|
1962
2382
|
const doc = await this.adapter.create(this.slug, processedData);
|
|
1963
2383
|
await this.runHooks("afterChange", doc, "create");
|
|
1964
2384
|
if (hasFieldHooks(this.collectionConfig.fields)) {
|
|
@@ -2019,6 +2439,7 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
|
|
|
2019
2439
|
);
|
|
2020
2440
|
}
|
|
2021
2441
|
processedData = await this.runHooks("beforeChange", processedData, "update", originalDoc);
|
|
2442
|
+
processedData = stripTransientKeys(processedData);
|
|
2022
2443
|
const doc = await this.adapter.update(this.slug, id, processedData);
|
|
2023
2444
|
await this.runHooks("afterChange", doc, "update", originalDoc);
|
|
2024
2445
|
if (hasFieldHooks(this.collectionConfig.fields)) {
|
|
@@ -3566,7 +3987,8 @@ async function handleUpload(config, request) {
|
|
|
3566
3987
|
filesize: file.size,
|
|
3567
3988
|
path: storedFile.path,
|
|
3568
3989
|
url: storedFile.url,
|
|
3569
|
-
alt: alt ?? ""
|
|
3990
|
+
alt: alt ?? "",
|
|
3991
|
+
_file: file
|
|
3570
3992
|
};
|
|
3571
3993
|
const api = getMomentumAPI().setContext({ user });
|
|
3572
3994
|
const doc = await api.collection(collection).create(mediaData);
|
|
@@ -3633,7 +4055,8 @@ async function handleCollectionUpload(globalConfig, request) {
|
|
|
3633
4055
|
mimeType: file.mimeType,
|
|
3634
4056
|
filesize: file.size,
|
|
3635
4057
|
path: storedFile.path,
|
|
3636
|
-
url: storedFile.url
|
|
4058
|
+
url: storedFile.url,
|
|
4059
|
+
_file: file
|
|
3637
4060
|
};
|
|
3638
4061
|
const api = getMomentumAPI().setContext({ user });
|
|
3639
4062
|
const doc = await api.collection(collectionSlug).create(docData);
|
|
@@ -3670,6 +4093,7 @@ function getMimeTypeFromPath(path) {
|
|
|
3670
4093
|
png: "image/png",
|
|
3671
4094
|
gif: "image/gif",
|
|
3672
4095
|
webp: "image/webp",
|
|
4096
|
+
avif: "image/avif",
|
|
3673
4097
|
svg: "image/svg+xml",
|
|
3674
4098
|
pdf: "application/pdf",
|
|
3675
4099
|
json: "application/json",
|
|
@@ -4745,13 +5169,12 @@ function getEmailBuilderFieldName(collection) {
|
|
|
4745
5169
|
return field?.name;
|
|
4746
5170
|
}
|
|
4747
5171
|
async function renderEmailPreviewHTML(doc, blocksFieldName) {
|
|
4748
|
-
const
|
|
4749
|
-
const { renderEmailFromBlocks } = await import(emailPkg);
|
|
5172
|
+
const { renderEmailFromBlocks: renderEmailFromBlocks2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
4750
5173
|
const blocks2 = doc[blocksFieldName];
|
|
4751
5174
|
if (!Array.isArray(blocks2) || blocks2.length === 0) {
|
|
4752
5175
|
return '<html><body style="display:flex;align-items:center;justify-content:center;min-height:100vh;color:#666;font-family:sans-serif"><p>No email blocks yet.</p></body></html>';
|
|
4753
5176
|
}
|
|
4754
|
-
return
|
|
5177
|
+
return renderEmailFromBlocks2({ blocks: blocks2 });
|
|
4755
5178
|
}
|
|
4756
5179
|
function nestBracketParams(flat) {
|
|
4757
5180
|
const result = {};
|
|
@@ -5340,6 +5763,63 @@ function createComprehensiveMomentumHandler(config) {
|
|
|
5340
5763
|
}
|
|
5341
5764
|
}
|
|
5342
5765
|
}
|
|
5766
|
+
if (seg2 === "preview" && seg1 && (method === "GET" || method === "POST")) {
|
|
5767
|
+
if (!user) {
|
|
5768
|
+
utils.setResponseStatus(event, 401);
|
|
5769
|
+
return { error: "Authentication required to access preview" };
|
|
5770
|
+
}
|
|
5771
|
+
try {
|
|
5772
|
+
const collectionSlug2 = seg0;
|
|
5773
|
+
const docId = seg1;
|
|
5774
|
+
const collectionConfig = config.collections.find((c) => c.slug === collectionSlug2);
|
|
5775
|
+
if (!collectionConfig) {
|
|
5776
|
+
utils.setResponseStatus(event, 404);
|
|
5777
|
+
return { error: "Collection not found" };
|
|
5778
|
+
}
|
|
5779
|
+
const accessFn = collectionConfig.access?.read;
|
|
5780
|
+
if (accessFn) {
|
|
5781
|
+
const allowed = await Promise.resolve(accessFn({ req: { user } }));
|
|
5782
|
+
if (!allowed) {
|
|
5783
|
+
utils.setResponseStatus(event, 403);
|
|
5784
|
+
return { error: "Access denied" };
|
|
5785
|
+
}
|
|
5786
|
+
}
|
|
5787
|
+
let docRecord;
|
|
5788
|
+
if (method === "POST") {
|
|
5789
|
+
const body2 = await safeReadBody(event, utils, method);
|
|
5790
|
+
if (body2["data"] && typeof body2["data"] === "object") {
|
|
5791
|
+
docRecord = body2["data"];
|
|
5792
|
+
} else {
|
|
5793
|
+
utils.setResponseStatus(event, 400);
|
|
5794
|
+
return { error: "POST preview requires { data: ... } body" };
|
|
5795
|
+
}
|
|
5796
|
+
} else {
|
|
5797
|
+
const contextApi = getContextualAPI(user);
|
|
5798
|
+
const doc = await contextApi.collection(collectionSlug2).findById(docId);
|
|
5799
|
+
if (!doc) {
|
|
5800
|
+
utils.setResponseStatus(event, 404);
|
|
5801
|
+
return { error: "Document not found" };
|
|
5802
|
+
}
|
|
5803
|
+
docRecord = doc;
|
|
5804
|
+
}
|
|
5805
|
+
const emailField = getEmailBuilderFieldName(collectionConfig);
|
|
5806
|
+
const html = emailField ? await renderEmailPreviewHTML(docRecord, emailField) : renderPreviewHTML({ doc: docRecord, collection: collectionConfig });
|
|
5807
|
+
utils.setResponseHeader(event, "Content-Type", "text/html; charset=utf-8");
|
|
5808
|
+
return utils.send(event, html);
|
|
5809
|
+
} catch (error) {
|
|
5810
|
+
const message = sanitizeErrorMessage(error, "Unknown error");
|
|
5811
|
+
if (message.includes("Access denied")) {
|
|
5812
|
+
utils.setResponseStatus(event, 403);
|
|
5813
|
+
return { error: message };
|
|
5814
|
+
}
|
|
5815
|
+
if (message.includes("not found")) {
|
|
5816
|
+
utils.setResponseStatus(event, 404);
|
|
5817
|
+
return { error: message };
|
|
5818
|
+
}
|
|
5819
|
+
utils.setResponseStatus(event, 500);
|
|
5820
|
+
return { error: "Preview failed", message };
|
|
5821
|
+
}
|
|
5822
|
+
}
|
|
5343
5823
|
if (seg1 && seg2 && method === "POST") {
|
|
5344
5824
|
const collectionSlug2 = seg0;
|
|
5345
5825
|
const docId = seg1;
|
|
@@ -5437,63 +5917,6 @@ function createComprehensiveMomentumHandler(config) {
|
|
|
5437
5917
|
};
|
|
5438
5918
|
}
|
|
5439
5919
|
}
|
|
5440
|
-
if (seg2 === "preview" && seg1 && (method === "GET" || method === "POST")) {
|
|
5441
|
-
if (!user) {
|
|
5442
|
-
utils.setResponseStatus(event, 401);
|
|
5443
|
-
return { error: "Authentication required to access preview" };
|
|
5444
|
-
}
|
|
5445
|
-
try {
|
|
5446
|
-
const collectionSlug2 = seg0;
|
|
5447
|
-
const docId = seg1;
|
|
5448
|
-
const collectionConfig = config.collections.find((c) => c.slug === collectionSlug2);
|
|
5449
|
-
if (!collectionConfig) {
|
|
5450
|
-
utils.setResponseStatus(event, 404);
|
|
5451
|
-
return { error: "Collection not found" };
|
|
5452
|
-
}
|
|
5453
|
-
const accessFn = collectionConfig.access?.read;
|
|
5454
|
-
if (accessFn) {
|
|
5455
|
-
const allowed = await Promise.resolve(accessFn({ req: { user } }));
|
|
5456
|
-
if (!allowed) {
|
|
5457
|
-
utils.setResponseStatus(event, 403);
|
|
5458
|
-
return { error: "Access denied" };
|
|
5459
|
-
}
|
|
5460
|
-
}
|
|
5461
|
-
let docRecord;
|
|
5462
|
-
if (method === "POST") {
|
|
5463
|
-
const body2 = await safeReadBody(event, utils, method);
|
|
5464
|
-
if (body2["data"] && typeof body2["data"] === "object") {
|
|
5465
|
-
docRecord = body2["data"];
|
|
5466
|
-
} else {
|
|
5467
|
-
utils.setResponseStatus(event, 400);
|
|
5468
|
-
return { error: "POST preview requires { data: ... } body" };
|
|
5469
|
-
}
|
|
5470
|
-
} else {
|
|
5471
|
-
const contextApi = getContextualAPI(user);
|
|
5472
|
-
const doc = await contextApi.collection(collectionSlug2).findById(docId);
|
|
5473
|
-
if (!doc) {
|
|
5474
|
-
utils.setResponseStatus(event, 404);
|
|
5475
|
-
return { error: "Document not found" };
|
|
5476
|
-
}
|
|
5477
|
-
docRecord = doc;
|
|
5478
|
-
}
|
|
5479
|
-
const emailField = getEmailBuilderFieldName(collectionConfig);
|
|
5480
|
-
const html = emailField ? await renderEmailPreviewHTML(docRecord, emailField) : renderPreviewHTML({ doc: docRecord, collection: collectionConfig });
|
|
5481
|
-
utils.setResponseHeader(event, "Content-Type", "text/html; charset=utf-8");
|
|
5482
|
-
return utils.send(event, html);
|
|
5483
|
-
} catch (error) {
|
|
5484
|
-
const message = sanitizeErrorMessage(error, "Unknown error");
|
|
5485
|
-
if (message.includes("Access denied")) {
|
|
5486
|
-
utils.setResponseStatus(event, 403);
|
|
5487
|
-
return { error: message };
|
|
5488
|
-
}
|
|
5489
|
-
if (message.includes("not found")) {
|
|
5490
|
-
utils.setResponseStatus(event, 404);
|
|
5491
|
-
return { error: message };
|
|
5492
|
-
}
|
|
5493
|
-
utils.setResponseStatus(event, 500);
|
|
5494
|
-
return { error: "Preview failed", message };
|
|
5495
|
-
}
|
|
5496
|
-
}
|
|
5497
5920
|
if (seg0 && seg1 && !seg2) {
|
|
5498
5921
|
const customKey = `${method}:${seg0}/${seg1}`;
|
|
5499
5922
|
const customEntry = customEndpointMap.get(customKey);
|
package/index.js
CHANGED
|
@@ -508,6 +508,382 @@ var init_src = __esm({
|
|
|
508
508
|
}
|
|
509
509
|
});
|
|
510
510
|
|
|
511
|
+
// libs/email/src/lib/utils/escape-html.ts
|
|
512
|
+
function escapeHtml2(unsafe) {
|
|
513
|
+
return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
514
|
+
}
|
|
515
|
+
var init_escape_html = __esm({
|
|
516
|
+
"libs/email/src/lib/utils/escape-html.ts"() {
|
|
517
|
+
"use strict";
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
// libs/email/src/lib/utils/css-inliner.ts
|
|
522
|
+
import juice from "juice";
|
|
523
|
+
function inlineCss(html) {
|
|
524
|
+
return juice(html, {
|
|
525
|
+
removeStyleTags: true,
|
|
526
|
+
preserveMediaQueries: true,
|
|
527
|
+
preserveFontFaces: true,
|
|
528
|
+
insertPreservedExtraCss: true
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
var init_css_inliner = __esm({
|
|
532
|
+
"libs/email/src/lib/utils/css-inliner.ts"() {
|
|
533
|
+
"use strict";
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
// libs/email/src/lib/utils/replace-variables.ts
|
|
538
|
+
function replaceVariables(text2, variables) {
|
|
539
|
+
return text2.replace(/\{\{(\w+)\}\}/g, (_, key) => variables[key] ?? "");
|
|
540
|
+
}
|
|
541
|
+
function replaceBlockVariables(blocks2, variables) {
|
|
542
|
+
return blocks2.map((block) => ({
|
|
543
|
+
...block,
|
|
544
|
+
data: replaceDataVariables(block.data, variables)
|
|
545
|
+
}));
|
|
546
|
+
}
|
|
547
|
+
function replaceDataVariables(data, variables) {
|
|
548
|
+
const result = {};
|
|
549
|
+
for (const [key, value] of Object.entries(data)) {
|
|
550
|
+
if (typeof value === "string") {
|
|
551
|
+
result[key] = replaceVariables(value, variables);
|
|
552
|
+
} else if (Array.isArray(value)) {
|
|
553
|
+
result[key] = value.map((item) => {
|
|
554
|
+
if (typeof item === "object" && item !== null && "blocks" in item) {
|
|
555
|
+
const col = item;
|
|
556
|
+
const nestedBlocks = Array.isArray(col["blocks"]) ? col["blocks"] : [];
|
|
557
|
+
return { ...col, blocks: replaceBlockVariables(nestedBlocks, variables) };
|
|
558
|
+
}
|
|
559
|
+
return item;
|
|
560
|
+
});
|
|
561
|
+
} else {
|
|
562
|
+
result[key] = value;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return result;
|
|
566
|
+
}
|
|
567
|
+
var init_replace_variables = __esm({
|
|
568
|
+
"libs/email/src/lib/utils/replace-variables.ts"() {
|
|
569
|
+
"use strict";
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
// libs/email/src/lib/utils/sanitize.ts
|
|
574
|
+
function sanitizeAlignment(value) {
|
|
575
|
+
return VALID_ALIGNMENTS.has(value) ? value : "left";
|
|
576
|
+
}
|
|
577
|
+
function sanitizeCssValue(value) {
|
|
578
|
+
return value.replace(/[;{}()"'<>\\]/g, "");
|
|
579
|
+
}
|
|
580
|
+
function sanitizeFontFamily(value) {
|
|
581
|
+
return value.replace(/[;{}()"<>\\]/g, "");
|
|
582
|
+
}
|
|
583
|
+
function sanitizeCssNumber(value, fallback) {
|
|
584
|
+
if (value === null || value === void 0)
|
|
585
|
+
return String(fallback);
|
|
586
|
+
const num = Number(value);
|
|
587
|
+
return Number.isFinite(num) && num >= 0 ? String(num) : String(fallback);
|
|
588
|
+
}
|
|
589
|
+
function sanitizeUrl(url) {
|
|
590
|
+
const trimmed = url.trim();
|
|
591
|
+
if (!trimmed || trimmed === "#")
|
|
592
|
+
return trimmed || "#";
|
|
593
|
+
try {
|
|
594
|
+
const parsed = new URL(trimmed);
|
|
595
|
+
return SAFE_URL_PROTOCOLS.has(parsed.protocol) ? trimmed : "#";
|
|
596
|
+
} catch {
|
|
597
|
+
if (trimmed.startsWith("/") || trimmed.startsWith("#"))
|
|
598
|
+
return trimmed;
|
|
599
|
+
return "#";
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
var VALID_ALIGNMENTS, SAFE_URL_PROTOCOLS;
|
|
603
|
+
var init_sanitize = __esm({
|
|
604
|
+
"libs/email/src/lib/utils/sanitize.ts"() {
|
|
605
|
+
"use strict";
|
|
606
|
+
VALID_ALIGNMENTS = /* @__PURE__ */ new Set(["left", "center", "right"]);
|
|
607
|
+
SAFE_URL_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:"]);
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
// libs/email/src/types.ts
|
|
612
|
+
var DEFAULT_EMAIL_THEME;
|
|
613
|
+
var init_types = __esm({
|
|
614
|
+
"libs/email/src/types.ts"() {
|
|
615
|
+
"use strict";
|
|
616
|
+
DEFAULT_EMAIL_THEME = {
|
|
617
|
+
primaryColor: "#18181b",
|
|
618
|
+
backgroundColor: "#f4f4f5",
|
|
619
|
+
textColor: "#3f3f46",
|
|
620
|
+
mutedColor: "#71717a",
|
|
621
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif",
|
|
622
|
+
borderRadius: "8px"
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
// libs/email/src/lib/render/render-blocks.ts
|
|
628
|
+
function renderEmailFromBlocks(template, options) {
|
|
629
|
+
const theme = { ...DEFAULT_EMAIL_THEME, ...template.theme };
|
|
630
|
+
const shouldInline = options?.inlineCss ?? true;
|
|
631
|
+
const blocks2 = options?.variables ? replaceBlockVariables(template.blocks, options.variables) : template.blocks;
|
|
632
|
+
const validBlocks = blocks2.filter((block) => {
|
|
633
|
+
if (!isValidBlock(block)) {
|
|
634
|
+
console.warn("[momentum:email] Skipping invalid email block:", block);
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
return true;
|
|
638
|
+
});
|
|
639
|
+
const blocksHtml = validBlocks.map((block) => renderBlock(block, theme, 0)).join("\n");
|
|
640
|
+
let html = wrapEmailDocument(blocksHtml, theme);
|
|
641
|
+
if (shouldInline) {
|
|
642
|
+
html = inlineCss(html);
|
|
643
|
+
}
|
|
644
|
+
return html;
|
|
645
|
+
}
|
|
646
|
+
function wrapEmailDocument(content, theme) {
|
|
647
|
+
const fontFamily = sanitizeFontFamily(theme.fontFamily);
|
|
648
|
+
const bgColor = sanitizeCssValue(theme.backgroundColor);
|
|
649
|
+
const borderRadius = sanitizeCssValue(theme.borderRadius);
|
|
650
|
+
return `<!DOCTYPE html>
|
|
651
|
+
<html lang="en">
|
|
652
|
+
<head>
|
|
653
|
+
<meta charset="UTF-8">
|
|
654
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
655
|
+
</head>
|
|
656
|
+
<body style="margin: 0; padding: 0; font-family: ${fontFamily}; background-color: ${bgColor}; line-height: 1.6;">
|
|
657
|
+
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background-color: ${bgColor};">
|
|
658
|
+
<tr>
|
|
659
|
+
<td style="padding: 40px 20px;">
|
|
660
|
+
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="max-width: 480px; margin: 0 auto; background-color: #ffffff; border-radius: ${borderRadius}; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
|
|
661
|
+
<tr>
|
|
662
|
+
<td style="padding: 40px;">
|
|
663
|
+
${content}
|
|
664
|
+
</td>
|
|
665
|
+
</tr>
|
|
666
|
+
</table>
|
|
667
|
+
</td>
|
|
668
|
+
</tr>
|
|
669
|
+
</table>
|
|
670
|
+
</body>
|
|
671
|
+
</html>`;
|
|
672
|
+
}
|
|
673
|
+
function isValidBlock(block) {
|
|
674
|
+
const rec = block;
|
|
675
|
+
return typeof block === "object" && block !== null && typeof rec["id"] === "string" && rec["id"].length > 0 && typeof rec["type"] === "string" && typeof rec["data"] === "object" && rec["data"] !== null;
|
|
676
|
+
}
|
|
677
|
+
function renderBlock(block, theme, depth) {
|
|
678
|
+
switch (block.type) {
|
|
679
|
+
case "header":
|
|
680
|
+
return renderHeaderBlock(block.data, theme);
|
|
681
|
+
case "text":
|
|
682
|
+
return renderTextBlock(block.data, theme);
|
|
683
|
+
case "button":
|
|
684
|
+
return renderButtonBlock(block.data, theme);
|
|
685
|
+
case "image":
|
|
686
|
+
return renderImageBlock(block.data);
|
|
687
|
+
case "divider":
|
|
688
|
+
return renderDividerBlock(block.data);
|
|
689
|
+
case "spacer":
|
|
690
|
+
return renderSpacerBlock(block.data);
|
|
691
|
+
case "columns":
|
|
692
|
+
if (depth >= MAX_BLOCK_DEPTH) {
|
|
693
|
+
console.warn("[momentum:email] Max nesting depth reached, skipping columns block");
|
|
694
|
+
return "";
|
|
695
|
+
}
|
|
696
|
+
return renderColumnsBlock(block.data, theme, depth);
|
|
697
|
+
case "footer":
|
|
698
|
+
return renderFooterBlock(block.data, theme);
|
|
699
|
+
default:
|
|
700
|
+
return `<!-- unknown block type: ${escapeHtml2(block.type)} -->`;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
function renderHeaderBlock(data, theme) {
|
|
704
|
+
const title = escapeHtml2(String(data["title"] ?? ""));
|
|
705
|
+
const subtitle = data["subtitle"] ? escapeHtml2(String(data["subtitle"])) : "";
|
|
706
|
+
const alignment = sanitizeAlignment(String(data["alignment"] ?? "left"));
|
|
707
|
+
return `<h1 style="margin: 0 0 8px; font-size: 24px; font-weight: 600; color: ${sanitizeCssValue(theme.textColor)}; text-align: ${alignment};">${title}</h1>${subtitle ? `<p style="margin: 0 0 16px; font-size: 16px; color: ${sanitizeCssValue(theme.mutedColor)}; text-align: ${alignment};">${subtitle}</p>` : ""}`;
|
|
708
|
+
}
|
|
709
|
+
function renderTextBlock(data, theme) {
|
|
710
|
+
const content = escapeHtml2(String(data["content"] ?? ""));
|
|
711
|
+
const fontSize = sanitizeCssNumber(data["fontSize"], 16);
|
|
712
|
+
const color = sanitizeCssValue(String(data["color"] ?? theme.textColor));
|
|
713
|
+
const alignment = sanitizeAlignment(String(data["alignment"] ?? "left"));
|
|
714
|
+
return `<p style="margin: 0 0 16px; font-size: ${fontSize}px; color: ${color}; text-align: ${alignment}; line-height: 1.6;">${content}</p>`;
|
|
715
|
+
}
|
|
716
|
+
function renderButtonBlock(data, theme) {
|
|
717
|
+
const label = escapeHtml2(String(data["label"] ?? "Click here"));
|
|
718
|
+
const href = escapeHtml2(sanitizeUrl(String(data["href"] ?? "#")));
|
|
719
|
+
const bgColor = sanitizeCssValue(String(data["backgroundColor"] ?? theme.primaryColor));
|
|
720
|
+
const color = sanitizeCssValue(String(data["color"] ?? "#ffffff"));
|
|
721
|
+
const alignment = sanitizeAlignment(String(data["alignment"] ?? "left"));
|
|
722
|
+
return `<table role="presentation" width="100%" cellspacing="0" cellpadding="0">
|
|
723
|
+
<tr>
|
|
724
|
+
<td style="padding: 0 0 16px;" align="${alignment}">
|
|
725
|
+
<a href="${href}" style="display: inline-block; padding: 12px 24px; background-color: ${bgColor}; color: ${color}; text-decoration: none; border-radius: 6px; font-weight: 500; font-size: 16px;">${label}</a>
|
|
726
|
+
</td>
|
|
727
|
+
</tr>
|
|
728
|
+
</table>`;
|
|
729
|
+
}
|
|
730
|
+
function renderImageBlock(data) {
|
|
731
|
+
const rawSrc = String(data["src"] ?? "").trim();
|
|
732
|
+
if (!rawSrc)
|
|
733
|
+
return "<!-- image block: no src configured -->";
|
|
734
|
+
const src = escapeHtml2(sanitizeUrl(rawSrc));
|
|
735
|
+
const alt = escapeHtml2(String(data["alt"] ?? ""));
|
|
736
|
+
const width = sanitizeCssValue(String(data["width"] ?? "100%"));
|
|
737
|
+
const img = `<img src="${src}" alt="${alt}" width="${width}" style="display: block; max-width: 100%; height: auto; border: 0;">`;
|
|
738
|
+
if (data["href"]) {
|
|
739
|
+
const href = escapeHtml2(sanitizeUrl(String(data["href"])));
|
|
740
|
+
return `<a href="${href}" style="display: block;">${img}</a>`;
|
|
741
|
+
}
|
|
742
|
+
return img;
|
|
743
|
+
}
|
|
744
|
+
function renderDividerBlock(data) {
|
|
745
|
+
const color = sanitizeCssValue(String(data["color"] ?? "#e4e4e7"));
|
|
746
|
+
const margin = sanitizeCssValue(String(data["margin"] ?? "24px 0"));
|
|
747
|
+
return `<hr style="border: none; border-top: 1px solid ${color}; margin: ${margin};">`;
|
|
748
|
+
}
|
|
749
|
+
function renderSpacerBlock(data) {
|
|
750
|
+
const height = sanitizeCssNumber(data["height"], 24);
|
|
751
|
+
return `<div style="height: ${height}px; line-height: ${height}px; font-size: 1px;"> </div>`;
|
|
752
|
+
}
|
|
753
|
+
function renderColumnsBlock(data, theme, depth) {
|
|
754
|
+
const rawColumns = data["columns"];
|
|
755
|
+
const columns = Array.isArray(rawColumns) ? rawColumns : [];
|
|
756
|
+
const width = Math.floor(100 / (columns.length || 1));
|
|
757
|
+
const tds = columns.map((col) => {
|
|
758
|
+
const colObj = (
|
|
759
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- narrowing unknown column objects
|
|
760
|
+
typeof col === "object" && col !== null ? col : {}
|
|
761
|
+
);
|
|
762
|
+
const rawBlocks = colObj["blocks"];
|
|
763
|
+
const colContent = (Array.isArray(rawBlocks) ? rawBlocks : []).filter(isValidBlock).map((b) => renderBlock(b, theme, depth + 1)).join("\n");
|
|
764
|
+
return `<td style="width: ${width}%; vertical-align: top; padding: 0 8px;">${colContent}</td>`;
|
|
765
|
+
}).join("\n");
|
|
766
|
+
return `<table role="presentation" width="100%" cellspacing="0" cellpadding="0"><tr>${tds}</tr></table>`;
|
|
767
|
+
}
|
|
768
|
+
function renderFooterBlock(data, theme) {
|
|
769
|
+
const text2 = escapeHtml2(String(data["text"] ?? ""));
|
|
770
|
+
const color = sanitizeCssValue(String(data["color"] ?? theme.mutedColor));
|
|
771
|
+
return `<p style="margin: 16px 0 0; font-size: 12px; color: ${color}; text-align: center;">${text2}</p>`;
|
|
772
|
+
}
|
|
773
|
+
var MAX_BLOCK_DEPTH;
|
|
774
|
+
var init_render_blocks = __esm({
|
|
775
|
+
"libs/email/src/lib/render/render-blocks.ts"() {
|
|
776
|
+
"use strict";
|
|
777
|
+
init_escape_html();
|
|
778
|
+
init_css_inliner();
|
|
779
|
+
init_replace_variables();
|
|
780
|
+
init_sanitize();
|
|
781
|
+
init_types();
|
|
782
|
+
MAX_BLOCK_DEPTH = 5;
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
// libs/email/src/lib/utils/blocks-to-plain-text.ts
|
|
787
|
+
function blocksToPlainText(blocks2, depth = 0) {
|
|
788
|
+
const lines = [];
|
|
789
|
+
for (const block of blocks2) {
|
|
790
|
+
const text2 = blockToText(block, depth);
|
|
791
|
+
if (text2) {
|
|
792
|
+
lines.push(text2);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return lines.join("\n\n");
|
|
796
|
+
}
|
|
797
|
+
function blockToText(block, depth) {
|
|
798
|
+
switch (block.type) {
|
|
799
|
+
case "header":
|
|
800
|
+
return headerToText(block.data);
|
|
801
|
+
case "text":
|
|
802
|
+
return String(block.data["content"] ?? "");
|
|
803
|
+
case "button":
|
|
804
|
+
return buttonToText(block.data);
|
|
805
|
+
case "footer":
|
|
806
|
+
return String(block.data["text"] ?? "");
|
|
807
|
+
case "columns":
|
|
808
|
+
if (depth >= MAX_BLOCK_DEPTH2) {
|
|
809
|
+
console.warn("[momentum:email] Max nesting depth reached, skipping columns block");
|
|
810
|
+
return "";
|
|
811
|
+
}
|
|
812
|
+
return columnsToText(block.data, depth);
|
|
813
|
+
case "divider":
|
|
814
|
+
case "spacer":
|
|
815
|
+
case "image":
|
|
816
|
+
return "";
|
|
817
|
+
default:
|
|
818
|
+
return "";
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
function headerToText(data) {
|
|
822
|
+
const title = String(data["title"] ?? "");
|
|
823
|
+
const subtitle = data["subtitle"] ? String(data["subtitle"]) : "";
|
|
824
|
+
if (!title && !subtitle)
|
|
825
|
+
return "";
|
|
826
|
+
if (!subtitle)
|
|
827
|
+
return title;
|
|
828
|
+
return `${title}
|
|
829
|
+
${subtitle}`;
|
|
830
|
+
}
|
|
831
|
+
function buttonToText(data) {
|
|
832
|
+
const label = String(data["label"] ?? "");
|
|
833
|
+
const href = data["href"] ? String(data["href"]) : "";
|
|
834
|
+
if (!label)
|
|
835
|
+
return "";
|
|
836
|
+
if (!href)
|
|
837
|
+
return label;
|
|
838
|
+
return `${label}: ${href}`;
|
|
839
|
+
}
|
|
840
|
+
function columnsToText(data, depth) {
|
|
841
|
+
const columns = data["columns"];
|
|
842
|
+
if (!Array.isArray(columns))
|
|
843
|
+
return "";
|
|
844
|
+
const parts = [];
|
|
845
|
+
for (const col of columns) {
|
|
846
|
+
if (col && typeof col === "object" && Array.isArray(col.blocks)) {
|
|
847
|
+
const colText = blocksToPlainText(col.blocks, depth + 1);
|
|
848
|
+
if (colText) {
|
|
849
|
+
parts.push(colText);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return parts.join("\n\n");
|
|
854
|
+
}
|
|
855
|
+
var MAX_BLOCK_DEPTH2;
|
|
856
|
+
var init_blocks_to_plain_text = __esm({
|
|
857
|
+
"libs/email/src/lib/utils/blocks-to-plain-text.ts"() {
|
|
858
|
+
"use strict";
|
|
859
|
+
MAX_BLOCK_DEPTH2 = 5;
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
// libs/email/src/server.ts
|
|
864
|
+
var server_exports = {};
|
|
865
|
+
__export(server_exports, {
|
|
866
|
+
DEFAULT_EMAIL_THEME: () => DEFAULT_EMAIL_THEME,
|
|
867
|
+
blocksToPlainText: () => blocksToPlainText,
|
|
868
|
+
escapeHtml: () => escapeHtml2,
|
|
869
|
+
inlineCss: () => inlineCss,
|
|
870
|
+
isValidBlock: () => isValidBlock,
|
|
871
|
+
renderEmailFromBlocks: () => renderEmailFromBlocks,
|
|
872
|
+
replaceBlockVariables: () => replaceBlockVariables,
|
|
873
|
+
replaceVariables: () => replaceVariables
|
|
874
|
+
});
|
|
875
|
+
var init_server = __esm({
|
|
876
|
+
"libs/email/src/server.ts"() {
|
|
877
|
+
"use strict";
|
|
878
|
+
init_render_blocks();
|
|
879
|
+
init_replace_variables();
|
|
880
|
+
init_blocks_to_plain_text();
|
|
881
|
+
init_css_inliner();
|
|
882
|
+
init_escape_html();
|
|
883
|
+
init_types();
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
|
|
511
887
|
// libs/logger/src/lib/log-level.ts
|
|
512
888
|
var LOG_LEVEL_VALUES = {
|
|
513
889
|
debug: 0,
|
|
@@ -941,6 +1317,31 @@ function validateRowCount(name, label, count, minRows, maxRows, errors) {
|
|
|
941
1317
|
}
|
|
942
1318
|
|
|
943
1319
|
// libs/core/src/lib/collections/media.collection.ts
|
|
1320
|
+
var validateFocalPoint = (value) => {
|
|
1321
|
+
if (value === null || value === void 0)
|
|
1322
|
+
return true;
|
|
1323
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
1324
|
+
return "Focal point must be an object with x and y coordinates";
|
|
1325
|
+
}
|
|
1326
|
+
const fp = Object.fromEntries(Object.entries(value));
|
|
1327
|
+
if (!("x" in fp) || !("y" in fp)) {
|
|
1328
|
+
return "Focal point must have both x and y properties";
|
|
1329
|
+
}
|
|
1330
|
+
const { x, y } = fp;
|
|
1331
|
+
if (typeof x !== "number" || !Number.isFinite(x)) {
|
|
1332
|
+
return "Focal point x must be a finite number";
|
|
1333
|
+
}
|
|
1334
|
+
if (typeof y !== "number" || !Number.isFinite(y)) {
|
|
1335
|
+
return "Focal point y must be a finite number";
|
|
1336
|
+
}
|
|
1337
|
+
if (x < 0 || x > 1) {
|
|
1338
|
+
return `Focal point x must be between 0 and 1 (received ${x})`;
|
|
1339
|
+
}
|
|
1340
|
+
if (y < 0 || y > 1) {
|
|
1341
|
+
return `Focal point y must be between 0 and 1 (received ${y})`;
|
|
1342
|
+
}
|
|
1343
|
+
return true;
|
|
1344
|
+
};
|
|
944
1345
|
var MediaCollection = defineCollection({
|
|
945
1346
|
slug: "media",
|
|
946
1347
|
labels: {
|
|
@@ -995,6 +1396,14 @@ var MediaCollection = defineCollection({
|
|
|
995
1396
|
json("focalPoint", {
|
|
996
1397
|
label: "Focal Point",
|
|
997
1398
|
description: "Focal point coordinates for image cropping",
|
|
1399
|
+
validate: validateFocalPoint,
|
|
1400
|
+
admin: {
|
|
1401
|
+
hidden: true
|
|
1402
|
+
}
|
|
1403
|
+
}),
|
|
1404
|
+
json("sizes", {
|
|
1405
|
+
label: "Image Sizes",
|
|
1406
|
+
description: "Generated image size variants",
|
|
998
1407
|
admin: {
|
|
999
1408
|
hidden: true
|
|
1000
1409
|
}
|
|
@@ -1764,6 +2173,15 @@ function deepEqual(a, b) {
|
|
|
1764
2173
|
(key) => Object.prototype.hasOwnProperty.call(bRec, key) && deepEqual(aRec[key], bRec[key])
|
|
1765
2174
|
);
|
|
1766
2175
|
}
|
|
2176
|
+
function stripTransientKeys(data) {
|
|
2177
|
+
const result = {};
|
|
2178
|
+
for (const [key, value] of Object.entries(data)) {
|
|
2179
|
+
if (!key.startsWith("_")) {
|
|
2180
|
+
result[key] = value;
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
return result;
|
|
2184
|
+
}
|
|
1767
2185
|
function flattenWhereClause(where) {
|
|
1768
2186
|
if (!where)
|
|
1769
2187
|
return {};
|
|
@@ -1927,6 +2345,7 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
|
|
|
1927
2345
|
);
|
|
1928
2346
|
}
|
|
1929
2347
|
processedData = await this.runHooks("beforeChange", processedData, "create");
|
|
2348
|
+
processedData = stripTransientKeys(processedData);
|
|
1930
2349
|
const doc = await this.adapter.create(this.slug, processedData);
|
|
1931
2350
|
await this.runHooks("afterChange", doc, "create");
|
|
1932
2351
|
if (hasFieldHooks(this.collectionConfig.fields)) {
|
|
@@ -1987,6 +2406,7 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
|
|
|
1987
2406
|
);
|
|
1988
2407
|
}
|
|
1989
2408
|
processedData = await this.runHooks("beforeChange", processedData, "update", originalDoc);
|
|
2409
|
+
processedData = stripTransientKeys(processedData);
|
|
1990
2410
|
const doc = await this.adapter.update(this.slug, id, processedData);
|
|
1991
2411
|
await this.runHooks("afterChange", doc, "update", originalDoc);
|
|
1992
2412
|
if (hasFieldHooks(this.collectionConfig.fields)) {
|
|
@@ -3547,7 +3967,8 @@ async function handleUpload(config, request) {
|
|
|
3547
3967
|
filesize: file.size,
|
|
3548
3968
|
path: storedFile.path,
|
|
3549
3969
|
url: storedFile.url,
|
|
3550
|
-
alt: alt ?? ""
|
|
3970
|
+
alt: alt ?? "",
|
|
3971
|
+
_file: file
|
|
3551
3972
|
};
|
|
3552
3973
|
const api = getMomentumAPI().setContext({ user });
|
|
3553
3974
|
const doc = await api.collection(collection).create(mediaData);
|
|
@@ -3614,7 +4035,8 @@ async function handleCollectionUpload(globalConfig, request) {
|
|
|
3614
4035
|
mimeType: file.mimeType,
|
|
3615
4036
|
filesize: file.size,
|
|
3616
4037
|
path: storedFile.path,
|
|
3617
|
-
url: storedFile.url
|
|
4038
|
+
url: storedFile.url,
|
|
4039
|
+
_file: file
|
|
3618
4040
|
};
|
|
3619
4041
|
const api = getMomentumAPI().setContext({ user });
|
|
3620
4042
|
const doc = await api.collection(collectionSlug).create(docData);
|
|
@@ -3651,6 +4073,7 @@ function getMimeTypeFromPath(path) {
|
|
|
3651
4073
|
png: "image/png",
|
|
3652
4074
|
gif: "image/gif",
|
|
3653
4075
|
webp: "image/webp",
|
|
4076
|
+
avif: "image/avif",
|
|
3654
4077
|
svg: "image/svg+xml",
|
|
3655
4078
|
pdf: "application/pdf",
|
|
3656
4079
|
json: "application/json",
|
|
@@ -4726,13 +5149,12 @@ function getEmailBuilderFieldName(collection) {
|
|
|
4726
5149
|
return field?.name;
|
|
4727
5150
|
}
|
|
4728
5151
|
async function renderEmailPreviewHTML(doc, blocksFieldName) {
|
|
4729
|
-
const
|
|
4730
|
-
const { renderEmailFromBlocks } = await import(emailPkg);
|
|
5152
|
+
const { renderEmailFromBlocks: renderEmailFromBlocks2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
4731
5153
|
const blocks2 = doc[blocksFieldName];
|
|
4732
5154
|
if (!Array.isArray(blocks2) || blocks2.length === 0) {
|
|
4733
5155
|
return '<html><body style="display:flex;align-items:center;justify-content:center;min-height:100vh;color:#666;font-family:sans-serif"><p>No email blocks yet.</p></body></html>';
|
|
4734
5156
|
}
|
|
4735
|
-
return
|
|
5157
|
+
return renderEmailFromBlocks2({ blocks: blocks2 });
|
|
4736
5158
|
}
|
|
4737
5159
|
function nestBracketParams(flat) {
|
|
4738
5160
|
const result = {};
|
|
@@ -5321,6 +5743,63 @@ function createComprehensiveMomentumHandler(config) {
|
|
|
5321
5743
|
}
|
|
5322
5744
|
}
|
|
5323
5745
|
}
|
|
5746
|
+
if (seg2 === "preview" && seg1 && (method === "GET" || method === "POST")) {
|
|
5747
|
+
if (!user) {
|
|
5748
|
+
utils.setResponseStatus(event, 401);
|
|
5749
|
+
return { error: "Authentication required to access preview" };
|
|
5750
|
+
}
|
|
5751
|
+
try {
|
|
5752
|
+
const collectionSlug2 = seg0;
|
|
5753
|
+
const docId = seg1;
|
|
5754
|
+
const collectionConfig = config.collections.find((c) => c.slug === collectionSlug2);
|
|
5755
|
+
if (!collectionConfig) {
|
|
5756
|
+
utils.setResponseStatus(event, 404);
|
|
5757
|
+
return { error: "Collection not found" };
|
|
5758
|
+
}
|
|
5759
|
+
const accessFn = collectionConfig.access?.read;
|
|
5760
|
+
if (accessFn) {
|
|
5761
|
+
const allowed = await Promise.resolve(accessFn({ req: { user } }));
|
|
5762
|
+
if (!allowed) {
|
|
5763
|
+
utils.setResponseStatus(event, 403);
|
|
5764
|
+
return { error: "Access denied" };
|
|
5765
|
+
}
|
|
5766
|
+
}
|
|
5767
|
+
let docRecord;
|
|
5768
|
+
if (method === "POST") {
|
|
5769
|
+
const body2 = await safeReadBody(event, utils, method);
|
|
5770
|
+
if (body2["data"] && typeof body2["data"] === "object") {
|
|
5771
|
+
docRecord = body2["data"];
|
|
5772
|
+
} else {
|
|
5773
|
+
utils.setResponseStatus(event, 400);
|
|
5774
|
+
return { error: "POST preview requires { data: ... } body" };
|
|
5775
|
+
}
|
|
5776
|
+
} else {
|
|
5777
|
+
const contextApi = getContextualAPI(user);
|
|
5778
|
+
const doc = await contextApi.collection(collectionSlug2).findById(docId);
|
|
5779
|
+
if (!doc) {
|
|
5780
|
+
utils.setResponseStatus(event, 404);
|
|
5781
|
+
return { error: "Document not found" };
|
|
5782
|
+
}
|
|
5783
|
+
docRecord = doc;
|
|
5784
|
+
}
|
|
5785
|
+
const emailField = getEmailBuilderFieldName(collectionConfig);
|
|
5786
|
+
const html = emailField ? await renderEmailPreviewHTML(docRecord, emailField) : renderPreviewHTML({ doc: docRecord, collection: collectionConfig });
|
|
5787
|
+
utils.setResponseHeader(event, "Content-Type", "text/html; charset=utf-8");
|
|
5788
|
+
return utils.send(event, html);
|
|
5789
|
+
} catch (error) {
|
|
5790
|
+
const message = sanitizeErrorMessage(error, "Unknown error");
|
|
5791
|
+
if (message.includes("Access denied")) {
|
|
5792
|
+
utils.setResponseStatus(event, 403);
|
|
5793
|
+
return { error: message };
|
|
5794
|
+
}
|
|
5795
|
+
if (message.includes("not found")) {
|
|
5796
|
+
utils.setResponseStatus(event, 404);
|
|
5797
|
+
return { error: message };
|
|
5798
|
+
}
|
|
5799
|
+
utils.setResponseStatus(event, 500);
|
|
5800
|
+
return { error: "Preview failed", message };
|
|
5801
|
+
}
|
|
5802
|
+
}
|
|
5324
5803
|
if (seg1 && seg2 && method === "POST") {
|
|
5325
5804
|
const collectionSlug2 = seg0;
|
|
5326
5805
|
const docId = seg1;
|
|
@@ -5418,63 +5897,6 @@ function createComprehensiveMomentumHandler(config) {
|
|
|
5418
5897
|
};
|
|
5419
5898
|
}
|
|
5420
5899
|
}
|
|
5421
|
-
if (seg2 === "preview" && seg1 && (method === "GET" || method === "POST")) {
|
|
5422
|
-
if (!user) {
|
|
5423
|
-
utils.setResponseStatus(event, 401);
|
|
5424
|
-
return { error: "Authentication required to access preview" };
|
|
5425
|
-
}
|
|
5426
|
-
try {
|
|
5427
|
-
const collectionSlug2 = seg0;
|
|
5428
|
-
const docId = seg1;
|
|
5429
|
-
const collectionConfig = config.collections.find((c) => c.slug === collectionSlug2);
|
|
5430
|
-
if (!collectionConfig) {
|
|
5431
|
-
utils.setResponseStatus(event, 404);
|
|
5432
|
-
return { error: "Collection not found" };
|
|
5433
|
-
}
|
|
5434
|
-
const accessFn = collectionConfig.access?.read;
|
|
5435
|
-
if (accessFn) {
|
|
5436
|
-
const allowed = await Promise.resolve(accessFn({ req: { user } }));
|
|
5437
|
-
if (!allowed) {
|
|
5438
|
-
utils.setResponseStatus(event, 403);
|
|
5439
|
-
return { error: "Access denied" };
|
|
5440
|
-
}
|
|
5441
|
-
}
|
|
5442
|
-
let docRecord;
|
|
5443
|
-
if (method === "POST") {
|
|
5444
|
-
const body2 = await safeReadBody(event, utils, method);
|
|
5445
|
-
if (body2["data"] && typeof body2["data"] === "object") {
|
|
5446
|
-
docRecord = body2["data"];
|
|
5447
|
-
} else {
|
|
5448
|
-
utils.setResponseStatus(event, 400);
|
|
5449
|
-
return { error: "POST preview requires { data: ... } body" };
|
|
5450
|
-
}
|
|
5451
|
-
} else {
|
|
5452
|
-
const contextApi = getContextualAPI(user);
|
|
5453
|
-
const doc = await contextApi.collection(collectionSlug2).findById(docId);
|
|
5454
|
-
if (!doc) {
|
|
5455
|
-
utils.setResponseStatus(event, 404);
|
|
5456
|
-
return { error: "Document not found" };
|
|
5457
|
-
}
|
|
5458
|
-
docRecord = doc;
|
|
5459
|
-
}
|
|
5460
|
-
const emailField = getEmailBuilderFieldName(collectionConfig);
|
|
5461
|
-
const html = emailField ? await renderEmailPreviewHTML(docRecord, emailField) : renderPreviewHTML({ doc: docRecord, collection: collectionConfig });
|
|
5462
|
-
utils.setResponseHeader(event, "Content-Type", "text/html; charset=utf-8");
|
|
5463
|
-
return utils.send(event, html);
|
|
5464
|
-
} catch (error) {
|
|
5465
|
-
const message = sanitizeErrorMessage(error, "Unknown error");
|
|
5466
|
-
if (message.includes("Access denied")) {
|
|
5467
|
-
utils.setResponseStatus(event, 403);
|
|
5468
|
-
return { error: message };
|
|
5469
|
-
}
|
|
5470
|
-
if (message.includes("not found")) {
|
|
5471
|
-
utils.setResponseStatus(event, 404);
|
|
5472
|
-
return { error: message };
|
|
5473
|
-
}
|
|
5474
|
-
utils.setResponseStatus(event, 500);
|
|
5475
|
-
return { error: "Preview failed", message };
|
|
5476
|
-
}
|
|
5477
|
-
}
|
|
5478
5900
|
if (seg0 && seg1 && !seg2) {
|
|
5479
5901
|
const customKey = `${method}:${seg0}/${seg1}`;
|
|
5480
5902
|
const customEntry = customEndpointMap.get(customKey);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@momentumcms/server-analog",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Nitro/h3 adapter for Momentum CMS with Analog.js support",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Momentum CMS Contributors",
|
|
@@ -32,9 +32,14 @@
|
|
|
32
32
|
"@momentumcms/storage": ">=0.0.1"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
+
"@angular/compiler": "~21.2.0",
|
|
36
|
+
"@angular/core": "~21.2.0",
|
|
37
|
+
"@angular/platform-browser": "~21.2.0",
|
|
38
|
+
"@angular/platform-server": "~21.2.0",
|
|
35
39
|
"@aws-sdk/client-s3": "^3.983.0",
|
|
36
40
|
"@aws-sdk/s3-request-presigner": "^3.983.0",
|
|
37
|
-
"graphql": "^16.12.0"
|
|
41
|
+
"graphql": "^16.12.0",
|
|
42
|
+
"juice": "^11.1.1"
|
|
38
43
|
},
|
|
39
44
|
"module": "./index.js"
|
|
40
45
|
}
|