@templatical/import-topol 0.51.1 → 0.52.1

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.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- //#region src/types.d.ts
2
1
  /**
3
2
  * Type definitions for the Topol.io importer.
4
3
  *
@@ -57,8 +56,6 @@ interface TopolDesign extends TopolNode {
57
56
  fonts?: unknown;
58
57
  style?: unknown;
59
58
  }
60
- //#endregion
61
- //#region src/converter.d.ts
62
59
  /**
63
60
  * Convert a Topol.io design into a Templatical template.
64
61
  *
@@ -79,6 +76,5 @@ interface TopolDesign extends TopolNode {
79
76
  * ```
80
77
  */
81
78
  export declare function convertTopolTemplate(design: TopolDesign | string): ImportResult;
82
- //#endregion
83
79
  export type { ConversionStatus, ImportReport, ImportReportEntry, ImportResult };
84
80
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DEFAULT_TEMPLATE_DEFAULTS, canonicalFontStack, createButtonBlock, createDefaultTemplateContent, createDividerBlock, createHtmlBlock, createImageBlock, createParagraphBlock, createSectionBlock, createSocialIconsBlock, createSpacerBlock, createTitleBlock, findOpenTagEnd, generateId } from "@templatical/types";
2
- //#region src/attribute-parser.ts
2
+ // src/attribute-parser.ts
3
3
  /**
4
4
  * Read one attribute as a string, treating Topol's explicit `null` — and an
5
5
  * empty string — as absent.
@@ -356,8 +356,7 @@ function readMjmlBackgroundImage(read, label, boxWidth) {
356
356
  notes
357
357
  };
358
358
  }
359
- //#endregion
360
- //#region src/global-style.ts
359
+ // src/global-style.ts
361
360
  /**
362
361
  * `DEFAULT_TEMPLATE_DEFAULTS` is typed `Partial<TemplateSettings>` so a
363
362
  * consumer can override any subset, but its own literal always sets the six
@@ -421,8 +420,7 @@ function readGlobalStyle(root, container, warnings) {
421
420
  selectorDefaults
422
421
  };
423
422
  }
424
- //#endregion
425
- //#region src/content-inference.ts
423
+ // src/content-inference.ts
426
424
  /**
427
425
  * A single heading element wrapping the whole content, captured with its level
428
426
  * and inner markup. Anchored at both ends so a heading with a sibling does not
@@ -511,8 +509,7 @@ function stripTags(html) {
511
509
  if (!html) return "";
512
510
  return removeTags(html).replace(/&(?:amp|lt|gt|quot|#39|apos|nbsp);/g, (m) => ENTITIES[m] ?? m).replace(/\s+/g, " ").trim();
513
511
  }
514
- //#endregion
515
- //#region src/block-mapper.ts
512
+ // src/block-mapper.ts
516
513
  /**
517
514
  * Read a node's own attribute, falling back to the design's per-tag then
518
515
  * per-selector default. Returns undefined when nothing sets it, so the caller
@@ -687,8 +684,7 @@ function convertLeaf(node, ctx) {
687
684
  };
688
685
  }
689
686
  }
690
- //#endregion
691
- //#region src/social-mapper.ts
687
+ // src/social-mapper.ts
692
688
  /**
693
689
  * Exhaustive over `SocialPlatform` on purpose: adding a member to that union
694
690
  * without adding it here is a compile error, so this cannot silently fall back
@@ -812,8 +808,7 @@ function convertSocial(node, _ctx) {
812
808
  }
813
809
  };
814
810
  }
815
- //#endregion
816
- //#region src/section-builder.ts
811
+ // src/section-builder.ts
817
812
  const LAYOUT_SHAPES = [
818
813
  {
819
814
  layout: "1",
@@ -969,8 +964,7 @@ function buildSection(node, ctx, entries) {
969
964
  ...background ? { backgroundImage: background.image } : {}
970
965
  })];
971
966
  }
972
- //#endregion
973
- //#region src/converter.ts
967
+ // src/converter.ts
974
968
  const ROOT_TAG = "mj-global-style";
975
969
  const INVALID_DESIGN_MESSAGE = "Invalid Topol template: expected the design JSON object — Topol hands you this directly from its editor's onSave callback, or under \"definition\" (template API) or \"json\" (predefined-templates API) in its API response.";
976
970
  const EMPTY_DESIGN_WARNING = "No convertible content was found in the Topol design. Check that the mj-container holds at least one mj-section.";
@@ -1041,7 +1035,6 @@ function safeParse(source) {
1041
1035
  throw new Error(INVALID_DESIGN_MESSAGE);
1042
1036
  }
1043
1037
  }
1044
- //#endregion
1045
1038
  export { convertTopolTemplate };
1046
1039
 
1047
1040
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/attribute-parser.ts","../src/global-style.ts","../src/content-inference.ts","../src/block-mapper.ts","../src/social-mapper.ts","../src/section-builder.ts","../src/converter.ts"],"sourcesContent":["import type {\n BackgroundImage,\n BackgroundPositionX,\n BackgroundPositionY,\n BackgroundSize,\n SpacingValue,\n} from \"@templatical/types\";\nimport type { TopolNode } from \"./types\";\n\n/**\n * Read one attribute as a string, treating Topol's explicit `null` — and an\n * empty string — as absent.\n *\n * Topol writes literal `null` for an unset attribute rather than omitting the\n * key, so a `!== undefined` check would report it as present and write a null\n * into the block. Every optional-field read goes through here.\n */\nexport function attr(node: TopolNode, key: string): string | undefined {\n const raw = node.attributes?.[key];\n if (raw === null || raw === undefined) return undefined;\n if (typeof raw === \"object\") return undefined;\n const value = String(raw);\n return value === \"\" ? undefined : value;\n}\n\n/** Read one attribute as a number, or `undefined` when unset or non-numeric. */\nexport function numAttr(node: TopolNode, key: string): number | undefined {\n const value = attr(node, key);\n if (value === undefined) return undefined;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\n/**\n * Parse a px-like value into a rounded integer. Returns 0 for a missing value\n * and for units the block model cannot express (em, rem, %).\n *\n * Trims first, then matches with no `\\s*` on either side of the optional\n * `px` unit. A trailing run of spaces can otherwise be claimed by either the\n * surrounding `\\s*` or by backtracking out of the digit/unit match, and a\n * *failing* match (no `px`, no digits — anything not shaped like a length)\n * retries every such split before giving up: `\"0\" + \" \".repeat(n) + \"x\"` is\n * polynomial in `n` against that pattern. Trimming first removes the only\n * whitespace the pattern needs to tolerate, so the match itself has nothing\n * ambiguous left to backtrack over.\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 trimmed = value.trim();\n const match = /^(-?\\d+(?:\\.\\d+)?)(?:px)?$/.exec(trimmed);\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 * Normalize a colour to 6-digit lowercase hex.\n *\n * Returns `\"\"` for transparent/inherit/none and for anything unrecognised —\n * the empty string is the block model's \"unset\", which the colour pickers\n * clear to, so 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 if (/^#[0-9a-f]{6}$/.test(trimmed)) return trimmed;\n if (/^#[0-9a-f]{3}$/.test(trimmed)) {\n const [, r, g, b] = trimmed;\n return `#${r}${r}${g}${g}${b}${b}`;\n }\n const rgb = trimmed.match(\n /^rgba?\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*(?:,\\s*[\\d.]+\\s*)?\\)$/,\n );\n if (rgb) return rgbToHex(+rgb[1], +rgb[2], +rgb[3]);\n return NAMED_COLORS[trimmed] ?? \"\";\n}\n\nfunction expandShorthand(value: string | undefined): SpacingValue {\n if (!value) return { top: 0, right: 0, bottom: 0, left: 0 };\n const v = value\n .trim()\n .split(/\\s+/)\n .map((p) => parsePxValue(p));\n switch (v.length) {\n case 1:\n return { top: v[0], right: v[0], bottom: v[0], left: v[0] };\n case 2:\n return { top: v[0], right: v[1], bottom: v[0], left: v[1] };\n case 3:\n return { top: v[0], right: v[1], bottom: v[2], left: v[1] };\n default:\n return { top: v[0], right: v[1], bottom: v[2], left: v[3] };\n }\n}\n\n/**\n * Read a node's padding, handling both forms Topol emits: a shorthand string\n * (`\"9px 9px 9px 9px\"`, used by most leaves) and four separate bare-number\n * keys (`padding-top: 24`, used by `mj-divider`). A separate key wins over the\n * shorthand for its own side.\n */\nexport function parsePadding(node: TopolNode): SpacingValue {\n const base = expandShorthand(attr(node, \"padding\"));\n const side = (key: string, fallback: number) => {\n const value = attr(node, key);\n return value === undefined ? fallback : parsePxValue(value);\n };\n return {\n top: side(\"padding-top\", base.top),\n right: side(\"padding-right\", base.right),\n bottom: side(\"padding-bottom\", base.bottom),\n left: side(\"padding-left\", base.left),\n };\n}\n\n/**\n * Read a percentage. Returns `null` when the value is not a percentage, so a\n * caller can tell \"no width given\" from \"0%\" — column matching depends on that\n * distinction.\n */\nexport function parsePercent(\n value: string | number | undefined,\n): number | null {\n if (value === undefined || value === null || value === \"\") return null;\n if (typeof value === \"number\") return Number.isFinite(value) ? value : null;\n const match = value.trim().match(/^(\\d+(?:\\.\\d+)?)\\s*%$/);\n return match ? parseFloat(match[1]) : null;\n}\n\n/** Narrow an alignment to the three the block model accepts. */\nexport function parseAlignment(\n value: string | undefined,\n fallback: \"left\" | \"center\" | \"right\" = \"left\",\n): \"left\" | \"center\" | \"right\" {\n const v = (value ?? \"\").trim().toLowerCase();\n return v === \"left\" || v === \"center\" || v === \"right\" ? v : fallback;\n}\n\n/** Narrow a border style to the three `DividerBlock.lineStyle` accepts. */\nexport function parseBorderStyle(\n value: string | undefined,\n): \"solid\" | \"dashed\" | \"dotted\" {\n const v = (value ?? \"\").trim().toLowerCase();\n return v === \"dashed\" || v === \"dotted\" ? v : \"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 {\n canonicalFontStack,\n DEFAULT_TEMPLATE_DEFAULTS,\n} from \"@templatical/types\";\nimport type { TemplateContent, TemplateSettings } from \"@templatical/types\";\nimport { attr, parseColor, parsePxValue } from \"./attribute-parser\";\nimport type { TopolDesign, TopolNode } from \"./types\";\n\n/**\n * `DEFAULT_TEMPLATE_DEFAULTS` is typed `Partial<TemplateSettings>` so a\n * consumer can override any subset, but its own literal always sets the six\n * required fields. Narrowing once here is what lets each read come out as\n * `number`/`string`/`boolean` rather than `| undefined`. A plain typed\n * assignment cannot substitute: `Partial<T>` is not assignable to\n * `Required<Pick<T, …>>`, so `tsc` rejects it outright.\n */\nconst REQUIRED_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\nexport interface GlobalStyle {\n settings: TemplateContent[\"settings\"];\n /** From nested objects like `\"mj-text\": { … }`. */\n tagDefaults: Record<string, Record<string, string>>;\n /** From flat `<selector>:<property>` keys; the bare selector is `\"\"`. */\n selectorDefaults: Record<string, Record<string, string>>;\n}\n\n/** A per-tag default, or undefined when the design sets none. */\nexport function tagDefault(\n style: GlobalStyle,\n tag: string,\n key: string,\n): string | undefined {\n return style.tagDefaults[tag]?.[key];\n}\n\n/** A per-selector default (`\"\"` is the document default), or undefined. */\nexport function selectorDefault(\n style: GlobalStyle,\n selector: string,\n key: string,\n): string | undefined {\n return style.selectorDefaults[selector]?.[key];\n}\n\n/**\n * Split the root's `attributes` into settings, per-tag defaults and\n * per-selector defaults.\n *\n * The root mixes two idioms: `<selector>:<property>` flat keys (`:color`,\n * `a:color`, `h1:font-family`) and `<tag>: { … }` nested objects, which are\n * the analogue of MJML's `mj-attributes`.\n */\nexport function readGlobalStyle(\n root: TopolDesign,\n container: TopolNode | undefined,\n warnings: string[],\n): GlobalStyle {\n const tagDefaults: Record<string, Record<string, string>> = Object.create(\n null,\n );\n const selectorDefaults: Record<\n string,\n Record<string, string>\n > = Object.create(null);\n\n for (const [key, value] of Object.entries(root.attributes ?? {})) {\n if (value !== null && typeof value === \"object\") {\n const bucket = (tagDefaults[key] ??= Object.create(null));\n for (const [k, v] of Object.entries(value)) {\n if (v !== null && v !== undefined) bucket[k] = String(v);\n }\n continue;\n }\n\n const colon = key.indexOf(\":\");\n // No colon means a root-level setting rather than a selector default —\n // `containerWidth` and `fonts` are the two real cases, and both are read\n // directly by name below instead of through the caches built here.\n if (colon === -1) continue;\n const selector = key.slice(0, colon);\n const property = key.slice(colon + 1);\n if (value === null || value === undefined) continue;\n const bucket = (selectorDefaults[selector] ??= Object.create(null));\n bucket[property] = String(value);\n }\n\n // containerWidth is authored only here, on the root. It is also denormalised\n // onto every leaf as the enclosing column's computed width (600/300/150 in\n // one template), so no other module may read it.\n const width =\n parsePxValue(attr(root, \"containerWidth\")) || REQUIRED_DEFAULTS.width;\n\n const backgroundColor =\n (container ? parseColor(attr(container, \"background-color\")) : \"\") ||\n REQUIRED_DEFAULTS.backgroundColor;\n\n const textColor =\n parseColor(selectorDefaults[\"\"]?.color) || REQUIRED_DEFAULTS.textColor;\n\n const fontFamily =\n canonicalFontStack(selectorDefaults[\"\"]?.[\"font-family\"]) ||\n canonicalFontStack(attr(root, \"fonts\")) ||\n REQUIRED_DEFAULTS.fontFamily;\n\n const linkColor = parseColor(selectorDefaults[\"a\"]?.color);\n\n const lineHeight = selectorDefaults[\"\"]?.[\"line-height\"];\n if (lineHeight !== undefined) {\n warnings.push(\n `Dropped the document line-height (${lineHeight}) — Templatical has no document-level line-height setting.`,\n );\n }\n\n const settings: TemplateContent[\"settings\"] = {\n width,\n backgroundColor,\n textColor,\n linkUnderline: REQUIRED_DEFAULTS.linkUnderline,\n fontFamily,\n locale: REQUIRED_DEFAULTS.locale,\n ...(linkColor ? { linkColor } : {}),\n };\n\n return { settings, tagDefaults, selectorDefaults };\n}\n","import { findOpenTagEnd } from \"@templatical/types\";\nimport type { HeadingLevel } from \"@templatical/types\";\n\nexport type InferredText =\n | { kind: \"title\"; level: HeadingLevel; inner: string; clampedFrom?: number }\n | { kind: \"paragraph\"; html: string };\n\n/**\n * A single heading element wrapping the whole content, captured with its level\n * and inner markup. Anchored at both ends so a heading with a sibling does not\n * match — that case is a paragraph.\n */\nconst SOLE_HEADING = /^<h([1-6])(?:\\s[^>]*)?>([\\s\\S]*)<\\/h\\1>$/i;\n\nconst BLOCK_ROOT = /^<(p|h[1-6]|ul|ol|blockquote|div|table)\\b/i;\n\n/**\n * Resolve a Topol `content` string to a Title or a Paragraph.\n *\n * Topol has no menu or table construct, so this is a two-way choice — the MJML\n * importer needs four arms because `mj-text` is the output of four different\n * block renderers there. Paragraph is the terminal arm and always reachable,\n * so this function is total.\n */\nexport function inferTextBlock(content: string | undefined): InferredText {\n const trimmed = (content ?? \"\").trim();\n if (trimmed === \"\") return { kind: \"paragraph\", html: \"<p></p>\" };\n\n const heading = trimmed.match(SOLE_HEADING);\n if (heading) {\n const source = Number(heading[1]);\n const level = Math.min(source, 4) as HeadingLevel;\n return {\n kind: \"title\",\n level,\n inner: heading[2].trim(),\n ...(source > 4 ? { clampedFrom: source } : {}),\n };\n }\n\n return {\n kind: \"paragraph\",\n html: BLOCK_ROOT.test(trimmed) ? trimmed : `<p>${trimmed}</p>`,\n };\n}\n\nconst ENTITIES: Record<string, string> = {\n \"&amp;\": \"&\",\n \"&lt;\": \"<\",\n \"&gt;\": \">\",\n \"&quot;\": '\"',\n \"&#39;\": \"'\",\n \"&apos;\": \"'\",\n \"&nbsp;\": \" \",\n};\n\n/**\n * Remove every `<tag>` from `html`, keeping the text between them.\n *\n * A single-pass linear scan rather than `/<[^>]*>/g`: that regex is\n * polynomial over input with many `<` starts and no closing `>`, since\n * `[^>]*` backtracks at every one of them — `\"<\".repeat(n)` is quadratic in\n * `n`. `findOpenTagEnd` (from `@templatical/types`) resolves each `<` with a\n * bounded, quote-aware forward scan instead, so the walk stays O(n): every\n * character is visited at most once by `indexOf` and once by\n * `findOpenTagEnd`, because the cursor only moves forward and an\n * unterminated tag ends the walk rather than retrying from inside it. On\n * that unterminated case the remainder of the string — the dangling `<`\n * included — is emitted as-is, matching what `/<[^>]*>/g` does with\n * malformed markup: it can never complete a match without a `>`, so the\n * text is left untouched from that point on.\n */\nfunction removeTags(html: string): string {\n let out = \"\";\n let i = 0;\n while (i < html.length) {\n const open = html.indexOf(\"<\", i);\n if (open === -1) {\n out += html.substring(i);\n break;\n }\n out += html.substring(i, open);\n const tagEnd = findOpenTagEnd(html, open + 1);\n if (tagEnd === -1) {\n out += html.substring(open);\n break;\n }\n i = tagEnd + 1;\n }\n return out;\n}\n\n/**\n * The visible text of a markup fragment, for a button label — `ButtonBlock.text`\n * is a plain string, so the markup Topol stores in `content` cannot travel.\n */\nexport function stripTags(html: string | undefined): string {\n if (!html) return \"\";\n const text = removeTags(html);\n const decoded = text.replace(\n /&(?:amp|lt|gt|quot|#39|apos|nbsp);/g,\n (m) => ENTITIES[m] ?? m,\n );\n return decoded.replace(/\\s+/g, \" \").trim();\n}\n","import {\n canonicalFontStack,\n createButtonBlock,\n createDividerBlock,\n createHtmlBlock,\n createImageBlock,\n createParagraphBlock,\n createSpacerBlock,\n createTitleBlock,\n} from \"@templatical/types\";\nimport type { Block, BlockStyles } from \"@templatical/types\";\nimport {\n attr,\n numAttr,\n parseAlignment,\n parseBorderStyle,\n parseColor,\n parsePadding,\n parsePxValue,\n} from \"./attribute-parser\";\nimport { inferTextBlock, stripTags } from \"./content-inference\";\nimport { selectorDefault, tagDefault, type GlobalStyle } from \"./global-style\";\nimport type { ImportReportEntry, TopolNode } from \"./types\";\n\nexport interface MapContext {\n style: GlobalStyle;\n /** The rendered width of the column holding this node, for `width: \"full\"`. */\n columnWidth: number;\n}\n\nexport interface Converted {\n block: Block | null;\n entry: ImportReportEntry;\n}\n\n/**\n * Read a node's own attribute, falling back to the design's per-tag then\n * per-selector default. Returns undefined when nothing sets it, so the caller\n * can omit the field and let the block factory's default stand.\n */\nfunction resolved(\n node: TopolNode,\n key: string,\n ctx: MapContext,\n selector?: string,\n): string | undefined {\n return (\n attr(node, key) ??\n tagDefault(ctx.style, node.tagName, key) ??\n (selector === undefined\n ? undefined\n : selectorDefault(ctx.style, selector, key))\n );\n}\n\n/**\n * The `styles` every leaf block shares.\n *\n * The block background is the row behind the element, which MJML reads from\n * `container-background-color` on every leaf and which the renderer writes\n * `styles.backgroundColor` back out as. A leaf's `background-color` must not\n * feed it: on `mj-button` it is the button face (`backgroundColor`, read in\n * `convertButton`), on `mj-text` MJML never paints it, and on image, spacer,\n * divider and social it is invalid and dropped. Reading it here paints a\n * button's whole row in its fill colour. `buildSection` reads\n * `background-color` itself, since on `mj-section` it is the section's fill.\n */\nexport function baseStyles(node: TopolNode): { styles: BlockStyles } {\n const backgroundColor = parseColor(attr(node, \"container-background-color\"));\n return {\n styles: {\n padding: parsePadding(node),\n ...(backgroundColor ? { backgroundColor } : {}),\n },\n };\n}\n\nfunction convertText(node: TopolNode, ctx: MapContext): Converted {\n const inferred = inferTextBlock(node.content);\n\n if (inferred.kind === \"paragraph\") {\n return {\n block: createParagraphBlock({\n content: inferred.html,\n ...baseStyles(node),\n }),\n entry: {\n sourceTag: \"mj-text\",\n templaticalBlockType: \"paragraph\",\n status: \"converted\",\n },\n };\n }\n\n const selector = `h${inferred.level}`;\n const color = parseColor(resolved(node, \"color\", ctx, selector));\n const fontFamily = canonicalFontStack(\n resolved(node, \"font-family\", ctx, selector),\n );\n\n const block = createTitleBlock({\n content: inferred.inner,\n level: inferred.level,\n textAlign: parseAlignment(attr(node, \"align\"), \"left\"),\n ...(color ? { color } : {}),\n ...(fontFamily ? { fontFamily } : {}),\n ...baseStyles(node),\n });\n\n return {\n block,\n entry: {\n sourceTag: \"mj-text\",\n templaticalBlockType: \"title\",\n status: inferred.clampedFrom ? \"approximated\" : \"converted\",\n ...(inferred.clampedFrom\n ? {\n note: `Heading level h${inferred.clampedFrom} clamped to 4 — Templatical titles support h1-h4.`,\n }\n : {}),\n },\n };\n}\n\nfunction convertButton(node: TopolNode, ctx: MapContext): Converted | null {\n const text = stripTags(node.content);\n if (!text) return null;\n\n const backgroundColor = parseColor(\n resolved(node, \"background-color\", ctx, \"button\"),\n );\n const textColor = parseColor(resolved(node, \"color\", ctx, \"button\"));\n const fontFamily = canonicalFontStack(\n resolved(node, \"font-family\", ctx, \"button\"),\n );\n const fontSizeRaw = resolved(node, \"font-size\", ctx);\n const radiusRaw = resolved(node, \"border-radius\", ctx);\n\n return {\n block: createButtonBlock({\n text,\n url: attr(node, \"href\") ?? \"\",\n ...(backgroundColor ? { backgroundColor } : {}),\n ...(textColor ? { textColor } : {}),\n ...(fontFamily ? { fontFamily } : {}),\n ...(fontSizeRaw === undefined\n ? {}\n : { fontSize: parsePxValue(fontSizeRaw) }),\n ...(radiusRaw === undefined\n ? {}\n : { borderRadius: parsePxValue(radiusRaw) }),\n align: parseAlignment(attr(node, \"align\"), \"center\"),\n ...baseStyles(node),\n }),\n entry: {\n sourceTag: node.tagName,\n templaticalBlockType: \"button\",\n status: \"converted\",\n },\n };\n}\n\nfunction convertImage(\n node: TopolNode,\n ctx: MapContext,\n sourceTag: string,\n): Converted | null {\n const src = attr(node, \"src\");\n if (!src) return null;\n\n const widthPercent = numAttr(node, \"widthPercent\");\n const pxWidth = numAttr(node, \"width\");\n const href = attr(node, \"href\");\n const base = baseStyles(node);\n // MJML caps an image at its column less its own side padding, so a px width\n // that reaches that renders as \"full\" does.\n const room =\n ctx.columnWidth - base.styles.padding.left - base.styles.padding.right;\n\n const width =\n widthPercent === 100 || pxWidth === undefined || pxWidth >= room\n ? (\"full\" as const)\n : Math.round(pxWidth);\n\n const isGif = sourceTag === \"mj-gif\";\n\n return {\n block: createImageBlock({\n src,\n alt: attr(node, \"alt\") ?? \"\",\n width,\n align: parseAlignment(attr(node, \"align\"), \"center\"),\n ...(href ? { linkUrl: href } : {}),\n ...base,\n }),\n entry: {\n sourceTag,\n templaticalBlockType: \"image\",\n status: isGif ? \"approximated\" : \"converted\",\n ...(isGif\n ? {\n note: \"<mj-gif> imported as an image block; Templatical has no dedicated GIF block.\",\n }\n : {}),\n },\n };\n}\n\nfunction convertSpacer(node: TopolNode): Converted {\n const height = attr(node, \"height\");\n return {\n block: createSpacerBlock({\n ...(height === undefined ? {} : { height: parsePxValue(height) }),\n ...baseStyles(node),\n }),\n entry: {\n sourceTag: \"mj-spacer\",\n templaticalBlockType: \"spacer\",\n status: \"converted\",\n },\n };\n}\n\nfunction convertDivider(node: TopolNode): Converted {\n const color = parseColor(attr(node, \"border-color\"));\n const thickness = attr(node, \"border-width\");\n return {\n block: createDividerBlock({\n lineStyle: parseBorderStyle(attr(node, \"border-style\")),\n ...(color ? { color } : {}),\n ...(thickness === undefined\n ? {}\n : { thickness: parsePxValue(thickness) }),\n ...baseStyles(node),\n }),\n entry: {\n sourceTag: \"mj-divider\",\n templaticalBlockType: \"divider\",\n status: \"converted\",\n },\n };\n}\n\n/**\n * Convert one leaf node to a block.\n *\n * Returns `null` for a node that produces nothing *and* warrants no report\n * entry — an image with no `src`, a button with no label. `mj-social` is not\n * handled here; the converter routes it to `convertSocial`.\n */\nexport function convertLeaf(\n node: TopolNode,\n ctx: MapContext,\n): Converted | null {\n switch (node.tagName) {\n case \"mj-text\":\n return convertText(node, ctx);\n case \"mj-button\":\n return convertButton(node, ctx);\n case \"mj-image\":\n return convertImage(node, ctx, \"mj-image\");\n case \"mj-gif\":\n return convertImage(node, ctx, \"mj-gif\");\n case \"mj-spacer\":\n return convertSpacer(node);\n case \"mj-divider\":\n return convertDivider(node);\n default:\n return {\n block: createHtmlBlock({\n content: JSON.stringify(node, null, 2),\n ...baseStyles(node),\n }),\n entry: {\n sourceTag: node.tagName,\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n note: `<${node.tagName}> has no Templatical block equivalent; the original node is preserved as JSON.`,\n },\n };\n }\n}\n","import { createSocialIconsBlock, generateId } from \"@templatical/types\";\nimport type {\n SocialIcon,\n SocialIconTone,\n SocialIconSize,\n SocialIconStyle,\n SocialPlatform,\n} from \"@templatical/types\";\nimport { attr, parseAlignment, parsePxValue } from \"./attribute-parser\";\nimport { baseStyles, type Converted, type MapContext } from \"./block-mapper\";\nimport type { TopolNode } from \"./types\";\n\n/**\n * Exhaustive over `SocialPlatform` on purpose: adding a member to that union\n * without adding it here is a compile error, so this cannot silently fall back\n * 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 ALIASES: Record<string, SocialPlatform> = {\n x: \"twitter\",\n \"x-twitter\": \"twitter\",\n};\n\nconst ICON_SIZES: Array<[number, SocialIconSize]> = [\n [24, \"small\"],\n [32, \"medium\"],\n [48, \"large\"],\n];\n\nconst KNOWN_STYLES = new Set<string>([\n \"solid\",\n \"outlined\",\n \"rounded\",\n \"square\",\n \"circle\",\n]);\n\n// Topol ships icon sets whose folder name is not a Templatical style name.\n// `outlinedbw` is the outlined set in black and white, so it imports as\n// outlined in the dark tone; mapping it to the `solid` fallback would import\n// an outlined icon set as filled. Measured: both `/social-icos/outlined/` and\n// `/social-icos/outlinedbw/` appear across the reference exports.\nconst STYLE_ALIASES: Record<\n string,\n { style: SocialIconStyle; tone: SocialIconTone }\n> = {\n outlinedbw: { style: \"outlined\", tone: \"dark\" },\n};\n\nfunction normalizePlatform(raw: string): SocialPlatform | null {\n const cleaned = raw.trim().toLowerCase();\n if (!cleaned) return null;\n if (ALIASES[cleaned]) return ALIASES[cleaned];\n return cleaned in KNOWN_PLATFORMS ? (cleaned as SocialPlatform) : null;\n}\n\nfunction nearestSize(px: number): { size: SocialIconSize; exact: boolean } {\n let best = ICON_SIZES[1];\n let gap = Infinity;\n for (const candidate of ICON_SIZES) {\n const d = Math.abs(candidate[0] - px);\n if (d < gap) {\n gap = d;\n best = candidate;\n }\n }\n return { size: best[1], exact: gap === 0 };\n}\n\n/** The style (and tone) the segment of `https://…/social-icos/<style>/` names. */\nfunction styleFromBaseUrl(\n baseUrl: string | undefined,\n): { style: SocialIconStyle; tone?: SocialIconTone } | undefined {\n if (!baseUrl) return undefined;\n const segment = baseUrl.split(\"?\")[0].split(\"/\").filter(Boolean).at(-1) ?? \"\";\n if (KNOWN_STYLES.has(segment)) return { style: segment as SocialIconStyle };\n return STYLE_ALIASES[segment];\n}\n\n/**\n * Convert Topol's MJML v3 social block.\n *\n * `display` decides which icons render and in what order; a platform with\n * `*-href` set but absent from `display` was not shown in the email and is\n * not imported.\n */\nexport function convertSocial(\n node: TopolNode,\n _ctx: MapContext,\n): Converted | null {\n const display = attr(node, \"display\");\n if (!display) return null;\n\n const notes: string[] = [];\n const icons: SocialIcon[] = [];\n\n for (const token of display.trim().split(/\\s+/)) {\n const rawName = token.split(\":\")[0];\n if (!rawName) continue;\n\n const url = attr(node, `${rawName}-href`);\n if (!url) continue;\n\n const platform = normalizePlatform(rawName);\n if (!platform) {\n notes.push(\n `Unrecognised social platform \"${rawName}\" mapped to \"website\".`,\n );\n }\n\n icons.push({ id: generateId(), platform: platform ?? \"website\", url });\n }\n\n if (icons.length === 0) return null;\n\n const fromBaseUrl = styleFromBaseUrl(attr(node, \"base-url\"));\n\n const declaredSize = attr(node, \"icon-size\");\n let iconSize: SocialIconSize | undefined;\n if (declaredSize !== undefined) {\n const px = parsePxValue(declaredSize);\n const resolved = nearestSize(px);\n iconSize = resolved.size;\n if (!resolved.exact) {\n notes.push(\n `Icon size ${declaredSize} is not one of 24/32/48px; resolved to \"${resolved.size}\".`,\n );\n }\n }\n\n return {\n block: createSocialIconsBlock({\n icons,\n align: parseAlignment(attr(node, \"align\"), \"center\"),\n ...(iconSize ? { iconSize } : {}),\n ...(fromBaseUrl ? { iconStyle: fromBaseUrl.style } : {}),\n ...(fromBaseUrl?.tone ? { iconTone: fromBaseUrl.tone } : {}),\n ...baseStyles(node),\n }),\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","import { createSectionBlock } from \"@templatical/types\";\nimport type { Block, ColumnLayout } from \"@templatical/types\";\nimport {\n attr,\n parseColor,\n parsePadding,\n parsePercent,\n readMjmlBackgroundImage,\n} from \"./attribute-parser\";\nimport { convertLeaf, type MapContext } from \"./block-mapper\";\nimport { convertSocial } from \"./social-mapper\";\nimport type { ImportReportEntry, TopolNode } from \"./types\";\n\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\nconst WIDTH_TOLERANCE = 2;\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\nfunction columnPixels(layout: ColumnLayout, container: number): number[] {\n switch (layout) {\n case \"2\":\n return [container / 2, container / 2];\n case \"3\":\n return [container / 3, container / 3, container / 3];\n case \"1-2\":\n return [container / 3, (container * 2) / 3];\n case \"2-1\":\n return [(container * 2) / 3, container / 3];\n default:\n return [container];\n }\n}\n\n/**\n * Resolve column widths to one of the five layouts `ColumnLayout` allows.\n *\n * `exact: false` means the caller reports `approximated` — Topol permits any\n * column count at any width and this union permits five shapes, so this is the\n * importer's main irreducible loss.\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 // All-unset is \"distribute equally\", which is exactly what the n-column\n // 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 const known = percents.filter((p): p is number => p !== null);\n const unsetCount = count - known.length;\n const remainder =\n unsetCount === 0\n ? 0\n : Math.max(0, 100 - known.reduce((a, b) => a + b, 0)) / unsetCount;\n const resolved = percents.map((p) => p ?? remainder);\n\n const sameCount = LAYOUT_SHAPES.filter((s) => s.percents.length === count);\n\n for (const shape of sameCount) {\n if (\n shape.percents.every(\n (want, i) => Math.abs(want - resolved[i]) <= WIDTH_TOLERANCE,\n )\n ) {\n return { layout: shape.layout, exact: true };\n }\n }\n\n const candidates =\n sameCount.length > 0\n ? sameCount\n : LAYOUT_SHAPES.filter((s) => s.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\nfunction convertColumnChildren(\n column: TopolNode,\n ctx: MapContext,\n entries: ImportReportEntry[],\n): Block[] {\n const blocks: Block[] = [];\n for (const child of column.children ?? []) {\n const converted =\n child.tagName === \"mj-social\"\n ? convertSocial(child, ctx)\n : convertLeaf(child, ctx);\n if (!converted) continue;\n entries.push(converted.entry);\n if (converted.block) blocks.push(converted.block);\n }\n return blocks;\n}\n\n/**\n * Build a `SectionBlock` from an `mj-section`. Returns an array so the caller\n * can treat an empty section (no columns) as producing nothing.\n *\n * Reads `attributes` only. The node's top-level camelCase keys\n * (`backgroundColor`, `paddingTop`, …) are stale editor state that disagrees\n * with `attributes` in every measured case.\n */\nexport function buildSection(\n node: TopolNode,\n ctx: MapContext,\n entries: ImportReportEntry[],\n): Block[] {\n const columns = (node.children ?? []).filter(\n (c) => c.tagName === \"mj-column\",\n );\n if (columns.length === 0) return [];\n\n const rawWidths = columns.map((c) => attr(c, \"width\"));\n const percents = rawWidths.map((w) => parsePercent(w));\n const { layout, exact } = matchColumnLayout(percents);\n\n const slots = COLUMN_COUNT[layout];\n const pixels = columnPixels(layout, ctx.style.settings.width);\n const children: Block[][] = Array.from({ length: slots }, () => []);\n\n const background = readMjmlBackgroundImage(\n (name) => attr(node, name),\n \"Section\",\n ctx.style.settings.width,\n );\n const notes = [\n ...(exact\n ? []\n : [\n `Column widths ${rawWidths.map((w) => w ?? \"auto\").join(\", \")} have no exact Templatical layout; resolved to \"${layout}\".`,\n ]),\n ...(background?.notes ?? []),\n ];\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 columns.forEach((column, index) => {\n const slot = Math.min(index, slots - 1);\n const columnCtx: MapContext = {\n ...ctx,\n columnWidth: Math.round(pixels[slot] ?? ctx.style.settings.width),\n };\n children[slot].push(...convertColumnChildren(column, columnCtx, entries));\n });\n\n const backgroundColor = parseColor(attr(node, \"background-color\"));\n\n return [\n createSectionBlock({\n columns: layout,\n children,\n styles: {\n padding: parsePadding(node),\n ...(backgroundColor ? { backgroundColor } : {}),\n },\n ...(background ? { backgroundImage: background.image } : {}),\n }),\n ];\n}\n","import { createDefaultTemplateContent } from \"@templatical/types\";\nimport type { Block, TemplateContent } from \"@templatical/types\";\nimport { readGlobalStyle } from \"./global-style\";\nimport { buildSection } from \"./section-builder\";\nimport type { MapContext } from \"./block-mapper\";\nimport type {\n ImportReport,\n ImportReportEntry,\n ImportResult,\n TopolDesign,\n} from \"./types\";\n\nconst ROOT_TAG = \"mj-global-style\";\n\nconst INVALID_DESIGN_MESSAGE =\n 'Invalid Topol template: expected the design JSON object — Topol hands you this directly from its editor\\'s onSave callback, or under \"definition\" (template API) or \"json\" (predefined-templates API) in its API response.';\n\nconst EMPTY_DESIGN_WARNING =\n \"No convertible content was found in the Topol design. Check that the mj-container holds at least one mj-section.\";\n\n/**\n * Convert a Topol.io design into a Templatical template.\n *\n * The input is the design object itself — the node whose `tagName` is\n * `mj-global-style`, not a whole API response. Topol's editor hands it to\n * you directly as the first argument to `onSave`; its REST APIs wrap it\n * instead, under `definition` on the template-retrieval endpoint or `json`\n * (alongside `html`) on the predefined-templates endpoint. That is\n * documented rather than sniffed: guessing which field holds the design\n * risks importing an envelope's `html`, which belongs to a different\n * package entirely.\n *\n * @example\n * ```ts\n * const { content, report } = convertTopolTemplate(design);\n * const editor = init({ container: '#editor', content });\n * console.log(report.summary);\n * ```\n */\nexport function convertTopolTemplate(\n design: TopolDesign | string,\n): ImportResult {\n const root: unknown = typeof design === \"string\" ? safeParse(design) : design;\n\n if (typeof root !== \"object\" || root === null || Array.isArray(root)) {\n throw new Error(INVALID_DESIGN_MESSAGE);\n }\n\n const topolDesign = root as TopolDesign;\n\n if (topolDesign.tagName !== ROOT_TAG) {\n // Topol's REST APIs wrap the design rather than returning it bare: the\n // template-retrieval endpoint uses \"definition\", the predefined/premade\n // templates endpoint uses \"json\" (alongside \"html\"). A response object\n // passed here by mistake still carries one of those keys, so that\n // mistake is named directly instead of falling through to the generic\n // wrong-tagName message below.\n if (\"json\" in topolDesign || \"definition\" in topolDesign) {\n throw new Error(INVALID_DESIGN_MESSAGE);\n }\n throw new Error(\n `Invalid Topol template: expected a root node with tagName \"${ROOT_TAG}\".`,\n );\n }\n\n const container = (topolDesign.children ?? []).find(\n (child) => child.tagName === \"mj-container\",\n );\n\n const entries: ImportReportEntry[] = [];\n const warnings: string[] = [];\n const blocks: Block[] = [];\n\n const style = readGlobalStyle(topolDesign, container, warnings);\n\n const ctx: MapContext = {\n style,\n columnWidth: style.settings.width,\n };\n\n for (const child of container?.children ?? []) {\n if (child.tagName !== \"mj-section\") continue;\n blocks.push(...buildSection(child, ctx, entries));\n }\n\n if (blocks.length === 0) {\n warnings.push(EMPTY_DESIGN_WARNING);\n }\n\n const content: TemplateContent = {\n ...createDefaultTemplateContent(),\n blocks,\n settings: style.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\nfunction safeParse(source: string): unknown {\n try {\n return JSON.parse(source);\n } catch {\n throw new Error(INVALID_DESIGN_MESSAGE);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/attribute-parser.ts","../src/global-style.ts","../src/content-inference.ts","../src/block-mapper.ts","../src/social-mapper.ts","../src/section-builder.ts","../src/converter.ts"],"sourcesContent":["import type {\n BackgroundImage,\n BackgroundPositionX,\n BackgroundPositionY,\n BackgroundSize,\n SpacingValue,\n} from \"@templatical/types\";\nimport type { TopolNode } from \"./types\";\n\n/**\n * Read one attribute as a string, treating Topol's explicit `null` — and an\n * empty string — as absent.\n *\n * Topol writes literal `null` for an unset attribute rather than omitting the\n * key, so a `!== undefined` check would report it as present and write a null\n * into the block. Every optional-field read goes through here.\n */\nexport function attr(node: TopolNode, key: string): string | undefined {\n const raw = node.attributes?.[key];\n if (raw === null || raw === undefined) return undefined;\n if (typeof raw === \"object\") return undefined;\n const value = String(raw);\n return value === \"\" ? undefined : value;\n}\n\n/** Read one attribute as a number, or `undefined` when unset or non-numeric. */\nexport function numAttr(node: TopolNode, key: string): number | undefined {\n const value = attr(node, key);\n if (value === undefined) return undefined;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\n/**\n * Parse a px-like value into a rounded integer. Returns 0 for a missing value\n * and for units the block model cannot express (em, rem, %).\n *\n * Trims first, then matches with no `\\s*` on either side of the optional\n * `px` unit. A trailing run of spaces can otherwise be claimed by either the\n * surrounding `\\s*` or by backtracking out of the digit/unit match, and a\n * *failing* match (no `px`, no digits — anything not shaped like a length)\n * retries every such split before giving up: `\"0\" + \" \".repeat(n) + \"x\"` is\n * polynomial in `n` against that pattern. Trimming first removes the only\n * whitespace the pattern needs to tolerate, so the match itself has nothing\n * ambiguous left to backtrack over.\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 trimmed = value.trim();\n const match = /^(-?\\d+(?:\\.\\d+)?)(?:px)?$/.exec(trimmed);\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 * Normalize a colour to 6-digit lowercase hex.\n *\n * Returns `\"\"` for transparent/inherit/none and for anything unrecognised —\n * the empty string is the block model's \"unset\", which the colour pickers\n * clear to, so 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 if (/^#[0-9a-f]{6}$/.test(trimmed)) return trimmed;\n if (/^#[0-9a-f]{3}$/.test(trimmed)) {\n const [, r, g, b] = trimmed;\n return `#${r}${r}${g}${g}${b}${b}`;\n }\n const rgb = trimmed.match(\n /^rgba?\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*(?:,\\s*[\\d.]+\\s*)?\\)$/,\n );\n if (rgb) return rgbToHex(+rgb[1], +rgb[2], +rgb[3]);\n return NAMED_COLORS[trimmed] ?? \"\";\n}\n\nfunction expandShorthand(value: string | undefined): SpacingValue {\n if (!value) return { top: 0, right: 0, bottom: 0, left: 0 };\n const v = value\n .trim()\n .split(/\\s+/)\n .map((p) => parsePxValue(p));\n switch (v.length) {\n case 1:\n return { top: v[0], right: v[0], bottom: v[0], left: v[0] };\n case 2:\n return { top: v[0], right: v[1], bottom: v[0], left: v[1] };\n case 3:\n return { top: v[0], right: v[1], bottom: v[2], left: v[1] };\n default:\n return { top: v[0], right: v[1], bottom: v[2], left: v[3] };\n }\n}\n\n/**\n * Read a node's padding, handling both forms Topol emits: a shorthand string\n * (`\"9px 9px 9px 9px\"`, used by most leaves) and four separate bare-number\n * keys (`padding-top: 24`, used by `mj-divider`). A separate key wins over the\n * shorthand for its own side.\n */\nexport function parsePadding(node: TopolNode): SpacingValue {\n const base = expandShorthand(attr(node, \"padding\"));\n const side = (key: string, fallback: number) => {\n const value = attr(node, key);\n return value === undefined ? fallback : parsePxValue(value);\n };\n return {\n top: side(\"padding-top\", base.top),\n right: side(\"padding-right\", base.right),\n bottom: side(\"padding-bottom\", base.bottom),\n left: side(\"padding-left\", base.left),\n };\n}\n\n/**\n * Read a percentage. Returns `null` when the value is not a percentage, so a\n * caller can tell \"no width given\" from \"0%\" — column matching depends on that\n * distinction.\n */\nexport function parsePercent(\n value: string | number | undefined,\n): number | null {\n if (value === undefined || value === null || value === \"\") return null;\n if (typeof value === \"number\") return Number.isFinite(value) ? value : null;\n const match = value.trim().match(/^(\\d+(?:\\.\\d+)?)\\s*%$/);\n return match ? parseFloat(match[1]) : null;\n}\n\n/** Narrow an alignment to the three the block model accepts. */\nexport function parseAlignment(\n value: string | undefined,\n fallback: \"left\" | \"center\" | \"right\" = \"left\",\n): \"left\" | \"center\" | \"right\" {\n const v = (value ?? \"\").trim().toLowerCase();\n return v === \"left\" || v === \"center\" || v === \"right\" ? v : fallback;\n}\n\n/** Narrow a border style to the three `DividerBlock.lineStyle` accepts. */\nexport function parseBorderStyle(\n value: string | undefined,\n): \"solid\" | \"dashed\" | \"dotted\" {\n const v = (value ?? \"\").trim().toLowerCase();\n return v === \"dashed\" || v === \"dotted\" ? v : \"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 {\n canonicalFontStack,\n DEFAULT_TEMPLATE_DEFAULTS,\n} from \"@templatical/types\";\nimport type { TemplateContent, TemplateSettings } from \"@templatical/types\";\nimport { attr, parseColor, parsePxValue } from \"./attribute-parser\";\nimport type { TopolDesign, TopolNode } from \"./types\";\n\n/**\n * `DEFAULT_TEMPLATE_DEFAULTS` is typed `Partial<TemplateSettings>` so a\n * consumer can override any subset, but its own literal always sets the six\n * required fields. Narrowing once here is what lets each read come out as\n * `number`/`string`/`boolean` rather than `| undefined`. A plain typed\n * assignment cannot substitute: `Partial<T>` is not assignable to\n * `Required<Pick<T, …>>`, so `tsc` rejects it outright.\n */\nconst REQUIRED_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\nexport interface GlobalStyle {\n settings: TemplateContent[\"settings\"];\n /** From nested objects like `\"mj-text\": { … }`. */\n tagDefaults: Record<string, Record<string, string>>;\n /** From flat `<selector>:<property>` keys; the bare selector is `\"\"`. */\n selectorDefaults: Record<string, Record<string, string>>;\n}\n\n/** A per-tag default, or undefined when the design sets none. */\nexport function tagDefault(\n style: GlobalStyle,\n tag: string,\n key: string,\n): string | undefined {\n return style.tagDefaults[tag]?.[key];\n}\n\n/** A per-selector default (`\"\"` is the document default), or undefined. */\nexport function selectorDefault(\n style: GlobalStyle,\n selector: string,\n key: string,\n): string | undefined {\n return style.selectorDefaults[selector]?.[key];\n}\n\n/**\n * Split the root's `attributes` into settings, per-tag defaults and\n * per-selector defaults.\n *\n * The root mixes two idioms: `<selector>:<property>` flat keys (`:color`,\n * `a:color`, `h1:font-family`) and `<tag>: { … }` nested objects, which are\n * the analogue of MJML's `mj-attributes`.\n */\nexport function readGlobalStyle(\n root: TopolDesign,\n container: TopolNode | undefined,\n warnings: string[],\n): GlobalStyle {\n const tagDefaults: Record<string, Record<string, string>> = Object.create(\n null,\n );\n const selectorDefaults: Record<\n string,\n Record<string, string>\n > = Object.create(null);\n\n for (const [key, value] of Object.entries(root.attributes ?? {})) {\n if (value !== null && typeof value === \"object\") {\n const bucket = (tagDefaults[key] ??= Object.create(null));\n for (const [k, v] of Object.entries(value)) {\n if (v !== null && v !== undefined) bucket[k] = String(v);\n }\n continue;\n }\n\n const colon = key.indexOf(\":\");\n // No colon means a root-level setting rather than a selector default —\n // `containerWidth` and `fonts` are the two real cases, and both are read\n // directly by name below instead of through the caches built here.\n if (colon === -1) continue;\n const selector = key.slice(0, colon);\n const property = key.slice(colon + 1);\n if (value === null || value === undefined) continue;\n const bucket = (selectorDefaults[selector] ??= Object.create(null));\n bucket[property] = String(value);\n }\n\n // containerWidth is authored only here, on the root. It is also denormalised\n // onto every leaf as the enclosing column's computed width (600/300/150 in\n // one template), so no other module may read it.\n const width =\n parsePxValue(attr(root, \"containerWidth\")) || REQUIRED_DEFAULTS.width;\n\n const backgroundColor =\n (container ? parseColor(attr(container, \"background-color\")) : \"\") ||\n REQUIRED_DEFAULTS.backgroundColor;\n\n const textColor =\n parseColor(selectorDefaults[\"\"]?.color) || REQUIRED_DEFAULTS.textColor;\n\n const fontFamily =\n canonicalFontStack(selectorDefaults[\"\"]?.[\"font-family\"]) ||\n canonicalFontStack(attr(root, \"fonts\")) ||\n REQUIRED_DEFAULTS.fontFamily;\n\n const linkColor = parseColor(selectorDefaults[\"a\"]?.color);\n\n const lineHeight = selectorDefaults[\"\"]?.[\"line-height\"];\n if (lineHeight !== undefined) {\n warnings.push(\n `Dropped the document line-height (${lineHeight}) — Templatical has no document-level line-height setting.`,\n );\n }\n\n const settings: TemplateContent[\"settings\"] = {\n width,\n backgroundColor,\n textColor,\n linkUnderline: REQUIRED_DEFAULTS.linkUnderline,\n fontFamily,\n locale: REQUIRED_DEFAULTS.locale,\n ...(linkColor ? { linkColor } : {}),\n };\n\n return { settings, tagDefaults, selectorDefaults };\n}\n","import { findOpenTagEnd } from \"@templatical/types\";\nimport type { HeadingLevel } from \"@templatical/types\";\n\nexport type InferredText =\n | { kind: \"title\"; level: HeadingLevel; inner: string; clampedFrom?: number }\n | { kind: \"paragraph\"; html: string };\n\n/**\n * A single heading element wrapping the whole content, captured with its level\n * and inner markup. Anchored at both ends so a heading with a sibling does not\n * match — that case is a paragraph.\n */\nconst SOLE_HEADING = /^<h([1-6])(?:\\s[^>]*)?>([\\s\\S]*)<\\/h\\1>$/i;\n\nconst BLOCK_ROOT = /^<(p|h[1-6]|ul|ol|blockquote|div|table)\\b/i;\n\n/**\n * Resolve a Topol `content` string to a Title or a Paragraph.\n *\n * Topol has no menu or table construct, so this is a two-way choice — the MJML\n * importer needs four arms because `mj-text` is the output of four different\n * block renderers there. Paragraph is the terminal arm and always reachable,\n * so this function is total.\n */\nexport function inferTextBlock(content: string | undefined): InferredText {\n const trimmed = (content ?? \"\").trim();\n if (trimmed === \"\") return { kind: \"paragraph\", html: \"<p></p>\" };\n\n const heading = trimmed.match(SOLE_HEADING);\n if (heading) {\n const source = Number(heading[1]);\n const level = Math.min(source, 4) as HeadingLevel;\n return {\n kind: \"title\",\n level,\n inner: heading[2].trim(),\n ...(source > 4 ? { clampedFrom: source } : {}),\n };\n }\n\n return {\n kind: \"paragraph\",\n html: BLOCK_ROOT.test(trimmed) ? trimmed : `<p>${trimmed}</p>`,\n };\n}\n\nconst ENTITIES: Record<string, string> = {\n \"&amp;\": \"&\",\n \"&lt;\": \"<\",\n \"&gt;\": \">\",\n \"&quot;\": '\"',\n \"&#39;\": \"'\",\n \"&apos;\": \"'\",\n \"&nbsp;\": \" \",\n};\n\n/**\n * Remove every `<tag>` from `html`, keeping the text between them.\n *\n * A single-pass linear scan rather than `/<[^>]*>/g`: that regex is\n * polynomial over input with many `<` starts and no closing `>`, since\n * `[^>]*` backtracks at every one of them — `\"<\".repeat(n)` is quadratic in\n * `n`. `findOpenTagEnd` (from `@templatical/types`) resolves each `<` with a\n * bounded, quote-aware forward scan instead, so the walk stays O(n): every\n * character is visited at most once by `indexOf` and once by\n * `findOpenTagEnd`, because the cursor only moves forward and an\n * unterminated tag ends the walk rather than retrying from inside it. On\n * that unterminated case the remainder of the string — the dangling `<`\n * included — is emitted as-is, matching what `/<[^>]*>/g` does with\n * malformed markup: it can never complete a match without a `>`, so the\n * text is left untouched from that point on.\n */\nfunction removeTags(html: string): string {\n let out = \"\";\n let i = 0;\n while (i < html.length) {\n const open = html.indexOf(\"<\", i);\n if (open === -1) {\n out += html.substring(i);\n break;\n }\n out += html.substring(i, open);\n const tagEnd = findOpenTagEnd(html, open + 1);\n if (tagEnd === -1) {\n out += html.substring(open);\n break;\n }\n i = tagEnd + 1;\n }\n return out;\n}\n\n/**\n * The visible text of a markup fragment, for a button label — `ButtonBlock.text`\n * is a plain string, so the markup Topol stores in `content` cannot travel.\n */\nexport function stripTags(html: string | undefined): string {\n if (!html) return \"\";\n const text = removeTags(html);\n const decoded = text.replace(\n /&(?:amp|lt|gt|quot|#39|apos|nbsp);/g,\n (m) => ENTITIES[m] ?? m,\n );\n return decoded.replace(/\\s+/g, \" \").trim();\n}\n","import {\n canonicalFontStack,\n createButtonBlock,\n createDividerBlock,\n createHtmlBlock,\n createImageBlock,\n createParagraphBlock,\n createSpacerBlock,\n createTitleBlock,\n} from \"@templatical/types\";\nimport type { Block, BlockStyles } from \"@templatical/types\";\nimport {\n attr,\n numAttr,\n parseAlignment,\n parseBorderStyle,\n parseColor,\n parsePadding,\n parsePxValue,\n} from \"./attribute-parser\";\nimport { inferTextBlock, stripTags } from \"./content-inference\";\nimport { selectorDefault, tagDefault, type GlobalStyle } from \"./global-style\";\nimport type { ImportReportEntry, TopolNode } from \"./types\";\n\nexport interface MapContext {\n style: GlobalStyle;\n /** The rendered width of the column holding this node, for `width: \"full\"`. */\n columnWidth: number;\n}\n\nexport interface Converted {\n block: Block | null;\n entry: ImportReportEntry;\n}\n\n/**\n * Read a node's own attribute, falling back to the design's per-tag then\n * per-selector default. Returns undefined when nothing sets it, so the caller\n * can omit the field and let the block factory's default stand.\n */\nfunction resolved(\n node: TopolNode,\n key: string,\n ctx: MapContext,\n selector?: string,\n): string | undefined {\n return (\n attr(node, key) ??\n tagDefault(ctx.style, node.tagName, key) ??\n (selector === undefined\n ? undefined\n : selectorDefault(ctx.style, selector, key))\n );\n}\n\n/**\n * The `styles` every leaf block shares.\n *\n * The block background is the row behind the element, which MJML reads from\n * `container-background-color` on every leaf and which the renderer writes\n * `styles.backgroundColor` back out as. A leaf's `background-color` must not\n * feed it: on `mj-button` it is the button face (`backgroundColor`, read in\n * `convertButton`), on `mj-text` MJML never paints it, and on image, spacer,\n * divider and social it is invalid and dropped. Reading it here paints a\n * button's whole row in its fill colour. `buildSection` reads\n * `background-color` itself, since on `mj-section` it is the section's fill.\n */\nexport function baseStyles(node: TopolNode): { styles: BlockStyles } {\n const backgroundColor = parseColor(attr(node, \"container-background-color\"));\n return {\n styles: {\n padding: parsePadding(node),\n ...(backgroundColor ? { backgroundColor } : {}),\n },\n };\n}\n\nfunction convertText(node: TopolNode, ctx: MapContext): Converted {\n const inferred = inferTextBlock(node.content);\n\n if (inferred.kind === \"paragraph\") {\n return {\n block: createParagraphBlock({\n content: inferred.html,\n ...baseStyles(node),\n }),\n entry: {\n sourceTag: \"mj-text\",\n templaticalBlockType: \"paragraph\",\n status: \"converted\",\n },\n };\n }\n\n const selector = `h${inferred.level}`;\n const color = parseColor(resolved(node, \"color\", ctx, selector));\n const fontFamily = canonicalFontStack(\n resolved(node, \"font-family\", ctx, selector),\n );\n\n const block = createTitleBlock({\n content: inferred.inner,\n level: inferred.level,\n textAlign: parseAlignment(attr(node, \"align\"), \"left\"),\n ...(color ? { color } : {}),\n ...(fontFamily ? { fontFamily } : {}),\n ...baseStyles(node),\n });\n\n return {\n block,\n entry: {\n sourceTag: \"mj-text\",\n templaticalBlockType: \"title\",\n status: inferred.clampedFrom ? \"approximated\" : \"converted\",\n ...(inferred.clampedFrom\n ? {\n note: `Heading level h${inferred.clampedFrom} clamped to 4 — Templatical titles support h1-h4.`,\n }\n : {}),\n },\n };\n}\n\nfunction convertButton(node: TopolNode, ctx: MapContext): Converted | null {\n const text = stripTags(node.content);\n if (!text) return null;\n\n const backgroundColor = parseColor(\n resolved(node, \"background-color\", ctx, \"button\"),\n );\n const textColor = parseColor(resolved(node, \"color\", ctx, \"button\"));\n const fontFamily = canonicalFontStack(\n resolved(node, \"font-family\", ctx, \"button\"),\n );\n const fontSizeRaw = resolved(node, \"font-size\", ctx);\n const radiusRaw = resolved(node, \"border-radius\", ctx);\n\n return {\n block: createButtonBlock({\n text,\n url: attr(node, \"href\") ?? \"\",\n ...(backgroundColor ? { backgroundColor } : {}),\n ...(textColor ? { textColor } : {}),\n ...(fontFamily ? { fontFamily } : {}),\n ...(fontSizeRaw === undefined\n ? {}\n : { fontSize: parsePxValue(fontSizeRaw) }),\n ...(radiusRaw === undefined\n ? {}\n : { borderRadius: parsePxValue(radiusRaw) }),\n align: parseAlignment(attr(node, \"align\"), \"center\"),\n ...baseStyles(node),\n }),\n entry: {\n sourceTag: node.tagName,\n templaticalBlockType: \"button\",\n status: \"converted\",\n },\n };\n}\n\nfunction convertImage(\n node: TopolNode,\n ctx: MapContext,\n sourceTag: string,\n): Converted | null {\n const src = attr(node, \"src\");\n if (!src) return null;\n\n const widthPercent = numAttr(node, \"widthPercent\");\n const pxWidth = numAttr(node, \"width\");\n const href = attr(node, \"href\");\n const base = baseStyles(node);\n // MJML caps an image at its column less its own side padding, so a px width\n // that reaches that renders as \"full\" does.\n const room =\n ctx.columnWidth - base.styles.padding.left - base.styles.padding.right;\n\n const width =\n widthPercent === 100 || pxWidth === undefined || pxWidth >= room\n ? (\"full\" as const)\n : Math.round(pxWidth);\n\n const isGif = sourceTag === \"mj-gif\";\n\n return {\n block: createImageBlock({\n src,\n alt: attr(node, \"alt\") ?? \"\",\n width,\n align: parseAlignment(attr(node, \"align\"), \"center\"),\n ...(href ? { linkUrl: href } : {}),\n ...base,\n }),\n entry: {\n sourceTag,\n templaticalBlockType: \"image\",\n status: isGif ? \"approximated\" : \"converted\",\n ...(isGif\n ? {\n note: \"<mj-gif> imported as an image block; Templatical has no dedicated GIF block.\",\n }\n : {}),\n },\n };\n}\n\nfunction convertSpacer(node: TopolNode): Converted {\n const height = attr(node, \"height\");\n return {\n block: createSpacerBlock({\n ...(height === undefined ? {} : { height: parsePxValue(height) }),\n ...baseStyles(node),\n }),\n entry: {\n sourceTag: \"mj-spacer\",\n templaticalBlockType: \"spacer\",\n status: \"converted\",\n },\n };\n}\n\nfunction convertDivider(node: TopolNode): Converted {\n const color = parseColor(attr(node, \"border-color\"));\n const thickness = attr(node, \"border-width\");\n return {\n block: createDividerBlock({\n lineStyle: parseBorderStyle(attr(node, \"border-style\")),\n ...(color ? { color } : {}),\n ...(thickness === undefined\n ? {}\n : { thickness: parsePxValue(thickness) }),\n ...baseStyles(node),\n }),\n entry: {\n sourceTag: \"mj-divider\",\n templaticalBlockType: \"divider\",\n status: \"converted\",\n },\n };\n}\n\n/**\n * Convert one leaf node to a block.\n *\n * Returns `null` for a node that produces nothing *and* warrants no report\n * entry — an image with no `src`, a button with no label. `mj-social` is not\n * handled here; the converter routes it to `convertSocial`.\n */\nexport function convertLeaf(\n node: TopolNode,\n ctx: MapContext,\n): Converted | null {\n switch (node.tagName) {\n case \"mj-text\":\n return convertText(node, ctx);\n case \"mj-button\":\n return convertButton(node, ctx);\n case \"mj-image\":\n return convertImage(node, ctx, \"mj-image\");\n case \"mj-gif\":\n return convertImage(node, ctx, \"mj-gif\");\n case \"mj-spacer\":\n return convertSpacer(node);\n case \"mj-divider\":\n return convertDivider(node);\n default:\n return {\n block: createHtmlBlock({\n content: JSON.stringify(node, null, 2),\n ...baseStyles(node),\n }),\n entry: {\n sourceTag: node.tagName,\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n note: `<${node.tagName}> has no Templatical block equivalent; the original node is preserved as JSON.`,\n },\n };\n }\n}\n","import { createSocialIconsBlock, generateId } from \"@templatical/types\";\nimport type {\n SocialIcon,\n SocialIconTone,\n SocialIconSize,\n SocialIconStyle,\n SocialPlatform,\n} from \"@templatical/types\";\nimport { attr, parseAlignment, parsePxValue } from \"./attribute-parser\";\nimport { baseStyles, type Converted, type MapContext } from \"./block-mapper\";\nimport type { TopolNode } from \"./types\";\n\n/**\n * Exhaustive over `SocialPlatform` on purpose: adding a member to that union\n * without adding it here is a compile error, so this cannot silently fall back\n * 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 ALIASES: Record<string, SocialPlatform> = {\n x: \"twitter\",\n \"x-twitter\": \"twitter\",\n};\n\nconst ICON_SIZES: Array<[number, SocialIconSize]> = [\n [24, \"small\"],\n [32, \"medium\"],\n [48, \"large\"],\n];\n\nconst KNOWN_STYLES = new Set<string>([\n \"solid\",\n \"outlined\",\n \"rounded\",\n \"square\",\n \"circle\",\n]);\n\n// Topol ships icon sets whose folder name is not a Templatical style name.\n// `outlinedbw` is the outlined set in black and white, so it imports as\n// outlined in the dark tone; mapping it to the `solid` fallback would import\n// an outlined icon set as filled. Measured: both `/social-icos/outlined/` and\n// `/social-icos/outlinedbw/` appear across the reference exports.\nconst STYLE_ALIASES: Record<\n string,\n { style: SocialIconStyle; tone: SocialIconTone }\n> = {\n outlinedbw: { style: \"outlined\", tone: \"dark\" },\n};\n\nfunction normalizePlatform(raw: string): SocialPlatform | null {\n const cleaned = raw.trim().toLowerCase();\n if (!cleaned) return null;\n if (ALIASES[cleaned]) return ALIASES[cleaned];\n return cleaned in KNOWN_PLATFORMS ? (cleaned as SocialPlatform) : null;\n}\n\nfunction nearestSize(px: number): { size: SocialIconSize; exact: boolean } {\n let best = ICON_SIZES[1];\n let gap = Infinity;\n for (const candidate of ICON_SIZES) {\n const d = Math.abs(candidate[0] - px);\n if (d < gap) {\n gap = d;\n best = candidate;\n }\n }\n return { size: best[1], exact: gap === 0 };\n}\n\n/** The style (and tone) the segment of `https://…/social-icos/<style>/` names. */\nfunction styleFromBaseUrl(\n baseUrl: string | undefined,\n): { style: SocialIconStyle; tone?: SocialIconTone } | undefined {\n if (!baseUrl) return undefined;\n const segment = baseUrl.split(\"?\")[0].split(\"/\").filter(Boolean).at(-1) ?? \"\";\n if (KNOWN_STYLES.has(segment)) return { style: segment as SocialIconStyle };\n return STYLE_ALIASES[segment];\n}\n\n/**\n * Convert Topol's MJML v3 social block.\n *\n * `display` decides which icons render and in what order; a platform with\n * `*-href` set but absent from `display` was not shown in the email and is\n * not imported.\n */\nexport function convertSocial(\n node: TopolNode,\n _ctx: MapContext,\n): Converted | null {\n const display = attr(node, \"display\");\n if (!display) return null;\n\n const notes: string[] = [];\n const icons: SocialIcon[] = [];\n\n for (const token of display.trim().split(/\\s+/)) {\n const rawName = token.split(\":\")[0];\n if (!rawName) continue;\n\n const url = attr(node, `${rawName}-href`);\n if (!url) continue;\n\n const platform = normalizePlatform(rawName);\n if (!platform) {\n notes.push(\n `Unrecognised social platform \"${rawName}\" mapped to \"website\".`,\n );\n }\n\n icons.push({ id: generateId(), platform: platform ?? \"website\", url });\n }\n\n if (icons.length === 0) return null;\n\n const fromBaseUrl = styleFromBaseUrl(attr(node, \"base-url\"));\n\n const declaredSize = attr(node, \"icon-size\");\n let iconSize: SocialIconSize | undefined;\n if (declaredSize !== undefined) {\n const px = parsePxValue(declaredSize);\n const resolved = nearestSize(px);\n iconSize = resolved.size;\n if (!resolved.exact) {\n notes.push(\n `Icon size ${declaredSize} is not one of 24/32/48px; resolved to \"${resolved.size}\".`,\n );\n }\n }\n\n return {\n block: createSocialIconsBlock({\n icons,\n align: parseAlignment(attr(node, \"align\"), \"center\"),\n ...(iconSize ? { iconSize } : {}),\n ...(fromBaseUrl ? { iconStyle: fromBaseUrl.style } : {}),\n ...(fromBaseUrl?.tone ? { iconTone: fromBaseUrl.tone } : {}),\n ...baseStyles(node),\n }),\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","import { createSectionBlock } from \"@templatical/types\";\nimport type { Block, ColumnLayout } from \"@templatical/types\";\nimport {\n attr,\n parseColor,\n parsePadding,\n parsePercent,\n readMjmlBackgroundImage,\n} from \"./attribute-parser\";\nimport { convertLeaf, type MapContext } from \"./block-mapper\";\nimport { convertSocial } from \"./social-mapper\";\nimport type { ImportReportEntry, TopolNode } from \"./types\";\n\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\nconst WIDTH_TOLERANCE = 2;\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\nfunction columnPixels(layout: ColumnLayout, container: number): number[] {\n switch (layout) {\n case \"2\":\n return [container / 2, container / 2];\n case \"3\":\n return [container / 3, container / 3, container / 3];\n case \"1-2\":\n return [container / 3, (container * 2) / 3];\n case \"2-1\":\n return [(container * 2) / 3, container / 3];\n default:\n return [container];\n }\n}\n\n/**\n * Resolve column widths to one of the five layouts `ColumnLayout` allows.\n *\n * `exact: false` means the caller reports `approximated` — Topol permits any\n * column count at any width and this union permits five shapes, so this is the\n * importer's main irreducible loss.\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 // All-unset is \"distribute equally\", which is exactly what the n-column\n // 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 const known = percents.filter((p): p is number => p !== null);\n const unsetCount = count - known.length;\n const remainder =\n unsetCount === 0\n ? 0\n : Math.max(0, 100 - known.reduce((a, b) => a + b, 0)) / unsetCount;\n const resolved = percents.map((p) => p ?? remainder);\n\n const sameCount = LAYOUT_SHAPES.filter((s) => s.percents.length === count);\n\n for (const shape of sameCount) {\n if (\n shape.percents.every(\n (want, i) => Math.abs(want - resolved[i]) <= WIDTH_TOLERANCE,\n )\n ) {\n return { layout: shape.layout, exact: true };\n }\n }\n\n const candidates =\n sameCount.length > 0\n ? sameCount\n : LAYOUT_SHAPES.filter((s) => s.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\nfunction convertColumnChildren(\n column: TopolNode,\n ctx: MapContext,\n entries: ImportReportEntry[],\n): Block[] {\n const blocks: Block[] = [];\n for (const child of column.children ?? []) {\n const converted =\n child.tagName === \"mj-social\"\n ? convertSocial(child, ctx)\n : convertLeaf(child, ctx);\n if (!converted) continue;\n entries.push(converted.entry);\n if (converted.block) blocks.push(converted.block);\n }\n return blocks;\n}\n\n/**\n * Build a `SectionBlock` from an `mj-section`. Returns an array so the caller\n * can treat an empty section (no columns) as producing nothing.\n *\n * Reads `attributes` only. The node's top-level camelCase keys\n * (`backgroundColor`, `paddingTop`, …) are stale editor state that disagrees\n * with `attributes` in every measured case.\n */\nexport function buildSection(\n node: TopolNode,\n ctx: MapContext,\n entries: ImportReportEntry[],\n): Block[] {\n const columns = (node.children ?? []).filter(\n (c) => c.tagName === \"mj-column\",\n );\n if (columns.length === 0) return [];\n\n const rawWidths = columns.map((c) => attr(c, \"width\"));\n const percents = rawWidths.map((w) => parsePercent(w));\n const { layout, exact } = matchColumnLayout(percents);\n\n const slots = COLUMN_COUNT[layout];\n const pixels = columnPixels(layout, ctx.style.settings.width);\n const children: Block[][] = Array.from({ length: slots }, () => []);\n\n const background = readMjmlBackgroundImage(\n (name) => attr(node, name),\n \"Section\",\n ctx.style.settings.width,\n );\n const notes = [\n ...(exact\n ? []\n : [\n `Column widths ${rawWidths.map((w) => w ?? \"auto\").join(\", \")} have no exact Templatical layout; resolved to \"${layout}\".`,\n ]),\n ...(background?.notes ?? []),\n ];\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 columns.forEach((column, index) => {\n const slot = Math.min(index, slots - 1);\n const columnCtx: MapContext = {\n ...ctx,\n columnWidth: Math.round(pixels[slot] ?? ctx.style.settings.width),\n };\n children[slot].push(...convertColumnChildren(column, columnCtx, entries));\n });\n\n const backgroundColor = parseColor(attr(node, \"background-color\"));\n\n return [\n createSectionBlock({\n columns: layout,\n children,\n styles: {\n padding: parsePadding(node),\n ...(backgroundColor ? { backgroundColor } : {}),\n },\n ...(background ? { backgroundImage: background.image } : {}),\n }),\n ];\n}\n","import { createDefaultTemplateContent } from \"@templatical/types\";\nimport type { Block, TemplateContent } from \"@templatical/types\";\nimport { readGlobalStyle } from \"./global-style\";\nimport { buildSection } from \"./section-builder\";\nimport type { MapContext } from \"./block-mapper\";\nimport type {\n ImportReport,\n ImportReportEntry,\n ImportResult,\n TopolDesign,\n} from \"./types\";\n\nconst ROOT_TAG = \"mj-global-style\";\n\nconst INVALID_DESIGN_MESSAGE =\n 'Invalid Topol template: expected the design JSON object — Topol hands you this directly from its editor\\'s onSave callback, or under \"definition\" (template API) or \"json\" (predefined-templates API) in its API response.';\n\nconst EMPTY_DESIGN_WARNING =\n \"No convertible content was found in the Topol design. Check that the mj-container holds at least one mj-section.\";\n\n/**\n * Convert a Topol.io design into a Templatical template.\n *\n * The input is the design object itself — the node whose `tagName` is\n * `mj-global-style`, not a whole API response. Topol's editor hands it to\n * you directly as the first argument to `onSave`; its REST APIs wrap it\n * instead, under `definition` on the template-retrieval endpoint or `json`\n * (alongside `html`) on the predefined-templates endpoint. That is\n * documented rather than sniffed: guessing which field holds the design\n * risks importing an envelope's `html`, which belongs to a different\n * package entirely.\n *\n * @example\n * ```ts\n * const { content, report } = convertTopolTemplate(design);\n * const editor = init({ container: '#editor', content });\n * console.log(report.summary);\n * ```\n */\nexport function convertTopolTemplate(\n design: TopolDesign | string,\n): ImportResult {\n const root: unknown = typeof design === \"string\" ? safeParse(design) : design;\n\n if (typeof root !== \"object\" || root === null || Array.isArray(root)) {\n throw new Error(INVALID_DESIGN_MESSAGE);\n }\n\n const topolDesign = root as TopolDesign;\n\n if (topolDesign.tagName !== ROOT_TAG) {\n // Topol's REST APIs wrap the design rather than returning it bare: the\n // template-retrieval endpoint uses \"definition\", the predefined/premade\n // templates endpoint uses \"json\" (alongside \"html\"). A response object\n // passed here by mistake still carries one of those keys, so that\n // mistake is named directly instead of falling through to the generic\n // wrong-tagName message below.\n if (\"json\" in topolDesign || \"definition\" in topolDesign) {\n throw new Error(INVALID_DESIGN_MESSAGE);\n }\n throw new Error(\n `Invalid Topol template: expected a root node with tagName \"${ROOT_TAG}\".`,\n );\n }\n\n const container = (topolDesign.children ?? []).find(\n (child) => child.tagName === \"mj-container\",\n );\n\n const entries: ImportReportEntry[] = [];\n const warnings: string[] = [];\n const blocks: Block[] = [];\n\n const style = readGlobalStyle(topolDesign, container, warnings);\n\n const ctx: MapContext = {\n style,\n columnWidth: style.settings.width,\n };\n\n for (const child of container?.children ?? []) {\n if (child.tagName !== \"mj-section\") continue;\n blocks.push(...buildSection(child, ctx, entries));\n }\n\n if (blocks.length === 0) {\n warnings.push(EMPTY_DESIGN_WARNING);\n }\n\n const content: TemplateContent = {\n ...createDefaultTemplateContent(),\n blocks,\n settings: style.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\nfunction safeParse(source: string): unknown {\n try {\n return JSON.parse(source);\n } catch {\n throw new Error(INVALID_DESIGN_MESSAGE);\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@templatical/import-topol",
3
3
  "description": "Convert Topol.io email templates to Templatical format",
4
- "version": "0.51.1",
4
+ "version": "0.52.1",
5
5
  "bugs": "https://github.com/templatical/sdk/issues",
6
6
  "dependencies": {
7
- "@templatical/types": "0.51.1"
7
+ "@templatical/types": "0.52.1"
8
8
  },
9
9
  "devDependencies": {
10
10
  "typescript": "^6.0.3",