@templatical/import-mjml 0.46.0 → 0.47.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.
package/dist/index.js CHANGED
@@ -470,6 +470,197 @@ function parseBorderStyle(value) {
470
470
  if (v === "dashed" || v === "dotted") return v;
471
471
  return "solid";
472
472
  }
473
+ const X_GRID = [
474
+ "left",
475
+ "center",
476
+ "right"
477
+ ];
478
+ const Y_GRID = [
479
+ "top",
480
+ "center",
481
+ "bottom"
482
+ ];
483
+ const PERCENT = /^(-?(?:\d+(?:\.\d+)?|\.\d+))%$/;
484
+ const LENGTH = /^-?(?:\d+(?:\.\d+)?|\.\d+)[a-z]*$/;
485
+ /** What MJML paints for a `background-*` attribute an `mj-section` / `mj-wrapper` leaves out. */
486
+ const MJML_BACKGROUND_DEFAULTS = {
487
+ size: "auto",
488
+ x: "center",
489
+ y: "top",
490
+ repeat: true
491
+ };
492
+ /**
493
+ * `background-size` as a keyword. A size with a side at 100%, or a px width
494
+ * spanning the box, fills it as `cover` does; any other length or percentage
495
+ * draws the image at a size of its own, which `auto` is closest to.
496
+ */
497
+ function backgroundSizeKeyword(raw, boxWidth) {
498
+ const parts = raw.toLowerCase().split(/\s+/);
499
+ const [first] = parts;
500
+ if (parts.length === 1 && (first === "cover" || first === "contain")) return {
501
+ size: first,
502
+ exact: true
503
+ };
504
+ if (parts.every((part) => part === "auto")) return {
505
+ size: "auto",
506
+ exact: true
507
+ };
508
+ const px = /^(\d+(?:\.\d+)?|\.\d+)px$/.exec(first);
509
+ return {
510
+ size: parts.includes("100%") || px !== null && boxWidth !== void 0 && Number(px[1]) >= boxWidth ? "cover" : "auto",
511
+ exact: false
512
+ };
513
+ }
514
+ /**
515
+ * `background-position` split into x and y the way MJML and CSS read it: a
516
+ * lone `top` / `bottom` names y and centres x, any other lone value is x
517
+ * with y centred, and a pair is x then y unless it leads with `top` /
518
+ * `bottom` (or with `center` before `left` / `right`). Three- and four-value
519
+ * edge offsets keep their edge keywords only.
520
+ */
521
+ function splitBackgroundPosition(raw) {
522
+ const parts = raw.toLowerCase().split(/\s+/);
523
+ if (parts.length === 1) {
524
+ const [only] = parts;
525
+ return only === "top" || only === "bottom" ? {
526
+ x: "center",
527
+ y: only,
528
+ exact: true
529
+ } : {
530
+ x: only,
531
+ y: "center",
532
+ exact: true
533
+ };
534
+ }
535
+ if (parts.length === 2) {
536
+ const [a, b] = parts;
537
+ return a === "top" || a === "bottom" || a === "center" && (b === "left" || b === "right") ? {
538
+ x: b,
539
+ y: a,
540
+ exact: true
541
+ } : {
542
+ x: a,
543
+ y: b,
544
+ exact: true
545
+ };
546
+ }
547
+ return {
548
+ x: parts.find((part) => part === "left" || part === "right") ?? "center",
549
+ y: parts.find((part) => part === "top" || part === "bottom") ?? "center",
550
+ exact: false
551
+ };
552
+ }
553
+ /**
554
+ * One axis as a grid point: its keyword; the point nearest a percentage,
555
+ * ties going to the centre; or the start edge for a length, which is an
556
+ * offset from it. `null` when the token is unreadable.
557
+ */
558
+ function backgroundAxisPoint(token, grid) {
559
+ const keyword = grid.find((point) => point === token);
560
+ if (keyword) return {
561
+ point: keyword,
562
+ exact: true
563
+ };
564
+ const percent = PERCENT.exec(token);
565
+ if (percent) {
566
+ const n = Number(percent[1]);
567
+ return {
568
+ point: n < 25 ? grid[0] : n > 75 ? grid[2] : grid[1],
569
+ exact: n === 0 || n === 50 || n === 100
570
+ };
571
+ }
572
+ if (LENGTH.test(token)) return {
573
+ point: grid[0],
574
+ exact: parseFloat(token) === 0
575
+ };
576
+ return null;
577
+ }
578
+ /**
579
+ * `background-repeat`: unrepeated only when every axis is `no-repeat`.
580
+ * Anything but plain `repeat` tiles too, inexactly.
581
+ */
582
+ function backgroundRepeatFlag(raw) {
583
+ const parts = raw.toLowerCase().split(/\s+/);
584
+ if (parts.every((part) => part === "no-repeat")) return {
585
+ repeat: false,
586
+ exact: true
587
+ };
588
+ return {
589
+ repeat: true,
590
+ exact: parts.every((part) => part === "repeat")
591
+ };
592
+ }
593
+ /**
594
+ * An `mj-section` / `mj-wrapper` background image, `null` when
595
+ * `background-url` is absent or blank. `read` returns an attribute's
596
+ * resolved value. Unset attributes take MJML's own defaults, so the import
597
+ * paints what MJML painted; a value with no exact keyword takes the nearest
598
+ * one and adds a note naming it. `label` names the element in each note, and
599
+ * a px `background-size` is measured against `boxWidth`.
600
+ */
601
+ function readMjmlBackgroundImage(read, label, boxWidth) {
602
+ const url = (read("background-url") ?? "").trim();
603
+ if (!url) return null;
604
+ const notes = [];
605
+ const lossy = (name, raw, shown) => {
606
+ notes.push(`${label} ${name} "${raw}" has no exact Templatical equivalent; imported as "${shown}".`);
607
+ };
608
+ let size = MJML_BACKGROUND_DEFAULTS.size;
609
+ const rawSize = read("background-size")?.trim();
610
+ if (rawSize) {
611
+ const parsed = backgroundSizeKeyword(rawSize, boxWidth);
612
+ size = parsed.size;
613
+ if (!parsed.exact) lossy("background-size", rawSize, size);
614
+ }
615
+ let x = MJML_BACKGROUND_DEFAULTS.x;
616
+ let y = MJML_BACKGROUND_DEFAULTS.y;
617
+ let lossyX = false;
618
+ let lossyY = false;
619
+ const rawPosition = read("background-position")?.trim();
620
+ if (rawPosition) {
621
+ const split = splitBackgroundPosition(rawPosition);
622
+ const readX = backgroundAxisPoint(split.x, X_GRID);
623
+ const readY = backgroundAxisPoint(split.y, Y_GRID);
624
+ x = readX?.point ?? x;
625
+ y = readY?.point ?? y;
626
+ lossyX = !split.exact || !readX?.exact;
627
+ lossyY = !split.exact || !readY?.exact;
628
+ }
629
+ const rawX = read("background-position-x")?.trim();
630
+ const overrideX = rawX ? backgroundAxisPoint(rawX.toLowerCase(), X_GRID) : null;
631
+ if (overrideX) {
632
+ x = overrideX.point;
633
+ lossyX = false;
634
+ }
635
+ const rawY = read("background-position-y")?.trim();
636
+ const overrideY = rawY ? backgroundAxisPoint(rawY.toLowerCase(), Y_GRID) : null;
637
+ if (overrideY) {
638
+ y = overrideY.point;
639
+ lossyY = false;
640
+ }
641
+ if (rawPosition && (lossyX || lossyY)) lossy("background-position", rawPosition, `${x} ${y}`);
642
+ if (rawX && !overrideX?.exact) lossy("background-position-x", rawX, x);
643
+ if (rawY && !overrideY?.exact) lossy("background-position-y", rawY, y);
644
+ let repeat = MJML_BACKGROUND_DEFAULTS.repeat;
645
+ const rawRepeat = read("background-repeat")?.trim();
646
+ if (rawRepeat) {
647
+ const parsed = backgroundRepeatFlag(rawRepeat);
648
+ repeat = parsed.repeat;
649
+ if (!parsed.exact) lossy("background-repeat", rawRepeat, repeat ? "repeat" : "no-repeat");
650
+ }
651
+ return {
652
+ image: {
653
+ url,
654
+ size,
655
+ position: {
656
+ x,
657
+ y
658
+ },
659
+ repeat
660
+ },
661
+ notes
662
+ };
663
+ }
473
664
  //#endregion
474
665
  //#region src/block-base.ts
475
666
  /**
@@ -1738,6 +1929,8 @@ function buildSection($el, ctx, entries, wrapper) {
1738
1929
  const notes = exact ? [] : [`Column widths ${shown} have no exact Templatical layout; resolved to "${layout}".`];
1739
1930
  const borderRadius = readBorderRadius(attrs, "Section", notes);
1740
1931
  const border = readBorder(attrs, "Section", notes);
1932
+ const background = readMjmlBackgroundImage((name) => attrs[name], "Section", ctx.containerWidth);
1933
+ if (background) notes.push(...background.notes);
1741
1934
  entries.push({
1742
1935
  sourceTag: "mj-section",
1743
1936
  templaticalBlockType: "section",
@@ -1761,6 +1954,7 @@ function buildSection($el, ctx, entries, wrapper) {
1761
1954
  ...grouped ? { stackOnMobile: false } : {},
1762
1955
  ...borderRadius ? { borderRadius } : {},
1763
1956
  ...border ? { border } : {},
1957
+ ...background ? { backgroundImage: background.image } : {},
1764
1958
  ...wrapper ? { wrapper } : {},
1765
1959
  ...baseFields(attrs, "native")
1766
1960
  })];
@@ -1776,19 +1970,37 @@ const WRAPPER_BORDER_ATTRIBUTES = [
1776
1970
  * `SectionWrapper` has no border, so each `border` or `border-<side>` the
1777
1971
  * wrapper sets pushes a note instead.
1778
1972
  */
