@templatical/import-mjml 0.31.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/attribute-resolver.ts","../src/attribute-parser.ts","../src/block-base.ts","../src/composite-mapper.ts","../src/text-inference.ts","../src/block-mapper.ts","../src/display-condition.ts","../src/head-parser.ts","../src/section-builder.ts","../src/converter.ts"],"sourcesContent":["import type { Cheerio, CheerioAPI } from \"cheerio\";\nimport type { AnyNode, Element } from \"domhandler\";\nimport type { BlockVisibility } from \"@templatical/types\";\n\nexport type Attrs = Record<string, string>;\n\n/**\n * The flattened contents of `mj-head > mj-attributes`, built once per document.\n */\nexport interface AttributeCascade {\n /** From `<mj-all …>` — applies to every element. */\n all: Attrs;\n /** From `<mj-text …>` etc. — keyed by lowercased tag name. */\n byTag: Record<string, Attrs>;\n /** From `<mj-class name=\"x\" …>` — keyed by class name. */\n byClass: Record<string, Attrs>;\n}\n\n/**\n * A node's tag name, lowercased.\n *\n * The parser configuration this package uses (`xmlMode: false`, set in\n * `converter.ts`) already lowercases every tag at parse time, so this call is\n * a no-op on that parser's output. It remains the one place every tag\n * comparison in this package goes through, so a comparison stays correct\n * regardless of the active parser configuration — a bare `$(\"mj-body\")` would\n * otherwise miss a document that shouts its tags.\n */\nexport function tagOf(node: AnyNode | undefined): string {\n if (!node) return \"\";\n return (node as Element).tagName?.toLowerCase() ?? \"\";\n}\n\n/**\n * Every element with the given tag name, matched case-insensitively.\n */\nexport function findByTag($: CheerioAPI, tag: string): Cheerio<Element> {\n const wanted = tag.toLowerCase();\n return $(\"*\").filter(\n (_, el) => tagOf(el) === wanted,\n ) as unknown as Cheerio<Element>;\n}\n\n/**\n * An element's element children.\n *\n * `.children()` already excludes text and comment nodes; the tag filter here\n * guards against a node whose `tagName` is undefined, not against either of\n * those.\n */\nexport function childElements(\n $el: Cheerio<Element>,\n $: CheerioAPI,\n): Cheerio<Element>[] {\n return $el\n .children()\n .toArray()\n .filter((node) => tagOf(node) !== \"\")\n .map((node) => $(node) as unknown as Cheerio<Element>);\n}\n\nfunction attrsOf($el: Cheerio<Element>): Attrs {\n const raw = $el.attr();\n if (!raw) return {};\n const out: Attrs = {};\n for (const [key, value] of Object.entries(raw)) {\n out[key.toLowerCase()] = value;\n }\n return out;\n}\n\n/**\n * Read `mj-head > mj-attributes` into the three buckets the cascade resolves\n * against. Called once per document; `resolveAttributes` is then pure lookup.\n */\nexport function buildAttributeCascade($: CheerioAPI): AttributeCascade {\n // Every bucket is a lookup table keyed by names taken from the imported\n // document, so a class or tag literally named `__proto__` must not be able\n // to reach the bucket's own prototype — `Object.create(null)` means a\n // computed-key write like `cascade.byClass[name] = …` can only ever create\n // an own property, never reassign what the bucket inherits from.\n const cascade: AttributeCascade = {\n all: Object.create(null),\n byTag: Object.create(null),\n byClass: Object.create(null),\n };\n\n const containers = findByTag($, \"mj-attributes\").toArray();\n for (const container of containers) {\n const $container = $(container) as unknown as Cheerio<Element>;\n for (const $child of childElements($container, $)) {\n const tag = tagOf($child[0]);\n const attrs = attrsOf($child);\n\n if (tag === \"mj-all\") {\n Object.assign(cascade.all, attrs);\n continue;\n }\n\n if (tag === \"mj-class\") {\n const { name, ...rest } = attrs;\n if (!name) continue;\n cascade.byClass[name] = { ...(cascade.byClass[name] ?? {}), ...rest };\n continue;\n }\n\n cascade.byTag[tag] = { ...(cascade.byTag[tag] ?? {}), ...attrs };\n }\n }\n\n return cascade;\n}\n\n/**\n * The first two cascade layers alone — `mj-all` then the per-tag default —\n * with neither a named `mj-class` nor the element's own inline attributes on\n * top. This is the ambient value a tag inherits before the element sets\n * anything of its own, which is what {@link ownAttr} compares against.\n */\nfunction resolveTagDefaults(tag: string, cascade: AttributeCascade): Attrs {\n return {\n ...cascade.all,\n ...(cascade.byTag[tag] ?? {}),\n };\n}\n\n/**\n * An element's effective attributes, highest precedence last:\n * `mj-all` → per-tag default → each named `mj-class` → the element's own\n * inline attributes.\n *\n * MJML's built-in component defaults are deliberately not modelled — the block\n * factories in `@templatical/types` supply that floor instead, which keeps this\n * module from becoming a copy of MJML's component table.\n */\nexport function resolveAttributes(\n $el: Cheerio<Element>,\n cascade: AttributeCascade,\n): Attrs {\n const own = attrsOf($el);\n const tag = tagOf($el[0]);\n\n const resolved: Attrs = resolveTagDefaults(tag, cascade);\n\n const classNames = (own[\"mj-class\"] ?? \"\")\n .trim()\n .split(/\\s+/)\n .filter(Boolean);\n for (const name of classNames) {\n Object.assign(resolved, cascade.byClass[name] ?? {});\n }\n\n Object.assign(resolved, own);\n delete resolved[\"mj-class\"];\n\n return resolved;\n}\n\n/**\n * A resolved attribute value, but only when the element is the reason it has\n * that value — a named `mj-class` counts as the element opting in, but a bare\n * `mj-all`/per-tag cascade default does not.\n *\n * This reverses the renderer's inherit-by-omission convention for `mj-text`'s\n * `color` and `font-family`: `renderers/title.ts`, `table.ts` and `menu.ts`\n * all emit that attribute only when the block sets its own value, otherwise\n * leaving the element to inherit the document default from\n * `<mj-attributes>` (`renderers/title.ts:31`). `resolveAttributes` flattens\n * that default onto every element of the tag regardless, so reading `attrs`\n * directly cannot tell \"the block set this\" from \"the document default\n * reached this element too\" — and treating the latter as the former invents\n * a per-block override the source never made.\n *\n * `resolved === ambient` also covers an element that repeats the ambient\n * value on purpose: the two cases render identically, so which one happened\n * is not observable in the output and dropping it costs nothing (the same\n * reasoning as §8.3b's image-width and §8.4b's paragraph-gap defaults).\n */\nexport function ownAttr(\n attrs: Attrs,\n key: string,\n tag: string,\n cascade: AttributeCascade,\n): string | undefined {\n const resolved = attrs[key];\n if (resolved === undefined) return undefined;\n return resolved === resolveTagDefaults(tag, cascade)[key]\n ? undefined\n : resolved;\n}\n\nconst HIDE_DESKTOP = \"tpl-hide-desktop\";\nconst HIDE_MOBILE = \"tpl-hide-mobile\";\n\n/**\n * Marks a rendered title or paragraph's rich-text spacing — mirrors\n * `RICH_TEXT_CSS_CLASS` in `packages/renderer/src/rich-text.ts`. `title.ts`\n * and `paragraph.ts` are the only two renderers that pass a second argument\n * to `getCssClassAttr`, so every rendered title and paragraph carries this\n * class on `css-class` alongside any visibility markers.\n */\nconst RICH_TEXT_CSS_CLASS = \"tpl-rich-text\";\n\n/**\n * Matches the per-block paragraph-gap class the same two renderers append,\n * e.g. `tpl-rich-text-8` (`richTextGapClass` in `rich-text.ts`). The gap\n * accepts a decimal (`tpl-rich-text-8.5`) because `ParagraphBlock.paragraphSpacing`\n * is a plain `number` that a headless caller can set to a fractional value,\n * even though the editor UI clamps it to an integer. Negative gaps are out of\n * scope — the renderer never emits one, so there is nothing here that needs to\n * recognise it.\n *\n * Shared by `readForeignCssClasses` (which classes to exclude) and\n * `readParagraphGap` (which class to decode), so the two can never disagree\n * about what a gap class looks like.\n */\nconst RICH_TEXT_GAP_CLASS = /^tpl-rich-text-(\\d+(?:\\.\\d+)?)$/;\n\nfunction cssClasses(attrs: Attrs): string[] {\n return (attrs[\"css-class\"] ?? \"\").trim().split(/\\s+/).filter(Boolean);\n}\n\n/**\n * Reverse of the renderer's `getCssClassAttr` (`packages/renderer/src/visibility.ts`).\n * `css-class` also carries the rich-text markers below (`RICH_TEXT_CSS_CLASS`,\n * `RICH_TEXT_GAP_CLASS`) — this function reads only the two visibility\n * classes off it and leaves those alone.\n *\n * Returns `undefined` when neither class is present — absence means visible\n * everywhere, and writing `{ desktop: true, mobile: true }` instead would put a\n * redundant key in every imported block.\n */\nexport function readVisibility(attrs: Attrs): BlockVisibility | undefined {\n const classes = cssClasses(attrs);\n const hideDesktop = classes.includes(HIDE_DESKTOP);\n const hideMobile = classes.includes(HIDE_MOBILE);\n\n if (!hideDesktop && !hideMobile) return undefined;\n\n return { desktop: !hideDesktop, mobile: !hideMobile };\n}\n\n/**\n * The paragraph gap encoded on a rendered title or paragraph's `css-class` —\n * reverse of `richTextGapClass` (`packages/renderer/src/rich-text.ts`), e.g.\n * `8` from `tpl-rich-text-8`.\n *\n * Returns `null` when no gap class is present. Callers must not confuse that\n * with a gap of `0`, which is a legitimate value in its own right.\n */\nexport function readParagraphGap(attrs: Attrs): number | null {\n for (const name of cssClasses(attrs)) {\n const match = RICH_TEXT_GAP_CLASS.exec(name);\n if (match) return parseFloat(match[1]);\n }\n return null;\n}\n\n/**\n * Classes on `css-class` that carry no Templatical meaning. The caller warns\n * about these rather than dropping them silently: they are consumer CSS with no\n * home in the block model, and a template that relies on them will render\n * differently after import.\n *\n * Excludes the renderer's own rich-text markers alongside the two visibility\n * classes, so importing a template the renderer itself produced does not\n * report a title or paragraph's own spacing class as foreign. A consumer's\n * own `tpl-`-prefixed class is not one of these markers and is still reported.\n */\nexport function readForeignCssClasses(attrs: Attrs): string[] {\n return cssClasses(attrs).filter(\n (name) =>\n name !== HIDE_DESKTOP &&\n name !== HIDE_MOBILE &&\n name !== RICH_TEXT_CSS_CLASS &&\n !RICH_TEXT_GAP_CLASS.test(name),\n );\n}\n","import type { SpacingValue } from \"@templatical/types\";\n\n/**\n * Parses a px-like MJML attribute value (`\"12px\"`, `\"12\"`, `12`) into a rounded\n * integer. Returns 0 for missing or unparseable input, and for units the block\n * model cannot express (em, rem, %) — a caller that needs to tell \"absent\" from\n * \"0\" must check the raw attribute itself.\n */\nexport function parsePxValue(value: string | number | undefined): number {\n if (value === undefined || value === null || value === \"\") return 0;\n if (typeof value === \"number\") return Math.round(value);\n const match = value.match(/^\\s*(-?\\d+(?:\\.\\d+)?)\\s*(?:px)?\\s*$/);\n return match ? Math.round(parseFloat(match[1])) : 0;\n}\n\nconst NAMED_COLORS: Record<string, string> = {\n black: \"#000000\",\n white: \"#ffffff\",\n red: \"#ff0000\",\n green: \"#008000\",\n blue: \"#0000ff\",\n yellow: \"#ffff00\",\n cyan: \"#00ffff\",\n magenta: \"#ff00ff\",\n gray: \"#808080\",\n grey: \"#808080\",\n silver: \"#c0c0c0\",\n maroon: \"#800000\",\n olive: \"#808000\",\n lime: \"#00ff00\",\n aqua: \"#00ffff\",\n teal: \"#008080\",\n navy: \"#000080\",\n fuchsia: \"#ff00ff\",\n purple: \"#800080\",\n orange: \"#ffa500\",\n pink: \"#ffc0cb\",\n};\n\nfunction rgbToHex(r: number, g: number, b: number): string {\n const clamp = (n: number) => Math.max(0, Math.min(255, Math.round(n)));\n const hex = (n: number) => clamp(n).toString(16).padStart(2, \"0\");\n return `#${hex(r)}${hex(g)}${hex(b)}`;\n}\n\n/**\n * Normalizes a colour value to a 6-digit lowercase hex string.\n *\n * Returns `\"\"` for transparent/inherit/none and for anything unrecognised.\n * The empty string is the block model's \"unset\" — the colour pickers clear to\n * it — so returning it is meaningfully different from returning a default.\n */\nexport function parseColor(value: string | undefined): string {\n if (!value) return \"\";\n const trimmed = value.trim().toLowerCase();\n if (trimmed === \"transparent\" || trimmed === \"inherit\" || trimmed === \"none\")\n return \"\";\n\n if (/^#[0-9a-f]{6}$/.test(trimmed)) return trimmed;\n\n if (/^#[0-9a-f]{3}$/.test(trimmed)) {\n const r = trimmed[1];\n const g = trimmed[2];\n const b = trimmed[3];\n return `#${r}${r}${g}${g}${b}${b}`;\n }\n\n const rgbMatch = trimmed.match(\n /^rgba?\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*(?:,\\s*[\\d.]+\\s*)?\\)$/,\n );\n if (rgbMatch) {\n return rgbToHex(\n parseInt(rgbMatch[1], 10),\n parseInt(rgbMatch[2], 10),\n parseInt(rgbMatch[3], 10),\n );\n }\n\n if (NAMED_COLORS[trimmed]) return NAMED_COLORS[trimmed];\n\n return \"\";\n}\n\n/**\n * Parses an MJML `padding` shorthand (1-4 values, CSS order) into a\n * SpacingValue.\n */\nexport function parsePaddingShorthand(value: string | undefined): SpacingValue {\n if (!value) return { top: 0, right: 0, bottom: 0, left: 0 };\n\n const values = value\n .trim()\n .split(/\\s+/)\n .map((p) => parsePxValue(p));\n\n switch (values.length) {\n case 1:\n return {\n top: values[0],\n right: values[0],\n bottom: values[0],\n left: values[0],\n };\n case 2:\n return {\n top: values[0],\n right: values[1],\n bottom: values[0],\n left: values[1],\n };\n case 3:\n return {\n top: values[0],\n right: values[1],\n bottom: values[2],\n left: values[1],\n };\n default:\n return {\n top: values[0],\n right: values[1],\n bottom: values[2],\n left: values[3],\n };\n }\n}\n\n/**\n * Strips quotes and returns the first font in a font-family stack.\n */\nexport function parseFontFamily(value: string | undefined): string {\n if (!value) return \"\";\n return value\n .split(\",\")[0]\n .trim()\n .replace(/^['\"]|['\"]$/g, \"\");\n}\n\n/**\n * Parses an alignment to one of the three the block model accepts.\n */\nexport function parseAlignment(\n value: string | undefined,\n fallback: \"left\" | \"center\" | \"right\" = \"left\",\n): \"left\" | \"center\" | \"right\" {\n const v = (value ?? \"\").trim().toLowerCase();\n if (v === \"left\" || v === \"center\" || v === \"right\") return v;\n return fallback;\n}\n\n/**\n * Reads a percentage value, or `null` when the value is not a percentage.\n *\n * `null` rather than a number, because column-width matching (§8.1) has to tell\n * \"no percentage given\" from \"0%\" — the former distributes widths equally, the\n * latter is a real (if degenerate) width.\n */\nexport function parsePercent(value: string | undefined): number | null {\n if (!value) return null;\n const match = value.trim().match(/^(\\d+(?:\\.\\d+)?)\\s*%$/);\n return match ? parseFloat(match[1]) : null;\n}\n\n/**\n * Reads a definite px length, or `null` when the value is not one.\n *\n * Unlike `parsePxValue`, which returns `0` for anything unparseable, this\n * tells \"no length given\" apart from \"0px\" — column-width recovery (§8.1)\n * needs that distinction for a px `mj-column` width the same way it needs\n * `parsePercent`'s `null` for a percentage one, so the two compose into a\n * single known-or-absent value the matcher can fill around.\n */\nexport function parseDefinitePx(value: string | undefined): number | null {\n if (!value) return null;\n const match = value.trim().match(/^(-?\\d+(?:\\.\\d+)?)\\s*(?:px)?$/);\n return match ? parseFloat(match[1]) : null;\n}\n\n/**\n * Narrows a border style to the three `DividerBlock.lineStyle` accepts.\n */\nexport function parseBorderStyle(\n value: string | undefined,\n): \"solid\" | \"dashed\" | \"dotted\" {\n const v = (value ?? \"\").trim().toLowerCase();\n if (v === \"dashed\" || v === \"dotted\") return v;\n return \"solid\";\n}\n","import type { Cheerio, CheerioAPI } from \"cheerio\";\nimport type { Element } from \"domhandler\";\nimport { createHtmlBlock } from \"@templatical/types\";\nimport type { Block, BlockStyles, BlockVisibility } from \"@templatical/types\";\nimport { parseColor, parsePaddingShorthand } from \"./attribute-parser\";\nimport {\n readForeignCssClasses,\n readVisibility,\n type Attrs,\n type AttributeCascade,\n} from \"./attribute-resolver\";\nimport type { ImportReportEntry } from \"./types\";\n\n/**\n * Everything a converter needs beyond the element itself.\n *\n * `containerWidth` is the width the element renders at — a section's column\n * width, or `settings.width` at top level. It is what lets an `mj-image` whose\n * px width equals its container restore `width: \"full\"` (§8.3b).\n */\nexport interface ConvertContext {\n $: CheerioAPI;\n cascade: AttributeCascade;\n containerWidth: number;\n warnings: string[];\n}\n\n/** A produced block plus its report entry. `block` is null only when skipped. */\nexport interface Converted {\n block: Block | null;\n entry: ImportReportEntry;\n}\n\n/**\n * The `styles` and `visibility` every block shares.\n *\n * `placement` mirrors the renderer's own `BgPlacement` split\n * (`packages/renderer/src/utils.ts`): `mj-section` is the one \"native\"\n * element, whose own `background-color` attribute *is* `styles.backgroundColor`\n * (`renderers/section.ts:32`). Every other block type emits\n * `container-background-color` for that same field, because a plain\n * `background-color` on those tags already means something else of the\n * element's own — a button's fill (`renderers/button.ts:43`), for one — and\n * reading it back as the block's container background would invent a fill\n * the source never had. Defaulting to `\"container\"` matches every caller but\n * `buildSection`.\n *\n * `visibility` is spread conditionally so an unset block carries no key — the\n * block model treats absence as \"visible everywhere\".\n */\nexport function baseFields(\n attrs: Attrs,\n placement: \"container\" | \"native\" = \"container\",\n): {\n styles: BlockStyles;\n visibility?: BlockVisibility;\n} {\n const bgKey =\n placement === \"native\" ? \"background-color\" : \"container-background-color\";\n const backgroundColor = parseColor(attrs[bgKey]);\n const visibility = readVisibility(attrs);\n\n return {\n styles: {\n padding: parsePaddingShorthand(attrs.padding),\n ...(backgroundColor ? { backgroundColor } : {}),\n },\n ...(visibility ? { visibility } : {}),\n };\n}\n\nexport function warnForeignClasses(\n attrs: Attrs,\n tag: string,\n ctx: ConvertContext,\n): void {\n for (const name of readForeignCssClasses(attrs)) {\n ctx.warnings.push(\n `Dropped CSS class \"${name}\" on <${tag}> — consumer CSS has no Templatical equivalent.`,\n );\n }\n}\n\nexport function isNewTab(attrs: Attrs): boolean {\n return (attrs.target ?? \"\").trim().toLowerCase() === \"_blank\";\n}\n\n/**\n * Wrap the element's own markup in an HTML block — the lossless fallback.\n */\nexport function convertHtmlFallback(\n $el: Cheerio<Element>,\n ctx: ConvertContext,\n attrs: Attrs,\n): Block {\n const outer = ctx.$.html($el) ?? \"\";\n return createHtmlBlock({ content: outer, ...baseFields(attrs) });\n}\n","import type { Cheerio } from \"cheerio\";\nimport {\n createMenuBlock,\n createSocialIconsBlock,\n createTableBlock,\n generateId,\n} from \"@templatical/types\";\nimport type {\n MenuItemData,\n SocialIcon,\n SocialIconSize,\n SocialIconStyle,\n SocialPlatform,\n TableRowData,\n} from \"@templatical/types\";\nimport type { Element } from \"domhandler\";\nimport {\n parseAlignment,\n parseColor,\n parseFontFamily,\n parsePxValue,\n} from \"./attribute-parser\";\nimport {\n childElements,\n ownAttr,\n resolveAttributes,\n tagOf,\n type Attrs,\n} from \"./attribute-resolver\";\nimport { baseFields, type ConvertContext, type Converted } from \"./block-base\";\n\n/**\n * Exhaustive over `SocialPlatform` on purpose: adding a member to that union\n * without adding it here is a compile error, so the importer cannot silently\n * fall back to \"website\" for a platform the block model gained.\n */\nconst KNOWN_PLATFORMS: Record<SocialPlatform, true> = {\n facebook: true,\n twitter: true,\n instagram: true,\n linkedin: true,\n youtube: true,\n tiktok: true,\n pinterest: true,\n email: true,\n whatsapp: true,\n telegram: true,\n discord: true,\n snapchat: true,\n reddit: true,\n github: true,\n dribbble: true,\n behance: true,\n website: true,\n};\n\nconst PLATFORM_ALIASES: Record<string, SocialPlatform> = {\n x: \"twitter\",\n \"x-twitter\": \"twitter\",\n};\n\nfunction normalizePlatform(raw: string): SocialPlatform | null {\n // MJML ships `<platform>-noshare` variants that render the same icon.\n const cleaned = raw\n .trim()\n .toLowerCase()\n .replace(/-noshare$/, \"\");\n if (!cleaned) return null;\n if (PLATFORM_ALIASES[cleaned]) return PLATFORM_ALIASES[cleaned];\n return cleaned in KNOWN_PLATFORMS ? (cleaned as SocialPlatform) : null;\n}\n\n/** The `<style>/<platform>.png` tail of the URL `renderers/social.ts:76` builds. */\nfunction platformFromSrc(src: string): { platform: string; style: string } {\n const parts = src.split(\"?\")[0].split(\"/\").filter(Boolean);\n const file = parts.at(-1) ?? \"\";\n return {\n platform: file.replace(/\\.[a-z0-9]+$/i, \"\"),\n style: parts.at(-2) ?? \"\",\n };\n}\n\nconst ICON_SIZES: Array<[number, SocialIconSize]> = [\n [24, \"small\"],\n [32, \"medium\"],\n [48, \"large\"],\n];\n\nfunction nearestIconSize(px: number): { size: SocialIconSize; exact: boolean } {\n let best = ICON_SIZES[1];\n let bestGap = Infinity;\n for (const candidate of ICON_SIZES) {\n const gap = Math.abs(candidate[0] - px);\n if (gap < bestGap) {\n bestGap = gap;\n best = candidate;\n }\n }\n return { size: best[1], exact: bestGap === 0 };\n}\n\nconst RADIUS_STYLES: Record<string, SocialIconStyle> = {\n \"50%\": \"circle\",\n \"8px\": \"rounded\",\n \"0\": \"square\",\n \"4px\": \"solid\",\n};\n\nconst KNOWN_ICON_STYLES = new Set<string>([\n \"solid\",\n \"outlined\",\n \"rounded\",\n \"square\",\n \"circle\",\n]);\n\nexport function convertSocial(\n $el: Cheerio<Element>,\n attrs: Attrs,\n ctx: ConvertContext,\n): Converted | null {\n const elements = childElements($el, ctx.$).filter(\n ($child) => tagOf($child[0]) === \"mj-social-element\",\n );\n if (elements.length === 0) return null;\n\n const notes: string[] = [];\n const icons: SocialIcon[] = [];\n\n let iconStyle: SocialIconStyle | null = null;\n let iconSizePx = 0;\n let spacing = 0;\n\n elements.forEach(($child, index) => {\n const childAttrs = resolveAttributes($child, ctx.cascade);\n const src = (childAttrs.src ?? \"\").trim();\n const fromSrc = src ? platformFromSrc(src) : { platform: \"\", style: \"\" };\n\n const rawName = (childAttrs.name ?? \"\").trim() || fromSrc.platform;\n const platform = normalizePlatform(rawName);\n if (!platform && rawName) {\n notes.push(\n `Unrecognised social platform \"${rawName}\" mapped to \"website\".`,\n );\n }\n\n icons.push({\n id: generateId(),\n platform: platform ?? \"website\",\n url: (childAttrs.href ?? \"\").trim(),\n });\n\n if (!iconStyle && KNOWN_ICON_STYLES.has(fromSrc.style)) {\n iconStyle = fromSrc.style as SocialIconStyle;\n }\n\n if (iconSizePx === 0) {\n iconSizePx = parsePxValue(childAttrs[\"icon-size\"]);\n }\n\n // The final element emits `0` right-padding, so spacing is only readable\n // from a non-final one (renderers/social.ts:79).\n if (spacing === 0 && index < elements.length - 1) {\n spacing = parsePxValue((childAttrs.padding ?? \"\").trim().split(/\\s+/)[1]);\n }\n\n if (!iconStyle) {\n const radius = (childAttrs[\"border-radius\"] ?? \"\").trim().toLowerCase();\n const mapped = RADIUS_STYLES[radius];\n if (mapped) {\n iconStyle = mapped;\n if (radius === \"4px\") {\n notes.push(\n 'Icon border-radius 4px maps to both \"solid\" and \"outlined\"; resolved to \"solid\".',\n );\n }\n }\n }\n });\n\n const declaredSize = parsePxValue(attrs[\"icon-size\"]) || iconSizePx;\n let iconSize: SocialIconSize | undefined;\n if (declaredSize > 0) {\n const resolved = nearestIconSize(declaredSize);\n iconSize = resolved.size;\n if (!resolved.exact) {\n notes.push(\n `Icon size ${declaredSize}px is not one of 24/32/48; resolved to \"${resolved.size}\".`,\n );\n }\n }\n\n const block = createSocialIconsBlock({\n icons,\n align: parseAlignment(attrs.align, \"center\"),\n ...(iconSize ? { iconSize } : {}),\n ...(iconStyle ? { iconStyle } : {}),\n ...(spacing > 0 ? { spacing } : {}),\n ...baseFields(attrs),\n });\n\n return {\n block,\n entry: {\n sourceTag: \"mj-social\",\n templaticalBlockType: \"social\",\n status: notes.length > 0 ? \"approximated\" : \"converted\",\n ...(notes.length > 0 ? { note: notes.join(\" \") } : {}),\n },\n };\n}\n\nexport function convertNavbar(\n $el: Cheerio<Element>,\n attrs: Attrs,\n ctx: ConvertContext,\n): Converted | null {\n const links = childElements($el, ctx.$).filter(\n ($child) => tagOf($child[0]) === \"mj-navbar-link\",\n );\n if (links.length === 0) return null;\n\n const items: MenuItemData[] = links.map(($link) => {\n const linkAttrs = resolveAttributes($link, ctx.cascade);\n // ownAttr, not linkAttrs directly — same cascade-vs-own distinction as\n // the block-level font-family read below: a hand-written document's own\n // <mj-attributes><mj-navbar-link color=\"…\"/></mj-attributes> default must\n // not be read back as this link's own override.\n const color = parseColor(\n ownAttr(linkAttrs, \"color\", \"mj-navbar-link\", ctx.cascade),\n );\n return {\n id: generateId(),\n text: ($link.text() ?? \"\").trim(),\n url: (linkAttrs.href ?? \"\").trim(),\n openInNewTab: (linkAttrs.target ?? \"\").toLowerCase() === \"_blank\",\n bold: (linkAttrs[\"font-weight\"] ?? \"\").toLowerCase() === \"bold\",\n underline: (linkAttrs[\"text-decoration\"] ?? \"\").includes(\"underline\"),\n ...(color ? { color } : {}),\n };\n });\n\n const fontSize = parsePxValue(attrs[\"font-size\"]);\n // ownAttr, not attrs directly — same cascade-vs-own distinction ownAttr\n // documents (attribute-resolver.ts): our own renderer never emits\n // mj-navbar, but a hand-written document's own\n // <mj-attributes><mj-navbar font-family=\"…\"/></mj-attributes> default must\n // not be read back as this navbar's own override.\n const fontFamily = parseFontFamily(\n ownAttr(attrs, \"font-family\", \"mj-navbar\", ctx.cascade),\n );\n\n const block = createMenuBlock({\n items,\n textAlign: parseAlignment(attrs.align, \"center\"),\n ...(fontSize > 0 ? { fontSize } : {}),\n ...(fontFamily ? { fontFamily } : {}),\n ...baseFields(attrs),\n });\n\n return {\n block,\n entry: {\n sourceTag: \"mj-navbar\",\n templaticalBlockType: \"menu\",\n status: \"converted\",\n },\n };\n}\n\nexport function convertNativeTable(\n $el: Cheerio<Element>,\n attrs: Attrs,\n ctx: ConvertContext,\n): Converted | null {\n const $ = ctx.$;\n // .find(\"tr\") is a descendant search, so a <table> nested inside a cell\n // would otherwise contribute its own rows here too, interleaved with\n // $el's own. Keep only rows with no \"table\" ancestor between themselves\n // and $el — a nested table's rows stay untouched inside their cell's\n // content, read verbatim by the .html() call below.\n const rowEls = $el\n .find(\"tr\")\n .toArray()\n .filter((tr) => $(tr).parentsUntil($el, \"table\").length === 0);\n if (rowEls.length === 0) return null;\n\n const rows: TableRowData[] = rowEls.map((rowEl) => ({\n id: generateId(),\n cells: $(rowEl)\n .children()\n .toArray()\n .map((cellEl) => ({ id: generateId(), content: $(cellEl).html() ?? \"\" })),\n }));\n\n const hasHeaderRow = $(rowEls[0])\n .children()\n .toArray()\n .some((cell) => tagOf(cell) === \"th\");\n\n // ownAttr, not attrs directly — same cascade-vs-own distinction as\n // convertNavbar above: a hand-written document's own\n // <mj-attributes><mj-table color=\"…\" font-family=\"…\"/></mj-attributes>\n // default must not be read back as this table's own override.\n const color = parseColor(ownAttr(attrs, \"color\", \"mj-table\", ctx.cascade));\n const fontSize = parsePxValue(attrs[\"font-size\"]);\n const fontFamily = parseFontFamily(\n ownAttr(attrs, \"font-family\", \"mj-table\", ctx.cascade),\n );\n\n const block = createTableBlock({\n rows,\n hasHeaderRow,\n textAlign: parseAlignment(attrs.align, \"left\"),\n ...(color ? { color } : {}),\n ...(fontSize > 0 ? { fontSize } : {}),\n ...(fontFamily ? { fontFamily } : {}),\n ...baseFields(attrs),\n });\n\n return {\n block,\n entry: {\n sourceTag: \"mj-table\",\n templaticalBlockType: \"table\",\n status: \"converted\",\n },\n };\n}\n","import { load } from \"cheerio\";\nimport type { Cheerio } from \"cheerio\";\nimport type { Element } from \"domhandler\";\nimport {\n createMenuBlock,\n createParagraphBlock,\n createTableBlock,\n createTitleBlock,\n generateId,\n RICH_TEXT_SPACING,\n} from \"@templatical/types\";\nimport type {\n HeadingLevel,\n MenuItemData,\n TableRowData,\n} from \"@templatical/types\";\nimport {\n parseAlignment,\n parseColor,\n parseFontFamily,\n parsePxValue,\n} from \"./attribute-parser\";\nimport {\n ownAttr,\n readParagraphGap,\n type Attrs,\n type AttributeCascade,\n} from \"./attribute-resolver\";\nimport { baseFields, type ConvertContext, type Converted } from \"./block-base\";\n\n/**\n * Re-parse an `mj-text`'s inner markup into its own document.\n *\n * The surrounding document already parses void elements correctly (`br`,\n * `img`, … stay void under `converter.ts`'s `xmlMode: false`), so this reparse\n * isn't compensating for a different parser here. It gives each\n * shape-detection helper (title / table / menu) an isolated, freshly\n * queryable document scoped to just this block's own markup — so\n * `$inner(\"tr\")` can only match rows that belong to this table, and\n * `$inner(\"body\")` has a real root to enumerate top-level nodes from. A\n * paragraph's markup needs none of that structure-probing, so it is passed\n * through verbatim and never reparsed.\n */\nfunction parseInner(html: string) {\n return load(`<body>${html}</body>`);\n}\n\nfunction rootElements(html: string): { tag: string; count: number } {\n const $inner = parseInner(html);\n const kids = $inner(\"body\").children().toArray();\n return {\n tag: kids.length > 0 ? (kids[0].tagName?.toLowerCase() ?? \"\") : \"\",\n count: kids.length,\n };\n}\n\nconst HEADING_LEVELS: Record<string, number> = {\n h1: 1,\n h2: 2,\n h3: 3,\n h4: 4,\n h5: 5,\n h6: 6,\n};\n\nfunction convertTitle(\n html: string,\n attrs: Attrs,\n sourceLevel: number,\n cascade: AttributeCascade,\n): Converted {\n const $inner = parseInner(html);\n const $heading = $inner(\"body\").children().first();\n const level = Math.min(sourceLevel, 4) as HeadingLevel;\n // Read via ownAttr, not attrs directly: title.ts emits color/font-family\n // only when the block sets its own, so a value that only reached this\n // element through the document-wide `<mj-attributes>` cascade (§7) is not\n // this title's own and must not be read back onto it.\n const color = parseColor(ownAttr(attrs, \"color\", \"mj-text\", cascade));\n const fontFamily = parseFontFamily(\n ownAttr(attrs, \"font-family\", \"mj-text\", cascade),\n );\n\n const block = createTitleBlock({\n content: $heading.html() ?? \"\",\n level,\n textAlign: parseAlignment(attrs.align, \"left\"),\n ...(color ? { color } : {}),\n ...(fontFamily ? { fontFamily } : {}),\n ...baseFields(attrs),\n });\n\n const clamped = sourceLevel > 4;\n\n return {\n block,\n entry: {\n sourceTag: \"mj-text\",\n templaticalBlockType: \"title\",\n status: clamped ? \"approximated\" : \"converted\",\n ...(clamped\n ? {\n note: `Heading level h${sourceLevel} clamped to 4 — Templatical titles support h1-h4.`,\n }\n : {}),\n },\n };\n}\n\nfunction convertTable(\n html: string,\n attrs: Attrs,\n cascade: AttributeCascade,\n): Converted {\n const $inner = parseInner(html);\n const $rows = $inner(\"tr\");\n\n const rows: TableRowData[] = $rows.toArray().map((rowEl) => ({\n id: generateId(),\n cells: $inner(rowEl)\n .children()\n .toArray()\n .map((cellEl) => ({\n id: generateId(),\n content: $inner(cellEl).html() ?? \"\",\n })),\n }));\n\n const hasHeaderRow =\n $rows.length > 0 && $inner($rows[0]).children(\"th\").length > 0;\n\n // ownAttr, not attrs directly — same cascade-vs-own distinction as\n // convertTitle above: table.ts also emits color/font-family only when the\n // block sets its own (renderers/table.ts:34,29).\n const color = parseColor(ownAttr(attrs, \"color\", \"mj-text\", cascade));\n const fontSize = parsePxValue(attrs[\"font-size\"]);\n const fontFamily = parseFontFamily(\n ownAttr(attrs, \"font-family\", \"mj-text\", cascade),\n );\n\n const block = createTableBlock({\n rows,\n hasHeaderRow,\n textAlign: parseAlignment(attrs.align, \"left\"),\n ...(color ? { color } : {}),\n ...(fontSize > 0 ? { fontSize } : {}),\n ...(fontFamily ? { fontFamily } : {}),\n ...baseFields(attrs),\n });\n\n return {\n block,\n entry: {\n sourceTag: \"mj-text\",\n templaticalBlockType: \"table\",\n status: \"converted\",\n },\n };\n}\n\n/**\n * A menu is top-level anchors with optional `<span>` separators between them —\n * exactly what `renderers/menu.ts` emits, and deliberately not matched when a\n * `<p>` wrapper is present (that is a paragraph containing links).\n */\nfunction looksLikeMenu(html: string): boolean {\n const $inner = parseInner(html);\n const kids = $inner(\"body\").children().toArray();\n if (kids.length === 0) return false;\n\n let anchors = 0;\n for (const kid of kids) {\n const tag = kid.tagName?.toLowerCase() ?? \"\";\n if (tag === \"a\") anchors += 1;\n else if (tag !== \"span\") return false;\n }\n\n return anchors > 0;\n}\n\nfunction convertMenu(\n html: string,\n attrs: Attrs,\n cascade: AttributeCascade,\n): Converted {\n const $inner = parseInner(html);\n\n const items: MenuItemData[] = $inner(\"body\")\n .children(\"a\")\n .toArray()\n .map((el) => {\n const $a = $inner(el);\n const itemColor = parseColor(\n $a.attr(\"style\")?.match(/color\\s*:\\s*([^;]+)/i)?.[1],\n );\n return {\n id: generateId(),\n text: ($a.text() ?? \"\").trim(),\n url: $a.attr(\"href\") ?? \"\",\n openInNewTab: ($a.attr(\"target\") ?? \"\").toLowerCase() === \"_blank\",\n bold: $a.find(\"strong, b\").length > 0,\n underline: ($a.attr(\"style\") ?? \"\").includes(\"underline\"),\n ...(itemColor ? { color: itemColor } : {}),\n };\n });\n\n const $separator = $inner(\"body\").children(\"span\").first();\n const separator = ($separator.text() ?? \"\").trim();\n const separatorColor = parseColor(\n $separator.attr(\"style\")?.match(/color\\s*:\\s*([^;]+)/i)?.[1],\n );\n const spacing = parsePxValue(\n $separator.attr(\"style\")?.match(/padding\\s*:\\s*0\\s+([\\d.]+px)/i)?.[1],\n );\n\n // ownAttr, not attrs directly — same cascade-vs-own distinction as\n // convertTitle above: menu.ts also emits color/font-family only when the\n // block sets its own (renderers/menu.ts:29,24).\n const color = parseColor(ownAttr(attrs, \"color\", \"mj-text\", cascade));\n const fontSize = parsePxValue(attrs[\"font-size\"]);\n const fontFamily = parseFontFamily(\n ownAttr(attrs, \"font-family\", \"mj-text\", cascade),\n );\n\n const block = createMenuBlock({\n items,\n textAlign: parseAlignment(attrs.align, \"center\"),\n ...(separator ? { separator } : {}),\n ...(separatorColor ? { separatorColor } : {}),\n ...(spacing > 0 ? { spacing } : {}),\n ...(color ? { color } : {}),\n ...(fontSize > 0 ? { fontSize } : {}),\n ...(fontFamily ? { fontFamily } : {}),\n ...baseFields(attrs),\n });\n\n return {\n block,\n entry: {\n sourceTag: \"mj-text\",\n templaticalBlockType: \"menu\",\n status: \"converted\",\n },\n };\n}\n\n/**\n * The editor's rich-text blocks assume a block-level wrapper, so bare text gets\n * one. Mirrors `ensureParagraphWrapped` in `@templatical/import-html`.\n */\nfunction ensureParagraphWrapped(html: string): string {\n const trimmed = html.trim();\n if (trimmed === \"\") return \"<p></p>\";\n if (/^<(p|h[1-6]|ul|ol|blockquote|div)\\b/i.test(trimmed)) return trimmed;\n return `<p>${trimmed}</p>`;\n}\n\nfunction convertParagraph(html: string, attrs: Attrs): Converted {\n // A custom gap round-trips through `css-class` as `tpl-rich-text-<gap>`\n // (`richTextGapClass` in `packages/renderer/src/rich-text.ts`); the default\n // gap round-trips the same way, so it must be excluded here rather than\n // just relying on absence — otherwise every imported paragraph would carry\n // an explicit (if harmless) `paragraphSpacing` equal to the default.\n //\n // Paragraph text colour is document-level (`settings.textColor`) —\n // `ParagraphBlock` has no per-block colour field, so `attrs.color` is not\n // read here.\n const gap = readParagraphGap(attrs);\n const paragraphSpacing =\n gap !== null && gap !== RICH_TEXT_SPACING.paragraphGap ? gap : undefined;\n\n const block = createParagraphBlock({\n content: ensureParagraphWrapped(html),\n ...(paragraphSpacing !== undefined ? { paragraphSpacing } : {}),\n ...baseFields(attrs),\n });\n\n return {\n block,\n entry: {\n sourceTag: \"mj-text\",\n templaticalBlockType: \"paragraph\",\n status: \"converted\",\n },\n };\n}\n\n/**\n * Resolve an `mj-text` to Title, Table, Menu or Paragraph by the shape of its\n * content — the reverse of the four renderers that all emit `mj-text`.\n *\n * A fifth renderer emits it too: `HtmlBlock` (`renderers/html.ts`), whose\n * content is arbitrary, so it has no shape to match and lands on the Paragraph\n * fallback. That is irreducible — nothing in the output marks a block's type —\n * and narrowing the Title/Table shapes to compensate would break the common\n * case to serve the rare one. See §10 of the design.\n *\n * Paragraph is the terminal arm and always reachable, so this is total.\n */\nexport function convertTextElement(\n $el: Cheerio<Element>,\n attrs: Attrs,\n ctx: ConvertContext,\n): Converted {\n const html = $el.html() ?? \"\";\n const root = rootElements(html);\n\n if (root.count === 1 && HEADING_LEVELS[root.tag]) {\n return convertTitle(html, attrs, HEADING_LEVELS[root.tag], ctx.cascade);\n }\n\n if (root.count === 1 && root.tag === \"table\") {\n return convertTable(html, attrs, ctx.cascade);\n }\n\n if (looksLikeMenu(html)) {\n return convertMenu(html, attrs, ctx.cascade);\n }\n\n return convertParagraph(html, attrs);\n}\n","import type { Cheerio } from \"cheerio\";\nimport type { Element } from \"domhandler\";\nimport {\n createButtonBlock,\n createDividerBlock,\n createHtmlBlock,\n createImageBlock,\n createSpacerBlock,\n} from \"@templatical/types\";\nimport type { Block } from \"@templatical/types\";\nimport {\n parseAlignment,\n parseBorderStyle,\n parseColor,\n parsePaddingShorthand,\n parsePxValue,\n} from \"./attribute-parser\";\nimport { resolveAttributes, tagOf, type Attrs } from \"./attribute-resolver\";\nimport {\n baseFields,\n convertHtmlFallback,\n isNewTab,\n warnForeignClasses,\n type ConvertContext,\n type Converted,\n} from \"./block-base\";\nimport {\n convertNativeTable,\n convertNavbar,\n convertSocial,\n} from \"./composite-mapper\";\nimport { convertTextElement } from \"./text-inference\";\n\n/** Tags handled elsewhere but recognised, so they never hit the unknown-tag arm. */\nconst STRUCTURAL_TAGS = new Set([\n \"mjml\",\n \"mj-head\",\n \"mj-body\",\n \"mj-wrapper\",\n \"mj-section\",\n \"mj-column\",\n \"mj-group\",\n \"mj-attributes\",\n \"mj-all\",\n \"mj-class\",\n \"mj-font\",\n \"mj-style\",\n \"mj-title\",\n \"mj-preview\",\n \"mj-breakpoint\",\n \"mj-html-attributes\",\n \"mj-social-element\",\n \"mj-navbar-link\",\n]);\n\n/** Tags with no Templatical equivalent that keep their markup verbatim. */\nconst NO_EQUIVALENT_TAGS = new Set([\"mj-hero\", \"mj-carousel\", \"mj-accordion\"]);\n\nfunction convertImage(\n $el: Cheerio<Element>,\n attrs: Attrs,\n ctx: ConvertContext,\n): Block | null {\n const src = (attrs.src ?? \"\").trim();\n if (!src) return null;\n\n const decorative = (attrs.role ?? \"\").trim().toLowerCase() === \"presentation\";\n const pxWidth = parsePxValue(attrs.width);\n const height = parsePxValue(attrs.height);\n const borderRadius = parsePxValue(attrs[\"border-radius\"]);\n const href = (attrs.href ?? \"\").trim();\n\n return createImageBlock({\n src,\n alt: decorative ? \"\" : (attrs.alt ?? \"\"),\n // A px width equal to the container is how `width: \"full\"` renders\n // (renderer/src/renderers/image.ts), so restore the flag rather than\n // freezing the number — otherwise a full-width image stops resizing with\n // the template.\n width: pxWidth === ctx.containerWidth ? \"full\" : pxWidth || \"full\",\n align: parseAlignment(attrs.align, \"center\"),\n ...(height > 0 ? { height } : {}),\n ...(borderRadius > 0 ? { borderRadius } : {}),\n ...(href ? { linkUrl: href } : {}),\n ...(href && isNewTab(attrs) ? { linkOpenInNewTab: true } : {}),\n ...(decorative ? { decorative: true } : {}),\n ...baseFields(attrs),\n });\n}\n\nfunction convertButton($el: Cheerio<Element>, attrs: Attrs): Block | null {\n const text = ($el.text() ?? \"\").trim();\n if (!text) return null;\n\n const backgroundColor = parseColor(attrs[\"background-color\"]);\n const textColor = parseColor(attrs.color);\n const fontSize = parsePxValue(attrs[\"font-size\"]);\n\n return createButtonBlock({\n text,\n url: (attrs.href ?? \"\").trim(),\n ...(backgroundColor ? { backgroundColor } : {}),\n ...(textColor ? { textColor } : {}),\n ...(fontSize > 0 ? { fontSize } : {}),\n ...(attrs[\"border-radius\"] !== undefined\n ? { borderRadius: parsePxValue(attrs[\"border-radius\"]) }\n : {}),\n ...(attrs[\"inner-padding\"] !== undefined\n ? { buttonPadding: parsePaddingShorthand(attrs[\"inner-padding\"]) }\n : {}),\n align: parseAlignment(attrs.align, \"center\"),\n ...(isNewTab(attrs) ? { openInNewTab: true } : {}),\n ...baseFields(attrs),\n });\n}\n\nfunction convertDivider(attrs: Attrs): Block {\n const color = parseColor(attrs[\"border-color\"]);\n const thickness = parsePxValue(attrs[\"border-width\"]);\n\n return createDividerBlock({\n lineStyle: parseBorderStyle(attrs[\"border-style\"]),\n ...(color ? { color } : {}),\n ...(attrs[\"border-width\"] !== undefined ? { thickness } : {}),\n ...baseFields(attrs),\n });\n}\n\nfunction convertSpacer(attrs: Attrs): Block {\n const height = parsePxValue(attrs.height);\n\n return createSpacerBlock({\n ...(attrs.height !== undefined ? { height } : {}),\n ...baseFields(attrs),\n });\n}\n\n/**\n * Convert one MJML element to a Templatical block.\n *\n * Returns `null` for an element that produces nothing *and* warrants no report\n * entry — an image with no `src`, a button with no label. A `Converted` whose\n * `block` is null is a *skip*, which does get an entry.\n */\nexport function convertElement(\n $el: Cheerio<Element>,\n ctx: ConvertContext,\n): Converted | null {\n const tag = tagOf($el[0]);\n if (!tag) return null;\n\n const attrs = resolveAttributes($el, ctx.cascade);\n warnForeignClasses(attrs, tag, ctx);\n\n if (tag === \"mj-include\") {\n const path = (attrs.path ?? \"\").trim();\n return {\n block: null,\n entry: {\n sourceTag: tag,\n templaticalBlockType: null,\n status: \"skipped\",\n note: `Cannot resolve <mj-include path=\"${path}\"> — the importer reads a single string and has no filesystem access. Inline the include before importing.`,\n },\n };\n }\n\n if (tag === \"mj-text\") {\n return convertTextElement($el, attrs, ctx);\n }\n\n if (tag === \"mj-social\") {\n return convertSocial($el, attrs, ctx);\n }\n\n if (tag === \"mj-navbar\") {\n return convertNavbar($el, attrs, ctx);\n }\n\n if (tag === \"mj-table\") {\n return convertNativeTable($el, attrs, ctx);\n }\n\n if (tag === \"mj-image\") {\n const block = convertImage($el, attrs, ctx);\n if (!block) return null;\n return {\n block,\n entry: {\n sourceTag: tag,\n templaticalBlockType: \"image\",\n status: \"converted\",\n },\n };\n }\n\n if (tag === \"mj-button\") {\n const block = convertButton($el, attrs);\n if (!block) return null;\n return {\n block,\n entry: {\n sourceTag: tag,\n templaticalBlockType: \"button\",\n status: \"converted\",\n },\n };\n }\n\n if (tag === \"mj-divider\") {\n return {\n block: convertDivider(attrs),\n entry: {\n sourceTag: tag,\n templaticalBlockType: \"divider\",\n status: \"converted\",\n },\n };\n }\n\n if (tag === \"mj-spacer\") {\n return {\n block: convertSpacer(attrs),\n entry: {\n sourceTag: tag,\n templaticalBlockType: \"spacer\",\n status: \"converted\",\n },\n };\n }\n\n if (tag === \"mj-raw\") {\n return {\n block: createHtmlBlock({\n content: $el.html() ?? \"\",\n ...baseFields(attrs),\n }),\n entry: {\n sourceTag: tag,\n templaticalBlockType: \"html\",\n status: \"converted\",\n },\n };\n }\n\n if (NO_EQUIVALENT_TAGS.has(tag)) {\n return {\n block: convertHtmlFallback($el, ctx, attrs),\n entry: {\n sourceTag: tag,\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n note: `<${tag}> has no Templatical block equivalent; the original markup is preserved.`,\n },\n };\n }\n\n if (STRUCTURAL_TAGS.has(tag)) return null;\n\n return {\n block: convertHtmlFallback($el, ctx, attrs),\n entry: {\n sourceTag: tag,\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n note: `<${tag}> is not a known MJML element (a custom component?); the original markup is preserved.`,\n },\n };\n}\n","import type { Cheerio } from \"cheerio\";\nimport type { Element } from \"domhandler\";\nimport { SYNTAX_PRESETS } from \"@templatical/types\";\nimport type { Block } from \"@templatical/types\";\nimport { tagOf } from \"./attribute-resolver\";\n\ntype DisplayCondition = NonNullable<Block[\"displayCondition\"]>;\n\nexport interface SiblingUnit {\n $el: Cheerio<Element>;\n displayCondition?: DisplayCondition;\n}\n\n/** Characters kept before an ellipsis is appended; the full condition stays in `before`. */\nconst LABEL_MAX = 46;\n\nconst ANCHORED_LOGIC = Object.values(SYNTAX_PRESETS).map(\n (preset) =>\n new RegExp(\n `^(?:${preset.logic.source})$`,\n preset.logic.flags.replace(\"g\", \"\"),\n ),\n);\n\n/**\n * Whether the text is exactly one logic tag and nothing else.\n *\n * Anchored against every registered syntax rather than one, so a mailchimp or\n * ampscript template is recognised as readily as a liquid one. MSO conditionals\n * are HTML comments and match none of them, which is what keeps hand-written\n * `mj-raw` pairs out of this path.\n */\nexport function isLogicTagOnly(text: string): boolean {\n const trimmed = text.trim();\n if (trimmed === \"\") return false;\n return ANCHORED_LOGIC.some((regex) => regex.test(trimmed));\n}\n\nfunction isLogicRaw($el: Cheerio<Element>): boolean {\n return tagOf($el[0]) === \"mj-raw\" && isLogicTagOnly($el.text() ?? \"\");\n}\n\nfunction synthesizeLabel(before: string): string {\n const trimmed = before.trim();\n if (trimmed.length <= LABEL_MAX) return trimmed;\n return `${trimmed.slice(0, LABEL_MAX)}…`;\n}\n\n/**\n * Group a run of siblings into units, folding each `logic-raw / element /\n * logic-raw` triple into one unit carrying a `displayCondition`.\n *\n * `label` is synthesised from `before`: it is editor metadata that appears\n * nowhere in the MJML, so it cannot be recovered — only reconstructed. `group`\n * and `description` are left absent for the same reason.\n *\n * Deliberately no check that the two tags pair *semantically*. The renderer\n * emits them only in this arrangement, and matching open/close keywords across\n * four syntaxes would be a table to maintain for no gain.\n */\nexport function planSiblings($siblings: Cheerio<Element>[]): SiblingUnit[] {\n const units: SiblingUnit[] = [];\n let i = 0;\n\n while (i < $siblings.length) {\n const $current = $siblings[i];\n\n const $middle = $siblings[i + 1];\n const $closing = $siblings[i + 2];\n\n const isTriple =\n isLogicRaw($current) &&\n $middle !== undefined &&\n tagOf($middle[0]) !== \"mj-raw\" &&\n $closing !== undefined &&\n isLogicRaw($closing);\n\n if (isTriple) {\n const before = ($current.text() ?? \"\").trim();\n const after = ($closing.text() ?? \"\").trim();\n units.push({\n $el: $middle,\n displayCondition: { label: synthesizeLabel(before), before, after },\n });\n i += 3;\n continue;\n }\n\n units.push({ $el: $current });\n i += 1;\n }\n\n return units;\n}\n","import type { CheerioAPI } from \"cheerio\";\nimport { DEFAULT_TEMPLATE_DEFAULTS } from \"@templatical/types\";\nimport type { TemplateContent, TemplateSettings } from \"@templatical/types\";\nimport { parseColor, parseFontFamily, parsePxValue } from \"./attribute-parser\";\nimport {\n childElements,\n findByTag,\n tagOf,\n type AttributeCascade,\n} from \"./attribute-resolver\";\n\n/** `mj-head` children this module reads; anything else is warned about. */\nconst CONSUMED_HEAD_TAGS = new Set([\n \"mj-attributes\",\n \"mj-preview\",\n \"mj-font\",\n \"mj-style\",\n \"mj-title\",\n]);\n\n/**\n * `DEFAULT_TEMPLATE_DEFAULTS` is typed `Partial<TemplateSettings>` so a\n * consumer can override any subset of it, but its own literal (see\n * `packages/types/src/defaults.ts`) always sets exactly the six required\n * `TemplateSettings` fields — `linkColor` and `preheaderText` are the two\n * optional ones and are correctly absent from it. Narrowing the type once\n * here, rather than at every read below, is what lets `width`, `fontFamily`\n * and the rest come out as `number`/`string`/`boolean` instead of `| undefined`.\n */\nconst REQUIRED_TEMPLATE_DEFAULTS = DEFAULT_TEMPLATE_DEFAULTS as Required<\n Pick<\n TemplateSettings,\n | \"width\"\n | \"backgroundColor\"\n | \"textColor\"\n | \"linkUnderline\"\n | \"fontFamily\"\n | \"locale\"\n >\n>;\n\ninterface AnchorRule {\n color?: string;\n underline?: boolean;\n}\n\n/**\n * Read the `a { … }` declarations out of the concatenated `mj-style` blocks.\n *\n * This is the reverse of how the renderer emits `settings.linkColor` and\n * `settings.linkUnderline` — as a global anchor rule — so a template that\n * round-trips keeps both. A stylesheet with no anchor rule leaves both unset.\n */\nfunction readAnchorRule(css: string): AnchorRule {\n const rule: AnchorRule = {};\n\n // Strip CSS comments to avoid breaking property matching when comments\n // precede or separate declarations (e.g. `a { /* note */ color: #0055ff; }`).\n const cssWithoutComments = css.replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\");\n\n for (const match of cssWithoutComments.matchAll(\n /(^|[},])\\s*a\\s*\\{([^}]*)\\}/g,\n )) {\n const body = match[2];\n\n const color = body.match(/(?:^|;)\\s*color\\s*:\\s*([^;]+)/i);\n if (color) {\n const parsed = parseColor(color[1]);\n if (parsed) rule.color = parsed;\n }\n\n const decoration = body.match(/(?:^|;)\\s*text-decoration\\s*:\\s*([^;]+)/i);\n if (decoration) {\n rule.underline = decoration[1].trim().toLowerCase().includes(\"underline\");\n }\n }\n\n return rule;\n}\n\n/**\n * Build `TemplateSettings` from `mj-body`'s attributes, the attribute cascade\n * and the remaining `mj-head` children.\n *\n * Optional keys (`preheaderText`, `linkColor`) are **omitted** rather than set\n * to `undefined`: an absent key is what the block model means by unset, and a\n * present-but-undefined key serialises into exported JSON.\n */\nexport function extractSettings(\n $: CheerioAPI,\n cascade: AttributeCascade,\n warnings: string[],\n): TemplateContent[\"settings\"] {\n const $body = findByTag($, \"mj-body\").first();\n const $root = findByTag($, \"mjml\").first();\n\n const width =\n parsePxValue($body.attr(\"width\")) || REQUIRED_TEMPLATE_DEFAULTS.width;\n const backgroundColor =\n parseColor($body.attr(\"background-color\")) ||\n REQUIRED_TEMPLATE_DEFAULTS.backgroundColor;\n\n const fontFromCascade =\n parseFontFamily(cascade.all[\"font-family\"]) ||\n parseFontFamily(cascade.byTag[\"mj-text\"]?.[\"font-family\"]);\n const fontFromDeclaration =\n findByTag($, \"mj-font\").first().attr(\"name\") ?? \"\";\n const fontFamily =\n fontFromCascade ||\n fontFromDeclaration ||\n REQUIRED_TEMPLATE_DEFAULTS.fontFamily;\n\n const textColor =\n parseColor(cascade.byTag[\"mj-text\"]?.color) ||\n REQUIRED_TEMPLATE_DEFAULTS.textColor;\n\n const previewText = findByTag($, \"mj-preview\").first().text().trim();\n\n // Every <mj-style> block in the document is pooled into one stylesheet before\n // the anchor rule is read, so a bare `a { … }` rule in any of them contributes.\n // Against the renderer's own output that is unambiguous: richTextStylesheet()\n // scopes every rule it emits to `p`, `ul`, `ol` and `li`, so the one bare\n // `a { … }` rule present is the document-level rule this function reads back.\n const styleCss = findByTag($, \"mj-style\")\n .toArray()\n .map((el) => $(el).text())\n .join(\"\\n\");\n const anchor = readAnchorRule(styleCss);\n\n const locale =\n ($root.attr(\"lang\") ?? \"\").trim() || REQUIRED_TEMPLATE_DEFAULTS.locale;\n\n const title = findByTag($, \"mj-title\").first().text().trim();\n if (title) {\n warnings.push(\n `Dropped <mj-title> (\"${title}\") — Templatical templates have no document-title field.`,\n );\n }\n\n const $head = findByTag($, \"mj-head\").first();\n if ($head.length > 0) {\n for (const $child of childElements($head, $)) {\n const tag = tagOf($child[0]);\n if (!CONSUMED_HEAD_TAGS.has(tag)) {\n warnings.push(`Dropped <${tag}> — it has no Templatical equivalent.`);\n }\n }\n }\n\n return {\n width,\n backgroundColor,\n textColor,\n linkUnderline: anchor.underline ?? REQUIRED_TEMPLATE_DEFAULTS.linkUnderline,\n fontFamily,\n locale,\n ...(anchor.color ? { linkColor: anchor.color } : {}),\n ...(previewText ? { preheaderText: previewText } : {}),\n };\n}\n","import type { Cheerio } from \"cheerio\";\nimport type { Element } from \"domhandler\";\nimport { createSectionBlock } from \"@templatical/types\";\nimport type {\n Block,\n ColumnLayout,\n SectionWrapper,\n SpacingValue,\n} from \"@templatical/types\";\nimport {\n parseColor,\n parseDefinitePx,\n parsePaddingShorthand,\n parsePercent,\n parsePxValue,\n} from \"./attribute-parser\";\nimport {\n childElements,\n resolveAttributes,\n tagOf,\n type Attrs,\n} from \"./attribute-resolver\";\nimport { convertElement } from \"./block-mapper\";\nimport {\n baseFields,\n convertHtmlFallback,\n type ConvertContext,\n} from \"./block-base\";\nimport { planSiblings } from \"./display-condition\";\nimport type { ImportReportEntry } from \"./types\";\n\n/** Reverse of `packages/renderer/src/columns.ts`, keyed by column count. */\nconst LAYOUT_SHAPES: Array<{ layout: ColumnLayout; percents: number[] }> = [\n { layout: \"1\", percents: [100] },\n { layout: \"2\", percents: [50, 50] },\n { layout: \"1-2\", percents: [33.33, 66.67] },\n { layout: \"2-1\", percents: [66.67, 33.33] },\n { layout: \"3\", percents: [33.33, 33.33, 33.34] },\n];\n\n/** Percentage points of drift tolerated per column before a match is inexact. */\nconst WIDTH_TOLERANCE = 2;\n\n/**\n * Resolve MJML column widths to one of the five layouts `ColumnLayout` allows.\n *\n * `exact: false` means the caller must report `approximated` — MJML permits any\n * number of columns at any width and this union permits five shapes, so this is\n * the importer's main irreducible loss (§8.1).\n */\nexport function matchColumnLayout(percents: Array<number | null>): {\n layout: ColumnLayout;\n exact: boolean;\n} {\n const count = percents.length;\n if (count === 0) return { layout: \"1\", exact: true };\n\n // No widths at all is MJML's \"distribute equally\", which is exactly what the\n // n-column layout means — not a missing value to approximate around.\n if (percents.every((p) => p === null)) {\n if (count === 1) return { layout: \"1\", exact: true };\n if (count === 2) return { layout: \"2\", exact: true };\n if (count === 3) return { layout: \"3\", exact: true };\n return { layout: \"3\", exact: false };\n }\n\n // A partial mix of known and absent widths fills each absent column with\n // its share of what the known widths leave over, not an equal split of the\n // whole row — an equal split is only correct when every column is absent,\n // which the branch above already handles. Clamped at 0 so a document whose\n // explicit widths already exceed 100% cannot produce a negative fill.\n const nullCount = percents.filter((p) => p === null).length;\n const knownSum = percents.reduce((sum: number, p) => sum + (p ?? 0), 0);\n const remainder = nullCount > 0 ? Math.max(0, 100 - knownSum) / nullCount : 0;\n const resolved = percents.map((p) => p ?? remainder);\n\n const sameCount = LAYOUT_SHAPES.filter(\n (shape) => shape.percents.length === count,\n );\n\n for (const shape of sameCount) {\n const fits = shape.percents.every(\n (want, i) => Math.abs(want - resolved[i]) <= WIDTH_TOLERANCE,\n );\n if (fits) return { layout: shape.layout, exact: true };\n }\n\n // Nearest same-count shape by total absolute error; if the count itself has\n // no shape (4+), collapse to \"3\" and let the caller fold the overflow.\n const candidates =\n sameCount.length > 0\n ? sameCount\n : LAYOUT_SHAPES.filter((shape) => shape.layout === \"3\");\n\n let best = candidates[0];\n let bestError = Infinity;\n for (const shape of candidates) {\n const error = shape.percents.reduce(\n (sum, want, i) => sum + Math.abs(want - (resolved[i] ?? 0)),\n 0,\n );\n if (error < bestError) {\n bestError = error;\n best = shape;\n }\n }\n\n return { layout: best.layout, exact: false };\n}\n\nconst COLUMN_COUNT: Record<ColumnLayout, number> = {\n \"1\": 1,\n \"2\": 2,\n \"3\": 3,\n \"2-1\": 2,\n \"1-2\": 2,\n};\n\n/** Column pixel widths per layout, mirroring `renderer/src/columns.ts`. */\nfunction columnPixels(layout: ColumnLayout, containerWidth: number): number[] {\n switch (layout) {\n case \"2\":\n return [containerWidth * 0.5, containerWidth * 0.5];\n case \"3\":\n return [containerWidth / 3, containerWidth / 3, containerWidth / 3];\n case \"1-2\":\n return [containerWidth / 3, (containerWidth * 2) / 3];\n case \"2-1\":\n return [(containerWidth * 2) / 3, containerWidth / 3];\n default:\n return [containerWidth];\n }\n}\n\n/**\n * The `mj-column` elements of a section, in document order: a direct\n * `mj-column` child contributes itself, and an `mj-group` child contributes\n * every `mj-column` it holds at the group's own position — so a section\n * mixing direct columns with one or more groups keeps every column instead\n * of losing all but the first group's. `grouped` is true whenever any\n * `mj-group` child is present, which is what drives `stackOnMobile: false`.\n */\nfunction readColumns(\n $el: Cheerio<Element>,\n ctx: ConvertContext,\n): { columns: Cheerio<Element>[]; grouped: boolean } {\n const kids = childElements($el, ctx.$);\n const columns: Cheerio<Element>[] = [];\n let grouped = false;\n\n for (const $kid of kids) {\n const tag = tagOf($kid[0]);\n if (tag === \"mj-column\") {\n columns.push($kid);\n } else if (tag === \"mj-group\") {\n grouped = true;\n columns.push(\n ...childElements($kid, ctx.$).filter(\n ($k) => tagOf($k[0]) === \"mj-column\",\n ),\n );\n }\n }\n\n return { columns, grouped };\n}\n\n/**\n * A column's children, in document order, one block per unit `planSiblings`\n * groups them into.\n *\n * Routed through `planSiblings` for the same reason `walkBody` is\n * (`converter.ts`): the renderer wraps a conditional block in bracketing\n * `mj-raw` guards inside a column exactly as it does at top level\n * (`renderers/section.ts` calls the same `wrapWithDisplayCondition` helper as\n * `index.ts`), so recovering the condition here needs the identical fold.\n */\nfunction convertColumnChildren(\n $column: Cheerio<Element>,\n ctx: ConvertContext,\n entries: ImportReportEntry[],\n): Block[] {\n const blocks: Block[] = [];\n\n for (const unit of planSiblings(childElements($column, ctx.$))) {\n const tag = tagOf(unit.$el[0]);\n\n // MJML forbids mj-section inside mj-column, and `addBlock` in\n // @templatical/core refuses a section into a column too, so producing one\n // here would be dropped downstream without a trace.\n if (tag === \"mj-section\" || tag === \"mj-wrapper\") {\n const attrs = resolveAttributes(unit.$el, ctx.cascade);\n const fallback = convertHtmlFallback(unit.$el, ctx, attrs);\n if (unit.displayCondition)\n fallback.displayCondition = unit.displayCondition;\n blocks.push(fallback);\n const noun = tag === \"mj-section\" ? \"section\" : \"wrapper\";\n entries.push({\n sourceTag: tag,\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n note: `MJML forbids <${tag}> inside <mj-column>; the nested ${noun}'s markup is preserved as an html block.`,\n });\n continue;\n }\n\n const converted = convertElement(unit.$el, ctx);\n if (!converted) continue;\n entries.push(converted.entry);\n if (!converted.block) continue;\n\n if (unit.displayCondition) {\n converted.block.displayCondition = unit.displayCondition;\n }\n\n blocks.push(converted.block);\n }\n\n return blocks;\n}\n\n/**\n * A column's width as a percentage of the section's container, or `null`\n * when the column carries no definite width of its own.\n *\n * `mj-column` accepts either a percentage or a px length, and a px width is\n * real geometry — converting it here lets it participate in\n * `matchColumnLayout` as a known value instead of falling through to \"auto\"\n * and losing the author's intended ratio.\n */\nfunction columnWidthPercent(\n value: string | undefined,\n containerWidth: number,\n): number | null {\n const percent = parsePercent(value);\n if (percent !== null) return percent;\n\n const px = parseDefinitePx(value);\n return px !== null && containerWidth > 0 ? (px / containerWidth) * 100 : null;\n}\n\n/**\n * Build a `SectionBlock` (always exactly one) from an `mj-section`.\n *\n * Returns an array so the caller can treat sections and wrappers uniformly.\n */\nexport function buildSection(\n $el: Cheerio<Element>,\n ctx: ConvertContext,\n entries: ImportReportEntry[],\n wrapper?: SectionWrapper,\n): Block[] {\n const attrs = resolveAttributes($el, ctx.cascade);\n const { columns, grouped } = readColumns($el, ctx);\n\n const rawWidths = columns.map(\n ($c) => resolveAttributes($c, ctx.cascade).width,\n );\n const percents = rawWidths.map((width) =>\n columnWidthPercent(width, ctx.containerWidth),\n );\n const { layout, exact } = matchColumnLayout(percents);\n const slots = COLUMN_COUNT[layout];\n const pixels = columnPixels(layout, ctx.containerWidth);\n\n // `entries` is the document-wide report the whole walk shares, not a\n // per-section array, so the section's own entry is pushed here — before its\n // children convert — to land ahead of them and ahead of whatever a sibling\n // section pushes next. Splicing it in afterward (e.g. `unshift`) would put\n // every section's entry at the front of the entire report instead of at the\n // front of its own children, reversing section order in a multi-section\n // document and interleaving children under the wrong parent.\n //\n // Built from the raw attribute strings, not the resolved `percents` — a px\n // width would otherwise show as a repeating-decimal percentage of the\n // container instead of the value the author actually wrote.\n const shown = rawWidths.map((w) => w || \"auto\").join(\", \");\n entries.push({\n sourceTag: \"mj-section\",\n templaticalBlockType: \"section\",\n status: exact ? \"converted\" : \"approximated\",\n ...(exact\n ? {}\n : {\n note: `Column widths ${shown} have no exact Templatical layout; resolved to \"${layout}\".`,\n }),\n });\n\n const children: Block[][] = Array.from({ length: slots }, () => []);\n\n columns.forEach(($column, index) => {\n // A 4th+ column folds into the last slot rather than becoming an html\n // block: its content converts perfectly and only the geometry is lost.\n const slot = Math.min(index, slots - 1);\n const columnCtx: ConvertContext = {\n ...ctx,\n containerWidth: Math.round(pixels[slot] ?? ctx.containerWidth),\n };\n children[slot].push(...convertColumnChildren($column, columnCtx, entries));\n });\n\n const borderRadius = parsePxValue(attrs[\"border-radius\"]);\n\n const section = createSectionBlock({\n columns: layout,\n children,\n ...(grouped ? { stackOnMobile: false } : {}),\n ...(borderRadius > 0 ? { borderRadius } : {}),\n ...(wrapper ? { wrapper } : {}),\n // \"native\": mj-section is the one element whose own background-color\n // attribute is its container fill (renderers/section.ts:32) — every other\n // baseFields caller reads container-background-color instead.\n ...baseFields(attrs, \"native\"),\n });\n\n return [section];\n}\n\nfunction readWrapper(attrs: Attrs): SectionWrapper {\n const backgroundColor = parseColor(attrs[\"background-color\"]);\n const padding: SpacingValue = parsePaddingShorthand(attrs.padding);\n const borderRadius = parsePxValue(attrs[\"border-radius\"]);\n\n return {\n ...(backgroundColor ? { backgroundColor } : {}),\n padding,\n ...(borderRadius > 0 ? { borderRadius } : {}),\n };\n}\n\n/**\n * Fold an `mj-wrapper` into the `wrapper` field of the section(s) it holds.\n *\n * A wrapper is not a block: `SectionWrapper` is exactly the band the renderer\n * emits an `mj-wrapper` for (`renderer/src/index.ts:223`), so representing it\n * as its own section would double the nesting on every round trip.\n */\nexport function buildWrapper(\n $el: Cheerio<Element>,\n ctx: ConvertContext,\n entries: ImportReportEntry[],\n): Block[] {\n const attrs = resolveAttributes($el, ctx.cascade);\n const wrapper = readWrapper(attrs);\n\n const sections = childElements($el, ctx.$).filter(\n ($k) => tagOf($k[0]) === \"mj-section\",\n );\n\n if (sections.length === 0) {\n entries.push({\n sourceTag: \"mj-wrapper\",\n templaticalBlockType: null,\n status: \"skipped\",\n note: \"An <mj-wrapper> with no <mj-section> children produces nothing.\",\n });\n return [];\n }\n\n if (sections.length > 1) {\n entries.push({\n sourceTag: \"mj-wrapper\",\n templaticalBlockType: \"section\",\n status: \"approximated\",\n note: `An <mj-wrapper> holding ${sections.length} sections was applied to each of them — Templatical has no multi-section band.`,\n });\n }\n\n return sections.flatMap(($section) =>\n buildSection($section, ctx, entries, wrapper),\n );\n}\n","import { load } from \"cheerio\";\nimport type { Cheerio } from \"cheerio\";\nimport type { Element } from \"domhandler\";\nimport {\n createDefaultTemplateContent,\n createSectionBlock,\n} from \"@templatical/types\";\nimport type { Block, TemplateContent } from \"@templatical/types\";\nimport {\n buildAttributeCascade,\n childElements,\n findByTag,\n tagOf,\n} from \"./attribute-resolver\";\nimport { convertElement } from \"./block-mapper\";\nimport type { ConvertContext } from \"./block-base\";\nimport { planSiblings } from \"./display-condition\";\nimport { extractSettings } from \"./head-parser\";\nimport { buildSection, buildWrapper } from \"./section-builder\";\nimport type { ImportReport, ImportReportEntry, ImportResult } from \"./types\";\n\nconst EMPTY_DOCUMENT_WARNING =\n \"No convertible content was found in the MJML. Check that the document has an <mj-body> with at least one <mj-section>.\";\n\n/**\n * Wrap blocks that sat directly under `mj-body` in a one-column section.\n *\n * Valid MJML puts every block inside an `mj-section`, but hand-written and\n * machine-mangled documents do not, and the editor canvas has no\n * representation for a block outside a section.\n */\nfunction wrapInSection(blocks: Block[]): Block {\n return createSectionBlock({\n columns: \"1\",\n children: [blocks],\n styles: { padding: { top: 0, right: 0, bottom: 0, left: 0 } },\n });\n}\n\nfunction walkBody(\n $body: Cheerio<Element>,\n ctx: ConvertContext,\n entries: ImportReportEntry[],\n): Block[] {\n const blocks: Block[] = [];\n let loose: Block[] = [];\n\n const flushLoose = () => {\n if (loose.length > 0) {\n blocks.push(wrapInSection(loose));\n loose = [];\n }\n };\n\n for (const unit of planSiblings(childElements($body, ctx.$))) {\n const tag = tagOf(unit.$el[0]);\n\n if (tag === \"mj-wrapper\" || tag === \"mj-section\") {\n flushLoose();\n const produced =\n tag === \"mj-wrapper\"\n ? buildWrapper(unit.$el, ctx, entries)\n : buildSection(unit.$el, ctx, entries);\n\n // A condition brackets one element, so it lands on every block that\n // element produced — which is more than one only for a multi-section\n // wrapper, where each band carries the same guard.\n for (const block of produced) {\n if (unit.displayCondition)\n block.displayCondition = unit.displayCondition;\n blocks.push(block);\n }\n continue;\n }\n\n const converted = convertElement(unit.$el, ctx);\n if (!converted) continue;\n entries.push(converted.entry);\n if (!converted.block) continue;\n\n if (unit.displayCondition) {\n converted.block.displayCondition = unit.displayCondition;\n }\n\n // An html fallback for a body-level element is already a top-level block;\n // wrapping it in a section would add nesting the source never had.\n if (converted.entry.status === \"html-fallback\") {\n flushLoose();\n blocks.push(converted.block);\n continue;\n }\n\n loose.push(converted.block);\n }\n\n flushLoose();\n return blocks;\n}\n\n/**\n * Convert an MJML document into a Templatical template.\n *\n * @example\n * ```ts\n * const { content, report } = convertMjmlTemplate(mjmlSource);\n *\n * const editor = init({ container: '#editor', content });\n *\n * console.log(report.summary);\n * console.log(report.warnings);\n * ```\n */\nexport function convertMjmlTemplate(mjml: string): ImportResult {\n if (typeof mjml !== \"string\") {\n throw new Error(\n \"Invalid MJML template: expected a string. Pass the raw MJML source as a string.\",\n );\n }\n if (mjml.trim().length === 0) {\n throw new Error(\n \"Invalid MJML template: input is empty. Pass the raw MJML source of an email.\",\n );\n }\n\n // The `xml` option routes parsing through htmlparser2 rather than parse5, so\n // custom `mj-*` tags survive as generic elements and no implicit\n // <html><head><body> is injected around them. Two htmlparser2 options are\n // then overridden away from what `xml: true` alone would give:\n //\n // - `xmlMode: false` puts htmlparser2 in HTML mode, which knows the void\n // element list (`br`, `img`, `hr`, …) and closes them immediately. In XML\n // mode a bare `<br>` stays open and everything after it — including the\n // rest of the paragraph — becomes ITS CHILD rather than its sibling. That\n // shape is exactly what TipTap emits for a hard break and what browser DOM\n // serialization produces, so it hits `<mj-text>` content routinely; a\n // later HTML-mode reparse (e.g. loading the block into the editor) then\n // invents a *second* `<br>` from the dangling `</br>`, per the HTML5 rule\n // that a stray `<br>` end tag opens a new `<br>` rather than closing one.\n // - `recognizeSelfClosing: true` restores what HTML mode otherwise gives up:\n // without it, a self-closing custom tag like `<mj-image src=\"…\" />` never\n // actually closes, and swallows whatever follows as its child instead of\n // its sibling — verified against a self-closing `<mj-all />` immediately\n // followed by a sibling `<mj-text />` inside `<mj-attributes>`, which\n // nested the second tag inside the first and silently dropped a per-tag\n // attribute default.\n const $ = load(mjml, { xml: { xmlMode: false, recognizeSelfClosing: true } });\n const cascade = buildAttributeCascade($);\n\n const entries: ImportReportEntry[] = [];\n const warnings: string[] = [];\n\n const settings = extractSettings($, cascade, warnings);\n\n const $body = findByTag($, \"mj-body\").first();\n const ctx: ConvertContext = {\n $,\n cascade,\n containerWidth: settings.width,\n warnings,\n };\n\n const blocks = $body.length > 0 ? walkBody($body, ctx, entries) : [];\n\n if (blocks.length === 0) {\n warnings.push(EMPTY_DOCUMENT_WARNING);\n }\n\n const content: TemplateContent = {\n ...createDefaultTemplateContent(),\n blocks,\n settings,\n };\n\n const summary = {\n total: entries.length,\n converted: entries.filter((e) => e.status === \"converted\").length,\n approximated: entries.filter((e) => e.status === \"approximated\").length,\n htmlFallback: entries.filter((e) => e.status === \"html-fallback\").length,\n skipped: entries.filter((e) => e.status === \"skipped\").length,\n };\n\n const report: ImportReport = { entries, warnings, summary };\n\n return { content, report };\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@templatical/import-mjml",
3
+ "description": "Convert MJML email templates to Templatical format",
4
+ "version": "0.31.0",
5
+ "bugs": "https://github.com/templatical/sdk/issues",
6
+ "dependencies": {
7
+ "@templatical/types": "0.31.0",
8
+ "cheerio": "^1.2.0",
9
+ "domhandler": "^6.0.1"
10
+ },
11
+ "devDependencies": {
12
+ "@templatical/renderer": "0.31.0",
13
+ "@types/node": "^25.9.5",
14
+ "typescript": "^6.0.3",
15
+ "vitest": "^4.1.11"
16
+ },
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js"
21
+ }
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "homepage": "https://templatical.com",
27
+ "keywords": [
28
+ "email",
29
+ "email-template",
30
+ "importer",
31
+ "migration",
32
+ "mjml",
33
+ "mjml-import",
34
+ "templatical"
35
+ ],
36
+ "license": "MIT",
37
+ "module": "./dist/index.js",
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/templatical/sdk.git",
44
+ "directory": "packages/import-mjml"
45
+ },
46
+ "type": "module",
47
+ "types": "./dist/index.d.ts",
48
+ "scripts": {
49
+ "build": "tsdown",
50
+ "test": "vitest run --config vitest.config.ts",
51
+ "typecheck": "tsc --noEmit"
52
+ }
53
+ }