Import supported MJML header metadata and typography
This commit is contained in:
1 parent
69db4bd62e
commit
f5139f2c82
3 files changed
+68
-5
No files matched your search
@@ -67,7 +67,7 @@ export interface MjmlDiagnostic extends Diagnostic {
|
||||
|
||||
export function importMjml(
|
||||
source: string,
|
||||
options: { id(): string; subject: string; preheader?: string },
|
||||
options: { id(): string; subject?: string; preheader?: string },
|
||||
): Result<BlockDocument, MjmlDiagnostic[]> {
|
||||
if (new TextEncoder().encode(source).byteLength > DEFAULT_LIMITS.maxBytes)
|
||||
return {
|
||||
@@ -111,6 +111,44 @@ export function importMjml(
|
||||
}
|
||||
for (const child of node.children ?? []) pending.push({ node: child, depth: depth + 1 });
|
||||
}
|
||||
const decode = (value: string) => value.replace(/&(?:#(x[0-9a-f]+|[0-9]+)|(amp|lt|gt|quot|apos));/gi, (original, numeric: string | undefined, name: string | undefined) => {
|
||||
if (numeric) {
|
||||
const point = numeric[0]!.toLowerCase() === "x" ? Number.parseInt(numeric.slice(1), 16) : Number(numeric);
|
||||
return point > 0 && point <= 0x10ffff && !(point >= 0xd800 && point <= 0xdfff) ? String.fromCodePoint(point) : original;
|
||||
}
|
||||
return ({ amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" } as Record<string, string>)[name!.toLowerCase()]!;
|
||||
});
|
||||
const brand: Record<string, string> = {};
|
||||
let importedSubject = "", importedPreheader = "";
|
||||
const attributeError = (node: MjmlAst, attribute?: string) => errors.push({ code: "mjml.header", message: `Unsupported MJML header setting ${node.tagName}${attribute ? `.${attribute}` : ""}.`, severity: "error", element: node.tagName, attribute });
|
||||
const onlyAttributes = (node: MjmlAst, allowed: string[]) => {
|
||||
for (const key of Object.keys(node.attributes ?? {})) if (!allowed.includes(key)) attributeError(node, key);
|
||||
};
|
||||
for (const head of (ast.children ?? []).filter(node => node.tagName === "mj-head")) {
|
||||
onlyAttributes(head, []);
|
||||
for (const node of head.children ?? []) {
|
||||
if (node.tagName === "mj-title" || node.tagName === "mj-preview") {
|
||||
onlyAttributes(node, []);
|
||||
if (node.tagName === "mj-title") importedSubject = decode(node.content ?? "");
|
||||
else importedPreheader = decode(node.content ?? "");
|
||||
} else if (node.tagName === "mj-attributes") {
|
||||
onlyAttributes(node, []);
|
||||
for (const defaults of node.children ?? []) {
|
||||
if (defaults.tagName === "mj-all") {
|
||||
onlyAttributes(defaults, ["font-family"]);
|
||||
if (defaults.attributes?.["font-family"]) brand.fontFamily = decode(defaults.attributes["font-family"]);
|
||||
} else if (defaults.tagName === "mj-text") {
|
||||
onlyAttributes(defaults, ["color"]);
|
||||
if (defaults.attributes?.color) brand.textColor = decode(defaults.attributes.color);
|
||||
} else attributeError(defaults);
|
||||
}
|
||||
} else if (node.tagName === "mj-breakpoint") {
|
||||
onlyAttributes(node, ["width"]);
|
||||
if (node.attributes?.width !== "480px") attributeError(node, "width");
|
||||
} else attributeError(node);
|
||||
}
|
||||
}
|
||||
const bodyAttributes = (ast.children ?? []).find(node => node.tagName === "mj-body")?.attributes ?? {};
|
||||
const wrapColumns = (nodes: BlockNode[]): BlockNode[] => {
|
||||
const output: BlockNode[] = [];
|
||||
for (let index = 0; index < nodes.length;) {
|
||||
@@ -139,6 +177,7 @@ export function importMjml(
|
||||
return output;
|
||||
};
|
||||
const convert = (node: MjmlAst): BlockNode[] => {
|
||||
if (node.tagName === "mj-head") return [];
|
||||
if (!supported.has(node.tagName)) {
|
||||
errors.push({
|
||||
code: "mjml.component",
|
||||
@@ -243,7 +282,7 @@ export function importMjml(
|
||||
id: options.id(),
|
||||
type: "layout.email-document",
|
||||
version: 1,
|
||||
props: { width: "600", background: "#f3f4f6" },
|
||||
props: { width: (bodyAttributes.width ?? "600").replace(/px$/, ""), background: bodyAttributes["background-color"] ?? "#f3f4f6", ...brand },
|
||||
styles: {},
|
||||
slots: {
|
||||
content: content.map((node) =>
|
||||
@@ -263,7 +302,7 @@ export function importMjml(
|
||||
const document: BlockDocument = {
|
||||
schemaVersion: 1,
|
||||
kind: "email",
|
||||
meta: { subject: options.subject, preheader: options.preheader ?? "", lang: "en" },
|
||||
meta: { subject: options.subject ?? (importedSubject || "Imported email"), preheader: options.preheader ?? importedPreheader, lang: "en" },
|
||||
theme: { tokens: {} },
|
||||
root,
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user