@templatical/import-unlayer 0.51.0 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- //#region src/types.d.ts
2
1
  /**
3
2
  * Unlayer JSON type definitions.
4
3
  * Based on the design JSON shape produced by `editor.saveDesign()` in
@@ -225,8 +224,6 @@ interface ImportResult {
225
224
  content: import("@templatical/types").TemplateContent;
226
225
  report: ImportReport;
227
226
  }
228
- //#endregion
229
- //#region src/converter.d.ts
230
227
  /**
231
228
  * Converts an Unlayer design JSON to Templatical TemplateContent.
232
229
  *
@@ -247,6 +244,5 @@ interface ImportResult {
247
244
  * ```
248
245
  */
249
246
  export declare function convertUnlayerTemplate(template: UnlayerTemplate): ImportResult;
250
- //#endregion
251
247
  export type { ConversionStatus, ImportReport, ImportReportEntry, ImportResult, UnlayerBody, UnlayerColumn, UnlayerContent, UnlayerContentValues, UnlayerRow, UnlayerTemplate };
252
248
  //# 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, 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 Unlayer content values.
5
5
  */
@@ -282,8 +282,7 @@ function readUnlayerBackgroundImage(bg, label, boxWidth) {
282
282
  notes
283
283
  };
284
284
  }
285
- //#endregion
286
- //#region src/block-mapper.ts
285
+ // src/block-mapper.ts
287
286
  const SOCIAL_PLATFORM_MAP = {
288
287
  facebook: "facebook",
289
288
  twitter: "twitter",
@@ -767,8 +766,7 @@ function convertContent(content, warnings, context = {}) {
767
766
  };
768
767
  }
769
768
  }
770
- //#endregion
771
- //#region src/converter.ts
769
+ // src/converter.ts
772
770
  function resolveColumnLayout(cells, warnings) {
773
771
  if (cells.length <= 1) return "1";
774
772
  if (cells.length === 3) return "3";
@@ -961,7 +959,6 @@ function convertUnlayerTemplate(template) {
961
959
  }
962
960
  };
963
961
  }
964
- //#endregion
965
962
  export { convertUnlayerTemplate };
966
963
 
