feat(email-renderer): add global brand styling
This commit is contained in:
1 parent
6cdd39ba38
commit
353cadda60
4 files changed
+17
-5
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/email-renderer",
|
"name": "@wrnexus/email-renderer",
|
||||||
"version": "0.1.1",
|
"version": "0.1.2",
|
||||||
"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": {
|
||||||
|
|||||||
@@ -44,6 +44,12 @@ export const emailDocumentBlock = defineBlock({
|
|||||||
props: object({
|
props: object({
|
||||||
width: string({ default: "600" }),
|
width: string({ default: "600" }),
|
||||||
background: string({ default: "#f3f4f6" }),
|
background: string({ default: "#f3f4f6" }),
|
||||||
|
contentBackground: string({ default: "#ffffff" }),
|
||||||
|
fontFamily: string({ default: "Arial, sans-serif" }),
|
||||||
|
textColor: string({ default: "#111827" }),
|
||||||
|
linkColor: string({ default: "#2563eb" }),
|
||||||
|
logoUrl: safeUrl({ schemes: ["https"], optional: true }),
|
||||||
|
logoAlt: string({ max: 300, default: "" }),
|
||||||
}) as never,
|
}) as never,
|
||||||
styles,
|
styles,
|
||||||
slots: { content: contentSlot },
|
slots: { content: contentSlot },
|
||||||
@@ -52,13 +58,13 @@ 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" },
|
props: { width: "600", background: "#f3f4f6", contentBackground: "#ffffff", fontFamily: "Arial, sans-serif", textColor: "#111827", linkColor: "#2563eb", logoUrl: "", logoAlt: "" },
|
||||||
styles: {},
|
styles: {},
|
||||||
slots: { content: [] },
|
slots: { content: [] },
|
||||||
}),
|
}),
|
||||||
renderers: {
|
renderers: {
|
||||||
emailHtml: ({ node, slots }) =>
|
emailHtml: ({ node, slots }) =>
|
||||||
`<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;background:${html(attrs(node).background)};"><tr><td align="center"><table role="presentation" class="wrn-email" width="${html(attrs(node).width)}" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;max-width:100%;width:${html(attrs(node).width)}px;">${slots.content}</table></td></tr></table>`,
|
`<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;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;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"><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"><table role="presentation" width="${html(attrs(node).width)}" class="email-body">${slots.content}</table></td></tr></table>`,
|
||||||
mjml: ({ node, slots }) =>
|
mjml: ({ node, slots }) =>
|
||||||
|
|||||||
@@ -71,8 +71,13 @@ export const emailKind = defineDocumentKind({
|
|||||||
urlSchemes: ["https", "mailto"],
|
urlSchemes: ["https", "mailto"],
|
||||||
});
|
});
|
||||||
|
|
||||||
const head = (document: BlockDocument, body: string, direction: "ltr" | "rtl") =>
|
const head = (document: BlockDocument, body: string, direction: "ltr" | "rtl") => {
|
||||||
`<!doctype html><html lang="${escapeHtml(document.meta.lang)}" dir="${direction}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="x-apple-disable-message-reformatting"><meta name="color-scheme" content="light dark"><title>${escapeHtml(document.meta.subject)}</title><style>@media only screen and (max-width:620px){.wrn-email{width:100%!important}.wrn-columns{display:block!important}.wrn-column{display:block!important;width:100%!important}}</style></head><body style="margin:0;padding:0;">${document.meta.preheader ? `<div style="display:none;max-height:0;overflow:hidden;opacity:0;">${escapeHtml(document.meta.preheader)}</div>` : ""}${body}</body></html>`;
|
const props = document.root.props as Record<string, unknown>;
|
||||||
|
const textColor = escapeHtml(props.textColor ?? "#111827");
|
||||||
|
const linkColor = escapeHtml(props.linkColor ?? "#2563eb");
|
||||||
|
const fontFamily = escapeHtml(props.fontFamily ?? "Arial, sans-serif");
|
||||||
|
return `<!doctype html><html lang="${escapeHtml(document.meta.lang)}" dir="${direction}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="x-apple-disable-message-reformatting"><meta name="color-scheme" content="light dark"><title>${escapeHtml(document.meta.subject)}</title><style>a{color:${linkColor}}@media only screen and (max-width:620px){.wrn-email{width:100%!important}.wrn-columns{display:block!important}.wrn-column{display:block!important;width:100%!important}}</style></head><body style="margin:0;padding:0;color:${textColor};font-family:${fontFamily};">${document.meta.preheader ? `<div style="display:none;max-height:0;overflow:hidden;opacity:0;">${escapeHtml(document.meta.preheader)}</div>` : ""}${body}</body></html>`;
|
||||||
|
};
|
||||||
const ampHead = (document: BlockDocument, body: string, direction: "ltr" | "rtl") =>
|
const ampHead = (document: BlockDocument, body: string, direction: "ltr" | "rtl") =>
|
||||||
`<!doctype html><html amp4email data-css-strict lang="${escapeHtml(document.meta.lang)}" dir="${direction}"><head><meta charset="utf-8"><script async src="https://cdn.ampproject.org/v0.js"></script><style amp4email-boilerplate>body{visibility:hidden}</style><style amp-custom>.email-body{max-width:600px;width:100%}.section{padding:24px}.button{background:#2563eb;border-radius:6px;color:#fff;display:inline-block;font-weight:700;padding:12px 20px;text-decoration:none}.spacer{height:24px}</style></head><body>${body}</body></html>`;
|
`<!doctype html><html amp4email data-css-strict lang="${escapeHtml(document.meta.lang)}" dir="${direction}"><head><meta charset="utf-8"><script async src="https://cdn.ampproject.org/v0.js"></script><style amp4email-boilerplate>body{visibility:hidden}</style><style amp-custom>.email-body{max-width:600px;width:100%}.section{padding:24px}.button{background:#2563eb;border-radius:6px;color:#fff;display:inline-block;font-weight:700;padding:12px 20px;text-decoration:none}.spacer{height:24px}</style></head><body>${body}</body></html>`;
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ describe("email renderer", () => {
|
|||||||
if (!first.ok) return;
|
if (!first.ok) return;
|
||||||
const value: EmailRenderBundle = first.value;
|
const value: EmailRenderBundle = first.value;
|
||||||
expect(value.parts.html.content).toContain("Hello <world>");
|
expect(value.parts.html.content).toContain("Hello <world>");
|
||||||
|
expect(value.parts.html.content).toContain("color:#111827");
|
||||||
expect(value.parts.text.content).toContain("Hello <world>");
|
expect(value.parts.text.content).toContain("Hello <world>");
|
||||||
expect(value.parts.amp?.validation.valid).toBe(true);
|
expect(value.parts.amp?.validation.valid).toBe(true);
|
||||||
expect(value.exports?.mjml).toContain("<mjml>");
|
expect(value.exports?.mjml).toContain("<mjml>");
|
||||||
|
|||||||
Reference in new issue
Block a user