@templatical/import-topol 0.52.1 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -51,11 +51,25 @@ interface TopolNode {
51
51
  [key: string]: unknown;
52
52
  }
53
53
  /** The design root — the node whose `tagName` is `mj-global-style`. */
54
+ interface TopolTranslation {
55
+ content?: string;
56
+ attributes?: Record<string, TopolAttrValue>;
57
+ }
58
+ interface TopolLanguage {
59
+ key: string;
60
+ primary?: boolean;
61
+ translations?: Record<string, TopolTranslation>;
62
+ }
54
63
  interface TopolDesign extends TopolNode {
64
+ langs?: TopolLanguage[];
55
65
  tagName: string;
56
66
  fonts?: unknown;
57
67
  style?: unknown;
58
68
  }
69
+ interface ConvertTopolOptions {
70
+ /** Used when the source declares no default language. */
71
+ defaultLocale?: string;
72
+ }
59
73
  /**
60
74
  * Convert a Topol.io design into a Templatical template.
61
75
  *
@@ -75,6 +89,6 @@ interface TopolDesign extends TopolNode {
75
89
  * console.log(report.summary);
76
90
  * ```
77
91
  */
78
- export declare function convertTopolTemplate(design: TopolDesign | string): ImportResult;
79
- export type { ConversionStatus, ImportReport, ImportReportEntry, ImportResult };
92
+ export declare function convertTopolTemplate(design: TopolDesign | string, options?: ConvertTopolOptions): ImportResult;
93
+ export type { ConversionStatus, ConvertTopolOptions, ImportReport, ImportReportEntry, ImportResult };
80
94
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,4 +1,56 @@
1
- import { DEFAULT_TEMPLATE_DEFAULTS, canonicalFontStack, createButtonBlock, createDefaultTemplateContent, createDividerBlock, createHtmlBlock, createImageBlock, createParagraphBlock, createSectionBlock, createSocialIconsBlock, createSpacerBlock, createTitleBlock, findOpenTagEnd, generateId } from "@templatical/types";
1
+ import { DEFAULT_TEMPLATE_DEFAULTS, canonicalFontStack, createButtonBlock, createDefaultTemplateContent, createDividerBlock, createHtmlBlock, createImageBlock, createParagraphBlock, createSectionBlock, createSocialIconsBlock, createSpacerBlock, createTitleBlock, findOpenTagEnd, generateId, isLocaleTag, sourceHash } from "@templatical/types";
2
+ // src/localization.ts
3
+ function warn(warnings, message) {
4
+ if (!warnings.includes(message)) warnings.push(message);
5
+ }
6
+ function localeTag(value, warnings) {
7
+ const locale = value.replaceAll("_", "-");
8
+ if (isLocaleTag(locale)) return locale;
9
+ warn(warnings, `Dropped language "${value}": unsupported language tag.`);
10
+ return null;
11
+ }
12
+ /** The `defaultLocale` option as a tag; `null`, with a warning, when it isn't one. */
13
+ function defaultLocaleOption(value, warnings) {
14
+ if (!value) return null;
15
+ const locale = value.replaceAll("_", "-");
16
+ if (isLocaleTag(locale)) return locale;
17
+ warn(warnings, `Ignored defaultLocale "${value}": unsupported language tag. Used "en".`);
18
+ return null;
19
+ }
20
+ function text(target, locale, path, base, value) {
21
+ if (base === void 0) {
22
+ set(target, locale, path, value);
23
+ return;
24
+ }
25
+ const layer = (target.l10n ??= {})[locale] ??= {};
26
+ (layer.text ??= {})[path] = {
27
+ value,
28
+ sourceHash: sourceHash(base)
29
+ };
30
+ }
31
+ function set(target, locale, path, value) {
32
+ const layer = (target.l10n ??= {})[locale] ??= {};
33
+ (layer.set ??= {})[path] = value;
34
+ }
35
+ /** Register only non-default versions, leaving single-language imports unchanged. */
36
+ function registerLocales(content) {
37
+ const locales = /* @__PURE__ */ new Set();
38
+ const visit = (target) => {
39
+ if (!target.l10n) return;
40
+ delete target.l10n[content.settings.locale];
41
+ for (const locale of Object.keys(target.l10n)) locales.add(locale);
42
+ if (Object.keys(target.l10n).length === 0) delete target.l10n;
43
+ };
44
+ visit(content.settings);
45
+ const walk = (blocks) => {
46
+ for (const block of blocks) {
47
+ visit(block);
48
+ if (block.type === "section") block.children.forEach(walk);
49
+ }
50
+ };
51
+ walk(content.blocks);
52
+ if (locales.size) content.settings.locales = [...locales];
53
+ }
2
54
  // src/attribute-parser.ts
