@templatical/import-beefree 0.29.1 → 0.30.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.js +31 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -8,6 +8,32 @@ function parsePxValue(value) {
|
|
|
8
8
|
const match = value.match(/^(-?\d+(?:\.\d+)?)\s*px/);
|
|
9
9
|
return match ? Math.round(parseFloat(match[1])) : 0;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* An image's corner radius in px, or `undefined` for square corners.
|
|
13
|
+
*
|
|
14
|
+
* Accepts a percentage as well as a px length, because `border-radius: 50%` is
|
|
15
|
+
* the idiomatic way to write a circular avatar and the block stores px only. A
|
|
16
|
+
* percentage resolves against the shorter rendered side, which is what turns a
|
|
17
|
+
* square image into a circle and a wide one into a pill.
|
|
18
|
+
*
|
|
19
|
+
* `width` and `height` must be the dimensions actually read from the source. A
|
|
20
|
+
* percentage with neither known is dropped rather than resolved against the
|
|
21
|
+
* caller's fallback width, which would invent a radius the template never
|
|
22
|
+
* expressed. A non-positive result is dropped for the same reason a px `0` is:
|
|
23
|
+
* both mean square, and only absence keeps it out of the exported JSON.
|
|
24
|
+
*/
|
|
25
|
+
function parseImageBorderRadius(value, width, height) {
|
|
26
|
+
if (!value) return void 0;
|
|
27
|
+
const percent = value.trim().match(/^(\d+(?:\.\d+)?)\s*%$/);
|
|
28
|
+
if (percent) {
|
|
29
|
+
const side = Math.min(width ?? Infinity, height ?? Infinity);
|
|
30
|
+
if (!Number.isFinite(side)) return void 0;
|
|
31
|
+
const radius = Math.round(parseFloat(percent[1]) / 100 * side);
|
|
32
|
+
return radius > 0 ? radius : void 0;
|
|
33
|
+
}
|
|
34
|
+
const px = parsePxValue(value);
|
|
35
|
+
return px > 0 ? px : void 0;
|
|
36
|
+
}
|
|
11
37
|
function parseColor(value) {
|
|
12
38
|
if (!value || value === "transparent") return "";
|
|
13
39
|
const trimmed = value.trim();
|
|
@@ -270,11 +296,14 @@ function convertHeading(descriptor) {
|
|
|
270
296
|
function convertImage(descriptor) {
|
|
271
297
|
const image = descriptor.image;
|
|
272
298
|
if (!image) return createImageBlock({ styles: makeStyles(descriptor) });
|
|
299
|
+
const readWidth = parsePxValue(image.width) || void 0;
|
|
300
|
+
const readHeight = parsePxValue(image.height) || void 0;
|
|
273
301
|
return createImageBlock({
|
|
274
302
|
src: image.src || "",
|
|
275
303
|
alt: image.alt || "",
|
|
276
|
-
width:
|
|
277
|
-
height:
|
|
304
|
+
width: readWidth ?? 600,
|
|
305
|
+
height: readHeight,
|
|
306
|
+
borderRadius: parseImageBorderRadius(image.style?.["border-radius"], readWidth, readHeight),
|
|
278
307
|
align: toAlign(image.style?.["text-align"], "center"),
|
|
279
308
|
linkUrl: image.href || void 0,
|
|
280
309
|
styles: makeStyles(descriptor)
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/style-parser.ts","../src/block-mapper.ts","../src/converter.ts"],"sourcesContent":["import type { SpacingValue } from \"@templatical/types\";\n\n/**\n * Parses CSS-like style values from BeeFree descriptors.\n */\n\nexport function parsePxValue(value: string | undefined): number {\n if (!value) return 0;\n const match = value.match(/^(-?\\d+(?:\\.\\d+)?)\\s*px/);\n return match ? Math.round(parseFloat(match[1])) : 0;\n}\n\nexport function parseColor(value: string | undefined): string {\n if (!value || value === \"transparent\") return \"\";\n\n const trimmed = value.trim();\n\n // Already a valid hex color\n if (/^#[0-9a-fA-F]{6}$/.test(trimmed)) return trimmed.toLowerCase();\n\n // 3-digit hex → 6-digit\n if (/^#[0-9a-fA-F]{3}$/.test(trimmed)) {\n const r = trimmed[1];\n const g = trimmed[2];\n const b = trimmed[3];\n return `#${r}${r}${g}${g}${b}${b}`.toLowerCase();\n }\n\n // Return as-is for rgb(), named colors, etc.\n return trimmed;\n}\n\nexport function parseBorderTop(value: string | undefined): {\n width: number;\n style: string;\n color: string;\n} {\n if (!value) return { width: 0, style: \"solid\", color: \"#000000\" };\n\n // \"2px solid #cccccc\"\n const parts = value.trim().split(/\\s+/);\n return {\n width: parsePxValue(parts[0]),\n style: parts[1] || \"solid\",\n color: parseColor(parts[2]) || \"#000000\",\n };\n}\n\nexport function extractPadding(\n style: Record<string, string> | undefined,\n): SpacingValue {\n if (!style) return { top: 0, right: 0, bottom: 0, left: 0 };\n\n // Check for shorthand `padding` first\n if (style.padding) {\n return parseShorthandPadding(style.padding);\n }\n\n return {\n top: parsePxValue(style[\"padding-top\"]),\n right: parsePxValue(style[\"padding-right\"]),\n bottom: parsePxValue(style[\"padding-bottom\"]),\n left: parsePxValue(style[\"padding-left\"]),\n };\n}\n\nfunction parseShorthandPadding(value: string): SpacingValue {\n const parts = value.trim().split(/\\s+/);\n const values = parts.map((p) => parsePxValue(p));\n\n switch (values.length) {\n case 1:\n return {\n top: values[0],\n right: values[0],\n bottom: values[0],\n left: values[0],\n };\n case 2:\n return {\n top: values[0],\n right: values[1],\n bottom: values[0],\n left: values[1],\n };\n case 3:\n return {\n top: values[0],\n right: values[1],\n bottom: values[2],\n left: values[1],\n };\n default:\n return {\n top: values[0],\n right: values[1],\n bottom: values[2],\n left: values[3],\n };\n }\n}\n\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 // Might be px — return 100 as default\n return 100;\n}\n\nexport function parseFontFamily(value: string | undefined): string {\n if (!value) return \"\";\n // Take the first font in the stack\n return value.split(\",\")[0].trim().replace(/['\"]/g, \"\");\n}\n","import {\n createTitleBlock,\n createParagraphBlock,\n createImageBlock,\n createButtonBlock,\n createDividerBlock,\n createSpacerBlock,\n createHtmlBlock,\n createSocialIconsBlock,\n createMenuBlock,\n createTableBlock,\n createVideoBlock,\n generateId,\n} from \"@templatical/types\";\nimport type {\n Block,\n HeadingLevel,\n SocialPlatform,\n SocialIcon,\n MenuItemData,\n TableRowData,\n TableCellData,\n} from \"@templatical/types\";\nimport type {\n BeeFreeeModule,\n BeeFreeeModuleDescriptor,\n ImportReportEntry,\n} from \"./types\";\nimport {\n parsePxValue,\n parseColor,\n parseBorderTop,\n extractPadding,\n parseWidthPercent,\n parseFontFamily,\n} from \"./style-parser\";\n\n/**\n * Maps BeeFree module type strings to short keys.\n */\nconst MODULE_TYPE_MAP: Record<string, string> = {\n \"mailup-bee-newsletter-modules-text\": \"paragraph\",\n \"mailup-bee-newsletter-modules-paragraph\": \"paragraph\",\n \"mailup-bee-newsletter-modules-heading\": \"title\",\n \"mailup-bee-newsletter-modules-list\": \"list\",\n \"mailup-bee-newsletter-modules-image\": \"image\",\n \"mailup-bee-newsletter-modules-button\": \"button\",\n \"mailup-bee-newsletter-modules-divider\": \"divider\",\n \"mailup-bee-newsletter-modules-spacer\": \"spacer\",\n \"mailup-bee-newsletter-modules-html\": \"html\",\n \"mailup-bee-newsletter-modules-social\": \"social\",\n \"mailup-bee-newsletter-modules-video\": \"video\",\n \"mailup-bee-newsletter-modules-menu\": \"menu\",\n \"mailup-bee-newsletter-modules-table\": \"table\",\n};\n\nconst SOCIAL_PLATFORM_MAP: Record<string, SocialPlatform> = {\n facebook: \"facebook\",\n twitter: \"twitter\",\n x: \"twitter\",\n instagram: \"instagram\",\n linkedin: \"linkedin\",\n youtube: \"youtube\",\n tiktok: \"tiktok\",\n pinterest: \"pinterest\",\n email: \"email\",\n whatsapp: \"whatsapp\",\n telegram: \"telegram\",\n discord: \"discord\",\n snapchat: \"snapchat\",\n reddit: \"reddit\",\n github: \"github\",\n dribbble: \"dribbble\",\n behance: \"behance\",\n};\n\ntype Align = \"left\" | \"center\" | \"right\";\ntype LineStyle = \"solid\" | \"dashed\" | \"dotted\";\n\nfunction toAlign(value: string | undefined, fallback: Align = \"left\"): Align {\n if (value === \"left\" || value === \"center\" || value === \"right\") return value;\n return fallback;\n}\n\nfunction toLineStyle(\n value: string | undefined,\n fallback: LineStyle = \"solid\",\n): LineStyle {\n if (value === \"solid\" || value === \"dashed\" || value === \"dotted\")\n return value;\n return fallback;\n}\n\n/**\n * Strip every `<…>` from a button label and reject any stray `<` or `>`\n * left behind (e.g. a truncated `<script`). The original\n * `value.replace(/<[^>]*>/g, \"\")` was both polynomial-ReDoS over\n * `<<<<…` inputs and an incomplete sanitizer — an unterminated `<script`\n * would survive the strip. Downstream HTML-escapes the label at render\n * time, but stripping here keeps the imported JSON clean.\n */\nfunction stripTagsPlain(text: string): string {\n let out = \"\";\n let i = 0;\n while (i < text.length) {\n if (text[i] === \"<\") {\n const close = text.indexOf(\">\", i + 1);\n if (close === -1) {\n // Unterminated tag — discard the rest.\n break;\n }\n i = close + 1;\n continue;\n }\n if (text[i] === \">\") {\n i++;\n continue;\n }\n out += text[i];\n i++;\n }\n return out;\n}\n\nfunction makeStyles(descriptor: BeeFreeeModuleDescriptor): Block[\"styles\"] {\n const padding = extractPadding(descriptor.style);\n const bg = parseColor(descriptor.style?.[\"background-color\"]);\n return {\n padding,\n ...(bg ? { backgroundColor: bg } : {}),\n };\n}\n\n/**\n * Apply BeeFree text styles as TipTap-compatible inline markup.\n * - text-align → added to each <p> tag's style attribute\n * - color, font-size, font-weight, font-family → wrapped in <span style=\"...\"> inside each <p>\n */\nfunction inlineStylesToHtml(\n html: string,\n style: Record<string, string | undefined>,\n): string {\n const spanParts: string[] = [];\n const fontSize = parsePxValue(style[\"font-size\"]);\n if (fontSize && fontSize !== 16) spanParts.push(`font-size: ${fontSize}px`);\n const color = parseColor(style.color);\n if (color && color !== \"#1a1a1a\") spanParts.push(`color: ${color}`);\n const fontWeight = style[\"font-weight\"];\n // \"400\" is the numeric synonym for \"normal\" — neither needs an explicit\n // span (matches the import-unlayer importer).\n if (fontWeight && fontWeight !== \"normal\" && fontWeight !== \"400\")\n spanParts.push(`font-weight: ${fontWeight}`);\n const fontFamily = parseFontFamily(style[\"font-family\"]);\n if (fontFamily) spanParts.push(`font-family: ${fontFamily}`);\n\n const textAlign = style[\"text-align\"];\n const pStyle =\n textAlign && textAlign !== \"left\" ? `text-align: ${textAlign}` : \"\";\n\n if (!pStyle && spanParts.length === 0) return html;\n\n const spanStyle = spanParts.join(\"; \");\n\n // Apply styles to each <p> tag in the HTML\n let result = html;\n\n if (pStyle) {\n // Add text-align to existing <p style=\"...\"> or add style to plain <p>\n result = result\n .replace(/<p style=\"([^\"]*)\">/g, `<p style=\"$1; ${pStyle}\">`)\n .replaceAll(\"<p>\", `<p style=\"${pStyle}\">`);\n }\n\n if (spanStyle) {\n // Wrap inner content of each <p> in a styled span.\n result = wrapParagraphInner(result, spanStyle);\n }\n\n return result;\n}\n\n/**\n * Wrap the inner content of every `<p …>…</p>` with `<span style=\"…\">…</span>`.\n *\n * Hand-rolled linear scanner instead of `/<p([^>]*)>([\\s\\S]*?)<\\/p>/g` because\n * the regex is polynomial-ReDoS: the engine retries `[\\s\\S]*?<\\/p>` at every\n * `<p` start, so inputs like `<p>a<p>a<p>a…` (no `</p>` ever) cost O(n²).\n * The scanner advances `i` monotonically via `indexOf`, keeping the work\n * strictly O(n).\n */\nfunction wrapParagraphInner(html: string, spanStyle: string): string {\n let out = \"\";\n let i = 0;\n while (i < html.length) {\n const open = html.indexOf(\"<p\", i);\n if (open === -1) {\n out += html.substring(i);\n break;\n }\n const afterTagName = html[open + 2];\n if (\n afterTagName !== \">\" &&\n afterTagName !== \" \" &&\n afterTagName !== \"\\t\" &&\n afterTagName !== \"\\n\" &&\n afterTagName !== \"\\r\" &&\n afterTagName !== \"/\"\n ) {\n out += html.substring(i, open + 2);\n i = open + 2;\n continue;\n }\n const openEnd = html.indexOf(\">\", open + 2);\n if (openEnd === -1) {\n out += html.substring(i);\n break;\n }\n const closeStart = html.indexOf(\"</p>\", openEnd + 1);\n if (closeStart === -1) {\n out += html.substring(i);\n break;\n }\n const inner = html.substring(openEnd + 1, closeStart);\n out += html.substring(i, openEnd + 1);\n out += `<span style=\"${spanStyle}\">${inner}</span></p>`;\n i = closeStart + 4;\n }\n return out;\n}\n\nfunction convertText(descriptor: BeeFreeeModuleDescriptor): Block {\n const textContent =\n descriptor.text ?? descriptor.paragraph ?? descriptor.list;\n const html = textContent?.html ?? \"\";\n const style = textContent?.style ?? {};\n\n return createParagraphBlock({\n content: inlineStylesToHtml(html, style),\n styles: makeStyles(descriptor),\n });\n}\n\nfunction parseHeadingLevel(tag: string): HeadingLevel {\n const match = tag.match(/^h(\\d)$/i);\n if (match) {\n const num = Number(match[1]);\n if (num >= 1 && num <= 4) return num as HeadingLevel;\n }\n return 2;\n}\n\nfunction convertHeading(descriptor: BeeFreeeModuleDescriptor): Block {\n const heading = descriptor.heading;\n if (!heading) return convertText(descriptor);\n\n const style = heading.style ?? {};\n const tag = heading.title ?? \"h2\";\n const text = heading.text ?? \"\";\n // Strip heading tags — content goes inside TipTap, heading tag is rendered by the block\n const content = text.startsWith(\"<\")\n ? text.replace(/^<h\\d[^>]*>|<\\/h\\d>$/gi, \"\")\n : text;\n\n return createTitleBlock({\n content: content ? `<p>${content}</p>` : \"<p></p>\",\n level: parseHeadingLevel(tag),\n color: parseColor(style.color) || \"#1a1a1a\",\n textAlign: toAlign(style[\"text-align\"]),\n fontFamily: parseFontFamily(style[\"font-family\"]) || undefined,\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertImage(descriptor: BeeFreeeModuleDescriptor): Block {\n const image = descriptor.image;\n if (!image) {\n return createImageBlock({ styles: makeStyles(descriptor) });\n }\n\n return createImageBlock({\n src: image.src || \"\",\n alt: image.alt || \"\",\n width: parsePxValue(image.width) || 600,\n // No default, unlike width: an absent height is what keeps the aspect\n // ratio, and BeeFree's \"auto\" parses to 0, which reads the same way.\n height: parsePxValue(image.height) || undefined,\n align: toAlign(image.style?.[\"text-align\"], \"center\"),\n linkUrl: image.href || undefined,\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertButton(descriptor: BeeFreeeModuleDescriptor): Block {\n const button = descriptor.button;\n if (!button) {\n return createButtonBlock({ styles: makeStyles(descriptor) });\n }\n\n const style = button.style ?? {};\n const label = button.label ? stripTagsPlain(button.label) : \"Button\";\n\n return createButtonBlock({\n text: label,\n url: button.href || \"#\",\n backgroundColor: parseColor(style[\"background-color\"]) || \"#4f46e5\",\n textColor: parseColor(style.color) || \"#ffffff\",\n borderRadius: parsePxValue(style[\"border-radius\"]),\n fontSize: parsePxValue(style[\"font-size\"]) || 16,\n fontFamily: parseFontFamily(style[\"font-family\"]) || undefined,\n align: toAlign(style[\"text-align\"], \"center\"),\n buttonPadding: {\n top: parsePxValue(style[\"padding-top\"]) || 12,\n right: parsePxValue(style[\"padding-right\"]) || 24,\n bottom: parsePxValue(style[\"padding-bottom\"]) || 12,\n left: parsePxValue(style[\"padding-left\"]) || 24,\n },\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertDivider(descriptor: BeeFreeeModuleDescriptor): Block {\n const divider = descriptor.divider;\n const style = divider?.style ?? {};\n const border = parseBorderTop(style[\"border-top\"]);\n\n return createDividerBlock({\n lineStyle: toLineStyle(border.style),\n color: border.color,\n thickness: border.width || 1,\n width: parseWidthPercent(style.width),\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertSpacer(descriptor: BeeFreeeModuleDescriptor): Block {\n const spacer = descriptor.spacer;\n const height = parsePxValue(spacer?.style?.height) || 24;\n\n return createSpacerBlock({\n height,\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertHtml(descriptor: BeeFreeeModuleDescriptor): Block {\n const html = descriptor.html?.html ?? \"\";\n\n return createHtmlBlock({\n content: html,\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertSocial(\n descriptor: BeeFreeeModuleDescriptor,\n warnings: string[],\n): Block {\n const iconsList = descriptor.iconsList;\n if (!iconsList?.icons) {\n return createSocialIconsBlock({ styles: makeStyles(descriptor) });\n }\n\n const icons: SocialIcon[] = [];\n\n for (const beeIcon of iconsList.icons) {\n const id = (beeIcon.id ?? beeIcon.name ?? \"\").toLowerCase();\n const platform = SOCIAL_PLATFORM_MAP[id];\n\n if (!platform) {\n warnings.push(\n `Unrecognized social icon \"${beeIcon.name || id}\" was skipped.`,\n );\n continue;\n }\n\n icons.push({\n id: generateId(),\n platform,\n url: beeIcon.image?.href || \"#\",\n });\n }\n\n return createSocialIconsBlock({\n icons,\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertVideo(descriptor: BeeFreeeModuleDescriptor): Block {\n const video = descriptor.video;\n if (!video) {\n return createVideoBlock({ styles: makeStyles(descriptor) });\n }\n\n return createVideoBlock({\n url: video.src || \"\",\n thumbnailUrl: video.thumbnail || \"\",\n alt: video.alt || \"\",\n width: parsePxValue(video.style?.width) || 600,\n height: parsePxValue(video.style?.height) || undefined,\n align: toAlign(video.style?.[\"text-align\"], \"center\"),\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertMenu(descriptor: BeeFreeeModuleDescriptor): Block {\n const menu = descriptor.menu;\n if (!menu) {\n return createMenuBlock({ styles: makeStyles(descriptor) });\n }\n\n const style = menu.style ?? {};\n\n const items: MenuItemData[] = (menu.items ?? []).map((item) => ({\n id: generateId(),\n text: item.text || \"\",\n url: item.link || item.href || \"#\",\n openInNewTab: item.target === \"_blank\",\n bold: false,\n underline: false,\n }));\n\n return createMenuBlock({\n items,\n separator: menu.separator || \"|\",\n separatorColor: parseColor(menu.separatorColor) || \"#999999\",\n fontSize: parsePxValue(style[\"font-size\"]) || 14,\n color: parseColor(style.color) || \"#1a1a1a\",\n fontFamily: parseFontFamily(style[\"font-family\"]) || undefined,\n textAlign: toAlign(style[\"text-align\"], \"center\"),\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertTable(descriptor: BeeFreeeModuleDescriptor): {\n block: Block;\n approximated: boolean;\n} {\n const table = descriptor.table;\n if (!table) {\n return {\n block: createTableBlock({ styles: makeStyles(descriptor) }),\n approximated: false,\n };\n }\n\n const style = table.style ?? {};\n\n // Templatical table cells are a plain-text field (the editor stores the\n // cell's innerText), so strip any HTML a BeeFree cell carried the same way\n // button labels are stripped. If a strip actually removed markup, the cell\n // lost formatting or a link URL, so the conversion is an approximation.\n let approximated = false;\n const rows: TableRowData[] = (table.rows ?? []).map((row) => ({\n id: generateId(),\n cells: (row.cells ?? []).map((cell): TableCellData => {\n const raw = cell.content ?? cell.html ?? \"\";\n const content = stripTagsPlain(raw);\n if (content !== raw) {\n approximated = true;\n }\n return { id: generateId(), content };\n }),\n }));\n\n return {\n block: createTableBlock({\n rows,\n hasHeaderRow: table.hasHeaderRow ?? false,\n headerBackgroundColor:\n parseColor(table.headerBackgroundColor) || undefined,\n borderColor: parseColor(style[\"border-color\"]) || \"#dddddd\",\n borderWidth: parsePxValue(style[\"border-width\"]) || 1,\n cellPadding:\n typeof table.cellPadding === \"number\"\n ? table.cellPadding\n : parsePxValue(table.cellPadding as string) || 8,\n fontSize: parsePxValue(style[\"font-size\"]) || 14,\n color: parseColor(style.color) || \"#1a1a1a\",\n textAlign: toAlign(style[\"text-align\"]),\n styles: makeStyles(descriptor),\n }),\n approximated,\n };\n}\n\nfunction convertHtmlFallback(module: BeeFreeeModule): Block {\n // Attempt to extract any HTML content from the descriptor\n const descriptor = module.descriptor;\n let html = \"\";\n\n // Try common content fields\n if (descriptor.text?.html) html = descriptor.text.html;\n else if (descriptor.html?.html) html = descriptor.html.html;\n else if (descriptor.heading?.text) html = descriptor.heading.text;\n else html = `<!-- Unsupported BeeFree module: ${module.type} -->`;\n\n return createHtmlBlock({\n content: html,\n styles: makeStyles(descriptor),\n });\n}\n\n/**\n * Converts a single BeeFree module to a Templatical block.\n * Returns the block and a report entry.\n */\nexport function convertModule(\n module: BeeFreeeModule,\n warnings: string[],\n): { block: Block; entry: ImportReportEntry } {\n const mappedType = MODULE_TYPE_MAP[module.type];\n const descriptor = module.descriptor;\n\n if (!mappedType) {\n return {\n block: convertHtmlFallback(module),\n entry: {\n beeFreeModuleType: module.type,\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n note: `Unknown module type \"${module.type}\" converted to HTML block.`,\n },\n };\n }\n\n let block: Block;\n let isApproximation = false;\n\n switch (mappedType) {\n case \"paragraph\":\n case \"list\":\n block = convertText(descriptor);\n break;\n case \"title\":\n block = convertHeading(descriptor);\n break;\n case \"image\":\n block = convertImage(descriptor);\n break;\n case \"button\":\n block = convertButton(descriptor);\n break;\n case \"divider\":\n block = convertDivider(descriptor);\n break;\n case \"spacer\":\n block = convertSpacer(descriptor);\n break;\n case \"html\":\n block = convertHtml(descriptor);\n break;\n case \"social\":\n block = convertSocial(descriptor, warnings);\n break;\n case \"video\":\n block = convertVideo(descriptor);\n break;\n case \"menu\":\n block = convertMenu(descriptor);\n isApproximation = true; // menu styles are approximate\n break;\n case \"table\": {\n const result = convertTable(descriptor);\n block = result.block;\n if (result.approximated) {\n isApproximation = true;\n warnings.push(\n \"HTML inside one or more table cells was reduced to plain text; any cell formatting or links were dropped.\",\n );\n }\n break;\n }\n default:\n block = convertHtmlFallback(module);\n return {\n block,\n entry: {\n beeFreeModuleType: module.type,\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n },\n };\n }\n\n return {\n block,\n entry: {\n beeFreeModuleType: module.type,\n templaticalBlockType: block.type,\n status: isApproximation ? \"approximated\" : \"converted\",\n },\n };\n}\n","import {\n createSectionBlock,\n createDefaultTemplateContent,\n} from \"@templatical/types\";\nimport type { Block, ColumnLayout, TemplateContent } from \"@templatical/types\";\nimport type {\n BeeFreeTemplate,\n BeeFreeeRow,\n BeeFreeeColumn,\n ImportResult,\n ImportReport,\n ImportReportEntry,\n} from \"./types\";\nimport { convertModule } from \"./block-mapper\";\nimport { parsePxValue, parseColor, parseFontFamily } from \"./style-parser\";\n\n/**\n * Determines the Templatical ColumnLayout from BeeFree column grid values.\n */\nfunction resolveColumnLayout(\n columns: BeeFreeeColumn[],\n warnings: string[],\n): ColumnLayout | null {\n if (columns.length === 1) return null; // Single column — no section wrapper needed\n if (columns.length === 3) return \"3\";\n\n if (columns.length === 2) {\n const left = columns[0][\"grid-columns\"] ?? 6;\n const right = columns[1][\"grid-columns\"] ?? 6;\n const total = left + right;\n const ratio = left / total;\n\n if (ratio > 0.58) return \"2-1\";\n if (ratio < 0.42) return \"1-2\";\n return \"2\";\n }\n\n if (columns.length >= 4) {\n warnings.push(\n `Row with ${columns.length} columns was flattened to a single column. BeeFree supports arbitrary columns, but Templatical supports up to 3.`,\n );\n return null; // Flatten to single column\n }\n\n return \"2\";\n}\n\n/**\n * Converts all modules in a column to Templatical blocks.\n */\nfunction convertColumnModules(\n column: BeeFreeeColumn,\n entries: ImportReportEntry[],\n warnings: string[],\n): Block[] {\n const blocks: Block[] = [];\n\n for (const module of column.modules) {\n const { block, entry } = convertModule(module, warnings);\n blocks.push(block);\n entries.push(entry);\n }\n\n return blocks;\n}\n\n/**\n * Processes a single BeeFree row into one or more Templatical blocks.\n */\nfunction processRow(\n row: BeeFreeeRow,\n entries: ImportReportEntry[],\n warnings: string[],\n): Block[] {\n const columns = row.columns;\n if (!columns || columns.length === 0) return [];\n\n // Track locked/synced metadata\n if (row.locked) {\n warnings.push(\n \"A locked row was imported. Lock metadata was dropped; content is preserved.\",\n );\n }\n if (row.synced) {\n warnings.push(\n \"A synced row was imported. Sync metadata was dropped; content is preserved.\",\n );\n }\n\n const layout = resolveColumnLayout(columns, warnings);\n\n // Extract section-level background from row content styles.\n const rowBg = parseColor(row.content?.style?.[\"background-color\"]);\n\n if (!layout) {\n // Single column (or flattened 4+ columns) — return modules directly.\n const blocks: Block[] = [];\n for (const column of columns) {\n blocks.push(...convertColumnModules(column, entries, warnings));\n }\n // A non-transparent row background can't survive on bare modules; wrap\n // them in a one-column section so the colored band still renders. Mirrors\n // the multi-column path and the Unlayer importer (which wraps every row).\n if (rowBg) {\n return [\n createSectionBlock({\n columns: \"1\",\n children: [blocks],\n styles: {\n padding: { top: 0, right: 0, bottom: 0, left: 0 },\n backgroundColor: rowBg,\n },\n }),\n ];\n }\n return blocks;\n }\n\n // Multi-column — wrap in a SectionBlock\n const children: Block[][] = columns.map((col) =>\n convertColumnModules(col, entries, warnings),\n );\n\n const section = createSectionBlock({\n columns: layout,\n children,\n styles: {\n padding: { top: 0, right: 0, bottom: 0, left: 0 },\n ...(rowBg ? { backgroundColor: rowBg } : {}),\n },\n });\n\n return [section];\n}\n\n/**\n * Extracts template-level settings from the BeeFree body.\n */\nfunction extractSettings(\n template: BeeFreeTemplate,\n): TemplateContent[\"settings\"] {\n const body = template.page.body;\n const contentStyle = body?.content?.style ?? {};\n const containerStyle = body?.container?.style ?? {};\n\n const width = parsePxValue(contentStyle[\"width\"] ?? contentStyle.width);\n const bgColor =\n parseColor(contentStyle[\"background-color\"]) ||\n parseColor(containerStyle[\"background-color\"]) ||\n \"#ffffff\";\n const fontFamily = parseFontFamily(contentStyle[\"font-family\"]) || \"Arial\";\n\n return {\n width: width || 600,\n backgroundColor: bgColor,\n textColor: \"#1a1a1a\",\n linkUnderline: false,\n fontFamily,\n locale: \"en\",\n };\n}\n\n/**\n * Converts a BeeFree template JSON to Templatical TemplateContent.\n *\n * @param template - The parsed BeeFree JSON object\n * @returns An ImportResult with the converted content and a detailed report\n *\n * @example\n * ```ts\n * import { convertBeeFreeTemplate } from '@templatical/import-beefree';\n *\n * const beeFreeJson = JSON.parse(fileContent);\n * const { content, report } = convertBeeFreeTemplate(beeFreeJson);\n *\n * // Use the content with the editor\n * const editor = init({ container: '#editor', content });\n *\n * // Check the report for any issues\n * console.log(report.summary);\n * console.log(report.warnings);\n * ```\n */\nexport function convertBeeFreeTemplate(\n template: BeeFreeTemplate,\n): ImportResult {\n // Validate structure\n if (!template?.page?.rows) {\n throw new Error(\n \"Invalid BeeFree template: missing page.rows. Ensure you are passing a valid BeeFree JSON export.\",\n );\n }\n\n const entries: ImportReportEntry[] = [];\n const warnings: string[] = [];\n const blocks: Block[] = [];\n\n // Extract web font warnings\n const webFonts = template.page.body?.webFonts;\n if (webFonts && webFonts.length > 1) {\n warnings.push(\n `Template uses ${webFonts.length} web fonts. Only the primary font is preserved; additional fonts may need to be configured via the fonts option.`,\n );\n }\n\n // Process rows\n for (const row of template.page.rows) {\n if (row.empty) continue;\n blocks.push(...processRow(row, entries, warnings));\n }\n\n // Build template content\n const content: TemplateContent = {\n ...createDefaultTemplateContent(),\n blocks,\n settings: extractSettings(template),\n };\n\n // Build report summary\n const summary = {\n total: entries.length,\n converted: entries.filter((e) => e.status === \"converted\").length,\n approximated: entries.filter((e) => e.status === \"approximated\").length,\n htmlFallback: entries.filter((e) => e.status === \"html-fallback\").length,\n skipped: entries.filter((e) => e.status === \"skipped\").length,\n };\n\n const report: ImportReport = { entries, warnings, summary };\n\n return { content, report };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/style-parser.ts","../src/block-mapper.ts","../src/converter.ts"],"sourcesContent":["import type { SpacingValue } from \"@templatical/types\";\n\n/**\n * Parses CSS-like style values from BeeFree descriptors.\n */\n\nexport function parsePxValue(value: string | undefined): number {\n if (!value) return 0;\n const match = value.match(/^(-?\\d+(?:\\.\\d+)?)\\s*px/);\n return match ? Math.round(parseFloat(match[1])) : 0;\n}\n\n/**\n * An image's corner radius in px, or `undefined` for square corners.\n *\n * Accepts a percentage as well as a px length, because `border-radius: 50%` is\n * the idiomatic way to write a circular avatar and the block stores px only. A\n * percentage resolves against the shorter rendered side, which is what turns a\n * square image into a circle and a wide one into a pill.\n *\n * `width` and `height` must be the dimensions actually read from the source. A\n * percentage with neither known is dropped rather than resolved against the\n * caller's fallback width, which would invent a radius the template never\n * expressed. A non-positive result is dropped for the same reason a px `0` is:\n * both mean square, and only absence keeps it out of the exported JSON.\n */\nexport function parseImageBorderRadius(\n value: string | undefined,\n width: number | undefined,\n height: number | undefined,\n): number | undefined {\n if (!value) return undefined;\n\n // Deliberately no `-?`: a negative percentage falls through to the px parse,\n // which rejects it too.\n const percent = value.trim().match(/^(\\d+(?:\\.\\d+)?)\\s*%$/);\n if (percent) {\n const side = Math.min(width ?? Infinity, height ?? Infinity);\n if (!Number.isFinite(side)) return undefined;\n const radius = Math.round((parseFloat(percent[1]) / 100) * side);\n return radius > 0 ? radius : undefined;\n }\n\n const px = parsePxValue(value);\n return px > 0 ? px : undefined;\n}\n\nexport function parseColor(value: string | undefined): string {\n if (!value || value === \"transparent\") return \"\";\n\n const trimmed = value.trim();\n\n // Already a valid hex color\n if (/^#[0-9a-fA-F]{6}$/.test(trimmed)) return trimmed.toLowerCase();\n\n // 3-digit hex → 6-digit\n if (/^#[0-9a-fA-F]{3}$/.test(trimmed)) {\n const r = trimmed[1];\n const g = trimmed[2];\n const b = trimmed[3];\n return `#${r}${r}${g}${g}${b}${b}`.toLowerCase();\n }\n\n // Return as-is for rgb(), named colors, etc.\n return trimmed;\n}\n\nexport function parseBorderTop(value: string | undefined): {\n width: number;\n style: string;\n color: string;\n} {\n if (!value) return { width: 0, style: \"solid\", color: \"#000000\" };\n\n // \"2px solid #cccccc\"\n const parts = value.trim().split(/\\s+/);\n return {\n width: parsePxValue(parts[0]),\n style: parts[1] || \"solid\",\n color: parseColor(parts[2]) || \"#000000\",\n };\n}\n\nexport function extractPadding(\n style: Record<string, string> | undefined,\n): SpacingValue {\n if (!style) return { top: 0, right: 0, bottom: 0, left: 0 };\n\n // Check for shorthand `padding` first\n if (style.padding) {\n return parseShorthandPadding(style.padding);\n }\n\n return {\n top: parsePxValue(style[\"padding-top\"]),\n right: parsePxValue(style[\"padding-right\"]),\n bottom: parsePxValue(style[\"padding-bottom\"]),\n left: parsePxValue(style[\"padding-left\"]),\n };\n}\n\nfunction parseShorthandPadding(value: string): SpacingValue {\n const parts = value.trim().split(/\\s+/);\n const values = parts.map((p) => parsePxValue(p));\n\n switch (values.length) {\n case 1:\n return {\n top: values[0],\n right: values[0],\n bottom: values[0],\n left: values[0],\n };\n case 2:\n return {\n top: values[0],\n right: values[1],\n bottom: values[0],\n left: values[1],\n };\n case 3:\n return {\n top: values[0],\n right: values[1],\n bottom: values[2],\n left: values[1],\n };\n default:\n return {\n top: values[0],\n right: values[1],\n bottom: values[2],\n left: values[3],\n };\n }\n}\n\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 // Might be px — return 100 as default\n return 100;\n}\n\nexport function parseFontFamily(value: string | undefined): string {\n if (!value) return \"\";\n // Take the first font in the stack\n return value.split(\",\")[0].trim().replace(/['\"]/g, \"\");\n}\n","import {\n createTitleBlock,\n createParagraphBlock,\n createImageBlock,\n createButtonBlock,\n createDividerBlock,\n createSpacerBlock,\n createHtmlBlock,\n createSocialIconsBlock,\n createMenuBlock,\n createTableBlock,\n createVideoBlock,\n generateId,\n} from \"@templatical/types\";\nimport type {\n Block,\n HeadingLevel,\n SocialPlatform,\n SocialIcon,\n MenuItemData,\n TableRowData,\n TableCellData,\n} from \"@templatical/types\";\nimport type {\n BeeFreeeModule,\n BeeFreeeModuleDescriptor,\n ImportReportEntry,\n} from \"./types\";\nimport {\n parseImageBorderRadius,\n parsePxValue,\n parseColor,\n parseBorderTop,\n extractPadding,\n parseWidthPercent,\n parseFontFamily,\n} from \"./style-parser\";\n\n/**\n * Maps BeeFree module type strings to short keys.\n */\nconst MODULE_TYPE_MAP: Record<string, string> = {\n \"mailup-bee-newsletter-modules-text\": \"paragraph\",\n \"mailup-bee-newsletter-modules-paragraph\": \"paragraph\",\n \"mailup-bee-newsletter-modules-heading\": \"title\",\n \"mailup-bee-newsletter-modules-list\": \"list\",\n \"mailup-bee-newsletter-modules-image\": \"image\",\n \"mailup-bee-newsletter-modules-button\": \"button\",\n \"mailup-bee-newsletter-modules-divider\": \"divider\",\n \"mailup-bee-newsletter-modules-spacer\": \"spacer\",\n \"mailup-bee-newsletter-modules-html\": \"html\",\n \"mailup-bee-newsletter-modules-social\": \"social\",\n \"mailup-bee-newsletter-modules-video\": \"video\",\n \"mailup-bee-newsletter-modules-menu\": \"menu\",\n \"mailup-bee-newsletter-modules-table\": \"table\",\n};\n\nconst SOCIAL_PLATFORM_MAP: Record<string, SocialPlatform> = {\n facebook: \"facebook\",\n twitter: \"twitter\",\n x: \"twitter\",\n instagram: \"instagram\",\n linkedin: \"linkedin\",\n youtube: \"youtube\",\n tiktok: \"tiktok\",\n pinterest: \"pinterest\",\n email: \"email\",\n whatsapp: \"whatsapp\",\n telegram: \"telegram\",\n discord: \"discord\",\n snapchat: \"snapchat\",\n reddit: \"reddit\",\n github: \"github\",\n dribbble: \"dribbble\",\n behance: \"behance\",\n};\n\ntype Align = \"left\" | \"center\" | \"right\";\ntype LineStyle = \"solid\" | \"dashed\" | \"dotted\";\n\nfunction toAlign(value: string | undefined, fallback: Align = \"left\"): Align {\n if (value === \"left\" || value === \"center\" || value === \"right\") return value;\n return fallback;\n}\n\nfunction toLineStyle(\n value: string | undefined,\n fallback: LineStyle = \"solid\",\n): LineStyle {\n if (value === \"solid\" || value === \"dashed\" || value === \"dotted\")\n return value;\n return fallback;\n}\n\n/**\n * Strip every `<…>` from a button label and reject any stray `<` or `>`\n * left behind (e.g. a truncated `<script`). The original\n * `value.replace(/<[^>]*>/g, \"\")` was both polynomial-ReDoS over\n * `<<<<…` inputs and an incomplete sanitizer — an unterminated `<script`\n * would survive the strip. Downstream HTML-escapes the label at render\n * time, but stripping here keeps the imported JSON clean.\n */\nfunction stripTagsPlain(text: string): string {\n let out = \"\";\n let i = 0;\n while (i < text.length) {\n if (text[i] === \"<\") {\n const close = text.indexOf(\">\", i + 1);\n if (close === -1) {\n // Unterminated tag — discard the rest.\n break;\n }\n i = close + 1;\n continue;\n }\n if (text[i] === \">\") {\n i++;\n continue;\n }\n out += text[i];\n i++;\n }\n return out;\n}\n\nfunction makeStyles(descriptor: BeeFreeeModuleDescriptor): Block[\"styles\"] {\n const padding = extractPadding(descriptor.style);\n const bg = parseColor(descriptor.style?.[\"background-color\"]);\n return {\n padding,\n ...(bg ? { backgroundColor: bg } : {}),\n };\n}\n\n/**\n * Apply BeeFree text styles as TipTap-compatible inline markup.\n * - text-align → added to each <p> tag's style attribute\n * - color, font-size, font-weight, font-family → wrapped in <span style=\"...\"> inside each <p>\n */\nfunction inlineStylesToHtml(\n html: string,\n style: Record<string, string | undefined>,\n): string {\n const spanParts: string[] = [];\n const fontSize = parsePxValue(style[\"font-size\"]);\n if (fontSize && fontSize !== 16) spanParts.push(`font-size: ${fontSize}px`);\n const color = parseColor(style.color);\n if (color && color !== \"#1a1a1a\") spanParts.push(`color: ${color}`);\n const fontWeight = style[\"font-weight\"];\n // \"400\" is the numeric synonym for \"normal\" — neither needs an explicit\n // span (matches the import-unlayer importer).\n if (fontWeight && fontWeight !== \"normal\" && fontWeight !== \"400\")\n spanParts.push(`font-weight: ${fontWeight}`);\n const fontFamily = parseFontFamily(style[\"font-family\"]);\n if (fontFamily) spanParts.push(`font-family: ${fontFamily}`);\n\n const textAlign = style[\"text-align\"];\n const pStyle =\n textAlign && textAlign !== \"left\" ? `text-align: ${textAlign}` : \"\";\n\n if (!pStyle && spanParts.length === 0) return html;\n\n const spanStyle = spanParts.join(\"; \");\n\n // Apply styles to each <p> tag in the HTML\n let result = html;\n\n if (pStyle) {\n // Add text-align to existing <p style=\"...\"> or add style to plain <p>\n result = result\n .replace(/<p style=\"([^\"]*)\">/g, `<p style=\"$1; ${pStyle}\">`)\n .replaceAll(\"<p>\", `<p style=\"${pStyle}\">`);\n }\n\n if (spanStyle) {\n // Wrap inner content of each <p> in a styled span.\n result = wrapParagraphInner(result, spanStyle);\n }\n\n return result;\n}\n\n/**\n * Wrap the inner content of every `<p …>…</p>` with `<span style=\"…\">…</span>`.\n *\n * Hand-rolled linear scanner instead of `/<p([^>]*)>([\\s\\S]*?)<\\/p>/g` because\n * the regex is polynomial-ReDoS: the engine retries `[\\s\\S]*?<\\/p>` at every\n * `<p` start, so inputs like `<p>a<p>a<p>a…` (no `</p>` ever) cost O(n²).\n * The scanner advances `i` monotonically via `indexOf`, keeping the work\n * strictly O(n).\n */\nfunction wrapParagraphInner(html: string, spanStyle: string): string {\n let out = \"\";\n let i = 0;\n while (i < html.length) {\n const open = html.indexOf(\"<p\", i);\n if (open === -1) {\n out += html.substring(i);\n break;\n }\n const afterTagName = html[open + 2];\n if (\n afterTagName !== \">\" &&\n afterTagName !== \" \" &&\n afterTagName !== \"\\t\" &&\n afterTagName !== \"\\n\" &&\n afterTagName !== \"\\r\" &&\n afterTagName !== \"/\"\n ) {\n out += html.substring(i, open + 2);\n i = open + 2;\n continue;\n }\n const openEnd = html.indexOf(\">\", open + 2);\n if (openEnd === -1) {\n out += html.substring(i);\n break;\n }\n const closeStart = html.indexOf(\"</p>\", openEnd + 1);\n if (closeStart === -1) {\n out += html.substring(i);\n break;\n }\n const inner = html.substring(openEnd + 1, closeStart);\n out += html.substring(i, openEnd + 1);\n out += `<span style=\"${spanStyle}\">${inner}</span></p>`;\n i = closeStart + 4;\n }\n return out;\n}\n\nfunction convertText(descriptor: BeeFreeeModuleDescriptor): Block {\n const textContent =\n descriptor.text ?? descriptor.paragraph ?? descriptor.list;\n const html = textContent?.html ?? \"\";\n const style = textContent?.style ?? {};\n\n return createParagraphBlock({\n content: inlineStylesToHtml(html, style),\n styles: makeStyles(descriptor),\n });\n}\n\nfunction parseHeadingLevel(tag: string): HeadingLevel {\n const match = tag.match(/^h(\\d)$/i);\n if (match) {\n const num = Number(match[1]);\n if (num >= 1 && num <= 4) return num as HeadingLevel;\n }\n return 2;\n}\n\nfunction convertHeading(descriptor: BeeFreeeModuleDescriptor): Block {\n const heading = descriptor.heading;\n if (!heading) return convertText(descriptor);\n\n const style = heading.style ?? {};\n const tag = heading.title ?? \"h2\";\n const text = heading.text ?? \"\";\n // Strip heading tags — content goes inside TipTap, heading tag is rendered by the block\n const content = text.startsWith(\"<\")\n ? text.replace(/^<h\\d[^>]*>|<\\/h\\d>$/gi, \"\")\n : text;\n\n return createTitleBlock({\n content: content ? `<p>${content}</p>` : \"<p></p>\",\n level: parseHeadingLevel(tag),\n color: parseColor(style.color) || \"#1a1a1a\",\n textAlign: toAlign(style[\"text-align\"]),\n fontFamily: parseFontFamily(style[\"font-family\"]) || undefined,\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertImage(descriptor: BeeFreeeModuleDescriptor): Block {\n const image = descriptor.image;\n if (!image) {\n return createImageBlock({ styles: makeStyles(descriptor) });\n }\n\n // Kept separate from the `?? 600` fallback below: a percentage radius has to\n // resolve against a width the template actually stated.\n const readWidth = parsePxValue(image.width) || undefined;\n const readHeight = parsePxValue(image.height) || undefined;\n\n return createImageBlock({\n src: image.src || \"\",\n alt: image.alt || \"\",\n width: readWidth ?? 600,\n // No default, unlike width: an absent height is what keeps the aspect\n // ratio, and BeeFree's \"auto\" parses to 0, which reads the same way.\n height: readHeight,\n borderRadius: parseImageBorderRadius(\n image.style?.[\"border-radius\"],\n readWidth,\n readHeight,\n ),\n align: toAlign(image.style?.[\"text-align\"], \"center\"),\n linkUrl: image.href || undefined,\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertButton(descriptor: BeeFreeeModuleDescriptor): Block {\n const button = descriptor.button;\n if (!button) {\n return createButtonBlock({ styles: makeStyles(descriptor) });\n }\n\n const style = button.style ?? {};\n const label = button.label ? stripTagsPlain(button.label) : \"Button\";\n\n return createButtonBlock({\n text: label,\n url: button.href || \"#\",\n backgroundColor: parseColor(style[\"background-color\"]) || \"#4f46e5\",\n textColor: parseColor(style.color) || \"#ffffff\",\n borderRadius: parsePxValue(style[\"border-radius\"]),\n fontSize: parsePxValue(style[\"font-size\"]) || 16,\n fontFamily: parseFontFamily(style[\"font-family\"]) || undefined,\n align: toAlign(style[\"text-align\"], \"center\"),\n buttonPadding: {\n top: parsePxValue(style[\"padding-top\"]) || 12,\n right: parsePxValue(style[\"padding-right\"]) || 24,\n bottom: parsePxValue(style[\"padding-bottom\"]) || 12,\n left: parsePxValue(style[\"padding-left\"]) || 24,\n },\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertDivider(descriptor: BeeFreeeModuleDescriptor): Block {\n const divider = descriptor.divider;\n const style = divider?.style ?? {};\n const border = parseBorderTop(style[\"border-top\"]);\n\n return createDividerBlock({\n lineStyle: toLineStyle(border.style),\n color: border.color,\n thickness: border.width || 1,\n width: parseWidthPercent(style.width),\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertSpacer(descriptor: BeeFreeeModuleDescriptor): Block {\n const spacer = descriptor.spacer;\n const height = parsePxValue(spacer?.style?.height) || 24;\n\n return createSpacerBlock({\n height,\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertHtml(descriptor: BeeFreeeModuleDescriptor): Block {\n const html = descriptor.html?.html ?? \"\";\n\n return createHtmlBlock({\n content: html,\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertSocial(\n descriptor: BeeFreeeModuleDescriptor,\n warnings: string[],\n): Block {\n const iconsList = descriptor.iconsList;\n if (!iconsList?.icons) {\n return createSocialIconsBlock({ styles: makeStyles(descriptor) });\n }\n\n const icons: SocialIcon[] = [];\n\n for (const beeIcon of iconsList.icons) {\n const id = (beeIcon.id ?? beeIcon.name ?? \"\").toLowerCase();\n const platform = SOCIAL_PLATFORM_MAP[id];\n\n if (!platform) {\n warnings.push(\n `Unrecognized social icon \"${beeIcon.name || id}\" was skipped.`,\n );\n continue;\n }\n\n icons.push({\n id: generateId(),\n platform,\n url: beeIcon.image?.href || \"#\",\n });\n }\n\n return createSocialIconsBlock({\n icons,\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertVideo(descriptor: BeeFreeeModuleDescriptor): Block {\n const video = descriptor.video;\n if (!video) {\n return createVideoBlock({ styles: makeStyles(descriptor) });\n }\n\n return createVideoBlock({\n url: video.src || \"\",\n thumbnailUrl: video.thumbnail || \"\",\n alt: video.alt || \"\",\n width: parsePxValue(video.style?.width) || 600,\n height: parsePxValue(video.style?.height) || undefined,\n align: toAlign(video.style?.[\"text-align\"], \"center\"),\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertMenu(descriptor: BeeFreeeModuleDescriptor): Block {\n const menu = descriptor.menu;\n if (!menu) {\n return createMenuBlock({ styles: makeStyles(descriptor) });\n }\n\n const style = menu.style ?? {};\n\n const items: MenuItemData[] = (menu.items ?? []).map((item) => ({\n id: generateId(),\n text: item.text || \"\",\n url: item.link || item.href || \"#\",\n openInNewTab: item.target === \"_blank\",\n bold: false,\n underline: false,\n }));\n\n return createMenuBlock({\n items,\n separator: menu.separator || \"|\",\n separatorColor: parseColor(menu.separatorColor) || \"#999999\",\n fontSize: parsePxValue(style[\"font-size\"]) || 14,\n color: parseColor(style.color) || \"#1a1a1a\",\n fontFamily: parseFontFamily(style[\"font-family\"]) || undefined,\n textAlign: toAlign(style[\"text-align\"], \"center\"),\n styles: makeStyles(descriptor),\n });\n}\n\nfunction convertTable(descriptor: BeeFreeeModuleDescriptor): {\n block: Block;\n approximated: boolean;\n} {\n const table = descriptor.table;\n if (!table) {\n return {\n block: createTableBlock({ styles: makeStyles(descriptor) }),\n approximated: false,\n };\n }\n\n const style = table.style ?? {};\n\n // Templatical table cells are a plain-text field (the editor stores the\n // cell's innerText), so strip any HTML a BeeFree cell carried the same way\n // button labels are stripped. If a strip actually removed markup, the cell\n // lost formatting or a link URL, so the conversion is an approximation.\n let approximated = false;\n const rows: TableRowData[] = (table.rows ?? []).map((row) => ({\n id: generateId(),\n cells: (row.cells ?? []).map((cell): TableCellData => {\n const raw = cell.content ?? cell.html ?? \"\";\n const content = stripTagsPlain(raw);\n if (content !== raw) {\n approximated = true;\n }\n return { id: generateId(), content };\n }),\n }));\n\n return {\n block: createTableBlock({\n rows,\n hasHeaderRow: table.hasHeaderRow ?? false,\n headerBackgroundColor:\n parseColor(table.headerBackgroundColor) || undefined,\n borderColor: parseColor(style[\"border-color\"]) || \"#dddddd\",\n borderWidth: parsePxValue(style[\"border-width\"]) || 1,\n cellPadding:\n typeof table.cellPadding === \"number\"\n ? table.cellPadding\n : parsePxValue(table.cellPadding as string) || 8,\n fontSize: parsePxValue(style[\"font-size\"]) || 14,\n color: parseColor(style.color) || \"#1a1a1a\",\n textAlign: toAlign(style[\"text-align\"]),\n styles: makeStyles(descriptor),\n }),\n approximated,\n };\n}\n\nfunction convertHtmlFallback(module: BeeFreeeModule): Block {\n // Attempt to extract any HTML content from the descriptor\n const descriptor = module.descriptor;\n let html = \"\";\n\n // Try common content fields\n if (descriptor.text?.html) html = descriptor.text.html;\n else if (descriptor.html?.html) html = descriptor.html.html;\n else if (descriptor.heading?.text) html = descriptor.heading.text;\n else html = `<!-- Unsupported BeeFree module: ${module.type} -->`;\n\n return createHtmlBlock({\n content: html,\n styles: makeStyles(descriptor),\n });\n}\n\n/**\n * Converts a single BeeFree module to a Templatical block.\n * Returns the block and a report entry.\n */\nexport function convertModule(\n module: BeeFreeeModule,\n warnings: string[],\n): { block: Block; entry: ImportReportEntry } {\n const mappedType = MODULE_TYPE_MAP[module.type];\n const descriptor = module.descriptor;\n\n if (!mappedType) {\n return {\n block: convertHtmlFallback(module),\n entry: {\n beeFreeModuleType: module.type,\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n note: `Unknown module type \"${module.type}\" converted to HTML block.`,\n },\n };\n }\n\n let block: Block;\n let isApproximation = false;\n\n switch (mappedType) {\n case \"paragraph\":\n case \"list\":\n block = convertText(descriptor);\n break;\n case \"title\":\n block = convertHeading(descriptor);\n break;\n case \"image\":\n block = convertImage(descriptor);\n break;\n case \"button\":\n block = convertButton(descriptor);\n break;\n case \"divider\":\n block = convertDivider(descriptor);\n break;\n case \"spacer\":\n block = convertSpacer(descriptor);\n break;\n case \"html\":\n block = convertHtml(descriptor);\n break;\n case \"social\":\n block = convertSocial(descriptor, warnings);\n break;\n case \"video\":\n block = convertVideo(descriptor);\n break;\n case \"menu\":\n block = convertMenu(descriptor);\n isApproximation = true; // menu styles are approximate\n break;\n case \"table\": {\n const result = convertTable(descriptor);\n block = result.block;\n if (result.approximated) {\n isApproximation = true;\n warnings.push(\n \"HTML inside one or more table cells was reduced to plain text; any cell formatting or links were dropped.\",\n );\n }\n break;\n }\n default:\n block = convertHtmlFallback(module);\n return {\n block,\n entry: {\n beeFreeModuleType: module.type,\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n },\n };\n }\n\n return {\n block,\n entry: {\n beeFreeModuleType: module.type,\n templaticalBlockType: block.type,\n status: isApproximation ? \"approximated\" : \"converted\",\n },\n };\n}\n","import {\n createSectionBlock,\n createDefaultTemplateContent,\n} from \"@templatical/types\";\nimport type { Block, ColumnLayout, TemplateContent } from \"@templatical/types\";\nimport type {\n BeeFreeTemplate,\n BeeFreeeRow,\n BeeFreeeColumn,\n ImportResult,\n ImportReport,\n ImportReportEntry,\n} from \"./types\";\nimport { convertModule } from \"./block-mapper\";\nimport { parsePxValue, parseColor, parseFontFamily } from \"./style-parser\";\n\n/**\n * Determines the Templatical ColumnLayout from BeeFree column grid values.\n */\nfunction resolveColumnLayout(\n columns: BeeFreeeColumn[],\n warnings: string[],\n): ColumnLayout | null {\n if (columns.length === 1) return null; // Single column — no section wrapper needed\n if (columns.length === 3) return \"3\";\n\n if (columns.length === 2) {\n const left = columns[0][\"grid-columns\"] ?? 6;\n const right = columns[1][\"grid-columns\"] ?? 6;\n const total = left + right;\n const ratio = left / total;\n\n if (ratio > 0.58) return \"2-1\";\n if (ratio < 0.42) return \"1-2\";\n return \"2\";\n }\n\n if (columns.length >= 4) {\n warnings.push(\n `Row with ${columns.length} columns was flattened to a single column. BeeFree supports arbitrary columns, but Templatical supports up to 3.`,\n );\n return null; // Flatten to single column\n }\n\n return \"2\";\n}\n\n/**\n * Converts all modules in a column to Templatical blocks.\n */\nfunction convertColumnModules(\n column: BeeFreeeColumn,\n entries: ImportReportEntry[],\n warnings: string[],\n): Block[] {\n const blocks: Block[] = [];\n\n for (const module of column.modules) {\n const { block, entry } = convertModule(module, warnings);\n blocks.push(block);\n entries.push(entry);\n }\n\n return blocks;\n}\n\n/**\n * Processes a single BeeFree row into one or more Templatical blocks.\n */\nfunction processRow(\n row: BeeFreeeRow,\n entries: ImportReportEntry[],\n warnings: string[],\n): Block[] {\n const columns = row.columns;\n if (!columns || columns.length === 0) return [];\n\n // Track locked/synced metadata\n if (row.locked) {\n warnings.push(\n \"A locked row was imported. Lock metadata was dropped; content is preserved.\",\n );\n }\n if (row.synced) {\n warnings.push(\n \"A synced row was imported. Sync metadata was dropped; content is preserved.\",\n );\n }\n\n const layout = resolveColumnLayout(columns, warnings);\n\n // Extract section-level background from row content styles.\n const rowBg = parseColor(row.content?.style?.[\"background-color\"]);\n\n if (!layout) {\n // Single column (or flattened 4+ columns) — return modules directly.\n const blocks: Block[] = [];\n for (const column of columns) {\n blocks.push(...convertColumnModules(column, entries, warnings));\n }\n // A non-transparent row background can't survive on bare modules; wrap\n // them in a one-column section so the colored band still renders. Mirrors\n // the multi-column path and the Unlayer importer (which wraps every row).\n if (rowBg) {\n return [\n createSectionBlock({\n columns: \"1\",\n children: [blocks],\n styles: {\n padding: { top: 0, right: 0, bottom: 0, left: 0 },\n backgroundColor: rowBg,\n },\n }),\n ];\n }\n return blocks;\n }\n\n // Multi-column — wrap in a SectionBlock\n const children: Block[][] = columns.map((col) =>\n convertColumnModules(col, entries, warnings),\n );\n\n const section = createSectionBlock({\n columns: layout,\n children,\n styles: {\n padding: { top: 0, right: 0, bottom: 0, left: 0 },\n ...(rowBg ? { backgroundColor: rowBg } : {}),\n },\n });\n\n return [section];\n}\n\n/**\n * Extracts template-level settings from the BeeFree body.\n */\nfunction extractSettings(\n template: BeeFreeTemplate,\n): TemplateContent[\"settings\"] {\n const body = template.page.body;\n const contentStyle = body?.content?.style ?? {};\n const containerStyle = body?.container?.style ?? {};\n\n const width = parsePxValue(contentStyle[\"width\"] ?? contentStyle.width);\n const bgColor =\n parseColor(contentStyle[\"background-color\"]) ||\n parseColor(containerStyle[\"background-color\"]) ||\n \"#ffffff\";\n const fontFamily = parseFontFamily(contentStyle[\"font-family\"]) || \"Arial\";\n\n return {\n width: width || 600,\n backgroundColor: bgColor,\n textColor: \"#1a1a1a\",\n linkUnderline: false,\n fontFamily,\n locale: \"en\",\n };\n}\n\n/**\n * Converts a BeeFree template JSON to Templatical TemplateContent.\n *\n * @param template - The parsed BeeFree JSON object\n * @returns An ImportResult with the converted content and a detailed report\n *\n * @example\n * ```ts\n * import { convertBeeFreeTemplate } from '@templatical/import-beefree';\n *\n * const beeFreeJson = JSON.parse(fileContent);\n * const { content, report } = convertBeeFreeTemplate(beeFreeJson);\n *\n * // Use the content with the editor\n * const editor = init({ container: '#editor', content });\n *\n * // Check the report for any issues\n * console.log(report.summary);\n * console.log(report.warnings);\n * ```\n */\nexport function convertBeeFreeTemplate(\n template: BeeFreeTemplate,\n): ImportResult {\n // Validate structure\n if (!template?.page?.rows) {\n throw new Error(\n \"Invalid BeeFree template: missing page.rows. Ensure you are passing a valid BeeFree JSON export.\",\n );\n }\n\n const entries: ImportReportEntry[] = [];\n const warnings: string[] = [];\n const blocks: Block[] = [];\n\n // Extract web font warnings\n const webFonts = template.page.body?.webFonts;\n if (webFonts && webFonts.length > 1) {\n warnings.push(\n `Template uses ${webFonts.length} web fonts. Only the primary font is preserved; additional fonts may need to be configured via the fonts option.`,\n );\n }\n\n // Process rows\n for (const row of template.page.rows) {\n if (row.empty) continue;\n blocks.push(...processRow(row, entries, warnings));\n }\n\n // Build template content\n const content: TemplateContent = {\n ...createDefaultTemplateContent(),\n blocks,\n settings: extractSettings(template),\n };\n\n // Build report summary\n const summary = {\n total: entries.length,\n converted: entries.filter((e) => e.status === \"converted\").length,\n approximated: entries.filter((e) => e.status === \"approximated\").length,\n htmlFallback: entries.filter((e) => e.status === \"html-fallback\").length,\n skipped: entries.filter((e) => e.status === \"skipped\").length,\n };\n\n const report: ImportReport = { entries, warnings, summary };\n\n return { content, report };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@templatical/import-beefree",
|
|
3
3
|
"description": "Convert BeeFree email templates to Templatical format",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.30.0",
|
|
5
5
|
"bugs": "https://github.com/templatical/sdk/issues",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@templatical/types": "0.
|
|
7
|
+
"@templatical/types": "0.30.0"
|
|
8
8
|
},
|
|
9
9
|
"devDependencies": {
|
|
10
10
|
"typescript": "^6.0.3",
|