@templatical/import-beefree 0.51.1 → 0.52.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- //#region src/types.d.ts
2
1
  /**
3
2
  * BeeFree JSON type definitions.
4
3
  * Based on the BeeFree export format used by the BEE Plugin / BEE Pro editor.
@@ -187,8 +186,6 @@ interface ImportResult {
187
186
  content: import("@templatical/types").TemplateContent;
188
187
  report: ImportReport;
189
188
  }
190
- //#endregion
191
- //#region src/converter.d.ts
192
189
  /**
193
190
  * Converts a BeeFree template JSON to Templatical TemplateContent.
194
191
  *
@@ -211,6 +208,5 @@ interface ImportResult {
211
208
  * ```
212
209
  */
213
210
  export declare function convertBeeFreeTemplate(template: BeeFreeTemplate): ImportResult;
214
- //#endregion
215
211
  export type { BeeFreeTemplate, ConversionStatus, ImportReport, ImportReportEntry, ImportResult };
216
212
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { canonicalFontStack, createButtonBlock, createDefaultTemplateContent, createDividerBlock, createHtmlBlock, createImageBlock, createMenuBlock, createParagraphBlock, createSectionBlock, createSocialIconsBlock, createSpacerBlock, createTableBlock, createTitleBlock, createVideoBlock, generateId } from "@templatical/types";
2
- //#region src/style-parser.ts
2
+ // src/style-parser.ts
3
3
  /**
4
4
  * Parses CSS-like style values from BeeFree descriptors.
5
5
  */
@@ -847,8 +847,7 @@ function convertModule(module, warnings, context = {}) {
847
847
  }
848
848
  };
849
849
  }
850
- //#endregion
851
- //#region src/converter.ts
850
+ // src/converter.ts
852
851
  /**
853
852
  * Determines the Templatical ColumnLayout from BeeFree column grid values.
854
853
  */
@@ -1049,7 +1048,6 @@ function convertBeeFreeTemplate(template) {
1049
1048
  }
1050
1049
  };
1051
1050
  }
1052
- //#endregion
1053
1051
  export { convertBeeFreeTemplate };
1054
1052
 