1779
- function readWrapper(attrs, notes) {
1973
+ function readWrapper(attrs, notes, boxWidth) {
1780
1974
  const backgroundColor = parseColor(attrs["background-color"]);
1781
1975
  const padding = parsePaddingShorthand(attrs.padding);
1782
1976
  const borderRadius = readBorderRadius(attrs, "Wrapper", notes);
1977
+ const background = readMjmlBackgroundImage((name) => attrs[name], "Wrapper", boxWidth);
1783
1978
  for (const name of WRAPPER_BORDER_ATTRIBUTES) {
1784
1979
  const raw = attrs[name];
1785
1980
  if (raw === void 0) continue;
1786
1981
  notes.push(`Wrapper ${name} "${raw.trim()}" has no Templatical equivalent; dropped.`);
1787
1982
  }
1983
+ if (background) notes.push(...background.notes);
1788
1984
  return {
1789
1985
  ...backgroundColor ? { backgroundColor } : {},
1790
1986
  padding,
1791
- ...borderRadius ? { borderRadius } : {}
1987
+ ...borderRadius ? { borderRadius } : {},
1988
+ ...background ? { backgroundImage: background.image } : {}
1989
+ };
1990
+ }
1991
+ /**
1992
+ * Each section gets its own band object and band image, so editing one
1993
+ * section's image leaves the others alone. `padding` and `borderRadius` stay
1994
+ * shared, as in the other importers.
1995
+ */
1996
+ function copyWrapper(wrapper) {
1997
+ const image = wrapper.backgroundImage;
1998
+ return {
1999
+ ...wrapper,
2000
+ ...image ? { backgroundImage: {
2001
+ ...image,
2002
+ position: { ...image.position }
2003
+ } } : {}
1792
2004
  };
1793
2005
  }
1794
2006
  /**
@@ -1801,7 +2013,7 @@ function readWrapper(attrs, notes) {
1801
2013
  function buildWrapper($el, ctx, entries) {
1802
2014
  const attrs = resolveAttributes($el, ctx.cascade);
1803
2015
  const notes = [];
1804
- const wrapper = readWrapper(attrs, notes);
2016
+ const wrapper = readWrapper(attrs, notes, ctx.containerWidth);
1805
2017
  const sections = childElements($el, ctx.$).filter(($k) => tagOf($k[0]) === "mj-section");
1806
2018
  if (sections.length === 0) {
1807
2019
  entries.push({
@@ -1819,7 +2031,7 @@ function buildWrapper($el, ctx, entries) {
1819
2031
  status: "approximated",
1820
2032
  note: notes.join(" ")
1821
2033
  });
1822
- return sections.flatMap(($section) => buildSection($section, ctx, entries, wrapper));
2034
+ return sections.flatMap(($section) => buildSection($section, ctx, entries, copyWrapper(wrapper)));
1823
2035
  }
1824
2036
  //#endregion
1825
2037
  //#region src/converter.ts
package/dist/index.js.map CHANGED
@@ -1 +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` pass it as a second argument to `getCssClassAttr`, so\n * every rendered title and paragraph carries this class on `css-class`\n * 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\n/**\n * Marks a rendered full-width button — mirrors `BUTTON_FULL_WIDTH_CSS_CLASS`\n * in `packages/renderer/src/renderers/button.ts`. It carries no meaning of its\n * own: `width=\"100%\"` on the same `mj-button` already says the button is\n * full-width.\n */\nconst BUTTON_FULL_WIDTH_CSS_CLASS = \"tpl-button-full\";\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 renderer's other markers (`RICH_TEXT_CSS_CLASS`,\n * `RICH_TEXT_GAP_CLASS`, `BUTTON_FULL_WIDTH_CSS_CLASS`) — this function reads\n * only the two visibility 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 and full-width button markers\n * alongside the two visibility classes, so importing a template the renderer\n * itself produced does not report one of its own classes as foreign. A\n * consumer's own `tpl-`-prefixed class is not one of these markers and is\n * 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 name !== BUTTON_FULL_WIDTH_CSS_CLASS &&\n !RICH_TEXT_GAP_CLASS.test(name),\n );\n}\n","import type {\n BorderRadiusValue,\n BorderSideValue,\n SpacingValue,\n} 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 * 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\nconst PX_LENGTH = /^(\\d+(?:\\.\\d+)?)(?:px)?$/i;\n\n/**\n * Reads a `border-radius` value: one px length, or the two- to four-value\n * shorthand (top-left, top-right, bottom-right, bottom-left), expanded the way\n * CSS expands it. Four matching corners come back as one number, the form the\n * renderer writes them in. `null` when any part is not a non-negative px\n * length (`50%`, `1em`, an elliptical `8px / 4px`), which CSS either reads\n * against the box or discards.\n */\nexport function parseBorderRadius(value: string): BorderRadiusValue | null {\n const parts = value.trim().split(/\\s+/);\n if (parts.length > 4) return null;\n\n const px: number[] = [];\n for (const part of parts) {\n const match = PX_LENGTH.exec(part);\n if (!match) return null;\n px.push(Math.round(parseFloat(match[1])));\n }\n\n const [\n topLeft,\n topRight = topLeft,\n bottomRight = topLeft,\n bottomLeft = topRight,\n ] = px;\n if (\n topRight === topLeft &&\n bottomRight === topLeft &&\n bottomLeft === topLeft\n ) {\n return topLeft;\n }\n return { topLeft, topRight, bottomRight, bottomLeft };\n}\n\nconst DRAWN_STYLES = new Set([\"solid\", \"dashed\", \"dotted\"]);\nconst NARROWED_STYLES = new Set([\n \"double\",\n \"groove\",\n \"ridge\",\n \"inset\",\n \"outset\",\n]);\nconst UNDRAWN_STYLES = new Set([\"none\", \"hidden\"]);\n\n/**\n * Splits a CSS value on whitespace outside parentheses, so `rgb(0, 0, 0)` stays\n * one token. A linear scan rather than a regex, which would backtrack on a long\n * run of spaces.\n */\nfunction splitCssTokens(value: string): string[] {\n const tokens: string[] = [];\n let current = \"\";\n let depth = 0;\n for (const ch of value) {\n if (ch === \"(\") depth++;\n if (ch === \")\") depth = Math.max(0, depth - 1);\n if (depth === 0 && /\\s/.test(ch)) {\n if (current) tokens.push(current);\n current = \"\";\n continue;\n }\n current += ch;\n }\n if (current) tokens.push(current);\n return tokens;\n}\n\n/**\n * Reads one CSS `border` declaration, its width, style and colour in any\n * order, as a `BorderSideValue`.\n *\n * `side` is `null` when CSS draws nothing: style `none` or `hidden`, no style\n * at all (CSS's initial style is `none`), or a `0` width. A style the block\n * model lacks narrows to solid and is named in `narrowedFrom`. A missing or\n * unknown colour is `\"\"`, which the renderer writes back as no colour, so CSS\n * draws `currentColor` both times.\n *\n * Returns `null` for a drawn border it cannot hold: a width that is not px\n * (`2em`, `thin`), no width at all (CSS draws `medium`), or a value with two\n * of any part.\n */\nexport function parseBorderDeclaration(\n value: string,\n): { side: BorderSideValue | null; narrowedFrom?: string } | null {\n let width: number | undefined;\n let style: string | undefined;\n let color: string | undefined;\n\n for (const token of splitCssTokens(value.trim())) {\n const lower = token.toLowerCase();\n const px = PX_LENGTH.exec(lower);\n const isStyle =\n DRAWN_STYLES.has(lower) ||\n NARROWED_STYLES.has(lower) ||\n UNDRAWN_STYLES.has(lower);\n\n if (px) {\n if (width !== undefined) return null;\n width = Math.round(parseFloat(px[1]));\n } else if (isStyle) {\n if (style !== undefined) return null;\n style = lower;\n } else if (/^[-+.\\d]/.test(lower) || /^(thin|medium|thick)$/.test(lower)) {\n return null;\n } else {\n if (color !== undefined) return null;\n color = token;\n }\n }\n\n if (style === undefined || UNDRAWN_STYLES.has(style) || width === 0) {\n return { side: null };\n }\n if (width === undefined) return null;\n\n const side: BorderSideValue = {\n width,\n style: NARROWED_STYLES.has(style)\n ? \"solid\"\n : (style as BorderSideValue[\"style\"]),\n color: parseColor(color),\n };\n return NARROWED_STYLES.has(style) ? { side, narrowedFrom: style } : { side };\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 { BORDER_SIDES, createHtmlBlock } from \"@templatical/types\";\nimport type {\n Block,\n BlockStyles,\n BlockVisibility,\n BorderRadiusValue,\n BorderSide,\n BorderSideValue,\n BorderValue,\n ContentDirection,\n} from \"@templatical/types\";\nimport {\n parseBorderDeclaration,\n parseBorderRadius,\n parseColor,\n parsePaddingShorthand,\n} 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), and what an\n * `mj-divider`'s px width is measured against, less `columnPadding` and the\n * divider's own side padding.\n *\n * `columnPadding` is the side padding (left plus right, px) of the\n * `mj-column` holding the element, 0 at top level. A Templatical column has no\n * padding of its own, so this is read only where it changes what a value\n * means: the width an `mj-divider` line can span.\n *\n * `direction` is the writing direction the imported template resolves to. Its\n * start edge is where a paragraph sits when its markup states no alignment,\n * so an `mj-text` aligned to that edge needs none.\n */\nexport interface ConvertContext {\n $: CheerioAPI;\n cascade: AttributeCascade;\n containerWidth: number;\n columnPadding: number;\n direction: ContentDirection;\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\n/**\n * `border-radius` as a `BorderRadiusValue`, `undefined` when the attribute is\n * absent. A value with no px reading is square corners, `0`, and pushes a\n * note: `label` names the element in it.\n */\nexport function readBorderRadius(\n attrs: Attrs,\n label: string,\n notes: string[],\n): BorderRadiusValue | undefined {\n const raw = attrs[\"border-radius\"];\n if (raw === undefined) return undefined;\n\n const radius = parseBorderRadius(raw);\n if (radius === null) {\n notes.push(\n `${label} border-radius \"${raw.trim()}\" could not be read; imported as square corners.`,\n );\n return 0;\n }\n return radius;\n}\n\n/**\n * What the editor's border control holds for a side with no width\n * (`BorderControl.vue`), so a border drawn on some sides reads back the way\n * the editor would have stored it.\n */\nconst UNDRAWN_SIDE: BorderSideValue = {\n width: 0,\n style: \"solid\",\n color: \"#000000\",\n};\n\n/**\n * `border` and `border-<side>` as a `BorderValue`, `undefined` when no side is\n * drawn. A side takes its own `border-<side>` over `border`, since MJML writes\n * the side after the shorthand. A declaration that a side actually uses pushes\n * a note when its style narrows to solid or it cannot be read, in which case\n * that side is left undrawn.\n */\nexport function readBorder(\n attrs: Attrs,\n label: string,\n notes: string[],\n): BorderValue | undefined {\n const read = new Map<string, BorderSideValue | null>();\n const sides = {} as Record<BorderSide, BorderSideValue | null>;\n\n for (const side of BORDER_SIDES) {\n const name =\n attrs[`border-${side}`] !== undefined\n ? `border-${side}`\n : attrs.border !== undefined\n ? \"border\"\n : null;\n if (name === null) {\n sides[side] = null;\n continue;\n }\n\n if (!read.has(name)) {\n const raw = attrs[name]!.trim();\n const parsed = parseBorderDeclaration(raw);\n if (parsed === null) {\n notes.push(\n `${label} ${name} \"${raw}\" could not be read; imported without it.`,\n );\n } else if (parsed.narrowedFrom) {\n notes.push(\n `${label} ${name} \"${raw}\" uses style ${parsed.narrowedFrom}; imported as solid.`,\n );\n }\n read.set(name, parsed?.side ?? null);\n }\n const parsed = read.get(name)!;\n sides[side] = parsed && { ...parsed };\n }\n\n if (BORDER_SIDES.every((side) => sides[side] === null)) return undefined;\n\n return {\n top: sides.top ?? { ...UNDRAWN_SIDE },\n right: sides.right ?? { ...UNDRAWN_SIDE },\n bottom: sides.bottom ?? { ...UNDRAWN_SIDE },\n left: sides.left ?? { ...UNDRAWN_SIDE },\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 canonicalFontStack,\n createMenuBlock,\n createSocialIconsBlock,\n createTableBlock,\n generateId,\n SOCIAL_ICON_TONES,\n SOCIAL_ICON_STYLES,\n} from \"@templatical/types\";\nimport type {\n MenuItemData,\n SocialIcon,\n SocialIconTone,\n SocialIconSize,\n SocialIconStyle,\n SocialPlatform,\n TableRowData,\n} from \"@templatical/types\";\nimport type { Element } from \"domhandler\";\nimport { parseAlignment, parseColor, parsePxValue } 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\n/**\n * Icon packs (and MJML's `-noshare` variants) append tokens to the platform\n * slug: `facebook-round-outlined.png`, `facebook-noshare`. Those stems are not\n * `SocialPlatform` values, so a lookup that stops at the raw filename maps\n * the icon to `\"website\"` and the platform name is lost.\n */\nconst PLATFORM_PACK_TOKENS = new Set([\n \"noshare\",\n \"round\",\n \"outlined\",\n \"square\",\n \"circle\",\n \"solid\",\n]);\n\nfunction lookupPlatform(cleaned: string): SocialPlatform | null {\n if (PLATFORM_ALIASES[cleaned]) return PLATFORM_ALIASES[cleaned];\n return cleaned in KNOWN_PLATFORMS ? (cleaned as SocialPlatform) : null;\n}\n\nfunction normalizePlatform(raw: string): SocialPlatform | null {\n const cleaned = raw.trim().toLowerCase();\n if (!cleaned) return null;\n\n const direct = lookupPlatform(cleaned);\n if (direct) return direct;\n\n const parts = cleaned.split(\"-\");\n while (\n parts.length > 1 &&\n PLATFORM_PACK_TOKENS.has(parts[parts.length - 1])\n ) {\n parts.pop();\n const candidate = parts.join(\"-\");\n const hit = lookupPlatform(candidate);\n if (hit) return hit;\n }\n return null;\n}\n\n/** The `<dir>/<platform>.png` tail of the URL `renderers/social.ts:79` 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\n/**\n * The style and tone in an icon folder the renderer names: `circle` for a\n * brand-colored circle, `circle-dark` for a dark one (`socialIconAssetDir`).\n */\nfunction iconDirParts(\n dir: string,\n): { style: SocialIconStyle; tone?: SocialIconTone } | null {\n const [style, tone, ...rest] = dir.split(\"-\");\n if (rest.length > 0 || !SOCIAL_ICON_STYLES.includes(style as SocialIconStyle))\n return null;\n if (tone === undefined) return { style: style as SocialIconStyle };\n return SOCIAL_ICON_TONES.includes(tone as SocialIconTone) && tone !== \"brand\"\n ? { style: style as SocialIconStyle, tone: tone as SocialIconTone }\n : null;\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 iconTone: SocialIconTone | undefined;\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 name = (childAttrs.name ?? \"\").trim();\n const alt = (childAttrs.alt ?? \"\").trim();\n const platform =\n normalizePlatform(name) ??\n normalizePlatform(fromSrc.platform) ??\n normalizePlatform(alt);\n if (!platform) {\n const rawName = name || fromSrc.platform || alt;\n if (rawName) {\n notes.push(\n `Unrecognised social platform \"${rawName}\" mapped to \"website\".`,\n );\n }\n }\n\n icons.push({\n id: generateId(),\n platform: platform ?? \"website\",\n url: (childAttrs.href ?? \"\").trim(),\n });\n\n const fromDir = iconStyle ? null : iconDirParts(fromSrc.style);\n if (fromDir) {\n iconStyle = fromDir.style;\n iconTone = fromDir.tone;\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:82).\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 ...(iconTone ? { iconTone } : {}),\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 = canonicalFontStack(\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 = canonicalFontStack(\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, CheerioAPI } from \"cheerio\";\nimport { isTag, isText } from \"domhandler\";\nimport type { AnyNode, Element } from \"domhandler\";\nimport {\n canonicalFontStack,\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 { parseAlignment, parseColor, parsePxValue } 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 is reparsed only when an attribute has to be carried\n * into it (`styleParagraphs`); otherwise it passes through verbatim.\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 = canonicalFontStack(\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 = canonicalFontStack(\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 *\n * `.children()` is elements only. A non-whitespace text-node sibling must\n * veto: an `mj-text` of prose plus a trailing `<a>` becomes a menu labelled\n * from the links and the prose is discarded. Whitespace-only nodes (newlines\n * between `<a>`/`<span>`) must not.\n */\nfunction looksLikeMenu(html: string): boolean {\n const $inner = parseInner(html);\n const kids = $inner(\"body\").contents().toArray();\n if (kids.length === 0) return false;\n\n let anchors = 0;\n for (const kid of kids) {\n if (isText(kid)) {\n if (kid.data.trim() !== \"\") return false;\n continue;\n }\n if (!isTag(kid)) continue;\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 = canonicalFontStack(\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\n/**\n * The size the renderer gives every `mj-text` (`<mj-text font-size=\"14px\">` in\n * `packages/renderer/src/index.ts`), so a paragraph at this size needs no span\n * to carry it.\n */\nconst RENDERER_TEXT_SIZE = 14;\n\nconst PARAGRAPH_ALIGNMENTS = new Set([\"left\", \"center\", \"right\", \"justify\"]);\n\n/** `<li>` children that end a run of the item's text instead of joining it. */\nconst LIST_ITEM_BLOCK_TAGS = new Set([\n \"p\",\n \"div\",\n \"ul\",\n \"ol\",\n \"blockquote\",\n \"table\",\n \"pre\",\n \"hr\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n]);\n\n/**\n * What an `mj-text` states about its paragraphs' alignment, colour and size.\n * `ParagraphBlock` has a field for none of them, so they travel in the markup.\n * An empty string states nothing.\n */\ninterface ParagraphStyle {\n textAlign: string;\n color: string;\n fontSize: string;\n}\n\nfunction readParagraphStyle(attrs: Attrs, ctx: ConvertContext): ParagraphStyle {\n // The start edge is where a paragraph sits with no alignment of its own, and\n // the renderer states it on every RTL paragraph (`renderers/paragraph.ts`).\n const align = (attrs.align ?? \"\").trim().toLowerCase();\n const startEdge = ctx.direction === \"rtl\" ? \"right\" : \"left\";\n\n // The resolved size, not ownAttr: a size set document-wide has no setting to\n // land in, so dropping it as ambient would lose it.\n const size = parsePxValue(attrs[\"font-size\"]);\n\n return {\n textAlign:\n PARAGRAPH_ALIGNMENTS.has(align) && align !== startEdge ? align : \"\",\n // A colour set document-wide is `settings.textColor` already.\n color: parseColor(ownAttr(attrs, \"color\", \"mj-text\", ctx.cascade)),\n fontSize: size > 0 && size !== RENDERER_TEXT_SIZE ? `${size}px` : \"\",\n };\n}\n\n/**\n * Put each run of an `<li>`'s inline content in a `<p>`, so that the\n * alignment has a paragraph to sit on.\n */\nfunction wrapListItemText($inner: CheerioAPI, li: Element): void {\n let run: AnyNode[] = [];\n const flush = () => {\n // wrapAll, not before() and append(): before() skips a text node, which\n // is what a run usually starts with, and the text would be lost.\n if (run.some((node) => isTag(node) || (isText(node) && node.data.trim()))) {\n $inner(run).wrapAll(\"<p></p>\");\n }\n run = [];\n };\n\n for (const child of [...li.children]) {\n if (isTag(child) && LIST_ITEM_BLOCK_TAGS.has(child.tagName.toLowerCase())) {\n flush();\n } else {\n run.push(child);\n }\n }\n flush();\n}\n\n/**\n * Align one `<p>` and wrap its content in a span carrying colour and size.\n * The `<p>`'s own `text-align` stays, and its own colour and size move into\n * the span, where they win over the attributes as they did on the `<p>`.\n */\nfunction styleParagraph(\n $inner: CheerioAPI,\n $p: Cheerio<Element>,\n style: ParagraphStyle,\n): void {\n if (style.textAlign && !$p.css(\"text-align\")) {\n $p.css(\"text-align\", style.textAlign);\n }\n\n const color = $p.css(\"color\") || style.color;\n const fontSize = $p.css(\"font-size\") || style.fontSize;\n if ((color || fontSize) && $p.contents().length > 0) {\n $p.css({ color: \"\", \"font-size\": \"\" });\n const $span = $inner(\"<span></span>\").css({\n ...(color ? { color } : {}),\n ...(fontSize ? { \"font-size\": fontSize } : {}),\n });\n $p.wrapInner($span);\n }\n\n if ($p.attr(\"style\") === \"\") $p.removeAttr(\"style\");\n}\n\n/**\n * Carry an `mj-text`'s alignment, colour and size into its paragraphs'\n * markup: `text-align` on each `<p>`, colour and size on one span inside it.\n * That is the shape the paragraph editor keeps through an edit\n * (`packages/editor/src/components/blocks/ParagraphEditor.vue` aligns\n * paragraphs and styles spans); a wrapper element around the paragraphs would\n * be dropped on the first edit.\n *\n * Markup with nothing to apply is returned as it came, unparsed.\n */\nfunction styleParagraphs(html: string, style: ParagraphStyle): string {\n if (!style.textAlign && !style.color && !style.fontSize) return html;\n\n const $inner = parseInner(html);\n $inner(\"li\").each((_, li) => wrapListItemText($inner, li));\n $inner(\"p\").each((_, p) => styleParagraph($inner, $inner(p), style));\n return $inner(\"body\").html() ?? html;\n}\n\nfunction convertParagraph(\n html: string,\n attrs: Attrs,\n ctx: ConvertContext,\n): 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 const gap = readParagraphGap(attrs);\n const paragraphSpacing =\n gap !== null && gap !== RICH_TEXT_SPACING.paragraphGap ? gap : undefined;\n\n const block = createParagraphBlock({\n content: styleParagraphs(\n ensureParagraphWrapped(html),\n readParagraphStyle(attrs, ctx),\n ),\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, ctx);\n}\n","import type { Cheerio } from \"cheerio\";\nimport type { Element } from \"domhandler\";\nimport {\n canonicalFontStack,\n createButtonBlock,\n createDividerBlock,\n createHtmlBlock,\n createImageBlock,\n createSpacerBlock,\n} from \"@templatical/types\";\nimport type { Block, ButtonBlock, DividerBlock } from \"@templatical/types\";\nimport {\n parseAlignment,\n parseBorderStyle,\n parseColor,\n parsePaddingShorthand,\n parsePxValue,\n} from \"./attribute-parser\";\nimport {\n ownAttr,\n resolveAttributes,\n tagOf,\n type Attrs,\n} from \"./attribute-resolver\";\nimport {\n baseFields,\n convertHtmlFallback,\n isNewTab,\n readBorder,\n readBorderRadius,\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): Converted | 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 href = (attrs.href ?? \"\").trim();\n\n const notes: string[] = [];\n const borderRadius = readBorderRadius(attrs, \"Image\", notes);\n const border = readBorder(attrs, \"Image\", notes);\n\n return {\n block: 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 ? { borderRadius } : {}),\n ...(border ? { border } : {}),\n ...(href ? { linkUrl: href } : {}),\n ...(href && isNewTab(attrs) ? { linkOpenInNewTab: true } : {}),\n ...(decorative ? { decorative: true } : {}),\n ...baseFields(attrs),\n }),\n entry: {\n sourceTag: \"mj-image\",\n templaticalBlockType: \"image\",\n status: notes.length > 0 ? \"approximated\" : \"converted\",\n ...(notes.length > 0 ? { note: notes.join(\" \") } : {}),\n },\n };\n}\n\n/**\n * `mj-button`'s `width` as `ButtonBlock.width`, where no width is a button\n * that fits its label. `100%` is `\"full\"` and a px width stays px, as the\n * renderer writes them. Any other percentage becomes px of `fillWidth`, the\n * box MJML sizes it against, since the block holds no percentage. A width\n * with no size fits the label, which is what MJML draws for it. Anything but\n * `100%` or a positive px width pushes a note.\n */\nfunction readButtonWidth(\n value: string | undefined,\n fillWidth: number,\n notes: string[],\n): ButtonBlock[\"width\"] {\n const raw = (value ?? \"\").trim();\n if (!raw) return undefined;\n\n const match = /^(-?\\d+(?:\\.\\d+)?)\\s*(px|%)?$/i.exec(raw);\n if (!match) {\n notes.push(\n `Button width \"${raw}\" could not be read; imported as fit to content.`,\n );\n return undefined;\n }\n const parsed = parseFloat(match[1]);\n const isPercent = match[2] === \"%\";\n\n if (isPercent && parsed >= 100) {\n if (parsed > 100) {\n notes.push(`Button width ${raw} was clamped to 100%.`);\n }\n return \"full\";\n }\n\n const px = Math.round(isPercent ? (parsed / 100) * fillWidth : parsed);\n if (px <= 0) {\n notes.push(\n `Button width ${raw} has no usable size; imported as fit to content.`,\n );\n return undefined;\n }\n if (isPercent) {\n notes.push(\n `Button width ${raw} was imported as ${px}px, which does not scale with its column.`,\n );\n }\n return px;\n}\n\n/**\n * A percentage `width` sizes the button against its column less the column's\n * side padding and its own, the same box an `mj-divider` line spans.\n *\n * `font-family` is read only when the button sets its own: the renderer\n * writes the document font onto `mj-all`, which reaches every button.\n */\nfunction convertButton(\n $el: Cheerio<Element>,\n attrs: Attrs,\n ctx: ConvertContext,\n): Converted | 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 const fontFamily = canonicalFontStack(\n ownAttr(attrs, \"font-family\", \"mj-button\", ctx.cascade),\n );\n const base = baseFields(attrs);\n const { left, right } = base.styles.padding;\n\n const notes: string[] = [];\n const width = readButtonWidth(\n attrs.width,\n ctx.containerWidth - ctx.columnPadding - left - right,\n notes,\n );\n const borderRadius = readBorderRadius(attrs, \"Button\", notes);\n const border = readBorder(attrs, \"Button\", notes);\n\n return {\n block: createButtonBlock({\n text,\n url: (attrs.href ?? \"\").trim(),\n ...(backgroundColor ? { backgroundColor } : {}),\n ...(textColor ? { textColor } : {}),\n ...(fontSize > 0 ? { fontSize } : {}),\n ...(fontFamily ? { fontFamily } : {}),\n ...(borderRadius !== undefined ? { borderRadius } : {}),\n ...(border ? { border } : {}),\n ...(attrs[\"inner-padding\"] !== undefined\n ? { buttonPadding: parsePaddingShorthand(attrs[\"inner-padding\"]) }\n : {}),\n ...(width !== undefined ? { width } : {}),\n align: parseAlignment(attrs.align, \"center\"),\n ...(isNewTab(attrs) ? { openInNewTab: true } : {}),\n ...base,\n }),\n entry: {\n sourceTag: \"mj-button\",\n templaticalBlockType: \"button\",\n status: notes.length > 0 ? \"approximated\" : \"converted\",\n ...(notes.length > 0 ? { note: notes.join(\" \") } : {}),\n },\n };\n}\n\n/**\n * `mj-divider`'s `width` as `DividerBlock.width`. A percentage is a share of\n * the column and stays one, to two decimals within 0–100%. `100%`, and a px\n * width that reaches `lineWidth`, span the line's whole box, which is\n * `\"full\"`. A clamped or unreadable value pushes a note.\n *\n * It reads a sign, which `parsePercent` does not, so a negative value is\n * clamped to 0 and reported instead of being dropped.\n */\nfunction readDividerWidth(\n value: string | undefined,\n lineWidth: number,\n notes: string[],\n): DividerBlock[\"width\"] {\n const raw = (value ?? \"\").trim();\n if (!raw) return \"full\";\n\n const match = /^(-?\\d+(?:\\.\\d+)?)\\s*(px|%)?$/i.exec(raw);\n if (!match) {\n notes.push(\n `Divider width \"${raw}\" could not be read; imported as full width.`,\n );\n return \"full\";\n }\n const parsed = parseFloat(match[1]);\n\n if (match[2] === \"%\") {\n const percent = Math.min(100, Math.max(0, parsed));\n if (percent !== parsed) {\n notes.push(`Divider width ${raw} was clamped to ${percent}%.`);\n }\n // Two decimals keep the value inside `DividerPercentWidth`'s pattern,\n // which a float printed in exponent form would leave.\n const share = Math.round(percent * 100) / 100;\n return share === 100 ? \"full\" : `${share}%`;\n }\n\n const px = Math.max(0, Math.round(parsed));\n if (px !== Math.round(parsed)) {\n notes.push(`Divider width ${raw} was clamped to 0px.`);\n }\n return px >= lineWidth ? \"full\" : px;\n}\n\n/**\n * `mj-divider` draws `100%` across its column less the column's side padding\n * and its own, so that is the width a px value is measured against. It places\n * a partial-width line by `align`, centred by default; Templatical centres\n * every divider, so a left- or right-aligned partial one comes back with a\n * note.\n */\nfunction convertDivider(attrs: Attrs, ctx: ConvertContext): Converted {\n const color = parseColor(attrs[\"border-color\"]);\n const thickness = parsePxValue(attrs[\"border-width\"]);\n const base = baseFields(attrs);\n const { left, right } = base.styles.padding;\n\n const notes: string[] = [];\n const width = readDividerWidth(\n attrs.width,\n ctx.containerWidth - ctx.columnPadding - left - right,\n notes,\n );\n\n const align = (attrs.align ?? \"\").trim().toLowerCase();\n if (width !== \"full\" && (align === \"left\" || align === \"right\")) {\n notes.push(\n `MJML aligns this divider ${align}; Templatical centres every divider.`,\n );\n }\n\n return {\n block: createDividerBlock({\n lineStyle: parseBorderStyle(attrs[\"border-style\"]),\n width,\n ...(color ? { color } : {}),\n ...(attrs[\"border-width\"] !== undefined ? { thickness } : {}),\n ...base,\n }),\n entry: {\n sourceTag: \"mj-divider\",\n templaticalBlockType: \"divider\",\n status: notes.length > 0 ? \"approximated\" : \"converted\",\n ...(notes.length > 0 ? { note: notes.join(\" \") } : {}),\n },\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 return convertImage($el, attrs, ctx);\n }\n\n if (tag === \"mj-button\") {\n return convertButton($el, attrs, ctx);\n }\n\n if (tag === \"mj-divider\") {\n return convertDivider(attrs, ctx);\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 {\n canonicalFontStack,\n DEFAULT_TEMPLATE_DEFAULTS,\n resolveContentDirection,\n} from \"@templatical/types\";\nimport type {\n ContentDirection,\n TemplateContent,\n TemplateSettings,\n} from \"@templatical/types\";\nimport { parseColor, 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 * `<mjml dir>` as `settings.direction`, kept only when it disagrees with the\n * direction the locale implies. The renderer always writes `dir`, resolved\n * from the locale when a template states no direction, so keeping a value that\n * agrees would add a key the source template never set. `auto`, MJML's\n * default, states no direction.\n */\nfunction readDirection(\n value: string | undefined,\n locale: string,\n): ContentDirection | undefined {\n const dir = (value ?? \"\").trim().toLowerCase();\n if (dir !== \"ltr\" && dir !== \"rtl\") return undefined;\n return dir === resolveContentDirection({ locale }) ? undefined : dir;\n}\n\n/**\n * Build `TemplateSettings` from the `mjml` and `mj-body` attributes, the\n * attribute cascade and the remaining `mj-head` children.\n *\n * Optional keys (`preheaderText`, `linkColor`, `direction`) are **omitted**\n * rather than set to `undefined`: an absent key is what the block model means\n * by unset, and a 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 canonicalFontStack(cascade.all[\"font-family\"]) ||\n canonicalFontStack(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: every other rule it\n // emits is scoped under a class (`.tpl-rich-text-8 p`, `.tpl-button-full a`),\n // so the one bare `a { … }` rule present is the document-level rule this\n // 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 const direction = readDirection($root.attr(\"dir\"), 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 ...(direction ? { direction } : {}),\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} 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 readBorder,\n readBorderRadius,\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 columnAttrs = columns.map(($c) => resolveAttributes($c, ctx.cascade));\n const rawWidths = columnAttrs.map((column) => column.width);\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 const notes: string[] = exact\n ? []\n : [\n `Column widths ${shown} have no exact Templatical layout; resolved to \"${layout}\".`,\n ];\n const borderRadius = readBorderRadius(attrs, \"Section\", notes);\n const border = readBorder(attrs, \"Section\", notes);\n entries.push({\n sourceTag: \"mj-section\",\n templaticalBlockType: \"section\",\n status: notes.length > 0 ? \"approximated\" : \"converted\",\n ...(notes.length > 0 ? { note: notes.join(\" \") } : {}),\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 { left, right } = parsePaddingShorthand(columnAttrs[index].padding);\n const columnCtx: ConvertContext = {\n ...ctx,\n containerWidth: Math.round(pixels[slot] ?? ctx.containerWidth),\n columnPadding: left + right,\n };\n children[slot].push(...convertColumnChildren($column, columnCtx, entries));\n });\n\n const section = createSectionBlock({\n columns: layout,\n children,\n ...(grouped ? { stackOnMobile: false } : {}),\n ...(borderRadius ? { borderRadius } : {}),\n ...(border ? { border } : {}),\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\nconst WRAPPER_BORDER_ATTRIBUTES = [\n \"border\",\n \"border-top\",\n \"border-right\",\n \"border-bottom\",\n \"border-left\",\n];\n\n/**\n * `SectionWrapper` has no border, so each `border` or `border-<side>` the\n * wrapper sets pushes a note instead.\n */\nfunction readWrapper(attrs: Attrs, notes: string[]): SectionWrapper {\n const backgroundColor = parseColor(attrs[\"background-color\"]);\n const padding: SpacingValue = parsePaddingShorthand(attrs.padding);\n const borderRadius = readBorderRadius(attrs, \"Wrapper\", notes);\n\n for (const name of WRAPPER_BORDER_ATTRIBUTES) {\n const raw = attrs[name];\n if (raw === undefined) continue;\n notes.push(\n `Wrapper ${name} \"${raw.trim()}\" has no Templatical equivalent; dropped.`,\n );\n }\n\n return {\n ...(backgroundColor ? { backgroundColor } : {}),\n padding,\n ...(borderRadius ? { 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 notes: string[] = [];\n const wrapper = readWrapper(attrs, notes);\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 notes.unshift(\n `An <mj-wrapper> holding ${sections.length} sections was applied to each of them — Templatical has no multi-section band.`,\n );\n }\n\n // A wrapper is not a block of its own, so it gets an entry only when it has\n // something to report.\n if (notes.length > 0) {\n entries.push({\n sourceTag: \"mj-wrapper\",\n templaticalBlockType: \"section\",\n status: \"approximated\",\n note: notes.join(\" \"),\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 resolveContentDirection,\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 columnPadding: 0,\n direction: resolveContentDirection(settings),\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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"}
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` pass it as a second argument to `getCssClassAttr`, so\n * every rendered title and paragraph carries this class on `css-class`\n * 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\n/**\n * Marks a rendered full-width button — mirrors `BUTTON_FULL_WIDTH_CSS_CLASS`\n * in `packages/renderer/src/renderers/button.ts`. It carries no meaning of its\n * own: `width=\"100%\"` on the same `mj-button` already says the button is\n * full-width.\n */\nconst BUTTON_FULL_WIDTH_CSS_CLASS = \"tpl-button-full\";\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 renderer's other markers (`RICH_TEXT_CSS_CLASS`,\n * `RICH_TEXT_GAP_CLASS`, `BUTTON_FULL_WIDTH_CSS_CLASS`) — this function reads\n * only the two visibility 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 and full-width button markers\n * alongside the two visibility classes, so importing a template the renderer\n * itself produced does not report one of its own classes as foreign. A\n * consumer's own `tpl-`-prefixed class is not one of these markers and is\n * 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 name !== BUTTON_FULL_WIDTH_CSS_CLASS &&\n !RICH_TEXT_GAP_CLASS.test(name),\n );\n}\n","import type {\n BackgroundImage,\n BackgroundPositionX,\n BackgroundPositionY,\n BackgroundSize,\n BorderRadiusValue,\n BorderSideValue,\n SpacingValue,\n} 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 * 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\nconst PX_LENGTH = /^(\\d+(?:\\.\\d+)?)(?:px)?$/i;\n\n/**\n * Reads a `border-radius` value: one px length, or the two- to four-value\n * shorthand (top-left, top-right, bottom-right, bottom-left), expanded the way\n * CSS expands it. Four matching corners come back as one number, the form the\n * renderer writes them in. `null` when any part is not a non-negative px\n * length (`50%`, `1em`, an elliptical `8px / 4px`), which CSS either reads\n * against the box or discards.\n */\nexport function parseBorderRadius(value: string): BorderRadiusValue | null {\n const parts = value.trim().split(/\\s+/);\n if (parts.length > 4) return null;\n\n const px: number[] = [];\n for (const part of parts) {\n const match = PX_LENGTH.exec(part);\n if (!match) return null;\n px.push(Math.round(parseFloat(match[1])));\n }\n\n const [\n topLeft,\n topRight = topLeft,\n bottomRight = topLeft,\n bottomLeft = topRight,\n ] = px;\n if (\n topRight === topLeft &&\n bottomRight === topLeft &&\n bottomLeft === topLeft\n ) {\n return topLeft;\n }\n return { topLeft, topRight, bottomRight, bottomLeft };\n}\n\nconst DRAWN_STYLES = new Set([\"solid\", \"dashed\", \"dotted\"]);\nconst NARROWED_STYLES = new Set([\n \"double\",\n \"groove\",\n \"ridge\",\n \"inset\",\n \"outset\",\n]);\nconst UNDRAWN_STYLES = new Set([\"none\", \"hidden\"]);\n\n/**\n * Splits a CSS value on whitespace outside parentheses, so `rgb(0, 0, 0)` stays\n * one token. A linear scan rather than a regex, which would backtrack on a long\n * run of spaces.\n */\nfunction splitCssTokens(value: string): string[] {\n const tokens: string[] = [];\n let current = \"\";\n let depth = 0;\n for (const ch of value) {\n if (ch === \"(\") depth++;\n if (ch === \")\") depth = Math.max(0, depth - 1);\n if (depth === 0 && /\\s/.test(ch)) {\n if (current) tokens.push(current);\n current = \"\";\n continue;\n }\n current += ch;\n }\n if (current) tokens.push(current);\n return tokens;\n}\n\n/**\n * Reads one CSS `border` declaration, its width, style and colour in any\n * order, as a `BorderSideValue`.\n *\n * `side` is `null` when CSS draws nothing: style `none` or `hidden`, no style\n * at all (CSS's initial style is `none`), or a `0` width. A style the block\n * model lacks narrows to solid and is named in `narrowedFrom`. A missing or\n * unknown colour is `\"\"`, which the renderer writes back as no colour, so CSS\n * draws `currentColor` both times.\n *\n * Returns `null` for a drawn border it cannot hold: a width that is not px\n * (`2em`, `thin`), no width at all (CSS draws `medium`), or a value with two\n * of any part.\n */\nexport function parseBorderDeclaration(\n value: string,\n): { side: BorderSideValue | null; narrowedFrom?: string } | null {\n let width: number | undefined;\n let style: string | undefined;\n let color: string | undefined;\n\n for (const token of splitCssTokens(value.trim())) {\n const lower = token.toLowerCase();\n const px = PX_LENGTH.exec(lower);\n const isStyle =\n DRAWN_STYLES.has(lower) ||\n NARROWED_STYLES.has(lower) ||\n UNDRAWN_STYLES.has(lower);\n\n if (px) {\n if (width !== undefined) return null;\n width = Math.round(parseFloat(px[1]));\n } else if (isStyle) {\n if (style !== undefined) return null;\n style = lower;\n } else if (/^[-+.\\d]/.test(lower) || /^(thin|medium|thick)$/.test(lower)) {\n return null;\n } else {\n if (color !== undefined) return null;\n color = token;\n }\n }\n\n if (style === undefined || UNDRAWN_STYLES.has(style) || width === 0) {\n return { side: null };\n }\n if (width === undefined) return null;\n\n const side: BorderSideValue = {\n width,\n style: NARROWED_STYLES.has(style)\n ? \"solid\"\n : (style as BorderSideValue[\"style\"]),\n color: parseColor(color),\n };\n return NARROWED_STYLES.has(style) ? { side, narrowedFrom: style } : { side };\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\n/** A background image read from the source, with a note for each value that had no exact keyword. */\nexport interface ReadBackgroundImage {\n image: BackgroundImage;\n notes: string[];\n}\n\nconst X_GRID = [\"left\", \"center\", \"right\"] as const;\nconst Y_GRID = [\"top\", \"center\", \"bottom\"] as const;\nconst PERCENT = /^(-?(?:\\d+(?:\\.\\d+)?|\\.\\d+))%$/;\nconst LENGTH = /^-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)[a-z]*$/;\n\n/** What MJML paints for a `background-*` attribute an `mj-section` / `mj-wrapper` leaves out. */\nconst MJML_BACKGROUND_DEFAULTS = {\n size: \"auto\",\n x: \"center\",\n y: \"top\",\n repeat: true,\n} as const;\n\n/**\n * `background-size` as a keyword. A size with a side at 100%, or a px width\n * spanning the box, fills it as `cover` does; any other length or percentage\n * draws the image at a size of its own, which `auto` is closest to.\n */\nfunction backgroundSizeKeyword(\n raw: string,\n boxWidth: number | undefined,\n): { size: BackgroundSize; exact: boolean } {\n const parts = raw.toLowerCase().split(/\\s+/);\n const [first] = parts;\n if (parts.length === 1 && (first === \"cover\" || first === \"contain\")) {\n return { size: first, exact: true };\n }\n if (parts.every((part) => part === \"auto\")) {\n return { size: \"auto\", exact: true };\n }\n const px = /^(\\d+(?:\\.\\d+)?|\\.\\d+)px$/.exec(first);\n const fills =\n parts.includes(\"100%\") ||\n (px !== null && boxWidth !== undefined && Number(px[1]) >= boxWidth);\n return { size: fills ? \"cover\" : \"auto\", exact: false };\n}\n\n/**\n * `background-position` split into x and y the way MJML and CSS read it: a\n * lone `top` / `bottom` names y and centres x, any other lone value is x\n * with y centred, and a pair is x then y unless it leads with `top` /\n * `bottom` (or with `center` before `left` / `right`). Three- and four-value\n * edge offsets keep their edge keywords only.\n */\nfunction splitBackgroundPosition(raw: string): {\n x: string;\n y: string;\n exact: boolean;\n} {\n const parts = raw.toLowerCase().split(/\\s+/);\n if (parts.length === 1) {\n const [only] = parts;\n return only === \"top\" || only === \"bottom\"\n ? { x: \"center\", y: only, exact: true }\n : { x: only, y: \"center\", exact: true };\n }\n if (parts.length === 2) {\n const [a, b] = parts;\n const yFirst =\n a === \"top\" ||\n a === \"bottom\" ||\n (a === \"center\" && (b === \"left\" || b === \"right\"));\n return yFirst ? { x: b, y: a, exact: true } : { x: a, y: b, exact: true };\n }\n return {\n x: parts.find((part) => part === \"left\" || part === \"right\") ?? \"center\",\n y: parts.find((part) => part === \"top\" || part === \"bottom\") ?? \"center\",\n exact: false,\n };\n}\n\n/**\n * One axis as a grid point: its keyword; the point nearest a percentage,\n * ties going to the centre; or the start edge for a length, which is an\n * offset from it. `null` when the token is unreadable.\n */\nfunction backgroundAxisPoint<T extends string>(\n token: string,\n grid: readonly [T, T, T],\n): { point: T; exact: boolean } | null {\n const keyword = grid.find((point) => point === token);\n if (keyword) return { point: keyword, exact: true };\n const percent = PERCENT.exec(token);\n if (percent) {\n const n = Number(percent[1]);\n return {\n point: n < 25 ? grid[0] : n > 75 ? grid[2] : grid[1],\n exact: n === 0 || n === 50 || n === 100,\n };\n }\n if (LENGTH.test(token)) {\n return { point: grid[0], exact: parseFloat(token) === 0 };\n }\n return null;\n}\n\n/**\n * `background-repeat`: unrepeated only when every axis is `no-repeat`.\n * Anything but plain `repeat` tiles too, inexactly.\n */\nfunction backgroundRepeatFlag(raw: string): {\n repeat: boolean;\n exact: boolean;\n} {\n const parts = raw.toLowerCase().split(/\\s+/);\n if (parts.every((part) => part === \"no-repeat\")) {\n return { repeat: false, exact: true };\n }\n return { repeat: true, exact: parts.every((part) => part === \"repeat\") };\n}\n\n/**\n * An `mj-section` / `mj-wrapper` background image, `null` when\n * `background-url` is absent or blank. `read` returns an attribute's\n * resolved value. Unset attributes take MJML's own defaults, so the import\n * paints what MJML painted; a value with no exact keyword takes the nearest\n * one and adds a note naming it. `label` names the element in each note, and\n * a px `background-size` is measured against `boxWidth`.\n */\nexport function readMjmlBackgroundImage(\n read: (name: string) => string | undefined,\n label: string,\n boxWidth: number | undefined,\n): ReadBackgroundImage | null {\n const url = (read(\"background-url\") ?? \"\").trim();\n if (!url) return null;\n\n const notes: string[] = [];\n const lossy = (name: string, raw: string, shown: string): void => {\n notes.push(\n `${label} ${name} \"${raw}\" has no exact Templatical equivalent; imported as \"${shown}\".`,\n );\n };\n\n let size: BackgroundSize = MJML_BACKGROUND_DEFAULTS.size;\n const rawSize = read(\"background-size\")?.trim();\n if (rawSize) {\n const parsed = backgroundSizeKeyword(rawSize, boxWidth);\n size = parsed.size;\n if (!parsed.exact) lossy(\"background-size\", rawSize, size);\n }\n\n let x: BackgroundPositionX = MJML_BACKGROUND_DEFAULTS.x;\n let y: BackgroundPositionY = MJML_BACKGROUND_DEFAULTS.y;\n let lossyX = false;\n let lossyY = false;\n const rawPosition = read(\"background-position\")?.trim();\n if (rawPosition) {\n const split = splitBackgroundPosition(rawPosition);\n const readX = backgroundAxisPoint(split.x, X_GRID);\n const readY = backgroundAxisPoint(split.y, Y_GRID);\n x = readX?.point ?? x;\n y = readY?.point ?? y;\n lossyX = !split.exact || !readX?.exact;\n lossyY = !split.exact || !readY?.exact;\n }\n // A readable `-x` / `-y` replaces its axis, and with it whatever the\n // position lost there; the position's note then names what was imported.\n const rawX = read(\"background-position-x\")?.trim();\n const overrideX = rawX\n ? backgroundAxisPoint(rawX.toLowerCase(), X_GRID)\n : null;\n if (overrideX) {\n x = overrideX.point;\n lossyX = false;\n }\n const rawY = read(\"background-position-y\")?.trim();\n const overrideY = rawY\n ? backgroundAxisPoint(rawY.toLowerCase(), Y_GRID)\n : null;\n if (overrideY) {\n y = overrideY.point;\n lossyY = false;\n }\n if (rawPosition && (lossyX || lossyY)) {\n lossy(\"background-position\", rawPosition, `${x} ${y}`);\n }\n if (rawX && !overrideX?.exact) lossy(\"background-position-x\", rawX, x);\n if (rawY && !overrideY?.exact) lossy(\"background-position-y\", rawY, y);\n\n let repeat: boolean = MJML_BACKGROUND_DEFAULTS.repeat;\n const rawRepeat = read(\"background-repeat\")?.trim();\n if (rawRepeat) {\n const parsed = backgroundRepeatFlag(rawRepeat);\n repeat = parsed.repeat;\n if (!parsed.exact) {\n lossy(\"background-repeat\", rawRepeat, repeat ? \"repeat\" : \"no-repeat\");\n }\n }\n\n return { image: { url, size, position: { x, y }, repeat }, notes };\n}\n","import type { Cheerio, CheerioAPI } from \"cheerio\";\nimport type { Element } from \"domhandler\";\nimport { BORDER_SIDES, createHtmlBlock } from \"@templatical/types\";\nimport type {\n Block,\n BlockStyles,\n BlockVisibility,\n BorderRadiusValue,\n BorderSide,\n BorderSideValue,\n BorderValue,\n ContentDirection,\n} from \"@templatical/types\";\nimport {\n parseBorderDeclaration,\n parseBorderRadius,\n parseColor,\n parsePaddingShorthand,\n} 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), and what an\n * `mj-divider`'s px width is measured against, less `columnPadding` and the\n * divider's own side padding.\n *\n * `columnPadding` is the side padding (left plus right, px) of the\n * `mj-column` holding the element, 0 at top level. A Templatical column has no\n * padding of its own, so this is read only where it changes what a value\n * means: the width an `mj-divider` line can span.\n *\n * `direction` is the writing direction the imported template resolves to. Its\n * start edge is where a paragraph sits when its markup states no alignment,\n * so an `mj-text` aligned to that edge needs none.\n */\nexport interface ConvertContext {\n $: CheerioAPI;\n cascade: AttributeCascade;\n containerWidth: number;\n columnPadding: number;\n direction: ContentDirection;\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\n/**\n * `border-radius` as a `BorderRadiusValue`, `undefined` when the attribute is\n * absent. A value with no px reading is square corners, `0`, and pushes a\n * note: `label` names the element in it.\n */\nexport function readBorderRadius(\n attrs: Attrs,\n label: string,\n notes: string[],\n): BorderRadiusValue | undefined {\n const raw = attrs[\"border-radius\"];\n if (raw === undefined) return undefined;\n\n const radius = parseBorderRadius(raw);\n if (radius === null) {\n notes.push(\n `${label} border-radius \"${raw.trim()}\" could not be read; imported as square corners.`,\n );\n return 0;\n }\n return radius;\n}\n\n/**\n * What the editor's border control holds for a side with no width\n * (`BorderControl.vue`), so a border drawn on some sides reads back the way\n * the editor would have stored it.\n */\nconst UNDRAWN_SIDE: BorderSideValue = {\n width: 0,\n style: \"solid\",\n color: \"#000000\",\n};\n\n/**\n * `border` and `border-<side>` as a `BorderValue`, `undefined` when no side is\n * drawn. A side takes its own `border-<side>` over `border`, since MJML writes\n * the side after the shorthand. A declaration that a side actually uses pushes\n * a note when its style narrows to solid or it cannot be read, in which case\n * that side is left undrawn.\n */\nexport function readBorder(\n attrs: Attrs,\n label: string,\n notes: string[],\n): BorderValue | undefined {\n const read = new Map<string, BorderSideValue | null>();\n const sides = {} as Record<BorderSide, BorderSideValue | null>;\n\n for (const side of BORDER_SIDES) {\n const name =\n attrs[`border-${side}`] !== undefined\n ? `border-${side}`\n : attrs.border !== undefined\n ? \"border\"\n : null;\n if (name === null) {\n sides[side] = null;\n continue;\n }\n\n if (!read.has(name)) {\n const raw = attrs[name]!.trim();\n const parsed = parseBorderDeclaration(raw);\n if (parsed === null) {\n notes.push(\n `${label} ${name} \"${raw}\" could not be read; imported without it.`,\n );\n } else if (parsed.narrowedFrom) {\n notes.push(\n `${label} ${name} \"${raw}\" uses style ${parsed.narrowedFrom}; imported as solid.`,\n );\n }\n read.set(name, parsed?.side ?? null);\n }\n const parsed = read.get(name)!;\n sides[side] = parsed && { ...parsed };\n }\n\n if (BORDER_SIDES.every((side) => sides[side] === null)) return undefined;\n\n return {\n top: sides.top ?? { ...UNDRAWN_SIDE },\n right: sides.right ?? { ...UNDRAWN_SIDE },\n bottom: sides.bottom ?? { ...UNDRAWN_SIDE },\n left: sides.left ?? { ...UNDRAWN_SIDE },\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 canonicalFontStack,\n createMenuBlock,\n createSocialIconsBlock,\n createTableBlock,\n generateId,\n SOCIAL_ICON_TONES,\n SOCIAL_ICON_STYLES,\n} from \"@templatical/types\";\nimport type {\n MenuItemData,\n SocialIcon,\n SocialIconTone,\n SocialIconSize,\n SocialIconStyle,\n SocialPlatform,\n TableRowData,\n} from \"@templatical/types\";\nimport type { Element } from \"domhandler\";\nimport { parseAlignment, parseColor, parsePxValue } 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\n/**\n * Icon packs (and MJML's `-noshare` variants) append tokens to the platform\n * slug: `facebook-round-outlined.png`, `facebook-noshare`. Those stems are not\n * `SocialPlatform` values, so a lookup that stops at the raw filename maps\n * the icon to `\"website\"` and the platform name is lost.\n */\nconst PLATFORM_PACK_TOKENS = new Set([\n \"noshare\",\n \"round\",\n \"outlined\",\n \"square\",\n \"circle\",\n \"solid\",\n]);\n\nfunction lookupPlatform(cleaned: string): SocialPlatform | null {\n if (PLATFORM_ALIASES[cleaned]) return PLATFORM_ALIASES[cleaned];\n return cleaned in KNOWN_PLATFORMS ? (cleaned as SocialPlatform) : null;\n}\n\nfunction normalizePlatform(raw: string): SocialPlatform | null {\n const cleaned = raw.trim().toLowerCase();\n if (!cleaned) return null;\n\n const direct = lookupPlatform(cleaned);\n if (direct) return direct;\n\n const parts = cleaned.split(\"-\");\n while (\n parts.length > 1 &&\n PLATFORM_PACK_TOKENS.has(parts[parts.length - 1])\n ) {\n parts.pop();\n const candidate = parts.join(\"-\");\n const hit = lookupPlatform(candidate);\n if (hit) return hit;\n }\n return null;\n}\n\n/** The `<dir>/<platform>.png` tail of the URL `renderers/social.ts:79` 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\n/**\n * The style and tone in an icon folder the renderer names: `circle` for a\n * brand-colored circle, `circle-dark` for a dark one (`socialIconAssetDir`).\n */\nfunction iconDirParts(\n dir: string,\n): { style: SocialIconStyle; tone?: SocialIconTone } | null {\n const [style, tone, ...rest] = dir.split(\"-\");\n if (rest.length > 0 || !SOCIAL_ICON_STYLES.includes(style as SocialIconStyle))\n return null;\n if (tone === undefined) return { style: style as SocialIconStyle };\n return SOCIAL_ICON_TONES.includes(tone as SocialIconTone) && tone !== \"brand\"\n ? { style: style as SocialIconStyle, tone: tone as SocialIconTone }\n : null;\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 iconTone: SocialIconTone | undefined;\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 name = (childAttrs.name ?? \"\").trim();\n const alt = (childAttrs.alt ?? \"\").trim();\n const platform =\n normalizePlatform(name) ??\n normalizePlatform(fromSrc.platform) ??\n normalizePlatform(alt);\n if (!platform) {\n const rawName = name || fromSrc.platform || alt;\n if (rawName) {\n notes.push(\n `Unrecognised social platform \"${rawName}\" mapped to \"website\".`,\n );\n }\n }\n\n icons.push({\n id: generateId(),\n platform: platform ?? \"website\",\n url: (childAttrs.href ?? \"\").trim(),\n });\n\n const fromDir = iconStyle ? null : iconDirParts(fromSrc.style);\n if (fromDir) {\n iconStyle = fromDir.style;\n iconTone = fromDir.tone;\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:82).\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 ...(iconTone ? { iconTone } : {}),\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 = canonicalFontStack(\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 = canonicalFontStack(\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, CheerioAPI } from \"cheerio\";\nimport { isTag, isText } from \"domhandler\";\nimport type { AnyNode, Element } from \"domhandler\";\nimport {\n canonicalFontStack,\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 { parseAlignment, parseColor, parsePxValue } 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 is reparsed only when an attribute has to be carried\n * into it (`styleParagraphs`); otherwise it passes through verbatim.\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 = canonicalFontStack(\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 = canonicalFontStack(\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 *\n * `.children()` is elements only. A non-whitespace text-node sibling must\n * veto: an `mj-text` of prose plus a trailing `<a>` becomes a menu labelled\n * from the links and the prose is discarded. Whitespace-only nodes (newlines\n * between `<a>`/`<span>`) must not.\n */\nfunction looksLikeMenu(html: string): boolean {\n const $inner = parseInner(html);\n const kids = $inner(\"body\").contents().toArray();\n if (kids.length === 0) return false;\n\n let anchors = 0;\n for (const kid of kids) {\n if (isText(kid)) {\n if (kid.data.trim() !== \"\") return false;\n continue;\n }\n if (!isTag(kid)) continue;\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 = canonicalFontStack(\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\n/**\n * The size the renderer gives every `mj-text` (`<mj-text font-size=\"14px\">` in\n * `packages/renderer/src/index.ts`), so a paragraph at this size needs no span\n * to carry it.\n */\nconst RENDERER_TEXT_SIZE = 14;\n\nconst PARAGRAPH_ALIGNMENTS = new Set([\"left\", \"center\", \"right\", \"justify\"]);\n\n/** `<li>` children that end a run of the item's text instead of joining it. */\nconst LIST_ITEM_BLOCK_TAGS = new Set([\n \"p\",\n \"div\",\n \"ul\",\n \"ol\",\n \"blockquote\",\n \"table\",\n \"pre\",\n \"hr\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n]);\n\n/**\n * What an `mj-text` states about its paragraphs' alignment, colour and size.\n * `ParagraphBlock` has a field for none of them, so they travel in the markup.\n * An empty string states nothing.\n */\ninterface ParagraphStyle {\n textAlign: string;\n color: string;\n fontSize: string;\n}\n\nfunction readParagraphStyle(attrs: Attrs, ctx: ConvertContext): ParagraphStyle {\n // The start edge is where a paragraph sits with no alignment of its own, and\n // the renderer states it on every RTL paragraph (`renderers/paragraph.ts`).\n const align = (attrs.align ?? \"\").trim().toLowerCase();\n const startEdge = ctx.direction === \"rtl\" ? \"right\" : \"left\";\n\n // The resolved size, not ownAttr: a size set document-wide has no setting to\n // land in, so dropping it as ambient would lose it.\n const size = parsePxValue(attrs[\"font-size\"]);\n\n return {\n textAlign:\n PARAGRAPH_ALIGNMENTS.has(align) && align !== startEdge ? align : \"\",\n // A colour set document-wide is `settings.textColor` already.\n color: parseColor(ownAttr(attrs, \"color\", \"mj-text\", ctx.cascade)),\n fontSize: size > 0 && size !== RENDERER_TEXT_SIZE ? `${size}px` : \"\",\n };\n}\n\n/**\n * Put each run of an `<li>`'s inline content in a `<p>`, so that the\n * alignment has a paragraph to sit on.\n */\nfunction wrapListItemText($inner: CheerioAPI, li: Element): void {\n let run: AnyNode[] = [];\n const flush = () => {\n // wrapAll, not before() and append(): before() skips a text node, which\n // is what a run usually starts with, and the text would be lost.\n if (run.some((node) => isTag(node) || (isText(node) && node.data.trim()))) {\n $inner(run).wrapAll(\"<p></p>\");\n }\n run = [];\n };\n\n for (const child of [...li.children]) {\n if (isTag(child) && LIST_ITEM_BLOCK_TAGS.has(child.tagName.toLowerCase())) {\n flush();\n } else {\n run.push(child);\n }\n }\n flush();\n}\n\n/**\n * Align one `<p>` and wrap its content in a span carrying colour and size.\n * The `<p>`'s own `text-align` stays, and its own colour and size move into\n * the span, where they win over the attributes as they did on the `<p>`.\n */\nfunction styleParagraph(\n $inner: CheerioAPI,\n $p: Cheerio<Element>,\n style: ParagraphStyle,\n): void {\n if (style.textAlign && !$p.css(\"text-align\")) {\n $p.css(\"text-align\", style.textAlign);\n }\n\n const color = $p.css(\"color\") || style.color;\n const fontSize = $p.css(\"font-size\") || style.fontSize;\n if ((color || fontSize) && $p.contents().length > 0) {\n $p.css({ color: \"\", \"font-size\": \"\" });\n const $span = $inner(\"<span></span>\").css({\n ...(color ? { color } : {}),\n ...(fontSize ? { \"font-size\": fontSize } : {}),\n });\n $p.wrapInner($span);\n }\n\n if ($p.attr(\"style\") === \"\") $p.removeAttr(\"style\");\n}\n\n/**\n * Carry an `mj-text`'s alignment, colour and size into its paragraphs'\n * markup: `text-align` on each `<p>`, colour and size on one span inside it.\n * That is the shape the paragraph editor keeps through an edit\n * (`packages/editor/src/components/blocks/ParagraphEditor.vue` aligns\n * paragraphs and styles spans); a wrapper element around the paragraphs would\n * be dropped on the first edit.\n *\n * Markup with nothing to apply is returned as it came, unparsed.\n */\nfunction styleParagraphs(html: string, style: ParagraphStyle): string {\n if (!style.textAlign && !style.color && !style.fontSize) return html;\n\n const $inner = parseInner(html);\n $inner(\"li\").each((_, li) => wrapListItemText($inner, li));\n $inner(\"p\").each((_, p) => styleParagraph($inner, $inner(p), style));\n return $inner(\"body\").html() ?? html;\n}\n\nfunction convertParagraph(\n html: string,\n attrs: Attrs,\n ctx: ConvertContext,\n): 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 const gap = readParagraphGap(attrs);\n const paragraphSpacing =\n gap !== null && gap !== RICH_TEXT_SPACING.paragraphGap ? gap : undefined;\n\n const block = createParagraphBlock({\n content: styleParagraphs(\n ensureParagraphWrapped(html),\n readParagraphStyle(attrs, ctx),\n ),\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, ctx);\n}\n","import type { Cheerio } from \"cheerio\";\nimport type { Element } from \"domhandler\";\nimport {\n canonicalFontStack,\n createButtonBlock,\n createDividerBlock,\n createHtmlBlock,\n createImageBlock,\n createSpacerBlock,\n} from \"@templatical/types\";\nimport type { Block, ButtonBlock, DividerBlock } from \"@templatical/types\";\nimport {\n parseAlignment,\n parseBorderStyle,\n parseColor,\n parsePaddingShorthand,\n parsePxValue,\n} from \"./attribute-parser\";\nimport {\n ownAttr,\n resolveAttributes,\n tagOf,\n type Attrs,\n} from \"./attribute-resolver\";\nimport {\n baseFields,\n convertHtmlFallback,\n isNewTab,\n readBorder,\n readBorderRadius,\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): Converted | 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 href = (attrs.href ?? \"\").trim();\n\n const notes: string[] = [];\n const borderRadius = readBorderRadius(attrs, \"Image\", notes);\n const border = readBorder(attrs, \"Image\", notes);\n\n return {\n block: 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 ? { borderRadius } : {}),\n ...(border ? { border } : {}),\n ...(href ? { linkUrl: href } : {}),\n ...(href && isNewTab(attrs) ? { linkOpenInNewTab: true } : {}),\n ...(decorative ? { decorative: true } : {}),\n ...baseFields(attrs),\n }),\n entry: {\n sourceTag: \"mj-image\",\n templaticalBlockType: \"image\",\n status: notes.length > 0 ? \"approximated\" : \"converted\",\n ...(notes.length > 0 ? { note: notes.join(\" \") } : {}),\n },\n };\n}\n\n/**\n * `mj-button`'s `width` as `ButtonBlock.width`, where no width is a button\n * that fits its label. `100%` is `\"full\"` and a px width stays px, as the\n * renderer writes them. Any other percentage becomes px of `fillWidth`, the\n * box MJML sizes it against, since the block holds no percentage. A width\n * with no size fits the label, which is what MJML draws for it. Anything but\n * `100%` or a positive px width pushes a note.\n */\nfunction readButtonWidth(\n value: string | undefined,\n fillWidth: number,\n notes: string[],\n): ButtonBlock[\"width\"] {\n const raw = (value ?? \"\").trim();\n if (!raw) return undefined;\n\n const match = /^(-?\\d+(?:\\.\\d+)?)\\s*(px|%)?$/i.exec(raw);\n if (!match) {\n notes.push(\n `Button width \"${raw}\" could not be read; imported as fit to content.`,\n );\n return undefined;\n }\n const parsed = parseFloat(match[1]);\n const isPercent = match[2] === \"%\";\n\n if (isPercent && parsed >= 100) {\n if (parsed > 100) {\n notes.push(`Button width ${raw} was clamped to 100%.`);\n }\n return \"full\";\n }\n\n const px = Math.round(isPercent ? (parsed / 100) * fillWidth : parsed);\n if (px <= 0) {\n notes.push(\n `Button width ${raw} has no usable size; imported as fit to content.`,\n );\n return undefined;\n }\n if (isPercent) {\n notes.push(\n `Button width ${raw} was imported as ${px}px, which does not scale with its column.`,\n );\n }\n return px;\n}\n\n/**\n * A percentage `width` sizes the button against its column less the column's\n * side padding and its own, the same box an `mj-divider` line spans.\n *\n * `font-family` is read only when the button sets its own: the renderer\n * writes the document font onto `mj-all`, which reaches every button.\n */\nfunction convertButton(\n $el: Cheerio<Element>,\n attrs: Attrs,\n ctx: ConvertContext,\n): Converted | 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 const fontFamily = canonicalFontStack(\n ownAttr(attrs, \"font-family\", \"mj-button\", ctx.cascade),\n );\n const base = baseFields(attrs);\n const { left, right } = base.styles.padding;\n\n const notes: string[] = [];\n const width = readButtonWidth(\n attrs.width,\n ctx.containerWidth - ctx.columnPadding - left - right,\n notes,\n );\n const borderRadius = readBorderRadius(attrs, \"Button\", notes);\n const border = readBorder(attrs, \"Button\", notes);\n\n return {\n block: createButtonBlock({\n text,\n url: (attrs.href ?? \"\").trim(),\n ...(backgroundColor ? { backgroundColor } : {}),\n ...(textColor ? { textColor } : {}),\n ...(fontSize > 0 ? { fontSize } : {}),\n ...(fontFamily ? { fontFamily } : {}),\n ...(borderRadius !== undefined ? { borderRadius } : {}),\n ...(border ? { border } : {}),\n ...(attrs[\"inner-padding\"] !== undefined\n ? { buttonPadding: parsePaddingShorthand(attrs[\"inner-padding\"]) }\n : {}),\n ...(width !== undefined ? { width } : {}),\n align: parseAlignment(attrs.align, \"center\"),\n ...(isNewTab(attrs) ? { openInNewTab: true } : {}),\n ...base,\n }),\n entry: {\n sourceTag: \"mj-button\",\n templaticalBlockType: \"button\",\n status: notes.length > 0 ? \"approximated\" : \"converted\",\n ...(notes.length > 0 ? { note: notes.join(\" \") } : {}),\n },\n };\n}\n\n/**\n * `mj-divider`'s `width` as `DividerBlock.width`. A percentage is a share of\n * the column and stays one, to two decimals within 0–100%. `100%`, and a px\n * width that reaches `lineWidth`, span the line's whole box, which is\n * `\"full\"`. A clamped or unreadable value pushes a note.\n *\n * It reads a sign, which `parsePercent` does not, so a negative value is\n * clamped to 0 and reported instead of being dropped.\n */\nfunction readDividerWidth(\n value: string | undefined,\n lineWidth: number,\n notes: string[],\n): DividerBlock[\"width\"] {\n const raw = (value ?? \"\").trim();\n if (!raw) return \"full\";\n\n const match = /^(-?\\d+(?:\\.\\d+)?)\\s*(px|%)?$/i.exec(raw);\n if (!match) {\n notes.push(\n `Divider width \"${raw}\" could not be read; imported as full width.`,\n );\n return \"full\";\n }\n const parsed = parseFloat(match[1]);\n\n if (match[2] === \"%\") {\n const percent = Math.min(100, Math.max(0, parsed));\n if (percent !== parsed) {\n notes.push(`Divider width ${raw} was clamped to ${percent}%.`);\n }\n // Two decimals keep the value inside `DividerPercentWidth`'s pattern,\n // which a float printed in exponent form would leave.\n const share = Math.round(percent * 100) / 100;\n return share === 100 ? \"full\" : `${share}%`;\n }\n\n const px = Math.max(0, Math.round(parsed));\n if (px !== Math.round(parsed)) {\n notes.push(`Divider width ${raw} was clamped to 0px.`);\n }\n return px >= lineWidth ? \"full\" : px;\n}\n\n/**\n * `mj-divider` draws `100%` across its column less the column's side padding\n * and its own, so that is the width a px value is measured against. It places\n * a partial-width line by `align`, centred by default; Templatical centres\n * every divider, so a left- or right-aligned partial one comes back with a\n * note.\n */\nfunction convertDivider(attrs: Attrs, ctx: ConvertContext): Converted {\n const color = parseColor(attrs[\"border-color\"]);\n const thickness = parsePxValue(attrs[\"border-width\"]);\n const base = baseFields(attrs);\n const { left, right } = base.styles.padding;\n\n const notes: string[] = [];\n const width = readDividerWidth(\n attrs.width,\n ctx.containerWidth - ctx.columnPadding - left - right,\n notes,\n );\n\n const align = (attrs.align ?? \"\").trim().toLowerCase();\n if (width !== \"full\" && (align === \"left\" || align === \"right\")) {\n notes.push(\n `MJML aligns this divider ${align}; Templatical centres every divider.`,\n );\n }\n\n return {\n block: createDividerBlock({\n lineStyle: parseBorderStyle(attrs[\"border-style\"]),\n width,\n ...(color ? { color } : {}),\n ...(attrs[\"border-width\"] !== undefined ? { thickness } : {}),\n ...base,\n }),\n entry: {\n sourceTag: \"mj-divider\",\n templaticalBlockType: \"divider\",\n status: notes.length > 0 ? \"approximated\" : \"converted\",\n ...(notes.length > 0 ? { note: notes.join(\" \") } : {}),\n },\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 return convertImage($el, attrs, ctx);\n }\n\n if (tag === \"mj-button\") {\n return convertButton($el, attrs, ctx);\n }\n\n if (tag === \"mj-divider\") {\n return convertDivider(attrs, ctx);\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 {\n canonicalFontStack,\n DEFAULT_TEMPLATE_DEFAULTS,\n resolveContentDirection,\n} from \"@templatical/types\";\nimport type {\n ContentDirection,\n TemplateContent,\n TemplateSettings,\n} from \"@templatical/types\";\nimport { parseColor, 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 * `<mjml dir>` as `settings.direction`, kept only when it disagrees with the\n * direction the locale implies. The renderer always writes `dir`, resolved\n * from the locale when a template states no direction, so keeping a value that\n * agrees would add a key the source template never set. `auto`, MJML's\n * default, states no direction.\n */\nfunction readDirection(\n value: string | undefined,\n locale: string,\n): ContentDirection | undefined {\n const dir = (value ?? \"\").trim().toLowerCase();\n if (dir !== \"ltr\" && dir !== \"rtl\") return undefined;\n return dir === resolveContentDirection({ locale }) ? undefined : dir;\n}\n\n/**\n * Build `TemplateSettings` from the `mjml` and `mj-body` attributes, the\n * attribute cascade and the remaining `mj-head` children.\n *\n * Optional keys (`preheaderText`, `linkColor`, `direction`) are **omitted**\n * rather than set to `undefined`: an absent key is what the block model means\n * by unset, and a 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 canonicalFontStack(cascade.all[\"font-family\"]) ||\n canonicalFontStack(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: every other rule it\n // emits is scoped under a class (`.tpl-rich-text-8 p`, `.tpl-button-full a`),\n // so the one bare `a { … }` rule present is the document-level rule this\n // 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 const direction = readDirection($root.attr(\"dir\"), 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 ...(direction ? { direction } : {}),\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 readMjmlBackgroundImage,\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 readBorder,\n readBorderRadius,\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 columnAttrs = columns.map(($c) => resolveAttributes($c, ctx.cascade));\n const rawWidths = columnAttrs.map((column) => column.width);\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 const notes: string[] = exact\n ? []\n : [\n `Column widths ${shown} have no exact Templatical layout; resolved to \"${layout}\".`,\n ];\n const borderRadius = readBorderRadius(attrs, \"Section\", notes);\n const border = readBorder(attrs, \"Section\", notes);\n const background = readMjmlBackgroundImage(\n (name) => attrs[name],\n \"Section\",\n ctx.containerWidth,\n );\n if (background) notes.push(...background.notes);\n entries.push({\n sourceTag: \"mj-section\",\n templaticalBlockType: \"section\",\n status: notes.length > 0 ? \"approximated\" : \"converted\",\n ...(notes.length > 0 ? { note: notes.join(\" \") } : {}),\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 { left, right } = parsePaddingShorthand(columnAttrs[index].padding);\n const columnCtx: ConvertContext = {\n ...ctx,\n containerWidth: Math.round(pixels[slot] ?? ctx.containerWidth),\n columnPadding: left + right,\n };\n children[slot].push(...convertColumnChildren($column, columnCtx, entries));\n });\n\n const section = createSectionBlock({\n columns: layout,\n children,\n ...(grouped ? { stackOnMobile: false } : {}),\n ...(borderRadius ? { borderRadius } : {}),\n ...(border ? { border } : {}),\n ...(background ? { backgroundImage: background.image } : {}),\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\nconst WRAPPER_BORDER_ATTRIBUTES = [\n \"border\",\n \"border-top\",\n \"border-right\",\n \"border-bottom\",\n \"border-left\",\n];\n\n/**\n * `SectionWrapper` has no border, so each `border` or `border-<side>` the\n * wrapper sets pushes a note instead.\n */\nfunction readWrapper(\n attrs: Attrs,\n notes: string[],\n boxWidth: number,\n): SectionWrapper {\n const backgroundColor = parseColor(attrs[\"background-color\"]);\n const padding: SpacingValue = parsePaddingShorthand(attrs.padding);\n const borderRadius = readBorderRadius(attrs, \"Wrapper\", notes);\n const background = readMjmlBackgroundImage(\n (name) => attrs[name],\n \"Wrapper\",\n boxWidth,\n );\n\n for (const name of WRAPPER_BORDER_ATTRIBUTES) {\n const raw = attrs[name];\n if (raw === undefined) continue;\n notes.push(\n `Wrapper ${name} \"${raw.trim()}\" has no Templatical equivalent; dropped.`,\n );\n }\n if (background) notes.push(...background.notes);\n\n return {\n ...(backgroundColor ? { backgroundColor } : {}),\n padding,\n ...(borderRadius ? { borderRadius } : {}),\n ...(background ? { backgroundImage: background.image } : {}),\n };\n}\n\n/**\n * Each section gets its own band object and band image, so editing one\n * section's image leaves the others alone. `padding` and `borderRadius` stay\n * shared, as in the other importers.\n */\nfunction copyWrapper(wrapper: SectionWrapper): SectionWrapper {\n const image = wrapper.backgroundImage;\n return {\n ...wrapper,\n ...(image\n ? { backgroundImage: { ...image, position: { ...image.position } } }\n : {}),\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 notes: string[] = [];\n const wrapper = readWrapper(attrs, notes, ctx.containerWidth);\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 notes.unshift(\n `An <mj-wrapper> holding ${sections.length} sections was applied to each of them — Templatical has no multi-section band.`,\n );\n }\n\n // A wrapper is not a block of its own, so it gets an entry only when it has\n // something to report.\n if (notes.length > 0) {\n entries.push({\n sourceTag: \"mj-wrapper\",\n templaticalBlockType: \"section\",\n status: \"approximated\",\n note: notes.join(\" \"),\n });\n }\n\n return sections.flatMap(($section) =>\n buildSection($section, ctx, entries, copyWrapper(wrapper)),\n );\n}\n","import { load } from \"cheerio\";\nimport type { Cheerio } from \"cheerio\";\nimport type { Element } from \"domhandler\";\nimport {\n createDefaultTemplateContent,\n createSectionBlock,\n resolveContentDirection,\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 columnPadding: 0,\n direction: resolveContentDirection(settings),\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 CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@templatical/import-mjml",
3
3
  "description": "Convert MJML email templates to Templatical format",
4
- "version": "0.46.0",
4
+ "version": "0.47.0",
5
5
  "bugs": "https://github.com/templatical/sdk/issues",
6
6
  "dependencies": {
7
- "@templatical/types": "0.46.0",
7
+ "@templatical/types": "0.47.0",
8
8
  "cheerio": "^1.2.0",
9
9
  "domhandler": "^6.0.1"
10
10
  },
11
11
  "devDependencies": {
12
- "@templatical/renderer": "0.46.0",
12
+ "@templatical/renderer": "0.47.0",
13
13
  "@types/node": "^25.9.9",
14
14
  "typescript": "^6.0.3",
15
- "vitest": "^5.0.3"
15
+ "vitest": "^5.0.4"
16
16
  },
17
17
  "exports": {
18
18
  ".": {