3
55
  /**
4
56
  * Read one attribute as a string, treating Topol's explicit `null` — and an
@@ -356,70 +408,6 @@ function readMjmlBackgroundImage(read, label, boxWidth) {
356
408
  notes
357
409
  };
358
410
  }
359
- // src/global-style.ts
360
- /**
361
- * `DEFAULT_TEMPLATE_DEFAULTS` is typed `Partial<TemplateSettings>` so a
362
- * consumer can override any subset, but its own literal always sets the six
363
- * required fields. Narrowing once here is what lets each read come out as
364
- * `number`/`string`/`boolean` rather than `| undefined`. A plain typed
365
- * assignment cannot substitute: `Partial<T>` is not assignable to
366
- * `Required<Pick<T, …>>`, so `tsc` rejects it outright.
367
- */
368
- const REQUIRED_DEFAULTS = DEFAULT_TEMPLATE_DEFAULTS;
369
- /** A per-tag default, or undefined when the design sets none. */
370
- function tagDefault(style, tag, key) {
371
- return style.tagDefaults[tag]?.[key];
372
- }
373
- /** A per-selector default (`""` is the document default), or undefined. */
374
- function selectorDefault(style, selector, key) {
375
- return style.selectorDefaults[selector]?.[key];
376
- }
377
- /**
378
- * Split the root's `attributes` into settings, per-tag defaults and
379
- * per-selector defaults.
380
- *
381
- * The root mixes two idioms: `<selector>:<property>` flat keys (`:color`,
382
- * `a:color`, `h1:font-family`) and `<tag>: { … }` nested objects, which are
383
- * the analogue of MJML's `mj-attributes`.
384
- */
385
- function readGlobalStyle(root, container, warnings) {
386
- const tagDefaults = Object.create(null);
387
- const selectorDefaults = Object.create(null);
388
- for (const [key, value] of Object.entries(root.attributes ?? {})) {
389
- if (value !== null && typeof value === "object") {
390
- const bucket = tagDefaults[key] ??= Object.create(null);
391
- for (const [k, v] of Object.entries(value)) if (v !== null && v !== void 0) bucket[k] = String(v);
392
- continue;
393
- }
394
- const colon = key.indexOf(":");
395
- if (colon === -1) continue;
396
- const selector = key.slice(0, colon);
397
- const property = key.slice(colon + 1);
398
- if (value === null || value === void 0) continue;
399
- const bucket = selectorDefaults[selector] ??= Object.create(null);
400
- bucket[property] = String(value);
401
- }
402
- const width = parsePxValue(attr(root, "containerWidth")) || REQUIRED_DEFAULTS.width;
403
- const backgroundColor = (container ? parseColor(attr(container, "background-color")) : "") || REQUIRED_DEFAULTS.backgroundColor;
404
- const textColor = parseColor(selectorDefaults[""]?.color) || REQUIRED_DEFAULTS.textColor;
405
- const fontFamily = canonicalFontStack(selectorDefaults[""]?.["font-family"]) || canonicalFontStack(attr(root, "fonts")) || REQUIRED_DEFAULTS.fontFamily;
406
- const linkColor = parseColor(selectorDefaults["a"]?.color);
407
- const lineHeight = selectorDefaults[""]?.["line-height"];
408
- if (lineHeight !== void 0) warnings.push(`Dropped the document line-height (${lineHeight}) — Templatical has no document-level line-height setting.`);
409
- return {
410
- settings: {
411
- width,
412
- backgroundColor,
413
- textColor,
414
- linkUnderline: REQUIRED_DEFAULTS.linkUnderline,
415
- fontFamily,
416
- locale: REQUIRED_DEFAULTS.locale,
417
- ...linkColor ? { linkColor } : {}
418
- },
419
- tagDefaults,
420
- selectorDefaults
421
- };
422
- }
423
411
  // src/content-inference.ts
