UNPKG

@maizzle/framework

Version:

Maizzle is a framework that helps you quickly build HTML emails with Tailwind CSS.

114 lines (113 loc) 4.76 kB
import { walk } from "../utils/ast/walker.js"; import "../utils/ast/index.js"; import { horizontalBorderPx } from "../utils/cssBox.js"; import safeParser from "postcss-safe-parser"; //#region src/transformers/msoPlaceholders.ts const RE_MAX_WIDTH = /(?:^|;\s*)max-width:\s*([^;]+)/i; const RE_WIDTH = /(?:^|;\s*)width:\s*([^;]+)/i; const RE_PERCENT = /^[\d.]+%$/; function resolveWidth(value) { const trimmed = value.trim(); if (RE_PERCENT.test(trimmed)) return trimmed; const m = trimmed.match(/^([\d.]+)(px|rem|em|pt)?$/i); if (!m) return null; const n = parseFloat(m[1]); const unit = (m[2] || "px").toLowerCase(); return `${Math.round(n * (unit === "rem" || unit === "em" ? 16 : unit === "pt" ? 1.333 : 1))}px`; } /** * Resolve all `__MAIZZLE_MSO*__` placeholders inside MSO conditional comments * by reading inlined style + `data-*` markers on the paired elements. * * Two placeholder families: * * MSOW (`__MAIZZLE_MSOW_{id}__`) — emitted by `<Container>` and `<Section>`. * Source element is marked with `data-maizzle-msow-id`. Reads inlined * `max-width:` (falls back to `width:`) and normalizes to px. Falls * back to `data-maizzle-msow-fallback` (default `600px`) when the * value can't be parsed. * * MSOTDSTYLE (`__MAIZZLE_MSOTDSTYLE_{id}__`) — emitted by `<Container>` and * `<Section>`'s MSO `<td>`. Source element is marked with * `data-maizzle-mso-td-id`. Extracts from the inlined style: * - `background-color` (always, when present) so Word paints the cell. * - `padding*` (only when no horizontal border on the element, since * Word drops div padding without a border and a copy would * double-pad with one). * Appends the `data-maizzle-mso-style` value (the user's `msoStyle` * prop) last so it wins on duplicates. Empty input resolves to '' * so the placeholder collapses cleanly. * * Single collect-walk + single substitute-walk: the same Container div * carries both marker kinds, so one element visit fills both maps. */ function msoPlaceholders(dom) { const widths = /* @__PURE__ */ new Map(); const tdStyles = /* @__PURE__ */ new Map(); walk(dom, (node) => { const a = node.attribs; if (!a) return; const msowId = a["data-maizzle-msow-id"]; const tdId = a["data-maizzle-mso-td-id"]; if (!msowId && !tdId) return; const style = a.style ?? ""; if (msowId) { delete a["data-maizzle-msow-id"]; const fallback = a["data-maizzle-msow-fallback"] ?? "600px"; delete a["data-maizzle-msow-fallback"]; const raw = style.match(RE_MAX_WIDTH)?.[1] ?? style.match(RE_WIDTH)?.[1]; const resolved = raw ? resolveWidth(raw) : null; widths.set(msowId, resolved ?? fallback); } if (tdId) { delete a["data-maizzle-mso-td-id"]; const msoStyle = (a["data-maizzle-mso-style"] ?? "").trim().replace(/;\s*$/, ""); delete a["data-maizzle-mso-style"]; /** * Build the MSO td's inline style from three sources, in CSS priority order * (earlier = lower, later wins on dupes): * * 1. `background-color` (always, when present) — Word paints the cell * under any padding area or inline-block gap, not just the div. * * 2. `padding*` (hoisted only when no horizontal border) — Word drops * div padding without a stabilizing border, so the td has to * carry it. With a border, Word renders div padding and a td * copy would double-pad. * * 3. The user's `mso-style` prop — last so it overrides anything the * auto-hoist computed. */ const parts = []; if (style) { const root = safeParser(style); let bgDecl; root.walkDecls("background-color", (d) => { bgDecl = `background-color: ${d.value}${d.important ? " !important" : ""}`; }); if (bgDecl) parts.push(bgDecl); if (horizontalBorderPx(root) === 0) root.walkDecls((d) => { if (/^padding(-|$)/.test(d.prop)) parts.push(`${d.prop}: ${d.value}${d.important ? " !important" : ""}`); }); } if (msoStyle) parts.push(msoStyle); tdStyles.set(tdId, parts.length ? ` style="${parts.join("; ")}"` : ""); } }); if (widths.size === 0 && tdStyles.size === 0) return dom; walk(dom, (node) => { if (node.type !== "comment") return; let data = node.data; if (!data) return; const hasMsow = widths.size > 0 && data.includes("__MAIZZLE_MSOW_"); const hasTd = tdStyles.size > 0 && data.includes("__MAIZZLE_MSOTDSTYLE_"); if (!hasMsow && !hasTd) return; if (hasMsow) for (const [id, val] of widths) data = data.replaceAll(`__MAIZZLE_MSOW_${id}__`, val); if (hasTd) for (const [id, val] of tdStyles) data = data.replaceAll(`__MAIZZLE_MSOTDSTYLE_${id}__`, val); node.data = data; }); return dom; } //#endregion export { msoPlaceholders }; //# sourceMappingURL=msoPlaceholders.js.map