🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@templatical/import-unlayer

Package Overview
Dependencies
Maintainers
1
Versions
55
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@templatical/import-unlayer - npm Package Compare versions

Comparing version
0.10.0
to
0.10.1
+125
-124
dist/index.d.ts

@@ -1,3 +0,2 @@

import * as _templatical_types from '@templatical/types';
//#region src/types.d.ts
/**

@@ -9,145 +8,145 @@ * Unlayer JSON type definitions.

interface UnlayerTemplate {
counters?: Record<string, number>;
body: UnlayerBody;
schemaVersion?: number;
counters?: Record<string, number>;
body: UnlayerBody;
schemaVersion?: number;
}
interface UnlayerBody {
id?: string;
rows: UnlayerRow[];
headers?: UnlayerRow[];
footers?: UnlayerRow[];
values: UnlayerBodyValues;
id?: string;
rows: UnlayerRow[];
headers?: UnlayerRow[];
footers?: UnlayerRow[];
values: UnlayerBodyValues;
}
interface UnlayerBackgroundImage {
url?: string;
fullWidth?: boolean;
repeat?: string;
size?: string;
position?: string;
url?: string;
fullWidth?: boolean;
repeat?: string;
size?: string;
position?: string;
}
interface UnlayerFontFamily {
label?: string;
value?: string;
label?: string;
value?: string;
}
interface UnlayerBodyValues {
backgroundColor?: string;
backgroundImage?: UnlayerBackgroundImage;
contentWidth?: string;
contentAlignment?: string;
fontFamily?: UnlayerFontFamily;
textColor?: string;
linkStyle?: Record<string, string>;
preheaderText?: string;
[key: string]: unknown;
backgroundColor?: string;
backgroundImage?: UnlayerBackgroundImage;
contentWidth?: string;
contentAlignment?: string;
fontFamily?: UnlayerFontFamily;
textColor?: string;
linkStyle?: Record<string, string>;
preheaderText?: string;
[key: string]: unknown;
}
interface UnlayerRow {
id?: string;
cells: number[];
columns: UnlayerColumn[];
values: UnlayerRowValues;
id?: string;
cells: number[];
columns: UnlayerColumn[];
values: UnlayerRowValues;
}
interface UnlayerRowValues {
backgroundColor?: string;
backgroundImage?: UnlayerBackgroundImage;
padding?: string;
columnsBackgroundColor?: string;
hideMobile?: boolean;
hideDesktop?: boolean;
noStackMobile?: boolean;
_meta?: Record<string, unknown>;
[key: string]: unknown;
backgroundColor?: string;
backgroundImage?: UnlayerBackgroundImage;
padding?: string;
columnsBackgroundColor?: string;
hideMobile?: boolean;
hideDesktop?: boolean;
noStackMobile?: boolean;
_meta?: Record<string, unknown>;
[key: string]: unknown;
}
interface UnlayerColumn {
id?: string;
contents: UnlayerContent[];
values: UnlayerColumnValues;
id?: string;
contents: UnlayerContent[];
values: UnlayerColumnValues;
}
interface UnlayerColumnValues {
backgroundColor?: string;
padding?: string;
border?: Record<string, string>;
_meta?: Record<string, unknown>;
[key: string]: unknown;
backgroundColor?: string;
padding?: string;
border?: Record<string, string>;
_meta?: Record<string, unknown>;
[key: string]: unknown;
}
interface UnlayerContent {
type: string;
values: UnlayerContentValues;
type: string;
values: UnlayerContentValues;
}
interface UnlayerLinkAction {
name?: string;
values?: {
href?: string;
target?: string;
};
name?: string;
values?: {
href?: string;
target?: string;
};
}
interface UnlayerImageSrc {
url?: string;
width?: number;
height?: number;
url?: string;
width?: number;
height?: number;
}
interface UnlayerButtonColors {
color?: string;
backgroundColor?: string;
hoverColor?: string;
hoverBackgroundColor?: string;
color?: string;
backgroundColor?: string;
hoverColor?: string;
hoverBackgroundColor?: string;
}
interface UnlayerBorder {
borderTopWidth?: string;
borderTopStyle?: string;
borderTopColor?: string;
borderTopWidth?: string;
borderTopStyle?: string;
borderTopColor?: string;
}
interface UnlayerMenuItem {
key?: string;
text: string;
link?: UnlayerLinkAction;
key?: string;
text: string;
link?: UnlayerLinkAction;
}
interface UnlayerMenu {
items: UnlayerMenuItem[];
items: UnlayerMenuItem[];
}
interface UnlayerSocialIcon {
name?: string;
url?: string;
type?: string;
name?: string;
url?: string;
type?: string;
}
interface UnlayerSocialIcons {
iconType?: string;
editor?: {
data?: {
showTitle?: boolean;
};
iconType?: string;
editor?: {
data?: {
showTitle?: boolean;
};
icons?: UnlayerSocialIcon[];
};
icons?: UnlayerSocialIcon[];
}
interface UnlayerContentValues {
containerPadding?: string;
textAlign?: string;
fontFamily?: UnlayerFontFamily;
fontSize?: string;
fontWeight?: string | number;
color?: string;
hideMobile?: boolean;
hideDesktop?: boolean;
text?: string;
headingType?: string;
src?: UnlayerImageSrc;
altText?: string;
action?: UnlayerLinkAction;
buttonColors?: UnlayerButtonColors;
borderRadius?: string;
padding?: string;
size?: {
autoWidth?: boolean;
width?: string;
};
href?: UnlayerLinkAction;
border?: UnlayerBorder;
containerPadding?: string;
textAlign?: string;
fontFamily?: UnlayerFontFamily;
fontSize?: string;
fontWeight?: string | number;
color?: string;
hideMobile?: boolean;
hideDesktop?: boolean;
text?: string;
headingType?: string;
src?: UnlayerImageSrc;
altText?: string;
action?: UnlayerLinkAction;
buttonColors?: UnlayerButtonColors;
borderRadius?: string;
padding?: string;
size?: {
autoWidth?: boolean;
width?: string;
html?: string;
menu?: UnlayerMenu;
layout?: "horizontal" | "vertical";
separator?: string;
icons?: UnlayerSocialIcons;
videoUrl?: string;
thumbnailUrl?: string;
[key: string]: unknown;
};
href?: UnlayerLinkAction;
border?: UnlayerBorder;
width?: string;
html?: string;
menu?: UnlayerMenu;
layout?: "horizontal" | "vertical";
separator?: string;
icons?: UnlayerSocialIcons;
videoUrl?: string;
thumbnailUrl?: string;
[key: string]: unknown;
}

@@ -162,6 +161,6 @@ /**

interface ImportReportEntry {
unlayerContentType: string;
templaticalBlockType: string | null;
status: ConversionStatus;
note?: string;
unlayerContentType: string;
templaticalBlockType: string | null;
status: ConversionStatus;
note?: string;
}

@@ -172,11 +171,11 @@ /**

interface ImportReport {
entries: ImportReportEntry[];
warnings: string[];
summary: {
total: number;
converted: number;
approximated: number;
htmlFallback: number;
skipped: number;
};
entries: ImportReportEntry[];
warnings: string[];
summary: {
total: number;
converted: number;
approximated: number;
htmlFallback: number;
skipped: number;
};
}

@@ -187,6 +186,7 @@ /**

interface ImportResult {
content: _templatical_types.TemplateContent;
report: ImportReport;
content: import("@templatical/types").TemplateContent;
report: ImportReport;
}
//#endregion
//#region src/converter.d.ts
/**

@@ -212,3 +212,4 @@ * Converts an Unlayer design JSON to Templatical TemplateContent.

declare function convertUnlayerTemplate(template: UnlayerTemplate): ImportResult;
//#endregion
export { type ConversionStatus, type ImportReport, type ImportReportEntry, type ImportResult, type UnlayerBody, type UnlayerColumn, type UnlayerContent, type UnlayerContentValues, type UnlayerRow, type UnlayerTemplate, convertUnlayerTemplate };
//# sourceMappingURL=index.d.ts.map

@@ -1,618 +0,597 @@

// src/converter.ts
import {
createSectionBlock,
createDefaultTemplateContent
} from "@templatical/types";
// src/block-mapper.ts
import {
createTitleBlock,
createParagraphBlock,
createImageBlock,
createButtonBlock,
createDividerBlock,
createSpacerBlock,
createHtmlBlock,
createSocialIconsBlock,
createMenuBlock,
createVideoBlock,
generateId
} from "@templatical/types";
// src/style-parser.ts
import { createButtonBlock, createDefaultTemplateContent, createDividerBlock, createHtmlBlock, createImageBlock, createMenuBlock, createParagraphBlock, createSectionBlock, createSocialIconsBlock, createSpacerBlock, createTitleBlock, createVideoBlock, generateId } from "@templatical/types";
//#region src/style-parser.ts
/**
* Parses CSS-like style values from Unlayer content values.
*/
function parsePxValue(value) {
if (value === void 0 || value === null) return 0;
if (typeof value === "number") return Math.round(value);
const match = value.match(/^(-?\d+(?:\.\d+)?)\s*(?:px\s*)?$/);
return match ? Math.round(parseFloat(match[1])) : 0;
if (value === void 0 || value === null) return 0;
if (typeof value === "number") return Math.round(value);
const match = value.match(/^(-?\d+(?:\.\d+)?)\s*(?:px\s*)?$/);
return match ? Math.round(parseFloat(match[1])) : 0;
}
function parseColor(value) {
if (!value || value === "transparent") return "";
const trimmed = value.trim();
if (/^#[0-9a-fA-F]{6}$/.test(trimmed)) return trimmed.toLowerCase();
if (/^#[0-9a-fA-F]{3}$/.test(trimmed)) {
const r = trimmed[1];
const g = trimmed[2];
const b = trimmed[3];
return `#${r}${r}${g}${g}${b}${b}`.toLowerCase();
}
return trimmed;
if (!value || value === "transparent") return "";
const trimmed = value.trim();
if (/^#[0-9a-fA-F]{6}$/.test(trimmed)) return trimmed.toLowerCase();
if (/^#[0-9a-fA-F]{3}$/.test(trimmed)) {
const r = trimmed[1];
const g = trimmed[2];
const b = trimmed[3];
return `#${r}${r}${g}${g}${b}${b}`.toLowerCase();
}
return trimmed;
}
function parsePaddingShorthand(value) {
if (!value) return { top: 0, right: 0, bottom: 0, left: 0 };
const parts = value.trim().split(/\s+/);
const values = parts.map((p) => parsePxValue(p));
switch (values.length) {
case 1:
return {
top: values[0],
right: values[0],
bottom: values[0],
left: values[0]
};
case 2:
return {
top: values[0],
right: values[1],
bottom: values[0],
left: values[1]
};
case 3:
return {
top: values[0],
right: values[1],
bottom: values[2],
left: values[1]
};
default:
return {
top: values[0],
right: values[1],
bottom: values[2],
left: values[3]
};
}
if (!value) return {
top: 0,
right: 0,
bottom: 0,
left: 0
};
const values = value.trim().split(/\s+/).map((p) => parsePxValue(p));
switch (values.length) {
case 1: return {
top: values[0],
right: values[0],
bottom: values[0],
left: values[0]
};
case 2: return {
top: values[0],
right: values[1],
bottom: values[0],
left: values[1]
};
case 3: return {
top: values[0],
right: values[1],
bottom: values[2],
left: values[1]
};
default: return {
top: values[0],
right: values[1],
bottom: values[2],
left: values[3]
};
}
}
function parseBorderObject(border) {
if (!border) return { width: 0, style: "solid", color: "#000000" };
return {
width: parsePxValue(border.borderTopWidth),
style: border.borderTopStyle || "solid",
color: parseColor(border.borderTopColor) || "#000000"
};
if (!border) return {
width: 0,
style: "solid",
color: "#000000"
};
return {
width: parsePxValue(border.borderTopWidth),
style: border.borderTopStyle || "solid",
color: parseColor(border.borderTopColor) || "#000000"
};
}
function parseWidthPercent(value) {
if (!value) return 100;
const match = value.match(/^(\d+(?:\.\d+)?)\s*%/);
if (match) return Math.round(parseFloat(match[1]));
return 100;
if (!value) return 100;
const match = value.match(/^(\d+(?:\.\d+)?)\s*%/);
if (match) return Math.round(parseFloat(match[1]));
return 100;
}
function parseFontFamily(value) {
if (!value) return "";
if (typeof value === "string") {
return value.split(",")[0].trim().replace(/['"]/g, "");
}
if (value.value) return value.value.split(",")[0].trim().replace(/['"]/g, "");
if (value.label) return value.label;
return "";
if (!value) return "";
if (typeof value === "string") return value.split(",")[0].trim().replace(/['"]/g, "");
if (value.value) return value.value.split(",")[0].trim().replace(/['"]/g, "");
if (value.label) return value.label;
return "";
}
// src/block-mapper.ts
var SOCIAL_PLATFORM_MAP = {
facebook: "facebook",
twitter: "twitter",
x: "twitter",
instagram: "instagram",
linkedin: "linkedin",
youtube: "youtube",
tiktok: "tiktok",
pinterest: "pinterest",
email: "email",
mail: "email",
whatsapp: "whatsapp",
telegram: "telegram",
discord: "discord",
snapchat: "snapchat",
reddit: "reddit",
github: "github",
dribbble: "dribbble",
behance: "behance"
//#endregion
//#region src/block-mapper.ts
const SOCIAL_PLATFORM_MAP = {
facebook: "facebook",
twitter: "twitter",
x: "twitter",
instagram: "instagram",
linkedin: "linkedin",
youtube: "youtube",
tiktok: "tiktok",
pinterest: "pinterest",
email: "email",
mail: "email",
whatsapp: "whatsapp",
telegram: "telegram",
discord: "discord",
snapchat: "snapchat",
reddit: "reddit",
github: "github",
dribbble: "dribbble",
behance: "behance"
};
function toAlign(value, fallback = "left") {
if (value === "left" || value === "center" || value === "right") return value;
return fallback;
if (value === "left" || value === "center" || value === "right") return value;
return fallback;
}
/**
* Strip every `<…>` from a button label and reject any stray `<` or `>`
* left behind (e.g. a truncated `<script`). The original
* `value.replace(/<[^>]*>/g, "")` was both polynomial-ReDoS over
* `<<<<…` inputs and an incomplete sanitizer — an unterminated `<script`
* would survive the strip. Downstream HTML-escapes the label at render
* time, but stripping here keeps the imported JSON clean.
*/
function stripTagsPlain(text) {
let out = "";
let i = 0;
while (i < text.length) {
if (text[i] === "<") {
const close = text.indexOf(">", i + 1);
if (close === -1) {
break;
}
i = close + 1;
continue;
}
if (text[i] === ">") {
i++;
continue;
}
out += text[i];
i++;
}
return out;
let out = "";
let i = 0;
while (i < text.length) {
if (text[i] === "<") {
const close = text.indexOf(">", i + 1);
if (close === -1) break;
i = close + 1;
continue;
}
if (text[i] === ">") {
i++;
continue;
}
out += text[i];
i++;
}
return out;
}
function toLineStyle(value, fallback = "solid") {
if (value === "solid" || value === "dashed" || value === "dotted")
return value;
return fallback;
if (value === "solid" || value === "dashed" || value === "dotted") return value;
return fallback;
}
function makeStyles(values) {
const padding = parsePaddingShorthand(values.containerPadding);
return {
padding
};
return { padding: parsePaddingShorthand(values.containerPadding) };
}
/**
* Apply Unlayer text-level styles as TipTap-compatible inline markup.
* Mirrors the BeeFree importer's helper but reads from Unlayer's flat
* values shape rather than a CSS style record.
*/
function inlineStylesToHtml(html, values) {
const spanParts = [];
const fontSize = parsePxValue(values.fontSize);
if (fontSize && fontSize !== 16) spanParts.push(`font-size: ${fontSize}px`);
const color = parseColor(values.color);
if (color && color !== "#1a1a1a") spanParts.push(`color: ${color}`);
const fontWeight = values.fontWeight;
if (fontWeight !== void 0 && fontWeight !== null && String(fontWeight) !== "normal" && String(fontWeight) !== "400") {
spanParts.push(`font-weight: ${fontWeight}`);
}
const fontFamily = parseFontFamily(values.fontFamily);
if (fontFamily) spanParts.push(`font-family: ${fontFamily}`);
const textAlign = values.textAlign;
const pStyle = textAlign && textAlign !== "left" ? `text-align: ${textAlign}` : "";
if (!pStyle && spanParts.length === 0) return html;
const spanStyle = spanParts.join("; ");
let result = html;
if (pStyle) {
result = result.replace(/<p style="([^"]*)">/g, `<p style="$1; ${pStyle}">`).replaceAll("<p>", `<p style="${pStyle}">`);
}
if (spanStyle) {
result = wrapParagraphInner(result, spanStyle);
}
return result;
const spanParts = [];
const fontSize = parsePxValue(values.fontSize);
if (fontSize && fontSize !== 16) spanParts.push(`font-size: ${fontSize}px`);
const color = parseColor(values.color);
if (color && color !== "#1a1a1a") spanParts.push(`color: ${color}`);
const fontWeight = values.fontWeight;
if (fontWeight !== void 0 && fontWeight !== null && String(fontWeight) !== "normal" && String(fontWeight) !== "400") spanParts.push(`font-weight: ${fontWeight}`);
const fontFamily = parseFontFamily(values.fontFamily);
if (fontFamily) spanParts.push(`font-family: ${fontFamily}`);
const textAlign = values.textAlign;
const pStyle = textAlign && textAlign !== "left" ? `text-align: ${textAlign}` : "";
if (!pStyle && spanParts.length === 0) return html;
const spanStyle = spanParts.join("; ");
let result = html;
if (pStyle) result = result.replace(/<p style="([^"]*)">/g, `<p style="$1; ${pStyle}">`).replaceAll("<p>", `<p style="${pStyle}">`);
if (spanStyle) result = wrapParagraphInner(result, spanStyle);
return result;
}
/**
* Wrap the inner content of every `<p …>…</p>` with `<span style="…">…</span>`.
*
* Hand-rolled linear scanner instead of `/<p([^>]*)>([\s\S]*?)<\/p>/g` because
* the regex is polynomial-ReDoS: the engine retries `[\s\S]*?<\/p>` at every
* `<p` start, so inputs like `<p>a<p>a<p>a…` (no `</p>` ever) cost O(n²).
* The scanner advances `i` monotonically via `indexOf`, keeping the work
* strictly O(n).
*/
function wrapParagraphInner(html, spanStyle) {
let out = "";
let i = 0;
while (i < html.length) {
const open = html.indexOf("<p", i);
if (open === -1) {
out += html.substring(i);
break;
}
const afterTagName = html[open + 2];
if (afterTagName !== ">" && afterTagName !== " " && afterTagName !== " " && afterTagName !== "\n" && afterTagName !== "\r" && afterTagName !== "/") {
out += html.substring(i, open + 2);
i = open + 2;
continue;
}
const openEnd = html.indexOf(">", open + 2);
if (openEnd === -1) {
out += html.substring(i);
break;
}
const closeStart = html.indexOf("</p>", openEnd + 1);
if (closeStart === -1) {
out += html.substring(i);
break;
}
const inner = html.substring(openEnd + 1, closeStart);
out += html.substring(i, openEnd + 1);
out += `<span style="${spanStyle}">${inner}</span></p>`;
i = closeStart + 4;
}
return out;
let out = "";
let i = 0;
while (i < html.length) {
const open = html.indexOf("<p", i);
if (open === -1) {
out += html.substring(i);
break;
}
const afterTagName = html[open + 2];
if (afterTagName !== ">" && afterTagName !== " " && afterTagName !== " " && afterTagName !== "\n" && afterTagName !== "\r" && afterTagName !== "/") {
out += html.substring(i, open + 2);
i = open + 2;
continue;
}
const openEnd = html.indexOf(">", open + 2);
if (openEnd === -1) {
out += html.substring(i);
break;
}
const closeStart = html.indexOf("</p>", openEnd + 1);
if (closeStart === -1) {
out += html.substring(i);
break;
}
const inner = html.substring(openEnd + 1, closeStart);
out += html.substring(i, openEnd + 1);
out += `<span style="${spanStyle}">${inner}</span></p>`;
i = closeStart + 4;
}
return out;
}
function ensureParagraphWrapped(html) {
if (!html) return "<p></p>";
if (/<p[\s>]/i.test(html)) return html;
return `<p>${html}</p>`;
if (!html) return "<p></p>";
if (/<p[\s>]/i.test(html)) return html;
return `<p>${html}</p>`;
}
function convertText(values) {
const html = ensureParagraphWrapped(values.text ?? "");
return createParagraphBlock({
content: inlineStylesToHtml(html, values),
styles: makeStyles(values)
});
return createParagraphBlock({
content: inlineStylesToHtml(ensureParagraphWrapped(values.text ?? ""), values),
styles: makeStyles(values)
});
}
function parseHeadingLevel(tag) {
if (!tag) return 2;
const match = tag.match(/^h(\d)$/i);
if (match) {
const num = Number(match[1]);
if (num >= 1 && num <= 4) return num;
}
return 2;
if (!tag) return 2;
const match = tag.match(/^h(\d)$/i);
if (match) {
const num = Number(match[1]);
if (num >= 1 && num <= 4) return num;
}
return 2;
}
function convertHeading(values) {
const text = values.text ?? "";
const stripped = text.replace(/^<h\d[^>]*>|<\/h\d>$/gi, "");
const content = stripped ? `<p>${stripped}</p>` : "<p></p>";
return createTitleBlock({
content,
level: parseHeadingLevel(values.headingType),
color: parseColor(values.color) || "#1a1a1a",
textAlign: toAlign(values.textAlign),
fontFamily: parseFontFamily(values.fontFamily) || void 0,
styles: makeStyles(values)
});
const stripped = (values.text ?? "").replace(/^<h\d[^>]*>|<\/h\d>$/gi, "");
return createTitleBlock({
content: stripped ? `<p>${stripped}</p>` : "<p></p>",
level: parseHeadingLevel(values.headingType),
color: parseColor(values.color) || "#1a1a1a",
textAlign: toAlign(values.textAlign),
fontFamily: parseFontFamily(values.fontFamily) || void 0,
styles: makeStyles(values)
});
}
function convertImage(values) {
const src = values.src;
const action = values.action?.values;
return createImageBlock({
src: src?.url || "",
alt: values.altText || "",
width: src?.width ? Math.round(src.width) : 600,
align: toAlign(values.textAlign, "center"),
linkUrl: action?.href || void 0,
linkOpenInNewTab: action?.target === "_blank" || void 0,
styles: makeStyles(values)
});
const src = values.src;
const action = values.action?.values;
return createImageBlock({
src: src?.url || "",
alt: values.altText || "",
width: src?.width ? Math.round(src.width) : 600,
align: toAlign(values.textAlign, "center"),
linkUrl: action?.href || void 0,
linkOpenInNewTab: action?.target === "_blank" || void 0,
styles: makeStyles(values)
});
}
function convertButton(values) {
const colors = values.buttonColors ?? {};
const padding = values.padding ? parsePaddingShorthand(values.padding) : { top: 12, right: 24, bottom: 12, left: 24 };
const label = stripTagsPlain(values.text ?? "Button");
const linkValues = values.href?.values;
return createButtonBlock({
text: label,
url: linkValues?.href || "#",
openInNewTab: linkValues?.target === "_blank" || void 0,
backgroundColor: parseColor(colors.backgroundColor) || "#4f46e5",
textColor: parseColor(colors.color) || "#ffffff",
borderRadius: parsePxValue(values.borderRadius),
fontSize: parsePxValue(values.fontSize) || 16,
fontFamily: parseFontFamily(values.fontFamily) || void 0,
buttonPadding: padding,
styles: makeStyles(values)
});
const colors = values.buttonColors ?? {};
const padding = values.padding ? parsePaddingShorthand(values.padding) : {
top: 12,
right: 24,
bottom: 12,
left: 24
};
const label = stripTagsPlain(values.text ?? "Button");
const linkValues = values.href?.values;
return createButtonBlock({
text: label,
url: linkValues?.href || "#",
openInNewTab: linkValues?.target === "_blank" || void 0,
backgroundColor: parseColor(colors.backgroundColor) || "#4f46e5",
textColor: parseColor(colors.color) || "#ffffff",
borderRadius: parsePxValue(values.borderRadius),
fontSize: parsePxValue(values.fontSize) || 16,
fontFamily: parseFontFamily(values.fontFamily) || void 0,
buttonPadding: padding,
styles: makeStyles(values)
});
}
function convertDivider(values) {
const border = parseBorderObject(values.border);
return createDividerBlock({
lineStyle: toLineStyle(border.style),
color: border.color,
thickness: border.width || 1,
width: parseWidthPercent(values.width),
styles: makeStyles(values)
});
const border = parseBorderObject(values.border);
return createDividerBlock({
lineStyle: toLineStyle(border.style),
color: border.color,
thickness: border.width || 1,
width: parseWidthPercent(values.width),
styles: makeStyles(values)
});
}
function convertSpacer(values) {
const padding = parsePaddingShorthand(values.containerPadding);
const height = parsePxValue(values.height) || padding.top + padding.bottom || 24;
return createSpacerBlock({
height,
styles: makeStyles(values)
});
const padding = parsePaddingShorthand(values.containerPadding);
return createSpacerBlock({
height: parsePxValue(values.height) || padding.top + padding.bottom || 24,
styles: makeStyles(values)
});
}
function convertHtml(values) {
return createHtmlBlock({
content: values.html ?? "",
styles: makeStyles(values)
});
return createHtmlBlock({
content: values.html ?? "",
styles: makeStyles(values)
});
}
function convertSocial(values, warnings) {
const iconList = values.icons?.icons ?? [];
const icons = [];
for (const unlayerIcon of iconList) {
const id = (unlayerIcon.name ?? "").toLowerCase();
const platform = SOCIAL_PLATFORM_MAP[id];
if (!platform) {
warnings.push(
`Unrecognized social icon "${unlayerIcon.name || id}" was skipped.`
);
continue;
}
icons.push({
id: generateId(),
platform,
url: unlayerIcon.url || "#"
});
}
return createSocialIconsBlock({
icons,
align: toAlign(values.textAlign, "center"),
styles: makeStyles(values)
});
const iconList = values.icons?.icons ?? [];
const icons = [];
for (const unlayerIcon of iconList) {
const id = (unlayerIcon.name ?? "").toLowerCase();
const platform = SOCIAL_PLATFORM_MAP[id];
if (!platform) {
warnings.push(`Unrecognized social icon "${unlayerIcon.name || id}" was skipped.`);
continue;
}
icons.push({
id: generateId(),
platform,
url: unlayerIcon.url || "#"
});
}
return createSocialIconsBlock({
icons,
align: toAlign(values.textAlign, "center"),
styles: makeStyles(values)
});
}
function convertVideo(values) {
return createVideoBlock({
url: values.videoUrl || "",
thumbnailUrl: values.thumbnailUrl || "",
alt: values.altText || "",
width: 600,
align: toAlign(values.textAlign, "center"),
styles: makeStyles(values)
});
return createVideoBlock({
url: values.videoUrl || "",
thumbnailUrl: values.thumbnailUrl || "",
alt: values.altText || "",
width: 600,
align: toAlign(values.textAlign, "center"),
styles: makeStyles(values)
});
}
function convertMenu(values) {
const menu = values.menu;
const items = (menu?.items ?? []).map((item) => ({
id: generateId(),
text: item.text || "",
url: item.link?.values?.href || "#",
openInNewTab: item.link?.values?.target === "_blank",
bold: false,
underline: false
}));
return createMenuBlock({
items,
separator: values.separator || "|",
separatorColor: "#999999",
fontSize: parsePxValue(values.fontSize) || 14,
color: parseColor(values.color) || "#1a1a1a",
fontFamily: parseFontFamily(values.fontFamily) || void 0,
textAlign: toAlign(values.textAlign, "center"),
styles: makeStyles(values)
});
return createMenuBlock({
items: (values.menu?.items ?? []).map((item) => ({
id: generateId(),
text: item.text || "",
url: item.link?.values?.href || "#",
openInNewTab: item.link?.values?.target === "_blank",
bold: false,
underline: false
})),
separator: values.separator || "|",
separatorColor: "#999999",
fontSize: parsePxValue(values.fontSize) || 14,
color: parseColor(values.color) || "#1a1a1a",
fontFamily: parseFontFamily(values.fontFamily) || void 0,
textAlign: toAlign(values.textAlign, "center"),
styles: makeStyles(values)
});
}
function convertHtmlFallback(content, comment) {
const safe = comment.replace(/</g, "&lt;").replace(/>/g, "&gt;");
return createHtmlBlock({
content: `<div style="padding:12px;border:1px dashed #d1d5db;border-radius:6px;background:#fafafa;color:#6b7280;font-family:sans-serif;font-size:13px;">${safe}</div>`,
styles: makeStyles(content.values)
});
return createHtmlBlock({
content: `<div style="padding:12px;border:1px dashed #d1d5db;border-radius:6px;background:#fafafa;color:#6b7280;font-family:sans-serif;font-size:13px;">${comment.replace(/</g, "&lt;").replace(/>/g, "&gt;")}</div>`,
styles: makeStyles(content.values)
});
}
/**
* Converts a single Unlayer content node to a Templatical block.
* Returns the block and a report entry.
*/
function convertContent(content, warnings) {
const type = content.type;
const values = content.values ?? {};
switch (type) {
case "text":
return {
block: convertText(values),
entry: {
unlayerContentType: type,
templaticalBlockType: "paragraph",
status: "converted"
}
};
case "heading":
return {
block: convertHeading(values),
entry: {
unlayerContentType: type,
templaticalBlockType: "title",
status: "converted"
}
};
case "image":
return {
block: convertImage(values),
entry: {
unlayerContentType: type,
templaticalBlockType: "image",
status: "converted"
}
};
case "button":
return {
block: convertButton(values),
entry: {
unlayerContentType: type,
templaticalBlockType: "button",
status: "converted"
}
};
case "divider":
return {
block: convertDivider(values),
entry: {
unlayerContentType: type,
templaticalBlockType: "divider",
status: "converted"
}
};
case "spacer":
return {
block: convertSpacer(values),
entry: {
unlayerContentType: type,
templaticalBlockType: "spacer",
status: "converted"
}
};
case "html":
return {
block: convertHtml(values),
entry: {
unlayerContentType: type,
templaticalBlockType: "html",
status: "converted"
}
};
case "menu":
return {
block: convertMenu(values),
entry: {
unlayerContentType: type,
templaticalBlockType: "menu",
status: "approximated",
note: "Menu styles map approximately; review spacing and separator color."
}
};
case "social":
return {
block: convertSocial(values, warnings),
entry: {
unlayerContentType: type,
templaticalBlockType: "social",
status: "converted"
}
};
case "video":
return {
block: convertVideo(values),
entry: {
unlayerContentType: type,
templaticalBlockType: "video",
status: "converted"
}
};
case "timer":
return {
block: convertHtmlFallback(
content,
"Unlayer timer block: rebuild manually in Templatical"
),
entry: {
unlayerContentType: type,
templaticalBlockType: "html",
status: "html-fallback",
note: "Timer modules have no direct Templatical equivalent; placeholder HTML inserted."
}
};
case "form":
return {
block: convertHtmlFallback(
content,
"Unlayer form block: not supported in Templatical (most email clients block form submission)"
),
entry: {
unlayerContentType: type,
templaticalBlockType: null,
status: "skipped",
note: "Unlayer forms have no Templatical equivalent and are skipped. Most email clients block form submission anyway."
}
};
default:
return {
block: convertHtmlFallback(
content,
`Unsupported Unlayer content type: ${type}`
),
entry: {
unlayerContentType: type,
templaticalBlockType: "html",
status: "html-fallback",
note: `Unknown content type "${type}" converted to HTML block.`
}
};
}
const type = content.type;
const values = content.values ?? {};
switch (type) {
case "text": return {
block: convertText(values),
entry: {
unlayerContentType: type,
templaticalBlockType: "paragraph",
status: "converted"
}
};
case "heading": return {
block: convertHeading(values),
entry: {
unlayerContentType: type,
templaticalBlockType: "title",
status: "converted"
}
};
case "image": return {
block: convertImage(values),
entry: {
unlayerContentType: type,
templaticalBlockType: "image",
status: "converted"
}
};
case "button": return {
block: convertButton(values),
entry: {
unlayerContentType: type,
templaticalBlockType: "button",
status: "converted"
}
};
case "divider": return {
block: convertDivider(values),
entry: {
unlayerContentType: type,
templaticalBlockType: "divider",
status: "converted"
}
};
case "spacer": return {
block: convertSpacer(values),
entry: {
unlayerContentType: type,
templaticalBlockType: "spacer",
status: "converted"
}
};
case "html": return {
block: convertHtml(values),
entry: {
unlayerContentType: type,
templaticalBlockType: "html",
status: "converted"
}
};
case "menu": return {
block: convertMenu(values),
entry: {
unlayerContentType: type,
templaticalBlockType: "menu",
status: "approximated",
note: "Menu styles map approximately; review spacing and separator color."
}
};
case "social": return {
block: convertSocial(values, warnings),
entry: {
unlayerContentType: type,
templaticalBlockType: "social",
status: "converted"
}
};
case "video": return {
block: convertVideo(values),
entry: {
unlayerContentType: type,
templaticalBlockType: "video",
status: "converted"
}
};
case "timer": return {
block: convertHtmlFallback(content, "Unlayer timer block: rebuild manually in Templatical"),
entry: {
unlayerContentType: type,
templaticalBlockType: "html",
status: "html-fallback",
note: "Timer modules have no direct Templatical equivalent; placeholder HTML inserted."
}
};
case "form": return {
block: convertHtmlFallback(content, "Unlayer form block: not supported in Templatical (most email clients block form submission)"),
entry: {
unlayerContentType: type,
templaticalBlockType: null,
status: "skipped",
note: "Unlayer forms have no Templatical equivalent and are skipped. Most email clients block form submission anyway."
}
};
default: return {
block: convertHtmlFallback(content, `Unsupported Unlayer content type: ${type}`),
entry: {
unlayerContentType: type,
templaticalBlockType: "html",
status: "html-fallback",
note: `Unknown content type "${type}" converted to HTML block.`
}
};
}
}
// src/converter.ts
//#endregion
//#region src/converter.ts
function resolveColumnLayout(cells, warnings) {
if (cells.length <= 1) return "1";
if (cells.length === 3) return "3";
if (cells.length === 2) {
const left = cells[0] ?? 1;
const right = cells[1] ?? 1;
const total = left + right;
const ratio = left / total;
if (ratio > 0.58) return "2-1";
if (ratio < 0.42) return "1-2";
return "2";
}
warnings.push(
`Row with ${cells.length} columns was flattened to a single column. Unlayer supports arbitrary columns, but Templatical supports up to 3.`
);
return "1";
if (cells.length <= 1) return "1";
if (cells.length === 3) return "3";
if (cells.length === 2) {
const left = cells[0] ?? 1;
const ratio = left / (left + (cells[1] ?? 1));
if (ratio > .58) return "2-1";
if (ratio < .42) return "1-2";
return "2";
}
warnings.push(`Row with ${cells.length} columns was flattened to a single column. Unlayer supports arbitrary columns, but Templatical supports up to 3.`);
return "1";
}
/**
* Converts all contents in a column to Templatical blocks.
*/
function convertColumnContents(column, entries, warnings) {
const blocks = [];
for (const content of column.contents ?? []) {
const { block, entry } = convertContent(content, warnings);
blocks.push(block);
entries.push(entry);
}
return blocks;
const blocks = [];
for (const content of column.contents ?? []) {
const { block, entry } = convertContent(content, warnings);
blocks.push(block);
entries.push(entry);
}
return blocks;
}
/**
* Processes a single Unlayer row into one or more Templatical blocks.
*/
function processRow(row, entries, warnings) {
const columns = row.columns;
if (!columns || columns.length === 0) return [];
const cells = row.cells ?? columns.map(() => 1);
const layout = resolveColumnLayout(cells, warnings);
let children;
if (layout === "1") {
const merged = [];
for (const column of columns) {
merged.push(...convertColumnContents(column, entries, warnings));
}
children = [merged];
} else {
children = columns.map(
(col) => convertColumnContents(col, entries, warnings)
);
}
const rowBg = parseColor(row.values?.backgroundColor);
const padding = parsePaddingShorthand(row.values?.padding);
const section = createSectionBlock({
columns: layout,
children,
styles: {
padding,
...rowBg ? { backgroundColor: rowBg } : {}
}
});
return [section];
const columns = row.columns;
if (!columns || columns.length === 0) return [];
const layout = resolveColumnLayout(row.cells ?? columns.map(() => 1), warnings);
let children;
if (layout === "1") {
const merged = [];
for (const column of columns) merged.push(...convertColumnContents(column, entries, warnings));
children = [merged];
} else children = columns.map((col) => convertColumnContents(col, entries, warnings));
const rowBg = parseColor(row.values?.backgroundColor);
const padding = parsePaddingShorthand(row.values?.padding);
return [createSectionBlock({
columns: layout,
children,
styles: {
padding,
...rowBg ? { backgroundColor: rowBg } : {}
}
})];
}
/**
* Extracts template-level settings from the Unlayer body values.
*/
function extractSettings(template) {
const values = template.body?.values ?? {};
const width = parsePxValue(values.contentWidth);
const bgColor = parseColor(values.backgroundColor) || "#ffffff";
const fontFamily = parseFontFamily(values.fontFamily) || "Arial";
return {
width: width || 600,
backgroundColor: bgColor,
fontFamily,
locale: "en"
};
const values = template.body?.values ?? {};
const width = parsePxValue(values.contentWidth);
const bgColor = parseColor(values.backgroundColor) || "#ffffff";
const fontFamily = parseFontFamily(values.fontFamily) || "Arial";
return {
width: width || 600,
backgroundColor: bgColor,
fontFamily,
locale: "en"
};
}
/**
* Converts an Unlayer design JSON to Templatical TemplateContent.
*
* @param template - The parsed Unlayer JSON object (the result of `editor.saveDesign(...)`)
* @returns An ImportResult with the converted content and a detailed report
*
* @example
* ```ts
* import { convertUnlayerTemplate } from '@templatical/import-unlayer';
*
* const unlayerJson = JSON.parse(fileContent);
* const { content, report } = convertUnlayerTemplate(unlayerJson);
*
* const editor = init({ container: '#editor', content });
*
* console.log(report.summary);
* console.log(report.warnings);
* ```
*/
function convertUnlayerTemplate(template) {
if (!template?.body?.rows) {
throw new Error(
"Invalid Unlayer template: missing body.rows. Ensure you are passing a valid Unlayer JSON design (the output of editor.saveDesign)."
);
}
const entries = [];
const warnings = [];
const blocks = [];
const headers = template.body.headers ?? [];
const footers = template.body.footers ?? [];
if (headers.length > 0) {
warnings.push(
`${headers.length} Unlayer header row(s) were imported as regular rows at the top of the template.`
);
for (const row of headers) {
blocks.push(...processRow(row, entries, warnings));
}
}
for (const row of template.body.rows) {
blocks.push(...processRow(row, entries, warnings));
}
if (footers.length > 0) {
warnings.push(
`${footers.length} Unlayer footer row(s) were imported as regular rows at the bottom of the template.`
);
for (const row of footers) {
blocks.push(...processRow(row, entries, warnings));
}
}
const content = {
...createDefaultTemplateContent(),
blocks,
settings: extractSettings(template)
};
const summary = {
total: entries.length,
converted: entries.filter((e) => e.status === "converted").length,
approximated: entries.filter((e) => e.status === "approximated").length,
htmlFallback: entries.filter((e) => e.status === "html-fallback").length,
skipped: entries.filter((e) => e.status === "skipped").length
};
const report = { entries, warnings, summary };
return { content, report };
if (!template?.body?.rows) throw new Error("Invalid Unlayer template: missing body.rows. Ensure you are passing a valid Unlayer JSON design (the output of editor.saveDesign).");
const entries = [];
const warnings = [];
const blocks = [];
const headers = template.body.headers ?? [];
const footers = template.body.footers ?? [];
if (headers.length > 0) {
warnings.push(`${headers.length} Unlayer header row(s) were imported as regular rows at the top of the template.`);
for (const row of headers) blocks.push(...processRow(row, entries, warnings));
}
for (const row of template.body.rows) blocks.push(...processRow(row, entries, warnings));
if (footers.length > 0) {
warnings.push(`${footers.length} Unlayer footer row(s) were imported as regular rows at the bottom of the template.`);
for (const row of footers) blocks.push(...processRow(row, entries, warnings));
}
return {
content: {
...createDefaultTemplateContent(),
blocks,
settings: extractSettings(template)
},
report: {
entries,
warnings,
summary: {
total: entries.length,
converted: entries.filter((e) => e.status === "converted").length,
approximated: entries.filter((e) => e.status === "approximated").length,
htmlFallback: entries.filter((e) => e.status === "html-fallback").length,
skipped: entries.filter((e) => e.status === "skipped").length
}
}
};
}
export {
convertUnlayerTemplate
};
//#endregion
export { convertUnlayerTemplate };
//# sourceMappingURL=index.js.map

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/converter.ts","../src/block-mapper.ts","../src/style-parser.ts"],"sourcesContent":["import {\n createSectionBlock,\n createDefaultTemplateContent,\n} from \"@templatical/types\";\nimport type { Block, ColumnLayout, TemplateContent } from \"@templatical/types\";\nimport type {\n UnlayerTemplate,\n UnlayerRow,\n UnlayerColumn,\n ImportResult,\n ImportReport,\n ImportReportEntry,\n} from \"./types\";\nimport { convertContent } from \"./block-mapper\";\nimport {\n parsePxValue,\n parseColor,\n parseFontFamily,\n parsePaddingShorthand,\n} from \"./style-parser\";\n\nfunction resolveColumnLayout(\n cells: number[],\n warnings: string[],\n): ColumnLayout {\n if (cells.length <= 1) return \"1\";\n if (cells.length === 3) return \"3\";\n\n if (cells.length === 2) {\n const left = cells[0] ?? 1;\n const right = cells[1] ?? 1;\n const total = left + right;\n const ratio = left / total;\n\n if (ratio > 0.58) return \"2-1\";\n if (ratio < 0.42) return \"1-2\";\n return \"2\";\n }\n\n warnings.push(\n `Row with ${cells.length} columns was flattened to a single column. Unlayer supports arbitrary columns, but Templatical supports up to 3.`,\n );\n return \"1\";\n}\n\n/**\n * Converts all contents in a column to Templatical blocks.\n */\nfunction convertColumnContents(\n column: UnlayerColumn,\n entries: ImportReportEntry[],\n warnings: string[],\n): Block[] {\n const blocks: Block[] = [];\n\n for (const content of column.contents ?? []) {\n const { block, entry } = convertContent(content, warnings);\n blocks.push(block);\n entries.push(entry);\n }\n\n return blocks;\n}\n\n/**\n * Processes a single Unlayer row into one or more Templatical blocks.\n */\nfunction processRow(\n row: UnlayerRow,\n entries: ImportReportEntry[],\n warnings: string[],\n): Block[] {\n const columns = row.columns;\n if (!columns || columns.length === 0) return [];\n\n const cells = row.cells ?? columns.map(() => 1);\n const layout = resolveColumnLayout(cells, warnings);\n\n let children: Block[][];\n if (layout === \"1\") {\n const merged: Block[] = [];\n for (const column of columns) {\n merged.push(...convertColumnContents(column, entries, warnings));\n }\n children = [merged];\n } else {\n children = columns.map((col) =>\n convertColumnContents(col, entries, warnings),\n );\n }\n\n const rowBg = parseColor(row.values?.backgroundColor);\n const padding = parsePaddingShorthand(row.values?.padding);\n\n const section = createSectionBlock({\n columns: layout,\n children,\n styles: {\n padding,\n ...(rowBg ? { backgroundColor: rowBg } : {}),\n },\n });\n\n return [section];\n}\n\n/**\n * Extracts template-level settings from the Unlayer body values.\n */\nfunction extractSettings(\n template: UnlayerTemplate,\n): TemplateContent[\"settings\"] {\n const values = template.body?.values ?? {};\n\n const width = parsePxValue(values.contentWidth);\n const bgColor = parseColor(values.backgroundColor) || \"#ffffff\";\n const fontFamily = parseFontFamily(values.fontFamily) || \"Arial\";\n\n return {\n width: width || 600,\n backgroundColor: bgColor,\n fontFamily,\n locale: \"en\",\n };\n}\n\n/**\n * Converts an Unlayer design JSON to Templatical TemplateContent.\n *\n * @param template - The parsed Unlayer JSON object (the result of `editor.saveDesign(...)`)\n * @returns An ImportResult with the converted content and a detailed report\n *\n * @example\n * ```ts\n * import { convertUnlayerTemplate } from '@templatical/import-unlayer';\n *\n * const unlayerJson = JSON.parse(fileContent);\n * const { content, report } = convertUnlayerTemplate(unlayerJson);\n *\n * const editor = init({ container: '#editor', content });\n *\n * console.log(report.summary);\n * console.log(report.warnings);\n * ```\n */\nexport function convertUnlayerTemplate(\n template: UnlayerTemplate,\n): ImportResult {\n if (!template?.body?.rows) {\n throw new Error(\n \"Invalid Unlayer template: missing body.rows. Ensure you are passing a valid Unlayer JSON design (the output of editor.saveDesign).\",\n );\n }\n\n const entries: ImportReportEntry[] = [];\n const warnings: string[] = [];\n const blocks: Block[] = [];\n\n const headers = template.body.headers ?? [];\n const footers = template.body.footers ?? [];\n\n if (headers.length > 0) {\n warnings.push(\n `${headers.length} Unlayer header row(s) were imported as regular rows at the top of the template.`,\n );\n for (const row of headers) {\n blocks.push(...processRow(row, entries, warnings));\n }\n }\n\n for (const row of template.body.rows) {\n blocks.push(...processRow(row, entries, warnings));\n }\n\n if (footers.length > 0) {\n warnings.push(\n `${footers.length} Unlayer footer row(s) were imported as regular rows at the bottom of the template.`,\n );\n for (const row of footers) {\n blocks.push(...processRow(row, entries, warnings));\n }\n }\n\n const content: TemplateContent = {\n ...createDefaultTemplateContent(),\n blocks,\n settings: extractSettings(template),\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 {\n createTitleBlock,\n createParagraphBlock,\n createImageBlock,\n createButtonBlock,\n createDividerBlock,\n createSpacerBlock,\n createHtmlBlock,\n createSocialIconsBlock,\n createMenuBlock,\n createVideoBlock,\n generateId,\n} from \"@templatical/types\";\nimport type {\n Block,\n HeadingLevel,\n SocialPlatform,\n SocialIcon,\n MenuItemData,\n} from \"@templatical/types\";\nimport type {\n UnlayerContent,\n UnlayerContentValues,\n ImportReportEntry,\n} from \"./types\";\nimport {\n parsePxValue,\n parseColor,\n parseFontFamily,\n parsePaddingShorthand,\n parseBorderObject,\n parseWidthPercent,\n} from \"./style-parser\";\n\nconst SOCIAL_PLATFORM_MAP: Record<string, SocialPlatform> = {\n facebook: \"facebook\",\n twitter: \"twitter\",\n x: \"twitter\",\n instagram: \"instagram\",\n linkedin: \"linkedin\",\n youtube: \"youtube\",\n tiktok: \"tiktok\",\n pinterest: \"pinterest\",\n email: \"email\",\n mail: \"email\",\n whatsapp: \"whatsapp\",\n telegram: \"telegram\",\n discord: \"discord\",\n snapchat: \"snapchat\",\n reddit: \"reddit\",\n github: \"github\",\n dribbble: \"dribbble\",\n behance: \"behance\",\n};\n\ntype Align = \"left\" | \"center\" | \"right\";\ntype LineStyle = \"solid\" | \"dashed\" | \"dotted\";\n\nfunction toAlign(value: string | undefined, fallback: Align = \"left\"): Align {\n if (value === \"left\" || value === \"center\" || value === \"right\") return value;\n return fallback;\n}\n\n/**\n * Strip every `<…>` from a button label and reject any stray `<` or `>`\n * left behind (e.g. a truncated `<script`). The original\n * `value.replace(/<[^>]*>/g, \"\")` was both polynomial-ReDoS over\n * `<<<<…` inputs and an incomplete sanitizer — an unterminated `<script`\n * would survive the strip. Downstream HTML-escapes the label at render\n * time, but stripping here keeps the imported JSON clean.\n */\nfunction stripTagsPlain(text: string): string {\n let out = \"\";\n let i = 0;\n while (i < text.length) {\n if (text[i] === \"<\") {\n const close = text.indexOf(\">\", i + 1);\n if (close === -1) {\n // Unterminated tag — discard the rest.\n break;\n }\n i = close + 1;\n continue;\n }\n if (text[i] === \">\") {\n i++;\n continue;\n }\n out += text[i];\n i++;\n }\n return out;\n}\n\nfunction toLineStyle(\n value: string | undefined,\n fallback: LineStyle = \"solid\",\n): LineStyle {\n if (value === \"solid\" || value === \"dashed\" || value === \"dotted\")\n return value;\n return fallback;\n}\n\nfunction makeStyles(values: UnlayerContentValues): Block[\"styles\"] {\n const padding = parsePaddingShorthand(values.containerPadding);\n return {\n padding,\n };\n}\n\n/**\n * Apply Unlayer text-level styles as TipTap-compatible inline markup.\n * Mirrors the BeeFree importer's helper but reads from Unlayer's flat\n * values shape rather than a CSS style record.\n */\nfunction inlineStylesToHtml(\n html: string,\n values: UnlayerContentValues,\n): string {\n const spanParts: string[] = [];\n const fontSize = parsePxValue(values.fontSize);\n if (fontSize && fontSize !== 16) spanParts.push(`font-size: ${fontSize}px`);\n const color = parseColor(values.color);\n if (color && color !== \"#1a1a1a\") spanParts.push(`color: ${color}`);\n const fontWeight = values.fontWeight;\n if (\n fontWeight !== undefined &&\n fontWeight !== null &&\n String(fontWeight) !== \"normal\" &&\n String(fontWeight) !== \"400\"\n ) {\n spanParts.push(`font-weight: ${fontWeight}`);\n }\n const fontFamily = parseFontFamily(values.fontFamily);\n if (fontFamily) spanParts.push(`font-family: ${fontFamily}`);\n\n const textAlign = values.textAlign;\n const pStyle =\n textAlign && textAlign !== \"left\" ? `text-align: ${textAlign}` : \"\";\n\n if (!pStyle && spanParts.length === 0) return html;\n\n const spanStyle = spanParts.join(\"; \");\n let result = html;\n\n if (pStyle) {\n result = result\n .replace(/<p style=\"([^\"]*)\">/g, `<p style=\"$1; ${pStyle}\">`)\n .replaceAll(\"<p>\", `<p style=\"${pStyle}\">`);\n }\n\n if (spanStyle) {\n result = wrapParagraphInner(result, spanStyle);\n }\n\n return result;\n}\n\n/**\n * Wrap the inner content of every `<p …>…</p>` with `<span style=\"…\">…</span>`.\n *\n * Hand-rolled linear scanner instead of `/<p([^>]*)>([\\s\\S]*?)<\\/p>/g` because\n * the regex is polynomial-ReDoS: the engine retries `[\\s\\S]*?<\\/p>` at every\n * `<p` start, so inputs like `<p>a<p>a<p>a…` (no `</p>` ever) cost O(n²).\n * The scanner advances `i` monotonically via `indexOf`, keeping the work\n * strictly O(n).\n */\nfunction wrapParagraphInner(html: string, spanStyle: string): string {\n let out = \"\";\n let i = 0;\n while (i < html.length) {\n const open = html.indexOf(\"<p\", i);\n if (open === -1) {\n out += html.substring(i);\n break;\n }\n const afterTagName = html[open + 2];\n if (\n afterTagName !== \">\" &&\n afterTagName !== \" \" &&\n afterTagName !== \"\\t\" &&\n afterTagName !== \"\\n\" &&\n afterTagName !== \"\\r\" &&\n afterTagName !== \"/\"\n ) {\n out += html.substring(i, open + 2);\n i = open + 2;\n continue;\n }\n const openEnd = html.indexOf(\">\", open + 2);\n if (openEnd === -1) {\n out += html.substring(i);\n break;\n }\n const closeStart = html.indexOf(\"</p>\", openEnd + 1);\n if (closeStart === -1) {\n out += html.substring(i);\n break;\n }\n const inner = html.substring(openEnd + 1, closeStart);\n out += html.substring(i, openEnd + 1);\n out += `<span style=\"${spanStyle}\">${inner}</span></p>`;\n i = closeStart + 4;\n }\n return out;\n}\n\nfunction ensureParagraphWrapped(html: string): string {\n if (!html) return \"<p></p>\";\n if (/<p[\\s>]/i.test(html)) return html;\n return `<p>${html}</p>`;\n}\n\nfunction convertText(values: UnlayerContentValues): Block {\n const html = ensureParagraphWrapped(values.text ?? \"\");\n\n return createParagraphBlock({\n content: inlineStylesToHtml(html, values),\n styles: makeStyles(values),\n });\n}\n\nfunction parseHeadingLevel(tag: string | undefined): HeadingLevel {\n if (!tag) return 2;\n const match = tag.match(/^h(\\d)$/i);\n if (match) {\n const num = Number(match[1]);\n if (num >= 1 && num <= 4) return num as HeadingLevel;\n }\n return 2;\n}\n\nfunction convertHeading(values: UnlayerContentValues): Block {\n const text = values.text ?? \"\";\n const stripped = text.replace(/^<h\\d[^>]*>|<\\/h\\d>$/gi, \"\");\n const content = stripped ? `<p>${stripped}</p>` : \"<p></p>\";\n\n return createTitleBlock({\n content,\n level: parseHeadingLevel(values.headingType),\n color: parseColor(values.color) || \"#1a1a1a\",\n textAlign: toAlign(values.textAlign),\n fontFamily: parseFontFamily(values.fontFamily) || undefined,\n styles: makeStyles(values),\n });\n}\n\nfunction convertImage(values: UnlayerContentValues): Block {\n const src = values.src;\n const action = values.action?.values;\n\n return createImageBlock({\n src: src?.url || \"\",\n alt: values.altText || \"\",\n width: src?.width ? Math.round(src.width) : 600,\n align: toAlign(values.textAlign, \"center\"),\n linkUrl: action?.href || undefined,\n linkOpenInNewTab: action?.target === \"_blank\" || undefined,\n styles: makeStyles(values),\n });\n}\n\nfunction convertButton(values: UnlayerContentValues): Block {\n const colors = values.buttonColors ?? {};\n const padding = values.padding\n ? parsePaddingShorthand(values.padding)\n : { top: 12, right: 24, bottom: 12, left: 24 };\n const label = stripTagsPlain(values.text ?? \"Button\");\n const linkValues = values.href?.values;\n\n return createButtonBlock({\n text: label,\n url: linkValues?.href || \"#\",\n openInNewTab: linkValues?.target === \"_blank\" || undefined,\n backgroundColor: parseColor(colors.backgroundColor) || \"#4f46e5\",\n textColor: parseColor(colors.color) || \"#ffffff\",\n borderRadius: parsePxValue(values.borderRadius),\n fontSize: parsePxValue(values.fontSize) || 16,\n fontFamily: parseFontFamily(values.fontFamily) || undefined,\n buttonPadding: padding,\n styles: makeStyles(values),\n });\n}\n\nfunction convertDivider(values: UnlayerContentValues): Block {\n const border = parseBorderObject(values.border);\n\n return createDividerBlock({\n lineStyle: toLineStyle(border.style),\n color: border.color,\n thickness: border.width || 1,\n width: parseWidthPercent(values.width),\n styles: makeStyles(values),\n });\n}\n\nfunction convertSpacer(values: UnlayerContentValues): Block {\n const padding = parsePaddingShorthand(values.containerPadding);\n const height =\n parsePxValue((values as { height?: string | number }).height) ||\n padding.top + padding.bottom ||\n 24;\n\n return createSpacerBlock({\n height,\n styles: makeStyles(values),\n });\n}\n\nfunction convertHtml(values: UnlayerContentValues): Block {\n return createHtmlBlock({\n content: values.html ?? \"\",\n styles: makeStyles(values),\n });\n}\n\nfunction convertSocial(\n values: UnlayerContentValues,\n warnings: string[],\n): Block {\n const iconList = values.icons?.icons ?? [];\n const icons: SocialIcon[] = [];\n\n for (const unlayerIcon of iconList) {\n const id = (unlayerIcon.name ?? \"\").toLowerCase();\n const platform = SOCIAL_PLATFORM_MAP[id];\n\n if (!platform) {\n warnings.push(\n `Unrecognized social icon \"${unlayerIcon.name || id}\" was skipped.`,\n );\n continue;\n }\n\n icons.push({\n id: generateId(),\n platform,\n url: unlayerIcon.url || \"#\",\n });\n }\n\n return createSocialIconsBlock({\n icons,\n align: toAlign(values.textAlign, \"center\"),\n styles: makeStyles(values),\n });\n}\n\nfunction convertVideo(values: UnlayerContentValues): Block {\n return createVideoBlock({\n url: values.videoUrl || \"\",\n thumbnailUrl: values.thumbnailUrl || \"\",\n alt: values.altText || \"\",\n width: 600,\n align: toAlign(values.textAlign, \"center\"),\n styles: makeStyles(values),\n });\n}\n\nfunction convertMenu(values: UnlayerContentValues): Block {\n const menu = values.menu;\n const items: MenuItemData[] = (menu?.items ?? []).map((item) => ({\n id: generateId(),\n text: item.text || \"\",\n url: item.link?.values?.href || \"#\",\n openInNewTab: item.link?.values?.target === \"_blank\",\n bold: false,\n underline: false,\n }));\n\n return createMenuBlock({\n items,\n separator: values.separator || \"|\",\n separatorColor: \"#999999\",\n fontSize: parsePxValue(values.fontSize) || 14,\n color: parseColor(values.color) || \"#1a1a1a\",\n fontFamily: parseFontFamily(values.fontFamily) || undefined,\n textAlign: toAlign(values.textAlign, \"center\"),\n styles: makeStyles(values),\n });\n}\n\nfunction convertHtmlFallback(content: UnlayerContent, comment: string): Block {\n const safe = comment.replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\");\n return createHtmlBlock({\n content: `<div style=\"padding:12px;border:1px dashed #d1d5db;border-radius:6px;background:#fafafa;color:#6b7280;font-family:sans-serif;font-size:13px;\">${safe}</div>`,\n styles: makeStyles(content.values),\n });\n}\n\n/**\n * Converts a single Unlayer content node to a Templatical block.\n * Returns the block and a report entry.\n */\nexport function convertContent(\n content: UnlayerContent,\n warnings: string[],\n): { block: Block; entry: ImportReportEntry } {\n const type = content.type;\n const values = content.values ?? ({} as UnlayerContentValues);\n\n switch (type) {\n case \"text\":\n return {\n block: convertText(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"paragraph\",\n status: \"converted\",\n },\n };\n case \"heading\":\n return {\n block: convertHeading(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"title\",\n status: \"converted\",\n },\n };\n case \"image\":\n return {\n block: convertImage(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"image\",\n status: \"converted\",\n },\n };\n case \"button\":\n return {\n block: convertButton(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"button\",\n status: \"converted\",\n },\n };\n case \"divider\":\n return {\n block: convertDivider(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"divider\",\n status: \"converted\",\n },\n };\n case \"spacer\":\n return {\n block: convertSpacer(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"spacer\",\n status: \"converted\",\n },\n };\n case \"html\":\n return {\n block: convertHtml(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"html\",\n status: \"converted\",\n },\n };\n case \"menu\":\n return {\n block: convertMenu(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"menu\",\n status: \"approximated\",\n note: \"Menu styles map approximately; review spacing and separator color.\",\n },\n };\n case \"social\":\n return {\n block: convertSocial(values, warnings),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"social\",\n status: \"converted\",\n },\n };\n case \"video\":\n return {\n block: convertVideo(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"video\",\n status: \"converted\",\n },\n };\n case \"timer\":\n return {\n block: convertHtmlFallback(\n content,\n \"Unlayer timer block: rebuild manually in Templatical\",\n ),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n note: \"Timer modules have no direct Templatical equivalent; placeholder HTML inserted.\",\n },\n };\n case \"form\":\n return {\n block: convertHtmlFallback(\n content,\n \"Unlayer form block: not supported in Templatical (most email clients block form submission)\",\n ),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: null,\n status: \"skipped\",\n note: \"Unlayer forms have no Templatical equivalent and are skipped. Most email clients block form submission anyway.\",\n },\n };\n default:\n return {\n block: convertHtmlFallback(\n content,\n `Unsupported Unlayer content type: ${type}`,\n ),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n note: `Unknown content type \"${type}\" converted to HTML block.`,\n },\n };\n }\n}\n","import type { SpacingValue } from \"@templatical/types\";\nimport type { UnlayerBorder, UnlayerFontFamily } from \"./types\";\n\n/**\n * Parses CSS-like style values from Unlayer content values.\n */\n\nexport function parsePxValue(value: string | number | undefined): number {\n if (value === undefined || value === null) return 0;\n if (typeof value === \"number\") return Math.round(value);\n // Single trailing-whitespace group with an optional `px` inside it. The\n // original `\\s*(?:px)?\\s*` had two `\\s*` quantifiers around an optional\n // group, which is the textbook polynomial-ReDoS shape (\"how should the\n // whitespace split across the two quantifiers?\") over inputs like\n // `9 ` with no `px`.\n const match = value.match(/^(-?\\d+(?:\\.\\d+)?)\\s*(?:px\\s*)?$/);\n return match ? Math.round(parseFloat(match[1])) : 0;\n}\n\nexport function parseColor(value: string | undefined): string {\n if (!value || value === \"transparent\") return \"\";\n\n const trimmed = value.trim();\n\n if (/^#[0-9a-fA-F]{6}$/.test(trimmed)) return trimmed.toLowerCase();\n\n if (/^#[0-9a-fA-F]{3}$/.test(trimmed)) {\n const r = trimmed[1];\n const g = trimmed[2];\n const b = trimmed[3];\n return `#${r}${r}${g}${g}${b}${b}`.toLowerCase();\n }\n\n return trimmed;\n}\n\nexport function parsePaddingShorthand(value: string | undefined): SpacingValue {\n if (!value) return { top: 0, right: 0, bottom: 0, left: 0 };\n\n const parts = value.trim().split(/\\s+/);\n const values = parts.map((p) => parsePxValue(p));\n\n switch (values.length) {\n case 1:\n return {\n top: values[0],\n right: values[0],\n bottom: values[0],\n left: values[0],\n };\n case 2:\n return {\n top: values[0],\n right: values[1],\n bottom: values[0],\n left: values[1],\n };\n case 3:\n return {\n top: values[0],\n right: values[1],\n bottom: values[2],\n left: values[1],\n };\n default:\n return {\n top: values[0],\n right: values[1],\n bottom: values[2],\n left: values[3],\n };\n }\n}\n\nexport function parseBorderObject(border: UnlayerBorder | undefined): {\n width: number;\n style: string;\n color: string;\n} {\n if (!border) return { width: 0, style: \"solid\", color: \"#000000\" };\n return {\n width: parsePxValue(border.borderTopWidth),\n style: border.borderTopStyle || \"solid\",\n color: parseColor(border.borderTopColor) || \"#000000\",\n };\n}\n\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\nexport function parseFontFamily(\n value: UnlayerFontFamily | string | undefined,\n): string {\n if (!value) return \"\";\n if (typeof value === \"string\") {\n return value.split(\",\")[0].trim().replace(/['\"]/g, \"\");\n }\n if (value.value) return value.value.split(\",\")[0].trim().replace(/['\"]/g, \"\");\n if (value.label) return value.label;\n return \"\";\n}\n"],"mappings":"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","names":[]}
{"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\";\nimport type { UnlayerBorder, UnlayerFontFamily } from \"./types\";\n\n/**\n * Parses CSS-like style values from Unlayer content values.\n */\n\nexport function parsePxValue(value: string | number | undefined): number {\n if (value === undefined || value === null) return 0;\n if (typeof value === \"number\") return Math.round(value);\n // Single trailing-whitespace group with an optional `px` inside it. The\n // original `\\s*(?:px)?\\s*` had two `\\s*` quantifiers around an optional\n // group, which is the textbook polynomial-ReDoS shape (\"how should the\n // whitespace split across the two quantifiers?\") over inputs like\n // `9 ` with no `px`.\n const match = value.match(/^(-?\\d+(?:\\.\\d+)?)\\s*(?:px\\s*)?$/);\n return match ? Math.round(parseFloat(match[1])) : 0;\n}\n\nexport function parseColor(value: string | undefined): string {\n if (!value || value === \"transparent\") return \"\";\n\n const trimmed = value.trim();\n\n if (/^#[0-9a-fA-F]{6}$/.test(trimmed)) return trimmed.toLowerCase();\n\n if (/^#[0-9a-fA-F]{3}$/.test(trimmed)) {\n const r = trimmed[1];\n const g = trimmed[2];\n const b = trimmed[3];\n return `#${r}${r}${g}${g}${b}${b}`.toLowerCase();\n }\n\n return trimmed;\n}\n\nexport function parsePaddingShorthand(value: string | undefined): SpacingValue {\n if (!value) return { top: 0, right: 0, bottom: 0, left: 0 };\n\n const parts = value.trim().split(/\\s+/);\n const values = parts.map((p) => parsePxValue(p));\n\n switch (values.length) {\n case 1:\n return {\n top: values[0],\n right: values[0],\n bottom: values[0],\n left: values[0],\n };\n case 2:\n return {\n top: values[0],\n right: values[1],\n bottom: values[0],\n left: values[1],\n };\n case 3:\n return {\n top: values[0],\n right: values[1],\n bottom: values[2],\n left: values[1],\n };\n default:\n return {\n top: values[0],\n right: values[1],\n bottom: values[2],\n left: values[3],\n };\n }\n}\n\nexport function parseBorderObject(border: UnlayerBorder | undefined): {\n width: number;\n style: string;\n color: string;\n} {\n if (!border) return { width: 0, style: \"solid\", color: \"#000000\" };\n return {\n width: parsePxValue(border.borderTopWidth),\n style: border.borderTopStyle || \"solid\",\n color: parseColor(border.borderTopColor) || \"#000000\",\n };\n}\n\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\nexport function parseFontFamily(\n value: UnlayerFontFamily | string | undefined,\n): string {\n if (!value) return \"\";\n if (typeof value === \"string\") {\n return value.split(\",\")[0].trim().replace(/['\"]/g, \"\");\n }\n if (value.value) return value.value.split(\",\")[0].trim().replace(/['\"]/g, \"\");\n if (value.label) return value.label;\n return \"\";\n}\n","import {\n createTitleBlock,\n createParagraphBlock,\n createImageBlock,\n createButtonBlock,\n createDividerBlock,\n createSpacerBlock,\n createHtmlBlock,\n createSocialIconsBlock,\n createMenuBlock,\n createVideoBlock,\n generateId,\n} from \"@templatical/types\";\nimport type {\n Block,\n HeadingLevel,\n SocialPlatform,\n SocialIcon,\n MenuItemData,\n} from \"@templatical/types\";\nimport type {\n UnlayerContent,\n UnlayerContentValues,\n ImportReportEntry,\n} from \"./types\";\nimport {\n parsePxValue,\n parseColor,\n parseFontFamily,\n parsePaddingShorthand,\n parseBorderObject,\n parseWidthPercent,\n} from \"./style-parser\";\n\nconst SOCIAL_PLATFORM_MAP: Record<string, SocialPlatform> = {\n facebook: \"facebook\",\n twitter: \"twitter\",\n x: \"twitter\",\n instagram: \"instagram\",\n linkedin: \"linkedin\",\n youtube: \"youtube\",\n tiktok: \"tiktok\",\n pinterest: \"pinterest\",\n email: \"email\",\n mail: \"email\",\n whatsapp: \"whatsapp\",\n telegram: \"telegram\",\n discord: \"discord\",\n snapchat: \"snapchat\",\n reddit: \"reddit\",\n github: \"github\",\n dribbble: \"dribbble\",\n behance: \"behance\",\n};\n\ntype Align = \"left\" | \"center\" | \"right\";\ntype LineStyle = \"solid\" | \"dashed\" | \"dotted\";\n\nfunction toAlign(value: string | undefined, fallback: Align = \"left\"): Align {\n if (value === \"left\" || value === \"center\" || value === \"right\") return value;\n return fallback;\n}\n\n/**\n * Strip every `<…>` from a button label and reject any stray `<` or `>`\n * left behind (e.g. a truncated `<script`). The original\n * `value.replace(/<[^>]*>/g, \"\")` was both polynomial-ReDoS over\n * `<<<<…` inputs and an incomplete sanitizer — an unterminated `<script`\n * would survive the strip. Downstream HTML-escapes the label at render\n * time, but stripping here keeps the imported JSON clean.\n */\nfunction stripTagsPlain(text: string): string {\n let out = \"\";\n let i = 0;\n while (i < text.length) {\n if (text[i] === \"<\") {\n const close = text.indexOf(\">\", i + 1);\n if (close === -1) {\n // Unterminated tag — discard the rest.\n break;\n }\n i = close + 1;\n continue;\n }\n if (text[i] === \">\") {\n i++;\n continue;\n }\n out += text[i];\n i++;\n }\n return out;\n}\n\nfunction toLineStyle(\n value: string | undefined,\n fallback: LineStyle = \"solid\",\n): LineStyle {\n if (value === \"solid\" || value === \"dashed\" || value === \"dotted\")\n return value;\n return fallback;\n}\n\nfunction makeStyles(values: UnlayerContentValues): Block[\"styles\"] {\n const padding = parsePaddingShorthand(values.containerPadding);\n return {\n padding,\n };\n}\n\n/**\n * Apply Unlayer text-level styles as TipTap-compatible inline markup.\n * Mirrors the BeeFree importer's helper but reads from Unlayer's flat\n * values shape rather than a CSS style record.\n */\nfunction inlineStylesToHtml(\n html: string,\n values: UnlayerContentValues,\n): string {\n const spanParts: string[] = [];\n const fontSize = parsePxValue(values.fontSize);\n if (fontSize && fontSize !== 16) spanParts.push(`font-size: ${fontSize}px`);\n const color = parseColor(values.color);\n if (color && color !== \"#1a1a1a\") spanParts.push(`color: ${color}`);\n const fontWeight = values.fontWeight;\n if (\n fontWeight !== undefined &&\n fontWeight !== null &&\n String(fontWeight) !== \"normal\" &&\n String(fontWeight) !== \"400\"\n ) {\n spanParts.push(`font-weight: ${fontWeight}`);\n }\n const fontFamily = parseFontFamily(values.fontFamily);\n if (fontFamily) spanParts.push(`font-family: ${fontFamily}`);\n\n const textAlign = values.textAlign;\n const pStyle =\n textAlign && textAlign !== \"left\" ? `text-align: ${textAlign}` : \"\";\n\n if (!pStyle && spanParts.length === 0) return html;\n\n const spanStyle = spanParts.join(\"; \");\n let result = html;\n\n if (pStyle) {\n result = result\n .replace(/<p style=\"([^\"]*)\">/g, `<p style=\"$1; ${pStyle}\">`)\n .replaceAll(\"<p>\", `<p style=\"${pStyle}\">`);\n }\n\n if (spanStyle) {\n result = wrapParagraphInner(result, spanStyle);\n }\n\n return result;\n}\n\n/**\n * Wrap the inner content of every `<p …>…</p>` with `<span style=\"…\">…</span>`.\n *\n * Hand-rolled linear scanner instead of `/<p([^>]*)>([\\s\\S]*?)<\\/p>/g` because\n * the regex is polynomial-ReDoS: the engine retries `[\\s\\S]*?<\\/p>` at every\n * `<p` start, so inputs like `<p>a<p>a<p>a…` (no `</p>` ever) cost O(n²).\n * The scanner advances `i` monotonically via `indexOf`, keeping the work\n * strictly O(n).\n */\nfunction wrapParagraphInner(html: string, spanStyle: string): string {\n let out = \"\";\n let i = 0;\n while (i < html.length) {\n const open = html.indexOf(\"<p\", i);\n if (open === -1) {\n out += html.substring(i);\n break;\n }\n const afterTagName = html[open + 2];\n if (\n afterTagName !== \">\" &&\n afterTagName !== \" \" &&\n afterTagName !== \"\\t\" &&\n afterTagName !== \"\\n\" &&\n afterTagName !== \"\\r\" &&\n afterTagName !== \"/\"\n ) {\n out += html.substring(i, open + 2);\n i = open + 2;\n continue;\n }\n const openEnd = html.indexOf(\">\", open + 2);\n if (openEnd === -1) {\n out += html.substring(i);\n break;\n }\n const closeStart = html.indexOf(\"</p>\", openEnd + 1);\n if (closeStart === -1) {\n out += html.substring(i);\n break;\n }\n const inner = html.substring(openEnd + 1, closeStart);\n out += html.substring(i, openEnd + 1);\n out += `<span style=\"${spanStyle}\">${inner}</span></p>`;\n i = closeStart + 4;\n }\n return out;\n}\n\nfunction ensureParagraphWrapped(html: string): string {\n if (!html) return \"<p></p>\";\n if (/<p[\\s>]/i.test(html)) return html;\n return `<p>${html}</p>`;\n}\n\nfunction convertText(values: UnlayerContentValues): Block {\n const html = ensureParagraphWrapped(values.text ?? \"\");\n\n return createParagraphBlock({\n content: inlineStylesToHtml(html, values),\n styles: makeStyles(values),\n });\n}\n\nfunction parseHeadingLevel(tag: string | undefined): HeadingLevel {\n if (!tag) return 2;\n const match = tag.match(/^h(\\d)$/i);\n if (match) {\n const num = Number(match[1]);\n if (num >= 1 && num <= 4) return num as HeadingLevel;\n }\n return 2;\n}\n\nfunction convertHeading(values: UnlayerContentValues): Block {\n const text = values.text ?? \"\";\n const stripped = text.replace(/^<h\\d[^>]*>|<\\/h\\d>$/gi, \"\");\n const content = stripped ? `<p>${stripped}</p>` : \"<p></p>\";\n\n return createTitleBlock({\n content,\n level: parseHeadingLevel(values.headingType),\n color: parseColor(values.color) || \"#1a1a1a\",\n textAlign: toAlign(values.textAlign),\n fontFamily: parseFontFamily(values.fontFamily) || undefined,\n styles: makeStyles(values),\n });\n}\n\nfunction convertImage(values: UnlayerContentValues): Block {\n const src = values.src;\n const action = values.action?.values;\n\n return createImageBlock({\n src: src?.url || \"\",\n alt: values.altText || \"\",\n width: src?.width ? Math.round(src.width) : 600,\n align: toAlign(values.textAlign, \"center\"),\n linkUrl: action?.href || undefined,\n linkOpenInNewTab: action?.target === \"_blank\" || undefined,\n styles: makeStyles(values),\n });\n}\n\nfunction convertButton(values: UnlayerContentValues): Block {\n const colors = values.buttonColors ?? {};\n const padding = values.padding\n ? parsePaddingShorthand(values.padding)\n : { top: 12, right: 24, bottom: 12, left: 24 };\n const label = stripTagsPlain(values.text ?? \"Button\");\n const linkValues = values.href?.values;\n\n return createButtonBlock({\n text: label,\n url: linkValues?.href || \"#\",\n openInNewTab: linkValues?.target === \"_blank\" || undefined,\n backgroundColor: parseColor(colors.backgroundColor) || \"#4f46e5\",\n textColor: parseColor(colors.color) || \"#ffffff\",\n borderRadius: parsePxValue(values.borderRadius),\n fontSize: parsePxValue(values.fontSize) || 16,\n fontFamily: parseFontFamily(values.fontFamily) || undefined,\n buttonPadding: padding,\n styles: makeStyles(values),\n });\n}\n\nfunction convertDivider(values: UnlayerContentValues): Block {\n const border = parseBorderObject(values.border);\n\n return createDividerBlock({\n lineStyle: toLineStyle(border.style),\n color: border.color,\n thickness: border.width || 1,\n width: parseWidthPercent(values.width),\n styles: makeStyles(values),\n });\n}\n\nfunction convertSpacer(values: UnlayerContentValues): Block {\n const padding = parsePaddingShorthand(values.containerPadding);\n const height =\n parsePxValue((values as { height?: string | number }).height) ||\n padding.top + padding.bottom ||\n 24;\n\n return createSpacerBlock({\n height,\n styles: makeStyles(values),\n });\n}\n\nfunction convertHtml(values: UnlayerContentValues): Block {\n return createHtmlBlock({\n content: values.html ?? \"\",\n styles: makeStyles(values),\n });\n}\n\nfunction convertSocial(\n values: UnlayerContentValues,\n warnings: string[],\n): Block {\n const iconList = values.icons?.icons ?? [];\n const icons: SocialIcon[] = [];\n\n for (const unlayerIcon of iconList) {\n const id = (unlayerIcon.name ?? \"\").toLowerCase();\n const platform = SOCIAL_PLATFORM_MAP[id];\n\n if (!platform) {\n warnings.push(\n `Unrecognized social icon \"${unlayerIcon.name || id}\" was skipped.`,\n );\n continue;\n }\n\n icons.push({\n id: generateId(),\n platform,\n url: unlayerIcon.url || \"#\",\n });\n }\n\n return createSocialIconsBlock({\n icons,\n align: toAlign(values.textAlign, \"center\"),\n styles: makeStyles(values),\n });\n}\n\nfunction convertVideo(values: UnlayerContentValues): Block {\n return createVideoBlock({\n url: values.videoUrl || \"\",\n thumbnailUrl: values.thumbnailUrl || \"\",\n alt: values.altText || \"\",\n width: 600,\n align: toAlign(values.textAlign, \"center\"),\n styles: makeStyles(values),\n });\n}\n\nfunction convertMenu(values: UnlayerContentValues): Block {\n const menu = values.menu;\n const items: MenuItemData[] = (menu?.items ?? []).map((item) => ({\n id: generateId(),\n text: item.text || \"\",\n url: item.link?.values?.href || \"#\",\n openInNewTab: item.link?.values?.target === \"_blank\",\n bold: false,\n underline: false,\n }));\n\n return createMenuBlock({\n items,\n separator: values.separator || \"|\",\n separatorColor: \"#999999\",\n fontSize: parsePxValue(values.fontSize) || 14,\n color: parseColor(values.color) || \"#1a1a1a\",\n fontFamily: parseFontFamily(values.fontFamily) || undefined,\n textAlign: toAlign(values.textAlign, \"center\"),\n styles: makeStyles(values),\n });\n}\n\nfunction convertHtmlFallback(content: UnlayerContent, comment: string): Block {\n const safe = comment.replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\");\n return createHtmlBlock({\n content: `<div style=\"padding:12px;border:1px dashed #d1d5db;border-radius:6px;background:#fafafa;color:#6b7280;font-family:sans-serif;font-size:13px;\">${safe}</div>`,\n styles: makeStyles(content.values),\n });\n}\n\n/**\n * Converts a single Unlayer content node to a Templatical block.\n * Returns the block and a report entry.\n */\nexport function convertContent(\n content: UnlayerContent,\n warnings: string[],\n): { block: Block; entry: ImportReportEntry } {\n const type = content.type;\n const values = content.values ?? ({} as UnlayerContentValues);\n\n switch (type) {\n case \"text\":\n return {\n block: convertText(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"paragraph\",\n status: \"converted\",\n },\n };\n case \"heading\":\n return {\n block: convertHeading(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"title\",\n status: \"converted\",\n },\n };\n case \"image\":\n return {\n block: convertImage(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"image\",\n status: \"converted\",\n },\n };\n case \"button\":\n return {\n block: convertButton(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"button\",\n status: \"converted\",\n },\n };\n case \"divider\":\n return {\n block: convertDivider(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"divider\",\n status: \"converted\",\n },\n };\n case \"spacer\":\n return {\n block: convertSpacer(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"spacer\",\n status: \"converted\",\n },\n };\n case \"html\":\n return {\n block: convertHtml(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"html\",\n status: \"converted\",\n },\n };\n case \"menu\":\n return {\n block: convertMenu(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"menu\",\n status: \"approximated\",\n note: \"Menu styles map approximately; review spacing and separator color.\",\n },\n };\n case \"social\":\n return {\n block: convertSocial(values, warnings),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"social\",\n status: \"converted\",\n },\n };\n case \"video\":\n return {\n block: convertVideo(values),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"video\",\n status: \"converted\",\n },\n };\n case \"timer\":\n return {\n block: convertHtmlFallback(\n content,\n \"Unlayer timer block: rebuild manually in Templatical\",\n ),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n note: \"Timer modules have no direct Templatical equivalent; placeholder HTML inserted.\",\n },\n };\n case \"form\":\n return {\n block: convertHtmlFallback(\n content,\n \"Unlayer form block: not supported in Templatical (most email clients block form submission)\",\n ),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: null,\n status: \"skipped\",\n note: \"Unlayer forms have no Templatical equivalent and are skipped. Most email clients block form submission anyway.\",\n },\n };\n default:\n return {\n block: convertHtmlFallback(\n content,\n `Unsupported Unlayer content type: ${type}`,\n ),\n entry: {\n unlayerContentType: type,\n templaticalBlockType: \"html\",\n status: \"html-fallback\",\n note: `Unknown content type \"${type}\" converted to HTML block.`,\n },\n };\n }\n}\n","import {\n createSectionBlock,\n createDefaultTemplateContent,\n} from \"@templatical/types\";\nimport type { Block, ColumnLayout, TemplateContent } from \"@templatical/types\";\nimport type {\n UnlayerTemplate,\n UnlayerRow,\n UnlayerColumn,\n ImportResult,\n ImportReport,\n ImportReportEntry,\n} from \"./types\";\nimport { convertContent } from \"./block-mapper\";\nimport {\n parsePxValue,\n parseColor,\n parseFontFamily,\n parsePaddingShorthand,\n} from \"./style-parser\";\n\nfunction resolveColumnLayout(\n cells: number[],\n warnings: string[],\n): ColumnLayout {\n if (cells.length <= 1) return \"1\";\n if (cells.length === 3) return \"3\";\n\n if (cells.length === 2) {\n const left = cells[0] ?? 1;\n const right = cells[1] ?? 1;\n const total = left + right;\n const ratio = left / total;\n\n if (ratio > 0.58) return \"2-1\";\n if (ratio < 0.42) return \"1-2\";\n return \"2\";\n }\n\n warnings.push(\n `Row with ${cells.length} columns was flattened to a single column. Unlayer supports arbitrary columns, but Templatical supports up to 3.`,\n );\n return \"1\";\n}\n\n/**\n * Converts all contents in a column to Templatical blocks.\n */\nfunction convertColumnContents(\n column: UnlayerColumn,\n entries: ImportReportEntry[],\n warnings: string[],\n): Block[] {\n const blocks: Block[] = [];\n\n for (const content of column.contents ?? []) {\n const { block, entry } = convertContent(content, warnings);\n blocks.push(block);\n entries.push(entry);\n }\n\n return blocks;\n}\n\n/**\n * Processes a single Unlayer row into one or more Templatical blocks.\n */\nfunction processRow(\n row: UnlayerRow,\n entries: ImportReportEntry[],\n warnings: string[],\n): Block[] {\n const columns = row.columns;\n if (!columns || columns.length === 0) return [];\n\n const cells = row.cells ?? columns.map(() => 1);\n const layout = resolveColumnLayout(cells, warnings);\n\n let children: Block[][];\n if (layout === \"1\") {\n const merged: Block[] = [];\n for (const column of columns) {\n merged.push(...convertColumnContents(column, entries, warnings));\n }\n children = [merged];\n } else {\n children = columns.map((col) =>\n convertColumnContents(col, entries, warnings),\n );\n }\n\n const rowBg = parseColor(row.values?.backgroundColor);\n const padding = parsePaddingShorthand(row.values?.padding);\n\n const section = createSectionBlock({\n columns: layout,\n children,\n styles: {\n padding,\n ...(rowBg ? { backgroundColor: rowBg } : {}),\n },\n });\n\n return [section];\n}\n\n/**\n * Extracts template-level settings from the Unlayer body values.\n */\nfunction extractSettings(\n template: UnlayerTemplate,\n): TemplateContent[\"settings\"] {\n const values = template.body?.values ?? {};\n\n const width = parsePxValue(values.contentWidth);\n const bgColor = parseColor(values.backgroundColor) || \"#ffffff\";\n const fontFamily = parseFontFamily(values.fontFamily) || \"Arial\";\n\n return {\n width: width || 600,\n backgroundColor: bgColor,\n fontFamily,\n locale: \"en\",\n };\n}\n\n/**\n * Converts an Unlayer design JSON to Templatical TemplateContent.\n *\n * @param template - The parsed Unlayer JSON object (the result of `editor.saveDesign(...)`)\n * @returns An ImportResult with the converted content and a detailed report\n *\n * @example\n * ```ts\n * import { convertUnlayerTemplate } from '@templatical/import-unlayer';\n *\n * const unlayerJson = JSON.parse(fileContent);\n * const { content, report } = convertUnlayerTemplate(unlayerJson);\n *\n * const editor = init({ container: '#editor', content });\n *\n * console.log(report.summary);\n * console.log(report.warnings);\n * ```\n */\nexport function convertUnlayerTemplate(\n template: UnlayerTemplate,\n): ImportResult {\n if (!template?.body?.rows) {\n throw new Error(\n \"Invalid Unlayer template: missing body.rows. Ensure you are passing a valid Unlayer JSON design (the output of editor.saveDesign).\",\n );\n }\n\n const entries: ImportReportEntry[] = [];\n const warnings: string[] = [];\n const blocks: Block[] = [];\n\n const headers = template.body.headers ?? [];\n const footers = template.body.footers ?? [];\n\n if (headers.length > 0) {\n warnings.push(\n `${headers.length} Unlayer header row(s) were imported as regular rows at the top of the template.`,\n );\n for (const row of headers) {\n blocks.push(...processRow(row, entries, warnings));\n }\n }\n\n for (const row of template.body.rows) {\n blocks.push(...processRow(row, entries, warnings));\n }\n\n if (footers.length > 0) {\n warnings.push(\n `${footers.length} Unlayer footer row(s) were imported as regular rows at the bottom of the template.`,\n );\n for (const row of footers) {\n blocks.push(...processRow(row, entries, warnings));\n }\n }\n\n const content: TemplateContent = {\n ...createDefaultTemplateContent(),\n blocks,\n settings: extractSettings(template),\n };\n\n const summary = {\n total: entries.length,\n converted: entries.filter((e) => e.status === \"converted\").length,\n approximated: entries.filter((e) => e.status === \"approximated\").length,\n htmlFallback: entries.filter((e) => e.status === \"html-fallback\").length,\n skipped: entries.filter((e) => e.status === \"skipped\").length,\n };\n\n const report: ImportReport = { entries, warnings, summary };\n\n return { content, report };\n}\n"],"mappings":"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"}
{
"name": "@templatical/import-unlayer",
"description": "Convert Unlayer email templates to Templatical format",
"version": "0.10.0",
"version": "0.10.1",
"bugs": "https://github.com/templatical/sdk/issues",
"dependencies": {
"@templatical/types": "0.10.0"
"@templatical/types": "0.10.1"
},
"devDependencies": {
"tsup": "^8.5.1",
"typescript": "^6.0.3",

@@ -45,3 +44,3 @@ "vitest": "^4.1.7"

"scripts": {
"build": "tsup",
"build": "tsdown",
"test": "vitest run --config vitest.config.ts",

@@ -48,0 +47,0 @@ "typecheck": "tsc --noEmit"