424
412
  /**
425
413
  * A single heading element wrapping the whole content, captured with its level
@@ -509,6 +497,70 @@ function stripTags(html) {
509
497
  if (!html) return "";
510
498
  return removeTags(html).replace(/&(?:amp|lt|gt|quot|#39|apos|nbsp);/g, (m) => ENTITIES[m] ?? m).replace(/\s+/g, " ").trim();
511
499
  }
500
+ // src/global-style.ts
501
+ /**
502
+ * `DEFAULT_TEMPLATE_DEFAULTS` is typed `Partial<TemplateSettings>` so a
503
+ * consumer can override any subset, but its own literal always sets the six
504
+ * required fields. Narrowing once here is what lets each read come out as
505
+ * `number`/`string`/`boolean` rather than `| undefined`. A plain typed
506
+ * assignment cannot substitute: `Partial<T>` is not assignable to
507
+ * `Required<Pick<T, …>>`, so `tsc` rejects it outright.
508
+ */
509
+ const REQUIRED_DEFAULTS = DEFAULT_TEMPLATE_DEFAULTS;
510
+ /** A per-tag default, or undefined when the design sets none. */
511
+ function tagDefault(style, tag, key) {
512
+ return style.tagDefaults[tag]?.[key];
513
+ }
514
+ /** A per-selector default (`""` is the document default), or undefined. */
515
+ function selectorDefault(style, selector, key) {
516
+ return style.selectorDefaults[selector]?.[key];
517
+ }
518
+ /**
519
+ * Split the root's `attributes` into settings, per-tag defaults and
520
+ * per-selector defaults.
521
+ *
522
+ * The root mixes two idioms: `<selector>:<property>` flat keys (`:color`,
523
+ * `a:color`, `h1:font-family`) and `<tag>: { … }` nested objects, which are
524
+ * the analogue of MJML's `mj-attributes`.
525
+ */
526
+ function readGlobalStyle(root, container, warnings) {
527
+ const tagDefaults = Object.create(null);
528
+ const selectorDefaults = Object.create(null);
529
+ for (const [key, value] of Object.entries(root.attributes ?? {})) {
530
+ if (value !== null && typeof value === "object") {
531
+ const bucket = tagDefaults[key] ??= Object.create(null);
532
+ for (const [k, v] of Object.entries(value)) if (v !== null && v !== void 0) bucket[k] = String(v);
533
+ continue;
534
+ }
535
+ const colon = key.indexOf(":");
536
+ if (colon === -1) continue;
537
+ const selector = key.slice(0, colon);
538
+ const property = key.slice(colon + 1);
539
+ if (value === null || value === void 0) continue;
540
+ const bucket = selectorDefaults[selector] ??= Object.create(null);
541
+ bucket[property] = String(value);
542
+ }
543
+ const width = parsePxValue(attr(root, "containerWidth")) || REQUIRED_DEFAULTS.width;
544
+ const backgroundColor = (container ? parseColor(attr(container, "background-color")) : "") || REQUIRED_DEFAULTS.backgroundColor;
545
+ const textColor = parseColor(selectorDefaults[""]?.color) || REQUIRED_DEFAULTS.textColor;
546
+ const fontFamily = canonicalFontStack(selectorDefaults[""]?.["font-family"]) || canonicalFontStack(attr(root, "fonts")) || REQUIRED_DEFAULTS.fontFamily;
547
+ const linkColor = parseColor(selectorDefaults["a"]?.color);
548
+ const lineHeight = selectorDefaults[""]?.["line-height"];
549
+ if (lineHeight !== void 0) warnings.push(`Dropped the document line-height (${lineHeight}) — Templatical has no document-level line-height setting.`);
550
+ return {
551
+ settings: {
552
+ width,
553
+ backgroundColor,
554
+ textColor,
555
+ linkUnderline: REQUIRED_DEFAULTS.linkUnderline,
556
+ fontFamily,
557
+ locale: REQUIRED_DEFAULTS.locale,
558
+ ...linkColor ? { linkColor } : {}
559
+ },
560
+ tagDefaults,
561
+ selectorDefaults
562
+ };
563
+ }
512
564
  // src/block-mapper.ts