967
964
  //# 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 { canonicalFontStack } from \"@templatical/types\";\nimport type {\n BackgroundImage,\n BackgroundPositionX,\n BackgroundPositionY,\n BackgroundSize,\n SpacingValue,\n} from \"@templatical/types\";\nimport type {\n UnlayerBackgroundImage,\n UnlayerBorder,\n UnlayerFontFamily,\n} from \"./types\";\n\n/**\n * Parses CSS-like style values from Unlayer content values.\n */\n\nexport function parsePxValue(value: string | number | undefined): number {\n if (value === undefined || value === null) return 0;\n if (typeof value === \"number\") return Math.round(value);\n // Single trailing-whitespace group with an optional `px` inside it. The\n // original `\\s*(?:px)?\\s*` had two `\\s*` quantifiers around an optional\n // group, which is the textbook polynomial-ReDoS shape (\"how should the\n // whitespace split across the two quantifiers?\") over inputs like\n // `9 ` with no `px`.\n const match = value.match(/^(-?\\d+(?:\\.\\d+)?)\\s*(?:px\\s*)?$/);\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 | number | undefined,\n width: number | undefined,\n height: number | undefined,\n): number | undefined {\n if (value === undefined || value === null || value === \"\") return undefined;\n if (typeof value === \"number\") {\n return value > 0 ? Math.round(value) : undefined;\n }\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\nexport function parseColor(value: string | undefined): string {\n if (!value || value === \"transparent\") return \"\";\n\n const trimmed = value.trim();\n\n if (/^#[0-9a-fA-F]{6}$/.test(trimmed)) return trimmed.toLowerCase();\n\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 trimmed;\n}\n\nexport function parsePaddingShorthand(value: string | undefined): SpacingValue {\n if (!value) return { top: 0, right: 0, bottom: 0, left: 0 };\n\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\nexport function parseBorderObject(border: UnlayerBorder | undefined): {\n width: number;\n style: string;\n color: string;\n} {\n if (!border) return { width: 0, style: \"solid\", color: \"#000000\" };\n return {\n width: parsePxValue(border.borderTopWidth),\n style: border.borderTopStyle || \"solid\",\n color: parseColor(border.borderTopColor) || \"#000000\",\n };\n}\n\n/**\n * A divider's `width` as a percentage or a px length; a unitless value is px.\n * `undefined` for anything else. The sign survives, so the caller can clamp.\n */\nexport function parseDividerWidth(\n value: string | number,\n): { unit: \"%\" | \"px\"; value: number } | undefined {\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? { unit: \"px\", value } : undefined;\n }\n // One `\\s*` before the unit, so there is a single way to match trailing\n // whitespace (see `parsePxValue` for the two-quantifier ReDoS shape).\n const match = value.trim().match(/^(-?\\d+(?:\\.\\d+)?)\\s*(%|px)?$/);\n if (!match) return undefined;\n return { unit: match[2] === \"%\" ? \"%\" : \"px\", value: parseFloat(match[1]) };\n}\n\n/**\n * The font stack to store for an Unlayer `fontFamily`, which is either a CSS\n * string or a `{ label, value }` pick from Unlayer's font list. The label is\n * read only when `value` sets no font.\n */\nexport function parseFontFamily(\n value: UnlayerFontFamily | string | undefined,\n): string {\n if (!value) return \"\";\n if (typeof value === \"string\") return canonicalFontStack(value);\n return canonicalFontStack(value.value) || canonicalFontStack(value.label);\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\nconst UNLAYER_POSITIONS: Readonly<\n Record<string, { x: BackgroundPositionX; y: BackgroundPositionY }>\n> = {\n \"top-left\": { x: \"left\", y: \"top\" },\n \"top-center\": { x: \"center\", y: \"top\" },\n \"top-right\": { x: \"right\", y: \"top\" },\n \"center-left\": { x: \"left\", y: \"center\" },\n center: { x: \"center\", y: \"center\" },\n \"center-right\": { x: \"right\", y: \"center\" },\n \"bottom-left\": { x: \"left\", y: \"bottom\" },\n \"bottom-center\": { x: \"center\", y: \"bottom\" },\n \"bottom-right\": { x: \"right\", y: \"bottom\" },\n};\n\n/**\n * A CSS `background-size` as a keyword. A size with a side at 100%, or a px\n * width spanning the box, fills it as `cover` does; any other length or\n * percentage 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 * 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 * An Unlayer row's `backgroundImage`, `null` when its `url` is blank. Unset\n * fields take Unlayer's editor defaults: `custom` size, which is the image at\n * its own size (`auto`), `center`, and `no-repeat`. A `custom` position reads\n * `customPosition` as CSS. A value with no exact keyword takes the nearest\n * one and adds a note naming it. Older designs carry `cover` and `center`\n * booleans instead of `size` and `position`; they map exactly, and only when\n * the string field is absent.\n */\nexport function readUnlayerBackgroundImage(\n bg: UnlayerBackgroundImage | undefined,\n label: string,\n boxWidth: number,\n): ReadBackgroundImage | null {\n const url = typeof bg?.url === \"string\" ? bg.url.trim() : \"\";\n if (!bg || !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 = \"auto\";\n const rawSize = typeof bg.size === \"string\" ? bg.size.trim() : \"\";\n if (rawSize === \"cover\" || rawSize === \"contain\") {\n size = rawSize;\n } else if (rawSize && rawSize !== \"custom\") {\n const parsed = backgroundSizeKeyword(rawSize, boxWidth);\n size = parsed.size;\n if (!parsed.exact) lossy(\"backgroundImage.size\", rawSize, size);\n } else if (!rawSize && typeof bg.cover === \"boolean\") {\n size = bg.cover ? \"cover\" : \"auto\";\n }\n\n let x: BackgroundPositionX = \"center\";\n let y: BackgroundPositionY = \"center\";\n const rawPosition = typeof bg.position === \"string\" ? bg.position.trim() : \"\";\n if (rawPosition === \"custom\") {\n const [rawX = \"\", rawY = \"\"] = Array.isArray(bg.customPosition)\n ? bg.customPosition.map((part) => String(part).trim())\n : [];\n const readX = backgroundAxisPoint(rawX.toLowerCase(), X_GRID);\n const readY = backgroundAxisPoint(rawY.toLowerCase(), Y_GRID);\n x = readX?.point ?? x;\n y = readY?.point ?? y;\n if (!readX?.exact || !readY?.exact) {\n lossy(\n \"backgroundImage.customPosition\",\n `${rawX} ${rawY}`.trim(),\n `${x} ${y}`,\n );\n }\n } else if (rawPosition) {\n const keyword = Object.hasOwn(UNLAYER_POSITIONS, rawPosition)\n ? UNLAYER_POSITIONS[rawPosition]\n : undefined;\n if (keyword) {\n x = keyword.x;\n y = keyword.y;\n } else {\n lossy(\"backgroundImage.position\", rawPosition, `${x} ${y}`);\n }\n } else if (typeof bg.center === \"boolean\") {\n x = bg.center ? \"center\" : \"left\";\n y = \"top\";\n }\n\n let repeat = false;\n if (typeof bg.repeat === \"boolean\") {\n repeat = bg.repeat;\n } else if (typeof bg.repeat === \"string\" && bg.repeat.trim()) {\n const rawRepeat = bg.repeat.trim();\n repeat = rawRepeat !== \"no-repeat\";\n if (repeat && rawRepeat !== \"repeat\") {\n lossy(\"backgroundImage.repeat\", rawRepeat, \"repeat\");\n }\n }\n\n return { image: { url, size, position: { x, y }, repeat }, notes };\n}\n","import {\n createTitleBlock,\n createParagraphBlock,\n createImageBlock,\n createButtonBlock,\n createDividerBlock,\n createSpacerBlock,\n createHtmlBlock,\n createSocialIconsBlock,\n createMenuBlock,\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} from \"@templatical/types\";\nimport type {\n UnlayerContent,\n UnlayerContentValues,\n UnlayerImageSrc,\n ImportReportEntry,\n} from \"./types\";\nimport {\n parseImageBorderRadius,\n parsePxValue,\n parseColor,\n parseFontFamily,\n parsePaddingShorthand,\n parseBorderObject,\n parseDividerWidth,\n} from \"./style-parser\";\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 mail: \"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\n/** The body width when a template's `contentWidth` states no px value. */\nexport const DEFAULT_BODY_WIDTH = 600;\n\n/** The template text colour when the body sets none. */\nexport const FALLBACK_TEXT_COLOR = \"#1a1a1a\";\n\n/**\n * What the caller knows about where a content node lands.\n */\nexport interface ContentContext {\n /**\n * Width in px of the Templatical column the block renders in. Defaults to\n * `DEFAULT_BODY_WIDTH`.\n */\n columnWidth?: number;\n /**\n * The template's `textColor`, which every text block without a colour of\n * its own inherits. Defaults to `FALLBACK_TEXT_COLOR`.\n */\n textColor?: string;\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\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 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\nfunction makeStyles(values: UnlayerContentValues): Block[\"styles\"] {\n const padding = parsePaddingShorthand(values.containerPadding);\n return {\n padding,\n };\n}\n\n/**\n * Apply Unlayer text-level styles as TipTap-compatible inline markup.\n * Mirrors the BeeFree importer's helper but reads from Unlayer's flat\n * values shape rather than a CSS style record.\n */\nfunction inlineStylesToHtml(\n html: string,\n values: UnlayerContentValues,\n textColor: string,\n): string {\n const spanParts: string[] = [];\n const fontSize = parsePxValue(values.fontSize);\n if (fontSize && fontSize !== 16) spanParts.push(`font-size: ${fontSize}px`);\n const color = parseColor(values.color);\n if (color && color !== textColor) spanParts.push(`color: ${color}`);\n const fontWeight = values.fontWeight;\n if (\n fontWeight !== undefined &&\n fontWeight !== null &&\n String(fontWeight) !== \"normal\" &&\n String(fontWeight) !== \"400\"\n ) {\n spanParts.push(`font-weight: ${fontWeight}`);\n }\n const fontFamily = parseFontFamily(values.fontFamily);\n if (fontFamily) spanParts.push(`font-family: ${fontFamily}`);\n\n const textAlign = values.textAlign;\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 let result = html;\n\n if (pStyle) {\n result = result\n .replace(/<p style=\"([^\"]*)\">/g, `<p style=\"$1; ${pStyle}\">`)\n .replaceAll(\"<p>\", `<p style=\"${pStyle}\">`);\n }\n\n if (spanStyle) {\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 ensureParagraphWrapped(html: string): string {\n if (!html) return \"<p></p>\";\n if (/<p[\\s>]/i.test(html)) return html;\n return `<p>${html}</p>`;\n}\n\nfunction convertText(values: UnlayerContentValues, textColor: string): Block {\n const html = ensureParagraphWrapped(values.text ?? \"\");\n\n return createParagraphBlock({\n content: inlineStylesToHtml(html, values, textColor),\n styles: makeStyles(values),\n });\n}\n\nfunction parseHeadingLevel(tag: string | undefined): HeadingLevel {\n if (!tag) return 2;\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(values: UnlayerContentValues): Block {\n const text = values.text ?? \"\";\n const stripped = text.replace(/^<h\\d[^>]*>|<\\/h\\d>$/gi, \"\");\n const content = stripped ? `<p>${stripped}</p>` : \"<p></p>\";\n\n return createTitleBlock({\n content,\n level: parseHeadingLevel(values.headingType),\n color: parseColor(values.color) || undefined,\n textAlign: toAlign(values.textAlign),\n fontFamily: parseFontFamily(values.fontFamily) || undefined,\n styles: makeStyles(values),\n });\n}\n\n/**\n * The width Unlayer shows an image at, as `ImageBlock.width`. `src.width` is\n * the file's own size: with `autoWidth` (the default) the image is shown at\n * that size capped at the column, without it at `maxWidth`, a percentage of\n * the column. `room` is the column less the image's side padding. No width, or\n * one that reaches `room`, is `\"full\"`.\n *\n * A percentage under 100% becomes px of `room`, which keeps its size on a\n * phone where a percentage would shrink with the column, so it pushes a note.\n */\nfunction resolveImageWidth(\n src: UnlayerImageSrc | undefined,\n room: number,\n notes: string[],\n): ImageBlock[\"width\"] {\n if (src?.autoWidth === false) {\n const raw = (src.maxWidth ?? \"\").trim();\n const parsed = raw ? parseDividerWidth(raw) : undefined;\n if (!parsed || parsed.value <= 0) {\n if (raw) {\n notes.push(\n `Image maxWidth \"${raw}\" could not be read; imported as full width.`,\n );\n }\n return \"full\";\n }\n const px = Math.round(\n parsed.unit === \"%\" ? (room * parsed.value) / 100 : parsed.value,\n );\n if (px >= room) return \"full\";\n if (parsed.unit === \"%\") {\n notes.push(\n `Unlayer shows this image at ${raw} of its column; imported as ${px}px, which keeps its size on a phone.`,\n );\n }\n return px;\n }\n\n const natural = src?.width ? Math.round(src.width) : 0;\n return natural <= 0 || natural >= room ? \"full\" : natural;\n}\n\nfunction convertImage(\n values: UnlayerContentValues,\n columnWidth: number,\n): { block: Block; notes: string[] } {\n const src = values.src;\n const action = values.action?.values;\n const styles = makeStyles(values);\n const notes: string[] = [];\n const room = columnWidth - styles.padding.left - styles.padding.right;\n const width = resolveImageWidth(src, room, notes);\n\n // `src.width` and `src.height` are the file's size, so the height is never\n // carried over: the image keeps its aspect ratio at the width it is shown\n // at. A percentage radius resolves against that shown size, and only when\n // Unlayer stated the file's width.\n const shownWidth = width === \"full\" ? room : width;\n const shownHeight =\n src?.width && src.height\n ? Math.round((src.height * shownWidth) / src.width)\n : undefined;\n\n const fileWidth = src?.width ? Math.round(src.width) : 0;\n const fileHeight = src?.height ? Math.round(src.height) : 0;\n\n return {\n block: createImageBlock({\n src: src?.url || \"\",\n alt: values.altText || \"\",\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 values.borderRadius,\n src?.width ? shownWidth : undefined,\n shownHeight,\n ),\n align: toAlign(values.textAlign, \"center\"),\n linkUrl: action?.href || undefined,\n linkOpenInNewTab: action?.target === \"_blank\" || undefined,\n styles,\n }),\n notes,\n };\n}\n\nfunction convertButton(values: UnlayerContentValues): Block {\n const colors = values.buttonColors ?? {};\n const padding = values.padding\n ? parsePaddingShorthand(values.padding)\n : { top: 12, right: 24, bottom: 12, left: 24 };\n const label = stripTagsPlain(values.text ?? \"Button\");\n const linkValues = values.href?.values;\n\n return createButtonBlock({\n text: label,\n url: linkValues?.href || \"#\",\n openInNewTab: linkValues?.target === \"_blank\" || undefined,\n backgroundColor: parseColor(colors.backgroundColor) || \"#4f46e5\",\n textColor: parseColor(colors.color) || \"#ffffff\",\n borderRadius: parsePxValue(values.borderRadius),\n fontSize: parsePxValue(values.fontSize) || 16,\n fontFamily: parseFontFamily(values.fontFamily) || undefined,\n align: toAlign(values.textAlign, \"center\"),\n buttonPadding: padding,\n styles: makeStyles(values),\n });\n}\n\n/**\n * `\"full\"` when the divider spans its column: no width, `100%`, or a px width\n * that reaches `room`, the span the column leaves the line. Any other\n * percentage stays a percentage, clamped to 0–100%.\n */\nfunction resolveDividerWidth(\n value: string | number | undefined,\n room: number,\n notes: string[],\n): DividerBlock[\"width\"] {\n const raw = String(value ?? \"\").trim();\n if (!raw) return \"full\";\n\n const parsed = parseDividerWidth(typeof value === \"number\" ? value : 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 px >= room ? \"full\" : px;\n}\n\n/**\n * Unlayer places a partial divider by `textAlign`, centred by default.\n * Templatical centres every divider, so a left- or right-aligned partial one\n * comes back with a note.\n */\nfunction convertDivider(\n values: UnlayerContentValues,\n columnWidth: number,\n): { block: Block; notes: string[] } {\n const border = parseBorderObject(values.border);\n const styles = makeStyles(values);\n const notes: string[] = [];\n // `mj-divider` draws 100% across the column less the divider's own side\n // padding, so a px width that reaches that span renders as \"full\" does.\n const room = columnWidth - styles.padding.left - styles.padding.right;\n const width = resolveDividerWidth(values.width, room, notes);\n\n const align = values.textAlign;\n if (width !== \"full\" && (align === \"left\" || align === \"right\")) {\n notes.push(\n `Unlayer 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\nfunction convertSpacer(values: UnlayerContentValues): Block {\n const padding = parsePaddingShorthand(values.containerPadding);\n const height =\n parsePxValue((values as { height?: string | number }).height) ||\n padding.top + padding.bottom ||\n 24;\n\n return createSpacerBlock({\n height,\n styles: makeStyles(values),\n });\n}\n\nfunction convertHtml(values: UnlayerContentValues): Block {\n return createHtmlBlock({\n content: values.html ?? \"\",\n styles: makeStyles(values),\n });\n}\n\nfunction convertSocial(\n values: UnlayerContentValues,\n warnings: string[],\n): Block {\n const iconList = values.icons?.icons ?? [];\n const icons: SocialIcon[] = [];\n\n for (const unlayerIcon of iconList) {\n const id = (unlayerIcon.name ?? \"\").toLowerCase();\n const platform = SOCIAL_PLATFORM_MAP[id];\n\n if (!platform) {\n warnings.push(\n `Unrecognized social icon \"${unlayerIcon.name || id}\" was skipped.`,\n );\n continue;\n }\n\n icons.push({\n id: generateId(),\n platform,\n url: unlayerIcon.url || \"#\",\n });\n }\n\n return createSocialIconsBlock({\n icons,\n align: toAlign(values.textAlign, \"center\"),\n styles: makeStyles(values),\n });\n}\n\nfunction convertVideo(values: UnlayerContentValues): Block {\n return createVideoBlock({\n url: values.videoUrl || \"\",\n thumbnailUrl: values.thumbnailUrl || \"\",\n alt: values.altText || \"\",\n width: 600,\n align: toAlign(values.textAlign, \"center\"),\n styles: makeStyles(values),\n });\n}\n\nfunction convertMenu(values: UnlayerContentValues): Block {\n const menu = values.menu;\n const items: MenuItemData[] = (menu?.items ?? []).map((item) => ({\n id: generateId(),\n text: item.text || \"\",\n url: item.link?.values?.href || \"#\",\n openInNewTab: item.link?.values?.target === \"_blank\",\n bold: false,\n underline: false,\n }));\n\n return createMenuBlock({\n items,\n separator: values.separator || \"|\",\n separatorColor: \"#999999\",\n fontSize: parsePxValue(values.fontSize) || 14,\n color: parseColor(values.color) || undefined,\n fontFamily: parseFontFamily(values.fontFamily) || undefined,\n textAlign: toAlign(values.textAlign, \"center\"),\n styles: makeStyles(values),\n });\n}\n\nfunction convertHtmlFallback(content: UnlayerContent, comment: string): Block {\n const safe = comment.replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\");\n return createHtmlBlock({\n content: `<div style=\"padding:12px;border:1px dashed #d1d5db;border-radius:6px;background:#fafafa;color:#6b7280;font-family:sans-serif;font-size:13px;\">${safe}</div>`,\n styles: makeStyles(content.values),\n });\n}\n\n/**\n * Converts a single Unlayer content node to a Templatical block.\n * Returns the block and a report entry.\n */\nexport function convertContent(\n content: UnlayerContent,\n warnings: string[],\n context: ContentContext = {},\n): { block: Block; entry: ImportReportEntry } {\n const type = content.type;\n const values = content.values ?? ({} as UnlayerContentValues);\n const columnWidth = context.columnWidth ?? DEFAULT_BODY_WIDTH;\n const textColor = context.textColor ?? FALLBACK_TEXT_COLOR;\n\n switch (type) {\n case \"text\":\n return {\n block: convertText(values, textColor),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"paragraph\",\n status: \"converted\",\n },\n };\n case \"heading\":\n return {\n block: convertHeading(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"title\",\n status: \"converted\",\n },\n };\n case \"image\": {\n const { block, notes } = convertImage(values, columnWidth);\n return {\n block,\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"image\",\n status: notes.length > 0 ? \"approximated\" : \"converted\",\n ...(notes.length > 0 ? { note: notes.join(\" \") } : {}),\n },\n };\n }\n case \"button\":\n return {\n block: convertButton(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"button\",\n status: \"converted\",\n },\n };\n case \"divider\": {\n const { block, notes } = convertDivider(values, columnWidth);\n return {\n block,\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"divider\",\n status: notes.length > 0 ? \"approximated\" : \"converted\",\n ...(notes.length > 0 ? { note: notes.join(\" \") } : {}),\n },\n };\n }\n case \"spacer\":\n return {\n block: convertSpacer(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"spacer\",\n status: \"converted\",\n },\n };\n case \"html\":\n return {\n block: convertHtml(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"html\",\n status: \"converted\",\n },\n };\n case \"menu\":\n return {\n block: convertMenu(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"menu\",\n status: \"approximated\",\n note: \"Menu styles map approximately; review spacing and separator color.\",\n },\n };\n case \"social\":\n return {\n block: convertSocial(values, warnings),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"social\",\n status: \"converted\",\n },\n };\n case \"video\":\n return {\n block: convertVideo(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"video\",\n status: \"converted\",\n },\n };\n case \"timer\":\n return {\n block: convertHtmlFallback(\n content,\n \"Unlayer timer block: rebuild manually in Templatical\",\n ),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n note: \"Timer modules have no direct Templatical equivalent; placeholder HTML inserted.\",\n },\n };\n case \"form\":\n return {\n block: convertHtmlFallback(\n content,\n \"Unlayer form block: not supported in Templatical (most email clients block form submission)\",\n ),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: null,\n status: \"skipped\",\n note: \"Unlayer forms have no Templatical equivalent and are skipped. Most email clients block form submission anyway.\",\n },\n };\n default:\n return {\n block: convertHtmlFallback(\n content,\n `Unsupported Unlayer content type: ${type}`,\n ),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n note: `Unknown content type \"${type}\" converted to HTML block.`,\n },\n };\n }\n}\n","import {\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 UnlayerTemplate,\n UnlayerRow,\n UnlayerColumn,\n ImportResult,\n ImportReport,\n ImportReportEntry,\n} from \"./types\";\nimport {\n convertContent,\n DEFAULT_BODY_WIDTH,\n FALLBACK_TEXT_COLOR,\n} from \"./block-mapper\";\nimport type { ContentContext } from \"./block-mapper\";\nimport {\n parsePxValue,\n parseColor,\n parseFontFamily,\n parsePaddingShorthand,\n readUnlayerBackgroundImage,\n} from \"./style-parser\";\n\nfunction resolveColumnLayout(\n cells: number[],\n warnings: string[],\n): ColumnLayout {\n if (cells.length <= 1) return \"1\";\n if (cells.length === 3) return \"3\";\n\n if (cells.length === 2) {\n const left = cells[0] ?? 1;\n const right = cells[1] ?? 1;\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 warnings.push(\n `Row with ${cells.length} columns was flattened to a single column. Unlayer supports arbitrary columns, but Templatical supports up to 3.`,\n );\n return \"1\";\n}\n\n/** Each column's share of the body width, by layout. */\nconst COLUMN_SHARES: Record<ColumnLayout, number[]> = {\n \"1\": [1],\n \"2\": [1 / 2, 1 / 2],\n \"3\": [1 / 3, 1 / 3, 1 / 3],\n \"1-2\": [1 / 3, 2 / 3],\n \"2-1\": [2 / 3, 1 / 3],\n};\n\n/**\n * A column's px width as the renderer draws it: the body width times the\n * column's share, rounded down. A column past the layout's last slot takes\n * the whole body width, the renderer's fallback.\n */\nfunction columnWidthAt(\n layout: ColumnLayout,\n bodyWidth: number,\n index: number,\n): number {\n return Math.floor(bodyWidth * (COLUMN_SHARES[layout][index] ?? 1));\n}\n\n/**\n * Converts all contents in a column to Templatical blocks.\n */\nfunction convertColumnContents(\n column: UnlayerColumn,\n context: ContentContext,\n entries: ImportReportEntry[],\n warnings: string[],\n): Block[] {\n const blocks: Block[] = [];\n\n for (const content of column.contents ?? []) {\n const { block, entry } = convertContent(content, warnings, context);\n blocks.push(block);\n entries.push(entry);\n }\n\n return blocks;\n}\n\n/**\n * The section's fill. `columnsBackgroundColor` covers the content width, the\n * area a section paints; `backgroundColor` is the band outside it, used only\n * when the row sets no content colour.\n */\nfunction resolveRowBackground(\n row: UnlayerRow,\n warnings: string[],\n): string | undefined {\n const content = parseColor(row.values?.columnsBackgroundColor);\n const band = parseColor(row.values?.backgroundColor);\n if (content && band && content !== band) {\n warnings.push(\n `Row background ${band} outside the content width was dropped; the section keeps the content background ${content}.`,\n );\n }\n return content || band || undefined;\n}\n\n/**\n * The image a row's section paints. `fullWidth: false` is the content area\n * a section paints, so it maps exactly. A full-width band (Unlayer's\n * default) shows through the content area only while\n * `columnsBackgroundColor` leaves it transparent: then the section paints it\n * at the content width, and otherwise it's dropped. Losses go to `warnings`,\n * since rows have no report entry.\n */\nfunction resolveRowBackgroundImage(\n row: UnlayerRow,\n width: number,\n warnings: string[],\n): BackgroundImage | undefined {\n const bg = row.values?.backgroundImage;\n const read = readUnlayerBackgroundImage(bg, \"Row\", width);\n if (!read) return undefined;\n\n const band = bg?.fullWidth !== false;\n const content = parseColor(row.values?.columnsBackgroundColor);\n if (band && content) {\n warnings.push(\n `Row background image \"${read.image.url}\" outside the content width was dropped; the section keeps the content background ${content}.`,\n );\n return undefined;\n }\n\n warnings.push(...read.notes);\n if (band) {\n warnings.push(\n `Row background image \"${read.image.url}\" spans the full width in Unlayer; the section paints it at the content width.`,\n );\n }\n return read.image;\n}\n\n/**\n * Processes a single Unlayer row into one or more Templatical blocks.\n *\n * Each column's px width derives from `settings.width` and the resolved\n * layout; every block also learns the template's `textColor`.\n */\nfunction processRow(\n row: UnlayerRow,\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 const cells = row.cells ?? columns.map(() => 1);\n const layout = resolveColumnLayout(cells, warnings);\n const contextAt = (index: number): ContentContext => ({\n columnWidth: columnWidthAt(layout, settings.width, index),\n textColor: settings.textColor,\n });\n\n let children: Block[][];\n if (layout === \"1\") {\n const merged: Block[] = [];\n for (const column of columns) {\n merged.push(\n ...convertColumnContents(column, contextAt(0), entries, warnings),\n );\n }\n children = [merged];\n } else {\n children = columns.map((col, index) =>\n convertColumnContents(col, contextAt(index), entries, warnings),\n );\n }\n\n const background = resolveRowBackground(row, warnings);\n const padding = parsePaddingShorthand(row.values?.padding);\n const backgroundImage = resolveRowBackgroundImage(\n row,\n settings.width,\n warnings,\n );\n\n const section = createSectionBlock({\n columns: layout,\n children,\n styles: {\n padding,\n ...(background ? { backgroundColor: background } : {}),\n },\n ...(backgroundImage ? { backgroundImage } : {}),\n });\n\n return [section];\n}\n\n/**\n * Extracts template-level settings from the Unlayer body values.\n */\nfunction extractSettings(\n template: UnlayerTemplate,\n): TemplateContent[\"settings\"] {\n const values = template.body?.values ?? {};\n\n const width = parsePxValue(values.contentWidth);\n const bgColor = parseColor(values.backgroundColor) || \"#ffffff\";\n const fontFamily = parseFontFamily(values.fontFamily) || \"Arial\";\n const textColor = parseColor(values.textColor) || FALLBACK_TEXT_COLOR;\n const linkColor = parseColor(values.linkStyle?.linkColor);\n // An unstated underline is on: Unlayer's default, the browser's and the SDK's.\n const underline = values.linkStyle?.linkUnderline;\n const linkUnderline = typeof underline === \"boolean\" ? underline : true;\n const preheaderText =\n typeof values.preheaderText === \"string\" ? values.preheaderText.trim() : \"\";\n\n return {\n width: width > 0 ? width : DEFAULT_BODY_WIDTH,\n backgroundColor: bgColor,\n textColor,\n linkUnderline,\n fontFamily,\n locale: \"en\",\n ...(linkColor ? { linkColor } : {}),\n ...(preheaderText ? { preheaderText } : {}),\n };\n}\n\n/**\n * Converts an Unlayer design JSON to Templatical TemplateContent.\n *\n * @param template - The parsed Unlayer JSON object (the result of `editor.saveDesign(...)`)\n * @returns An ImportResult with the converted content and a detailed report\n *\n * @example\n * ```ts\n * import { convertUnlayerTemplate } from '@templatical/import-unlayer';\n *\n * const unlayerJson = JSON.parse(fileContent);\n * const { content, report } = convertUnlayerTemplate(unlayerJson);\n *\n * const editor = init({ container: '#editor', content });\n *\n * console.log(report.summary);\n * console.log(report.warnings);\n * ```\n */\nexport function convertUnlayerTemplate(\n template: UnlayerTemplate,\n): ImportResult {\n if (!template?.body?.rows) {\n throw new Error(\n \"Invalid Unlayer template: missing body.rows. Ensure you are passing a valid Unlayer JSON design (the output of editor.saveDesign).\",\n );\n }\n\n const entries: ImportReportEntry[] = [];\n const warnings: string[] = [];\n const blocks: Block[] = [];\n\n const headers = template.body.headers ?? [];\n const footers = template.body.footers ?? [];\n const settings = extractSettings(template);\n\n const pageImage = template.body.values?.backgroundImage?.url;\n if (typeof pageImage === \"string\" && pageImage.trim()) {\n warnings.push(\n `The email background image \"${pageImage.trim()}\" was dropped; Templatical has no email-level background image.`,\n );\n }\n\n if (headers.length > 0) {\n warnings.push(\n `${headers.length} Unlayer header row(s) were imported as regular rows at the top of the template.`,\n );\n for (const row of headers) {\n blocks.push(...processRow(row, settings, entries, warnings));\n }\n }\n\n for (const row of template.body.rows) {\n blocks.push(...processRow(row, settings, entries, warnings));\n }\n\n if (footers.length > 0) {\n warnings.push(\n `${footers.length} Unlayer footer row(s) were imported as regular rows at the bottom of the template.`,\n );\n for (const row of footers) {\n blocks.push(...processRow(row, settings, entries, warnings));\n }\n }\n\n const content: TemplateContent = {\n ...createDefaultTemplateContent(),\n blocks,\n settings,\n };\n\n const summary = {\n total: entries.length,\n converted: entries.filter((e) => e.status === \"converted\").length,\n approximated: entries.filter((e) => e.status === \"approximated\").length,\n htmlFallback: entries.filter((e) => e.status === \"html-fallback\").length,\n skipped: entries.filter((e) => e.status === \"skipped\").length,\n };\n\n const report: ImportReport = { entries, warnings, summary };\n\n return { content, report };\n}\n"],"mappings":";;;;;AAkBA,SAAgB,aAAa,OAA4C;CACvE,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM,OAAO;CAClD,IAAI,OAAO,UAAU,UAAU,OAAO,KAAK,MAAM,KAAK;CAMtD,MAAM,QAAQ,MAAM,MAAM,kCAAkC;CAC5D,OAAO,QAAQ,KAAK,MAAM,WAAW,MAAM,EAAE,CAAC,IAAI;AACpD;;;;;;;;;;;;;;;AAgBA,SAAgB,uBACd,OACA,OACA,QACoB;CACpB,IAAI,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,IAAI,OAAO,KAAA;CAClE,IAAI,OAAO,UAAU,UACnB,OAAO,QAAQ,IAAI,KAAK,MAAM,KAAK,IAAI,KAAA;CAKzC,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;AAEA,SAAgB,WAAW,OAAmC;CAC5D,IAAI,CAAC,SAAS,UAAU,eAAe,OAAO;CAE9C,MAAM,UAAU,MAAM,KAAK;CAE3B,IAAI,oBAAoB,KAAK,OAAO,GAAG,OAAO,QAAQ,YAAY;CAElE,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;CAEA,OAAO;AACT;AAEA,SAAgB,sBAAsB,OAAyC;CAC7E,IAAI,CAAC,OAAO,OAAO;EAAE,KAAK;EAAG,OAAO;EAAG,QAAQ;EAAG,MAAM;CAAE;CAG1D,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;AAEA,SAAgB,kBAAkB,QAIhC;CACA,IAAI,CAAC,QAAQ,OAAO;EAAE,OAAO;EAAG,OAAO;EAAS,OAAO;CAAU;CACjE,OAAO;EACL,OAAO,aAAa,OAAO,cAAc;EACzC,OAAO,OAAO,kBAAkB;EAChC,OAAO,WAAW,OAAO,cAAc,KAAK;CAC9C;AACF;;;;;AAMA,SAAgB,kBACd,OACiD;CACjD,IAAI,OAAO,UAAU,UACnB,OAAO,OAAO,SAAS,KAAK,IAAI;EAAE,MAAM;EAAM;CAAM,IAAI,KAAA;CAI1D,MAAM,QAAQ,MAAM,KAAK,CAAC,CAAC,MAAM,+BAA+B;CAChE,IAAI,CAAC,OAAO,OAAO,KAAA;CACnB,OAAO;EAAE,MAAM,MAAM,OAAO,MAAM,MAAM;EAAM,OAAO,WAAW,MAAM,EAAE;CAAE;AAC5E;;;;;;AAOA,SAAgB,gBACd,OACQ;CACR,IAAI,CAAC,OAAO,OAAO;CACnB,IAAI,OAAO,UAAU,UAAU,OAAO,mBAAmB,KAAK;CAC9D,OAAO,mBAAmB,MAAM,KAAK,KAAK,mBAAmB,MAAM,KAAK;AAC1E;AAQA,MAAM,SAAS;CAAC;CAAQ;CAAU;AAAO;AACzC,MAAM,SAAS;CAAC;CAAO;CAAU;AAAQ;AACzC,MAAM,UAAU;AAChB,MAAM,SAAS;AAEf,MAAM,oBAEF;CACF,YAAY;EAAE,GAAG;EAAQ,GAAG;CAAM;CAClC,cAAc;EAAE,GAAG;EAAU,GAAG;CAAM;CACtC,aAAa;EAAE,GAAG;EAAS,GAAG;CAAM;CACpC,eAAe;EAAE,GAAG;EAAQ,GAAG;CAAS;CACxC,QAAQ;EAAE,GAAG;EAAU,GAAG;CAAS;CACnC,gBAAgB;EAAE,GAAG;EAAS,GAAG;CAAS;CAC1C,eAAe;EAAE,GAAG;EAAQ,GAAG;CAAS;CACxC,iBAAiB;EAAE,GAAG;EAAU,GAAG;CAAS;CAC5C,gBAAgB;EAAE,GAAG;EAAS,GAAG;CAAS;AAC5C;;;;;;AAOA,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;;;;;;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;;;;;;;;;;AAWA,SAAgB,2BACd,IACA,OACA,UAC4B;CAC5B,MAAM,MAAM,OAAO,IAAI,QAAQ,WAAW,GAAG,IAAI,KAAK,IAAI;CAC1D,IAAI,CAAC,MAAM,CAAC,KAAK,OAAO;CAExB,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;CAC3B,MAAM,UAAU,OAAO,GAAG,SAAS,WAAW,GAAG,KAAK,KAAK,IAAI;CAC/D,IAAI,YAAY,WAAW,YAAY,WACrC,OAAO;MACF,IAAI,WAAW,YAAY,UAAU;EAC1C,MAAM,SAAS,sBAAsB,SAAS,QAAQ;EACtD,OAAO,OAAO;EACd,IAAI,CAAC,OAAO,OAAO,MAAM,wBAAwB,SAAS,IAAI;CAChE,OAAO,IAAI,CAAC,WAAW,OAAO,GAAG,UAAU,WACzC,OAAO,GAAG,QAAQ,UAAU;CAG9B,IAAI,IAAyB;CAC7B,IAAI,IAAyB;CAC7B,MAAM,cAAc,OAAO,GAAG,aAAa,WAAW,GAAG,SAAS,KAAK,IAAI;CAC3E,IAAI,gBAAgB,UAAU;EAC5B,MAAM,CAAC,OAAO,IAAI,OAAO,MAAM,MAAM,QAAQ,GAAG,cAAc,IAC1D,GAAG,eAAe,KAAK,SAAS,OAAO,IAAI,CAAC,CAAC,KAAK,CAAC,IACnD,CAAC;EACL,MAAM,QAAQ,oBAAoB,KAAK,YAAY,GAAG,MAAM;EAC5D,MAAM,QAAQ,oBAAoB,KAAK,YAAY,GAAG,MAAM;EAC5D,IAAI,OAAO,SAAS;EACpB,IAAI,OAAO,SAAS;EACpB,IAAI,CAAC,OAAO,SAAS,CAAC,OAAO,OAC3B,MACE,kCACA,GAAG,KAAK,GAAG,OAAO,KAAK,GACvB,GAAG,EAAE,GAAG,GACV;CAEJ,OAAO,IAAI,aAAa;EACtB,MAAM,UAAU,OAAO,OAAO,mBAAmB,WAAW,IACxD,kBAAkB,eAClB,KAAA;EACJ,IAAI,SAAS;GACX,IAAI,QAAQ;GACZ,IAAI,QAAQ;EACd,OACE,MAAM,4BAA4B,aAAa,GAAG,EAAE,GAAG,GAAG;CAE9D,OAAO,IAAI,OAAO,GAAG,WAAW,WAAW;EACzC,IAAI,GAAG,SAAS,WAAW;EAC3B,IAAI;CACN;CAEA,IAAI,SAAS;CACb,IAAI,OAAO,GAAG,WAAW,WACvB,SAAS,GAAG;MACP,IAAI,OAAO,GAAG,WAAW,YAAY,GAAG,OAAO,KAAK,GAAG;EAC5D,MAAM,YAAY,GAAG,OAAO,KAAK;EACjC,SAAS,cAAc;EACvB,IAAI,UAAU,cAAc,UAC1B,MAAM,0BAA0B,WAAW,QAAQ;CAEvD;CAEA,OAAO;EAAE,OAAO;GAAE;GAAK;GAAM,UAAU;IAAE;IAAG;GAAE;GAAG;EAAO;EAAG;CAAM;AACnE;;;AC3RA,MAAM,sBAAsD;CAC1D,UAAU;CACV,SAAS;CACT,GAAG;CACH,WAAW;CACX,UAAU;CACV,SAAS;CACT,QAAQ;CACR,WAAW;CACX,OAAO;CACP,MAAM;CACN,UAAU;CACV,UAAU;CACV,SAAS;CACT,UAAU;CACV,QAAQ;CACR,QAAQ;CACR,UAAU;CACV,SAAS;AACX;AA2BA,SAAS,QAAQ,OAA2B,WAAkB,QAAe;CAC3E,IAAI,UAAU,UAAU,UAAU,YAAY,UAAU,SAAS,OAAO;CACxE,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,YACP,OACA,WAAsB,SACX;CACX,IAAI,UAAU,WAAW,UAAU,YAAY,UAAU,UACvD,OAAO;CACT,OAAO;AACT;AAEA,SAAS,WAAW,QAA+C;CAEjE,OAAO,EACL,SAFc,sBAAsB,OAAO,gBAErC,EACR;AACF;;;;;;AAOA,SAAS,mBACP,MACA,QACA,WACQ;CACR,MAAM,YAAsB,CAAC;CAC7B,MAAM,WAAW,aAAa,OAAO,QAAQ;CAC7C,IAAI,YAAY,aAAa,IAAI,UAAU,KAAK,cAAc,SAAS,GAAG;CAC1E,MAAM,QAAQ,WAAW,OAAO,KAAK;CACrC,IAAI,SAAS,UAAU,WAAW,UAAU,KAAK,UAAU,OAAO;CAClE,MAAM,aAAa,OAAO;CAC1B,IACE,eAAe,KAAA,KACf,eAAe,QACf,OAAO,UAAU,MAAM,YACvB,OAAO,UAAU,MAAM,OAEvB,UAAU,KAAK,gBAAgB,YAAY;CAE7C,MAAM,aAAa,gBAAgB,OAAO,UAAU;CACpD,IAAI,YAAY,UAAU,KAAK,gBAAgB,YAAY;CAE3D,MAAM,YAAY,OAAO;CACzB,MAAM,SACJ,aAAa,cAAc,SAAS,eAAe,cAAc;CAEnE,IAAI,CAAC,UAAU,UAAU,WAAW,GAAG,OAAO;CAE9C,MAAM,YAAY,UAAU,KAAK,IAAI;CACrC,IAAI,SAAS;CAEb,IAAI,QACF,SAAS,OACN,QAAQ,wBAAwB,iBAAiB,OAAO,GAAG,CAAC,CAC5D,WAAW,OAAO,aAAa,OAAO,GAAG;CAG9C,IAAI,WACF,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,uBAAuB,MAAsB;CACpD,IAAI,CAAC,MAAM,OAAO;CAClB,IAAI,WAAW,KAAK,IAAI,GAAG,OAAO;CAClC,OAAO,MAAM,KAAK;AACpB;AAEA,SAAS,YAAY,QAA8B,WAA0B;CAC3E,MAAM,OAAO,uBAAuB,OAAO,QAAQ,EAAE;CAErD,OAAO,qBAAqB;EAC1B,SAAS,mBAAmB,MAAM,QAAQ,SAAS;EACnD,QAAQ,WAAW,MAAM;CAC3B,CAAC;AACH;AAEA,SAAS,kBAAkB,KAAuC;CAChE,IAAI,CAAC,KAAK,OAAO;CACjB,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,eAAe,QAAqC;CAE3D,MAAM,YADO,OAAO,QAAQ,GAAA,CACN,QAAQ,0BAA0B,EAAE;CAC1D,MAAM,UAAU,WAAW,MAAM,SAAS,QAAQ;CAElD,OAAO,iBAAiB;EACtB;EACA,OAAO,kBAAkB,OAAO,WAAW;EAC3C,OAAO,WAAW,OAAO,KAAK,KAAK,KAAA;EACnC,WAAW,QAAQ,OAAO,SAAS;EACnC,YAAY,gBAAgB,OAAO,UAAU,KAAK,KAAA;EAClD,QAAQ,WAAW,MAAM;CAC3B,CAAC;AACH;;;;;;;;;;;AAYA,SAAS,kBACP,KACA,MACA,OACqB;CACrB,IAAI,KAAK,cAAc,OAAO;EAC5B,MAAM,OAAO,IAAI,YAAY,GAAA,CAAI,KAAK;EACtC,MAAM,SAAS,MAAM,kBAAkB,GAAG,IAAI,KAAA;EAC9C,IAAI,CAAC,UAAU,OAAO,SAAS,GAAG;GAChC,IAAI,KACF,MAAM,KACJ,mBAAmB,IAAI,6CACzB;GAEF,OAAO;EACT;EACA,MAAM,KAAK,KAAK,MACd,OAAO,SAAS,MAAO,OAAO,OAAO,QAAS,MAAM,OAAO,KAC7D;EACA,IAAI,MAAM,MAAM,OAAO;EACvB,IAAI,OAAO,SAAS,KAClB,MAAM,KACJ,+BAA+B,IAAI,8BAA8B,GAAG,qCACtE;EAEF,OAAO;CACT;CAEA,MAAM,UAAU,KAAK,QAAQ,KAAK,MAAM,IAAI,KAAK,IAAI;CACrD,OAAO,WAAW,KAAK,WAAW,OAAO,SAAS;AACpD;AAEA,SAAS,aACP,QACA,aACmC;CACnC,MAAM,MAAM,OAAO;CACnB,MAAM,SAAS,OAAO,QAAQ;CAC9B,MAAM,SAAS,WAAW,MAAM;CAChC,MAAM,QAAkB,CAAC;CACzB,MAAM,OAAO,cAAc,OAAO,QAAQ,OAAO,OAAO,QAAQ;CAChE,MAAM,QAAQ,kBAAkB,KAAK,MAAM,KAAK;CAMhD,MAAM,aAAa,UAAU,SAAS,OAAO;CAC7C,MAAM,cACJ,KAAK,SAAS,IAAI,SACd,KAAK,MAAO,IAAI,SAAS,aAAc,IAAI,KAAK,IAChD,KAAA;CAEN,MAAM,YAAY,KAAK,QAAQ,KAAK,MAAM,IAAI,KAAK,IAAI;CACvD,MAAM,aAAa,KAAK,SAAS,KAAK,MAAM,IAAI,MAAM,IAAI;CAE1D,OAAO;EACL,OAAO,iBAAiB;GACtB,KAAK,KAAK,OAAO;GACjB,KAAK,OAAO,WAAW;GACvB;GAEA,GAAI,YAAY,KAAK,aAAa,IAC9B;IAAE,cAAc;IAAW,eAAe;GAAW,IACrD,CAAC;GACL,cAAc,uBACZ,OAAO,cACP,KAAK,QAAQ,aAAa,KAAA,GAC1B,WACF;GACA,OAAO,QAAQ,OAAO,WAAW,QAAQ;GACzC,SAAS,QAAQ,QAAQ,KAAA;GACzB,kBAAkB,QAAQ,WAAW,YAAY,KAAA;GACjD;EACF,CAAC;EACD;CACF;AACF;AAEA,SAAS,cAAc,QAAqC;CAC1D,MAAM,SAAS,OAAO,gBAAgB,CAAC;CACvC,MAAM,UAAU,OAAO,UACnB,sBAAsB,OAAO,OAAO,IACpC;EAAE,KAAK;EAAI,OAAO;EAAI,QAAQ;EAAI,MAAM;CAAG;CAC/C,MAAM,QAAQ,eAAe,OAAO,QAAQ,QAAQ;CACpD,MAAM,aAAa,OAAO,MAAM;CAEhC,OAAO,kBAAkB;EACvB,MAAM;EACN,KAAK,YAAY,QAAQ;EACzB,cAAc,YAAY,WAAW,YAAY,KAAA;EACjD,iBAAiB,WAAW,OAAO,eAAe,KAAK;EACvD,WAAW,WAAW,OAAO,KAAK,KAAK;EACvC,cAAc,aAAa,OAAO,YAAY;EAC9C,UAAU,aAAa,OAAO,QAAQ,KAAK;EAC3C,YAAY,gBAAgB,OAAO,UAAU,KAAK,KAAA;EAClD,OAAO,QAAQ,OAAO,WAAW,QAAQ;EACzC,eAAe;EACf,QAAQ,WAAW,MAAM;CAC3B,CAAC;AACH;;;;;;AAOA,SAAS,oBACP,OACA,MACA,OACuB;CACvB,MAAM,MAAM,OAAO,SAAS,EAAE,CAAC,CAAC,KAAK;CACrC,IAAI,CAAC,KAAK,OAAO;CAEjB,MAAM,SAAS,kBAAkB,OAAO,UAAU,WAAW,QAAQ,GAAG;CACxE,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,MAAM,OAAO,SAAS;AAC/B;;;;;;AAOA,SAAS,eACP,QACA,aACmC;CACnC,MAAM,SAAS,kBAAkB,OAAO,MAAM;CAC9C,MAAM,SAAS,WAAW,MAAM;CAChC,MAAM,QAAkB,CAAC;CAGzB,MAAM,OAAO,cAAc,OAAO,QAAQ,OAAO,OAAO,QAAQ;CAChE,MAAM,QAAQ,oBAAoB,OAAO,OAAO,MAAM,KAAK;CAE3D,MAAM,QAAQ,OAAO;CACrB,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;AAEA,SAAS,cAAc,QAAqC;CAC1D,MAAM,UAAU,sBAAsB,OAAO,gBAAgB;CAC7D,MAAM,SACJ,aAAc,OAAwC,MAAM,KAC5D,QAAQ,MAAM,QAAQ,UACtB;CAEF,OAAO,kBAAkB;EACvB;EACA,QAAQ,WAAW,MAAM;CAC3B,CAAC;AACH;AAEA,SAAS,YAAY,QAAqC;CACxD,OAAO,gBAAgB;EACrB,SAAS,OAAO,QAAQ;EACxB,QAAQ,WAAW,MAAM;CAC3B,CAAC;AACH;AAEA,SAAS,cACP,QACA,UACO;CACP,MAAM,WAAW,OAAO,OAAO,SAAS,CAAC;CACzC,MAAM,QAAsB,CAAC;CAE7B,KAAK,MAAM,eAAe,UAAU;EAClC,MAAM,MAAM,YAAY,QAAQ,GAAA,CAAI,YAAY;EAChD,MAAM,WAAW,oBAAoB;EAErC,IAAI,CAAC,UAAU;GACb,SAAS,KACP,6BAA6B,YAAY,QAAQ,GAAG,eACtD;GACA;EACF;EAEA,MAAM,KAAK;GACT,IAAI,WAAW;GACf;GACA,KAAK,YAAY,OAAO;EAC1B,CAAC;CACH;CAEA,OAAO,uBAAuB;EAC5B;EACA,OAAO,QAAQ,OAAO,WAAW,QAAQ;EACzC,QAAQ,WAAW,MAAM;CAC3B,CAAC;AACH;AAEA,SAAS,aAAa,QAAqC;CACzD,OAAO,iBAAiB;EACtB,KAAK,OAAO,YAAY;EACxB,cAAc,OAAO,gBAAgB;EACrC,KAAK,OAAO,WAAW;EACvB,OAAO;EACP,OAAO,QAAQ,OAAO,WAAW,QAAQ;EACzC,QAAQ,WAAW,MAAM;CAC3B,CAAC;AACH;AAEA,SAAS,YAAY,QAAqC;CAExD,MAAM,SADO,OAAO,MACiB,SAAS,CAAC,EAAA,CAAG,KAAK,UAAU;EAC/D,IAAI,WAAW;EACf,MAAM,KAAK,QAAQ;EACnB,KAAK,KAAK,MAAM,QAAQ,QAAQ;EAChC,cAAc,KAAK,MAAM,QAAQ,WAAW;EAC5C,MAAM;EACN,WAAW;CACb,EAAE;CAEF,OAAO,gBAAgB;EACrB;EACA,WAAW,OAAO,aAAa;EAC/B,gBAAgB;EAChB,UAAU,aAAa,OAAO,QAAQ,KAAK;EAC3C,OAAO,WAAW,OAAO,KAAK,KAAK,KAAA;EACnC,YAAY,gBAAgB,OAAO,UAAU,KAAK,KAAA;EAClD,WAAW,QAAQ,OAAO,WAAW,QAAQ;EAC7C,QAAQ,WAAW,MAAM;CAC3B,CAAC;AACH;AAEA,SAAS,oBAAoB,SAAyB,SAAwB;CAC5E,MAAM,OAAO,QAAQ,QAAQ,MAAM,MAAM,CAAC,CAAC,QAAQ,MAAM,MAAM;CAC/D,OAAO,gBAAgB;EACrB,SAAS,iJAAiJ,KAAK;EAC/J,QAAQ,WAAW,QAAQ,MAAM;CACnC,CAAC;AACH;;;;;AAMA,SAAgB,eACd,SACA,UACA,UAA0B,CAAC,GACiB;CAC5C,MAAM,OAAO,QAAQ;CACrB,MAAM,SAAS,QAAQ,UAAW,CAAC;CACnC,MAAM,cAAc,QAAQ,eAAA;CAC5B,MAAM,YAAY,QAAQ,aAAA;CAE1B,QAAQ,MAAR;EACE,KAAK,QACH,OAAO;GACL,OAAO,YAAY,QAAQ,SAAS;GACpC,OAAO;IACL,oBAAoB;IACpB,sBAAsB;IACtB,QAAQ;GACV;EACF;EACF,KAAK,WACH,OAAO;GACL,OAAO,eAAe,MAAM;GAC5B,OAAO;IACL,oBAAoB;IACpB,sBAAsB;IACtB,QAAQ;GACV;EACF;EACF,KAAK,SAAS;GACZ,MAAM,EAAE,OAAO,UAAU,aAAa,QAAQ,WAAW;GACzD,OAAO;IACL;IACA,OAAO;KACL,oBAAoB;KACpB,sBAAsB;KACtB,QAAQ,MAAM,SAAS,IAAI,iBAAiB;KAC5C,GAAI,MAAM,SAAS,IAAI,EAAE,MAAM,MAAM,KAAK,GAAG,EAAE,IAAI,CAAC;IACtD;GACF;EACF;EACA,KAAK,UACH,OAAO;GACL,OAAO,cAAc,MAAM;GAC3B,OAAO;IACL,oBAAoB;IACpB,sBAAsB;IACtB,QAAQ;GACV;EACF;EACF,KAAK,WAAW;GACd,MAAM,EAAE,OAAO,UAAU,eAAe,QAAQ,WAAW;GAC3D,OAAO;IACL;IACA,OAAO;KACL,oBAAoB;KACpB,sBAAsB;KACtB,QAAQ,MAAM,SAAS,IAAI,iBAAiB;KAC5C,GAAI,MAAM,SAAS,IAAI,EAAE,MAAM,MAAM,KAAK,GAAG,EAAE,IAAI,CAAC;IACtD;GACF;EACF;EACA,KAAK,UACH,OAAO;GACL,OAAO,cAAc,MAAM;GAC3B,OAAO;IACL,oBAAoB;IACpB,sBAAsB;IACtB,QAAQ;GACV;EACF;EACF,KAAK,QACH,OAAO;GACL,OAAO,YAAY,MAAM;GACzB,OAAO;IACL,oBAAoB;IACpB,sBAAsB;IACtB,QAAQ;GACV;EACF;EACF,KAAK,QACH,OAAO;GACL,OAAO,YAAY,MAAM;GACzB,OAAO;IACL,oBAAoB;IACpB,sBAAsB;IACtB,QAAQ;IACR,MAAM;GACR;EACF;EACF,KAAK,UACH,OAAO;GACL,OAAO,cAAc,QAAQ,QAAQ;GACrC,OAAO;IACL,oBAAoB;IACpB,sBAAsB;IACtB,QAAQ;GACV;EACF;EACF,KAAK,SACH,OAAO;GACL,OAAO,aAAa,MAAM;GAC1B,OAAO;IACL,oBAAoB;IACpB,sBAAsB;IACtB,QAAQ;GACV;EACF;EACF,KAAK,SACH,OAAO;GACL,OAAO,oBACL,SACA,sDACF;GACA,OAAO;IACL,oBAAoB;IACpB,sBAAsB;IACtB,QAAQ;IACR,MAAM;GACR;EACF;EACF,KAAK,QACH,OAAO;GACL,OAAO,oBACL,SACA,6FACF;GACA,OAAO;IACL,oBAAoB;IACpB,sBAAsB;IACtB,QAAQ;IACR,MAAM;GACR;EACF;EACF,SACE,OAAO;GACL,OAAO,oBACL,SACA,qCAAqC,MACvC;GACA,OAAO;IACL,oBAAoB;IACpB,sBAAsB;IACtB,QAAQ;IACR,MAAM,yBAAyB,KAAK;GACtC;EACF;CACJ;AACF;;;ACnqBA,SAAS,oBACP,OACA,UACc;CACd,IAAI,MAAM,UAAU,GAAG,OAAO;CAC9B,IAAI,MAAM,WAAW,GAAG,OAAO;CAE/B,IAAI,MAAM,WAAW,GAAG;EACtB,MAAM,OAAO,MAAM,MAAM;EAGzB,MAAM,QAAQ,QADA,QADA,MAAM,MAAM;EAI1B,IAAI,QAAQ,KAAM,OAAO;EACzB,IAAI,QAAQ,KAAM,OAAO;EACzB,OAAO;CACT;CAEA,SAAS,KACP,YAAY,MAAM,OAAO,iHAC3B;CACA,OAAO;AACT;;AAGA,MAAM,gBAAgD;CACpD,KAAK,CAAC,CAAC;CACP,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;CAClB,KAAK;EAAC,IAAI;EAAG,IAAI;EAAG,IAAI;CAAC;CACzB,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;CACpB,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;AACtB;;;;;;AAOA,SAAS,cACP,QACA,WACA,OACQ;CACR,OAAO,KAAK,MAAM,aAAa,cAAc,OAAO,CAAC,UAAU,EAAE;AACnE;;;;AAKA,SAAS,sBACP,QACA,SACA,SACA,UACS;CACT,MAAM,SAAkB,CAAC;CAEzB,KAAK,MAAM,WAAW,OAAO,YAAY,CAAC,GAAG;EAC3C,MAAM,EAAE,OAAO,UAAU,eAAe,SAAS,UAAU,OAAO;EAClE,OAAO,KAAK,KAAK;EACjB,QAAQ,KAAK,KAAK;CACpB;CAEA,OAAO;AACT;;;;;;AAOA,SAAS,qBACP,KACA,UACoB;CACpB,MAAM,UAAU,WAAW,IAAI,QAAQ,sBAAsB;CAC7D,MAAM,OAAO,WAAW,IAAI,QAAQ,eAAe;CACnD,IAAI,WAAW,QAAQ,YAAY,MACjC,SAAS,KACP,kBAAkB,KAAK,mFAAmF,QAAQ,EACpH;CAEF,OAAO,WAAW,QAAQ,KAAA;AAC5B;;;;;;;;;AAUA,SAAS,0BACP,KACA,OACA,UAC6B;CAC7B,MAAM,KAAK,IAAI,QAAQ;CACvB,MAAM,OAAO,2BAA2B,IAAI,OAAO,KAAK;CACxD,IAAI,CAAC,MAAM,OAAO,KAAA;CAElB,MAAM,OAAO,IAAI,cAAc;CAC/B,MAAM,UAAU,WAAW,IAAI,QAAQ,sBAAsB;CAC7D,IAAI,QAAQ,SAAS;EACnB,SAAS,KACP,yBAAyB,KAAK,MAAM,IAAI,oFAAoF,QAAQ,EACtI;EACA;CACF;CAEA,SAAS,KAAK,GAAG,KAAK,KAAK;CAC3B,IAAI,MACF,SAAS,KACP,yBAAyB,KAAK,MAAM,IAAI,+EAC1C;CAEF,OAAO,KAAK;AACd;;;;;;;AAQA,SAAS,WACP,KACA,UACA,SACA,UACS;CACT,MAAM,UAAU,IAAI;CACpB,IAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,OAAO,CAAC;CAG9C,MAAM,SAAS,oBADD,IAAI,SAAS,QAAQ,UAAU,CAAC,GACJ,QAAQ;CAClD,MAAM,aAAa,WAAmC;EACpD,aAAa,cAAc,QAAQ,SAAS,OAAO,KAAK;EACxD,WAAW,SAAS;CACtB;CAEA,IAAI;CACJ,IAAI,WAAW,KAAK;EAClB,MAAM,SAAkB,CAAC;EACzB,KAAK,MAAM,UAAU,SACnB,OAAO,KACL,GAAG,sBAAsB,QAAQ,UAAU,CAAC,GAAG,SAAS,QAAQ,CAClE;EAEF,WAAW,CAAC,MAAM;CACpB,OACE,WAAW,QAAQ,KAAK,KAAK,UAC3B,sBAAsB,KAAK,UAAU,KAAK,GAAG,SAAS,QAAQ,CAChE;CAGF,MAAM,aAAa,qBAAqB,KAAK,QAAQ;CACrD,MAAM,UAAU,sBAAsB,IAAI,QAAQ,OAAO;CACzD,MAAM,kBAAkB,0BACtB,KACA,SAAS,OACT,QACF;CAYA,OAAO,CAVS,mBAAmB;EACjC,SAAS;EACT;EACA,QAAQ;GACN;GACA,GAAI,aAAa,EAAE,iBAAiB,WAAW,IAAI,CAAC;EACtD;EACA,GAAI,kBAAkB,EAAE,gBAAgB,IAAI,CAAC;CAC/C,CAEc,CAAC;AACjB;;;;AAKA,SAAS,gBACP,UAC6B;CAC7B,MAAM,SAAS,SAAS,MAAM,UAAU,CAAC;CAEzC,MAAM,QAAQ,aAAa,OAAO,YAAY;CAC9C,MAAM,UAAU,WAAW,OAAO,eAAe,KAAK;CACtD,MAAM,aAAa,gBAAgB,OAAO,UAAU,KAAK;CACzD,MAAM,YAAY,WAAW,OAAO,SAAS,KAAA;CAC7C,MAAM,YAAY,WAAW,OAAO,WAAW,SAAS;CAExD,MAAM,YAAY,OAAO,WAAW;CACpC,MAAM,gBAAgB,OAAO,cAAc,YAAY,YAAY;CACnE,MAAM,gBACJ,OAAO,OAAO,kBAAkB,WAAW,OAAO,cAAc,KAAK,IAAI;CAE3E,OAAO;EACL,OAAO,QAAQ,IAAI,QAAA;EACnB,iBAAiB;EACjB;EACA;EACA;EACA,QAAQ;EACR,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;EACjC,GAAI,gBAAgB,EAAE,cAAc,IAAI,CAAC;CAC3C;AACF;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,uBACd,UACc;CACd,IAAI,CAAC,UAAU,MAAM,MACnB,MAAM,IAAI,MACR,oIACF;CAGF,MAAM,UAA+B,CAAC;CACtC,MAAM,WAAqB,CAAC;CAC5B,MAAM,SAAkB,CAAC;CAEzB,MAAM,UAAU,SAAS,KAAK,WAAW,CAAC;CAC1C,MAAM,UAAU,SAAS,KAAK,WAAW,CAAC;CAC1C,MAAM,WAAW,gBAAgB,QAAQ;CAEzC,MAAM,YAAY,SAAS,KAAK,QAAQ,iBAAiB;CACzD,IAAI,OAAO,cAAc,YAAY,UAAU,KAAK,GAClD,SAAS,KACP,+BAA+B,UAAU,KAAK,EAAE,gEAClD;CAGF,IAAI,QAAQ,SAAS,GAAG;EACtB,SAAS,KACP,GAAG,QAAQ,OAAO,iFACpB;EACA,KAAK,MAAM,OAAO,SAChB,OAAO,KAAK,GAAG,WAAW,KAAK,UAAU,SAAS,QAAQ,CAAC;CAE/D;CAEA,KAAK,MAAM,OAAO,SAAS,KAAK,MAC9B,OAAO,KAAK,GAAG,WAAW,KAAK,UAAU,SAAS,QAAQ,CAAC;CAG7D,IAAI,QAAQ,SAAS,GAAG;EACtB,SAAS,KACP,GAAG,QAAQ,OAAO,oFACpB;EACA,KAAK,MAAM,OAAO,SAChB,OAAO,KAAK,GAAG,WAAW,KAAK,UAAU,SAAS,QAAQ,CAAC;CAE/D;CAkBA,OAAO;EAAE,SAAA;GAfP,GAAG,6BAA6B;GAChC;GACA;EAaa;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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/style-parser.ts","../src/block-mapper.ts","../src/converter.ts"],"sourcesContent":["import { canonicalFontStack } from \"@templatical/types\";\nimport type {\n BackgroundImage,\n BackgroundPositionX,\n BackgroundPositionY,\n BackgroundSize,\n SpacingValue,\n} from \"@templatical/types\";\nimport type {\n UnlayerBackgroundImage,\n UnlayerBorder,\n UnlayerFontFamily,\n} from \"./types\";\n\n/**\n * Parses CSS-like style values from Unlayer content values.\n */\n\nexport function parsePxValue(value: string | number | undefined): number {\n if (value === undefined || value === null) return 0;\n if (typeof value === \"number\") return Math.round(value);\n // Single trailing-whitespace group with an optional `px` inside it. The\n // original `\\s*(?:px)?\\s*` had two `\\s*` quantifiers around an optional\n // group, which is the textbook polynomial-ReDoS shape (\"how should the\n // whitespace split across the two quantifiers?\") over inputs like\n // `9 ` with no `px`.\n const match = value.match(/^(-?\\d+(?:\\.\\d+)?)\\s*(?:px\\s*)?$/);\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 | number | undefined,\n width: number | undefined,\n height: number | undefined,\n): number | undefined {\n if (value === undefined || value === null || value === \"\") return undefined;\n if (typeof value === \"number\") {\n return value > 0 ? Math.round(value) : undefined;\n }\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\nexport function parseColor(value: string | undefined): string {\n if (!value || value === \"transparent\") return \"\";\n\n const trimmed = value.trim();\n\n if (/^#[0-9a-fA-F]{6}$/.test(trimmed)) return trimmed.toLowerCase();\n\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 trimmed;\n}\n\nexport function parsePaddingShorthand(value: string | undefined): SpacingValue {\n if (!value) return { top: 0, right: 0, bottom: 0, left: 0 };\n\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\nexport function parseBorderObject(border: UnlayerBorder | undefined): {\n width: number;\n style: string;\n color: string;\n} {\n if (!border) return { width: 0, style: \"solid\", color: \"#000000\" };\n return {\n width: parsePxValue(border.borderTopWidth),\n style: border.borderTopStyle || \"solid\",\n color: parseColor(border.borderTopColor) || \"#000000\",\n };\n}\n\n/**\n * A divider's `width` as a percentage or a px length; a unitless value is px.\n * `undefined` for anything else. The sign survives, so the caller can clamp.\n */\nexport function parseDividerWidth(\n value: string | number,\n): { unit: \"%\" | \"px\"; value: number } | undefined {\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? { unit: \"px\", value } : undefined;\n }\n // One `\\s*` before the unit, so there is a single way to match trailing\n // whitespace (see `parsePxValue` for the two-quantifier ReDoS shape).\n const match = value.trim().match(/^(-?\\d+(?:\\.\\d+)?)\\s*(%|px)?$/);\n if (!match) return undefined;\n return { unit: match[2] === \"%\" ? \"%\" : \"px\", value: parseFloat(match[1]) };\n}\n\n/**\n * The font stack to store for an Unlayer `fontFamily`, which is either a CSS\n * string or a `{ label, value }` pick from Unlayer's font list. The label is\n * read only when `value` sets no font.\n */\nexport function parseFontFamily(\n value: UnlayerFontFamily | string | undefined,\n): string {\n if (!value) return \"\";\n if (typeof value === \"string\") return canonicalFontStack(value);\n return canonicalFontStack(value.value) || canonicalFontStack(value.label);\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\nconst UNLAYER_POSITIONS: Readonly<\n Record<string, { x: BackgroundPositionX; y: BackgroundPositionY }>\n> = {\n \"top-left\": { x: \"left\", y: \"top\" },\n \"top-center\": { x: \"center\", y: \"top\" },\n \"top-right\": { x: \"right\", y: \"top\" },\n \"center-left\": { x: \"left\", y: \"center\" },\n center: { x: \"center\", y: \"center\" },\n \"center-right\": { x: \"right\", y: \"center\" },\n \"bottom-left\": { x: \"left\", y: \"bottom\" },\n \"bottom-center\": { x: \"center\", y: \"bottom\" },\n \"bottom-right\": { x: \"right\", y: \"bottom\" },\n};\n\n/**\n * A CSS `background-size` as a keyword. A size with a side at 100%, or a px\n * width spanning the box, fills it as `cover` does; any other length or\n * percentage 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 * 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 * An Unlayer row's `backgroundImage`, `null` when its `url` is blank. Unset\n * fields take Unlayer's editor defaults: `custom` size, which is the image at\n * its own size (`auto`), `center`, and `no-repeat`. A `custom` position reads\n * `customPosition` as CSS. A value with no exact keyword takes the nearest\n * one and adds a note naming it. Older designs carry `cover` and `center`\n * booleans instead of `size` and `position`; they map exactly, and only when\n * the string field is absent.\n */\nexport function readUnlayerBackgroundImage(\n bg: UnlayerBackgroundImage | undefined,\n label: string,\n boxWidth: number,\n): ReadBackgroundImage | null {\n const url = typeof bg?.url === \"string\" ? bg.url.trim() : \"\";\n if (!bg || !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 = \"auto\";\n const rawSize = typeof bg.size === \"string\" ? bg.size.trim() : \"\";\n if (rawSize === \"cover\" || rawSize === \"contain\") {\n size = rawSize;\n } else if (rawSize && rawSize !== \"custom\") {\n const parsed = backgroundSizeKeyword(rawSize, boxWidth);\n size = parsed.size;\n if (!parsed.exact) lossy(\"backgroundImage.size\", rawSize, size);\n } else if (!rawSize && typeof bg.cover === \"boolean\") {\n size = bg.cover ? \"cover\" : \"auto\";\n }\n\n let x: BackgroundPositionX = \"center\";\n let y: BackgroundPositionY = \"center\";\n const rawPosition = typeof bg.position === \"string\" ? bg.position.trim() : \"\";\n if (rawPosition === \"custom\") {\n const [rawX = \"\", rawY = \"\"] = Array.isArray(bg.customPosition)\n ? bg.customPosition.map((part) => String(part).trim())\n : [];\n const readX = backgroundAxisPoint(rawX.toLowerCase(), X_GRID);\n const readY = backgroundAxisPoint(rawY.toLowerCase(), Y_GRID);\n x = readX?.point ?? x;\n y = readY?.point ?? y;\n if (!readX?.exact || !readY?.exact) {\n lossy(\n \"backgroundImage.customPosition\",\n `${rawX} ${rawY}`.trim(),\n `${x} ${y}`,\n );\n }\n } else if (rawPosition) {\n const keyword = Object.hasOwn(UNLAYER_POSITIONS, rawPosition)\n ? UNLAYER_POSITIONS[rawPosition]\n : undefined;\n if (keyword) {\n x = keyword.x;\n y = keyword.y;\n } else {\n lossy(\"backgroundImage.position\", rawPosition, `${x} ${y}`);\n }\n } else if (typeof bg.center === \"boolean\") {\n x = bg.center ? \"center\" : \"left\";\n y = \"top\";\n }\n\n let repeat = false;\n if (typeof bg.repeat === \"boolean\") {\n repeat = bg.repeat;\n } else if (typeof bg.repeat === \"string\" && bg.repeat.trim()) {\n const rawRepeat = bg.repeat.trim();\n repeat = rawRepeat !== \"no-repeat\";\n if (repeat && rawRepeat !== \"repeat\") {\n lossy(\"backgroundImage.repeat\", rawRepeat, \"repeat\");\n }\n }\n\n return { image: { url, size, position: { x, y }, repeat }, notes };\n}\n","import {\n createTitleBlock,\n createParagraphBlock,\n createImageBlock,\n createButtonBlock,\n createDividerBlock,\n createSpacerBlock,\n createHtmlBlock,\n createSocialIconsBlock,\n createMenuBlock,\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} from \"@templatical/types\";\nimport type {\n UnlayerContent,\n UnlayerContentValues,\n UnlayerImageSrc,\n ImportReportEntry,\n} from \"./types\";\nimport {\n parseImageBorderRadius,\n parsePxValue,\n parseColor,\n parseFontFamily,\n parsePaddingShorthand,\n parseBorderObject,\n parseDividerWidth,\n} from \"./style-parser\";\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 mail: \"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\n/** The body width when a template's `contentWidth` states no px value. */\nexport const DEFAULT_BODY_WIDTH = 600;\n\n/** The template text colour when the body sets none. */\nexport const FALLBACK_TEXT_COLOR = \"#1a1a1a\";\n\n/**\n * What the caller knows about where a content node lands.\n */\nexport interface ContentContext {\n /**\n * Width in px of the Templatical column the block renders in. Defaults to\n * `DEFAULT_BODY_WIDTH`.\n */\n columnWidth?: number;\n /**\n * The template's `textColor`, which every text block without a colour of\n * its own inherits. Defaults to `FALLBACK_TEXT_COLOR`.\n */\n textColor?: string;\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\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 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\nfunction makeStyles(values: UnlayerContentValues): Block[\"styles\"] {\n const padding = parsePaddingShorthand(values.containerPadding);\n return {\n padding,\n };\n}\n\n/**\n * Apply Unlayer text-level styles as TipTap-compatible inline markup.\n * Mirrors the BeeFree importer's helper but reads from Unlayer's flat\n * values shape rather than a CSS style record.\n */\nfunction inlineStylesToHtml(\n html: string,\n values: UnlayerContentValues,\n textColor: string,\n): string {\n const spanParts: string[] = [];\n const fontSize = parsePxValue(values.fontSize);\n if (fontSize && fontSize !== 16) spanParts.push(`font-size: ${fontSize}px`);\n const color = parseColor(values.color);\n if (color && color !== textColor) spanParts.push(`color: ${color}`);\n const fontWeight = values.fontWeight;\n if (\n fontWeight !== undefined &&\n fontWeight !== null &&\n String(fontWeight) !== \"normal\" &&\n String(fontWeight) !== \"400\"\n ) {\n spanParts.push(`font-weight: ${fontWeight}`);\n }\n const fontFamily = parseFontFamily(values.fontFamily);\n if (fontFamily) spanParts.push(`font-family: ${fontFamily}`);\n\n const textAlign = values.textAlign;\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 let result = html;\n\n if (pStyle) {\n result = result\n .replace(/<p style=\"([^\"]*)\">/g, `<p style=\"$1; ${pStyle}\">`)\n .replaceAll(\"<p>\", `<p style=\"${pStyle}\">`);\n }\n\n if (spanStyle) {\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 ensureParagraphWrapped(html: string): string {\n if (!html) return \"<p></p>\";\n if (/<p[\\s>]/i.test(html)) return html;\n return `<p>${html}</p>`;\n}\n\nfunction convertText(values: UnlayerContentValues, textColor: string): Block {\n const html = ensureParagraphWrapped(values.text ?? \"\");\n\n return createParagraphBlock({\n content: inlineStylesToHtml(html, values, textColor),\n styles: makeStyles(values),\n });\n}\n\nfunction parseHeadingLevel(tag: string | undefined): HeadingLevel {\n if (!tag) return 2;\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(values: UnlayerContentValues): Block {\n const text = values.text ?? \"\";\n const stripped = text.replace(/^<h\\d[^>]*>|<\\/h\\d>$/gi, \"\");\n const content = stripped ? `<p>${stripped}</p>` : \"<p></p>\";\n\n return createTitleBlock({\n content,\n level: parseHeadingLevel(values.headingType),\n color: parseColor(values.color) || undefined,\n textAlign: toAlign(values.textAlign),\n fontFamily: parseFontFamily(values.fontFamily) || undefined,\n styles: makeStyles(values),\n });\n}\n\n/**\n * The width Unlayer shows an image at, as `ImageBlock.width`. `src.width` is\n * the file's own size: with `autoWidth` (the default) the image is shown at\n * that size capped at the column, without it at `maxWidth`, a percentage of\n * the column. `room` is the column less the image's side padding. No width, or\n * one that reaches `room`, is `\"full\"`.\n *\n * A percentage under 100% becomes px of `room`, which keeps its size on a\n * phone where a percentage would shrink with the column, so it pushes a note.\n */\nfunction resolveImageWidth(\n src: UnlayerImageSrc | undefined,\n room: number,\n notes: string[],\n): ImageBlock[\"width\"] {\n if (src?.autoWidth === false) {\n const raw = (src.maxWidth ?? \"\").trim();\n const parsed = raw ? parseDividerWidth(raw) : undefined;\n if (!parsed || parsed.value <= 0) {\n if (raw) {\n notes.push(\n `Image maxWidth \"${raw}\" could not be read; imported as full width.`,\n );\n }\n return \"full\";\n }\n const px = Math.round(\n parsed.unit === \"%\" ? (room * parsed.value) / 100 : parsed.value,\n );\n if (px >= room) return \"full\";\n if (parsed.unit === \"%\") {\n notes.push(\n `Unlayer shows this image at ${raw} of its column; imported as ${px}px, which keeps its size on a phone.`,\n );\n }\n return px;\n }\n\n const natural = src?.width ? Math.round(src.width) : 0;\n return natural <= 0 || natural >= room ? \"full\" : natural;\n}\n\nfunction convertImage(\n values: UnlayerContentValues,\n columnWidth: number,\n): { block: Block; notes: string[] } {\n const src = values.src;\n const action = values.action?.values;\n const styles = makeStyles(values);\n const notes: string[] = [];\n const room = columnWidth - styles.padding.left - styles.padding.right;\n const width = resolveImageWidth(src, room, notes);\n\n // `src.width` and `src.height` are the file's size, so the height is never\n // carried over: the image keeps its aspect ratio at the width it is shown\n // at. A percentage radius resolves against that shown size, and only when\n // Unlayer stated the file's width.\n const shownWidth = width === \"full\" ? room : width;\n const shownHeight =\n src?.width && src.height\n ? Math.round((src.height * shownWidth) / src.width)\n : undefined;\n\n const fileWidth = src?.width ? Math.round(src.width) : 0;\n const fileHeight = src?.height ? Math.round(src.height) : 0;\n\n return {\n block: createImageBlock({\n src: src?.url || \"\",\n alt: values.altText || \"\",\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 values.borderRadius,\n src?.width ? shownWidth : undefined,\n shownHeight,\n ),\n align: toAlign(values.textAlign, \"center\"),\n linkUrl: action?.href || undefined,\n linkOpenInNewTab: action?.target === \"_blank\" || undefined,\n styles,\n }),\n notes,\n };\n}\n\nfunction convertButton(values: UnlayerContentValues): Block {\n const colors = values.buttonColors ?? {};\n const padding = values.padding\n ? parsePaddingShorthand(values.padding)\n : { top: 12, right: 24, bottom: 12, left: 24 };\n const label = stripTagsPlain(values.text ?? \"Button\");\n const linkValues = values.href?.values;\n\n return createButtonBlock({\n text: label,\n url: linkValues?.href || \"#\",\n openInNewTab: linkValues?.target === \"_blank\" || undefined,\n backgroundColor: parseColor(colors.backgroundColor) || \"#4f46e5\",\n textColor: parseColor(colors.color) || \"#ffffff\",\n borderRadius: parsePxValue(values.borderRadius),\n fontSize: parsePxValue(values.fontSize) || 16,\n fontFamily: parseFontFamily(values.fontFamily) || undefined,\n align: toAlign(values.textAlign, \"center\"),\n buttonPadding: padding,\n styles: makeStyles(values),\n });\n}\n\n/**\n * `\"full\"` when the divider spans its column: no width, `100%`, or a px width\n * that reaches `room`, the span the column leaves the line. Any other\n * percentage stays a percentage, clamped to 0–100%.\n */\nfunction resolveDividerWidth(\n value: string | number | undefined,\n room: number,\n notes: string[],\n): DividerBlock[\"width\"] {\n const raw = String(value ?? \"\").trim();\n if (!raw) return \"full\";\n\n const parsed = parseDividerWidth(typeof value === \"number\" ? value : 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 px >= room ? \"full\" : px;\n}\n\n/**\n * Unlayer places a partial divider by `textAlign`, centred by default.\n * Templatical centres every divider, so a left- or right-aligned partial one\n * comes back with a note.\n */\nfunction convertDivider(\n values: UnlayerContentValues,\n columnWidth: number,\n): { block: Block; notes: string[] } {\n const border = parseBorderObject(values.border);\n const styles = makeStyles(values);\n const notes: string[] = [];\n // `mj-divider` draws 100% across the column less the divider's own side\n // padding, so a px width that reaches that span renders as \"full\" does.\n const room = columnWidth - styles.padding.left - styles.padding.right;\n const width = resolveDividerWidth(values.width, room, notes);\n\n const align = values.textAlign;\n if (width !== \"full\" && (align === \"left\" || align === \"right\")) {\n notes.push(\n `Unlayer 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\nfunction convertSpacer(values: UnlayerContentValues): Block {\n const padding = parsePaddingShorthand(values.containerPadding);\n const height =\n parsePxValue((values as { height?: string | number }).height) ||\n padding.top + padding.bottom ||\n 24;\n\n return createSpacerBlock({\n height,\n styles: makeStyles(values),\n });\n}\n\nfunction convertHtml(values: UnlayerContentValues): Block {\n return createHtmlBlock({\n content: values.html ?? \"\",\n styles: makeStyles(values),\n });\n}\n\nfunction convertSocial(\n values: UnlayerContentValues,\n warnings: string[],\n): Block {\n const iconList = values.icons?.icons ?? [];\n const icons: SocialIcon[] = [];\n\n for (const unlayerIcon of iconList) {\n const id = (unlayerIcon.name ?? \"\").toLowerCase();\n const platform = SOCIAL_PLATFORM_MAP[id];\n\n if (!platform) {\n warnings.push(\n `Unrecognized social icon \"${unlayerIcon.name || id}\" was skipped.`,\n );\n continue;\n }\n\n icons.push({\n id: generateId(),\n platform,\n url: unlayerIcon.url || \"#\",\n });\n }\n\n return createSocialIconsBlock({\n icons,\n align: toAlign(values.textAlign, \"center\"),\n styles: makeStyles(values),\n });\n}\n\nfunction convertVideo(values: UnlayerContentValues): Block {\n return createVideoBlock({\n url: values.videoUrl || \"\",\n thumbnailUrl: values.thumbnailUrl || \"\",\n alt: values.altText || \"\",\n width: 600,\n align: toAlign(values.textAlign, \"center\"),\n styles: makeStyles(values),\n });\n}\n\nfunction convertMenu(values: UnlayerContentValues): Block {\n const menu = values.menu;\n const items: MenuItemData[] = (menu?.items ?? []).map((item) => ({\n id: generateId(),\n text: item.text || \"\",\n url: item.link?.values?.href || \"#\",\n openInNewTab: item.link?.values?.target === \"_blank\",\n bold: false,\n underline: false,\n }));\n\n return createMenuBlock({\n items,\n separator: values.separator || \"|\",\n separatorColor: \"#999999\",\n fontSize: parsePxValue(values.fontSize) || 14,\n color: parseColor(values.color) || undefined,\n fontFamily: parseFontFamily(values.fontFamily) || undefined,\n textAlign: toAlign(values.textAlign, \"center\"),\n styles: makeStyles(values),\n });\n}\n\nfunction convertHtmlFallback(content: UnlayerContent, comment: string): Block {\n const safe = comment.replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\");\n return createHtmlBlock({\n content: `<div style=\"padding:12px;border:1px dashed #d1d5db;border-radius:6px;background:#fafafa;color:#6b7280;font-family:sans-serif;font-size:13px;\">${safe}</div>`,\n styles: makeStyles(content.values),\n });\n}\n\n/**\n * Converts a single Unlayer content node to a Templatical block.\n * Returns the block and a report entry.\n */\nexport function convertContent(\n content: UnlayerContent,\n warnings: string[],\n context: ContentContext = {},\n): { block: Block; entry: ImportReportEntry } {\n const type = content.type;\n const values = content.values ?? ({} as UnlayerContentValues);\n const columnWidth = context.columnWidth ?? DEFAULT_BODY_WIDTH;\n const textColor = context.textColor ?? FALLBACK_TEXT_COLOR;\n\n switch (type) {\n case \"text\":\n return {\n block: convertText(values, textColor),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"paragraph\",\n status: \"converted\",\n },\n };\n case \"heading\":\n return {\n block: convertHeading(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"title\",\n status: \"converted\",\n },\n };\n case \"image\": {\n const { block, notes } = convertImage(values, columnWidth);\n return {\n block,\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"image\",\n status: notes.length > 0 ? \"approximated\" : \"converted\",\n ...(notes.length > 0 ? { note: notes.join(\" \") } : {}),\n },\n };\n }\n case \"button\":\n return {\n block: convertButton(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"button\",\n status: \"converted\",\n },\n };\n case \"divider\": {\n const { block, notes } = convertDivider(values, columnWidth);\n return {\n block,\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"divider\",\n status: notes.length > 0 ? \"approximated\" : \"converted\",\n ...(notes.length > 0 ? { note: notes.join(\" \") } : {}),\n },\n };\n }\n case \"spacer\":\n return {\n block: convertSpacer(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"spacer\",\n status: \"converted\",\n },\n };\n case \"html\":\n return {\n block: convertHtml(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"html\",\n status: \"converted\",\n },\n };\n case \"menu\":\n return {\n block: convertMenu(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"menu\",\n status: \"approximated\",\n note: \"Menu styles map approximately; review spacing and separator color.\",\n },\n };\n case \"social\":\n return {\n block: convertSocial(values, warnings),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"social\",\n status: \"converted\",\n },\n };\n case \"video\":\n return {\n block: convertVideo(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"video\",\n status: \"converted\",\n },\n };\n case \"timer\":\n return {\n block: convertHtmlFallback(\n content,\n \"Unlayer timer block: rebuild manually in Templatical\",\n ),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n note: \"Timer modules have no direct Templatical equivalent; placeholder HTML inserted.\",\n },\n };\n case \"form\":\n return {\n block: convertHtmlFallback(\n content,\n \"Unlayer form block: not supported in Templatical (most email clients block form submission)\",\n ),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: null,\n status: \"skipped\",\n note: \"Unlayer forms have no Templatical equivalent and are skipped. Most email clients block form submission anyway.\",\n },\n };\n default:\n return {\n block: convertHtmlFallback(\n content,\n `Unsupported Unlayer content type: ${type}`,\n ),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n note: `Unknown content type \"${type}\" converted to HTML block.`,\n },\n };\n }\n}\n","import {\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 UnlayerTemplate,\n UnlayerRow,\n UnlayerColumn,\n ImportResult,\n ImportReport,\n ImportReportEntry,\n} from \"./types\";\nimport {\n convertContent,\n DEFAULT_BODY_WIDTH,\n FALLBACK_TEXT_COLOR,\n} from \"./block-mapper\";\nimport type { ContentContext } from \"./block-mapper\";\nimport {\n parsePxValue,\n parseColor,\n parseFontFamily,\n parsePaddingShorthand,\n readUnlayerBackgroundImage,\n} from \"./style-parser\";\n\nfunction resolveColumnLayout(\n cells: number[],\n warnings: string[],\n): ColumnLayout {\n if (cells.length <= 1) return \"1\";\n if (cells.length === 3) return \"3\";\n\n if (cells.length === 2) {\n const left = cells[0] ?? 1;\n const right = cells[1] ?? 1;\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 warnings.push(\n `Row with ${cells.length} columns was flattened to a single column. Unlayer supports arbitrary columns, but Templatical supports up to 3.`,\n );\n return \"1\";\n}\n\n/** Each column's share of the body width, by layout. */\nconst COLUMN_SHARES: Record<ColumnLayout, number[]> = {\n \"1\": [1],\n \"2\": [1 / 2, 1 / 2],\n \"3\": [1 / 3, 1 / 3, 1 / 3],\n \"1-2\": [1 / 3, 2 / 3],\n \"2-1\": [2 / 3, 1 / 3],\n};\n\n/**\n * A column's px width as the renderer draws it: the body width times the\n * column's share, rounded down. A column past the layout's last slot takes\n * the whole body width, the renderer's fallback.\n */\nfunction columnWidthAt(\n layout: ColumnLayout,\n bodyWidth: number,\n index: number,\n): number {\n return Math.floor(bodyWidth * (COLUMN_SHARES[layout][index] ?? 1));\n}\n\n/**\n * Converts all contents in a column to Templatical blocks.\n */\nfunction convertColumnContents(\n column: UnlayerColumn,\n context: ContentContext,\n entries: ImportReportEntry[],\n warnings: string[],\n): Block[] {\n const blocks: Block[] = [];\n\n for (const content of column.contents ?? []) {\n const { block, entry } = convertContent(content, warnings, context);\n blocks.push(block);\n entries.push(entry);\n }\n\n return blocks;\n}\n\n/**\n * The section's fill. `columnsBackgroundColor` covers the content width, the\n * area a section paints; `backgroundColor` is the band outside it, used only\n * when the row sets no content colour.\n */\nfunction resolveRowBackground(\n row: UnlayerRow,\n warnings: string[],\n): string | undefined {\n const content = parseColor(row.values?.columnsBackgroundColor);\n const band = parseColor(row.values?.backgroundColor);\n if (content && band && content !== band) {\n warnings.push(\n `Row background ${band} outside the content width was dropped; the section keeps the content background ${content}.`,\n );\n }\n return content || band || undefined;\n}\n\n/**\n * The image a row's section paints. `fullWidth: false` is the content area\n * a section paints, so it maps exactly. A full-width band (Unlayer's\n * default) shows through the content area only while\n * `columnsBackgroundColor` leaves it transparent: then the section paints it\n * at the content width, and otherwise it's dropped. Losses go to `warnings`,\n * since rows have no report entry.\n */\nfunction resolveRowBackgroundImage(\n row: UnlayerRow,\n width: number,\n warnings: string[],\n): BackgroundImage | undefined {\n const bg = row.values?.backgroundImage;\n const read = readUnlayerBackgroundImage(bg, \"Row\", width);\n if (!read) return undefined;\n\n const band = bg?.fullWidth !== false;\n const content = parseColor(row.values?.columnsBackgroundColor);\n if (band && content) {\n warnings.push(\n `Row background image \"${read.image.url}\" outside the content width was dropped; the section keeps the content background ${content}.`,\n );\n return undefined;\n }\n\n warnings.push(...read.notes);\n if (band) {\n warnings.push(\n `Row background image \"${read.image.url}\" spans the full width in Unlayer; the section paints it at the content width.`,\n );\n }\n return read.image;\n}\n\n/**\n * Processes a single Unlayer row into one or more Templatical blocks.\n *\n * Each column's px width derives from `settings.width` and the resolved\n * layout; every block also learns the template's `textColor`.\n */\nfunction processRow(\n row: UnlayerRow,\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 const cells = row.cells ?? columns.map(() => 1);\n const layout = resolveColumnLayout(cells, warnings);\n const contextAt = (index: number): ContentContext => ({\n columnWidth: columnWidthAt(layout, settings.width, index),\n textColor: settings.textColor,\n });\n\n let children: Block[][];\n if (layout === \"1\") {\n const merged: Block[] = [];\n for (const column of columns) {\n merged.push(\n ...convertColumnContents(column, contextAt(0), entries, warnings),\n );\n }\n children = [merged];\n } else {\n children = columns.map((col, index) =>\n convertColumnContents(col, contextAt(index), entries, warnings),\n );\n }\n\n const background = resolveRowBackground(row, warnings);\n const padding = parsePaddingShorthand(row.values?.padding);\n const backgroundImage = resolveRowBackgroundImage(\n row,\n settings.width,\n warnings,\n );\n\n const section = createSectionBlock({\n columns: layout,\n children,\n styles: {\n padding,\n ...(background ? { backgroundColor: background } : {}),\n },\n ...(backgroundImage ? { backgroundImage } : {}),\n });\n\n return [section];\n}\n\n/**\n * Extracts template-level settings from the Unlayer body values.\n */\nfunction extractSettings(\n template: UnlayerTemplate,\n): TemplateContent[\"settings\"] {\n const values = template.body?.values ?? {};\n\n const width = parsePxValue(values.contentWidth);\n const bgColor = parseColor(values.backgroundColor) || \"#ffffff\";\n const fontFamily = parseFontFamily(values.fontFamily) || \"Arial\";\n const textColor = parseColor(values.textColor) || FALLBACK_TEXT_COLOR;\n const linkColor = parseColor(values.linkStyle?.linkColor);\n // An unstated underline is on: Unlayer's default, the browser's and the SDK's.\n const underline = values.linkStyle?.linkUnderline;\n const linkUnderline = typeof underline === \"boolean\" ? underline : true;\n const preheaderText =\n typeof values.preheaderText === \"string\" ? values.preheaderText.trim() : \"\";\n\n return {\n width: width > 0 ? width : DEFAULT_BODY_WIDTH,\n backgroundColor: bgColor,\n textColor,\n linkUnderline,\n fontFamily,\n locale: \"en\",\n ...(linkColor ? { linkColor } : {}),\n ...(preheaderText ? { preheaderText } : {}),\n };\n}\n\n/**\n * Converts an Unlayer design JSON to Templatical TemplateContent.\n *\n * @param template - The parsed Unlayer JSON object (the result of `editor.saveDesign(...)`)\n * @returns An ImportResult with the converted content and a detailed report\n *\n * @example\n * ```ts\n * import { convertUnlayerTemplate } from '@templatical/import-unlayer';\n *\n * const unlayerJson = JSON.parse(fileContent);\n * const { content, report } = convertUnlayerTemplate(unlayerJson);\n *\n * const editor = init({ container: '#editor', content });\n *\n * console.log(report.summary);\n * console.log(report.warnings);\n * ```\n */\nexport function convertUnlayerTemplate(\n template: UnlayerTemplate,\n): ImportResult {\n if (!template?.body?.rows) {\n throw new Error(\n \"Invalid Unlayer template: missing body.rows. Ensure you are passing a valid Unlayer JSON design (the output of editor.saveDesign).\",\n );\n }\n\n const entries: ImportReportEntry[] = [];\n const warnings: string[] = [];\n const blocks: Block[] = [];\n\n const headers = template.body.headers ?? [];\n const footers = template.body.footers ?? [];\n const settings = extractSettings(template);\n\n const pageImage = template.body.values?.backgroundImage?.url;\n if (typeof pageImage === \"string\" && pageImage.trim()) {\n warnings.push(\n `The email background image \"${pageImage.trim()}\" was dropped; Templatical has no email-level background image.`,\n );\n }\n\n if (headers.length > 0) {\n warnings.push(\n `${headers.length} Unlayer header row(s) were imported as regular rows at the top of the template.`,\n );\n for (const row of headers) {\n blocks.push(...processRow(row, settings, entries, warnings));\n }\n }\n\n for (const row of template.body.rows) {\n blocks.push(...processRow(row, settings, entries, warnings));\n }\n\n if (footers.length > 0) {\n warnings.push(\n `${footers.length} Unlayer footer row(s) were imported as regular rows at the bottom of the template.`,\n );\n for (const row of footers) {\n blocks.push(...processRow(row, settings, entries, warnings));\n }\n }\n\n const content: TemplateContent = {\n ...createDefaultTemplateContent(),\n blocks,\n settings,\n };\n\n const summary = {\n total: entries.length,\n converted: entries.filter((e) => e.status === \"converted\").length,\n approximated: entries.filter((e) => e.status === \"approximated\").length,\n htmlFallback: entries.filter((e) => e.status === \"html-fallback\").length,\n skipped: entries.filter((e) => e.status === \"skipped\").length,\n };\n\n const report: ImportReport = { entries, warnings, summary };\n\n return { content, report };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@templatical/import-unlayer",
3
3
  "description": "Convert Unlayer email templates to Templatical format",
4
- "version": "0.51.0",
4
+ "version": "0.52.0",
5
5
  "bugs": "https://github.com/templatical/sdk/issues",
6
6
  "dependencies": {
7
- "@templatical/types": "0.51.0"
7
+ "@templatical/types": "0.52.0"
8
8
  },
9
9
  "devDependencies": {
10
10
  "typescript": "^6.0.3",