1055
1053
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/style-parser.ts","../src/block-mapper.ts","../src/converter.ts"],"sourcesContent":["import type {\n BackgroundImage,\n BackgroundPositionX,\n BackgroundPositionY,\n BackgroundSize,\n SpacingValue,\n} from \"@templatical/types\";\n\n/**\n * Parses CSS-like style values from BeeFree descriptors.\n */\n\n/**\n * The CSS-wide keywords. Each one defers to the cascade instead of naming a\n * value, and a block field that carried one would reach the renderer\n * verbatim, so the parsers read them as unset and the caller's fallback\n * applies.\n */\nconst CSS_WIDE_KEYWORDS = new Set([\"inherit\", \"initial\", \"unset\", \"revert\"]);\n\nfunction isCssWideKeyword(value: string): boolean {\n return CSS_WIDE_KEYWORDS.has(value.trim().toLowerCase());\n}\n\nexport function parsePxValue(value: string | undefined): number {\n if (!value) return 0;\n const match = value.match(/^(-?\\d+(?:\\.\\d+)?)\\s*px/);\n return match ? Math.round(parseFloat(match[1])) : 0;\n}\n\n/**\n * An image's corner radius in px, or `undefined` for square corners.\n *\n * Accepts a percentage as well as a px length, because `border-radius: 50%` is\n * the idiomatic way to write a circular avatar and the block stores px only. A\n * percentage resolves against the shorter rendered side, which is what turns a\n * square image into a circle and a wide one into a pill.\n *\n * `width` and `height` must be the dimensions actually read from the source. A\n * percentage with neither known is dropped rather than resolved against the\n * caller's fallback width, which would invent a radius the template never\n * expressed. A non-positive result is dropped for the same reason a px `0` is:\n * both mean square, and only absence keeps it out of the exported JSON.\n */\nexport function parseImageBorderRadius(\n value: string | undefined,\n width: number | undefined,\n height: number | undefined,\n): number | undefined {\n if (!value) return undefined;\n\n // Deliberately no `-?`: a negative percentage falls through to the px parse,\n // which rejects it too.\n const percent = value.trim().match(/^(\\d+(?:\\.\\d+)?)\\s*%$/);\n if (percent) {\n const side = Math.min(width ?? Infinity, height ?? Infinity);\n if (!Number.isFinite(side)) return undefined;\n const radius = Math.round((parseFloat(percent[1]) / 100) * side);\n return radius > 0 ? radius : undefined;\n }\n\n const px = parsePxValue(value);\n return px > 0 ? px : undefined;\n}\n\n/**\n * A color as lowercase 6-digit hex where it is hex, otherwise as written.\n *\n * `\"\"` means unset: returned for transparent, `none` and the CSS-wide\n * keywords, so the caller's fallback applies.\n */\nexport function parseColor(value: string | undefined): string {\n if (!value) return \"\";\n\n const trimmed = value.trim();\n const keyword = trimmed.toLowerCase();\n if (\n keyword === \"transparent\" ||\n keyword === \"none\" ||\n isCssWideKeyword(keyword)\n )\n return \"\";\n\n // Already a valid hex color\n if (/^#[0-9a-fA-F]{6}$/.test(trimmed)) return trimmed.toLowerCase();\n\n // 3-digit hex → 6-digit\n if (/^#[0-9a-fA-F]{3}$/.test(trimmed)) {\n const r = trimmed[1];\n const g = trimmed[2];\n const b = trimmed[3];\n return `#${r}${r}${g}${g}${b}${b}`.toLowerCase();\n }\n\n // Return as-is for rgb(), named colors, etc.\n return trimmed;\n}\n\nexport function parseBorderTop(value: string | undefined): {\n width: number;\n style: string;\n color: string;\n} {\n if (!value) return { width: 0, style: \"solid\", color: \"#000000\" };\n\n // \"2px solid #cccccc\"\n const parts = value.trim().split(/\\s+/);\n return {\n width: parsePxValue(parts[0]),\n style: parts[1] || \"solid\",\n color: parseColor(parts[2]) || \"#000000\",\n };\n}\n\nexport function extractPadding(\n style: Record<string, string> | undefined,\n): SpacingValue {\n if (!style) return { top: 0, right: 0, bottom: 0, left: 0 };\n\n // Check for shorthand `padding` first\n if (style.padding) {\n return parseShorthandPadding(style.padding);\n }\n\n return {\n top: parsePxValue(style[\"padding-top\"]),\n right: parsePxValue(style[\"padding-right\"]),\n bottom: parsePxValue(style[\"padding-bottom\"]),\n left: parsePxValue(style[\"padding-left\"]),\n };\n}\n\nfunction parseShorthandPadding(value: string): SpacingValue {\n const parts = value.trim().split(/\\s+/);\n const values = parts.map((p) => parsePxValue(p));\n\n switch (values.length) {\n case 1:\n return {\n top: values[0],\n right: values[0],\n bottom: values[0],\n left: values[0],\n };\n case 2:\n return {\n top: values[0],\n right: values[1],\n bottom: values[0],\n left: values[1],\n };\n case 3:\n return {\n top: values[0],\n right: values[1],\n bottom: values[2],\n left: values[1],\n };\n default:\n return {\n top: values[0],\n right: values[1],\n bottom: values[2],\n left: values[3],\n };\n }\n}\n\n/**\n * A divider's `width` as a number and its unit, or `undefined` when it is\n * neither a px nor a % length (`auto`, a keyword, anything unreadable).\n */\nexport function parseDividerWidth(\n value: string,\n): { value: number; unit: \"px\" | \"%\" } | undefined {\n const match = value.trim().match(/^(-?\\d+(?:\\.\\d+)?)\\s*(%|px)$/);\n if (!match) return undefined;\n return { value: parseFloat(match[1]), unit: match[2] === \"%\" ? \"%\" : \"px\" };\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/**\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/** CSS's initial values: what a browser paints for a longhand BeeFree leaves out. */\nconst CSS_BACKGROUND_DEFAULTS = {\n size: \"auto\",\n x: \"left\",\n y: \"top\",\n repeat: true,\n} as const;\n\n/**\n * The URL inside the first CSS `url(…)`, unquoted and trimmed; \"\" when there\n * is none or it is malformed. A forward scan rather than a regex, so a long\n * run of unclosed `url(` can't make it backtrack.\n */\nfunction cssUrl(value: string | undefined): string {\n const text = value ?? \"\";\n const start = text.search(/url\\(/i);\n if (start === -1) return \"\";\n let i = start + 4;\n while (i < text.length && /\\s/.test(text[i])) i++;\n const quote = text[i];\n if (quote === '\"' || quote === \"'\") {\n const close = text.indexOf(quote, i + 1);\n if (close === -1) return \"\";\n let j = close + 1;\n while (j < text.length && /\\s/.test(text[j])) j++;\n return text[j] === \")\" ? text.slice(i + 1, close).trim() : \"\";\n }\n const close = text.indexOf(\")\", i);\n if (close === -1) return \"\";\n const inner = text.slice(i, close);\n return /[\"']/.test(inner) ? \"\" : inner.trim();\n}\n\n/**\n * A row's background image from a BeeFree style map (`content.style` or\n * `container.style`), `null` when `background-image` holds no `url()`.\n * Unset longhands take CSS's initial values; a value with no exact keyword\n * takes the nearest one and adds a note naming it. `label` names the element\n * in each note, and a px `background-size` is measured against `boxWidth`.\n */\nexport function readBeefreeBackgroundImage(\n style: Record<string, string> | undefined,\n label: string,\n boxWidth: number | undefined,\n): ReadBackgroundImage | null {\n const url = cssUrl(style?.[\"background-image\"]);\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 = CSS_BACKGROUND_DEFAULTS.size;\n const rawSize = style?.[\"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 = CSS_BACKGROUND_DEFAULTS.x;\n let y: BackgroundPositionY = CSS_BACKGROUND_DEFAULTS.y;\n const rawPosition = style?.[\"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 if (!split.exact || !readX?.exact || !readY?.exact) {\n lossy(\"background-position\", rawPosition, `${x} ${y}`);\n }\n }\n\n let repeat: boolean = CSS_BACKGROUND_DEFAULTS.repeat;\n const rawRepeat = style?.[\"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 createTitleBlock,\n createParagraphBlock,\n createImageBlock,\n createButtonBlock,\n createDividerBlock,\n createSpacerBlock,\n createHtmlBlock,\n createSocialIconsBlock,\n createMenuBlock,\n createTableBlock,\n createVideoBlock,\n generateId,\n} from \"@templatical/types\";\nimport type {\n Block,\n DividerBlock,\n HeadingLevel,\n ImageBlock,\n SocialPlatform,\n SocialIcon,\n MenuItemData,\n TableRowData,\n TableCellData,\n} from \"@templatical/types\";\nimport type {\n BeeFreeeModule,\n BeeFreeeModuleDescriptor,\n ImportReportEntry,\n} from \"./types\";\nimport {\n parseImageBorderRadius,\n parsePxValue,\n parseColor,\n parseBorderTop,\n extractPadding,\n parseDividerWidth,\n} from \"./style-parser\";\n\n/**\n * The template text color the import writes when the body sets none.\n */\nexport const FALLBACK_TEXT_COLOR = \"#1a1a1a\";\n\n/**\n * What the caller knows about where a module lands.\n */\nexport interface ModuleContext {\n /**\n * Width in px of the Templatical column the block renders in. Without it a\n * px divider width is kept as pixels.\n */\n columnWidth?: number;\n /**\n * The template's `textColor`, which every text block without a color of\n * its own inherits. Defaults to `FALLBACK_TEXT_COLOR`.\n */\n textColor?: string;\n}\n\n/**\n * Maps BeeFree module type strings to short keys.\n */\nconst MODULE_TYPE_MAP: Record<string, string> = {\n \"mailup-bee-newsletter-modules-text\": \"paragraph\",\n \"mailup-bee-newsletter-modules-paragraph\": \"paragraph\",\n \"mailup-bee-newsletter-modules-heading\": \"title\",\n \"mailup-bee-newsletter-modules-list\": \"list\",\n \"mailup-bee-newsletter-modules-image\": \"image\",\n \"mailup-bee-newsletter-modules-button\": \"button\",\n \"mailup-bee-newsletter-modules-divider\": \"divider\",\n \"mailup-bee-newsletter-modules-spacer\": \"spacer\",\n \"mailup-bee-newsletter-modules-html\": \"html\",\n \"mailup-bee-newsletter-modules-social\": \"social\",\n \"mailup-bee-newsletter-modules-video\": \"video\",\n \"mailup-bee-newsletter-modules-menu\": \"menu\",\n \"mailup-bee-newsletter-modules-table\": \"table\",\n};\n\nconst SOCIAL_PLATFORM_MAP: Record<string, SocialPlatform> = {\n facebook: \"facebook\",\n twitter: \"twitter\",\n x: \"twitter\",\n instagram: \"instagram\",\n linkedin: \"linkedin\",\n youtube: \"youtube\",\n tiktok: \"tiktok\",\n pinterest: \"pinterest\",\n email: \"email\",\n whatsapp: \"whatsapp\",\n telegram: \"telegram\",\n discord: \"discord\",\n snapchat: \"snapchat\",\n reddit: \"reddit\",\n github: \"github\",\n dribbble: \"dribbble\",\n behance: \"behance\",\n};\n\ntype Align = \"left\" | \"center\" | \"right\";\ntype LineStyle = \"solid\" | \"dashed\" | \"dotted\";\n\nfunction toAlign(value: string | undefined, fallback: Align = \"left\"): Align {\n if (value === \"left\" || value === \"center\" || value === \"right\") return value;\n return fallback;\n}\n\nfunction toLineStyle(\n value: string | undefined,\n fallback: LineStyle = \"solid\",\n): LineStyle {\n if (value === \"solid\" || value === \"dashed\" || value === \"dotted\")\n return value;\n return fallback;\n}\n\n/**\n * Strip every `<…>` from a button label and reject any stray `<` or `>`\n * left behind (e.g. a truncated `<script`). The original\n * `value.replace(/<[^>]*>/g, \"\")` was both polynomial-ReDoS over\n * `<<<<…` inputs and an incomplete sanitizer — an unterminated `<script`\n * would survive the strip. Downstream HTML-escapes the label at render\n * time, but stripping here keeps the imported JSON clean.\n */\nfunction stripTagsPlain(text: string): string {\n let out = \"\";\n let i = 0;\n while (i < text.length) {\n if (text[i] === \"<\") {\n const close = text.indexOf(\">\", i + 1);\n if (close === -1) {\n // Unterminated tag — discard the rest.\n break;\n }\n i = close + 1;\n continue;\n }\n if (text[i] === \">\") {\n i++;\n continue;\n }\n out += text[i];\n i++;\n }\n return out;\n}\n\nfunction makeStyles(descriptor: BeeFreeeModuleDescriptor): Block[\"styles\"] {\n const padding = extractPadding(descriptor.style);\n const bg = parseColor(descriptor.style?.[\"background-color\"]);\n return {\n padding,\n ...(bg ? { backgroundColor: bg } : {}),\n };\n}\n\n/**\n * Apply BeeFree text styles as TipTap-compatible inline markup.\n * - text-align → added to each <p> tag's style attribute\n * - color, font-size, font-weight, font-family → wrapped in <span style=\"...\"> inside each <p>\n */\nfunction inlineStylesToHtml(\n html: string,\n style: Record<string, string | undefined>,\n textColor: string,\n): string {\n const spanParts: string[] = [];\n const fontSize = parsePxValue(style[\"font-size\"]);\n if (fontSize && fontSize !== 16) spanParts.push(`font-size: ${fontSize}px`);\n const color = parseColor(style.color);\n // A paragraph inherits the template's text color, so only a different\n // color needs a span to survive.\n if (color && color !== textColor) spanParts.push(`color: ${color}`);\n const fontWeight = style[\"font-weight\"];\n // \"400\" is the numeric synonym for \"normal\" — neither needs an explicit\n // span (matches the import-unlayer importer).\n if (fontWeight && fontWeight !== \"normal\" && fontWeight !== \"400\")\n spanParts.push(`font-weight: ${fontWeight}`);\n const fontFamily = canonicalFontStack(style[\"font-family\"]);\n if (fontFamily) spanParts.push(`font-family: ${fontFamily}`);\n\n const textAlign = style[\"text-align\"];\n const pStyle =\n textAlign && textAlign !== \"left\" ? `text-align: ${textAlign}` : \"\";\n\n if (!pStyle && spanParts.length === 0) return html;\n\n const spanStyle = spanParts.join(\"; \");\n\n // Apply styles to each <p> tag in the HTML\n let result = html;\n\n if (pStyle) {\n // Add text-align to existing <p style=\"...\"> or add style to plain <p>\n result = result\n .replace(/<p style=\"([^\"]*)\">/g, `<p style=\"$1; ${pStyle}\">`)\n .replaceAll(\"<p>\", `<p style=\"${pStyle}\">`);\n }\n\n if (spanStyle) {\n // Wrap inner content of each <p> in a styled span.\n result = wrapParagraphInner(result, spanStyle);\n }\n\n return result;\n}\n\n/**\n * Wrap the inner content of every `<p …>…</p>` with `<span style=\"…\">…</span>`.\n *\n * Hand-rolled linear scanner instead of `/<p([^>]*)>([\\s\\S]*?)<\\/p>/g` because\n * the regex is polynomial-ReDoS: the engine retries `[\\s\\S]*?<\\/p>` at every\n * `<p` start, so inputs like `<p>a<p>a<p>a…` (no `</p>` ever) cost O(n²).\n * The scanner advances `i` monotonically via `indexOf`, keeping the work\n * strictly O(n).\n */\nfunction wrapParagraphInner(html: string, spanStyle: string): string {\n let out = \"\";\n let i = 0;\n while (i < html.length) {\n const open = html.indexOf(\"<p\", i);\n if (open === -1) {\n out += html.substring(i);\n break;\n }\n const afterTagName = html[open + 2];\n if (\n afterTagName !== \">\" &&\n afterTagName !== \" \" &&\n afterTagName !== \"\\t\" &&\n afterTagName !== \"\\n\" &&\n afterTagName !== \"\\r\" &&\n afterTagName !== \"/\"\n ) {\n out += html.substring(i, open + 2);\n i = open + 2;\n continue;\n }\n const openEnd = html.indexOf(\">\", open + 2);\n if (openEnd === -1) {\n out += html.substring(i);\n break;\n }\n const closeStart = html.indexOf(\"</p>\", openEnd + 1);\n if (closeStart === -1) {\n out += html.substring(i);\n break;\n }\n const inner = html.substring(openEnd + 1, closeStart);\n out += html.substring(i, openEnd + 1);\n out += `<span style=\"${spanStyle}\">${inner}</span></p>`;\n i = closeStart + 4;\n }\n return out;\n}\n\nfunction convertText(\n descriptor: BeeFreeeModuleDescriptor,\n textColor: string,\n): Block {\n const textContent =\n descriptor.text ?? descriptor.paragraph ?? descriptor.list;\n const html = textContent?.html ?? \"\";\n const style = textContent?.style ?? {};\n\n return createParagraphBlock({\n content: inlineStylesToHtml(html, style, textColor),\n styles: makeStyles(descriptor),\n });\n}\n\nfunction parseHeadingLevel(tag: string): HeadingLevel {\n const match = tag.match(/^h(\\d)$/i);\n if (match) {\n const num = Number(match[1]);\n if (num >= 1 && num <= 4) return num as HeadingLevel;\n }\n return 2;\n}\n\nfunction convertHeading(\n descriptor: BeeFreeeModuleDescriptor,\n textColor: string,\n): Block {\n const heading = descriptor.heading;\n if (!heading) return convertText(descriptor, textColor);\n\n const style = heading.style ?? {};\n const tag = heading.title ?? \"h2\";\n const text = heading.text ?? \"\";\n // Strip heading tags — content goes inside TipTap, heading tag is rendered by the block\n const content = text.startsWith(\"<\")\n ? text.replace(/^<h\\d[^>]*>|<\\/h\\d>$/gi, \"\")\n : text;\n\n return createTitleBlock({\n content: content ? `<p>${content}</p>` : \"<p></p>\",\n level: parseHeadingLevel(tag),\n color: parseColor(style.color) || undefined,\n textAlign: toAlign(style[\"text-align\"]),\n fontFamily: canonicalFontStack(style[\"font-family\"]) || undefined,\n styles: makeStyles(descriptor),\n });\n}\n\n/**\n * The classes BeeFree records in `computedStyle.class` for an image: its\n * alignment and `autowidth` / `fixedwidth`.\n */\nfunction computedClasses(descriptor: BeeFreeeModuleDescriptor): string[] {\n const value = descriptor.computedStyle?.class;\n return typeof value === \"string\" ? value.trim().split(/\\s+/) : [];\n}\n\n/**\n * The width BeeFree shows an image at, as `ImageBlock.width`. `image.width` and\n * `image.height` are the file's own size: an `autowidth` image fills its\n * column, and a `fixedwidth` one is shown at `computedStyle.width`. An export\n * without `computedStyle` falls back to `image.width`. No width, or one that\n * reaches `room` (the column less the image's side padding), is `\"full\"`.\n */\nfunction resolveImageWidth(\n descriptor: BeeFreeeModuleDescriptor,\n room: number | undefined,\n): ImageBlock[\"width\"] {\n if (computedClasses(descriptor).includes(\"autowidth\")) return \"full\";\n const computed = descriptor.computedStyle?.width;\n const shown =\n parsePxValue(typeof computed === \"string\" ? computed : undefined) ||\n parsePxValue(descriptor.image?.width);\n if (shown <= 0) return \"full\";\n return room !== undefined && shown >= room ? \"full\" : shown;\n}\n\nfunction convertImage(\n descriptor: BeeFreeeModuleDescriptor,\n columnWidth: number | undefined,\n): Block {\n const image = descriptor.image;\n const styles = makeStyles(descriptor);\n if (!image) {\n return createImageBlock({ styles });\n }\n\n const room =\n columnWidth === undefined\n ? undefined\n : columnWidth - styles.padding.left - styles.padding.right;\n const width = resolveImageWidth(descriptor, room);\n\n // `image.width` × `image.height` is the file, so the height is never carried\n // over: the image keeps its aspect ratio at the width it is shown at. A\n // percentage radius resolves against that shown size, and only when BeeFree\n // stated a width.\n const fileWidth = parsePxValue(image.width);\n const fileHeight = parsePxValue(image.height);\n const shownWidth = width === \"full\" ? room : width;\n const stated = fileWidth > 0 || width !== \"full\";\n const shownHeight =\n shownWidth !== undefined && fileWidth > 0 && fileHeight > 0\n ? Math.round((fileHeight * shownWidth) / fileWidth)\n : undefined;\n\n const classes = computedClasses(descriptor);\n const classAlign = ([\"left\", \"center\", \"right\"] as const).find((side) =>\n classes.includes(side),\n );\n\n return createImageBlock({\n src: image.src || \"\",\n alt: image.alt || \"\",\n width,\n // The file's size feeds the editor's aspect-ratio lock; never rendered.\n ...(fileWidth > 0 && fileHeight > 0\n ? { naturalWidth: fileWidth, naturalHeight: fileHeight }\n : {}),\n borderRadius: parseImageBorderRadius(\n image.style?.[\"border-radius\"],\n stated ? shownWidth : undefined,\n shownHeight,\n ),\n // Exports keep the alignment in `computedStyle.class` (\"left fixedwidth\")\n // and often have no image `style` at all.\n align: toAlign(image.style?.[\"text-align\"] ?? classAlign, \"center\"),\n linkUrl: image.href || undefined,\n styles,\n });\n}\n\nfunction convertButton(descriptor: BeeFreeeModuleDescriptor): Block {\n const button = descriptor.button;\n if (!button) {\n return createButtonBlock({ styles: makeStyles(descriptor) });\n }\n\n const style = button.style ?? {};\n const label = button.label ? stripTagsPlain(button.label) : \"Button\";\n\n return createButtonBlock({\n text: label,\n url: button.href || \"#\",\n backgroundColor: parseColor(style[\"background-color\"]) || \"#4f46e5\",\n textColor: parseColor(style.color) || \"#ffffff\",\n borderRadius: parsePxValue(style[\"border-radius\"]),\n fontSize: parsePxValue(style[\"font-size\"]) || 16,\n fontFamily: canonicalFontStack(style[\"font-family\"]) || undefined,\n align: toAlign(style[\"text-align\"], \"center\"),\n buttonPadding: {\n top: parsePxValue(style[\"padding-top\"]) || 12,\n right: parsePxValue(style[\"padding-right\"]) || 24,\n bottom: parsePxValue(style[\"padding-bottom\"]) || 12,\n left: parsePxValue(style[\"padding-left\"]) || 24,\n },\n styles: makeStyles(descriptor),\n });\n}\n\n/**\n * `\"full\"` when the divider spans its column: no width, `100%`, or a px width\n * that reaches `contentWidth`. Without a `contentWidth` a px width stands as\n * stated. Any other percentage stays a percentage, clamped to 0–100%.\n */\nfunction resolveDividerWidth(\n value: string | undefined,\n contentWidth: number | undefined,\n notes: string[],\n): DividerBlock[\"width\"] {\n const raw = (value ?? \"\").trim();\n if (!raw) return \"full\";\n\n const parsed = parseDividerWidth(raw);\n if (!parsed) {\n notes.push(\n `Divider width \"${raw}\" could not be read; imported as full width.`,\n );\n return \"full\";\n }\n\n if (parsed.unit === \"%\") {\n const percent = Math.min(100, Math.max(0, parsed.value));\n if (percent !== parsed.value) {\n notes.push(`Divider width ${raw} was clamped to ${percent}%.`);\n }\n // Two decimals keep the value inside `DividerPercentWidth`'s pattern,\n // which a float printed in exponent form would leave.\n const share = Math.round(percent * 100) / 100;\n return share === 100 ? \"full\" : `${share}%`;\n }\n\n const px = Math.max(0, Math.round(parsed.value));\n if (px !== Math.round(parsed.value)) {\n notes.push(`Divider width ${raw} was clamped to 0px.`);\n }\n return contentWidth !== undefined && px >= contentWidth ? \"full\" : px;\n}\n\nfunction convertDivider(\n descriptor: BeeFreeeModuleDescriptor,\n columnWidth: number | undefined,\n): { block: Block; notes: string[] } {\n const style = descriptor.divider?.style ?? {};\n const border = parseBorderTop(style[\"border-top\"]);\n const styles = makeStyles(descriptor);\n const notes: string[] = [];\n\n // MJML draws a full divider across its column less the divider's own\n // padding, so a px width that reaches that renders the same as \"full\", and\n // \"full\" keeps narrowing with the column on a phone.\n const contentWidth =\n columnWidth === undefined\n ? undefined\n : columnWidth - styles.padding.left - styles.padding.right;\n const width = resolveDividerWidth(style.width, contentWidth, notes);\n\n const align = dividerAlign(descriptor);\n if (width !== \"full\" && (align === \"left\" || align === \"right\")) {\n notes.push(\n `BeeFree aligns this divider ${align}; Templatical centres every divider.`,\n );\n }\n\n return {\n block: createDividerBlock({\n lineStyle: toLineStyle(border.style),\n color: border.color,\n thickness: border.width || 1,\n width,\n styles,\n }),\n notes,\n };\n}\n\n/**\n * BeeFree keeps a divider's alignment in `computedStyle.align`; some exports\n * also write it to the module style's `text-align`.\n */\nfunction dividerAlign(\n descriptor: BeeFreeeModuleDescriptor,\n): string | undefined {\n const computed = descriptor.computedStyle?.align;\n return typeof computed === \"string\"\n ? computed\n : descriptor.style?.[\"text-align\"];\n}\n\nfunction convertSpacer(descriptor: BeeFreeeModuleDescriptor): Block {\n const spacer = descriptor.spacer;\n const height = parsePxValue(spacer?.style?.height) || 24;\n\n return createSpacerBlock({\n height,\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertHtml(descriptor: BeeFreeeModuleDescriptor): Block {\n const html = descriptor.html?.html ?? \"\";\n\n return createHtmlBlock({\n content: html,\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertSocial(\n descriptor: BeeFreeeModuleDescriptor,\n warnings: string[],\n): Block {\n const iconsList = descriptor.iconsList;\n if (!iconsList?.icons) {\n return createSocialIconsBlock({ styles: makeStyles(descriptor) });\n }\n\n const icons: SocialIcon[] = [];\n\n for (const beeIcon of iconsList.icons) {\n const id = (beeIcon.id ?? beeIcon.name ?? \"\").toLowerCase();\n const platform = SOCIAL_PLATFORM_MAP[id];\n\n if (!platform) {\n warnings.push(\n `Unrecognized social icon \"${beeIcon.name || id}\" was skipped.`,\n );\n continue;\n }\n\n icons.push({\n id: generateId(),\n platform,\n url: beeIcon.image?.href || \"#\",\n });\n }\n\n return createSocialIconsBlock({\n icons,\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertVideo(descriptor: BeeFreeeModuleDescriptor): Block {\n const video = descriptor.video;\n if (!video) {\n return createVideoBlock({ styles: makeStyles(descriptor) });\n }\n\n return createVideoBlock({\n url: video.src || \"\",\n thumbnailUrl: video.thumbnail || \"\",\n alt: video.alt || \"\",\n width: parsePxValue(video.style?.width) || 600,\n height: parsePxValue(video.style?.height) || undefined,\n align: toAlign(video.style?.[\"text-align\"], \"center\"),\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertMenu(descriptor: BeeFreeeModuleDescriptor): Block {\n const menu = descriptor.menu;\n if (!menu) {\n return createMenuBlock({ styles: makeStyles(descriptor) });\n }\n\n const style = menu.style ?? {};\n\n const items: MenuItemData[] = (menu.items ?? []).map((item) => ({\n id: generateId(),\n text: item.text || \"\",\n url: item.link || item.href || \"#\",\n openInNewTab: item.target === \"_blank\",\n bold: false,\n underline: false,\n }));\n\n return createMenuBlock({\n items,\n separator: menu.separator || \"|\",\n separatorColor: parseColor(menu.separatorColor) || \"#999999\",\n fontSize: parsePxValue(style[\"font-size\"]) || 14,\n color: parseColor(style.color) || undefined,\n fontFamily: canonicalFontStack(style[\"font-family\"]) || undefined,\n textAlign: toAlign(style[\"text-align\"], \"center\"),\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertTable(descriptor: BeeFreeeModuleDescriptor): {\n block: Block;\n approximated: boolean;\n} {\n const table = descriptor.table;\n if (!table) {\n return {\n block: createTableBlock({ styles: makeStyles(descriptor) }),\n approximated: false,\n };\n }\n\n const style = table.style ?? {};\n\n // Templatical table cells are a plain-text field (the editor stores the\n // cell's innerText), so strip any HTML a BeeFree cell carried the same way\n // button labels are stripped. If a strip actually removed markup, the cell\n // lost formatting or a link URL, so the conversion is an approximation.\n let approximated = false;\n const rows: TableRowData[] = (table.rows ?? []).map((row) => ({\n id: generateId(),\n cells: (row.cells ?? []).map((cell): TableCellData => {\n const raw = cell.content ?? cell.html ?? \"\";\n const content = stripTagsPlain(raw);\n if (content !== raw) {\n approximated = true;\n }\n return { id: generateId(), content };\n }),\n }));\n\n return {\n block: createTableBlock({\n rows,\n hasHeaderRow: table.hasHeaderRow ?? false,\n headerBackgroundColor:\n parseColor(table.headerBackgroundColor) || undefined,\n borderColor: parseColor(style[\"border-color\"]) || \"#dddddd\",\n borderWidth: parsePxValue(style[\"border-width\"]) || 1,\n cellPadding:\n typeof table.cellPadding === \"number\"\n ? table.cellPadding\n : parsePxValue(table.cellPadding as string) || 8,\n fontSize: parsePxValue(style[\"font-size\"]) || 14,\n color: parseColor(style.color) || undefined,\n textAlign: toAlign(style[\"text-align\"]),\n styles: makeStyles(descriptor),\n }),\n approximated,\n };\n}\n\nfunction convertHtmlFallback(module: BeeFreeeModule): Block {\n // Attempt to extract any HTML content from the descriptor\n const descriptor = module.descriptor;\n let html = \"\";\n\n // Try common content fields\n if (descriptor.text?.html) html = descriptor.text.html;\n else if (descriptor.html?.html) html = descriptor.html.html;\n else if (descriptor.heading?.text) html = descriptor.heading.text;\n else html = `<!-- Unsupported BeeFree module: ${module.type} -->`;\n\n return createHtmlBlock({\n content: html,\n styles: makeStyles(descriptor),\n });\n}\n\n/**\n * Converts a single BeeFree module to a Templatical block.\n * Returns the block and a report entry.\n */\nexport function convertModule(\n module: BeeFreeeModule,\n warnings: string[],\n context: ModuleContext = {},\n): { block: Block; entry: ImportReportEntry } {\n const mappedType = MODULE_TYPE_MAP[module.type];\n const descriptor = module.descriptor;\n const textColor = context.textColor ?? FALLBACK_TEXT_COLOR;\n\n if (!mappedType) {\n return {\n block: convertHtmlFallback(module),\n entry: {\n beeFreeModuleType: module.type,\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n note: `Unknown module type \"${module.type}\" converted to HTML block.`,\n },\n };\n }\n\n let block: Block;\n let isApproximation = false;\n let notes: string[] = [];\n\n switch (mappedType) {\n case \"paragraph\":\n case \"list\":\n block = convertText(descriptor, textColor);\n break;\n case \"title\":\n block = convertHeading(descriptor, textColor);\n break;\n case \"image\":\n block = convertImage(descriptor, context.columnWidth);\n break;\n case \"button\":\n block = convertButton(descriptor);\n break;\n case \"divider\": {\n const result = convertDivider(descriptor, context.columnWidth);\n block = result.block;\n notes = result.notes;\n isApproximation = notes.length > 0;\n break;\n }\n case \"spacer\":\n block = convertSpacer(descriptor);\n break;\n case \"html\":\n block = convertHtml(descriptor);\n break;\n case \"social\":\n block = convertSocial(descriptor, warnings);\n break;\n case \"video\":\n block = convertVideo(descriptor);\n break;\n case \"menu\":\n block = convertMenu(descriptor);\n isApproximation = true; // menu styles are approximate\n break;\n case \"table\": {\n const result = convertTable(descriptor);\n block = result.block;\n if (result.approximated) {\n isApproximation = true;\n warnings.push(\n \"HTML inside one or more table cells was reduced to plain text; any cell formatting or links were dropped.\",\n );\n }\n break;\n }\n default:\n block = convertHtmlFallback(module);\n return {\n block,\n entry: {\n beeFreeModuleType: module.type,\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n },\n };\n }\n\n return {\n block,\n entry: {\n beeFreeModuleType: module.type,\n templaticalBlockType: block.type,\n status: isApproximation ? \"approximated\" : \"converted\",\n ...(notes.length > 0 ? { note: notes.join(\" \") } : {}),\n },\n };\n}\n","import {\n canonicalFontStack,\n createSectionBlock,\n createDefaultTemplateContent,\n} from \"@templatical/types\";\nimport type {\n BackgroundImage,\n Block,\n ColumnLayout,\n TemplateContent,\n} from \"@templatical/types\";\nimport type {\n BeeFreeTemplate,\n BeeFreeeRow,\n BeeFreeeColumn,\n ImportResult,\n ImportReport,\n ImportReportEntry,\n} from \"./types\";\nimport {\n convertModule,\n FALLBACK_TEXT_COLOR,\n type ModuleContext,\n} from \"./block-mapper\";\nimport {\n parsePxValue,\n parseColor,\n readBeefreeBackgroundImage,\n} from \"./style-parser\";\n\n/**\n * Determines the Templatical ColumnLayout from BeeFree column grid values.\n */\nfunction resolveColumnLayout(\n columns: BeeFreeeColumn[],\n warnings: string[],\n): ColumnLayout | null {\n if (columns.length === 1) return null; // Single column — no section wrapper needed\n if (columns.length === 3) return \"3\";\n\n if (columns.length === 2) {\n const left = columns[0][\"grid-columns\"] ?? 6;\n const right = columns[1][\"grid-columns\"] ?? 6;\n const total = left + right;\n const ratio = left / total;\n\n if (ratio > 0.58) return \"2-1\";\n if (ratio < 0.42) return \"1-2\";\n return \"2\";\n }\n\n if (columns.length >= 4) {\n warnings.push(\n `Row with ${columns.length} columns was flattened to a single column. BeeFree supports arbitrary columns, but Templatical supports up to 3.`,\n );\n return null; // Flatten to single column\n }\n\n return \"2\";\n}\n\n/**\n * Column widths in px for a layout, mirroring `renderer/src/columns.ts`, and\n * floored as the renderer floors the width it hands each column.\n */\nfunction columnPixels(layout: ColumnLayout, bodyWidth: number): number[] {\n switch (layout) {\n case \"2\":\n return [bodyWidth * 0.5, bodyWidth * 0.5].map(Math.floor);\n case \"3\":\n return [bodyWidth / 3, bodyWidth / 3, bodyWidth / 3].map(Math.floor);\n case \"1-2\":\n return [bodyWidth / 3, (bodyWidth * 2) / 3].map(Math.floor);\n case \"2-1\":\n return [(bodyWidth * 2) / 3, bodyWidth / 3].map(Math.floor);\n default:\n return [Math.floor(bodyWidth)];\n }\n}\n\n/**\n * Converts all modules in a column to Templatical blocks.\n */\nfunction convertColumnModules(\n column: BeeFreeeColumn,\n context: ModuleContext,\n entries: ImportReportEntry[],\n warnings: string[],\n): Block[] {\n const blocks: Block[] = [];\n\n for (const module of column.modules) {\n const { block, entry } = convertModule(module, warnings, context);\n blocks.push(block);\n entries.push(entry);\n }\n\n return blocks;\n}\n\n/**\n * The image a row's section paints. `content.style` is the content area a\n * section paints, so its image maps exactly. `container.style` is the\n * full-width band: it shows through only while the content area has neither\n * an image nor a colour, and then the section paints it at the content\n * width; otherwise it's dropped. Losses go to `warnings`, since rows have no\n * report entry.\n */\nfunction resolveRowBackgroundImage(\n row: BeeFreeeRow,\n rowBg: string,\n width: number,\n warnings: string[],\n): BackgroundImage | undefined {\n const content = readBeefreeBackgroundImage(row.content?.style, \"Row\", width);\n const band = readBeefreeBackgroundImage(\n row.container?.style,\n \"Row container\",\n width,\n );\n\n if (content) {\n warnings.push(...content.notes);\n if (band) {\n warnings.push(\n `Row container background image \"${band.image.url}\" outside the content width was dropped; the section keeps the content background image.`,\n );\n }\n return content.image;\n }\n if (!band) return undefined;\n if (rowBg) {\n warnings.push(\n `Row container background image \"${band.image.url}\" outside the content width was dropped; the section keeps the content background ${rowBg}.`,\n );\n return undefined;\n }\n warnings.push(\n ...band.notes,\n `Row container background image \"${band.image.url}\" spans the full width in BeeFree; the section paints it at the content width.`,\n );\n return band.image;\n}\n\n/**\n * Processes a single BeeFree row into one or more Templatical blocks.\n */\nfunction processRow(\n row: BeeFreeeRow,\n settings: TemplateContent[\"settings\"],\n entries: ImportReportEntry[],\n warnings: string[],\n): Block[] {\n const columns = row.columns;\n if (!columns || columns.length === 0) return [];\n\n // Track locked/synced metadata\n if (row.locked) {\n warnings.push(\n \"A locked row was imported. Lock metadata was dropped; content is preserved.\",\n );\n }\n if (row.synced) {\n warnings.push(\n \"A synced row was imported. Sync metadata was dropped; content is preserved.\",\n );\n }\n\n const layout = resolveColumnLayout(columns, warnings);\n\n // Extract section-level background from row content styles.\n const rowBg = parseColor(row.content?.style?.[\"background-color\"]);\n const backgroundImage = resolveRowBackgroundImage(\n row,\n rowBg,\n settings.width,\n warnings,\n );\n\n if (!layout) {\n // Single column (or flattened 4+ columns) — return modules directly.\n // Either way the modules end up spanning the body.\n const blocks: Block[] = [];\n const context = {\n columnWidth: settings.width,\n textColor: settings.textColor,\n };\n for (const column of columns) {\n blocks.push(...convertColumnModules(column, context, entries, warnings));\n }\n // A row background — a colour or an image — can't survive on bare\n // modules; wrap them in a one-column section so it still renders. Mirrors\n // the multi-column path and the Unlayer importer (which wraps every row).\n if (rowBg || backgroundImage) {\n return [\n createSectionBlock({\n columns: \"1\",\n children: [blocks],\n styles: {\n padding: { top: 0, right: 0, bottom: 0, left: 0 },\n ...(rowBg ? { backgroundColor: rowBg } : {}),\n },\n ...(backgroundImage ? { backgroundImage } : {}),\n }),\n ];\n }\n return blocks;\n }\n\n // Multi-column — wrap in a SectionBlock\n const widths = columnPixels(layout, settings.width);\n const children: Block[][] = columns.map((col, index) =>\n convertColumnModules(\n col,\n { columnWidth: widths[index], textColor: settings.textColor },\n entries,\n warnings,\n ),\n );\n\n const section = createSectionBlock({\n columns: layout,\n children,\n styles: {\n padding: { top: 0, right: 0, bottom: 0, left: 0 },\n ...(rowBg ? { backgroundColor: rowBg } : {}),\n },\n ...(backgroundImage ? { backgroundImage } : {}),\n });\n\n return [section];\n}\n\n/**\n * Extracts template-level settings from the BeeFree body.\n */\nfunction extractSettings(\n template: BeeFreeTemplate,\n): TemplateContent[\"settings\"] {\n const body = template.page.body;\n const contentStyle = body?.content?.style ?? {};\n const computedStyle = body?.content?.computedStyle ?? {};\n const containerStyle = body?.container?.style ?? {};\n\n // Exports keep the body width in `computedStyle.messageWidth` and usually\n // carry no `style.width`.\n const width =\n parsePxValue(computedStyle.messageWidth) ||\n parsePxValue(contentStyle.width);\n const bgColor =\n parseColor(contentStyle[\"background-color\"]) ||\n parseColor(containerStyle[\"background-color\"]) ||\n \"#ffffff\";\n const fontFamily = canonicalFontStack(contentStyle[\"font-family\"]) || \"Arial\";\n const linkColor = parseColor(computedStyle.linkColor);\n\n return {\n width: width || 600,\n backgroundColor: bgColor,\n textColor: parseColor(contentStyle.color) || FALLBACK_TEXT_COLOR,\n ...(linkColor ? { linkColor } : {}),\n // BeeFree underlines per link, in the link's own markup, and has no\n // template-wide switch, so links take the browser default: underlined.\n linkUnderline: true,\n fontFamily,\n locale: \"en\",\n };\n}\n\n/**\n * Converts a BeeFree template JSON to Templatical TemplateContent.\n *\n * @param template - The parsed BeeFree JSON object\n * @returns An ImportResult with the converted content and a detailed report\n *\n * @example\n * ```ts\n * import { convertBeeFreeTemplate } from '@templatical/import-beefree';\n *\n * const beeFreeJson = JSON.parse(fileContent);\n * const { content, report } = convertBeeFreeTemplate(beeFreeJson);\n *\n * // Use the content with the editor\n * const editor = init({ container: '#editor', content });\n *\n * // Check the report for any issues\n * console.log(report.summary);\n * console.log(report.warnings);\n * ```\n */\nexport function convertBeeFreeTemplate(\n template: BeeFreeTemplate,\n): ImportResult {\n // Validate structure\n if (!template?.page?.rows) {\n throw new Error(\n \"Invalid BeeFree template: missing page.rows. Ensure you are passing a valid BeeFree JSON export.\",\n );\n }\n\n const entries: ImportReportEntry[] = [];\n const warnings: string[] = [];\n const blocks: Block[] = [];\n\n // Extract web font warnings\n const webFonts = template.page.body?.webFonts;\n if (webFonts && webFonts.length > 1) {\n warnings.push(\n `Template uses ${webFonts.length} web fonts. Only the primary font is preserved; additional fonts may need to be configured via the fonts option.`,\n );\n }\n\n // Rows are measured against the width the imported template renders at.\n const settings = extractSettings(template);\n\n // Process rows\n for (const row of template.page.rows) {\n if (row.empty) continue;\n blocks.push(...processRow(row, settings, entries, warnings));\n }\n\n // Build template content\n const content: TemplateContent = {\n ...createDefaultTemplateContent(),\n blocks,\n settings,\n };\n\n // Build report summary\n const summary = {\n total: entries.length,\n converted: entries.filter((e) => e.status === \"converted\").length,\n approximated: entries.filter((e) => e.status === \"approximated\").length,\n htmlFallback: entries.filter((e) => e.status === \"html-fallback\").length,\n skipped: entries.filter((e) => e.status === \"skipped\").length,\n };\n\n const report: ImportReport = { entries, warnings, summary };\n\n return { content, report };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/style-parser.ts","../src/block-mapper.ts","../src/converter.ts"],"sourcesContent":["import type {\n BackgroundImage,\n BackgroundPositionX,\n BackgroundPositionY,\n BackgroundSize,\n SpacingValue,\n} from \"@templatical/types\";\n\n/**\n * Parses CSS-like style values from BeeFree descriptors.\n */\n\n/**\n * The CSS-wide keywords. Each one defers to the cascade instead of naming a\n * value, and a block field that carried one would reach the renderer\n * verbatim, so the parsers read them as unset and the caller's fallback\n * applies.\n */\nconst CSS_WIDE_KEYWORDS = new Set([\"inherit\", \"initial\", \"unset\", \"revert\"]);\n\nfunction isCssWideKeyword(value: string): boolean {\n return CSS_WIDE_KEYWORDS.has(value.trim().toLowerCase());\n}\n\nexport function parsePxValue(value: string | undefined): number {\n if (!value) return 0;\n const match = value.match(/^(-?\\d+(?:\\.\\d+)?)\\s*px/);\n return match ? Math.round(parseFloat(match[1])) : 0;\n}\n\n/**\n * An image's corner radius in px, or `undefined` for square corners.\n *\n * Accepts a percentage as well as a px length, because `border-radius: 50%` is\n * the idiomatic way to write a circular avatar and the block stores px only. A\n * percentage resolves against the shorter rendered side, which is what turns a\n * square image into a circle and a wide one into a pill.\n *\n * `width` and `height` must be the dimensions actually read from the source. A\n * percentage with neither known is dropped rather than resolved against the\n * caller's fallback width, which would invent a radius the template never\n * expressed. A non-positive result is dropped for the same reason a px `0` is:\n * both mean square, and only absence keeps it out of the exported JSON.\n */\nexport function parseImageBorderRadius(\n value: string | undefined,\n width: number | undefined,\n height: number | undefined,\n): number | undefined {\n if (!value) return undefined;\n\n // Deliberately no `-?`: a negative percentage falls through to the px parse,\n // which rejects it too.\n const percent = value.trim().match(/^(\\d+(?:\\.\\d+)?)\\s*%$/);\n if (percent) {\n const side = Math.min(width ?? Infinity, height ?? Infinity);\n if (!Number.isFinite(side)) return undefined;\n const radius = Math.round((parseFloat(percent[1]) / 100) * side);\n return radius > 0 ? radius : undefined;\n }\n\n const px = parsePxValue(value);\n return px > 0 ? px : undefined;\n}\n\n/**\n * A color as lowercase 6-digit hex where it is hex, otherwise as written.\n *\n * `\"\"` means unset: returned for transparent, `none` and the CSS-wide\n * keywords, so the caller's fallback applies.\n */\nexport function parseColor(value: string | undefined): string {\n if (!value) return \"\";\n\n const trimmed = value.trim();\n const keyword = trimmed.toLowerCase();\n if (\n keyword === \"transparent\" ||\n keyword === \"none\" ||\n isCssWideKeyword(keyword)\n )\n return \"\";\n\n // Already a valid hex color\n if (/^#[0-9a-fA-F]{6}$/.test(trimmed)) return trimmed.toLowerCase();\n\n // 3-digit hex → 6-digit\n if (/^#[0-9a-fA-F]{3}$/.test(trimmed)) {\n const r = trimmed[1];\n const g = trimmed[2];\n const b = trimmed[3];\n return `#${r}${r}${g}${g}${b}${b}`.toLowerCase();\n }\n\n // Return as-is for rgb(), named colors, etc.\n return trimmed;\n}\n\nexport function parseBorderTop(value: string | undefined): {\n width: number;\n style: string;\n color: string;\n} {\n if (!value) return { width: 0, style: \"solid\", color: \"#000000\" };\n\n // \"2px solid #cccccc\"\n const parts = value.trim().split(/\\s+/);\n return {\n width: parsePxValue(parts[0]),\n style: parts[1] || \"solid\",\n color: parseColor(parts[2]) || \"#000000\",\n };\n}\n\nexport function extractPadding(\n style: Record<string, string> | undefined,\n): SpacingValue {\n if (!style) return { top: 0, right: 0, bottom: 0, left: 0 };\n\n // Check for shorthand `padding` first\n if (style.padding) {\n return parseShorthandPadding(style.padding);\n }\n\n return {\n top: parsePxValue(style[\"padding-top\"]),\n right: parsePxValue(style[\"padding-right\"]),\n bottom: parsePxValue(style[\"padding-bottom\"]),\n left: parsePxValue(style[\"padding-left\"]),\n };\n}\n\nfunction parseShorthandPadding(value: string): SpacingValue {\n const parts = value.trim().split(/\\s+/);\n const values = parts.map((p) => parsePxValue(p));\n\n switch (values.length) {\n case 1:\n return {\n top: values[0],\n right: values[0],\n bottom: values[0],\n left: values[0],\n };\n case 2:\n return {\n top: values[0],\n right: values[1],\n bottom: values[0],\n left: values[1],\n };\n case 3:\n return {\n top: values[0],\n right: values[1],\n bottom: values[2],\n left: values[1],\n };\n default:\n return {\n top: values[0],\n right: values[1],\n bottom: values[2],\n left: values[3],\n };\n }\n}\n\n/**\n * A divider's `width` as a number and its unit, or `undefined` when it is\n * neither a px nor a % length (`auto`, a keyword, anything unreadable).\n */\nexport function parseDividerWidth(\n value: string,\n): { value: number; unit: \"px\" | \"%\" } | undefined {\n const match = value.trim().match(/^(-?\\d+(?:\\.\\d+)?)\\s*(%|px)$/);\n if (!match) return undefined;\n return { value: parseFloat(match[1]), unit: match[2] === \"%\" ? \"%\" : \"px\" };\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/**\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/** CSS's initial values: what a browser paints for a longhand BeeFree leaves out. */\nconst CSS_BACKGROUND_DEFAULTS = {\n size: \"auto\",\n x: \"left\",\n y: \"top\",\n repeat: true,\n} as const;\n\n/**\n * The URL inside the first CSS `url(…)`, unquoted and trimmed; \"\" when there\n * is none or it is malformed. A forward scan rather than a regex, so a long\n * run of unclosed `url(` can't make it backtrack.\n */\nfunction cssUrl(value: string | undefined): string {\n const text = value ?? \"\";\n const start = text.search(/url\\(/i);\n if (start === -1) return \"\";\n let i = start + 4;\n while (i < text.length && /\\s/.test(text[i])) i++;\n const quote = text[i];\n if (quote === '\"' || quote === \"'\") {\n const close = text.indexOf(quote, i + 1);\n if (close === -1) return \"\";\n let j = close + 1;\n while (j < text.length && /\\s/.test(text[j])) j++;\n return text[j] === \")\" ? text.slice(i + 1, close).trim() : \"\";\n }\n const close = text.indexOf(\")\", i);\n if (close === -1) return \"\";\n const inner = text.slice(i, close);\n return /[\"']/.test(inner) ? \"\" : inner.trim();\n}\n\n/**\n * A row's background image from a BeeFree style map (`content.style` or\n * `container.style`), `null` when `background-image` holds no `url()`.\n * Unset longhands take CSS's initial values; a value with no exact keyword\n * takes the nearest one and adds a note naming it. `label` names the element\n * in each note, and a px `background-size` is measured against `boxWidth`.\n */\nexport function readBeefreeBackgroundImage(\n style: Record<string, string> | undefined,\n label: string,\n boxWidth: number | undefined,\n): ReadBackgroundImage | null {\n const url = cssUrl(style?.[\"background-image\"]);\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 = CSS_BACKGROUND_DEFAULTS.size;\n const rawSize = style?.[\"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 = CSS_BACKGROUND_DEFAULTS.x;\n let y: BackgroundPositionY = CSS_BACKGROUND_DEFAULTS.y;\n const rawPosition = style?.[\"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 if (!split.exact || !readX?.exact || !readY?.exact) {\n lossy(\"background-position\", rawPosition, `${x} ${y}`);\n }\n }\n\n let repeat: boolean = CSS_BACKGROUND_DEFAULTS.repeat;\n const rawRepeat = style?.[\"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 createTitleBlock,\n createParagraphBlock,\n createImageBlock,\n createButtonBlock,\n createDividerBlock,\n createSpacerBlock,\n createHtmlBlock,\n createSocialIconsBlock,\n createMenuBlock,\n createTableBlock,\n createVideoBlock,\n generateId,\n} from \"@templatical/types\";\nimport type {\n Block,\n DividerBlock,\n HeadingLevel,\n ImageBlock,\n SocialPlatform,\n SocialIcon,\n MenuItemData,\n TableRowData,\n TableCellData,\n} from \"@templatical/types\";\nimport type {\n BeeFreeeModule,\n BeeFreeeModuleDescriptor,\n ImportReportEntry,\n} from \"./types\";\nimport {\n parseImageBorderRadius,\n parsePxValue,\n parseColor,\n parseBorderTop,\n extractPadding,\n parseDividerWidth,\n} from \"./style-parser\";\n\n/**\n * The template text color the import writes when the body sets none.\n */\nexport const FALLBACK_TEXT_COLOR = \"#1a1a1a\";\n\n/**\n * What the caller knows about where a module lands.\n */\nexport interface ModuleContext {\n /**\n * Width in px of the Templatical column the block renders in. Without it a\n * px divider width is kept as pixels.\n */\n columnWidth?: number;\n /**\n * The template's `textColor`, which every text block without a color of\n * its own inherits. Defaults to `FALLBACK_TEXT_COLOR`.\n */\n textColor?: string;\n}\n\n/**\n * Maps BeeFree module type strings to short keys.\n */\nconst MODULE_TYPE_MAP: Record<string, string> = {\n \"mailup-bee-newsletter-modules-text\": \"paragraph\",\n \"mailup-bee-newsletter-modules-paragraph\": \"paragraph\",\n \"mailup-bee-newsletter-modules-heading\": \"title\",\n \"mailup-bee-newsletter-modules-list\": \"list\",\n \"mailup-bee-newsletter-modules-image\": \"image\",\n \"mailup-bee-newsletter-modules-button\": \"button\",\n \"mailup-bee-newsletter-modules-divider\": \"divider\",\n \"mailup-bee-newsletter-modules-spacer\": \"spacer\",\n \"mailup-bee-newsletter-modules-html\": \"html\",\n \"mailup-bee-newsletter-modules-social\": \"social\",\n \"mailup-bee-newsletter-modules-video\": \"video\",\n \"mailup-bee-newsletter-modules-menu\": \"menu\",\n \"mailup-bee-newsletter-modules-table\": \"table\",\n};\n\nconst SOCIAL_PLATFORM_MAP: Record<string, SocialPlatform> = {\n facebook: \"facebook\",\n twitter: \"twitter\",\n x: \"twitter\",\n instagram: \"instagram\",\n linkedin: \"linkedin\",\n youtube: \"youtube\",\n tiktok: \"tiktok\",\n pinterest: \"pinterest\",\n email: \"email\",\n whatsapp: \"whatsapp\",\n telegram: \"telegram\",\n discord: \"discord\",\n snapchat: \"snapchat\",\n reddit: \"reddit\",\n github: \"github\",\n dribbble: \"dribbble\",\n behance: \"behance\",\n};\n\ntype Align = \"left\" | \"center\" | \"right\";\ntype LineStyle = \"solid\" | \"dashed\" | \"dotted\";\n\nfunction toAlign(value: string | undefined, fallback: Align = \"left\"): Align {\n if (value === \"left\" || value === \"center\" || value === \"right\") return value;\n return fallback;\n}\n\nfunction toLineStyle(\n value: string | undefined,\n fallback: LineStyle = \"solid\",\n): LineStyle {\n if (value === \"solid\" || value === \"dashed\" || value === \"dotted\")\n return value;\n return fallback;\n}\n\n/**\n * Strip every `<…>` from a button label and reject any stray `<` or `>`\n * left behind (e.g. a truncated `<script`). The original\n * `value.replace(/<[^>]*>/g, \"\")` was both polynomial-ReDoS over\n * `<<<<…` inputs and an incomplete sanitizer — an unterminated `<script`\n * would survive the strip. Downstream HTML-escapes the label at render\n * time, but stripping here keeps the imported JSON clean.\n */\nfunction stripTagsPlain(text: string): string {\n let out = \"\";\n let i = 0;\n while (i < text.length) {\n if (text[i] === \"<\") {\n const close = text.indexOf(\">\", i + 1);\n if (close === -1) {\n // Unterminated tag — discard the rest.\n break;\n }\n i = close + 1;\n continue;\n }\n if (text[i] === \">\") {\n i++;\n continue;\n }\n out += text[i];\n i++;\n }\n return out;\n}\n\nfunction makeStyles(descriptor: BeeFreeeModuleDescriptor): Block[\"styles\"] {\n const padding = extractPadding(descriptor.style);\n const bg = parseColor(descriptor.style?.[\"background-color\"]);\n return {\n padding,\n ...(bg ? { backgroundColor: bg } : {}),\n };\n}\n\n/**\n * Apply BeeFree text styles as TipTap-compatible inline markup.\n * - text-align → added to each <p> tag's style attribute\n * - color, font-size, font-weight, font-family → wrapped in <span style=\"...\"> inside each <p>\n */\nfunction inlineStylesToHtml(\n html: string,\n style: Record<string, string | undefined>,\n textColor: string,\n): string {\n const spanParts: string[] = [];\n const fontSize = parsePxValue(style[\"font-size\"]);\n if (fontSize && fontSize !== 16) spanParts.push(`font-size: ${fontSize}px`);\n const color = parseColor(style.color);\n // A paragraph inherits the template's text color, so only a different\n // color needs a span to survive.\n if (color && color !== textColor) spanParts.push(`color: ${color}`);\n const fontWeight = style[\"font-weight\"];\n // \"400\" is the numeric synonym for \"normal\" — neither needs an explicit\n // span (matches the import-unlayer importer).\n if (fontWeight && fontWeight !== \"normal\" && fontWeight !== \"400\")\n spanParts.push(`font-weight: ${fontWeight}`);\n const fontFamily = canonicalFontStack(style[\"font-family\"]);\n if (fontFamily) spanParts.push(`font-family: ${fontFamily}`);\n\n const textAlign = style[\"text-align\"];\n const pStyle =\n textAlign && textAlign !== \"left\" ? `text-align: ${textAlign}` : \"\";\n\n if (!pStyle && spanParts.length === 0) return html;\n\n const spanStyle = spanParts.join(\"; \");\n\n // Apply styles to each <p> tag in the HTML\n let result = html;\n\n if (pStyle) {\n // Add text-align to existing <p style=\"...\"> or add style to plain <p>\n result = result\n .replace(/<p style=\"([^\"]*)\">/g, `<p style=\"$1; ${pStyle}\">`)\n .replaceAll(\"<p>\", `<p style=\"${pStyle}\">`);\n }\n\n if (spanStyle) {\n // Wrap inner content of each <p> in a styled span.\n result = wrapParagraphInner(result, spanStyle);\n }\n\n return result;\n}\n\n/**\n * Wrap the inner content of every `<p …>…</p>` with `<span style=\"…\">…</span>`.\n *\n * Hand-rolled linear scanner instead of `/<p([^>]*)>([\\s\\S]*?)<\\/p>/g` because\n * the regex is polynomial-ReDoS: the engine retries `[\\s\\S]*?<\\/p>` at every\n * `<p` start, so inputs like `<p>a<p>a<p>a…` (no `</p>` ever) cost O(n²).\n * The scanner advances `i` monotonically via `indexOf`, keeping the work\n * strictly O(n).\n */\nfunction wrapParagraphInner(html: string, spanStyle: string): string {\n let out = \"\";\n let i = 0;\n while (i < html.length) {\n const open = html.indexOf(\"<p\", i);\n if (open === -1) {\n out += html.substring(i);\n break;\n }\n const afterTagName = html[open + 2];\n if (\n afterTagName !== \">\" &&\n afterTagName !== \" \" &&\n afterTagName !== \"\\t\" &&\n afterTagName !== \"\\n\" &&\n afterTagName !== \"\\r\" &&\n afterTagName !== \"/\"\n ) {\n out += html.substring(i, open + 2);\n i = open + 2;\n continue;\n }\n const openEnd = html.indexOf(\">\", open + 2);\n if (openEnd === -1) {\n out += html.substring(i);\n break;\n }\n const closeStart = html.indexOf(\"</p>\", openEnd + 1);\n if (closeStart === -1) {\n out += html.substring(i);\n break;\n }\n const inner = html.substring(openEnd + 1, closeStart);\n out += html.substring(i, openEnd + 1);\n out += `<span style=\"${spanStyle}\">${inner}</span></p>`;\n i = closeStart + 4;\n }\n return out;\n}\n\nfunction convertText(\n descriptor: BeeFreeeModuleDescriptor,\n textColor: string,\n): Block {\n const textContent =\n descriptor.text ?? descriptor.paragraph ?? descriptor.list;\n const html = textContent?.html ?? \"\";\n const style = textContent?.style ?? {};\n\n return createParagraphBlock({\n content: inlineStylesToHtml(html, style, textColor),\n styles: makeStyles(descriptor),\n });\n}\n\nfunction parseHeadingLevel(tag: string): HeadingLevel {\n const match = tag.match(/^h(\\d)$/i);\n if (match) {\n const num = Number(match[1]);\n if (num >= 1 && num <= 4) return num as HeadingLevel;\n }\n return 2;\n}\n\nfunction convertHeading(\n descriptor: BeeFreeeModuleDescriptor,\n textColor: string,\n): Block {\n const heading = descriptor.heading;\n if (!heading) return convertText(descriptor, textColor);\n\n const style = heading.style ?? {};\n const tag = heading.title ?? \"h2\";\n const text = heading.text ?? \"\";\n // Strip heading tags — content goes inside TipTap, heading tag is rendered by the block\n const content = text.startsWith(\"<\")\n ? text.replace(/^<h\\d[^>]*>|<\\/h\\d>$/gi, \"\")\n : text;\n\n return createTitleBlock({\n content: content ? `<p>${content}</p>` : \"<p></p>\",\n level: parseHeadingLevel(tag),\n color: parseColor(style.color) || undefined,\n textAlign: toAlign(style[\"text-align\"]),\n fontFamily: canonicalFontStack(style[\"font-family\"]) || undefined,\n styles: makeStyles(descriptor),\n });\n}\n\n/**\n * The classes BeeFree records in `computedStyle.class` for an image: its\n * alignment and `autowidth` / `fixedwidth`.\n */\nfunction computedClasses(descriptor: BeeFreeeModuleDescriptor): string[] {\n const value = descriptor.computedStyle?.class;\n return typeof value === \"string\" ? value.trim().split(/\\s+/) : [];\n}\n\n/**\n * The width BeeFree shows an image at, as `ImageBlock.width`. `image.width` and\n * `image.height` are the file's own size: an `autowidth` image fills its\n * column, and a `fixedwidth` one is shown at `computedStyle.width`. An export\n * without `computedStyle` falls back to `image.width`. No width, or one that\n * reaches `room` (the column less the image's side padding), is `\"full\"`.\n */\nfunction resolveImageWidth(\n descriptor: BeeFreeeModuleDescriptor,\n room: number | undefined,\n): ImageBlock[\"width\"] {\n if (computedClasses(descriptor).includes(\"autowidth\")) return \"full\";\n const computed = descriptor.computedStyle?.width;\n const shown =\n parsePxValue(typeof computed === \"string\" ? computed : undefined) ||\n parsePxValue(descriptor.image?.width);\n if (shown <= 0) return \"full\";\n return room !== undefined && shown >= room ? \"full\" : shown;\n}\n\nfunction convertImage(\n descriptor: BeeFreeeModuleDescriptor,\n columnWidth: number | undefined,\n): Block {\n const image = descriptor.image;\n const styles = makeStyles(descriptor);\n if (!image) {\n return createImageBlock({ styles });\n }\n\n const room =\n columnWidth === undefined\n ? undefined\n : columnWidth - styles.padding.left - styles.padding.right;\n const width = resolveImageWidth(descriptor, room);\n\n // `image.width` × `image.height` is the file, so the height is never carried\n // over: the image keeps its aspect ratio at the width it is shown at. A\n // percentage radius resolves against that shown size, and only when BeeFree\n // stated a width.\n const fileWidth = parsePxValue(image.width);\n const fileHeight = parsePxValue(image.height);\n const shownWidth = width === \"full\" ? room : width;\n const stated = fileWidth > 0 || width !== \"full\";\n const shownHeight =\n shownWidth !== undefined && fileWidth > 0 && fileHeight > 0\n ? Math.round((fileHeight * shownWidth) / fileWidth)\n : undefined;\n\n const classes = computedClasses(descriptor);\n const classAlign = ([\"left\", \"center\", \"right\"] as const).find((side) =>\n classes.includes(side),\n );\n\n return createImageBlock({\n src: image.src || \"\",\n alt: image.alt || \"\",\n width,\n // The file's size feeds the editor's aspect-ratio lock; never rendered.\n ...(fileWidth > 0 && fileHeight > 0\n ? { naturalWidth: fileWidth, naturalHeight: fileHeight }\n : {}),\n borderRadius: parseImageBorderRadius(\n image.style?.[\"border-radius\"],\n stated ? shownWidth : undefined,\n shownHeight,\n ),\n // Exports keep the alignment in `computedStyle.class` (\"left fixedwidth\")\n // and often have no image `style` at all.\n align: toAlign(image.style?.[\"text-align\"] ?? classAlign, \"center\"),\n linkUrl: image.href || undefined,\n styles,\n });\n}\n\nfunction convertButton(descriptor: BeeFreeeModuleDescriptor): Block {\n const button = descriptor.button;\n if (!button) {\n return createButtonBlock({ styles: makeStyles(descriptor) });\n }\n\n const style = button.style ?? {};\n const label = button.label ? stripTagsPlain(button.label) : \"Button\";\n\n return createButtonBlock({\n text: label,\n url: button.href || \"#\",\n backgroundColor: parseColor(style[\"background-color\"]) || \"#4f46e5\",\n textColor: parseColor(style.color) || \"#ffffff\",\n borderRadius: parsePxValue(style[\"border-radius\"]),\n fontSize: parsePxValue(style[\"font-size\"]) || 16,\n fontFamily: canonicalFontStack(style[\"font-family\"]) || undefined,\n align: toAlign(style[\"text-align\"], \"center\"),\n buttonPadding: {\n top: parsePxValue(style[\"padding-top\"]) || 12,\n right: parsePxValue(style[\"padding-right\"]) || 24,\n bottom: parsePxValue(style[\"padding-bottom\"]) || 12,\n left: parsePxValue(style[\"padding-left\"]) || 24,\n },\n styles: makeStyles(descriptor),\n });\n}\n\n/**\n * `\"full\"` when the divider spans its column: no width, `100%`, or a px width\n * that reaches `contentWidth`. Without a `contentWidth` a px width stands as\n * stated. Any other percentage stays a percentage, clamped to 0–100%.\n */\nfunction resolveDividerWidth(\n value: string | undefined,\n contentWidth: number | undefined,\n notes: string[],\n): DividerBlock[\"width\"] {\n const raw = (value ?? \"\").trim();\n if (!raw) return \"full\";\n\n const parsed = parseDividerWidth(raw);\n if (!parsed) {\n notes.push(\n `Divider width \"${raw}\" could not be read; imported as full width.`,\n );\n return \"full\";\n }\n\n if (parsed.unit === \"%\") {\n const percent = Math.min(100, Math.max(0, parsed.value));\n if (percent !== parsed.value) {\n notes.push(`Divider width ${raw} was clamped to ${percent}%.`);\n }\n // Two decimals keep the value inside `DividerPercentWidth`'s pattern,\n // which a float printed in exponent form would leave.\n const share = Math.round(percent * 100) / 100;\n return share === 100 ? \"full\" : `${share}%`;\n }\n\n const px = Math.max(0, Math.round(parsed.value));\n if (px !== Math.round(parsed.value)) {\n notes.push(`Divider width ${raw} was clamped to 0px.`);\n }\n return contentWidth !== undefined && px >= contentWidth ? \"full\" : px;\n}\n\nfunction convertDivider(\n descriptor: BeeFreeeModuleDescriptor,\n columnWidth: number | undefined,\n): { block: Block; notes: string[] } {\n const style = descriptor.divider?.style ?? {};\n const border = parseBorderTop(style[\"border-top\"]);\n const styles = makeStyles(descriptor);\n const notes: string[] = [];\n\n // MJML draws a full divider across its column less the divider's own\n // padding, so a px width that reaches that renders the same as \"full\", and\n // \"full\" keeps narrowing with the column on a phone.\n const contentWidth =\n columnWidth === undefined\n ? undefined\n : columnWidth - styles.padding.left - styles.padding.right;\n const width = resolveDividerWidth(style.width, contentWidth, notes);\n\n const align = dividerAlign(descriptor);\n if (width !== \"full\" && (align === \"left\" || align === \"right\")) {\n notes.push(\n `BeeFree aligns this divider ${align}; Templatical centres every divider.`,\n );\n }\n\n return {\n block: createDividerBlock({\n lineStyle: toLineStyle(border.style),\n color: border.color,\n thickness: border.width || 1,\n width,\n styles,\n }),\n notes,\n };\n}\n\n/**\n * BeeFree keeps a divider's alignment in `computedStyle.align`; some exports\n * also write it to the module style's `text-align`.\n */\nfunction dividerAlign(\n descriptor: BeeFreeeModuleDescriptor,\n): string | undefined {\n const computed = descriptor.computedStyle?.align;\n return typeof computed === \"string\"\n ? computed\n : descriptor.style?.[\"text-align\"];\n}\n\nfunction convertSpacer(descriptor: BeeFreeeModuleDescriptor): Block {\n const spacer = descriptor.spacer;\n const height = parsePxValue(spacer?.style?.height) || 24;\n\n return createSpacerBlock({\n height,\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertHtml(descriptor: BeeFreeeModuleDescriptor): Block {\n const html = descriptor.html?.html ?? \"\";\n\n return createHtmlBlock({\n content: html,\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertSocial(\n descriptor: BeeFreeeModuleDescriptor,\n warnings: string[],\n): Block {\n const iconsList = descriptor.iconsList;\n if (!iconsList?.icons) {\n return createSocialIconsBlock({ styles: makeStyles(descriptor) });\n }\n\n const icons: SocialIcon[] = [];\n\n for (const beeIcon of iconsList.icons) {\n const id = (beeIcon.id ?? beeIcon.name ?? \"\").toLowerCase();\n const platform = SOCIAL_PLATFORM_MAP[id];\n\n if (!platform) {\n warnings.push(\n `Unrecognized social icon \"${beeIcon.name || id}\" was skipped.`,\n );\n continue;\n }\n\n icons.push({\n id: generateId(),\n platform,\n url: beeIcon.image?.href || \"#\",\n });\n }\n\n return createSocialIconsBlock({\n icons,\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertVideo(descriptor: BeeFreeeModuleDescriptor): Block {\n const video = descriptor.video;\n if (!video) {\n return createVideoBlock({ styles: makeStyles(descriptor) });\n }\n\n return createVideoBlock({\n url: video.src || \"\",\n thumbnailUrl: video.thumbnail || \"\",\n alt: video.alt || \"\",\n width: parsePxValue(video.style?.width) || 600,\n height: parsePxValue(video.style?.height) || undefined,\n align: toAlign(video.style?.[\"text-align\"], \"center\"),\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertMenu(descriptor: BeeFreeeModuleDescriptor): Block {\n const menu = descriptor.menu;\n if (!menu) {\n return createMenuBlock({ styles: makeStyles(descriptor) });\n }\n\n const style = menu.style ?? {};\n\n const items: MenuItemData[] = (menu.items ?? []).map((item) => ({\n id: generateId(),\n text: item.text || \"\",\n url: item.link || item.href || \"#\",\n openInNewTab: item.target === \"_blank\",\n bold: false,\n underline: false,\n }));\n\n return createMenuBlock({\n items,\n separator: menu.separator || \"|\",\n separatorColor: parseColor(menu.separatorColor) || \"#999999\",\n fontSize: parsePxValue(style[\"font-size\"]) || 14,\n color: parseColor(style.color) || undefined,\n fontFamily: canonicalFontStack(style[\"font-family\"]) || undefined,\n textAlign: toAlign(style[\"text-align\"], \"center\"),\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertTable(descriptor: BeeFreeeModuleDescriptor): {\n block: Block;\n approximated: boolean;\n} {\n const table = descriptor.table;\n if (!table) {\n return {\n block: createTableBlock({ styles: makeStyles(descriptor) }),\n approximated: false,\n };\n }\n\n const style = table.style ?? {};\n\n // Templatical table cells are a plain-text field (the editor stores the\n // cell's innerText), so strip any HTML a BeeFree cell carried the same way\n // button labels are stripped. If a strip actually removed markup, the cell\n // lost formatting or a link URL, so the conversion is an approximation.\n let approximated = false;\n const rows: TableRowData[] = (table.rows ?? []).map((row) => ({\n id: generateId(),\n cells: (row.cells ?? []).map((cell): TableCellData => {\n const raw = cell.content ?? cell.html ?? \"\";\n const content = stripTagsPlain(raw);\n if (content !== raw) {\n approximated = true;\n }\n return { id: generateId(), content };\n }),\n }));\n\n return {\n block: createTableBlock({\n rows,\n hasHeaderRow: table.hasHeaderRow ?? false,\n headerBackgroundColor:\n parseColor(table.headerBackgroundColor) || undefined,\n borderColor: parseColor(style[\"border-color\"]) || \"#dddddd\",\n borderWidth: parsePxValue(style[\"border-width\"]) || 1,\n cellPadding:\n typeof table.cellPadding === \"number\"\n ? table.cellPadding\n : parsePxValue(table.cellPadding as string) || 8,\n fontSize: parsePxValue(style[\"font-size\"]) || 14,\n color: parseColor(style.color) || undefined,\n textAlign: toAlign(style[\"text-align\"]),\n styles: makeStyles(descriptor),\n }),\n approximated,\n };\n}\n\nfunction convertHtmlFallback(module: BeeFreeeModule): Block {\n // Attempt to extract any HTML content from the descriptor\n const descriptor = module.descriptor;\n let html = \"\";\n\n // Try common content fields\n if (descriptor.text?.html) html = descriptor.text.html;\n else if (descriptor.html?.html) html = descriptor.html.html;\n else if (descriptor.heading?.text) html = descriptor.heading.text;\n else html = `<!-- Unsupported BeeFree module: ${module.type} -->`;\n\n return createHtmlBlock({\n content: html,\n styles: makeStyles(descriptor),\n });\n}\n\n/**\n * Converts a single BeeFree module to a Templatical block.\n * Returns the block and a report entry.\n */\nexport function convertModule(\n module: BeeFreeeModule,\n warnings: string[],\n context: ModuleContext = {},\n): { block: Block; entry: ImportReportEntry } {\n const mappedType = MODULE_TYPE_MAP[module.type];\n const descriptor = module.descriptor;\n const textColor = context.textColor ?? FALLBACK_TEXT_COLOR;\n\n if (!mappedType) {\n return {\n block: convertHtmlFallback(module),\n entry: {\n beeFreeModuleType: module.type,\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n note: `Unknown module type \"${module.type}\" converted to HTML block.`,\n },\n };\n }\n\n let block: Block;\n let isApproximation = false;\n let notes: string[] = [];\n\n switch (mappedType) {\n case \"paragraph\":\n case \"list\":\n block = convertText(descriptor, textColor);\n break;\n case \"title\":\n block = convertHeading(descriptor, textColor);\n break;\n case \"image\":\n block = convertImage(descriptor, context.columnWidth);\n break;\n case \"button\":\n block = convertButton(descriptor);\n break;\n case \"divider\": {\n const result = convertDivider(descriptor, context.columnWidth);\n block = result.block;\n notes = result.notes;\n isApproximation = notes.length > 0;\n break;\n }\n case \"spacer\":\n block = convertSpacer(descriptor);\n break;\n case \"html\":\n block = convertHtml(descriptor);\n break;\n case \"social\":\n block = convertSocial(descriptor, warnings);\n break;\n case \"video\":\n block = convertVideo(descriptor);\n break;\n case \"menu\":\n block = convertMenu(descriptor);\n isApproximation = true; // menu styles are approximate\n break;\n case \"table\": {\n const result = convertTable(descriptor);\n block = result.block;\n if (result.approximated) {\n isApproximation = true;\n warnings.push(\n \"HTML inside one or more table cells was reduced to plain text; any cell formatting or links were dropped.\",\n );\n }\n break;\n }\n default:\n block = convertHtmlFallback(module);\n return {\n block,\n entry: {\n beeFreeModuleType: module.type,\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n },\n };\n }\n\n return {\n block,\n entry: {\n beeFreeModuleType: module.type,\n templaticalBlockType: block.type,\n status: isApproximation ? \"approximated\" : \"converted\",\n ...(notes.length > 0 ? { note: notes.join(\" \") } : {}),\n },\n };\n}\n","import {\n canonicalFontStack,\n createSectionBlock,\n createDefaultTemplateContent,\n} from \"@templatical/types\";\nimport type {\n BackgroundImage,\n Block,\n ColumnLayout,\n TemplateContent,\n} from \"@templatical/types\";\nimport type {\n BeeFreeTemplate,\n BeeFreeeRow,\n BeeFreeeColumn,\n ImportResult,\n ImportReport,\n ImportReportEntry,\n} from \"./types\";\nimport {\n convertModule,\n FALLBACK_TEXT_COLOR,\n type ModuleContext,\n} from \"./block-mapper\";\nimport {\n parsePxValue,\n parseColor,\n readBeefreeBackgroundImage,\n} from \"./style-parser\";\n\n/**\n * Determines the Templatical ColumnLayout from BeeFree column grid values.\n */\nfunction resolveColumnLayout(\n columns: BeeFreeeColumn[],\n warnings: string[],\n): ColumnLayout | null {\n if (columns.length === 1) return null; // Single column — no section wrapper needed\n if (columns.length === 3) return \"3\";\n\n if (columns.length === 2) {\n const left = columns[0][\"grid-columns\"] ?? 6;\n const right = columns[1][\"grid-columns\"] ?? 6;\n const total = left + right;\n const ratio = left / total;\n\n if (ratio > 0.58) return \"2-1\";\n if (ratio < 0.42) return \"1-2\";\n return \"2\";\n }\n\n if (columns.length >= 4) {\n warnings.push(\n `Row with ${columns.length} columns was flattened to a single column. BeeFree supports arbitrary columns, but Templatical supports up to 3.`,\n );\n return null; // Flatten to single column\n }\n\n return \"2\";\n}\n\n/**\n * Column widths in px for a layout, mirroring `renderer/src/columns.ts`, and\n * floored as the renderer floors the width it hands each column.\n */\nfunction columnPixels(layout: ColumnLayout, bodyWidth: number): number[] {\n switch (layout) {\n case \"2\":\n return [bodyWidth * 0.5, bodyWidth * 0.5].map(Math.floor);\n case \"3\":\n return [bodyWidth / 3, bodyWidth / 3, bodyWidth / 3].map(Math.floor);\n case \"1-2\":\n return [bodyWidth / 3, (bodyWidth * 2) / 3].map(Math.floor);\n case \"2-1\":\n return [(bodyWidth * 2) / 3, bodyWidth / 3].map(Math.floor);\n default:\n return [Math.floor(bodyWidth)];\n }\n}\n\n/**\n * Converts all modules in a column to Templatical blocks.\n */\nfunction convertColumnModules(\n column: BeeFreeeColumn,\n context: ModuleContext,\n entries: ImportReportEntry[],\n warnings: string[],\n): Block[] {\n const blocks: Block[] = [];\n\n for (const module of column.modules) {\n const { block, entry } = convertModule(module, warnings, context);\n blocks.push(block);\n entries.push(entry);\n }\n\n return blocks;\n}\n\n/**\n * The image a row's section paints. `content.style` is the content area a\n * section paints, so its image maps exactly. `container.style` is the\n * full-width band: it shows through only while the content area has neither\n * an image nor a colour, and then the section paints it at the content\n * width; otherwise it's dropped. Losses go to `warnings`, since rows have no\n * report entry.\n */\nfunction resolveRowBackgroundImage(\n row: BeeFreeeRow,\n rowBg: string,\n width: number,\n warnings: string[],\n): BackgroundImage | undefined {\n const content = readBeefreeBackgroundImage(row.content?.style, \"Row\", width);\n const band = readBeefreeBackgroundImage(\n row.container?.style,\n \"Row container\",\n width,\n );\n\n if (content) {\n warnings.push(...content.notes);\n if (band) {\n warnings.push(\n `Row container background image \"${band.image.url}\" outside the content width was dropped; the section keeps the content background image.`,\n );\n }\n return content.image;\n }\n if (!band) return undefined;\n if (rowBg) {\n warnings.push(\n `Row container background image \"${band.image.url}\" outside the content width was dropped; the section keeps the content background ${rowBg}.`,\n );\n return undefined;\n }\n warnings.push(\n ...band.notes,\n `Row container background image \"${band.image.url}\" spans the full width in BeeFree; the section paints it at the content width.`,\n );\n return band.image;\n}\n\n/**\n * Processes a single BeeFree row into one or more Templatical blocks.\n */\nfunction processRow(\n row: BeeFreeeRow,\n settings: TemplateContent[\"settings\"],\n entries: ImportReportEntry[],\n warnings: string[],\n): Block[] {\n const columns = row.columns;\n if (!columns || columns.length === 0) return [];\n\n // Track locked/synced metadata\n if (row.locked) {\n warnings.push(\n \"A locked row was imported. Lock metadata was dropped; content is preserved.\",\n );\n }\n if (row.synced) {\n warnings.push(\n \"A synced row was imported. Sync metadata was dropped; content is preserved.\",\n );\n }\n\n const layout = resolveColumnLayout(columns, warnings);\n\n // Extract section-level background from row content styles.\n const rowBg = parseColor(row.content?.style?.[\"background-color\"]);\n const backgroundImage = resolveRowBackgroundImage(\n row,\n rowBg,\n settings.width,\n warnings,\n );\n\n if (!layout) {\n // Single column (or flattened 4+ columns) — return modules directly.\n // Either way the modules end up spanning the body.\n const blocks: Block[] = [];\n const context = {\n columnWidth: settings.width,\n textColor: settings.textColor,\n };\n for (const column of columns) {\n blocks.push(...convertColumnModules(column, context, entries, warnings));\n }\n // A row background — a colour or an image — can't survive on bare\n // modules; wrap them in a one-column section so it still renders. Mirrors\n // the multi-column path and the Unlayer importer (which wraps every row).\n if (rowBg || backgroundImage) {\n return [\n createSectionBlock({\n columns: \"1\",\n children: [blocks],\n styles: {\n padding: { top: 0, right: 0, bottom: 0, left: 0 },\n ...(rowBg ? { backgroundColor: rowBg } : {}),\n },\n ...(backgroundImage ? { backgroundImage } : {}),\n }),\n ];\n }\n return blocks;\n }\n\n // Multi-column — wrap in a SectionBlock\n const widths = columnPixels(layout, settings.width);\n const children: Block[][] = columns.map((col, index) =>\n convertColumnModules(\n col,\n { columnWidth: widths[index], textColor: settings.textColor },\n entries,\n warnings,\n ),\n );\n\n const section = createSectionBlock({\n columns: layout,\n children,\n styles: {\n padding: { top: 0, right: 0, bottom: 0, left: 0 },\n ...(rowBg ? { backgroundColor: rowBg } : {}),\n },\n ...(backgroundImage ? { backgroundImage } : {}),\n });\n\n return [section];\n}\n\n/**\n * Extracts template-level settings from the BeeFree body.\n */\nfunction extractSettings(\n template: BeeFreeTemplate,\n): TemplateContent[\"settings\"] {\n const body = template.page.body;\n const contentStyle = body?.content?.style ?? {};\n const computedStyle = body?.content?.computedStyle ?? {};\n const containerStyle = body?.container?.style ?? {};\n\n // Exports keep the body width in `computedStyle.messageWidth` and usually\n // carry no `style.width`.\n const width =\n parsePxValue(computedStyle.messageWidth) ||\n parsePxValue(contentStyle.width);\n const bgColor =\n parseColor(contentStyle[\"background-color\"]) ||\n parseColor(containerStyle[\"background-color\"]) ||\n \"#ffffff\";\n const fontFamily = canonicalFontStack(contentStyle[\"font-family\"]) || \"Arial\";\n const linkColor = parseColor(computedStyle.linkColor);\n\n return {\n width: width || 600,\n backgroundColor: bgColor,\n textColor: parseColor(contentStyle.color) || FALLBACK_TEXT_COLOR,\n ...(linkColor ? { linkColor } : {}),\n // BeeFree underlines per link, in the link's own markup, and has no\n // template-wide switch, so links take the browser default: underlined.\n linkUnderline: true,\n fontFamily,\n locale: \"en\",\n };\n}\n\n/**\n * Converts a BeeFree template JSON to Templatical TemplateContent.\n *\n * @param template - The parsed BeeFree JSON object\n * @returns An ImportResult with the converted content and a detailed report\n *\n * @example\n * ```ts\n * import { convertBeeFreeTemplate } from '@templatical/import-beefree';\n *\n * const beeFreeJson = JSON.parse(fileContent);\n * const { content, report } = convertBeeFreeTemplate(beeFreeJson);\n *\n * // Use the content with the editor\n * const editor = init({ container: '#editor', content });\n *\n * // Check the report for any issues\n * console.log(report.summary);\n * console.log(report.warnings);\n * ```\n */\nexport function convertBeeFreeTemplate(\n template: BeeFreeTemplate,\n): ImportResult {\n // Validate structure\n if (!template?.page?.rows) {\n throw new Error(\n \"Invalid BeeFree template: missing page.rows. Ensure you are passing a valid BeeFree JSON export.\",\n );\n }\n\n const entries: ImportReportEntry[] = [];\n const warnings: string[] = [];\n const blocks: Block[] = [];\n\n // Extract web font warnings\n const webFonts = template.page.body?.webFonts;\n if (webFonts && webFonts.length > 1) {\n warnings.push(\n `Template uses ${webFonts.length} web fonts. Only the primary font is preserved; additional fonts may need to be configured via the fonts option.`,\n );\n }\n\n // Rows are measured against the width the imported template renders at.\n const settings = extractSettings(template);\n\n // Process rows\n for (const row of template.page.rows) {\n if (row.empty) continue;\n blocks.push(...processRow(row, settings, entries, warnings));\n }\n\n // Build template content\n const content: TemplateContent = {\n ...createDefaultTemplateContent(),\n blocks,\n settings,\n };\n\n // Build report summary\n const summary = {\n total: entries.length,\n converted: entries.filter((e) => e.status === \"converted\").length,\n approximated: entries.filter((e) => e.status === \"approximated\").length,\n htmlFallback: entries.filter((e) => e.status === \"html-fallback\").length,\n skipped: entries.filter((e) => e.status === \"skipped\").length,\n };\n\n const report: ImportReport = { entries, warnings, summary };\n\n return { content, report };\n}\n"],"mappings":";;;;;;;;;;;AAkBA,MAAM,oCAAoB,IAAI,IAAI;CAAC;CAAW;CAAW;CAAS;AAAQ,CAAC;AAE3E,SAAS,iBAAiB,OAAwB;CAChD,OAAO,kBAAkB,IAAI,MAAM,KAAK,CAAC,CAAC,YAAY,CAAC;AACzD;AAEA,SAAgB,aAAa,OAAmC;CAC9D,IAAI,CAAC,OAAO,OAAO;CACnB,MAAM,QAAQ,MAAM,MAAM,yBAAyB;CACnD,OAAO,QAAQ,KAAK,MAAM,WAAW,MAAM,EAAE,CAAC,IAAI;AACpD;;;;;;;;;;;;;;;AAgBA,SAAgB,uBACd,OACA,OACA,QACoB;CACpB,IAAI,CAAC,OAAO,OAAO,KAAA;CAInB,MAAM,UAAU,MAAM,KAAK,CAAC,CAAC,MAAM,uBAAuB;CAC1D,IAAI,SAAS;EACX,MAAM,OAAO,KAAK,IAAI,SAAS,UAAU,UAAU,QAAQ;EAC3D,IAAI,CAAC,OAAO,SAAS,IAAI,GAAG,OAAO,KAAA;EACnC,MAAM,SAAS,KAAK,MAAO,WAAW,QAAQ,EAAE,IAAI,MAAO,IAAI;EAC/D,OAAO,SAAS,IAAI,SAAS,KAAA;CAC/B;CAEA,MAAM,KAAK,aAAa,KAAK;CAC7B,OAAO,KAAK,IAAI,KAAK,KAAA;AACvB;;;;;;;AAQA,SAAgB,WAAW,OAAmC;CAC5D,IAAI,CAAC,OAAO,OAAO;CAEnB,MAAM,UAAU,MAAM,KAAK;CAC3B,MAAM,UAAU,QAAQ,YAAY;CACpC,IACE,YAAY,iBACZ,YAAY,UACZ,iBAAiB,OAAO,GAExB,OAAO;CAGT,IAAI,oBAAoB,KAAK,OAAO,GAAG,OAAO,QAAQ,YAAY;CAGlE,IAAI,oBAAoB,KAAK,OAAO,GAAG;EACrC,MAAM,IAAI,QAAQ;EAClB,MAAM,IAAI,QAAQ;EAClB,MAAM,IAAI,QAAQ;EAClB,OAAO,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,YAAY;CACjD;CAGA,OAAO;AACT;AAEA,SAAgB,eAAe,OAI7B;CACA,IAAI,CAAC,OAAO,OAAO;EAAE,OAAO;EAAG,OAAO;EAAS,OAAO;CAAU;CAGhE,MAAM,QAAQ,MAAM,KAAK,CAAC,CAAC,MAAM,KAAK;CACtC,OAAO;EACL,OAAO,aAAa,MAAM,EAAE;EAC5B,OAAO,MAAM,MAAM;EACnB,OAAO,WAAW,MAAM,EAAE,KAAK;CACjC;AACF;AAEA,SAAgB,eACd,OACc;CACd,IAAI,CAAC,OAAO,OAAO;EAAE,KAAK;EAAG,OAAO;EAAG,QAAQ;EAAG,MAAM;CAAE;CAG1D,IAAI,MAAM,SACR,OAAO,sBAAsB,MAAM,OAAO;CAG5C,OAAO;EACL,KAAK,aAAa,MAAM,cAAc;EACtC,OAAO,aAAa,MAAM,gBAAgB;EAC1C,QAAQ,aAAa,MAAM,iBAAiB;EAC5C,MAAM,aAAa,MAAM,eAAe;CAC1C;AACF;AAEA,SAAS,sBAAsB,OAA6B;CAE1D,MAAM,SADQ,MAAM,KAAK,CAAC,CAAC,MAAM,KACd,CAAC,CAAC,KAAK,MAAM,aAAa,CAAC,CAAC;CAE/C,QAAQ,OAAO,QAAf;EACE,KAAK,GACH,OAAO;GACL,KAAK,OAAO;GACZ,OAAO,OAAO;GACd,QAAQ,OAAO;GACf,MAAM,OAAO;EACf;EACF,KAAK,GACH,OAAO;GACL,KAAK,OAAO;GACZ,OAAO,OAAO;GACd,QAAQ,OAAO;GACf,MAAM,OAAO;EACf;EACF,KAAK,GACH,OAAO;GACL,KAAK,OAAO;GACZ,OAAO,OAAO;GACd,QAAQ,OAAO;GACf,MAAM,OAAO;EACf;EACF,SACE,OAAO;GACL,KAAK,OAAO;GACZ,OAAO,OAAO;GACd,QAAQ,OAAO;GACf,MAAM,OAAO;EACf;CACJ;AACF;;;;;AAMA,SAAgB,kBACd,OACiD;CACjD,MAAM,QAAQ,MAAM,KAAK,CAAC,CAAC,MAAM,8BAA8B;CAC/D,IAAI,CAAC,OAAO,OAAO,KAAA;CACnB,OAAO;EAAE,OAAO,WAAW,MAAM,EAAE;EAAG,MAAM,MAAM,OAAO,MAAM,MAAM;CAAK;AAC5E;AAQA,MAAM,SAAS;CAAC;CAAQ;CAAU;AAAO;AACzC,MAAM,SAAS;CAAC;CAAO;CAAU;AAAQ;AACzC,MAAM,UAAU;AAChB,MAAM,SAAS;;;;;;AAOf,SAAS,sBACP,KACA,UAC0C;CAC1C,MAAM,QAAQ,IAAI,YAAY,CAAC,CAAC,MAAM,KAAK;CAC3C,MAAM,CAAC,SAAS;CAChB,IAAI,MAAM,WAAW,MAAM,UAAU,WAAW,UAAU,YACxD,OAAO;EAAE,MAAM;EAAO,OAAO;CAAK;CAEpC,IAAI,MAAM,OAAO,SAAS,SAAS,MAAM,GACvC,OAAO;EAAE,MAAM;EAAQ,OAAO;CAAK;CAErC,MAAM,KAAK,4BAA4B,KAAK,KAAK;CAIjD,OAAO;EAAE,MAFP,MAAM,SAAS,MAAM,KACpB,OAAO,QAAQ,aAAa,KAAA,KAAa,OAAO,GAAG,EAAE,KAAK,WACtC,UAAU;EAAQ,OAAO;CAAM;AACxD;;;;;;;;AASA,SAAS,wBAAwB,KAI/B;CACA,MAAM,QAAQ,IAAI,YAAY,CAAC,CAAC,MAAM,KAAK;CAC3C,IAAI,MAAM,WAAW,GAAG;EACtB,MAAM,CAAC,QAAQ;EACf,OAAO,SAAS,SAAS,SAAS,WAC9B;GAAE,GAAG;GAAU,GAAG;GAAM,OAAO;EAAK,IACpC;GAAE,GAAG;GAAM,GAAG;GAAU,OAAO;EAAK;CAC1C;CACA,IAAI,MAAM,WAAW,GAAG;EACtB,MAAM,CAAC,GAAG,KAAK;EAKf,OAHE,MAAM,SACN,MAAM,YACL,MAAM,aAAa,MAAM,UAAU,MAAM,WAC5B;GAAE,GAAG;GAAG,GAAG;GAAG,OAAO;EAAK,IAAI;GAAE,GAAG;GAAG,GAAG;GAAG,OAAO;EAAK;CAC1E;CACA,OAAO;EACL,GAAG,MAAM,MAAM,SAAS,SAAS,UAAU,SAAS,OAAO,KAAK;EAChE,GAAG,MAAM,MAAM,SAAS,SAAS,SAAS,SAAS,QAAQ,KAAK;EAChE,OAAO;CACT;AACF;;;;;;AAOA,SAAS,oBACP,OACA,MACqC;CACrC,MAAM,UAAU,KAAK,MAAM,UAAU,UAAU,KAAK;CACpD,IAAI,SAAS,OAAO;EAAE,OAAO;EAAS,OAAO;CAAK;CAClD,MAAM,UAAU,QAAQ,KAAK,KAAK;CAClC,IAAI,SAAS;EACX,MAAM,IAAI,OAAO,QAAQ,EAAE;EAC3B,OAAO;GACL,OAAO,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK;GAClD,OAAO,MAAM,KAAK,MAAM,MAAM,MAAM;EACtC;CACF;CACA,IAAI,OAAO,KAAK,KAAK,GACnB,OAAO;EAAE,OAAO,KAAK;EAAI,OAAO,WAAW,KAAK,MAAM;CAAE;CAE1D,OAAO;AACT;;;;;AAMA,SAAS,qBAAqB,KAG5B;CACA,MAAM,QAAQ,IAAI,YAAY,CAAC,CAAC,MAAM,KAAK;CAC3C,IAAI,MAAM,OAAO,SAAS,SAAS,WAAW,GAC5C,OAAO;EAAE,QAAQ;EAAO,OAAO;CAAK;CAEtC,OAAO;EAAE,QAAQ;EAAM,OAAO,MAAM,OAAO,SAAS,SAAS,QAAQ;CAAE;AACzE;;AAGA,MAAM,0BAA0B;CAC9B,MAAM;CACN,GAAG;CACH,GAAG;CACH,QAAQ;AACV;;;;;;AAOA,SAAS,OAAO,OAAmC;CACjD,MAAM,OAAO,SAAS;CACtB,MAAM,QAAQ,KAAK,OAAO,QAAQ;CAClC,IAAI,UAAU,IAAI,OAAO;CACzB,IAAI,IAAI,QAAQ;CAChB,OAAO,IAAI,KAAK,UAAU,KAAK,KAAK,KAAK,EAAE,GAAG;CAC9C,MAAM,QAAQ,KAAK;CACnB,IAAI,UAAU,QAAO,UAAU,KAAK;EAClC,MAAM,QAAQ,KAAK,QAAQ,OAAO,IAAI,CAAC;EACvC,IAAI,UAAU,IAAI,OAAO;EACzB,IAAI,IAAI,QAAQ;EAChB,OAAO,IAAI,KAAK,UAAU,KAAK,KAAK,KAAK,EAAE,GAAG;EAC9C,OAAO,KAAK,OAAO,MAAM,KAAK,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,KAAK,IAAI;CAC7D;CACA,MAAM,QAAQ,KAAK,QAAQ,KAAK,CAAC;CACjC,IAAI,UAAU,IAAI,OAAO;CACzB,MAAM,QAAQ,KAAK,MAAM,GAAG,KAAK;CACjC,OAAO,OAAO,KAAK,KAAK,IAAI,KAAK,MAAM,KAAK;AAC9C;;;;;;;;AASA,SAAgB,2BACd,OACA,OACA,UAC4B;CAC5B,MAAM,MAAM,OAAO,QAAQ,mBAAmB;CAC9C,IAAI,CAAC,KAAK,OAAO;CAEjB,MAAM,QAAkB,CAAC;CACzB,MAAM,SAAS,MAAc,KAAa,UAAwB;EAChE,MAAM,KACJ,GAAG,MAAM,GAAG,KAAK,IAAI,IAAI,sDAAsD,MAAM,GACvF;CACF;CAEA,IAAI,OAAuB,wBAAwB;CACnD,MAAM,UAAU,QAAQ,kBAAkB,EAAE,KAAK;CACjD,IAAI,SAAS;EACX,MAAM,SAAS,sBAAsB,SAAS,QAAQ;EACtD,OAAO,OAAO;EACd,IAAI,CAAC,OAAO,OAAO,MAAM,mBAAmB,SAAS,IAAI;CAC3D;CAEA,IAAI,IAAyB,wBAAwB;CACrD,IAAI,IAAyB,wBAAwB;CACrD,MAAM,cAAc,QAAQ,sBAAsB,EAAE,KAAK;CACzD,IAAI,aAAa;EACf,MAAM,QAAQ,wBAAwB,WAAW;EACjD,MAAM,QAAQ,oBAAoB,MAAM,GAAG,MAAM;EACjD,MAAM,QAAQ,oBAAoB,MAAM,GAAG,MAAM;EACjD,IAAI,OAAO,SAAS;EACpB,IAAI,OAAO,SAAS;EACpB,IAAI,CAAC,MAAM,SAAS,CAAC,OAAO,SAAS,CAAC,OAAO,OAC3C,MAAM,uBAAuB,aAAa,GAAG,EAAE,GAAG,GAAG;CAEzD;CAEA,IAAI,SAAkB,wBAAwB;CAC9C,MAAM,YAAY,QAAQ,oBAAoB,EAAE,KAAK;CACrD,IAAI,WAAW;EACb,MAAM,SAAS,qBAAqB,SAAS;EAC7C,SAAS,OAAO;EAChB,IAAI,CAAC,OAAO,OACV,MAAM,qBAAqB,WAAW,SAAS,WAAW,WAAW;CAEzE;CAEA,OAAO;EAAE,OAAO;GAAE;GAAK;GAAM,UAAU;IAAE;IAAG;GAAE;GAAG;EAAO;EAAG;CAAM;AACnE;;;;ACzTA,MAAM,kBAA0C;CAC9C,sCAAsC;CACtC,2CAA2C;CAC3C,yCAAyC;CACzC,sCAAsC;CACtC,uCAAuC;CACvC,wCAAwC;CACxC,yCAAyC;CACzC,wCAAwC;CACxC,sCAAsC;CACtC,wCAAwC;CACxC,uCAAuC;CACvC,sCAAsC;CACtC,uCAAuC;AACzC;AAEA,MAAM,sBAAsD;CAC1D,UAAU;CACV,SAAS;CACT,GAAG;CACH,WAAW;CACX,UAAU;CACV,SAAS;CACT,QAAQ;CACR,WAAW;CACX,OAAO;CACP,UAAU;CACV,UAAU;CACV,SAAS;CACT,UAAU;CACV,QAAQ;CACR,QAAQ;CACR,UAAU;CACV,SAAS;AACX;AAKA,SAAS,QAAQ,OAA2B,WAAkB,QAAe;CAC3E,IAAI,UAAU,UAAU,UAAU,YAAY,UAAU,SAAS,OAAO;CACxE,OAAO;AACT;AAEA,SAAS,YACP,OACA,WAAsB,SACX;CACX,IAAI,UAAU,WAAW,UAAU,YAAY,UAAU,UACvD,OAAO;CACT,OAAO;AACT;;;;;;;;;AAUA,SAAS,eAAe,MAAsB;CAC5C,IAAI,MAAM;CACV,IAAI,IAAI;CACR,OAAO,IAAI,KAAK,QAAQ;EACtB,IAAI,KAAK,OAAO,KAAK;GACnB,MAAM,QAAQ,KAAK,QAAQ,KAAK,IAAI,CAAC;GACrC,IAAI,UAAU,IAEZ;GAEF,IAAI,QAAQ;GACZ;EACF;EACA,IAAI,KAAK,OAAO,KAAK;GACnB;GACA;EACF;EACA,OAAO,KAAK;EACZ;CACF;CACA,OAAO;AACT;AAEA,SAAS,WAAW,YAAuD;CACzE,MAAM,UAAU,eAAe,WAAW,KAAK;CAC/C,MAAM,KAAK,WAAW,WAAW,QAAQ,mBAAmB;CAC5D,OAAO;EACL;EACA,GAAI,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAAC;CACtC;AACF;;;;;;AAOA,SAAS,mBACP,MACA,OACA,WACQ;CACR,MAAM,YAAsB,CAAC;CAC7B,MAAM,WAAW,aAAa,MAAM,YAAY;CAChD,IAAI,YAAY,aAAa,IAAI,UAAU,KAAK,cAAc,SAAS,GAAG;CAC1E,MAAM,QAAQ,WAAW,MAAM,KAAK;CAGpC,IAAI,SAAS,UAAU,WAAW,UAAU,KAAK,UAAU,OAAO;CAClE,MAAM,aAAa,MAAM;CAGzB,IAAI,cAAc,eAAe,YAAY,eAAe,OAC1D,UAAU,KAAK,gBAAgB,YAAY;CAC7C,MAAM,aAAa,mBAAmB,MAAM,cAAc;CAC1D,IAAI,YAAY,UAAU,KAAK,gBAAgB,YAAY;CAE3D,MAAM,YAAY,MAAM;CACxB,MAAM,SACJ,aAAa,cAAc,SAAS,eAAe,cAAc;CAEnE,IAAI,CAAC,UAAU,UAAU,WAAW,GAAG,OAAO;CAE9C,MAAM,YAAY,UAAU,KAAK,IAAI;CAGrC,IAAI,SAAS;CAEb,IAAI,QAEF,SAAS,OACN,QAAQ,wBAAwB,iBAAiB,OAAO,GAAG,CAAC,CAC5D,WAAW,OAAO,aAAa,OAAO,GAAG;CAG9C,IAAI,WAEF,SAAS,mBAAmB,QAAQ,SAAS;CAG/C,OAAO;AACT;;;;;;;;;;AAWA,SAAS,mBAAmB,MAAc,WAA2B;CACnE,IAAI,MAAM;CACV,IAAI,IAAI;CACR,OAAO,IAAI,KAAK,QAAQ;EACtB,MAAM,OAAO,KAAK,QAAQ,MAAM,CAAC;EACjC,IAAI,SAAS,IAAI;GACf,OAAO,KAAK,UAAU,CAAC;GACvB;EACF;EACA,MAAM,eAAe,KAAK,OAAO;EACjC,IACE,iBAAiB,OACjB,iBAAiB,OACjB,iBAAiB,OACjB,iBAAiB,QACjB,iBAAiB,QACjB,iBAAiB,KACjB;GACA,OAAO,KAAK,UAAU,GAAG,OAAO,CAAC;GACjC,IAAI,OAAO;GACX;EACF;EACA,MAAM,UAAU,KAAK,QAAQ,KAAK,OAAO,CAAC;EAC1C,IAAI,YAAY,IAAI;GAClB,OAAO,KAAK,UAAU,CAAC;GACvB;EACF;EACA,MAAM,aAAa,KAAK,QAAQ,QAAQ,UAAU,CAAC;EACnD,IAAI,eAAe,IAAI;GACrB,OAAO,KAAK,UAAU,CAAC;GACvB;EACF;EACA,MAAM,QAAQ,KAAK,UAAU,UAAU,GAAG,UAAU;EACpD,OAAO,KAAK,UAAU,GAAG,UAAU,CAAC;EACpC,OAAO,gBAAgB,UAAU,IAAI,MAAM;EAC3C,IAAI,aAAa;CACnB;CACA,OAAO;AACT;AAEA,SAAS,YACP,YACA,WACO;CACP,MAAM,cACJ,WAAW,QAAQ,WAAW,aAAa,WAAW;CACxD,MAAM,OAAO,aAAa,QAAQ;CAClC,MAAM,QAAQ,aAAa,SAAS,CAAC;CAErC,OAAO,qBAAqB;EAC1B,SAAS,mBAAmB,MAAM,OAAO,SAAS;EAClD,QAAQ,WAAW,UAAU;CAC/B,CAAC;AACH;AAEA,SAAS,kBAAkB,KAA2B;CACpD,MAAM,QAAQ,IAAI,MAAM,UAAU;CAClC,IAAI,OAAO;EACT,MAAM,MAAM,OAAO,MAAM,EAAE;EAC3B,IAAI,OAAO,KAAK,OAAO,GAAG,OAAO;CACnC;CACA,OAAO;AACT;AAEA,SAAS,eACP,YACA,WACO;CACP,MAAM,UAAU,WAAW;CAC3B,IAAI,CAAC,SAAS,OAAO,YAAY,YAAY,SAAS;CAEtD,MAAM,QAAQ,QAAQ,SAAS,CAAC;CAChC,MAAM,MAAM,QAAQ,SAAS;CAC7B,MAAM,OAAO,QAAQ,QAAQ;CAE7B,MAAM,UAAU,KAAK,WAAW,GAAG,IAC/B,KAAK,QAAQ,0BAA0B,EAAE,IACzC;CAEJ,OAAO,iBAAiB;EACtB,SAAS,UAAU,MAAM,QAAQ,QAAQ;EACzC,OAAO,kBAAkB,GAAG;EAC5B,OAAO,WAAW,MAAM,KAAK,KAAK,KAAA;EAClC,WAAW,QAAQ,MAAM,aAAa;EACtC,YAAY,mBAAmB,MAAM,cAAc,KAAK,KAAA;EACxD,QAAQ,WAAW,UAAU;CAC/B,CAAC;AACH;;;;;AAMA,SAAS,gBAAgB,YAAgD;CACvE,MAAM,QAAQ,WAAW,eAAe;CACxC,OAAO,OAAO,UAAU,WAAW,MAAM,KAAK,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC;AAClE;;;;;;;;AASA,SAAS,kBACP,YACA,MACqB;CACrB,IAAI,gBAAgB,UAAU,CAAC,CAAC,SAAS,WAAW,GAAG,OAAO;CAC9D,MAAM,WAAW,WAAW,eAAe;CAC3C,MAAM,QACJ,aAAa,OAAO,aAAa,WAAW,WAAW,KAAA,CAAS,KAChE,aAAa,WAAW,OAAO,KAAK;CACtC,IAAI,SAAS,GAAG,OAAO;CACvB,OAAO,SAAS,KAAA,KAAa,SAAS,OAAO,SAAS;AACxD;AAEA,SAAS,aACP,YACA,aACO;CACP,MAAM,QAAQ,WAAW;CACzB,MAAM,SAAS,WAAW,UAAU;CACpC,IAAI,CAAC,OACH,OAAO,iBAAiB,EAAE,OAAO,CAAC;CAGpC,MAAM,OACJ,gBAAgB,KAAA,IACZ,KAAA,IACA,cAAc,OAAO,QAAQ,OAAO,OAAO,QAAQ;CACzD,MAAM,QAAQ,kBAAkB,YAAY,IAAI;CAMhD,MAAM,YAAY,aAAa,MAAM,KAAK;CAC1C,MAAM,aAAa,aAAa,MAAM,MAAM;CAC5C,MAAM,aAAa,UAAU,SAAS,OAAO;CAC7C,MAAM,SAAS,YAAY,KAAK,UAAU;CAC1C,MAAM,cACJ,eAAe,KAAA,KAAa,YAAY,KAAK,aAAa,IACtD,KAAK,MAAO,aAAa,aAAc,SAAS,IAChD,KAAA;CAEN,MAAM,UAAU,gBAAgB,UAAU;CAC1C,MAAM,aAAc;EAAC;EAAQ;EAAU;CAAO,CAAC,CAAW,MAAM,SAC9D,QAAQ,SAAS,IAAI,CACvB;CAEA,OAAO,iBAAiB;EACtB,KAAK,MAAM,OAAO;EAClB,KAAK,MAAM,OAAO;EAClB;EAEA,GAAI,YAAY,KAAK,aAAa,IAC9B;GAAE,cAAc;GAAW,eAAe;EAAW,IACrD,CAAC;EACL,cAAc,uBACZ,MAAM,QAAQ,kBACd,SAAS,aAAa,KAAA,GACtB,WACF;EAGA,OAAO,QAAQ,MAAM,QAAQ,iBAAiB,YAAY,QAAQ;EAClE,SAAS,MAAM,QAAQ,KAAA;EACvB;CACF,CAAC;AACH;AAEA,SAAS,cAAc,YAA6C;CAClE,MAAM,SAAS,WAAW;CAC1B,IAAI,CAAC,QACH,OAAO,kBAAkB,EAAE,QAAQ,WAAW,UAAU,EAAE,CAAC;CAG7D,MAAM,QAAQ,OAAO,SAAS,CAAC;CAC/B,MAAM,QAAQ,OAAO,QAAQ,eAAe,OAAO,KAAK,IAAI;CAE5D,OAAO,kBAAkB;EACvB,MAAM;EACN,KAAK,OAAO,QAAQ;EACpB,iBAAiB,WAAW,MAAM,mBAAmB,KAAK;EAC1D,WAAW,WAAW,MAAM,KAAK,KAAK;EACtC,cAAc,aAAa,MAAM,gBAAgB;EACjD,UAAU,aAAa,MAAM,YAAY,KAAK;EAC9C,YAAY,mBAAmB,MAAM,cAAc,KAAK,KAAA;EACxD,OAAO,QAAQ,MAAM,eAAe,QAAQ;EAC5C,eAAe;GACb,KAAK,aAAa,MAAM,cAAc,KAAK;GAC3C,OAAO,aAAa,MAAM,gBAAgB,KAAK;GAC/C,QAAQ,aAAa,MAAM,iBAAiB,KAAK;GACjD,MAAM,aAAa,MAAM,eAAe,KAAK;EAC/C;EACA,QAAQ,WAAW,UAAU;CAC/B,CAAC;AACH;;;;;;AAOA,SAAS,oBACP,OACA,cACA,OACuB;CACvB,MAAM,OAAO,SAAS,GAAA,CAAI,KAAK;CAC/B,IAAI,CAAC,KAAK,OAAO;CAEjB,MAAM,SAAS,kBAAkB,GAAG;CACpC,IAAI,CAAC,QAAQ;EACX,MAAM,KACJ,kBAAkB,IAAI,6CACxB;EACA,OAAO;CACT;CAEA,IAAI,OAAO,SAAS,KAAK;EACvB,MAAM,UAAU,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,OAAO,KAAK,CAAC;EACvD,IAAI,YAAY,OAAO,OACrB,MAAM,KAAK,iBAAiB,IAAI,kBAAkB,QAAQ,GAAG;EAI/D,MAAM,QAAQ,KAAK,MAAM,UAAU,GAAG,IAAI;EAC1C,OAAO,UAAU,MAAM,SAAS,GAAG,MAAM;CAC3C;CAEA,MAAM,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,KAAK,CAAC;CAC/C,IAAI,OAAO,KAAK,MAAM,OAAO,KAAK,GAChC,MAAM,KAAK,iBAAiB,IAAI,qBAAqB;CAEvD,OAAO,iBAAiB,KAAA,KAAa,MAAM,eAAe,SAAS;AACrE;AAEA,SAAS,eACP,YACA,aACmC;CACnC,MAAM,QAAQ,WAAW,SAAS,SAAS,CAAC;CAC5C,MAAM,SAAS,eAAe,MAAM,aAAa;CACjD,MAAM,SAAS,WAAW,UAAU;CACpC,MAAM,QAAkB,CAAC;CAKzB,MAAM,eACJ,gBAAgB,KAAA,IACZ,KAAA,IACA,cAAc,OAAO,QAAQ,OAAO,OAAO,QAAQ;CACzD,MAAM,QAAQ,oBAAoB,MAAM,OAAO,cAAc,KAAK;CAElE,MAAM,QAAQ,aAAa,UAAU;CACrC,IAAI,UAAU,WAAW,UAAU,UAAU,UAAU,UACrD,MAAM,KACJ,+BAA+B,MAAM,qCACvC;CAGF,OAAO;EACL,OAAO,mBAAmB;GACxB,WAAW,YAAY,OAAO,KAAK;GACnC,OAAO,OAAO;GACd,WAAW,OAAO,SAAS;GAC3B;GACA;EACF,CAAC;EACD;CACF;AACF;;;;;AAMA,SAAS,aACP,YACoB;CACpB,MAAM,WAAW,WAAW,eAAe;CAC3C,OAAO,OAAO,aAAa,WACvB,WACA,WAAW,QAAQ;AACzB;AAEA,SAAS,cAAc,YAA6C;CAClE,MAAM,SAAS,WAAW;CAC1B,MAAM,SAAS,aAAa,QAAQ,OAAO,MAAM,KAAK;CAEtD,OAAO,kBAAkB;EACvB;EACA,QAAQ,WAAW,UAAU;CAC/B,CAAC;AACH;AAEA,SAAS,YAAY,YAA6C;CAChE,MAAM,OAAO,WAAW,MAAM,QAAQ;CAEtC,OAAO,gBAAgB;EACrB,SAAS;EACT,QAAQ,WAAW,UAAU;CAC/B,CAAC;AACH;AAEA,SAAS,cACP,YACA,UACO;CACP,MAAM,YAAY,WAAW;CAC7B,IAAI,CAAC,WAAW,OACd,OAAO,uBAAuB,EAAE,QAAQ,WAAW,UAAU,EAAE,CAAC;CAGlE,MAAM,QAAsB,CAAC;CAE7B,KAAK,MAAM,WAAW,UAAU,OAAO;EACrC,MAAM,MAAM,QAAQ,MAAM,QAAQ,QAAQ,GAAA,CAAI,YAAY;EAC1D,MAAM,WAAW,oBAAoB;EAErC,IAAI,CAAC,UAAU;GACb,SAAS,KACP,6BAA6B,QAAQ,QAAQ,GAAG,eAClD;GACA;EACF;EAEA,MAAM,KAAK;GACT,IAAI,WAAW;GACf;GACA,KAAK,QAAQ,OAAO,QAAQ;EAC9B,CAAC;CACH;CAEA,OAAO,uBAAuB;EAC5B;EACA,QAAQ,WAAW,UAAU;CAC/B,CAAC;AACH;AAEA,SAAS,aAAa,YAA6C;CACjE,MAAM,QAAQ,WAAW;CACzB,IAAI,CAAC,OACH,OAAO,iBAAiB,EAAE,QAAQ,WAAW,UAAU,EAAE,CAAC;CAG5D,OAAO,iBAAiB;EACtB,KAAK,MAAM,OAAO;EAClB,cAAc,MAAM,aAAa;EACjC,KAAK,MAAM,OAAO;EAClB,OAAO,aAAa,MAAM,OAAO,KAAK,KAAK;EAC3C,QAAQ,aAAa,MAAM,OAAO,MAAM,KAAK,KAAA;EAC7C,OAAO,QAAQ,MAAM,QAAQ,eAAe,QAAQ;EACpD,QAAQ,WAAW,UAAU;CAC/B,CAAC;AACH;AAEA,SAAS,YAAY,YAA6C;CAChE,MAAM,OAAO,WAAW;CACxB,IAAI,CAAC,MACH,OAAO,gBAAgB,EAAE,QAAQ,WAAW,UAAU,EAAE,CAAC;CAG3D,MAAM,QAAQ,KAAK,SAAS,CAAC;CAE7B,MAAM,SAAyB,KAAK,SAAS,CAAC,EAAA,CAAG,KAAK,UAAU;EAC9D,IAAI,WAAW;EACf,MAAM,KAAK,QAAQ;EACnB,KAAK,KAAK,QAAQ,KAAK,QAAQ;EAC/B,cAAc,KAAK,WAAW;EAC9B,MAAM;EACN,WAAW;CACb,EAAE;CAEF,OAAO,gBAAgB;EACrB;EACA,WAAW,KAAK,aAAa;EAC7B,gBAAgB,WAAW,KAAK,cAAc,KAAK;EACnD,UAAU,aAAa,MAAM,YAAY,KAAK;EAC9C,OAAO,WAAW,MAAM,KAAK,KAAK,KAAA;EAClC,YAAY,mBAAmB,MAAM,cAAc,KAAK,KAAA;EACxD,WAAW,QAAQ,MAAM,eAAe,QAAQ;EAChD,QAAQ,WAAW,UAAU;CAC/B,CAAC;AACH;AAEA,SAAS,aAAa,YAGpB;CACA,MAAM,QAAQ,WAAW;CACzB,IAAI,CAAC,OACH,OAAO;EACL,OAAO,iBAAiB,EAAE,QAAQ,WAAW,UAAU,EAAE,CAAC;EAC1D,cAAc;CAChB;CAGF,MAAM,QAAQ,MAAM,SAAS,CAAC;CAM9B,IAAI,eAAe;CACnB,MAAM,QAAwB,MAAM,QAAQ,CAAC,EAAA,CAAG,KAAK,SAAS;EAC5D,IAAI,WAAW;EACf,QAAQ,IAAI,SAAS,CAAC,EAAA,CAAG,KAAK,SAAwB;GACpD,MAAM,MAAM,KAAK,WAAW,KAAK,QAAQ;GACzC,MAAM,UAAU,eAAe,GAAG;GAClC,IAAI,YAAY,KACd,eAAe;GAEjB,OAAO;IAAE,IAAI,WAAW;IAAG;GAAQ;EACrC,CAAC;CACH,EAAE;CAEF,OAAO;EACL,OAAO,iBAAiB;GACtB;GACA,cAAc,MAAM,gBAAgB;GACpC,uBACE,WAAW,MAAM,qBAAqB,KAAK,KAAA;GAC7C,aAAa,WAAW,MAAM,eAAe,KAAK;GAClD,aAAa,aAAa,MAAM,eAAe,KAAK;GACpD,aACE,OAAO,MAAM,gBAAgB,WACzB,MAAM,cACN,aAAa,MAAM,WAAqB,KAAK;GACnD,UAAU,aAAa,MAAM,YAAY,KAAK;GAC9C,OAAO,WAAW,MAAM,KAAK,KAAK,KAAA;GAClC,WAAW,QAAQ,MAAM,aAAa;GACtC,QAAQ,WAAW,UAAU;EAC/B,CAAC;EACD;CACF;AACF;AAEA,SAAS,oBAAoB,QAA+B;CAE1D,MAAM,aAAa,OAAO;CAC1B,IAAI,OAAO;CAGX,IAAI,WAAW,MAAM,MAAM,OAAO,WAAW,KAAK;MAC7C,IAAI,WAAW,MAAM,MAAM,OAAO,WAAW,KAAK;MAClD,IAAI,WAAW,SAAS,MAAM,OAAO,WAAW,QAAQ;MACxD,OAAO,oCAAoC,OAAO,KAAK;CAE5D,OAAO,gBAAgB;EACrB,SAAS;EACT,QAAQ,WAAW,UAAU;CAC/B,CAAC;AACH;;;;;AAMA,SAAgB,cACd,QACA,UACA,UAAyB,CAAC,GACkB;CAC5C,MAAM,aAAa,gBAAgB,OAAO;CAC1C,MAAM,aAAa,OAAO;CAC1B,MAAM,YAAY,QAAQ,aAAA;CAE1B,IAAI,CAAC,YACH,OAAO;EACL,OAAO,oBAAoB,MAAM;EACjC,OAAO;GACL,mBAAmB,OAAO;GAC1B,sBAAsB;GACtB,QAAQ;GACR,MAAM,wBAAwB,OAAO,KAAK;EAC5C;CACF;CAGF,IAAI;CACJ,IAAI,kBAAkB;CACtB,IAAI,QAAkB,CAAC;CAEvB,QAAQ,YAAR;EACE,KAAK;EACL,KAAK;GACH,QAAQ,YAAY,YAAY,SAAS;GACzC;EACF,KAAK;GACH,QAAQ,eAAe,YAAY,SAAS;GAC5C;EACF,KAAK;GACH,QAAQ,aAAa,YAAY,QAAQ,WAAW;GACpD;EACF,KAAK;GACH,QAAQ,cAAc,UAAU;GAChC;EACF,KAAK,WAAW;GACd,MAAM,SAAS,eAAe,YAAY,QAAQ,WAAW;GAC7D,QAAQ,OAAO;GACf,QAAQ,OAAO;GACf,kBAAkB,MAAM,SAAS;GACjC;EACF;EACA,KAAK;GACH,QAAQ,cAAc,UAAU;GAChC;EACF,KAAK;GACH,QAAQ,YAAY,UAAU;GAC9B;EACF,KAAK;GACH,QAAQ,cAAc,YAAY,QAAQ;GAC1C;EACF,KAAK;GACH,QAAQ,aAAa,UAAU;GAC/B;EACF,KAAK;GACH,QAAQ,YAAY,UAAU;GAC9B,kBAAkB;GAClB;EACF,KAAK,SAAS;GACZ,MAAM,SAAS,aAAa,UAAU;GACtC,QAAQ,OAAO;GACf,IAAI,OAAO,cAAc;IACvB,kBAAkB;IAClB,SAAS,KACP,2GACF;GACF;GACA;EACF;EACA;GACE,QAAQ,oBAAoB,MAAM;GAClC,OAAO;IACL;IACA,OAAO;KACL,mBAAmB,OAAO;KAC1B,sBAAsB;KACtB,QAAQ;IACV;GACF;CACJ;CAEA,OAAO;EACL;EACA,OAAO;GACL,mBAAmB,OAAO;GAC1B,sBAAsB,MAAM;GAC5B,QAAQ,kBAAkB,iBAAiB;GAC3C,GAAI,MAAM,SAAS,IAAI,EAAE,MAAM,MAAM,KAAK,GAAG,EAAE,IAAI,CAAC;EACtD;CACF;AACF;;;;;ACruBA,SAAS,oBACP,SACA,UACqB;CACrB,IAAI,QAAQ,WAAW,GAAG,OAAO;CACjC,IAAI,QAAQ,WAAW,GAAG,OAAO;CAEjC,IAAI,QAAQ,WAAW,GAAG;EACxB,MAAM,OAAO,QAAQ,EAAE,CAAC,mBAAmB;EAG3C,MAAM,QAAQ,QADA,QADA,QAAQ,EAAE,CAAC,mBAAmB;EAI5C,IAAI,QAAQ,KAAM,OAAO;EACzB,IAAI,QAAQ,KAAM,OAAO;EACzB,OAAO;CACT;CAEA,IAAI,QAAQ,UAAU,GAAG;EACvB,SAAS,KACP,YAAY,QAAQ,OAAO,iHAC7B;EACA,OAAO;CACT;CAEA,OAAO;AACT;;;;;AAMA,SAAS,aAAa,QAAsB,WAA6B;CACvE,QAAQ,QAAR;EACE,KAAK,KACH,OAAO,CAAC,YAAY,IAAK,YAAY,EAAG,CAAC,CAAC,IAAI,KAAK,KAAK;EAC1D,KAAK,KACH,OAAO;GAAC,YAAY;GAAG,YAAY;GAAG,YAAY;EAAC,CAAC,CAAC,IAAI,KAAK,KAAK;EACrE,KAAK,OACH,OAAO,CAAC,YAAY,GAAI,YAAY,IAAK,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK;EAC5D,KAAK,OACH,OAAO,CAAE,YAAY,IAAK,GAAG,YAAY,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK;EAC5D,SACE,OAAO,CAAC,KAAK,MAAM,SAAS,CAAC;CACjC;AACF;;;;AAKA,SAAS,qBACP,QACA,SACA,SACA,UACS;CACT,MAAM,SAAkB,CAAC;CAEzB,KAAK,MAAM,UAAU,OAAO,SAAS;EACnC,MAAM,EAAE,OAAO,UAAU,cAAc,QAAQ,UAAU,OAAO;EAChE,OAAO,KAAK,KAAK;EACjB,QAAQ,KAAK,KAAK;CACpB;CAEA,OAAO;AACT;;;;;;;;;AAUA,SAAS,0BACP,KACA,OACA,OACA,UAC6B;CAC7B,MAAM,UAAU,2BAA2B,IAAI,SAAS,OAAO,OAAO,KAAK;CAC3E,MAAM,OAAO,2BACX,IAAI,WAAW,OACf,iBACA,KACF;CAEA,IAAI,SAAS;EACX,SAAS,KAAK,GAAG,QAAQ,KAAK;EAC9B,IAAI,MACF,SAAS,KACP,mCAAmC,KAAK,MAAM,IAAI,yFACpD;EAEF,OAAO,QAAQ;CACjB;CACA,IAAI,CAAC,MAAM,OAAO,KAAA;CAClB,IAAI,OAAO;EACT,SAAS,KACP,mCAAmC,KAAK,MAAM,IAAI,oFAAoF,MAAM,EAC9I;EACA;CACF;CACA,SAAS,KACP,GAAG,KAAK,OACR,mCAAmC,KAAK,MAAM,IAAI,+EACpD;CACA,OAAO,KAAK;AACd;;;;AAKA,SAAS,WACP,KACA,UACA,SACA,UACS;CACT,MAAM,UAAU,IAAI;CACpB,IAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,OAAO,CAAC;CAG9C,IAAI,IAAI,QACN,SAAS,KACP,6EACF;CAEF,IAAI,IAAI,QACN,SAAS,KACP,6EACF;CAGF,MAAM,SAAS,oBAAoB,SAAS,QAAQ;CAGpD,MAAM,QAAQ,WAAW,IAAI,SAAS,QAAQ,mBAAmB;CACjE,MAAM,kBAAkB,0BACtB,KACA,OACA,SAAS,OACT,QACF;CAEA,IAAI,CAAC,QAAQ;EAGX,MAAM,SAAkB,CAAC;EACzB,MAAM,UAAU;GACd,aAAa,SAAS;GACtB,WAAW,SAAS;EACtB;EACA,KAAK,MAAM,UAAU,SACnB,OAAO,KAAK,GAAG,qBAAqB,QAAQ,SAAS,SAAS,QAAQ,CAAC;EAKzE,IAAI,SAAS,iBACX,OAAO,CACL,mBAAmB;GACjB,SAAS;GACT,UAAU,CAAC,MAAM;GACjB,QAAQ;IACN,SAAS;KAAE,KAAK;KAAG,OAAO;KAAG,QAAQ;KAAG,MAAM;IAAE;IAChD,GAAI,QAAQ,EAAE,iBAAiB,MAAM,IAAI,CAAC;GAC5C;GACA,GAAI,kBAAkB,EAAE,gBAAgB,IAAI,CAAC;EAC/C,CAAC,CACH;EAEF,OAAO;CACT;CAGA,MAAM,SAAS,aAAa,QAAQ,SAAS,KAAK;CAClD,MAAM,WAAsB,QAAQ,KAAK,KAAK,UAC5C,qBACE,KACA;EAAE,aAAa,OAAO;EAAQ,WAAW,SAAS;CAAU,GAC5D,SACA,QACF,CACF;CAYA,OAAO,CAVS,mBAAmB;EACjC,SAAS;EACT;EACA,QAAQ;GACN,SAAS;IAAE,KAAK;IAAG,OAAO;IAAG,QAAQ;IAAG,MAAM;GAAE;GAChD,GAAI,QAAQ,EAAE,iBAAiB,MAAM,IAAI,CAAC;EAC5C;EACA,GAAI,kBAAkB,EAAE,gBAAgB,IAAI,CAAC;CAC/C,CAEc,CAAC;AACjB;;;;AAKA,SAAS,gBACP,UAC6B;CAC7B,MAAM,OAAO,SAAS,KAAK;CAC3B,MAAM,eAAe,MAAM,SAAS,SAAS,CAAC;CAC9C,MAAM,gBAAgB,MAAM,SAAS,iBAAiB,CAAC;CACvD,MAAM,iBAAiB,MAAM,WAAW,SAAS,CAAC;CAIlD,MAAM,QACJ,aAAa,cAAc,YAAY,KACvC,aAAa,aAAa,KAAK;CACjC,MAAM,UACJ,WAAW,aAAa,mBAAmB,KAC3C,WAAW,eAAe,mBAAmB,KAC7C;CACF,MAAM,aAAa,mBAAmB,aAAa,cAAc,KAAK;CACtE,MAAM,YAAY,WAAW,cAAc,SAAS;CAEpD,OAAO;EACL,OAAO,SAAS;EAChB,iBAAiB;EACjB,WAAW,WAAW,aAAa,KAAK,KAAA;EACxC,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;EAGjC,eAAe;EACf;EACA,QAAQ;CACV;AACF;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,uBACd,UACc;CAEd,IAAI,CAAC,UAAU,MAAM,MACnB,MAAM,IAAI,MACR,kGACF;CAGF,MAAM,UAA+B,CAAC;CACtC,MAAM,WAAqB,CAAC;CAC5B,MAAM,SAAkB,CAAC;CAGzB,MAAM,WAAW,SAAS,KAAK,MAAM;CACrC,IAAI,YAAY,SAAS,SAAS,GAChC,SAAS,KACP,iBAAiB,SAAS,OAAO,iHACnC;CAIF,MAAM,WAAW,gBAAgB,QAAQ;CAGzC,KAAK,MAAM,OAAO,SAAS,KAAK,MAAM;EACpC,IAAI,IAAI,OAAO;EACf,OAAO,KAAK,GAAG,WAAW,KAAK,UAAU,SAAS,QAAQ,CAAC;CAC7D;CAoBA,OAAO;EAAE,SAAA;GAhBP,GAAG,6BAA6B;GAChC;GACA;EAca;EAAG,QAAA;GAFa;GAAS;GAAU,SAAA;IAPhD,OAAO,QAAQ;IACf,WAAW,QAAQ,QAAQ,MAAM,EAAE,WAAW,WAAW,CAAC,CAAC;IAC3D,cAAc,QAAQ,QAAQ,MAAM,EAAE,WAAW,cAAc,CAAC,CAAC;IACjE,cAAc,QAAQ,QAAQ,MAAM,EAAE,WAAW,eAAe,CAAC,CAAC;IAClE,SAAS,QAAQ,QAAQ,MAAM,EAAE,WAAW,SAAS,CAAC,CAAC;GAGD;EAEjC;CAAE;AAC3B"}
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@templatical/import-beefree",
3
3
  "description": "Convert BeeFree email templates to Templatical format",
4
- "version": "0.51.1",
4
+ "version": "0.52.1",
5
5
  "bugs": "https://github.com/templatical/sdk/issues",
6
6
  "dependencies": {
7
- "@templatical/types": "0.51.1"
7
+ "@templatical/types": "0.52.1"
8
8
  },
9
9
  "devDependencies": {
10
10
  "typescript": "^6.0.3",