513
565
  /**
514
566
  * Read a node's own attribute, falling back to the design's per-tag then
@@ -915,6 +967,10 @@ function convertColumnChildren(column, ctx, entries) {
915
967
  const blocks = [];
916
968
  for (const child of column.children ?? []) {
917
969
  const converted = child.tagName === "mj-social" ? convertSocial(child, ctx) : convertLeaf(child, ctx);
970
+ if (child.uid) ctx.uidMap?.set(child.uid, {
971
+ node: child,
972
+ block: converted?.block ?? null
973
+ });
918
974
  if (!converted) continue;
919
975
  entries.push(converted.entry);
920
976
  if (converted.block) blocks.push(converted.block);
@@ -987,7 +1043,7 @@ const EMPTY_DESIGN_WARNING = "No convertible content was found in the Topol desi
987
1043
  * console.log(report.summary);
988
1044
  * ```
989
1045
  */
990
- function convertTopolTemplate(design) {
1046
+ function convertTopolTemplate(design, options = {}) {
991
1047
  const root = typeof design === "string" ? safeParse(design) : design;
992
1048
  if (typeof root !== "object" || root === null || Array.isArray(root)) throw new Error(INVALID_DESIGN_MESSAGE);
993
1049
  const topolDesign = root;
@@ -1000,7 +1056,12 @@ function convertTopolTemplate(design) {
1000
1056
  const warnings = [];
1001
1057
  const blocks = [];
1002
1058
  const style = readGlobalStyle(topolDesign, container, warnings);
1059
+ const languages = topolDesign.langs ?? [];
1060
+ const primary = languages.find((language) => language.primary) ?? languages[0];
1061
+ style.settings.locale = (primary ? localeTag(primary.key, warnings) : null) ?? defaultLocaleOption(options.defaultLocale, warnings) ?? "en";
1062
+ const uidMap = /* @__PURE__ */ new Map();
1003
1063
  const ctx = {
1064
+ uidMap,
1004
1065
  style,
1005
1066
  columnWidth: style.settings.width
1006
1067
  };
@@ -1009,12 +1070,56 @@ function convertTopolTemplate(design) {
1009
1070
  blocks.push(...buildSection(child, ctx, entries));
1010
1071
  }
1011
1072
  if (blocks.length === 0) warnings.push(EMPTY_DESIGN_WARNING);
1073
+ const content = {
1074
+ ...createDefaultTemplateContent(),
1075
+ blocks,
1076
+ settings: style.settings
1077
+ };
1078
+ const preview = primary?.translations?.["mj-preview"]?.content;
1079
+ if (typeof preview === "string") content.settings.preheaderText = preview;
1080
+ for (const language of languages) {
1081
+ const locale = localeTag(language.key, warnings);
1082
+ if (!locale) continue;
1083
+ for (const [uid, translation] of Object.entries(language.translations ?? {})) {
1084
+ if (uid === "mj-subject") {
1085
+ warn(warnings, `Dropped language "${locale}" mj-subject: the host owns this field.`);
1086
+ continue;
1087
+ }
1088
+ if (locale === content.settings.locale) continue;
1089
+ if (uid === "mj-preview") {
1090
+ if (typeof translation.content === "string") text(content.settings, locale, "preheaderText", content.settings.preheaderText, translation.content);
1091
+ continue;
1092
+ }
1093
+ const target = uidMap.get(uid);
1094
+ const block = target?.block;
1095
+ if (!block || block.type === "html") {
1096
+ warn(warnings, `Dropped language "${locale}" translation for uid "${uid}": target converted to ${block ? "HTML fallback" : "null"}.`);
1097
+ continue;
1098
+ }
1099
+ if (typeof translation.content === "string") {
1100
+ const converted = convertLeaf({
1101
+ ...target.node,
1102
+ content: translation.content
1103
+ }, ctx)?.block;
1104
+ if ((block.type === "title" || block.type === "paragraph") && converted && (converted.type === "title" || converted.type === "paragraph")) text(block, locale, "content", block.content, converted.content);
1105
+ else if (block.type === "button" && converted?.type === "button") text(block, locale, "text", block.text, converted.text);
1106
+ else warn(warnings, `Dropped language "${locale}" content for uid "${uid}": no language mapping for this format.`);
1107
+ }
1108
+ for (const [key, value] of Object.entries(translation.attributes ?? {})) {
1109
+ if (typeof value !== "string") continue;
1110
+ if (key === "alt" && block.type === "image") text(block, locale, "alt", block.alt, value);
1111
+ else if (key === "href" && block.type === "button") set(block, locale, "url", value);
1112
+ else if (key === "href" && block.type === "image") set(block, locale, "linkUrl", value);
1113
+ else if (key === "src" && block.type === "image") set(block, locale, "src", value);
1114
+ else warn(warnings, `Dropped language "${locale}" attribute "${key}" for uid "${uid}": no language mapping for this format.`);
1115
+ }
1116
+ }
1117
+ }
1118
+ registerLocales(content);
1119
+ const versions = languages.map((language) => localeTag(language.key, warnings)).filter((locale) => locale !== null && locale !== content.settings.locale);
1120
+ if (versions.length) content.settings.locales = [...new Set(versions)];
1012
1121
  return {
1013
- content: {
1014
- ...createDefaultTemplateContent(),
1015
- blocks,
1016
- settings: style.settings
1017
- },
1122
+ content,
1018
1123
  report: {
1019
1124
  entries,
1020
1125
  warnings,
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/localization.ts","../src/attribute-parser.ts","../src/content-inference.ts","../src/global-style.ts","../src/block-mapper.ts","../src/social-mapper.ts","../src/section-builder.ts","../src/converter.ts"],"sourcesContent":["import { isLocaleTag, sourceHash } from \"@templatical/types\";\nimport type {\n Block,\n BlockLocaleLayer,\n TemplateContent,\n} from \"@templatical/types\";\n\ntype Target = { l10n?: Record<string, BlockLocaleLayer> };\n\nexport function warn(warnings: string[], message: string): void {\n if (!warnings.includes(message)) warnings.push(message);\n}\n\nexport function localeTag(value: string, warnings: string[]): string | null {\n const locale = value.replaceAll(\"_\", \"-\");\n if (isLocaleTag(locale)) return locale;\n warn(warnings, `Dropped language \"${value}\": unsupported language tag.`);\n return null;\n}\n\n/** The `defaultLocale` option as a tag; `null`, with a warning, when it isn't one. */\nexport function defaultLocaleOption(\n value: string | undefined,\n warnings: string[],\n): string | null {\n if (!value) return null;\n const locale = value.replaceAll(\"_\", \"-\");\n if (isLocaleTag(locale)) return locale;\n warn(\n warnings,\n `Ignored defaultLocale \"${value}\": unsupported language tag. Used \"en\".`,\n );\n return null;\n}\n\nexport function text(\n target: Target,\n locale: string,\n path: string,\n base: string | undefined,\n value: string,\n): void {\n if (base === undefined) {\n set(target, locale, path, value);\n return;\n }\n const layer = ((target.l10n ??= {})[locale] ??= {});\n (layer.text ??= {})[path] = { value, sourceHash: sourceHash(base) };\n}\n\nexport function set(\n target: Target,\n locale: string,\n path: string,\n value: unknown,\n): void {\n const layer = ((target.l10n ??= {})[locale] ??= {});\n (layer.set ??= {})[path] = value;\n}\n\n/** Register only non-default versions, leaving single-language imports unchanged. */\nexport function registerLocales(content: TemplateContent): void {\n const locales = new Set<string>();\n const visit = (target: Target) => {\n if (!target.l10n) return;\n delete target.l10n[content.settings.locale];\n for (const locale of Object.keys(target.l10n)) locales.add(locale);\n if (Object.keys(target.l10n).length === 0) delete target.l10n;\n };\n visit(content.settings);\n const walk = (blocks: Block[]) => {\n for (const block of blocks) {\n visit(block);\n if (block.type === \"section\") block.children.forEach(walk);\n }\n };\n walk(content.blocks);\n if (locales.size) content.settings.locales = [...locales];\n}\n","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 { 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 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 {\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 uidMap?: Map<string, { node: TopolNode; block: Block | null }>;\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 (child.uid)\n ctx.uidMap?.set(child.uid, {\n node: child,\n block: converted?.block ?? null,\n });\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 {\n defaultLocaleOption,\n localeTag,\n registerLocales,\n set,\n text,\n warn,\n} from \"./localization\";\nimport { convertLeaf } from \"./block-mapper\";\nimport { 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\nexport interface ConvertTopolOptions {\n /** Used when the source declares no default language. */\n defaultLocale?: string;\n}\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 options: ConvertTopolOptions = {},\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 const languages = topolDesign.langs ?? [];\n const primary =\n languages.find((language) => language.primary) ?? languages[0];\n style.settings.locale =\n (primary ? localeTag(primary.key, warnings) : null) ??\n defaultLocaleOption(options.defaultLocale, warnings) ??\n \"en\";\n const uidMap: NonNullable<MapContext[\"uidMap\"]> = new Map();\n\n const ctx: MapContext = {\n uidMap,\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 preview = primary?.translations?.[\"mj-preview\"]?.content;\n if (typeof preview === \"string\") content.settings.preheaderText = preview;\n for (const language of languages) {\n const locale = localeTag(language.key, warnings);\n if (!locale) continue;\n for (const [uid, translation] of Object.entries(\n language.translations ?? {},\n )) {\n if (uid === \"mj-subject\") {\n warn(\n warnings,\n `Dropped language \"${locale}\" mj-subject: the host owns this field.`,\n );\n continue;\n }\n if (locale === content.settings.locale) continue;\n if (uid === \"mj-preview\") {\n if (typeof translation.content === \"string\")\n text(\n content.settings,\n locale,\n \"preheaderText\",\n content.settings.preheaderText,\n translation.content,\n );\n continue;\n }\n const target = uidMap.get(uid);\n const block = target?.block;\n if (!block || block.type === \"html\") {\n warn(\n warnings,\n `Dropped language \"${locale}\" translation for uid \"${uid}\": target converted to ${block ? \"HTML fallback\" : \"null\"}.`,\n );\n continue;\n }\n if (typeof translation.content === \"string\") {\n const converted = convertLeaf(\n { ...target.node, content: translation.content },\n ctx,\n )?.block;\n if (\n (block.type === \"title\" || block.type === \"paragraph\") &&\n converted &&\n (converted.type === \"title\" || converted.type === \"paragraph\")\n )\n text(block, locale, \"content\", block.content, converted.content);\n else if (block.type === \"button\" && converted?.type === \"button\")\n text(block, locale, \"text\", block.text, converted.text);\n else\n warn(\n warnings,\n `Dropped language \"${locale}\" content for uid \"${uid}\": no language mapping for this format.`,\n );\n }\n for (const [key, value] of Object.entries(translation.attributes ?? {})) {\n if (typeof value !== \"string\") continue;\n if (key === \"alt\" && block.type === \"image\")\n text(block, locale, \"alt\", block.alt, value);\n else if (key === \"href\" && block.type === \"button\")\n set(block, locale, \"url\", value);\n else if (key === \"href\" && block.type === \"image\")\n set(block, locale, \"linkUrl\", value);\n else if (key === \"src\" && block.type === \"image\")\n set(block, locale, \"src\", value);\n else\n warn(\n warnings,\n `Dropped language \"${locale}\" attribute \"${key}\" for uid \"${uid}\": no language mapping for this format.`,\n );\n }\n }\n }\n registerLocales(content);\n const versions = languages\n .map((language) => localeTag(language.key, warnings))\n .filter(\n (locale): locale is string =>\n locale !== null && locale !== content.settings.locale,\n );\n if (versions.length) content.settings.locales = [...new Set(versions)];\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.52.1",
4
+ "version": "0.54.0",
5
5
  "bugs": "https://github.com/templatical/sdk/issues",
6
6
  "dependencies": {
7
- "@templatical/types": "0.52.1"
7
+ "@templatical/types": "0.54.0"
8
8
  },
9
9
  "devDependencies": {
10
10
  "typescript": "^6.0.3",