@templatical/import-html 0.5.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/converter.ts","../src/style-parser.ts","../src/css-resolver.ts","../src/block-mapper.ts","../src/section-builder.ts"],"sourcesContent":["import { load } from \"cheerio\";\nimport type { CheerioAPI, Cheerio } from \"cheerio\";\nimport type { Element } from \"domhandler\";\nimport {\n createDefaultTemplateContent,\n createSectionBlock,\n} from \"@templatical/types\";\nimport type { Block, TemplateContent } from \"@templatical/types\";\nimport { resolveCssStyles } from \"./css-resolver\";\nimport { convertElement } from \"./block-mapper\";\nimport { processTable } from \"./section-builder\";\nimport {\n parseColor,\n parseFontFamily,\n parsePxValue,\n parseStyleAttribute,\n} from \"./style-parser\";\nimport type { ImportReport, ImportReportEntry, ImportResult } from \"./types\";\n\nfunction emptyMargin() {\n return { top: 0, right: 0, bottom: 0, left: 0 };\n}\n\nfunction emptyPadding() {\n return { top: 0, right: 0, bottom: 0, left: 0 };\n}\n\nfunction readPreheader($: CheerioAPI): string | undefined {\n // Convention: a hidden <div> at the very top with `display:none` containing\n // the preheader text. Match if it appears in the first ~3 children of body.\n const candidates = $(\"body\")\n .children()\n .slice(0, 5)\n .filter((_, el) => {\n const styles = parseStyleAttribute($(el).attr(\"style\"));\n return (styles.display ?? \"\").toLowerCase() === \"none\";\n });\n if (candidates.length === 0) return undefined;\n const text = $(candidates[0]).text().trim();\n return text || undefined;\n}\n\nfunction extractSettings($: CheerioAPI): TemplateContent[\"settings\"] {\n const $body = $(\"body\");\n const bodyStyles = parseStyleAttribute($body.attr(\"style\"));\n const fontFamily = parseFontFamily(bodyStyles[\"font-family\"]) || \"Arial\";\n const backgroundColor =\n parseColor(bodyStyles[\"background-color\"]) ||\n parseColor(bodyStyles.background) ||\n \"#ffffff\";\n\n // Width heuristic: outermost table width attr, or \".container\" width style.\n const $outerTable = $body.find(\"table\").first();\n const widthAttr = parsePxValue($outerTable.attr(\"width\"));\n const widthStyle = parsePxValue(\n parseStyleAttribute($outerTable.attr(\"style\")).width,\n );\n const width = widthAttr || widthStyle || 600;\n\n const preheaderText = readPreheader($);\n\n return {\n width,\n backgroundColor,\n fontFamily,\n ...(preheaderText ? { preheaderText } : {}),\n };\n}\n\n/**\n * Wrap a list of free-floating blocks (those produced by top-level non-table\n * elements) in a single one-column section.\n */\nfunction wrapInSection(blocks: Block[]): Block {\n return createSectionBlock({\n columns: \"1\",\n children: [blocks],\n styles: {\n padding: emptyPadding(),\n margin: emptyMargin(),\n },\n });\n}\n\n/**\n * Walk top-level body children. Tables become sections; loose content\n * elements are accumulated and wrapped in a single one-column section.\n */\nfunction processBody(\n $: CheerioAPI,\n entries: ImportReportEntry[],\n warnings: string[],\n): Block[] {\n const blocks: Block[] = [];\n const $body = $(\"body\");\n const children = $body.children().toArray();\n\n let pendingLoose: Block[] = [];\n\n const flushLoose = () => {\n if (pendingLoose.length > 0) {\n blocks.push(wrapInSection(pendingLoose));\n pendingLoose = [];\n }\n };\n\n for (const childEl of children) {\n const tag = childEl.tagName?.toLowerCase() ?? \"\";\n const $child = $(childEl) as unknown as Cheerio<Element>;\n\n if (tag === \"table\") {\n flushLoose();\n blocks.push(...processTable($child, $, entries, warnings, false));\n continue;\n }\n\n // Skip hidden preheader divs — already captured in settings.\n const childStyles = parseStyleAttribute($child.attr(\"style\"));\n if ((childStyles.display ?? \"\").toLowerCase() === \"none\") continue;\n\n // Containers like a wrapping <div> with table children: recurse.\n if (\n (tag === \"div\" || tag === \"center\" || tag === \"main\") &&\n $child.find(\"table\").length > 0\n ) {\n flushLoose();\n $child.children().each((_, innerEl) => {\n const innerTag = innerEl.tagName?.toLowerCase() ?? \"\";\n const $inner = $(innerEl) as unknown as Cheerio<Element>;\n if (innerTag === \"table\") {\n blocks.push(...processTable($inner, $, entries, warnings, false));\n } else {\n const r = convertElement($inner, $);\n if (r) {\n entries.push(r.entry);\n pendingLoose.push(r.block);\n }\n }\n });\n flushLoose();\n continue;\n }\n\n const r = convertElement($child, $);\n if (r) {\n entries.push(r.entry);\n pendingLoose.push(r.block);\n }\n }\n\n flushLoose();\n return blocks;\n}\n\n/**\n * Converts an HTML email template to Templatical TemplateContent.\n *\n * Designed for table-based marketing email HTML (output of MJML, Mailchimp,\n * SendGrid, Campaign Monitor, hand-coded emails). Modern HTML using flex/grid\n * layouts is preserved via HTML-fallback blocks.\n *\n * @param html - The raw HTML string (full document or body fragment).\n * @returns An ImportResult with the converted content and a detailed report.\n *\n * @example\n * ```ts\n * import { convertHtmlTemplate } from '@templatical/import-html';\n *\n * const html = await fetch('/email.html').then((r) => r.text());\n * const { content, report } = convertHtmlTemplate(html);\n *\n * const editor = init({ container: '#editor', content });\n *\n * console.log(report.summary);\n * console.log(report.warnings);\n * ```\n */\nexport function convertHtmlTemplate(html: string): ImportResult {\n if (typeof html !== \"string\") {\n throw new Error(\n \"Invalid HTML template: expected a string. Pass the raw HTML source as a string.\",\n );\n }\n if (html.trim().length === 0) {\n throw new Error(\n \"Invalid HTML template: input is empty. Pass the raw HTML source of an email.\",\n );\n }\n\n const $ = load(html);\n resolveCssStyles($);\n\n // Drop tags that are never useful in the editor canvas.\n $(\"script, noscript, link, meta, title\").remove();\n\n const entries: ImportReportEntry[] = [];\n const warnings: string[] = [];\n\n const blocks = processBody($, entries, warnings);\n\n if (blocks.length === 0) {\n warnings.push(\n \"No convertible content was found in the HTML. The email may use a non-table layout — modern HTML support is limited.\",\n );\n }\n\n const content: TemplateContent = {\n ...createDefaultTemplateContent(),\n blocks,\n settings: extractSettings($),\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","import type { SpacingValue } from \"@templatical/types\";\n\n/**\n * Parses a CSS `style=\"...\"` attribute string into a flat key/value record.\n * Keys are lowercased; values are trimmed. Quotes around values are not stripped.\n */\nexport function parseStyleAttribute(\n styleAttr: string | undefined,\n): Record<string, string> {\n const result: Record<string, string> = {};\n if (!styleAttr) return result;\n\n for (const decl of styleAttr.split(\";\")) {\n const idx = decl.indexOf(\":\");\n if (idx === -1) continue;\n const key = decl.slice(0, idx).trim().toLowerCase();\n const value = decl.slice(idx + 1).trim();\n if (key && value) result[key] = value;\n }\n\n return result;\n}\n\n/**\n * Serializes a flat key/value record back to a `style` attribute string.\n */\nexport function serializeStyleAttribute(\n styles: Record<string, string>,\n): string {\n return Object.entries(styles)\n .map(([k, v]) => `${k}: ${v}`)\n .join(\"; \");\n}\n\n/**\n * Parses a px-like CSS value (`\"12px\"`, `\"12\"`, `12`) into a rounded integer.\n * Returns 0 for missing or unparseable input. Ignores em/% units.\n */\nexport function parsePxValue(value: string | number | undefined): number {\n if (value === undefined || value === null || value === \"\") return 0;\n if (typeof value === \"number\") return Math.round(value);\n const match = value.match(/^(-?\\d+(?:\\.\\d+)?)\\s*(?:px)?\\s*$/);\n return match ? Math.round(parseFloat(match[1])) : 0;\n}\n\n/**\n * Parses a width value that may be a percentage. Returns the numeric percent\n * (0-100). For non-percent values, returns 100.\n */\nexport function parseWidthPercent(value: string | undefined): number {\n if (!value) return 100;\n const match = value.match(/^(\\d+(?:\\.\\d+)?)\\s*%/);\n if (match) return Math.round(parseFloat(match[1]));\n return 100;\n}\n\nconst NAMED_COLORS: Record<string, string> = {\n black: \"#000000\",\n white: \"#ffffff\",\n red: \"#ff0000\",\n green: \"#008000\",\n blue: \"#0000ff\",\n yellow: \"#ffff00\",\n cyan: \"#00ffff\",\n magenta: \"#ff00ff\",\n gray: \"#808080\",\n grey: \"#808080\",\n silver: \"#c0c0c0\",\n maroon: \"#800000\",\n olive: \"#808000\",\n lime: \"#00ff00\",\n aqua: \"#00ffff\",\n teal: \"#008080\",\n navy: \"#000080\",\n fuchsia: \"#ff00ff\",\n purple: \"#800080\",\n orange: \"#ffa500\",\n pink: \"#ffc0cb\",\n};\n\nfunction rgbToHex(r: number, g: number, b: number): string {\n const clamp = (n: number) => Math.max(0, Math.min(255, Math.round(n)));\n const hex = (n: number) => clamp(n).toString(16).padStart(2, \"0\");\n return `#${hex(r)}${hex(g)}${hex(b)}`;\n}\n\n/**\n * Normalizes a CSS color value to a 6-digit lowercase hex string.\n * - 3-digit hex expands to 6-digit\n * - rgb()/rgba() converts to hex (alpha is dropped)\n * - Named colors map via lookup\n * - \"transparent\" / unknown returns \"\"\n */\nexport function parseColor(value: string | undefined): string {\n if (!value) return \"\";\n const trimmed = value.trim().toLowerCase();\n if (trimmed === \"transparent\" || trimmed === \"inherit\" || trimmed === \"none\")\n return \"\";\n\n if (/^#[0-9a-f]{6}$/.test(trimmed)) return trimmed;\n\n if (/^#[0-9a-f]{3}$/.test(trimmed)) {\n const r = trimmed[1];\n const g = trimmed[2];\n const b = trimmed[3];\n return `#${r}${r}${g}${g}${b}${b}`;\n }\n\n const rgbMatch = trimmed.match(\n /^rgba?\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*(?:,\\s*[\\d.]+\\s*)?\\)$/,\n );\n if (rgbMatch) {\n return rgbToHex(\n parseInt(rgbMatch[1], 10),\n parseInt(rgbMatch[2], 10),\n parseInt(rgbMatch[3], 10),\n );\n }\n\n if (NAMED_COLORS[trimmed]) return NAMED_COLORS[trimmed];\n\n return \"\";\n}\n\n/**\n * Parses a CSS `padding` shorthand (1-4 values) into a SpacingValue.\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\n/**\n * Reads CSS padding from a style record, preferring the longhand props\n * (padding-top/right/bottom/left) and falling back to the `padding` shorthand.\n */\nexport function readPaddingFromStyles(\n styles: Record<string, string>,\n): SpacingValue {\n const shorthand = parsePaddingShorthand(styles.padding);\n return {\n top: parsePxValue(styles[\"padding-top\"]) || shorthand.top,\n right: parsePxValue(styles[\"padding-right\"]) || shorthand.right,\n bottom: parsePxValue(styles[\"padding-bottom\"]) || shorthand.bottom,\n left: parsePxValue(styles[\"padding-left\"]) || shorthand.left,\n };\n}\n\n/**\n * Strips quotes and returns the first font in a font-family stack.\n */\nexport function parseFontFamily(value: string | undefined): string {\n if (!value) return \"\";\n return value\n .split(\",\")[0]\n .trim()\n .replace(/^['\"]|['\"]$/g, \"\");\n}\n\n/**\n * Normalizes a font-weight value to a string CSS keyword/number that\n * the editor accepts. Returns \"\" when the value is the default (normal/400).\n */\nexport function parseFontWeight(value: string | undefined): string {\n if (!value) return \"\";\n const trimmed = value.trim().toLowerCase();\n if (trimmed === \"normal\" || trimmed === \"400\") return \"\";\n return trimmed;\n}\n\n/**\n * Parses CSS text-align to one of the allowed editor alignments.\n */\nexport function parseAlignment(\n value: string | undefined,\n fallback: \"left\" | \"center\" | \"right\" = \"left\",\n): \"left\" | \"center\" | \"right\" {\n const v = (value ?? \"\").trim().toLowerCase();\n if (v === \"left\" || v === \"center\" || v === \"right\") return v;\n return fallback;\n}\n\n/**\n * Parses a CSS `border` shorthand (`\"1px solid #ccc\"`) into width/style/color.\n * Order-tolerant: each token is classified by content.\n */\nexport function parseBorderShorthand(value: string | undefined): {\n width: number;\n style: string;\n color: string;\n} {\n const fallback = { width: 0, style: \"solid\", color: \"#000000\" };\n if (!value) return fallback;\n\n const styleKeywords = new Set([\n \"none\",\n \"hidden\",\n \"dotted\",\n \"dashed\",\n \"solid\",\n \"double\",\n \"groove\",\n \"ridge\",\n \"inset\",\n \"outset\",\n ]);\n\n let width = 0;\n let style = \"solid\";\n let color = \"#000000\";\n\n for (const token of value.trim().split(/\\s+/)) {\n const lower = token.toLowerCase();\n if (styleKeywords.has(lower)) {\n style = lower;\n } else if (/^-?\\d+(?:\\.\\d+)?(?:px)?$/i.test(lower)) {\n width = parsePxValue(lower);\n } else {\n const c = parseColor(lower);\n if (c) color = c;\n }\n }\n\n return { width, style, color };\n}\n","import type { CheerioAPI } from \"cheerio\";\nimport { parseStyleAttribute, serializeStyleAttribute } from \"./style-parser\";\n\ninterface CssRule {\n selectors: string[];\n declarations: Record<string, string>;\n}\n\n/**\n * Strips all CSS comments. Handles nested-looking content safely.\n */\nfunction stripComments(css: string): string {\n return css.replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\");\n}\n\n/**\n * Strips at-rule blocks (@media, @font-face, @keyframes, @supports, etc.)\n * and their nested content. Leaves top-level rules in place.\n *\n * Email HTML rarely benefits from @media (we render at one viewport),\n * and resolving it onto elements would not be visually faithful anyway.\n */\nfunction stripAtRules(css: string): string {\n let result = \"\";\n let i = 0;\n while (i < css.length) {\n if (css[i] === \"@\") {\n // skip until matching `{...}` block or terminating `;`\n const semiIdx = css.indexOf(\";\", i);\n const braceIdx = css.indexOf(\"{\", i);\n\n if (braceIdx === -1 || (semiIdx !== -1 && semiIdx < braceIdx)) {\n i = semiIdx === -1 ? css.length : semiIdx + 1;\n continue;\n }\n\n // skip the entire `{...}` block, accounting for nesting\n let depth = 0;\n let j = braceIdx;\n for (; j < css.length; j++) {\n if (css[j] === \"{\") depth++;\n else if (css[j] === \"}\") {\n depth--;\n if (depth === 0) {\n j++;\n break;\n }\n }\n }\n i = j;\n } else {\n result += css[i];\n i++;\n }\n }\n return result;\n}\n\n/**\n * Parses a CSS declarations block (`color: red; font-size: 14px`) into\n * a flat record. `!important` markers are dropped.\n */\nfunction parseDeclarations(text: string): Record<string, string> {\n const result: Record<string, string> = {};\n for (const decl of text.split(\";\")) {\n const idx = decl.indexOf(\":\");\n if (idx === -1) continue;\n const key = decl.slice(0, idx).trim().toLowerCase();\n let value = decl.slice(idx + 1).trim();\n value = value.replace(/!important\\s*$/i, \"\").trim();\n if (key && value) result[key] = value;\n }\n return result;\n}\n\n/**\n * A selector is \"supported\" by cheerio's matcher if it has no pseudo-classes\n * or pseudo-elements. Resolving e.g. `a:hover` onto an inline style would be\n * wrong (it would always apply), so we skip such rules entirely.\n */\nfunction isSupportedSelector(selector: string): boolean {\n if (!selector) return false;\n if (selector.includes(\":\")) return false;\n if (selector.includes(\"@\")) return false;\n return true;\n}\n\n/**\n * Parses the full content of one or more `<style>` tags into a list of rules.\n * Skips at-rules and selectors with pseudo-classes.\n */\nexport function parseStyleSheet(css: string): CssRule[] {\n const rules: CssRule[] = [];\n const cleaned = stripAtRules(stripComments(css));\n\n // Greedily walk top-level `selectors { decls }` blocks.\n const blockRe = /([^{}]+)\\{([^{}]*)\\}/g;\n let match: RegExpExecArray | null;\n while ((match = blockRe.exec(cleaned)) !== null) {\n const selectorPart = match[1].trim();\n const declarationPart = match[2];\n if (!selectorPart) continue;\n\n const selectors = selectorPart\n .split(\",\")\n .map((s) => s.trim())\n .filter(isSupportedSelector);\n if (selectors.length === 0) continue;\n\n const declarations = parseDeclarations(declarationPart);\n if (Object.keys(declarations).length === 0) continue;\n\n rules.push({ selectors, declarations });\n }\n\n return rules;\n}\n\n/**\n * Reads all `<style>` tags from the document, parses them into rules,\n * applies each rule's declarations to matching elements (merging with\n * existing inline `style=\"\"` attributes — inline always wins), and removes\n * the `<style>` tags from the document.\n *\n * No specificity is computed; rules are applied in source order, with later\n * rules overriding earlier ones. Inline styles always override resolved\n * rules. This is sufficient for typical email HTML, where authors already\n * inline most styles.\n */\nexport function resolveCssStyles($: CheerioAPI): void {\n const styleTags = $(\"style\");\n if (styleTags.length === 0) return;\n\n const allRules: CssRule[] = [];\n styleTags.each((_, el) => {\n const css = $(el).text();\n if (css) allRules.push(...parseStyleSheet(css));\n });\n\n // First pass: capture each element's original inline styles, so we can\n // distinguish \"author wrote this inline\" from \"we just resolved a rule into it\".\n const inlineByEl = new WeakMap<object, Record<string, string>>();\n $(\"[style]\").each((_, el) => {\n inlineByEl.set(el as object, parseStyleAttribute($(el).attr(\"style\")));\n });\n\n // Second pass: apply rules in source order. Later rules override earlier\n // resolved ones; original inline always wins at the end.\n const resolvedByEl = new WeakMap<object, Record<string, string>>();\n\n for (const rule of allRules) {\n for (const selector of rule.selectors) {\n let matched: ReturnType<CheerioAPI>;\n try {\n matched = $(selector);\n } catch {\n // cheerio threw on an exotic selector — skip the rule.\n continue;\n }\n matched.each((_, el) => {\n const key = el as object;\n const current = resolvedByEl.get(key) ?? {};\n for (const [k, v] of Object.entries(rule.declarations)) {\n current[k] = v;\n }\n resolvedByEl.set(key, current);\n });\n }\n }\n\n // Third pass: merge resolved + original inline (inline wins) and write back.\n $(\"*\").each((_, el) => {\n const key = el as object;\n const resolved = resolvedByEl.get(key);\n if (!resolved) return;\n const inline = inlineByEl.get(key) ?? {};\n const merged: Record<string, string> = { ...resolved };\n for (const [k, v] of Object.entries(inline)) merged[k] = v;\n $(el).attr(\"style\", serializeStyleAttribute(merged));\n });\n\n styleTags.remove();\n}\n","import type { CheerioAPI, Cheerio } from \"cheerio\";\nimport type { Element, AnyNode } from \"domhandler\";\nimport {\n createTitleBlock,\n createParagraphBlock,\n createImageBlock,\n createButtonBlock,\n createDividerBlock,\n createSpacerBlock,\n createHtmlBlock,\n} from \"@templatical/types\";\nimport type { Block, HeadingLevel, SpacingValue } from \"@templatical/types\";\nimport type { ImportReportEntry } from \"./types\";\nimport {\n parseAlignment,\n parseBorderShorthand,\n parseColor,\n parseFontFamily,\n parseFontWeight,\n parsePxValue,\n parseStyleAttribute,\n readPaddingFromStyles,\n} from \"./style-parser\";\n\nconst HEADING_TAGS = new Set([\"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\"]);\nconst TEXT_TAGS = new Set([\"p\", \"span\", \"div\"]);\n\nfunction emptyMargin(): SpacingValue {\n return { top: 0, right: 0, bottom: 0, left: 0 };\n}\n\nfunction emptyPadding(): SpacingValue {\n return { top: 0, right: 0, bottom: 0, left: 0 };\n}\n\nfunction tagOf(el: Element | AnyNode): string {\n if (\"tagName\" in el && typeof el.tagName === \"string\")\n return el.tagName.toLowerCase();\n return \"\";\n}\n\nfunction getStyles($el: Cheerio<Element>): Record<string, string> {\n return parseStyleAttribute($el.attr(\"style\"));\n}\n\n/**\n * Returns the inner HTML of `$el`.\n */\nexport function getInnerHtml($el: Cheerio<Element>): string {\n return $el.html() ?? \"\";\n}\n\nfunction ensureParagraphWrapped(html: string): string {\n if (!html.trim()) return \"<p></p>\";\n if (/<(p|h[1-6]|ul|ol|blockquote)[\\s>]/i.test(html)) return html;\n return `<p>${html}</p>`;\n}\n\nfunction safeHtmlComment(message: string, raw: string): string {\n const escapedMessage = message\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n return `<!-- ${escapedMessage} -->\\n${raw}`;\n}\n\n/**\n * Heading element (h1-h6) → Title block.\n */\nfunction convertHeading($el: Cheerio<Element>): Block {\n const tag = tagOf($el[0]);\n const styles = getStyles($el);\n const levelMatch = tag.match(/^h(\\d)$/);\n const rawLevel = levelMatch ? Number(levelMatch[1]) : 2;\n const level: HeadingLevel = (\n rawLevel >= 1 && rawLevel <= 4 ? rawLevel : Math.min(rawLevel, 4)\n ) as HeadingLevel;\n\n const innerHtml = getInnerHtml($el);\n const content = innerHtml.trim() ? `<p>${innerHtml}</p>` : \"<p></p>\";\n\n return createTitleBlock({\n content,\n level,\n color: parseColor(styles.color) || \"#1a1a1a\",\n textAlign: parseAlignment(styles[\"text-align\"]),\n fontFamily: parseFontFamily(styles[\"font-family\"]) || undefined,\n styles: {\n padding: readPaddingFromStyles(styles),\n margin: emptyMargin(),\n },\n });\n}\n\n/**\n * Paragraph or block-level text container → Paragraph block.\n */\nfunction convertParagraph($el: Cheerio<Element>): Block {\n const styles = getStyles($el);\n const innerHtml = getInnerHtml($el);\n const wrapped = ensureParagraphWrapped(innerHtml);\n\n // Apply container-level styles to the wrapping <p>.\n const fontParts: string[] = [];\n const fontSize = parsePxValue(styles[\"font-size\"]);\n if (fontSize && fontSize !== 16) fontParts.push(`font-size: ${fontSize}px`);\n const color = parseColor(styles.color);\n if (color && color !== \"#1a1a1a\") fontParts.push(`color: ${color}`);\n const fontWeight = parseFontWeight(styles[\"font-weight\"]);\n if (fontWeight) fontParts.push(`font-weight: ${fontWeight}`);\n const fontFamily = parseFontFamily(styles[\"font-family\"]);\n if (fontFamily) fontParts.push(`font-family: ${fontFamily}`);\n const textAlign = styles[\"text-align\"];\n\n let result = wrapped;\n if (textAlign && textAlign !== \"left\") {\n result = result\n .replace(\n /<p style=\"([^\"]*)\">/g,\n `<p style=\"$1; text-align: ${textAlign}\">`,\n )\n .replaceAll(\"<p>\", `<p style=\"text-align: ${textAlign}\">`);\n }\n if (fontParts.length > 0) {\n const span = fontParts.join(\"; \");\n result = result.replace(\n /<p([^>]*)>([\\s\\S]*?)<\\/p>/g,\n `<p$1><span style=\"${span}\">$2</span></p>`,\n );\n }\n\n return createParagraphBlock({\n content: result,\n styles: {\n padding: readPaddingFromStyles(styles),\n margin: emptyMargin(),\n },\n });\n}\n\n/**\n * <img> → Image block.\n */\nfunction convertImage($el: Cheerio<Element>): Block {\n const styles = getStyles($el);\n const src = $el.attr(\"src\") ?? \"\";\n const alt = $el.attr(\"alt\") ?? \"\";\n const widthAttr = $el.attr(\"width\");\n const widthStyle = styles.width;\n const width = parsePxValue(widthAttr) || parsePxValue(widthStyle) || 600;\n\n return createImageBlock({\n src,\n alt,\n width,\n align: parseAlignment(styles[\"text-align\"], \"center\"),\n styles: {\n padding: readPaddingFromStyles(styles),\n margin: emptyMargin(),\n },\n });\n}\n\n/**\n * <a> styled as a button → Button block.\n *\n * Heuristic: a single `<a>` with a non-transparent background-color OR padding\n * OR border-radius OR display: inline-block / block is treated as a button.\n */\nexport function looksLikeButton(styles: Record<string, string>): boolean {\n if (parseColor(styles[\"background-color\"]) || parseColor(styles.background))\n return true;\n if (\n styles.padding ||\n styles[\"padding-top\"] ||\n styles[\"padding-bottom\"] ||\n styles[\"padding-left\"] ||\n styles[\"padding-right\"]\n )\n return true;\n if (parsePxValue(styles[\"border-radius\"])) return true;\n const display = (styles.display ?? \"\").toLowerCase();\n if (display === \"inline-block\" || display === \"block\") return true;\n return false;\n}\n\nfunction convertButton($el: Cheerio<Element>): Block {\n const styles = getStyles($el);\n const text = ($el.text() ?? \"Button\").trim() || \"Button\";\n const url = $el.attr(\"href\") ?? \"#\";\n const target = $el.attr(\"target\");\n\n return createButtonBlock({\n text,\n url,\n openInNewTab: target === \"_blank\" || undefined,\n backgroundColor:\n parseColor(styles[\"background-color\"]) ||\n parseColor(styles.background) ||\n \"#4f46e5\",\n textColor: parseColor(styles.color) || \"#ffffff\",\n borderRadius: parsePxValue(styles[\"border-radius\"]),\n fontSize: parsePxValue(styles[\"font-size\"]) || 16,\n fontFamily: parseFontFamily(styles[\"font-family\"]) || undefined,\n buttonPadding: readPaddingFromStyles(styles),\n styles: {\n padding: emptyPadding(),\n margin: emptyMargin(),\n },\n });\n}\n\n/**\n * <hr> → Divider block.\n */\nfunction convertDivider($el: Cheerio<Element>): Block {\n const styles = getStyles($el);\n const border = parseBorderShorthand(styles[\"border-top\"] ?? styles.border);\n const lineStyle =\n border.style === \"dashed\" || border.style === \"dotted\"\n ? border.style\n : \"solid\";\n\n return createDividerBlock({\n lineStyle: lineStyle as \"solid\" | \"dashed\" | \"dotted\",\n color: border.color || \"#e5e7eb\",\n thickness: border.width || 1,\n width: 100,\n styles: {\n padding: readPaddingFromStyles(styles),\n margin: emptyMargin(),\n },\n });\n}\n\n/**\n * Empty `<td>` with explicit height → Spacer block.\n */\nfunction convertSpacer($el: Cheerio<Element>): Block {\n const styles = getStyles($el);\n const heightAttr = $el.attr(\"height\");\n const height =\n parsePxValue(heightAttr) ||\n parsePxValue(styles.height) ||\n parsePxValue(styles[\"line-height\"]) ||\n 24;\n\n return createSpacerBlock({\n height,\n styles: {\n padding: emptyPadding(),\n margin: emptyMargin(),\n },\n });\n}\n\n/**\n * Wraps the element's outerHTML in an HTML block (the lossless fallback).\n */\nexport function convertHtmlFallback(\n $el: Cheerio<Element>,\n $: CheerioAPI,\n note?: string,\n): Block {\n const outer = $.html($el) ?? \"\";\n const content = note ? safeHtmlComment(note, outer) : outer;\n const styles = getStyles($el);\n\n return createHtmlBlock({\n content,\n styles: {\n padding: readPaddingFromStyles(styles),\n margin: emptyMargin(),\n },\n });\n}\n\n/**\n * Decides whether a `<td>` looks like a vertical spacer:\n * empty (or only ` `) AND has an explicit height.\n */\nexport function isSpacerCell($el: Cheerio<Element>): boolean {\n const text = ($el.text() ?? \"\").replace(/\\s| /g, \"\");\n if (text !== \"\") return false;\n if ($el.find(\"img, a, hr\").length > 0) return false;\n\n const styles = getStyles($el);\n const hasHeight =\n parsePxValue($el.attr(\"height\")) > 0 ||\n parsePxValue(styles.height) > 0 ||\n parsePxValue(styles[\"line-height\"]) > 0;\n return hasHeight;\n}\n\n/**\n * Decides whether a `<td>` is a button container — i.e. has exactly one\n * `<a>` inside that itself looks like a button.\n */\nexport function isButtonCell(\n $el: Cheerio<Element>,\n $: CheerioAPI,\n): { match: boolean; anchor?: Cheerio<Element> } {\n const anchors = $el.find(\"a\");\n if (anchors.length !== 1) return { match: false };\n const anchor = $(anchors[0]);\n if (looksLikeButton(getStyles(anchor))) return { match: true, anchor };\n // Cell-level styling (bg, padding) wrapping a plain anchor also reads as button.\n if (looksLikeButton(getStyles($el))) return { match: true, anchor };\n return { match: false };\n}\n\n/**\n * Converts a single content-bearing element (heading / paragraph / image /\n * anchor-as-button / divider) to a Templatical block.\n *\n * Returns `null` for elements that do not contain any meaningful content\n * (the caller should skip them).\n */\nexport function convertElement(\n $el: Cheerio<Element>,\n $: CheerioAPI,\n): { block: Block; entry: ImportReportEntry } | null {\n const tag = tagOf($el[0]);\n if (!tag) return null;\n\n if (HEADING_TAGS.has(tag)) {\n return {\n block: convertHeading($el),\n entry: {\n sourceTag: tag,\n templaticalBlockType: \"title\",\n status: \"converted\",\n },\n };\n }\n\n if (tag === \"img\") {\n return {\n block: convertImage($el),\n entry: {\n sourceTag: tag,\n templaticalBlockType: \"image\",\n status: \"converted\",\n },\n };\n }\n\n if (tag === \"a\") {\n if (looksLikeButton(getStyles($el))) {\n return {\n block: convertButton($el),\n entry: {\n sourceTag: tag,\n templaticalBlockType: \"button\",\n status: \"converted\",\n },\n };\n }\n // Plain anchor — wrap as paragraph.\n return {\n block: convertParagraph($el),\n entry: {\n sourceTag: tag,\n templaticalBlockType: \"paragraph\",\n status: \"approximated\",\n note: \"Inline anchor wrapped in a paragraph block.\",\n },\n };\n }\n\n if (tag === \"hr\") {\n return {\n block: convertDivider($el),\n entry: {\n sourceTag: tag,\n templaticalBlockType: \"divider\",\n status: \"converted\",\n },\n };\n }\n\n if (TEXT_TAGS.has(tag)) {\n const text = ($el.text() ?? \"\").trim();\n if (!text && $el.find(\"img, a\").length === 0) return null;\n return {\n block: convertParagraph($el),\n entry: {\n sourceTag: tag,\n templaticalBlockType: \"paragraph\",\n status: \"converted\",\n },\n };\n }\n\n // Unknown element — preserve as HTML.\n return {\n block: convertHtmlFallback(\n $el,\n $,\n `Unsupported element <${tag}>: preserved as raw HTML`,\n ),\n entry: {\n sourceTag: tag,\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n note: `Unknown element \"${tag}\" preserved as HTML block.`,\n },\n };\n}\n\n/**\n * Helpers exported for tests.\n */\nexport const _internal = {\n convertButton,\n convertDivider,\n convertHeading,\n convertImage,\n convertParagraph,\n convertSpacer,\n ensureParagraphWrapped,\n};\n","import type { CheerioAPI, Cheerio } from \"cheerio\";\nimport type { Element } from \"domhandler\";\nimport {\n createSectionBlock,\n createButtonBlock,\n createSpacerBlock,\n} from \"@templatical/types\";\nimport type { Block, ColumnLayout } from \"@templatical/types\";\nimport {\n convertElement,\n convertHtmlFallback,\n isButtonCell,\n isSpacerCell,\n looksLikeButton,\n} from \"./block-mapper\";\nimport {\n parseColor,\n parsePxValue,\n parseStyleAttribute,\n readPaddingFromStyles,\n} from \"./style-parser\";\nimport type { ImportReportEntry } from \"./types\";\n\nfunction emptyMargin() {\n return { top: 0, right: 0, bottom: 0, left: 0 };\n}\n\nfunction emptyPadding() {\n return { top: 0, right: 0, bottom: 0, left: 0 };\n}\n\nfunction getStyles($el: Cheerio<Element>): Record<string, string> {\n return parseStyleAttribute($el.attr(\"style\"));\n}\n\nfunction buildCellButton(\n $cell: Cheerio<Element>,\n $anchor: Cheerio<Element>,\n): Block {\n const cellStyles = getStyles($cell);\n const aStyles = getStyles($anchor);\n // Anchor styles win when they overlap (typical: anchor sets text color, cell sets bg).\n const merged = { ...cellStyles, ...aStyles };\n const text = ($anchor.text() ?? \"Button\").trim() || \"Button\";\n const url = $anchor.attr(\"href\") ?? \"#\";\n const target = $anchor.attr(\"target\");\n\n return createButtonBlock({\n text,\n url,\n openInNewTab: target === \"_blank\" || undefined,\n backgroundColor:\n parseColor(merged[\"background-color\"]) ||\n parseColor(merged.background) ||\n \"#4f46e5\",\n textColor: parseColor(merged.color) || \"#ffffff\",\n borderRadius: parsePxValue(merged[\"border-radius\"]),\n fontSize: parsePxValue(merged[\"font-size\"]) || 16,\n buttonPadding: readPaddingFromStyles(merged),\n styles: {\n padding: emptyPadding(),\n margin: emptyMargin(),\n },\n });\n}\n\nfunction buildSpacerFromCell($cell: Cheerio<Element>): Block {\n const cellStyles = getStyles($cell);\n const height =\n parsePxValue($cell.attr(\"height\")) ||\n parsePxValue(cellStyles.height) ||\n parsePxValue(cellStyles[\"line-height\"]) ||\n 24;\n return createSpacerBlock({\n height,\n styles: {\n padding: emptyPadding(),\n margin: emptyMargin(),\n },\n });\n}\n\n/**\n * Returns the direct child `<tr>` rows of a table, including those one level\n * inside `<thead>`, `<tbody>`, or `<tfoot>` (which the HTML parser inserts\n * automatically).\n */\nfunction getDirectRows(\n $table: Cheerio<Element>,\n $: CheerioAPI,\n): Cheerio<Element>[] {\n const rows: Cheerio<Element>[] = [];\n $table.children(\"tr\").each((_, el) => {\n rows.push($(el) as unknown as Cheerio<Element>);\n });\n $table.children(\"thead, tbody, tfoot\").each((_, group) => {\n $(group)\n .children(\"tr\")\n .each((_i, el) => {\n rows.push($(el) as unknown as Cheerio<Element>);\n });\n });\n return rows;\n}\n\nfunction getDirectCells(\n $row: Cheerio<Element>,\n $: CheerioAPI,\n): Cheerio<Element>[] {\n const cells: Cheerio<Element>[] = [];\n $row.children(\"td, th\").each((_, el) => {\n cells.push($(el) as unknown as Cheerio<Element>);\n });\n return cells;\n}\n\nfunction isLayoutTable($table: Cheerio<Element>, $: CheerioAPI): boolean {\n // A table is \"layout\" if any descendant carries content email blocks rely on,\n // OR if any cell contains a non-text element (custom tags, semantic blocks,\n // etc. — those should be preserved as html-fallback at the element level\n // rather than collapsing the entire table into one html block).\n // A bare data table (cells contain only text) is preserved as HTML.\n if (\n $table.find(\n \"img, a, h1, h2, h3, h4, h5, h6, table, hr, p, div, span, ul, ol, li, blockquote, video, iframe\",\n ).length > 0\n )\n return true;\n\n let hasNonStandardChild = false;\n $table.find(\"td, th\").each((_, td) => {\n if (hasNonStandardChild) return;\n if ($(td).children().length > 0) hasNonStandardChild = true;\n });\n return hasNonStandardChild;\n}\n\nfunction resolveColumnLayout(\n cellCount: number,\n warnings: string[],\n): ColumnLayout {\n if (cellCount <= 1) return \"1\";\n if (cellCount === 2) return \"2\";\n if (cellCount === 3) return \"3\";\n warnings.push(\n `Row with ${cellCount} columns was flattened to a single column. Templatical supports up to 3 columns per section.`,\n );\n return \"1\";\n}\n\nfunction extractCellBlocks(\n $cell: Cheerio<Element>,\n $: CheerioAPI,\n entries: ImportReportEntry[],\n warnings: string[],\n): Block[] {\n if (isSpacerCell($cell)) {\n entries.push({\n sourceTag: \"td\",\n templaticalBlockType: \"spacer\",\n status: \"converted\",\n });\n return [buildSpacerFromCell($cell)];\n }\n\n const btn = isButtonCell($cell, $);\n if (btn.match && btn.anchor) {\n entries.push({\n sourceTag: \"td\",\n templaticalBlockType: \"button\",\n status: \"converted\",\n });\n return [buildCellButton($cell, btn.anchor)];\n }\n\n const blocks: Block[] = [];\n const childEls = $cell.children().toArray();\n\n if (childEls.length === 0) {\n const text = ($cell.text() ?? \"\").trim();\n if (!text) return [];\n const r = convertElement($cell, $);\n if (r) {\n entries.push(r.entry);\n blocks.push(r.block);\n }\n return blocks;\n }\n\n for (const childEl of childEls) {\n const $child = $(childEl) as unknown as Cheerio<Element>;\n const tag = childEl.tagName?.toLowerCase() ?? \"\";\n\n if (tag === \"table\") {\n const inner = processTable($child, $, entries, warnings, true);\n blocks.push(...inner);\n continue;\n }\n\n if (tag === \"a\" && looksLikeButton(getStyles($child))) {\n const r = convertElement($child, $);\n if (r) {\n entries.push(r.entry);\n blocks.push(r.block);\n }\n continue;\n }\n\n const r = convertElement($child, $);\n if (r) {\n entries.push(r.entry);\n blocks.push(r.block);\n }\n }\n\n return blocks;\n}\n\n/**\n * Walk a `<table>` and produce Section blocks (one per row).\n *\n * @param flattenInline - When true (used for nested tables), drop the section\n * wrapper and return the flat block list. Templatical sections cannot nest,\n * so nested layout-tables are merged into their parent cell.\n */\nexport function processTable(\n $table: Cheerio<Element>,\n $: CheerioAPI,\n entries: ImportReportEntry[],\n warnings: string[],\n flattenInline = false,\n): Block[] {\n if (!isLayoutTable($table, $)) {\n entries.push({\n sourceTag: \"table\",\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n note: \"Data table preserved as HTML block.\",\n });\n return [convertHtmlFallback($table, $, \"Data table preserved as HTML\")];\n }\n\n const rows = getDirectRows($table, $);\n if (rows.length === 0) return [];\n\n const sections: Block[] = [];\n\n for (const $row of rows) {\n const cells = getDirectCells($row, $);\n if (cells.length === 0) continue;\n\n const layout = resolveColumnLayout(cells.length, warnings);\n\n let columnsBlocks: Block[][];\n if (layout === \"1\") {\n const merged: Block[] = [];\n for (const $cell of cells) {\n merged.push(...extractCellBlocks($cell, $, entries, warnings));\n }\n columnsBlocks = [merged];\n } else {\n columnsBlocks = cells.map(($cell) =>\n extractCellBlocks($cell, $, entries, warnings),\n );\n }\n\n if (flattenInline) {\n for (const col of columnsBlocks) sections.push(...col);\n continue;\n }\n\n const rowStyles = getStyles($row);\n const bgColor =\n parseColor(rowStyles[\"background-color\"]) ||\n parseColor(rowStyles.background);\n const padding = readPaddingFromStyles(rowStyles);\n\n sections.push(\n createSectionBlock({\n columns: layout,\n children: columnsBlocks,\n styles: {\n padding,\n margin: emptyMargin(),\n ...(bgColor ? { backgroundColor: bgColor } : {}),\n },\n }),\n );\n }\n\n return sections;\n}\n"],"mappings":"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","names":["createSectionBlock","createButtonBlock","createSpacerBlock","emptyMargin","emptyPadding","getStyles","createButtonBlock","createSpacerBlock","r","emptyMargin","emptyPadding","createSectionBlock","r"]}
|
|
1
|
+
{"version":3,"sources":["../src/converter.ts","../src/style-parser.ts","../src/css-resolver.ts","../src/block-mapper.ts","../src/section-builder.ts"],"sourcesContent":["import { load } from \"cheerio\";\nimport type { CheerioAPI, Cheerio } from \"cheerio\";\nimport type { Element } from \"domhandler\";\nimport {\n createDefaultTemplateContent,\n createSectionBlock,\n} from \"@templatical/types\";\nimport type { Block, TemplateContent } from \"@templatical/types\";\nimport { resolveCssStyles } from \"./css-resolver\";\nimport { convertElement } from \"./block-mapper\";\nimport { processTable } from \"./section-builder\";\nimport {\n parseColor,\n parseFontFamily,\n parsePxValue,\n parseStyleAttribute,\n} from \"./style-parser\";\nimport type { ImportReport, ImportReportEntry, ImportResult } from \"./types\";\n\nfunction emptyMargin() {\n return { top: 0, right: 0, bottom: 0, left: 0 };\n}\n\nfunction emptyPadding() {\n return { top: 0, right: 0, bottom: 0, left: 0 };\n}\n\nfunction readPreheader($: CheerioAPI): string | undefined {\n // Convention: a hidden <div> at the very top with `display:none` containing\n // the preheader text. Match if it appears in the first ~3 children of body.\n const candidates = $(\"body\")\n .children()\n .slice(0, 5)\n .filter((_, el) => {\n const styles = parseStyleAttribute($(el).attr(\"style\"));\n return (styles.display ?? \"\").toLowerCase() === \"none\";\n });\n if (candidates.length === 0) return undefined;\n const text = $(candidates[0]).text().trim();\n return text || undefined;\n}\n\nfunction extractSettings($: CheerioAPI): TemplateContent[\"settings\"] {\n const $body = $(\"body\");\n const bodyStyles = parseStyleAttribute($body.attr(\"style\"));\n const fontFamily = parseFontFamily(bodyStyles[\"font-family\"]) || \"Arial\";\n const backgroundColor =\n parseColor(bodyStyles[\"background-color\"]) ||\n parseColor(bodyStyles.background) ||\n \"#ffffff\";\n\n // Width heuristic: outermost table width attr, or \".container\" width style.\n const $outerTable = $body.find(\"table\").first();\n const widthAttr = parsePxValue($outerTable.attr(\"width\"));\n const widthStyle = parsePxValue(\n parseStyleAttribute($outerTable.attr(\"style\")).width,\n );\n const width = widthAttr || widthStyle || 600;\n\n const preheaderText = readPreheader($);\n\n return {\n width,\n backgroundColor,\n fontFamily,\n locale: \"en\",\n ...(preheaderText ? { preheaderText } : {}),\n };\n}\n\n/**\n * Wrap a list of free-floating blocks (those produced by top-level non-table\n * elements) in a single one-column section.\n */\nfunction wrapInSection(blocks: Block[]): Block {\n return createSectionBlock({\n columns: \"1\",\n children: [blocks],\n styles: {\n padding: emptyPadding(),\n margin: emptyMargin(),\n },\n });\n}\n\n/**\n * Walk top-level body children. Tables become sections; loose content\n * elements are accumulated and wrapped in a single one-column section.\n */\nfunction processBody(\n $: CheerioAPI,\n entries: ImportReportEntry[],\n warnings: string[],\n): Block[] {\n const blocks: Block[] = [];\n const $body = $(\"body\");\n const children = $body.children().toArray();\n\n let pendingLoose: Block[] = [];\n\n const flushLoose = () => {\n if (pendingLoose.length > 0) {\n blocks.push(wrapInSection(pendingLoose));\n pendingLoose = [];\n }\n };\n\n for (const childEl of children) {\n const tag = childEl.tagName?.toLowerCase() ?? \"\";\n const $child = $(childEl) as unknown as Cheerio<Element>;\n\n if (tag === \"table\") {\n flushLoose();\n blocks.push(...processTable($child, $, entries, warnings, false));\n continue;\n }\n\n // Skip hidden preheader divs — already captured in settings.\n const childStyles = parseStyleAttribute($child.attr(\"style\"));\n if ((childStyles.display ?? \"\").toLowerCase() === \"none\") continue;\n\n // Containers like a wrapping <div> with table children: recurse.\n if (\n (tag === \"div\" || tag === \"center\" || tag === \"main\") &&\n $child.find(\"table\").length > 0\n ) {\n flushLoose();\n $child.children().each((_, innerEl) => {\n const innerTag = innerEl.tagName?.toLowerCase() ?? \"\";\n const $inner = $(innerEl) as unknown as Cheerio<Element>;\n if (innerTag === \"table\") {\n blocks.push(...processTable($inner, $, entries, warnings, false));\n } else {\n const r = convertElement($inner, $);\n if (r) {\n entries.push(r.entry);\n pendingLoose.push(r.block);\n }\n }\n });\n flushLoose();\n continue;\n }\n\n const r = convertElement($child, $);\n if (r) {\n entries.push(r.entry);\n pendingLoose.push(r.block);\n }\n }\n\n flushLoose();\n return blocks;\n}\n\n/**\n * Converts an HTML email template to Templatical TemplateContent.\n *\n * Designed for table-based marketing email HTML (output of MJML, Mailchimp,\n * SendGrid, Campaign Monitor, hand-coded emails). Modern HTML using flex/grid\n * layouts is preserved via HTML-fallback blocks.\n *\n * @param html - The raw HTML string (full document or body fragment).\n * @returns An ImportResult with the converted content and a detailed report.\n *\n * @example\n * ```ts\n * import { convertHtmlTemplate } from '@templatical/import-html';\n *\n * const html = await fetch('/email.html').then((r) => r.text());\n * const { content, report } = convertHtmlTemplate(html);\n *\n * const editor = init({ container: '#editor', content });\n *\n * console.log(report.summary);\n * console.log(report.warnings);\n * ```\n */\nexport function convertHtmlTemplate(html: string): ImportResult {\n if (typeof html !== \"string\") {\n throw new Error(\n \"Invalid HTML template: expected a string. Pass the raw HTML source as a string.\",\n );\n }\n if (html.trim().length === 0) {\n throw new Error(\n \"Invalid HTML template: input is empty. Pass the raw HTML source of an email.\",\n );\n }\n\n const $ = load(html);\n resolveCssStyles($);\n\n // Drop tags that are never useful in the editor canvas.\n $(\"script, noscript, link, meta, title\").remove();\n\n const entries: ImportReportEntry[] = [];\n const warnings: string[] = [];\n\n const blocks = processBody($, entries, warnings);\n\n if (blocks.length === 0) {\n warnings.push(\n \"No convertible content was found in the HTML. The email may use a non-table layout — modern HTML support is limited.\",\n );\n }\n\n const content: TemplateContent = {\n ...createDefaultTemplateContent(),\n blocks,\n settings: extractSettings($),\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","import type { SpacingValue } from \"@templatical/types\";\n\n/**\n * Parses a CSS `style=\"...\"` attribute string into a flat key/value record.\n * Keys are lowercased; values are trimmed. Quotes around values are not stripped.\n */\nexport function parseStyleAttribute(\n styleAttr: string | undefined,\n): Record<string, string> {\n const result: Record<string, string> = {};\n if (!styleAttr) return result;\n\n for (const decl of styleAttr.split(\";\")) {\n const idx = decl.indexOf(\":\");\n if (idx === -1) continue;\n const key = decl.slice(0, idx).trim().toLowerCase();\n const value = decl.slice(idx + 1).trim();\n if (key && value) result[key] = value;\n }\n\n return result;\n}\n\n/**\n * Serializes a flat key/value record back to a `style` attribute string.\n */\nexport function serializeStyleAttribute(\n styles: Record<string, string>,\n): string {\n return Object.entries(styles)\n .map(([k, v]) => `${k}: ${v}`)\n .join(\"; \");\n}\n\n/**\n * Parses a px-like CSS value (`\"12px\"`, `\"12\"`, `12`) into a rounded integer.\n * Returns 0 for missing or unparseable input. Ignores em/% units.\n */\nexport function parsePxValue(value: string | number | undefined): number {\n if (value === undefined || value === null || value === \"\") return 0;\n if (typeof value === \"number\") return Math.round(value);\n const match = value.match(/^(-?\\d+(?:\\.\\d+)?)\\s*(?:px)?\\s*$/);\n return match ? Math.round(parseFloat(match[1])) : 0;\n}\n\n/**\n * Parses a width value that may be a percentage. Returns the numeric percent\n * (0-100). For non-percent values, returns 100.\n */\nexport function parseWidthPercent(value: string | undefined): number {\n if (!value) return 100;\n const match = value.match(/^(\\d+(?:\\.\\d+)?)\\s*%/);\n if (match) return Math.round(parseFloat(match[1]));\n return 100;\n}\n\nconst NAMED_COLORS: Record<string, string> = {\n black: \"#000000\",\n white: \"#ffffff\",\n red: \"#ff0000\",\n green: \"#008000\",\n blue: \"#0000ff\",\n yellow: \"#ffff00\",\n cyan: \"#00ffff\",\n magenta: \"#ff00ff\",\n gray: \"#808080\",\n grey: \"#808080\",\n silver: \"#c0c0c0\",\n maroon: \"#800000\",\n olive: \"#808000\",\n lime: \"#00ff00\",\n aqua: \"#00ffff\",\n teal: \"#008080\",\n navy: \"#000080\",\n fuchsia: \"#ff00ff\",\n purple: \"#800080\",\n orange: \"#ffa500\",\n pink: \"#ffc0cb\",\n};\n\nfunction rgbToHex(r: number, g: number, b: number): string {\n const clamp = (n: number) => Math.max(0, Math.min(255, Math.round(n)));\n const hex = (n: number) => clamp(n).toString(16).padStart(2, \"0\");\n return `#${hex(r)}${hex(g)}${hex(b)}`;\n}\n\n/**\n * Normalizes a CSS color value to a 6-digit lowercase hex string.\n * - 3-digit hex expands to 6-digit\n * - rgb()/rgba() converts to hex (alpha is dropped)\n * - Named colors map via lookup\n * - \"transparent\" / unknown returns \"\"\n */\nexport function parseColor(value: string | undefined): string {\n if (!value) return \"\";\n const trimmed = value.trim().toLowerCase();\n if (trimmed === \"transparent\" || trimmed === \"inherit\" || trimmed === \"none\")\n return \"\";\n\n if (/^#[0-9a-f]{6}$/.test(trimmed)) return trimmed;\n\n if (/^#[0-9a-f]{3}$/.test(trimmed)) {\n const r = trimmed[1];\n const g = trimmed[2];\n const b = trimmed[3];\n return `#${r}${r}${g}${g}${b}${b}`;\n }\n\n const rgbMatch = trimmed.match(\n /^rgba?\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*(?:,\\s*[\\d.]+\\s*)?\\)$/,\n );\n if (rgbMatch) {\n return rgbToHex(\n parseInt(rgbMatch[1], 10),\n parseInt(rgbMatch[2], 10),\n parseInt(rgbMatch[3], 10),\n );\n }\n\n if (NAMED_COLORS[trimmed]) return NAMED_COLORS[trimmed];\n\n return \"\";\n}\n\n/**\n * Parses a CSS `padding` shorthand (1-4 values) into a SpacingValue.\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\n/**\n * Reads CSS padding from a style record, preferring the longhand props\n * (padding-top/right/bottom/left) and falling back to the `padding` shorthand.\n */\nexport function readPaddingFromStyles(\n styles: Record<string, string>,\n): SpacingValue {\n const shorthand = parsePaddingShorthand(styles.padding);\n return {\n top: parsePxValue(styles[\"padding-top\"]) || shorthand.top,\n right: parsePxValue(styles[\"padding-right\"]) || shorthand.right,\n bottom: parsePxValue(styles[\"padding-bottom\"]) || shorthand.bottom,\n left: parsePxValue(styles[\"padding-left\"]) || shorthand.left,\n };\n}\n\n/**\n * Strips quotes and returns the first font in a font-family stack.\n */\nexport function parseFontFamily(value: string | undefined): string {\n if (!value) return \"\";\n return value\n .split(\",\")[0]\n .trim()\n .replace(/^['\"]|['\"]$/g, \"\");\n}\n\n/**\n * Normalizes a font-weight value to a string CSS keyword/number that\n * the editor accepts. Returns \"\" when the value is the default (normal/400).\n */\nexport function parseFontWeight(value: string | undefined): string {\n if (!value) return \"\";\n const trimmed = value.trim().toLowerCase();\n if (trimmed === \"normal\" || trimmed === \"400\") return \"\";\n return trimmed;\n}\n\n/**\n * Parses CSS text-align to one of the allowed editor alignments.\n */\nexport function parseAlignment(\n value: string | undefined,\n fallback: \"left\" | \"center\" | \"right\" = \"left\",\n): \"left\" | \"center\" | \"right\" {\n const v = (value ?? \"\").trim().toLowerCase();\n if (v === \"left\" || v === \"center\" || v === \"right\") return v;\n return fallback;\n}\n\n/**\n * Parses a CSS `border` shorthand (`\"1px solid #ccc\"`) into width/style/color.\n * Order-tolerant: each token is classified by content.\n */\nexport function parseBorderShorthand(value: string | undefined): {\n width: number;\n style: string;\n color: string;\n} {\n const fallback = { width: 0, style: \"solid\", color: \"#000000\" };\n if (!value) return fallback;\n\n const styleKeywords = new Set([\n \"none\",\n \"hidden\",\n \"dotted\",\n \"dashed\",\n \"solid\",\n \"double\",\n \"groove\",\n \"ridge\",\n \"inset\",\n \"outset\",\n ]);\n\n let width = 0;\n let style = \"solid\";\n let color = \"#000000\";\n\n for (const token of value.trim().split(/\\s+/)) {\n const lower = token.toLowerCase();\n if (styleKeywords.has(lower)) {\n style = lower;\n } else if (/^-?\\d+(?:\\.\\d+)?(?:px)?$/i.test(lower)) {\n width = parsePxValue(lower);\n } else {\n const c = parseColor(lower);\n if (c) color = c;\n }\n }\n\n return { width, style, color };\n}\n","import type { CheerioAPI } from \"cheerio\";\nimport { parseStyleAttribute, serializeStyleAttribute } from \"./style-parser\";\n\ninterface CssRule {\n selectors: string[];\n declarations: Record<string, string>;\n}\n\n/**\n * Strips all CSS comments. Handles nested-looking content safely.\n */\nfunction stripComments(css: string): string {\n return css.replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\");\n}\n\n/**\n * Strips at-rule blocks (@media, @font-face, @keyframes, @supports, etc.)\n * and their nested content. Leaves top-level rules in place.\n *\n * Email HTML rarely benefits from @media (we render at one viewport),\n * and resolving it onto elements would not be visually faithful anyway.\n */\nfunction stripAtRules(css: string): string {\n let result = \"\";\n let i = 0;\n while (i < css.length) {\n if (css[i] === \"@\") {\n // skip until matching `{...}` block or terminating `;`\n const semiIdx = css.indexOf(\";\", i);\n const braceIdx = css.indexOf(\"{\", i);\n\n if (braceIdx === -1 || (semiIdx !== -1 && semiIdx < braceIdx)) {\n i = semiIdx === -1 ? css.length : semiIdx + 1;\n continue;\n }\n\n // skip the entire `{...}` block, accounting for nesting\n let depth = 0;\n let j = braceIdx;\n for (; j < css.length; j++) {\n if (css[j] === \"{\") depth++;\n else if (css[j] === \"}\") {\n depth--;\n if (depth === 0) {\n j++;\n break;\n }\n }\n }\n i = j;\n } else {\n result += css[i];\n i++;\n }\n }\n return result;\n}\n\n/**\n * Parses a CSS declarations block (`color: red; font-size: 14px`) into\n * a flat record. `!important` markers are dropped.\n */\nfunction parseDeclarations(text: string): Record<string, string> {\n const result: Record<string, string> = {};\n for (const decl of text.split(\";\")) {\n const idx = decl.indexOf(\":\");\n if (idx === -1) continue;\n const key = decl.slice(0, idx).trim().toLowerCase();\n let value = decl.slice(idx + 1).trim();\n value = value.replace(/!important\\s*$/i, \"\").trim();\n if (key && value) result[key] = value;\n }\n return result;\n}\n\n/**\n * A selector is \"supported\" by cheerio's matcher if it has no pseudo-classes\n * or pseudo-elements. Resolving e.g. `a:hover` onto an inline style would be\n * wrong (it would always apply), so we skip such rules entirely.\n */\nfunction isSupportedSelector(selector: string): boolean {\n if (!selector) return false;\n if (selector.includes(\":\")) return false;\n if (selector.includes(\"@\")) return false;\n return true;\n}\n\n/**\n * Parses the full content of one or more `<style>` tags into a list of rules.\n * Skips at-rules and selectors with pseudo-classes.\n */\nexport function parseStyleSheet(css: string): CssRule[] {\n const rules: CssRule[] = [];\n const cleaned = stripAtRules(stripComments(css));\n\n // Greedily walk top-level `selectors { decls }` blocks.\n const blockRe = /([^{}]+)\\{([^{}]*)\\}/g;\n let match: RegExpExecArray | null;\n while ((match = blockRe.exec(cleaned)) !== null) {\n const selectorPart = match[1].trim();\n const declarationPart = match[2];\n if (!selectorPart) continue;\n\n const selectors = selectorPart\n .split(\",\")\n .map((s) => s.trim())\n .filter(isSupportedSelector);\n if (selectors.length === 0) continue;\n\n const declarations = parseDeclarations(declarationPart);\n if (Object.keys(declarations).length === 0) continue;\n\n rules.push({ selectors, declarations });\n }\n\n return rules;\n}\n\n/**\n * Reads all `<style>` tags from the document, parses them into rules,\n * applies each rule's declarations to matching elements (merging with\n * existing inline `style=\"\"` attributes — inline always wins), and removes\n * the `<style>` tags from the document.\n *\n * No specificity is computed; rules are applied in source order, with later\n * rules overriding earlier ones. Inline styles always override resolved\n * rules. This is sufficient for typical email HTML, where authors already\n * inline most styles.\n */\nexport function resolveCssStyles($: CheerioAPI): void {\n const styleTags = $(\"style\");\n if (styleTags.length === 0) return;\n\n const allRules: CssRule[] = [];\n styleTags.each((_, el) => {\n const css = $(el).text();\n if (css) allRules.push(...parseStyleSheet(css));\n });\n\n // First pass: capture each element's original inline styles, so we can\n // distinguish \"author wrote this inline\" from \"we just resolved a rule into it\".\n const inlineByEl = new WeakMap<object, Record<string, string>>();\n $(\"[style]\").each((_, el) => {\n inlineByEl.set(el as object, parseStyleAttribute($(el).attr(\"style\")));\n });\n\n // Second pass: apply rules in source order. Later rules override earlier\n // resolved ones; original inline always wins at the end.\n const resolvedByEl = new WeakMap<object, Record<string, string>>();\n\n for (const rule of allRules) {\n for (const selector of rule.selectors) {\n let matched: ReturnType<CheerioAPI>;\n try {\n matched = $(selector);\n } catch {\n // cheerio threw on an exotic selector — skip the rule.\n continue;\n }\n matched.each((_, el) => {\n const key = el as object;\n const current = resolvedByEl.get(key) ?? {};\n for (const [k, v] of Object.entries(rule.declarations)) {\n current[k] = v;\n }\n resolvedByEl.set(key, current);\n });\n }\n }\n\n // Third pass: merge resolved + original inline (inline wins) and write back.\n $(\"*\").each((_, el) => {\n const key = el as object;\n const resolved = resolvedByEl.get(key);\n if (!resolved) return;\n const inline = inlineByEl.get(key) ?? {};\n const merged: Record<string, string> = { ...resolved };\n for (const [k, v] of Object.entries(inline)) merged[k] = v;\n $(el).attr(\"style\", serializeStyleAttribute(merged));\n });\n\n styleTags.remove();\n}\n","import type { CheerioAPI, Cheerio } from \"cheerio\";\nimport type { Element, AnyNode } from \"domhandler\";\nimport {\n createTitleBlock,\n createParagraphBlock,\n createImageBlock,\n createButtonBlock,\n createDividerBlock,\n createSpacerBlock,\n createHtmlBlock,\n} from \"@templatical/types\";\nimport type { Block, HeadingLevel, SpacingValue } from \"@templatical/types\";\nimport type { ImportReportEntry } from \"./types\";\nimport {\n parseAlignment,\n parseBorderShorthand,\n parseColor,\n parseFontFamily,\n parseFontWeight,\n parsePxValue,\n parseStyleAttribute,\n readPaddingFromStyles,\n} from \"./style-parser\";\n\nconst HEADING_TAGS = new Set([\"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\"]);\nconst TEXT_TAGS = new Set([\"p\", \"span\", \"div\"]);\n\nfunction emptyMargin(): SpacingValue {\n return { top: 0, right: 0, bottom: 0, left: 0 };\n}\n\nfunction emptyPadding(): SpacingValue {\n return { top: 0, right: 0, bottom: 0, left: 0 };\n}\n\nfunction tagOf(el: Element | AnyNode): string {\n if (\"tagName\" in el && typeof el.tagName === \"string\")\n return el.tagName.toLowerCase();\n return \"\";\n}\n\nfunction getStyles($el: Cheerio<Element>): Record<string, string> {\n return parseStyleAttribute($el.attr(\"style\"));\n}\n\n/**\n * Returns the inner HTML of `$el`.\n */\nexport function getInnerHtml($el: Cheerio<Element>): string {\n return $el.html() ?? \"\";\n}\n\nfunction ensureParagraphWrapped(html: string): string {\n if (!html.trim()) return \"<p></p>\";\n if (/<(p|h[1-6]|ul|ol|blockquote)[\\s>]/i.test(html)) return html;\n return `<p>${html}</p>`;\n}\n\nfunction safeHtmlComment(message: string, raw: string): string {\n const escapedMessage = message\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n return `<!-- ${escapedMessage} -->\\n${raw}`;\n}\n\n/**\n * Heading element (h1-h6) → Title block.\n */\nfunction convertHeading($el: Cheerio<Element>): Block {\n const tag = tagOf($el[0]);\n const styles = getStyles($el);\n const levelMatch = tag.match(/^h(\\d)$/);\n const rawLevel = levelMatch ? Number(levelMatch[1]) : 2;\n const level: HeadingLevel = (\n rawLevel >= 1 && rawLevel <= 4 ? rawLevel : Math.min(rawLevel, 4)\n ) as HeadingLevel;\n\n const innerHtml = getInnerHtml($el);\n const content = innerHtml.trim() ? `<p>${innerHtml}</p>` : \"<p></p>\";\n\n return createTitleBlock({\n content,\n level,\n color: parseColor(styles.color) || \"#1a1a1a\",\n textAlign: parseAlignment(styles[\"text-align\"]),\n fontFamily: parseFontFamily(styles[\"font-family\"]) || undefined,\n styles: {\n padding: readPaddingFromStyles(styles),\n margin: emptyMargin(),\n },\n });\n}\n\n/**\n * Paragraph or block-level text container → Paragraph block.\n */\nfunction convertParagraph($el: Cheerio<Element>): Block {\n const styles = getStyles($el);\n const innerHtml = getInnerHtml($el);\n const wrapped = ensureParagraphWrapped(innerHtml);\n\n // Apply container-level styles to the wrapping <p>.\n const fontParts: string[] = [];\n const fontSize = parsePxValue(styles[\"font-size\"]);\n if (fontSize && fontSize !== 16) fontParts.push(`font-size: ${fontSize}px`);\n const color = parseColor(styles.color);\n if (color && color !== \"#1a1a1a\") fontParts.push(`color: ${color}`);\n const fontWeight = parseFontWeight(styles[\"font-weight\"]);\n if (fontWeight) fontParts.push(`font-weight: ${fontWeight}`);\n const fontFamily = parseFontFamily(styles[\"font-family\"]);\n if (fontFamily) fontParts.push(`font-family: ${fontFamily}`);\n const textAlign = styles[\"text-align\"];\n\n let result = wrapped;\n if (textAlign && textAlign !== \"left\") {\n result = result\n .replace(\n /<p style=\"([^\"]*)\">/g,\n `<p style=\"$1; text-align: ${textAlign}\">`,\n )\n .replaceAll(\"<p>\", `<p style=\"text-align: ${textAlign}\">`);\n }\n if (fontParts.length > 0) {\n const span = fontParts.join(\"; \");\n result = result.replace(\n /<p([^>]*)>([\\s\\S]*?)<\\/p>/g,\n `<p$1><span style=\"${span}\">$2</span></p>`,\n );\n }\n\n return createParagraphBlock({\n content: result,\n styles: {\n padding: readPaddingFromStyles(styles),\n margin: emptyMargin(),\n },\n });\n}\n\n/**\n * <img> → Image block.\n */\nfunction convertImage($el: Cheerio<Element>): Block {\n const styles = getStyles($el);\n const src = $el.attr(\"src\") ?? \"\";\n const alt = $el.attr(\"alt\") ?? \"\";\n const widthAttr = $el.attr(\"width\");\n const widthStyle = styles.width;\n const width = parsePxValue(widthAttr) || parsePxValue(widthStyle) || 600;\n\n return createImageBlock({\n src,\n alt,\n width,\n align: parseAlignment(styles[\"text-align\"], \"center\"),\n styles: {\n padding: readPaddingFromStyles(styles),\n margin: emptyMargin(),\n },\n });\n}\n\n/**\n * <a> styled as a button → Button block.\n *\n * Heuristic: a single `<a>` with a non-transparent background-color OR padding\n * OR border-radius OR display: inline-block / block is treated as a button.\n */\nexport function looksLikeButton(styles: Record<string, string>): boolean {\n if (parseColor(styles[\"background-color\"]) || parseColor(styles.background))\n return true;\n if (\n styles.padding ||\n styles[\"padding-top\"] ||\n styles[\"padding-bottom\"] ||\n styles[\"padding-left\"] ||\n styles[\"padding-right\"]\n )\n return true;\n if (parsePxValue(styles[\"border-radius\"])) return true;\n const display = (styles.display ?? \"\").toLowerCase();\n if (display === \"inline-block\" || display === \"block\") return true;\n return false;\n}\n\nfunction convertButton($el: Cheerio<Element>): Block {\n const styles = getStyles($el);\n const text = ($el.text() ?? \"Button\").trim() || \"Button\";\n const url = $el.attr(\"href\") ?? \"#\";\n const target = $el.attr(\"target\");\n\n return createButtonBlock({\n text,\n url,\n openInNewTab: target === \"_blank\" || undefined,\n backgroundColor:\n parseColor(styles[\"background-color\"]) ||\n parseColor(styles.background) ||\n \"#4f46e5\",\n textColor: parseColor(styles.color) || \"#ffffff\",\n borderRadius: parsePxValue(styles[\"border-radius\"]),\n fontSize: parsePxValue(styles[\"font-size\"]) || 16,\n fontFamily: parseFontFamily(styles[\"font-family\"]) || undefined,\n buttonPadding: readPaddingFromStyles(styles),\n styles: {\n padding: emptyPadding(),\n margin: emptyMargin(),\n },\n });\n}\n\n/**\n * <hr> → Divider block.\n */\nfunction convertDivider($el: Cheerio<Element>): Block {\n const styles = getStyles($el);\n const border = parseBorderShorthand(styles[\"border-top\"] ?? styles.border);\n const lineStyle =\n border.style === \"dashed\" || border.style === \"dotted\"\n ? border.style\n : \"solid\";\n\n return createDividerBlock({\n lineStyle: lineStyle as \"solid\" | \"dashed\" | \"dotted\",\n color: border.color || \"#e5e7eb\",\n thickness: border.width || 1,\n width: 100,\n styles: {\n padding: readPaddingFromStyles(styles),\n margin: emptyMargin(),\n },\n });\n}\n\n/**\n * Empty `<td>` with explicit height → Spacer block.\n */\nfunction convertSpacer($el: Cheerio<Element>): Block {\n const styles = getStyles($el);\n const heightAttr = $el.attr(\"height\");\n const height =\n parsePxValue(heightAttr) ||\n parsePxValue(styles.height) ||\n parsePxValue(styles[\"line-height\"]) ||\n 24;\n\n return createSpacerBlock({\n height,\n styles: {\n padding: emptyPadding(),\n margin: emptyMargin(),\n },\n });\n}\n\n/**\n * Wraps the element's outerHTML in an HTML block (the lossless fallback).\n */\nexport function convertHtmlFallback(\n $el: Cheerio<Element>,\n $: CheerioAPI,\n note?: string,\n): Block {\n const outer = $.html($el) ?? \"\";\n const content = note ? safeHtmlComment(note, outer) : outer;\n const styles = getStyles($el);\n\n return createHtmlBlock({\n content,\n styles: {\n padding: readPaddingFromStyles(styles),\n margin: emptyMargin(),\n },\n });\n}\n\n/**\n * Decides whether a `<td>` looks like a vertical spacer:\n * empty (or only ` `) AND has an explicit height.\n */\nexport function isSpacerCell($el: Cheerio<Element>): boolean {\n const text = ($el.text() ?? \"\").replace(/\\s| /g, \"\");\n if (text !== \"\") return false;\n if ($el.find(\"img, a, hr\").length > 0) return false;\n\n const styles = getStyles($el);\n const hasHeight =\n parsePxValue($el.attr(\"height\")) > 0 ||\n parsePxValue(styles.height) > 0 ||\n parsePxValue(styles[\"line-height\"]) > 0;\n return hasHeight;\n}\n\n/**\n * Decides whether a `<td>` is a button container — i.e. has exactly one\n * `<a>` inside that itself looks like a button.\n */\nexport function isButtonCell(\n $el: Cheerio<Element>,\n $: CheerioAPI,\n): { match: boolean; anchor?: Cheerio<Element> } {\n const anchors = $el.find(\"a\");\n if (anchors.length !== 1) return { match: false };\n const anchor = $(anchors[0]);\n if (looksLikeButton(getStyles(anchor))) return { match: true, anchor };\n // Cell-level styling (bg, padding) wrapping a plain anchor also reads as button.\n if (looksLikeButton(getStyles($el))) return { match: true, anchor };\n return { match: false };\n}\n\n/**\n * Converts a single content-bearing element (heading / paragraph / image /\n * anchor-as-button / divider) to a Templatical block.\n *\n * Returns `null` for elements that do not contain any meaningful content\n * (the caller should skip them).\n */\nexport function convertElement(\n $el: Cheerio<Element>,\n $: CheerioAPI,\n): { block: Block; entry: ImportReportEntry } | null {\n const tag = tagOf($el[0]);\n if (!tag) return null;\n\n if (HEADING_TAGS.has(tag)) {\n return {\n block: convertHeading($el),\n entry: {\n sourceTag: tag,\n templaticalBlockType: \"title\",\n status: \"converted\",\n },\n };\n }\n\n if (tag === \"img\") {\n return {\n block: convertImage($el),\n entry: {\n sourceTag: tag,\n templaticalBlockType: \"image\",\n status: \"converted\",\n },\n };\n }\n\n if (tag === \"a\") {\n if (looksLikeButton(getStyles($el))) {\n return {\n block: convertButton($el),\n entry: {\n sourceTag: tag,\n templaticalBlockType: \"button\",\n status: \"converted\",\n },\n };\n }\n // Plain anchor — wrap as paragraph.\n return {\n block: convertParagraph($el),\n entry: {\n sourceTag: tag,\n templaticalBlockType: \"paragraph\",\n status: \"approximated\",\n note: \"Inline anchor wrapped in a paragraph block.\",\n },\n };\n }\n\n if (tag === \"hr\") {\n return {\n block: convertDivider($el),\n entry: {\n sourceTag: tag,\n templaticalBlockType: \"divider\",\n status: \"converted\",\n },\n };\n }\n\n if (TEXT_TAGS.has(tag)) {\n const text = ($el.text() ?? \"\").trim();\n if (!text && $el.find(\"img, a\").length === 0) return null;\n return {\n block: convertParagraph($el),\n entry: {\n sourceTag: tag,\n templaticalBlockType: \"paragraph\",\n status: \"converted\",\n },\n };\n }\n\n // Unknown element — preserve as HTML.\n return {\n block: convertHtmlFallback(\n $el,\n $,\n `Unsupported element <${tag}>: preserved as raw HTML`,\n ),\n entry: {\n sourceTag: tag,\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n note: `Unknown element \"${tag}\" preserved as HTML block.`,\n },\n };\n}\n\n/**\n * Helpers exported for tests.\n */\nexport const _internal = {\n convertButton,\n convertDivider,\n convertHeading,\n convertImage,\n convertParagraph,\n convertSpacer,\n ensureParagraphWrapped,\n};\n","import type { CheerioAPI, Cheerio } from \"cheerio\";\nimport type { Element } from \"domhandler\";\nimport {\n createSectionBlock,\n createButtonBlock,\n createSpacerBlock,\n} from \"@templatical/types\";\nimport type { Block, ColumnLayout } from \"@templatical/types\";\nimport {\n convertElement,\n convertHtmlFallback,\n isButtonCell,\n isSpacerCell,\n looksLikeButton,\n} from \"./block-mapper\";\nimport {\n parseColor,\n parsePxValue,\n parseStyleAttribute,\n readPaddingFromStyles,\n} from \"./style-parser\";\nimport type { ImportReportEntry } from \"./types\";\n\nfunction emptyMargin() {\n return { top: 0, right: 0, bottom: 0, left: 0 };\n}\n\nfunction emptyPadding() {\n return { top: 0, right: 0, bottom: 0, left: 0 };\n}\n\nfunction getStyles($el: Cheerio<Element>): Record<string, string> {\n return parseStyleAttribute($el.attr(\"style\"));\n}\n\nfunction buildCellButton(\n $cell: Cheerio<Element>,\n $anchor: Cheerio<Element>,\n): Block {\n const cellStyles = getStyles($cell);\n const aStyles = getStyles($anchor);\n // Anchor styles win when they overlap (typical: anchor sets text color, cell sets bg).\n const merged = { ...cellStyles, ...aStyles };\n const text = ($anchor.text() ?? \"Button\").trim() || \"Button\";\n const url = $anchor.attr(\"href\") ?? \"#\";\n const target = $anchor.attr(\"target\");\n\n return createButtonBlock({\n text,\n url,\n openInNewTab: target === \"_blank\" || undefined,\n backgroundColor:\n parseColor(merged[\"background-color\"]) ||\n parseColor(merged.background) ||\n \"#4f46e5\",\n textColor: parseColor(merged.color) || \"#ffffff\",\n borderRadius: parsePxValue(merged[\"border-radius\"]),\n fontSize: parsePxValue(merged[\"font-size\"]) || 16,\n buttonPadding: readPaddingFromStyles(merged),\n styles: {\n padding: emptyPadding(),\n margin: emptyMargin(),\n },\n });\n}\n\nfunction buildSpacerFromCell($cell: Cheerio<Element>): Block {\n const cellStyles = getStyles($cell);\n const height =\n parsePxValue($cell.attr(\"height\")) ||\n parsePxValue(cellStyles.height) ||\n parsePxValue(cellStyles[\"line-height\"]) ||\n 24;\n return createSpacerBlock({\n height,\n styles: {\n padding: emptyPadding(),\n margin: emptyMargin(),\n },\n });\n}\n\n/**\n * Returns the direct child `<tr>` rows of a table, including those one level\n * inside `<thead>`, `<tbody>`, or `<tfoot>` (which the HTML parser inserts\n * automatically).\n */\nfunction getDirectRows(\n $table: Cheerio<Element>,\n $: CheerioAPI,\n): Cheerio<Element>[] {\n const rows: Cheerio<Element>[] = [];\n $table.children(\"tr\").each((_, el) => {\n rows.push($(el) as unknown as Cheerio<Element>);\n });\n $table.children(\"thead, tbody, tfoot\").each((_, group) => {\n $(group)\n .children(\"tr\")\n .each((_i, el) => {\n rows.push($(el) as unknown as Cheerio<Element>);\n });\n });\n return rows;\n}\n\nfunction getDirectCells(\n $row: Cheerio<Element>,\n $: CheerioAPI,\n): Cheerio<Element>[] {\n const cells: Cheerio<Element>[] = [];\n $row.children(\"td, th\").each((_, el) => {\n cells.push($(el) as unknown as Cheerio<Element>);\n });\n return cells;\n}\n\nfunction isLayoutTable($table: Cheerio<Element>, $: CheerioAPI): boolean {\n // A table is \"layout\" if any descendant carries content email blocks rely on,\n // OR if any cell contains a non-text element (custom tags, semantic blocks,\n // etc. — those should be preserved as html-fallback at the element level\n // rather than collapsing the entire table into one html block).\n // A bare data table (cells contain only text) is preserved as HTML.\n if (\n $table.find(\n \"img, a, h1, h2, h3, h4, h5, h6, table, hr, p, div, span, ul, ol, li, blockquote, video, iframe\",\n ).length > 0\n )\n return true;\n\n let hasNonStandardChild = false;\n $table.find(\"td, th\").each((_, td) => {\n if (hasNonStandardChild) return;\n if ($(td).children().length > 0) hasNonStandardChild = true;\n });\n return hasNonStandardChild;\n}\n\nfunction resolveColumnLayout(\n cellCount: number,\n warnings: string[],\n): ColumnLayout {\n if (cellCount <= 1) return \"1\";\n if (cellCount === 2) return \"2\";\n if (cellCount === 3) return \"3\";\n warnings.push(\n `Row with ${cellCount} columns was flattened to a single column. Templatical supports up to 3 columns per section.`,\n );\n return \"1\";\n}\n\nfunction extractCellBlocks(\n $cell: Cheerio<Element>,\n $: CheerioAPI,\n entries: ImportReportEntry[],\n warnings: string[],\n): Block[] {\n if (isSpacerCell($cell)) {\n entries.push({\n sourceTag: \"td\",\n templaticalBlockType: \"spacer\",\n status: \"converted\",\n });\n return [buildSpacerFromCell($cell)];\n }\n\n const btn = isButtonCell($cell, $);\n if (btn.match && btn.anchor) {\n entries.push({\n sourceTag: \"td\",\n templaticalBlockType: \"button\",\n status: \"converted\",\n });\n return [buildCellButton($cell, btn.anchor)];\n }\n\n const blocks: Block[] = [];\n const childEls = $cell.children().toArray();\n\n if (childEls.length === 0) {\n const text = ($cell.text() ?? \"\").trim();\n if (!text) return [];\n const r = convertElement($cell, $);\n if (r) {\n entries.push(r.entry);\n blocks.push(r.block);\n }\n return blocks;\n }\n\n for (const childEl of childEls) {\n const $child = $(childEl) as unknown as Cheerio<Element>;\n const tag = childEl.tagName?.toLowerCase() ?? \"\";\n\n if (tag === \"table\") {\n const inner = processTable($child, $, entries, warnings, true);\n blocks.push(...inner);\n continue;\n }\n\n if (tag === \"a\" && looksLikeButton(getStyles($child))) {\n const r = convertElement($child, $);\n if (r) {\n entries.push(r.entry);\n blocks.push(r.block);\n }\n continue;\n }\n\n const r = convertElement($child, $);\n if (r) {\n entries.push(r.entry);\n blocks.push(r.block);\n }\n }\n\n return blocks;\n}\n\n/**\n * Walk a `<table>` and produce Section blocks (one per row).\n *\n * @param flattenInline - When true (used for nested tables), drop the section\n * wrapper and return the flat block list. Templatical sections cannot nest,\n * so nested layout-tables are merged into their parent cell.\n */\nexport function processTable(\n $table: Cheerio<Element>,\n $: CheerioAPI,\n entries: ImportReportEntry[],\n warnings: string[],\n flattenInline = false,\n): Block[] {\n if (!isLayoutTable($table, $)) {\n entries.push({\n sourceTag: \"table\",\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n note: \"Data table preserved as HTML block.\",\n });\n return [convertHtmlFallback($table, $, \"Data table preserved as HTML\")];\n }\n\n const rows = getDirectRows($table, $);\n if (rows.length === 0) return [];\n\n const sections: Block[] = [];\n\n for (const $row of rows) {\n const cells = getDirectCells($row, $);\n if (cells.length === 0) continue;\n\n const layout = resolveColumnLayout(cells.length, warnings);\n\n let columnsBlocks: Block[][];\n if (layout === \"1\") {\n const merged: Block[] = [];\n for (const $cell of cells) {\n merged.push(...extractCellBlocks($cell, $, entries, warnings));\n }\n columnsBlocks = [merged];\n } else {\n columnsBlocks = cells.map(($cell) =>\n extractCellBlocks($cell, $, entries, warnings),\n );\n }\n\n if (flattenInline) {\n for (const col of columnsBlocks) sections.push(...col);\n continue;\n }\n\n const rowStyles = getStyles($row);\n const bgColor =\n parseColor(rowStyles[\"background-color\"]) ||\n parseColor(rowStyles.background);\n const padding = readPaddingFromStyles(rowStyles);\n\n sections.push(\n createSectionBlock({\n columns: layout,\n children: columnsBlocks,\n styles: {\n padding,\n margin: emptyMargin(),\n ...(bgColor ? { backgroundColor: bgColor } : {}),\n },\n }),\n );\n }\n\n return sections;\n}\n"],"mappings":"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","names":["createSectionBlock","createButtonBlock","createSpacerBlock","emptyMargin","emptyPadding","getStyles","createButtonBlock","createSpacerBlock","r","emptyMargin","emptyPadding","createSectionBlock","r"]}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@templatical/import-html",
|
|
3
3
|
"description": "Convert HTML email templates to Templatical format",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.1",
|
|
5
5
|
"bugs": "https://github.com/templatical/sdk/issues",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"cheerio": "^1.0.0",
|
|
8
8
|
"domhandler": "^5.0.3",
|
|
9
|
-
"@templatical/types": "0.
|
|
9
|
+
"@templatical/types": "0.6.1"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"tsup": "^8.5.1",
|