Preserve configured brand logo dimensions across email targets
Quality / quality (ubuntu-latest) (push) Failing after 10m2s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
Clintchiz committed 2026-09-05 18:24:38 +05:30
1 parent c023a98052
commit 33e350a66a
3 files changed
+17 -5

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/email-renderer", "name": "@wrnexus/email-renderer",
"version": "0.1.36", "version": "0.1.37",
"type": "module", "type": "module",
"description": "WRNexus block email renderers with HTML, text, AMP Email, and MJML interoperability.", "description": "WRNexus block email renderers with HTML, text, AMP Email, and MJML interoperability.",
"exports": { "exports": {
+6 -4
View File
@@ -145,6 +145,8 @@ export const emailDocumentBlock = defineBlock({
linkColor: brandColor("#2563eb"), linkColor: brandColor("#2563eb"),
logoUrl: safeUrl({ schemes: ["https"], optional: true }), logoUrl: safeUrl({ schemes: ["https"], optional: true }),
logoAlt: string({ max: 300, default: "" }), logoAlt: string({ max: 300, default: "" }),
logoWidth: imageDimension("200"),
logoHeight: imageDimension("64"),
}) as never, }) as never,
styles, styles,
slots: { content: contentSlot }, slots: { content: contentSlot },
@@ -153,17 +155,17 @@ export const emailDocumentBlock = defineBlock({
id: id(), id: id(),
type: "layout.email-document", type: "layout.email-document",
version: 1, version: 1,
props: { width: "600", background: "#f3f4f6", contentBackground: "#ffffff", fontFamily: "Arial, sans-serif", textColor: "#111827", linkColor: "#2563eb", logoUrl: "", logoAlt: "" }, props: { width: "600", background: "#f3f4f6", contentBackground: "#ffffff", fontFamily: "Arial, sans-serif", textColor: "#111827", linkColor: "#2563eb", logoUrl: "", logoAlt: "", logoWidth: "200", logoHeight: "64" },
styles: {}, styles: {},
slots: { content: [] }, slots: { content: [] },
}), }),
renderers: { renderers: {
emailHtml: ({ node, slots }) => emailHtml: ({ node, slots }) =>
`<table data-block-id="${html(node.id)}" role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;table-layout:fixed;background:${html(attrs(node).background ?? "#f3f4f6")};color:${html(attrs(node).textColor ?? "#111827")};font-family:${html(attrs(node).fontFamily ?? "Arial, sans-serif")};"><tr><td align="center">${attrs(node).logoUrl ? `<img src="${html(attrs(node).logoUrl)}" alt="${html(attrs(node).logoAlt)}" style="display:block;height:auto;max-width:200px;padding:20px;" />` : ""}<table role="presentation" class="wrn-email" width="${html(attrs(node).width)}" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;table-layout:fixed;max-width:100%;width:${html(attrs(node).width)}px;background:${html(attrs(node).contentBackground ?? "#ffffff")};color:${html(attrs(node).textColor ?? "#111827")};font-family:${html(attrs(node).fontFamily ?? "Arial, sans-serif")};">${slots.content}</table></td></tr></table>`, `<table data-block-id="${html(node.id)}" role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;table-layout:fixed;background:${html(attrs(node).background ?? "#f3f4f6")};color:${html(attrs(node).textColor ?? "#111827")};font-family:${html(attrs(node).fontFamily ?? "Arial, sans-serif")};"><tr><td align="center">${attrs(node).logoUrl ? `<img src="${html(attrs(node).logoUrl)}" alt="${html(attrs(node).logoAlt)}" width="${html(attrs(node).logoWidth ?? "200")}" height="${html(attrs(node).logoHeight ?? "64")}" style="display:block;height:auto;max-width:100%;width:${html(attrs(node).logoWidth ?? "200")}px;margin:20px auto;" />` : ""}<table role="presentation" class="wrn-email" width="${html(attrs(node).width)}" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;table-layout:fixed;max-width:100%;width:${html(attrs(node).width)}px;background:${html(attrs(node).contentBackground ?? "#ffffff")};color:${html(attrs(node).textColor ?? "#111827")};font-family:${html(attrs(node).fontFamily ?? "Arial, sans-serif")};">${slots.content}</table></td></tr></table>`,
ampEmail: ({ node, slots }) => ampEmail: ({ node, slots }) =>
`<table role="presentation" width="100%" class="email-bg"><tr><td align="center">${attrs(node).logoUrl ? `<amp-img class="marketing-image" src="${html(attrs(node).logoUrl)}" alt="${html(attrs(node).logoAlt)}" width="200" height="64" layout="fixed"></amp-img>` : ""}<table role="presentation" width="${html(attrs(node).width)}" class="email-body">${slots.content}</table></td></tr></table>`, `<table role="presentation" width="100%" class="email-bg"><tr><td align="center">${attrs(node).logoUrl ? `<amp-img class="marketing-image" src="${html(attrs(node).logoUrl)}" alt="${html(attrs(node).logoAlt)}" width="${html(attrs(node).logoWidth ?? "200")}" height="${html(attrs(node).logoHeight ?? "64")}" layout="fixed"></amp-img>` : ""}<table role="presentation" width="${html(attrs(node).width)}" class="email-body">${slots.content}</table></td></tr></table>`,
mjml: ({ node, slots, context }) => mjml: ({ node, slots, context }) =>
`<mjml>${mjmlDocumentHead(node, context)}<mj-body width="${html(attrs(node).width ?? "600")}px" background-color="${html(attrs(node).background ?? "#f3f4f6")}">${attrs(node).logoUrl ? `<mj-section padding="20px"><mj-column><mj-image src="${html(attrs(node).logoUrl)}" alt="${html(attrs(node).logoAlt)}" width="200px" padding="0" /></mj-column></mj-section>` : ""}${slots.content}</mj-body></mjml>`, `<mjml>${mjmlDocumentHead(node, context)}<mj-body width="${html(attrs(node).width ?? "600")}px" background-color="${html(attrs(node).background ?? "#f3f4f6")}">${attrs(node).logoUrl ? `<mj-section padding="20px"><mj-column><mj-image src="${html(attrs(node).logoUrl)}" alt="${html(attrs(node).logoAlt)}" width="${html(attrs(node).logoWidth ?? "200")}px" height="${html(attrs(node).logoHeight ?? "64")}px" padding="0" /></mj-column></mj-section>` : ""}${slots.content}</mj-body></mjml>`,
}, },
textRenderer: ({ slots }) => slots.content, textRenderer: ({ slots }) => slots.content,
} satisfies BlockDefinition); } satisfies BlockDefinition);
@@ -111,6 +111,8 @@ describe("email renderer", () => {
const draft = structuredClone(document); const draft = structuredClone(document);
draft.root.props.logoUrl = "https://example.com/logo.png"; draft.root.props.logoUrl = "https://example.com/logo.png";
draft.root.props.logoAlt = 'Brand & "Company"'; draft.root.props.logoAlt = 'Brand & "Company"';
draft.root.props.logoWidth = "180";
draft.root.props.logoHeight = "90";
const result = await renderEmail(draft,{amp:true,mjml:true}); const result = await renderEmail(draft,{amp:true,mjml:true});
expect(result.ok).toBe(true); expect(result.ok).toBe(true);
if (!result.ok) return; if (!result.ok) return;
@@ -120,6 +122,14 @@ describe("email renderer", () => {
} }
expect(result.value.parts.amp?.validation.valid).toBe(true); expect(result.value.parts.amp?.validation.valid).toBe(true);
expect(await validateMjml(result.value.exports!.mjml!)).toEqual([]); expect(await validateMjml(result.value.exports!.mjml!)).toEqual([]);
expect(result.value.parts.html.content).toContain('width="180" height="90"');
expect(result.value.parts.amp?.content).toContain('width="180" height="90"');
expect(result.value.exports?.mjml).toContain('width="180px" height="90px"');
for (const value of ["0", "4001", "180px", "1;display:none"]) {
const invalid = structuredClone(draft);
invalid.root.props.logoWidth = value;
expect((await renderEmail(invalid)).ok).toBe(false);
}
draft.root.props.logoUrl = ""; draft.root.props.logoUrl = "";
const empty = await renderEmail(draft,{amp:true,mjml:true}); const empty = await renderEmail(draft,{amp:true,mjml:true});
expect(empty.ok).toBe(true); expect(empty.ok).toBe(true);