@templatical/import-chamaileon 0.45.0 → 0.47.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 +260 -20
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_TEMPLATE_DEFAULTS, createButtonBlock, createDefaultTemplateContent, createDividerBlock, createHtmlBlock, createImageBlock, createParagraphBlock, createSectionBlock, createSocialIconsBlock, createTitleBlock, createVideoBlock, findOpenTagEnd, generateId } from "@templatical/types";
|
|
1
|
+
import { DEFAULT_TEMPLATE_DEFAULTS, canonicalFontStack, createButtonBlock, createDefaultTemplateContent, createDividerBlock, createHtmlBlock, createImageBlock, createParagraphBlock, createSectionBlock, createSocialIconsBlock, createTitleBlock, createVideoBlock, findOpenTagEnd, generateId } from "@templatical/types";
|
|
2
2
|
//#region src/normalize.ts
|
|
3
3
|
function isUnset(value) {
|
|
4
4
|
if (value === null || value === void 0) return true;
|
|
@@ -176,11 +176,210 @@ function parseLineStyle(value) {
|
|
|
176
176
|
color: border.color
|
|
177
177
|
};
|
|
178
178
|
}
|
|
179
|
-
/**
|
|
180
|
-
function
|
|
181
|
-
if (isUnset(value)
|
|
182
|
-
|
|
183
|
-
|
|
179
|
+
/** The font stack to store, or `undefined` when `value` sets no font. */
|
|
180
|
+
function fontStack(value) {
|
|
181
|
+
if (isUnset(value)) return void 0;
|
|
182
|
+
return canonicalFontStack(value) || void 0;
|
|
183
|
+
}
|
|
184
|
+
const X_GRID = [
|
|
185
|
+
"left",
|
|
186
|
+
"center",
|
|
187
|
+
"right"
|
|
188
|
+
];
|
|
189
|
+
const Y_GRID = [
|
|
190
|
+
"top",
|
|
191
|
+
"center",
|
|
192
|
+
"bottom"
|
|
193
|
+
];
|
|
194
|
+
const PERCENT = /^(-?(?:\d+(?:\.\d+)?|\.\d+))%$/;
|
|
195
|
+
const LENGTH = /^-?(?:\d+(?:\.\d+)?|\.\d+)[a-z]*$/;
|
|
196
|
+
/**
|
|
197
|
+
* `background-size` as a keyword. A size with a side at 100%, or a px width
|
|
198
|
+
* spanning the box, fills it as `cover` does; any other length or percentage
|
|
199
|
+
* draws the image at a size of its own, which `auto` is closest to.
|
|
200
|
+
*/
|
|
201
|
+
function backgroundSizeKeyword(raw, boxWidth) {
|
|
202
|
+
const parts = raw.toLowerCase().split(/\s+/);
|
|
203
|
+
const [first] = parts;
|
|
204
|
+
if (parts.length === 1 && (first === "cover" || first === "contain")) return {
|
|
205
|
+
size: first,
|
|
206
|
+
exact: true
|
|
207
|
+
};
|
|
208
|
+
if (parts.every((part) => part === "auto")) return {
|
|
209
|
+
size: "auto",
|
|
210
|
+
exact: true
|
|
211
|
+
};
|
|
212
|
+
const px = /^(\d+(?:\.\d+)?|\.\d+)px$/.exec(first);
|
|
213
|
+
return {
|
|
214
|
+
size: parts.includes("100%") || px !== null && boxWidth !== void 0 && Number(px[1]) >= boxWidth ? "cover" : "auto",
|
|
215
|
+
exact: false
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* `background-position` split into x and y the way MJML and CSS read it: a
|
|
220
|
+
* lone `top` / `bottom` names y and centres x, any other lone value is x
|
|
221
|
+
* with y centred, and a pair is x then y unless it leads with `top` /
|
|
222
|
+
* `bottom` (or with `center` before `left` / `right`). Three- and four-value
|
|
223
|
+
* edge offsets keep their edge keywords only.
|
|
224
|
+
*/
|
|
225
|
+
function splitBackgroundPosition(raw) {
|
|
226
|
+
const parts = raw.toLowerCase().split(/\s+/);
|
|
227
|
+
if (parts.length === 1) {
|
|
228
|
+
const [only] = parts;
|
|
229
|
+
return only === "top" || only === "bottom" ? {
|
|
230
|
+
x: "center",
|
|
231
|
+
y: only,
|
|
232
|
+
exact: true
|
|
233
|
+
} : {
|
|
234
|
+
x: only,
|
|
235
|
+
y: "center",
|
|
236
|
+
exact: true
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
if (parts.length === 2) {
|
|
240
|
+
const [a, b] = parts;
|
|
241
|
+
return a === "top" || a === "bottom" || a === "center" && (b === "left" || b === "right") ? {
|
|
242
|
+
x: b,
|
|
243
|
+
y: a,
|
|
244
|
+
exact: true
|
|
245
|
+
} : {
|
|
246
|
+
x: a,
|
|
247
|
+
y: b,
|
|
248
|
+
exact: true
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
x: parts.find((part) => part === "left" || part === "right") ?? "center",
|
|
253
|
+
y: parts.find((part) => part === "top" || part === "bottom") ?? "center",
|
|
254
|
+
exact: false
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* One axis as a grid point: its keyword; the point nearest a percentage,
|
|
259
|
+
* ties going to the centre; or the start edge for a length, which is an
|
|
260
|
+
* offset from it. `null` when the token is unreadable.
|
|
261
|
+
*/
|
|
262
|
+
function backgroundAxisPoint(token, grid) {
|
|
263
|
+
const keyword = grid.find((point) => point === token);
|
|
264
|
+
if (keyword) return {
|
|
265
|
+
point: keyword,
|
|
266
|
+
exact: true
|
|
267
|
+
};
|
|
268
|
+
const percent = PERCENT.exec(token);
|
|
269
|
+
if (percent) {
|
|
270
|
+
const n = Number(percent[1]);
|
|
271
|
+
return {
|
|
272
|
+
point: n < 25 ? grid[0] : n > 75 ? grid[2] : grid[1],
|
|
273
|
+
exact: n === 0 || n === 50 || n === 100
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
if (LENGTH.test(token)) return {
|
|
277
|
+
point: grid[0],
|
|
278
|
+
exact: parseFloat(token) === 0
|
|
279
|
+
};
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* `background-repeat`: unrepeated only when every axis is `no-repeat`.
|
|
284
|
+
* Anything but plain `repeat` tiles too, inexactly.
|
|
285
|
+
*/
|
|
286
|
+
function backgroundRepeatFlag(raw) {
|
|
287
|
+
const parts = raw.toLowerCase().split(/\s+/);
|
|
288
|
+
if (parts.every((part) => part === "no-repeat")) return {
|
|
289
|
+
repeat: false,
|
|
290
|
+
exact: true
|
|
291
|
+
};
|
|
292
|
+
return {
|
|
293
|
+
repeat: true,
|
|
294
|
+
exact: parts.every((part) => part === "repeat")
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
/** CSS's initial values: what a browser paints for a longhand Chamaileon leaves out. */
|
|
298
|
+
const CSS_BACKGROUND_DEFAULTS = {
|
|
299
|
+
size: "auto",
|
|
300
|
+
x: "left",
|
|
301
|
+
y: "top",
|
|
302
|
+
repeat: true
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
305
|
+
* The URL inside the first CSS `url(…)`, unquoted and trimmed; "" when there
|
|
306
|
+
* is none or it is malformed. A forward scan rather than a regex, so a long
|
|
307
|
+
* run of unclosed `url(` can't make it backtrack.
|
|
308
|
+
*/
|
|
309
|
+
function cssUrl(value) {
|
|
310
|
+
const text = value ?? "";
|
|
311
|
+
const start = text.search(/url\(/i);
|
|
312
|
+
if (start === -1) return "";
|
|
313
|
+
let i = start + 4;
|
|
314
|
+
while (i < text.length && /\s/.test(text[i])) i++;
|
|
315
|
+
const quote = text[i];
|
|
316
|
+
if (quote === "\"" || quote === "'") {
|
|
317
|
+
const close = text.indexOf(quote, i + 1);
|
|
318
|
+
if (close === -1) return "";
|
|
319
|
+
let j = close + 1;
|
|
320
|
+
while (j < text.length && /\s/.test(text[j])) j++;
|
|
321
|
+
return text[j] === ")" ? text.slice(i + 1, close).trim() : "";
|
|
322
|
+
}
|
|
323
|
+
const close = text.indexOf(")", i);
|
|
324
|
+
if (close === -1) return "";
|
|
325
|
+
const inner = text.slice(i, close);
|
|
326
|
+
return /["']/.test(inner) ? "" : inner.trim();
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* A background image from a Chamaileon style map, `null` when
|
|
330
|
+
* `${prefix}Image` holds no `url()`. `prefix` picks the band
|
|
331
|
+
* (`background*`, also a box's and the body's) or a fullwidth's content area
|
|
332
|
+
* (`contentBackground*`). Unset longhands take CSS's initial values; a value
|
|
333
|
+
* with no exact keyword takes the nearest one and adds a note naming it.
|
|
334
|
+
*/
|
|
335
|
+
function readChamaileonBackgroundImage(style, prefix, label, boxWidth) {
|
|
336
|
+
const value = (suffix) => {
|
|
337
|
+
const raw = styleValue(style, `${prefix}${suffix}`);
|
|
338
|
+
return isUnset(raw) ? void 0 : String(raw).trim();
|
|
339
|
+
};
|
|
340
|
+
const url = cssUrl(value("Image"));
|
|
341
|
+
if (!url) return null;
|
|
342
|
+
const notes = [];
|
|
343
|
+
const lossy = (name, raw, shown) => {
|
|
344
|
+
notes.push(`${label} ${name} "${raw}" has no exact Templatical equivalent; imported as "${shown}".`);
|
|
345
|
+
};
|
|
346
|
+
let size = CSS_BACKGROUND_DEFAULTS.size;
|
|
347
|
+
const rawSize = value("Size");
|
|
348
|
+
if (rawSize) {
|
|
349
|
+
const parsed = backgroundSizeKeyword(rawSize, boxWidth);
|
|
350
|
+
size = parsed.size;
|
|
351
|
+
if (!parsed.exact) lossy("background-size", rawSize, size);
|
|
352
|
+
}
|
|
353
|
+
let x = CSS_BACKGROUND_DEFAULTS.x;
|
|
354
|
+
let y = CSS_BACKGROUND_DEFAULTS.y;
|
|
355
|
+
const rawPosition = value("Position");
|
|
356
|
+
if (rawPosition) {
|
|
357
|
+
const split = splitBackgroundPosition(rawPosition);
|
|
358
|
+
const readX = backgroundAxisPoint(split.x, X_GRID);
|
|
359
|
+
const readY = backgroundAxisPoint(split.y, Y_GRID);
|
|
360
|
+
x = readX?.point ?? x;
|
|
361
|
+
y = readY?.point ?? y;
|
|
362
|
+
if (!split.exact || !readX?.exact || !readY?.exact) lossy("background-position", rawPosition, `${x} ${y}`);
|
|
363
|
+
}
|
|
364
|
+
let repeat = CSS_BACKGROUND_DEFAULTS.repeat;
|
|
365
|
+
const rawRepeat = value("Repeat");
|
|
366
|
+
if (rawRepeat) {
|
|
367
|
+
const parsed = backgroundRepeatFlag(rawRepeat);
|
|
368
|
+
repeat = parsed.repeat;
|
|
369
|
+
if (!parsed.exact) lossy("background-repeat", rawRepeat, repeat ? "repeat" : "no-repeat");
|
|
370
|
+
}
|
|
371
|
+
return {
|
|
372
|
+
image: {
|
|
373
|
+
url,
|
|
374
|
+
size,
|
|
375
|
+
position: {
|
|
376
|
+
x,
|
|
377
|
+
y
|
|
378
|
+
},
|
|
379
|
+
repeat
|
|
380
|
+
},
|
|
381
|
+
notes
|
|
382
|
+
};
|
|
184
383
|
}
|
|
185
384
|
//#endregion
|
|
186
385
|
//#region src/content-inference.ts
|
|
@@ -491,7 +690,7 @@ function convertTypedText(node, ctx) {
|
|
|
491
690
|
if (titleLevel !== void 0) {
|
|
492
691
|
const level = inferred.kind === "title" ? inferred.level : titleLevel;
|
|
493
692
|
const color = parseColor(styleValue(style, "color"));
|
|
494
|
-
const fontFamily =
|
|
693
|
+
const fontFamily = fontStack(styleValue(style, "fontFamily"));
|
|
495
694
|
return finish(createTitleBlock({
|
|
496
695
|
content: inferred.kind === "title" ? inferred.inner : stripTags(raw),
|
|
497
696
|
level,
|
|
@@ -513,7 +712,7 @@ function convertButton(node, ctx) {
|
|
|
513
712
|
const textColor = parseColor(styleValue(style, "color"));
|
|
514
713
|
const borderRadius = parsePx(styleValue(style, "borderRadius"));
|
|
515
714
|
const fontSize = parsePx(styleValue(style, "fontSize"));
|
|
516
|
-
const fontFamily =
|
|
715
|
+
const fontFamily = fontStack(styleValue(style, "fontFamily"));
|
|
517
716
|
const block = createButtonBlock({
|
|
518
717
|
text: stripTags(asString$1(attrs.text)),
|
|
519
718
|
url: asString$1(attrs.href) ?? "",
|
|
@@ -660,7 +859,7 @@ function typedTitleLevel(subType) {
|
|
|
660
859
|
function headingLooks(style, level, ctx) {
|
|
661
860
|
const nested = readNested(style[`h${level}`], ctx);
|
|
662
861
|
const color = parseColor(style[`h${level}Color`] ?? nested.color);
|
|
663
|
-
const fontFamily =
|
|
862
|
+
const fontFamily = fontStack(style[`h${level}FontFamily`] ?? nested.fontFamily);
|
|
664
863
|
return {
|
|
665
864
|
...color ? { color } : {},
|
|
666
865
|
...fontFamily ? { fontFamily } : {}
|
|
@@ -802,10 +1001,20 @@ function buildFullwidth(node, ctx, entries) {
|
|
|
802
1001
|
const attrs = readAttrs(node, ctx.variables, ctx.stats);
|
|
803
1002
|
const contentFill = parseColor(styleValue(style, "contentBackgroundColor"));
|
|
804
1003
|
const outerFill = parseColor(styleValue(style, "backgroundColor"));
|
|
1004
|
+
const content = readChamaileonBackgroundImage(style, "contentBackground", "Section", ctx.bodyWidth);
|
|
1005
|
+
const band = readChamaileonBackgroundImage(style, "background", "fullwidth", ctx.bodyWidth);
|
|
805
1006
|
const original = node.children ?? [];
|
|
806
|
-
const sole = original.length === 1 ? original[0] : void 0;
|
|
807
|
-
const
|
|
808
|
-
const
|
|
1007
|
+
const sole = original.length === 1 && original[0].type === "box" ? original[0] : void 0;
|
|
1008
|
+
const soleStyle = sole ? readStyle(sole, ctx.variables, ctx.stats) : {};
|
|
1009
|
+
const copySoleFill = sole !== void 0 && losesPaint(soleStyle) && contentFill === void 0;
|
|
1010
|
+
const soleImage = sole !== void 0 && !content ? readChamaileonBackgroundImage(soleStyle, "background", "Box", ctx.bodyWidth) : null;
|
|
1011
|
+
if (copySoleFill && !soleImage && hasBoxImage(soleStyle)) entries.push({
|
|
1012
|
+
sourceTag: "box",
|
|
1013
|
+
templaticalBlockType: null,
|
|
1014
|
+
status: "approximated",
|
|
1015
|
+
note: BOX_IMAGE_DROPPED
|
|
1016
|
+
});
|
|
1017
|
+
const flattened = flattenBoxes(original, ctx, entries, copySoleFill ? sole : void 0, soleImage ? sole : void 0);
|
|
809
1018
|
if (flattened.length === 0) return [];
|
|
810
1019
|
const mcIndex = flattened.findIndex((child) => child.type === "multicolumn");
|
|
811
1020
|
const reasons = [];
|
|
@@ -842,7 +1051,21 @@ function buildFullwidth(node, ctx, entries) {
|
|
|
842
1051
|
const fill = contentFill ?? soleFill;
|
|
843
1052
|
section.styles.padding = contentPadding(style);
|
|
844
1053
|
if (fill) section.styles.backgroundColor = fill;
|
|
845
|
-
|
|
1054
|
+
const own = content ?? soleImage;
|
|
1055
|
+
let backgroundImage = own?.image;
|
|
1056
|
+
if (own) reasons.push(...own.notes);
|
|
1057
|
+
if (band) {
|
|
1058
|
+
if (!own && fill === void 0) {
|
|
1059
|
+
backgroundImage = band.image;
|
|
1060
|
+
reasons.push(...band.notes, "fullwidth background image spans the full width in Chamaileon; the section paints it at the content width.");
|
|
1061
|
+
} else reasons.push("fullwidth background image outside the content width was dropped.");
|
|
1062
|
+
}
|
|
1063
|
+
if (backgroundImage) section.backgroundImage = backgroundImage;
|
|
1064
|
+
if (outerFill) {
|
|
1065
|
+
if (!backgroundImage) section.wrapper = { backgroundColor: outerFill };
|
|
1066
|
+
else if (backgroundImage === band?.image) section.styles.backgroundColor = outerFill;
|
|
1067
|
+
else reasons.push(`fullwidth background colour ${outerFill} was dropped: Outlook desktop paints a wrapper colour over the section's background image.`);
|
|
1068
|
+
}
|
|
846
1069
|
const visibility = readVisibility(attrs);
|
|
847
1070
|
if (visibility) section.visibility = visibility;
|
|
848
1071
|
entries.push({
|
|
@@ -853,7 +1076,11 @@ function buildFullwidth(node, ctx, entries) {
|
|
|
853
1076
|
});
|
|
854
1077
|
return [section];
|
|
855
1078
|
}
|
|
856
|
-
|
|
1079
|
+
const BOX_IMAGE_DROPPED = "box background image dropped";
|
|
1080
|
+
function hasBoxImage(style) {
|
|
1081
|
+
return readChamaileonBackgroundImage(style, "background", "Box", void 0) !== null;
|
|
1082
|
+
}
|
|
1083
|
+
function flattenBoxes(nodes, ctx, entries, silent, imageKept) {
|
|
857
1084
|
const out = [];
|
|
858
1085
|
for (const child of nodes) {
|
|
859
1086
|
if (child.type === "placeholder") continue;
|
|
@@ -861,7 +1088,8 @@ function flattenBoxes(nodes, ctx, entries, silent) {
|
|
|
861
1088
|
out.push(child);
|
|
862
1089
|
continue;
|
|
863
1090
|
}
|
|
864
|
-
|
|
1091
|
+
const keepsImage = child === imageKept;
|
|
1092
|
+
if (child !== silent && isPaintedBox(child, ctx, keepsImage)) reportPaintedBox(child, ctx, entries, keepsImage);
|
|
865
1093
|
out.push(...flattenBoxes(child.children ?? [], ctx, entries, void 0));
|
|
866
1094
|
}
|
|
867
1095
|
return out;
|
|
@@ -909,21 +1137,31 @@ function convertColumnChildren(nodes, ctx, entries) {
|
|
|
909
1137
|
}
|
|
910
1138
|
return blocks;
|
|
911
1139
|
}
|
|
912
|
-
|
|
913
|
-
|
|
1140
|
+
/**
|
|
1141
|
+
* Whether flattening the box loses paint: a fill, padding, a radius, or a
|
|
1142
|
+
* background image the section doesn't keep (`imageKept`).
|
|
1143
|
+
*/
|
|
1144
|
+
function isPaintedBox(node, ctx, imageKept = false) {
|
|
1145
|
+
return losesPaint(readStyle(node, ctx.variables, ctx.stats), imageKept);
|
|
1146
|
+
}
|
|
1147
|
+
function losesPaint(style, imageKept = false) {
|
|
914
1148
|
if (parseColor(styleValue(style, "backgroundColor"))) return true;
|
|
1149
|
+
if (!imageKept && hasBoxImage(style)) return true;
|
|
915
1150
|
const padding = parsePadding(style);
|
|
916
1151
|
if (padding.top !== 0 || padding.right !== 0 || padding.bottom !== 0 || padding.left !== 0) return true;
|
|
917
1152
|
const radius = parsePx(styleValue(style, "borderRadius"));
|
|
918
1153
|
return radius !== void 0 && radius > 0;
|
|
919
1154
|
}
|
|
920
|
-
function reportPaintedBox(node, ctx, entries) {
|
|
921
|
-
const
|
|
1155
|
+
function reportPaintedBox(node, ctx, entries, imageKept = false) {
|
|
1156
|
+
const style = readStyle(node, ctx.variables, ctx.stats);
|
|
1157
|
+
const fill = parseColor(styleValue(style, "backgroundColor"));
|
|
1158
|
+
const image = !imageKept && hasBoxImage(style);
|
|
1159
|
+
const lost = [fill ? `box fill ${fill} dropped` : "", image ? BOX_IMAGE_DROPPED : ""].filter(Boolean);
|
|
922
1160
|
entries.push({
|
|
923
1161
|
sourceTag: "box",
|
|
924
1162
|
templaticalBlockType: null,
|
|
925
1163
|
status: "approximated",
|
|
926
|
-
note:
|
|
1164
|
+
note: lost.length > 0 ? lost.join("; ") : "box padding/radius dropped"
|
|
927
1165
|
});
|
|
928
1166
|
}
|
|
929
1167
|
function contentPadding(style) {
|
|
@@ -995,6 +1233,8 @@ function convertChamaileonTemplate(doc) {
|
|
|
995
1233
|
const bodyStyle = readStyle(body, variables, stats);
|
|
996
1234
|
const width = parsePx(styleValue(bodyStyle, "bodyWidth")) ?? REQUIRED_DEFAULTS.width;
|
|
997
1235
|
const backgroundColor = parseColor(styleValue(bodyStyle, "backgroundColor")) ?? REQUIRED_DEFAULTS.backgroundColor;
|
|
1236
|
+
const pageImage = readChamaileonBackgroundImage(bodyStyle, "background", "Body", width);
|
|
1237
|
+
if (pageImage) warnings.push(`The email background image "${pageImage.image.url}" was dropped; Templatical has no email-level background image.`);
|
|
998
1238
|
const previewRaw = typeof document.previewText === "string" ? document.previewText.trim() : "";
|
|
999
1239
|
if (typeof document.subjectLine === "string" ? document.subjectLine.trim() : "") warnings.push(`Dropped subjectLine ("${document.subjectLine}") — Templatical templates have no subject-line field.`);
|
|
1000
1240
|
const fontFiles = document.fontFiles;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["asString","report","readVisibility","asString","readVisibility"],"sources":["../src/normalize.ts","../src/attribute-parser.ts","../src/content-inference.ts","../src/social-mapper.ts","../src/block-mapper.ts","../src/column-layout.ts","../src/section-builder.ts","../src/converter.ts"],"sourcesContent":["import type { ChamaileonNode, ChamaileonVariable } from \"./types\";\n\nexport function isUnset(value: unknown): boolean {\n if (value === null || value === undefined) return true;\n if (typeof value !== \"string\") return false;\n const trimmed = value.trim();\n return (\n trimmed === \"\" ||\n trimmed === \"url()\" ||\n trimmed.toLowerCase() === \"transparent\"\n );\n}\n\nexport function camelKey(key: string): string {\n return key.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());\n}\n\nfunction isVariableObject(\n value: unknown,\n): value is { reference?: unknown; default?: unknown } {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n (\"reference\" in value || \"default\" in value)\n );\n}\n\nexport function unwrapValue(\n value: unknown,\n variables: ChamaileonVariable[],\n stats?: { resolvedVariables: number },\n): unknown {\n if (!isVariableObject(value)) return value;\n const resolved = !isUnset(value.default)\n ? value.default\n : (() => {\n const ref =\n typeof value.reference === \"string\" ? value.reference : undefined;\n if (!ref) return undefined;\n const found = variables.find((v) => v.name === ref);\n return found && !isUnset(found.value) ? found.value : undefined;\n })();\n if (stats && !isUnset(resolved)) stats.resolvedVariables += 1;\n return resolved;\n}\n\nfunction readMap(\n raw: Record<string, unknown> | undefined,\n variables: ChamaileonVariable[],\n stats?: { resolvedVariables: number },\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n if (!raw) return out;\n for (const [key, value] of Object.entries(raw)) {\n const unwrapped = unwrapValue(value, variables, stats);\n if (isUnset(unwrapped)) continue;\n out[camelKey(key)] = unwrapped;\n }\n return out;\n}\n\nexport function readStyle(\n node: ChamaileonNode,\n variables: ChamaileonVariable[],\n stats?: { resolvedVariables: number },\n): Record<string, unknown> {\n return readMap(node.style, variables, stats);\n}\n\nexport function readAttrs(\n node: ChamaileonNode,\n variables: ChamaileonVariable[],\n stats?: { resolvedVariables: number },\n): Record<string, unknown> {\n return readMap(node.attrs, variables, stats);\n}\n\nexport function styleValue(\n style: Record<string, unknown>,\n key: string,\n): unknown {\n return style[camelKey(key)];\n}\n","import type { SpacingValue } from \"@templatical/types\";\nimport { isUnset, styleValue } from \"./normalize\";\n\n/**\n * Parse a px-like value into a rounded integer. Returns `undefined` when unset\n * or for units the block model cannot express (em, rem, %).\n *\n * Trims first, then matches with no surrounding `\\s*` so a failing match cannot\n * backtrack across trailing spaces.\n */\nexport function parsePx(value: unknown): number | undefined {\n if (isUnset(value)) return undefined;\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? Math.round(value) : undefined;\n }\n if (typeof value !== \"string\") return undefined;\n const trimmed = value.trim();\n const match = /^(-?\\d+(?:\\.\\d+)?)(?:px)?$/.exec(trimmed);\n return match ? Math.round(parseFloat(match[1])) : undefined;\n}\n\nconst NAMED_COLORS: Record<string, string> = {\n black: \"#000000\",\n white: \"#ffffff\",\n red: \"#ff0000\",\n green: \"#008000\",\n blue: \"#0000ff\",\n yellow: \"#ffff00\",\n cyan: \"#00ffff\",\n magenta: \"#ff00ff\",\n gray: \"#808080\",\n grey: \"#808080\",\n silver: \"#c0c0c0\",\n maroon: \"#800000\",\n olive: \"#808000\",\n lime: \"#00ff00\",\n aqua: \"#00ffff\",\n teal: \"#008080\",\n navy: \"#000080\",\n fuchsia: \"#ff00ff\",\n purple: \"#800080\",\n orange: \"#ffa500\",\n pink: \"#ffc0cb\",\n};\n\nfunction rgbToHex(r: number, g: number, b: number): string {\n const clamp = (n: number) => Math.max(0, Math.min(255, Math.round(n)));\n const hex = (n: number) => clamp(n).toString(16).padStart(2, \"0\");\n return `#${hex(r)}${hex(g)}${hex(b)}`;\n}\n\n/**\n * Normalize a colour to 6-digit lowercase hex.\n *\n * Returns `undefined` for unset inputs (including transparent) and for anything\n * unrecognised — optional colour fields stay omitted rather than empty string.\n */\nexport function parseColor(value: unknown): string | undefined {\n if (isUnset(value)) return undefined;\n if (typeof value !== \"string\") return undefined;\n const trimmed = value.trim().toLowerCase();\n if (trimmed === \"transparent\" || trimmed === \"inherit\" || trimmed === \"none\")\n return undefined;\n if (/^#[0-9a-f]{6}$/.test(trimmed)) return trimmed;\n if (/^#[0-9a-f]{3}$/.test(trimmed)) {\n const [, r, g, b] = trimmed;\n return `#${r}${r}${g}${g}${b}${b}`;\n }\n const rgb = trimmed.match(\n /^rgba?\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*(?:,\\s*[\\d.]+\\s*)?\\)$/,\n );\n if (rgb) return rgbToHex(+rgb[1], +rgb[2], +rgb[3]);\n return NAMED_COLORS[trimmed];\n}\n\n/** Read split padding sides from a normalized style map; missing sides are 0. */\nexport function parsePadding(style: Record<string, unknown>): SpacingValue {\n const side = (key: string) => parsePx(styleValue(style, key)) ?? 0;\n return {\n top: side(\"paddingTop\"),\n right: side(\"paddingRight\"),\n bottom: side(\"paddingBottom\"),\n left: side(\"paddingLeft\"),\n };\n}\n\n/** Narrow an alignment to the three the block model accepts. */\nexport function parseAlignment(\n value: unknown,\n fallback: \"left\" | \"center\" | \"right\",\n): \"left\" | \"center\" | \"right\" {\n if (typeof value !== \"string\") return fallback;\n const v = value.trim().toLowerCase();\n return v === \"left\" || v === \"center\" || v === \"right\" ? v : fallback;\n}\n\ntype CssBorder = {\n width: number;\n style: \"solid\" | \"dashed\" | \"dotted\";\n color: string;\n};\n\nfunction isSpace(ch: string): boolean {\n return ch === \" \" || ch === \"\\t\" || ch === \"\\n\" || ch === \"\\r\";\n}\n\nfunction skipSpaces(s: string, i: number): number {\n while (i < s.length && isSpace(s[i]!)) i++;\n return i;\n}\n\n/**\n * `\"2px solid #00a591\"` — width, style, colour.\n *\n * Linear: a regex whose last two tokens are \"spaces, then the rest of the\n * string\" is polynomial on a long run of spaces after the style word\n * (`js/polynomial-redos`). Every character here is visited at most once.\n */\nfunction parseCssBorder(value: unknown): CssBorder | undefined {\n if (isUnset(value) || typeof value !== \"string\") return undefined;\n const s = value.trim();\n let i = 0;\n const numStart = i;\n while (i < s.length && s[i]! >= \"0\" && s[i]! <= \"9\") i++;\n if (i < s.length && s[i] === \".\") {\n i++;\n while (i < s.length && s[i]! >= \"0\" && s[i]! <= \"9\") i++;\n }\n if (i === numStart) return undefined;\n const width = Math.round(parseFloat(s.slice(numStart, i)));\n if (s.slice(i, i + 2).toLowerCase() !== \"px\") return undefined;\n i += 2;\n i = skipSpaces(s, i);\n const styleStart = i;\n while (i < s.length && !isSpace(s[i]!)) i++;\n const styleWord = s.slice(styleStart, i).toLowerCase();\n if (\n styleWord !== \"solid\" &&\n styleWord !== \"dashed\" &&\n styleWord !== \"dotted\"\n ) {\n return undefined;\n }\n i = skipSpaces(s, i);\n if (i >= s.length) return undefined;\n const color = parseColor(s.slice(i));\n if (color === undefined || width === 0) return undefined;\n return { width, style: styleWord, color };\n}\n\n/** Parse a CSS border shorthand for outlined buttons / leaf borders. */\nexport function parseBorderShorthand(value: unknown): CssBorder | undefined {\n return parseCssBorder(value);\n}\n\n/** Parse a 2.0 divider `attrs.lineStyle` string into thickness/lineStyle/color. */\nexport function parseLineStyle(value: unknown):\n | {\n thickness: number;\n lineStyle: \"solid\" | \"dashed\" | \"dotted\";\n color: string;\n }\n | undefined {\n const border = parseCssBorder(value);\n if (!border) return undefined;\n return {\n thickness: border.width,\n lineStyle: border.style,\n color: border.color,\n };\n}\n\n/** Strip quotes and return the first family in a stack. */\nexport function firstFamily(value: unknown): string | undefined {\n if (isUnset(value) || typeof value !== \"string\") return undefined;\n const family = value\n .split(\",\")[0]\n .trim()\n .replace(/^['\"]|['\"]$/g, \"\");\n return family === \"\" ? undefined : family;\n}\n","import { findOpenTagEnd } from \"@templatical/types\";\nimport type { HeadingLevel } from \"@templatical/types\";\n\nexport type InferredText =\n | { kind: \"title\"; level: HeadingLevel; inner: string; clampedFrom?: number }\n | { kind: \"paragraph\"; html: string };\n\n/**\n * A single heading element wrapping the whole content, captured with its level\n * and inner markup. Anchored at both ends so a heading with a sibling does not\n * match — that case is a paragraph.\n */\nconst SOLE_HEADING = /^<h([1-6])(?:\\s[^>]*)?>([\\s\\S]*)<\\/h\\1>$/i;\n\nconst BLOCK_ROOT = /^<(p|h[1-6]|ul|ol|blockquote|div|table)\\b/i;\n\n/**\n * Resolve a Chamaileon `attrs.text` string to a Title or a Paragraph.\n *\n * Chamaileon has no menu or table construct for this arm, so this is a two-way\n * choice — the MJML importer needs four arms because `mj-text` is the output\n * of four different block renderers there. Paragraph is the terminal arm and\n * always reachable, so this function is total.\n */\nexport function inferTextBlock(content: string | undefined): InferredText {\n const trimmed = (content ?? \"\").trim();\n if (trimmed === \"\") return { kind: \"paragraph\", html: \"<p></p>\" };\n\n const heading = trimmed.match(SOLE_HEADING);\n if (heading) {\n const source = Number(heading[1]);\n const level = Math.min(source, 4) as HeadingLevel;\n return {\n kind: \"title\",\n level,\n inner: heading[2].trim(),\n ...(source > 4 ? { clampedFrom: source } : {}),\n };\n }\n\n return {\n kind: \"paragraph\",\n html: BLOCK_ROOT.test(trimmed) ? trimmed : `<p>${trimmed}</p>`,\n };\n}\n\nconst ENTITIES: Record<string, string> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n \""\": '\"',\n \"'\": \"'\",\n \"'\": \"'\",\n \" \": \" \",\n};\n\n/**\n * Remove every `<tag>` from `html`, keeping the text between them.\n *\n * A single-pass linear scan rather than `/<[^>]*>/g`: that regex is\n * polynomial over input with many `<` starts and no closing `>`, since\n * `[^>]*` backtracks at every one of them — `\"<\".repeat(n)` is quadratic in\n * `n`. `findOpenTagEnd` (from `@templatical/types`) resolves each `<` with a\n * bounded, quote-aware forward scan instead, so the walk stays O(n): every\n * character is visited at most once by `indexOf` and once by\n * `findOpenTagEnd`, because the cursor only moves forward and an\n * unterminated tag ends the walk rather than retrying from inside it. On\n * that unterminated case the remainder of the string — the dangling `<`\n * included — is emitted as-is, matching what `/<[^>]*>/g` does with\n * malformed markup: it can never complete a match without a `>`, so the\n * text is left untouched from that point on.\n */\nfunction removeTags(html: string): string {\n let out = \"\";\n let i = 0;\n while (i < html.length) {\n const open = html.indexOf(\"<\", i);\n if (open === -1) {\n out += html.substring(i);\n break;\n }\n out += html.substring(i, open);\n const tagEnd = findOpenTagEnd(html, open + 1);\n if (tagEnd === -1) {\n out += html.substring(open);\n break;\n }\n i = tagEnd + 1;\n }\n return out;\n}\n\n/**\n * The visible text of a markup fragment, for a button label — `ButtonBlock.text`\n * is a plain string, so the markup Chamaileon stores in `attrs.text` cannot\n * travel.\n */\nexport function stripTags(html: string | undefined): string {\n if (!html) return \"\";\n const text = removeTags(html);\n const decoded = text.replace(\n /&(?:amp|lt|gt|quot|#39|apos|nbsp);/g,\n (m) => ENTITIES[m] ?? m,\n );\n return decoded.replace(/\\s+/g, \" \").trim();\n}\n","import { createSocialIconsBlock, generateId } from \"@templatical/types\";\nimport type {\n BlockStyles,\n BlockVisibility,\n SocialIcon,\n SocialIconSize,\n SocialIconStyle,\n SocialPlatform,\n} from \"@templatical/types\";\nimport { parseAlignment, parsePadding, parsePx } from \"./attribute-parser\";\nimport type { Converted, MapContext } from \"./block-mapper\";\nimport { readAttrs, readStyle } from \"./normalize\";\nimport type {\n ChamaileonNode,\n ConversionStatus,\n ImportReportEntry,\n} from \"./types\";\n\n/**\n * Exhaustive over `SocialPlatform` on purpose: adding a member to that union\n * without adding it here is a compile error, so this cannot silently fall back\n * to \"website\" for a platform the block model gained.\n */\nconst KNOWN_PLATFORMS: Record<SocialPlatform, true> = {\n facebook: true,\n twitter: true,\n instagram: true,\n linkedin: true,\n youtube: true,\n tiktok: true,\n pinterest: true,\n email: true,\n whatsapp: true,\n telegram: true,\n discord: true,\n snapchat: true,\n reddit: true,\n github: true,\n dribbble: true,\n behance: true,\n website: true,\n};\n\nconst ICON_SIZES: Array<[number, SocialIconSize]> = [\n [24, \"small\"],\n [32, \"medium\"],\n [48, \"large\"],\n];\n\nconst KNOWN_STYLES = new Set<string>([\n \"solid\",\n \"outlined\",\n \"rounded\",\n \"square\",\n \"circle\",\n]);\n\nfunction normalizePlatform(raw: string): SocialPlatform | null {\n const cleaned = raw.trim().toLowerCase();\n if (!cleaned) return null;\n return cleaned in KNOWN_PLATFORMS ? (cleaned as SocialPlatform) : null;\n}\n\nfunction nearestSize(px: number): { size: SocialIconSize; exact: boolean } {\n let best = ICON_SIZES[1];\n let gap = Infinity;\n for (const candidate of ICON_SIZES) {\n const d = Math.abs(candidate[0] - px);\n if (d < gap) {\n gap = d;\n best = candidate;\n }\n }\n return { size: best[1], exact: gap === 0 };\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction asIconStyle(value: unknown): SocialIconStyle | undefined {\n if (typeof value !== \"string\") return undefined;\n const v = value.trim().toLowerCase();\n return KNOWN_STYLES.has(v) ? (v as SocialIconStyle) : undefined;\n}\n\n/**\n * Convert a Chamaileon `social` leaf into a `SocialIconsBlock`.\n *\n * `attrs.elements` is the source of truth for which icons render and in what\n * order. A row of images is not sniffed as social — only `type === \"social\"`.\n */\nexport function convertSocial(\n node: ChamaileonNode,\n ctx: MapContext,\n): Converted {\n const style = readStyle(node, ctx.variables, ctx.stats);\n const attrs = readAttrs(node, ctx.variables, ctx.stats);\n const rawElements = attrs.elements;\n const elements = Array.isArray(rawElements) ? rawElements : [];\n\n if (elements.length === 0) {\n return {\n block: null,\n entry: report(\"social\", null, \"skipped\", \"social has no elements\"),\n };\n }\n\n const notes: string[] = [];\n const icons: SocialIcon[] = [];\n\n for (const item of elements) {\n if (typeof item !== \"object\" || item === null || Array.isArray(item)) {\n continue;\n }\n const el = item as Record<string, unknown>;\n const rawType = asString(el.type) ?? \"\";\n const platform = normalizePlatform(rawType);\n if (!platform) {\n notes.push(\n `Unrecognised social platform \"${rawType}\" mapped to \"website\".`,\n );\n }\n icons.push({\n id: generateId(),\n platform: platform ?? \"website\",\n url: asString(el.link) ?? \"\",\n });\n }\n\n if (icons.length === 0) {\n return {\n block: null,\n entry: report(\"social\", null, \"skipped\", \"social has no elements\"),\n };\n }\n\n let iconSize: SocialIconSize | undefined;\n const sizePx = parsePx(attrs.size);\n if (sizePx !== undefined) {\n const resolved = nearestSize(sizePx);\n iconSize = resolved.size;\n if (!resolved.exact) {\n notes.push(\n `Icon size ${attrs.size} is not one of 24/32/48px; resolved to \"${resolved.size}\".`,\n );\n }\n }\n\n const iconStyle = asIconStyle(attrs.color) ?? asIconStyle(attrs.iconSet);\n const spacing = parsePx(attrs.spacing);\n\n const block = createSocialIconsBlock({\n icons,\n align: parseAlignment(attrs.align ?? style.align, \"center\"),\n ...(iconSize ? { iconSize } : {}),\n ...(iconStyle ? { iconStyle } : {}),\n ...(spacing !== undefined ? { spacing } : {}),\n styles: { padding: parsePadding(style) } satisfies BlockStyles,\n });\n\n const visibility = readVisibility(attrs);\n if (visibility) block.visibility = visibility;\n\n return {\n block,\n entry: report(\n \"social\",\n \"social\",\n notes.length > 0 ? \"approximated\" : \"converted\",\n notes.length > 0 ? notes.join(\" \") : undefined,\n ),\n };\n}\n\nfunction readVisibility(\n attrs: Record<string, unknown>,\n): BlockVisibility | undefined {\n const hideOnMobile = attrs.hideOnMobile === true;\n const hideOnDesktop = attrs.hideOnDesktop === true;\n if (!hideOnMobile && !hideOnDesktop) return undefined;\n return { desktop: !hideOnDesktop, mobile: !hideOnMobile };\n}\n\nfunction report(\n sourceTag: string,\n templaticalBlockType: string | null,\n status: ConversionStatus,\n note?: string,\n): ImportReportEntry {\n return {\n sourceTag,\n templaticalBlockType,\n status,\n ...(note ? { note } : {}),\n };\n}\n","import {\n createButtonBlock,\n createDividerBlock,\n createHtmlBlock,\n createImageBlock,\n createParagraphBlock,\n createTitleBlock,\n createVideoBlock,\n} from \"@templatical/types\";\nimport type {\n Block,\n BlockStyles,\n BlockVisibility,\n ButtonBlock,\n HeadingLevel,\n} from \"@templatical/types\";\nimport {\n firstFamily,\n parseAlignment,\n parseColor,\n parseLineStyle,\n parsePadding,\n parsePx,\n} from \"./attribute-parser\";\nimport { inferTextBlock, stripTags } from \"./content-inference\";\nimport {\n camelKey,\n isUnset,\n readAttrs,\n readStyle,\n styleValue,\n unwrapValue,\n} from \"./normalize\";\nimport { convertSocial } from \"./social-mapper\";\nimport type {\n ChamaileonNode,\n ChamaileonVariable,\n ConversionStatus,\n ImportReportEntry,\n} from \"./types\";\n\nexport interface MapContext {\n bodyWidth: number;\n columnWidth: number;\n variables: ChamaileonVariable[];\n warnings: string[];\n stats?: { resolvedVariables: number };\n}\n\nexport interface Converted {\n block: Block | null;\n entry: ImportReportEntry;\n}\n\nconst LOOP_TYPES = new Set([\n \"block-level-loop\",\n \"block-level-conditional\",\n \"loop\",\n \"conditional\",\n \"branch\",\n]);\n\nexport function isLoopType(type: string | undefined): boolean {\n return typeof type === \"string\" && LOOP_TYPES.has(type);\n}\n\nexport function readLoopExpression(\n node: ChamaileonNode,\n ctx: MapContext,\n): string | undefined {\n const value = readAttrs(node, ctx.variables, ctx.stats).expression;\n return typeof value === \"string\" ? value : undefined;\n}\n\nexport function skippedLoopEntry(\n type: string,\n expression: string | undefined,\n): ImportReportEntry {\n return {\n sourceTag: type,\n templaticalBlockType: null,\n status: \"skipped\",\n note: expression ? `${type} ${expression}` : type,\n };\n}\n\nexport function markLoopApproximated(\n entries: ImportReportEntry[],\n from: number,\n type: string,\n expression: string | undefined,\n): void {\n const extra = expression ? `${type} ${expression}` : type;\n for (let i = from; i < entries.length; i++) {\n const entry = entries[i];\n if (entry.status === \"converted\") {\n entry.status = \"approximated\";\n }\n entry.note = entry.note ? `${entry.note} ${extra}` : extra;\n }\n}\n\n/**\n * Convert one Chamaileon leaf into a Templatical block.\n *\n * Structure nodes (`fullwidth` / `box` / `multicolumn` / `column`)\n * html-fallback if they reach this function.\n */\nexport function convertLeaf(node: ChamaileonNode, ctx: MapContext): Converted {\n switch (node.type) {\n case \"text\":\n return convertText(node, ctx);\n case \"typed-text\":\n return convertTypedText(node, ctx);\n case \"button\":\n return convertButton(node, ctx);\n case \"image\":\n return convertImage(node, ctx, \"converted\");\n case \"dynamic-image\":\n return convertImage(\n node,\n ctx,\n \"approximated\",\n \"dynamic-image imported as an image block\",\n );\n case \"divider\":\n return convertDivider(node, ctx);\n case \"video\":\n return convertVideo(node, ctx);\n case \"code\":\n return convertCode(node, ctx);\n case \"social\":\n return convertSocial(node, ctx);\n default:\n return convertUnknown(node, ctx);\n }\n}\n\nfunction convertText(node: ChamaileonNode, ctx: MapContext): Converted {\n const { style, attrs } = readNode(node, ctx);\n const raw = asString(attrs.text);\n const inferred = inferTextBlock(raw);\n\n if (inferred.kind === \"paragraph\") {\n return finish(\n createParagraphBlock({\n content: inferred.html,\n ...paddingStyles(style),\n }),\n attrs,\n report(\"text\", \"paragraph\", \"converted\"),\n );\n }\n\n const heading = headingLooks(style, inferred.level, ctx);\n return finish(\n createTitleBlock({\n content: inferred.inner,\n level: inferred.level,\n textAlign: parseAlignment(\n alignFromMarkup(raw) ?? attrs.align ?? style.align,\n \"left\",\n ),\n ...heading,\n ...paddingStyles(style),\n }),\n attrs,\n inferred.clampedFrom\n ? report(\n \"text\",\n \"title\",\n \"approximated\",\n `Heading level h${inferred.clampedFrom} clamped to 4 — Templatical titles support h1-h4.`,\n )\n : report(\"text\", \"title\", \"converted\"),\n );\n}\n\nfunction convertTypedText(node: ChamaileonNode, ctx: MapContext): Converted {\n const { style, attrs } = readNode(node, ctx);\n const raw = asString(attrs.text);\n const inferred = inferTextBlock(raw);\n const subType = asString(style.subType)?.trim().toLowerCase() ?? \"paragraph\";\n const titleLevel = typedTitleLevel(subType);\n\n if (titleLevel !== undefined) {\n const level = inferred.kind === \"title\" ? inferred.level : titleLevel;\n const color = parseColor(styleValue(style, \"color\"));\n const fontFamily = firstFamily(styleValue(style, \"fontFamily\"));\n return finish(\n createTitleBlock({\n content: inferred.kind === \"title\" ? inferred.inner : stripTags(raw),\n level,\n textAlign: parseAlignment(\n style.align ?? attrs.align ?? alignFromMarkup(raw),\n \"left\",\n ),\n ...(color ? { color } : {}),\n ...(fontFamily ? { fontFamily } : {}),\n ...paddingStyles(style),\n }),\n attrs,\n report(\"typed-text\", \"title\", \"converted\"),\n );\n }\n\n const list = subType === \"list\";\n return finish(\n createParagraphBlock({\n content:\n inferred.kind === \"paragraph\"\n ? inferred.html\n : raw?.trim() || \"<p></p>\",\n ...paddingStyles(style),\n }),\n attrs,\n list\n ? report(\n \"typed-text\",\n \"paragraph\",\n \"approximated\",\n \"typed-text list imported as a paragraph\",\n )\n : report(\"typed-text\", \"paragraph\", \"converted\"),\n );\n}\n\nfunction convertButton(node: ChamaileonNode, ctx: MapContext): Converted {\n const { style, attrs } = readNode(node, ctx);\n const fill = parseColor(styleValue(style, \"backgroundColor\"));\n const textColor = parseColor(styleValue(style, \"color\"));\n const borderRadius = parsePx(styleValue(style, \"borderRadius\"));\n const fontSize = parsePx(styleValue(style, \"fontSize\"));\n const fontFamily = firstFamily(styleValue(style, \"fontFamily\"));\n\n const block: ButtonBlock = createButtonBlock({\n text: stripTags(asString(attrs.text)),\n url: asString(attrs.href) ?? \"\",\n ...(fill ? { backgroundColor: fill } : {}),\n ...(textColor ? { textColor } : {}),\n ...(borderRadius !== undefined ? { borderRadius } : {}),\n ...(fontSize !== undefined ? { fontSize } : {}),\n ...(fontFamily ? { fontFamily } : {}),\n align: parseAlignment(attrs.align ?? style.align, \"center\"),\n ...paddingStyles(style),\n });\n\n // createButtonBlock defaults fill to #333333; an unset Chamaileon\n // background is an outlined button, which has no Templatical equivalent.\n if (!fill) {\n block.backgroundColor = \"#ffffff\";\n }\n\n return finish(\n block,\n attrs,\n fill\n ? report(\"button\", \"button\", \"converted\")\n : report(\n \"button\",\n \"button\",\n \"approximated\",\n \"outlined button has no Templatical equivalent\",\n ),\n );\n}\n\nfunction convertImage(\n node: ChamaileonNode,\n ctx: MapContext,\n status: \"converted\" | \"approximated\",\n note?: string,\n): Converted {\n const { style, attrs } = readNode(node, ctx);\n const sourceTag = node.type ?? \"image\";\n const src = asString(attrs.src) ?? asString(styleValue(style, \"src\")) ?? \"\";\n const width = resolvedWidth(style, ctx.columnWidth);\n const linkUrl = asString(attrs.link);\n const borderRadius = parsePx(styleValue(style, \"borderRadius\"));\n\n return finish(\n createImageBlock({\n src,\n alt: asString(attrs.altText) ?? \"\",\n ...(width !== undefined ? { width } : {}),\n align: parseAlignment(attrs.align ?? style.align, \"center\"),\n ...(linkUrl ? { linkUrl } : {}),\n ...(borderRadius && borderRadius > 0 ? { borderRadius } : {}),\n ...paddingStyles(style),\n }),\n attrs,\n report(sourceTag, \"image\", status, note),\n );\n}\n\nfunction convertDivider(node: ChamaileonNode, ctx: MapContext): Converted {\n const { style, attrs } = readNode(node, ctx);\n const fromLine = parseLineStyle(attrs.lineStyle);\n // 4.x `style.width` is line thickness, not DividerBlock.width.\n const thickness = fromLine?.thickness ?? parsePx(styleValue(style, \"width\"));\n const lineStyle = fromLine?.lineStyle ?? asLineStyle(style.type);\n const color = fromLine?.color ?? parseColor(styleValue(style, \"color\"));\n\n return finish(\n createDividerBlock({\n ...(thickness !== undefined ? { thickness } : {}),\n ...(lineStyle ? { lineStyle } : {}),\n ...(color ? { color } : {}),\n width: \"full\",\n ...paddingStyles(style),\n }),\n attrs,\n report(\"divider\", \"divider\", \"converted\"),\n );\n}\n\nfunction convertVideo(node: ChamaileonNode, ctx: MapContext): Converted {\n const { style, attrs } = readNode(node, ctx);\n const width = resolvedWidth(style, ctx.columnWidth);\n const alt = asString(attrs.altText);\n\n return finish(\n createVideoBlock({\n url: videoUrl(attrs),\n thumbnailUrl: asString(attrs.src) ?? \"\",\n ...(alt !== undefined ? { alt } : {}),\n ...(width !== undefined ? { width } : {}),\n align: parseAlignment(attrs.align ?? style.align, \"center\"),\n ...paddingStyles(style),\n }),\n attrs,\n report(\"video\", \"video\", \"converted\"),\n );\n}\n\nfunction videoUrl(attrs: Record<string, unknown>): string {\n const link = asString(attrs.link);\n if (link) return link;\n const videoId = asString(attrs.videoId);\n if (!videoId) return \"\";\n const provider = asString(attrs.videoProvider)?.trim().toLowerCase();\n if (provider === \"youtube\") {\n return `https://www.youtube.com/watch?v=${videoId}`;\n }\n if (provider === \"vimeo\") {\n return `https://vimeo.com/${videoId}`;\n }\n return \"\";\n}\n\nfunction convertCode(node: ChamaileonNode, ctx: MapContext): Converted {\n const { style, attrs } = readNode(node, ctx);\n return finish(\n createHtmlBlock({\n content: firstSetContent(attrs.html, attrs.code, attrs.content),\n ...paddingStyles(style),\n }),\n attrs,\n report(\"code\", \"html\", \"converted\"),\n );\n}\n\nfunction convertUnknown(node: ChamaileonNode, ctx: MapContext): Converted {\n const { style, attrs } = readNode(node, ctx);\n const sourceTag = node.type ?? \"unknown\";\n return finish(\n createHtmlBlock({\n content: JSON.stringify(node),\n ...paddingStyles(style),\n }),\n attrs,\n report(\n sourceTag,\n \"html\",\n \"html-fallback\",\n `${sourceTag} has no Templatical block equivalent; the original node is preserved as JSON.`,\n ),\n );\n}\n\nfunction readNode(\n node: ChamaileonNode,\n ctx: MapContext,\n): { style: Record<string, unknown>; attrs: Record<string, unknown> } {\n return {\n style: readStyle(node, ctx.variables, ctx.stats),\n attrs: readAttrs(node, ctx.variables, ctx.stats),\n };\n}\n\nfunction paddingStyles(style: Record<string, unknown>): {\n styles: BlockStyles;\n} {\n return { styles: { padding: parsePadding(style) } };\n}\n\nfunction finish(\n block: Block,\n attrs: Record<string, unknown>,\n entry: ImportReportEntry,\n): Converted {\n const visibility = readVisibility(attrs);\n if (visibility) block.visibility = visibility;\n return { block, entry };\n}\n\nfunction readVisibility(\n attrs: Record<string, unknown>,\n): BlockVisibility | undefined {\n const hideOnMobile = attrs.hideOnMobile === true;\n const hideOnDesktop = attrs.hideOnDesktop === true;\n if (!hideOnMobile && !hideOnDesktop) return undefined;\n return { desktop: !hideOnDesktop, mobile: !hideOnMobile };\n}\n\nfunction report(\n sourceTag: string,\n templaticalBlockType: string,\n status: ConversionStatus,\n note?: string,\n): ImportReportEntry {\n return {\n sourceTag,\n templaticalBlockType,\n status,\n ...(note ? { note } : {}),\n };\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction firstSetContent(...values: unknown[]): string {\n for (const value of values) {\n if (isUnset(value)) continue;\n if (typeof value === \"string\") return value;\n return JSON.stringify(value);\n }\n return \"\";\n}\n\nfunction resolvedWidth(\n style: Record<string, unknown>,\n columnWidth: number,\n): number | \"full\" | undefined {\n const px = parsePx(styleValue(style, \"width\"));\n if (px === undefined) return undefined;\n return px >= columnWidth ? \"full\" : px;\n}\n\nfunction asLineStyle(\n value: unknown,\n): \"solid\" | \"dashed\" | \"dotted\" | undefined {\n if (typeof value !== \"string\") return undefined;\n const v = value.trim().toLowerCase();\n return v === \"solid\" || v === \"dashed\" || v === \"dotted\" ? v : undefined;\n}\n\nfunction typedTitleLevel(subType: string): HeadingLevel | undefined {\n if (subType === \"title\" || subType === \"heading1\") return 1;\n if (subType === \"heading2\") return 2;\n if (subType === \"heading3\") return 3;\n return undefined;\n}\n\nfunction headingLooks(\n style: Record<string, unknown>,\n level: HeadingLevel,\n ctx: MapContext,\n): { color?: string; fontFamily?: string } {\n const nested = readNested(style[`h${level}`], ctx);\n const color = parseColor(style[`h${level}Color`] ?? nested.color);\n const fontFamily = firstFamily(\n style[`h${level}FontFamily`] ?? nested.fontFamily,\n );\n return {\n ...(color ? { color } : {}),\n ...(fontFamily ? { fontFamily } : {}),\n };\n}\n\nfunction readNested(value: unknown, ctx: MapContext): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return {};\n }\n const out: Record<string, unknown> = {};\n for (const [key, raw] of Object.entries(value as Record<string, unknown>)) {\n const unwrapped = unwrapValue(raw, ctx.variables, ctx.stats);\n if (isUnset(unwrapped)) continue;\n out[camelKey(key)] = unwrapped;\n }\n return out;\n}\n\nfunction alignFromMarkup(html: string | undefined): string | undefined {\n if (!html) return undefined;\n const match = /text-align\\s*:\\s*(left|center|right)/i.exec(html);\n return match?.[1]?.toLowerCase();\n}\n","import type { ColumnLayout } from \"@templatical/types\";\n\nconst LAYOUT_SHAPES: Array<{ layout: ColumnLayout; percents: number[] }> = [\n { layout: \"1\", percents: [100] },\n { layout: \"2\", percents: [50, 50] },\n { layout: \"1-2\", percents: [33.33, 66.67] },\n { layout: \"2-1\", percents: [66.67, 33.33] },\n { layout: \"3\", percents: [33.33, 33.33, 33.34] },\n];\n\nconst WIDTH_TOLERANCE = 2;\n\nexport const COLUMN_COUNT: Record<ColumnLayout, number> = {\n \"1\": 1,\n \"2\": 2,\n \"3\": 3,\n \"2-1\": 2,\n \"1-2\": 2,\n};\n\nexport function columnPixels(\n layout: ColumnLayout,\n container: number,\n): number[] {\n switch (layout) {\n case \"2\":\n return [container / 2, container / 2];\n case \"3\":\n return [container / 3, container / 3, container / 3];\n case \"1-2\":\n return [container / 3, (container * 2) / 3];\n case \"2-1\":\n return [(container * 2) / 3, container / 3];\n default:\n return [container];\n }\n}\n\n/**\n * Resolve column widths to one of the five layouts `ColumnLayout` allows.\n *\n * `exact: false` means the caller reports `approximated` — Chamaileon permits\n * any column count at any width and this union permits five shapes, so this is\n * the importer's main irreducible loss.\n */\nexport function matchColumnLayout(percents: Array<number | null>): {\n layout: ColumnLayout;\n exact: boolean;\n} {\n const count = percents.length;\n if (count === 0) return { layout: \"1\", exact: true };\n\n // All-unset is \"distribute equally\", which is exactly what the n-column\n // layout means — not a missing value to approximate around.\n if (percents.every((p) => p === null)) {\n if (count === 1) return { layout: \"1\", exact: true };\n if (count === 2) return { layout: \"2\", exact: true };\n if (count === 3) return { layout: \"3\", exact: true };\n return { layout: \"3\", exact: false };\n }\n\n const known = percents.filter((p): p is number => p !== null);\n const unsetCount = count - known.length;\n const remainder =\n unsetCount === 0\n ? 0\n : Math.max(0, 100 - known.reduce((a, b) => a + b, 0)) / unsetCount;\n const resolved = percents.map((p) => p ?? remainder);\n\n const sameCount = LAYOUT_SHAPES.filter((s) => s.percents.length === count);\n\n for (const shape of sameCount) {\n if (\n shape.percents.every(\n (want, i) => Math.abs(want - resolved[i]) <= WIDTH_TOLERANCE,\n )\n ) {\n return { layout: shape.layout, exact: true };\n }\n }\n\n const candidates =\n sameCount.length > 0\n ? sameCount\n : LAYOUT_SHAPES.filter((s) => s.layout === \"3\");\n\n let best = candidates[0];\n let bestError = Infinity;\n for (const shape of candidates) {\n const error = shape.percents.reduce(\n (sum, want, i) => sum + Math.abs(want - (resolved[i] ?? 0)),\n 0,\n );\n if (error < bestError) {\n bestError = error;\n best = shape;\n }\n }\n\n return { layout: best.layout, exact: false };\n}\n\n/** Convert absolute px column widths to percents of `bodyWidth`. */\nexport function widthsToPercents(\n widthsPx: Array<number | undefined>,\n bodyWidth: number,\n): Array<number | null> {\n return widthsPx.map((w) => (w === undefined ? null : (w / bodyWidth) * 100));\n}\n","import { createSectionBlock } from \"@templatical/types\";\nimport type { Block, BlockVisibility, SpacingValue } from \"@templatical/types\";\nimport { parseColor, parsePadding, parsePx } from \"./attribute-parser\";\nimport {\n convertLeaf,\n isLoopType,\n markLoopApproximated,\n readLoopExpression,\n skippedLoopEntry,\n type MapContext,\n} from \"./block-mapper\";\nimport {\n COLUMN_COUNT,\n columnPixels,\n matchColumnLayout,\n widthsToPercents,\n} from \"./column-layout\";\nimport { readAttrs, readStyle, styleValue } from \"./normalize\";\nimport type { ChamaileonNode, ImportReportEntry } from \"./types\";\n\n/**\n * Build a `SectionBlock` from a Chamaileon `fullwidth`. Returns an array so\n * an empty fullwidth produces nothing.\n *\n * Boxes flatten into the parent. A nested `multicolumn` flattens into the\n * parent column — never a nested `type: \"section\"`.\n */\nexport function buildFullwidth(\n node: ChamaileonNode,\n ctx: MapContext,\n entries: ImportReportEntry[],\n): Block[] {\n const style = readStyle(node, ctx.variables, ctx.stats);\n const attrs = readAttrs(node, ctx.variables, ctx.stats);\n const contentFill = parseColor(styleValue(style, \"contentBackgroundColor\"));\n const outerFill = parseColor(styleValue(style, \"backgroundColor\"));\n\n const original = node.children ?? [];\n const sole = original.length === 1 ? original[0] : undefined;\n const copySoleFill =\n sole?.type === \"box\" &&\n isPaintedBox(sole, ctx) &&\n contentFill === undefined;\n\n const flattened = flattenBoxes(\n original,\n ctx,\n entries,\n copySoleFill ? sole : undefined,\n );\n if (flattened.length === 0) return [];\n\n const mcIndex = flattened.findIndex((child) => child.type === \"multicolumn\");\n const reasons: string[] = [];\n\n const section = createSectionBlock();\n\n if (mcIndex === -1) {\n section.columns = \"1\";\n section.children = [\n convertColumnChildren(\n flattened,\n withColumnWidth(ctx, ctx.bodyWidth),\n entries,\n ),\n ];\n } else {\n const driving = flattened[mcIndex];\n const before = flattened.slice(0, mcIndex);\n const after = flattened.slice(mcIndex + 1);\n if (before.length > 0 || after.length > 0) {\n reasons.push(\n \"fullwidth mixes a multicolumn with sibling leaves; siblings were placed in column 0.\",\n );\n }\n\n const columns = (driving.children ?? []).filter(\n (child) => child.type === \"column\",\n );\n const widthsPx = columns.map((column) =>\n parsePx(styleValue(readStyle(column, ctx.variables, ctx.stats), \"width\")),\n );\n const { layout, exact } = matchColumnLayout(\n widthsToPercents(widthsPx, ctx.bodyWidth),\n );\n if (!exact) {\n reasons.push(\n `Column widths ${formatWidths(widthsPx)} have no exact Templatical layout; resolved to \"${layout}\".`,\n );\n }\n\n const stacking = asString(\n readAttrs(driving, ctx.variables, ctx.stats).stacking,\n );\n if (stacking === \"none\") {\n section.stackOnMobile = false;\n } else if (stacking !== undefined && stacking !== \"left-on-top\") {\n reasons.push(`unknown stacking \"${stacking}\" omitted`);\n }\n\n const slots = COLUMN_COUNT[layout];\n const pixels = columnPixels(layout, ctx.bodyWidth);\n const children: Block[][] = Array.from({ length: slots }, () => []);\n const slotCtx = (slot: number): MapContext =>\n withColumnWidth(ctx, Math.round(pixels[slot] ?? ctx.bodyWidth));\n\n children[0].push(...convertColumnChildren(before, slotCtx(0), entries));\n columns.forEach((column, index) => {\n const slot = Math.min(index, slots - 1);\n children[slot].push(\n ...convertColumnChildren(column.children ?? [], slotCtx(slot), entries),\n );\n });\n children[0].push(...convertColumnChildren(after, slotCtx(0), entries));\n\n section.columns = layout;\n section.children = children;\n }\n\n const soleFill = copySoleFill\n ? parseColor(\n styleValue(\n readStyle(sole, ctx.variables, ctx.stats),\n \"backgroundColor\",\n ),\n )\n : undefined;\n const fill = contentFill ?? soleFill;\n\n section.styles.padding = contentPadding(style);\n if (fill) section.styles.backgroundColor = fill;\n if (outerFill) section.wrapper = { backgroundColor: outerFill };\n\n const visibility = readVisibility(attrs);\n if (visibility) section.visibility = visibility;\n\n entries.push({\n sourceTag: \"fullwidth\",\n templaticalBlockType: \"section\",\n status: reasons.length > 0 ? \"approximated\" : \"converted\",\n ...(reasons.length > 0 ? { note: reasons.join(\" \") } : {}),\n });\n\n return [section];\n}\n\nfunction flattenBoxes(\n nodes: ChamaileonNode[],\n ctx: MapContext,\n entries: ImportReportEntry[],\n silent: ChamaileonNode | undefined,\n): ChamaileonNode[] {\n const out: ChamaileonNode[] = [];\n for (const child of nodes) {\n if (child.type === \"placeholder\") continue;\n if (child.type !== \"box\") {\n out.push(child);\n continue;\n }\n if (child !== silent && isPaintedBox(child, ctx)) {\n reportPaintedBox(child, ctx, entries);\n }\n out.push(...flattenBoxes(child.children ?? [], ctx, entries, undefined));\n }\n return out;\n}\n\nfunction convertColumnChildren(\n nodes: ChamaileonNode[],\n ctx: MapContext,\n entries: ImportReportEntry[],\n): Block[] {\n const blocks: Block[] = [];\n for (const child of nodes) {\n if (child.type === \"placeholder\") continue;\n if (child.type === \"box\") {\n if (isPaintedBox(child, ctx)) reportPaintedBox(child, ctx, entries);\n blocks.push(...convertColumnChildren(child.children ?? [], ctx, entries));\n continue;\n }\n if (child.type === \"multicolumn\") {\n const innerColumns = (child.children ?? []).filter(\n (column) => column.type === \"column\",\n );\n entries.push({\n sourceTag: \"multicolumn\",\n templaticalBlockType: null,\n status: \"approximated\",\n note: `nested multicolumn flattened (${innerColumns.length} columns)`,\n });\n for (const column of innerColumns) {\n blocks.push(\n ...convertColumnChildren(column.children ?? [], ctx, entries),\n );\n }\n continue;\n }\n if (child.type === \"column\") {\n blocks.push(...convertColumnChildren(child.children ?? [], ctx, entries));\n continue;\n }\n if (isLoopType(child.type)) {\n const type = child.type ?? \"loop\";\n const expression = readLoopExpression(child, ctx);\n const kids = child.children ?? [];\n if (kids.length === 0) {\n entries.push(skippedLoopEntry(type, expression));\n continue;\n }\n const from = entries.length;\n blocks.push(...convertColumnChildren(kids, ctx, entries));\n markLoopApproximated(entries, from, type, expression);\n continue;\n }\n const converted = convertLeaf(child, ctx);\n entries.push(converted.entry);\n if (converted.block) blocks.push(converted.block);\n }\n return blocks;\n}\n\nfunction isPaintedBox(node: ChamaileonNode, ctx: MapContext): boolean {\n const style = readStyle(node, ctx.variables, ctx.stats);\n if (parseColor(styleValue(style, \"backgroundColor\"))) return true;\n const padding = parsePadding(style);\n if (\n padding.top !== 0 ||\n padding.right !== 0 ||\n padding.bottom !== 0 ||\n padding.left !== 0\n ) {\n return true;\n }\n const radius = parsePx(styleValue(style, \"borderRadius\"));\n return radius !== undefined && radius > 0;\n}\n\nfunction reportPaintedBox(\n node: ChamaileonNode,\n ctx: MapContext,\n entries: ImportReportEntry[],\n): void {\n const fill = parseColor(\n styleValue(readStyle(node, ctx.variables, ctx.stats), \"backgroundColor\"),\n );\n entries.push({\n sourceTag: \"box\",\n templaticalBlockType: null,\n status: \"approximated\",\n note: fill ? `box fill ${fill} dropped` : \"box padding/radius dropped\",\n });\n}\n\nfunction contentPadding(style: Record<string, unknown>): SpacingValue {\n return {\n top: parsePx(styleValue(style, \"contentPaddingTop\")) ?? 0,\n right: parsePx(styleValue(style, \"contentPaddingRight\")) ?? 0,\n bottom: parsePx(styleValue(style, \"contentPaddingBottom\")) ?? 0,\n left: parsePx(styleValue(style, \"contentPaddingLeft\")) ?? 0,\n };\n}\n\nfunction withColumnWidth(ctx: MapContext, columnWidth: number): MapContext {\n return { ...ctx, columnWidth };\n}\n\nfunction formatWidths(widthsPx: Array<number | undefined>): string {\n return widthsPx\n .map((width) => (width === undefined ? \"auto\" : `${width}px`))\n .join(\", \");\n}\n\nfunction readVisibility(\n attrs: Record<string, unknown>,\n): BlockVisibility | undefined {\n const hideOnMobile = attrs.hideOnMobile === true;\n const hideOnDesktop = attrs.hideOnDesktop === true;\n if (!hideOnMobile && !hideOnDesktop) return undefined;\n return { desktop: !hideOnDesktop, mobile: !hideOnMobile };\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n","import {\n createDefaultTemplateContent,\n DEFAULT_TEMPLATE_DEFAULTS,\n} from \"@templatical/types\";\nimport type {\n Block,\n TemplateContent,\n TemplateSettings,\n} from \"@templatical/types\";\nimport { parseColor, parsePx } from \"./attribute-parser\";\nimport {\n convertLeaf,\n isLoopType,\n markLoopApproximated,\n readLoopExpression,\n skippedLoopEntry,\n type MapContext,\n} from \"./block-mapper\";\nimport { readStyle, styleValue } from \"./normalize\";\nimport { buildFullwidth } from \"./section-builder\";\nimport type {\n ChamaileonDocument,\n ChamaileonNode,\n ImportReportEntry,\n ImportResult,\n} from \"./types\";\n\nconst INVALID_MESSAGE =\n \"Invalid Chamaileon template: expected the document JSON object from getDocument().\";\nconst WRONG_BODY_MESSAGE =\n 'Invalid Chamaileon template: expected body.type to be \"body\".';\nconst EMPTY_WARNING =\n \"No convertible content was found in the Chamaileon document. Check that body.children holds at least one fullwidth.\";\n\n/**\n * `DEFAULT_TEMPLATE_DEFAULTS` is typed `Partial<TemplateSettings>` so a\n * consumer can override any subset, but its own literal always sets the six\n * required fields. Narrowing once here is what lets each fallback come out as\n * `number`/`string`/`boolean` rather than `| undefined`.\n */\nconst REQUIRED_DEFAULTS = DEFAULT_TEMPLATE_DEFAULTS as Required<\n Pick<\n TemplateSettings,\n | \"width\"\n | \"backgroundColor\"\n | \"textColor\"\n | \"linkUnderline\"\n | \"fontFamily\"\n | \"locale\"\n >\n>;\n\n/**\n * Convert a Chamaileon `getDocument()` tree into a Templatical template.\n *\n * The input is the document object itself — `{ body, variables?, … }` —\n * not `getEmailHtml()` markup and not an API envelope. That is documented\n * rather than sniffed: guessing between the persist JSON and compiled HTML\n * would send the HTML through the wrong package.\n *\n * @example\n * ```ts\n * const document = await editorInstance.methods.getDocument();\n * const { content, report } = convertChamaileonTemplate(document);\n * ```\n */\nexport function convertChamaileonTemplate(\n doc: ChamaileonDocument | string,\n): ImportResult {\n const root: unknown = typeof doc === \"string\" ? safeParse(doc) : doc;\n\n if (typeof root !== \"object\" || root === null || Array.isArray(root)) {\n throw new Error(INVALID_MESSAGE);\n }\n\n const document = root as ChamaileonDocument;\n const body = document.body;\n if (!body || body.type !== \"body\") {\n throw new Error(WRONG_BODY_MESSAGE);\n }\n\n const entries: ImportReportEntry[] = [];\n const warnings: string[] = [];\n const blocks: Block[] = [];\n const stats = { resolvedVariables: 0 };\n const variables = document.variables ?? [];\n\n const bodyStyle = readStyle(body, variables, stats);\n const width =\n parsePx(styleValue(bodyStyle, \"bodyWidth\")) ?? REQUIRED_DEFAULTS.width;\n const backgroundColor =\n parseColor(styleValue(bodyStyle, \"backgroundColor\")) ??\n REQUIRED_DEFAULTS.backgroundColor;\n\n const previewRaw =\n typeof document.previewText === \"string\" ? document.previewText.trim() : \"\";\n const subjectRaw =\n typeof document.subjectLine === \"string\" ? document.subjectLine.trim() : \"\";\n if (subjectRaw) {\n warnings.push(\n `Dropped subjectLine (\"${document.subjectLine}\") — Templatical templates have no subject-line field.`,\n );\n }\n\n const fontFiles = document.fontFiles;\n if (\n fontFiles &&\n typeof fontFiles === \"object\" &&\n !Array.isArray(fontFiles) &&\n Object.keys(fontFiles).length > 0\n ) {\n warnings.push(\n \"Dropped fontFiles — Templatical templates have no document-level font-file table.\",\n );\n }\n\n const ctx: MapContext = {\n bodyWidth: width,\n columnWidth: width,\n variables,\n warnings,\n stats,\n };\n\n for (const child of body.children ?? []) {\n convertBodyChild(child, ctx, entries, blocks);\n }\n\n if (stats.resolvedVariables > 0) {\n warnings.push(\n `Resolved ${stats.resolvedVariables} colour/image variables to their default values.`,\n );\n }\n\n if (blocks.length === 0) {\n warnings.push(EMPTY_WARNING);\n }\n\n const defaults = createDefaultTemplateContent();\n const content: TemplateContent = {\n ...defaults,\n blocks,\n settings: {\n ...defaults.settings,\n width,\n backgroundColor,\n ...(previewRaw ? { preheaderText: previewRaw } : {}),\n },\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 return { content, report: { entries, warnings, summary } };\n}\n\nfunction convertBodyChild(\n node: ChamaileonNode,\n ctx: MapContext,\n entries: ImportReportEntry[],\n blocks: Block[],\n): void {\n if (node.type === \"fullwidth\") {\n blocks.push(...buildFullwidth(node, ctx, entries));\n return;\n }\n if (isLoopType(node.type)) {\n convertLoop(node, ctx, entries, blocks);\n return;\n }\n const converted = convertLeaf(node, ctx);\n entries.push(converted.entry);\n if (converted.block) blocks.push(converted.block);\n}\n\nfunction convertLoop(\n node: ChamaileonNode,\n ctx: MapContext,\n entries: ImportReportEntry[],\n blocks: Block[],\n): void {\n const type = node.type ?? \"loop\";\n const expression = readLoopExpression(node, ctx);\n const children = node.children ?? [];\n if (children.length === 0) {\n entries.push(skippedLoopEntry(type, expression));\n return;\n }\n const from = entries.length;\n for (const child of children) {\n convertBodyChild(child, ctx, entries, blocks);\n }\n markLoopApproximated(entries, from, type, expression);\n}\n\nfunction safeParse(source: string): unknown {\n try {\n return JSON.parse(source);\n } catch {\n throw new Error(INVALID_MESSAGE);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["asString","report","readVisibility","asString","readVisibility"],"sources":["../src/normalize.ts","../src/attribute-parser.ts","../src/content-inference.ts","../src/social-mapper.ts","../src/block-mapper.ts","../src/column-layout.ts","../src/section-builder.ts","../src/converter.ts"],"sourcesContent":["import type { ChamaileonNode, ChamaileonVariable } from \"./types\";\n\nexport function isUnset(value: unknown): boolean {\n if (value === null || value === undefined) return true;\n if (typeof value !== \"string\") return false;\n const trimmed = value.trim();\n return (\n trimmed === \"\" ||\n trimmed === \"url()\" ||\n trimmed.toLowerCase() === \"transparent\"\n );\n}\n\nexport function camelKey(key: string): string {\n return key.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());\n}\n\nfunction isVariableObject(\n value: unknown,\n): value is { reference?: unknown; default?: unknown } {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n (\"reference\" in value || \"default\" in value)\n );\n}\n\nexport function unwrapValue(\n value: unknown,\n variables: ChamaileonVariable[],\n stats?: { resolvedVariables: number },\n): unknown {\n if (!isVariableObject(value)) return value;\n const resolved = !isUnset(value.default)\n ? value.default\n : (() => {\n const ref =\n typeof value.reference === \"string\" ? value.reference : undefined;\n if (!ref) return undefined;\n const found = variables.find((v) => v.name === ref);\n return found && !isUnset(found.value) ? found.value : undefined;\n })();\n if (stats && !isUnset(resolved)) stats.resolvedVariables += 1;\n return resolved;\n}\n\nfunction readMap(\n raw: Record<string, unknown> | undefined,\n variables: ChamaileonVariable[],\n stats?: { resolvedVariables: number },\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n if (!raw) return out;\n for (const [key, value] of Object.entries(raw)) {\n const unwrapped = unwrapValue(value, variables, stats);\n if (isUnset(unwrapped)) continue;\n out[camelKey(key)] = unwrapped;\n }\n return out;\n}\n\nexport function readStyle(\n node: ChamaileonNode,\n variables: ChamaileonVariable[],\n stats?: { resolvedVariables: number },\n): Record<string, unknown> {\n return readMap(node.style, variables, stats);\n}\n\nexport function readAttrs(\n node: ChamaileonNode,\n variables: ChamaileonVariable[],\n stats?: { resolvedVariables: number },\n): Record<string, unknown> {\n return readMap(node.attrs, variables, stats);\n}\n\nexport function styleValue(\n style: Record<string, unknown>,\n key: string,\n): unknown {\n return style[camelKey(key)];\n}\n","import { canonicalFontStack } from \"@templatical/types\";\nimport type {\n BackgroundImage,\n BackgroundPositionX,\n BackgroundPositionY,\n BackgroundSize,\n SpacingValue,\n} from \"@templatical/types\";\nimport { isUnset, styleValue } from \"./normalize\";\n\n/**\n * Parse a px-like value into a rounded integer. Returns `undefined` when unset\n * or for units the block model cannot express (em, rem, %).\n *\n * Trims first, then matches with no surrounding `\\s*` so a failing match cannot\n * backtrack across trailing spaces.\n */\nexport function parsePx(value: unknown): number | undefined {\n if (isUnset(value)) return undefined;\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? Math.round(value) : undefined;\n }\n if (typeof value !== \"string\") return undefined;\n const trimmed = value.trim();\n const match = /^(-?\\d+(?:\\.\\d+)?)(?:px)?$/.exec(trimmed);\n return match ? Math.round(parseFloat(match[1])) : undefined;\n}\n\nconst NAMED_COLORS: Record<string, string> = {\n black: \"#000000\",\n white: \"#ffffff\",\n red: \"#ff0000\",\n green: \"#008000\",\n blue: \"#0000ff\",\n yellow: \"#ffff00\",\n cyan: \"#00ffff\",\n magenta: \"#ff00ff\",\n gray: \"#808080\",\n grey: \"#808080\",\n silver: \"#c0c0c0\",\n maroon: \"#800000\",\n olive: \"#808000\",\n lime: \"#00ff00\",\n aqua: \"#00ffff\",\n teal: \"#008080\",\n navy: \"#000080\",\n fuchsia: \"#ff00ff\",\n purple: \"#800080\",\n orange: \"#ffa500\",\n pink: \"#ffc0cb\",\n};\n\nfunction rgbToHex(r: number, g: number, b: number): string {\n const clamp = (n: number) => Math.max(0, Math.min(255, Math.round(n)));\n const hex = (n: number) => clamp(n).toString(16).padStart(2, \"0\");\n return `#${hex(r)}${hex(g)}${hex(b)}`;\n}\n\n/**\n * Normalize a colour to 6-digit lowercase hex.\n *\n * Returns `undefined` for unset inputs (including transparent) and for anything\n * unrecognised — optional colour fields stay omitted rather than empty string.\n */\nexport function parseColor(value: unknown): string | undefined {\n if (isUnset(value)) return undefined;\n if (typeof value !== \"string\") return undefined;\n const trimmed = value.trim().toLowerCase();\n if (trimmed === \"transparent\" || trimmed === \"inherit\" || trimmed === \"none\")\n return undefined;\n if (/^#[0-9a-f]{6}$/.test(trimmed)) return trimmed;\n if (/^#[0-9a-f]{3}$/.test(trimmed)) {\n const [, r, g, b] = trimmed;\n return `#${r}${r}${g}${g}${b}${b}`;\n }\n const rgb = trimmed.match(\n /^rgba?\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*(?:,\\s*[\\d.]+\\s*)?\\)$/,\n );\n if (rgb) return rgbToHex(+rgb[1], +rgb[2], +rgb[3]);\n return NAMED_COLORS[trimmed];\n}\n\n/** Read split padding sides from a normalized style map; missing sides are 0. */\nexport function parsePadding(style: Record<string, unknown>): SpacingValue {\n const side = (key: string) => parsePx(styleValue(style, key)) ?? 0;\n return {\n top: side(\"paddingTop\"),\n right: side(\"paddingRight\"),\n bottom: side(\"paddingBottom\"),\n left: side(\"paddingLeft\"),\n };\n}\n\n/** Narrow an alignment to the three the block model accepts. */\nexport function parseAlignment(\n value: unknown,\n fallback: \"left\" | \"center\" | \"right\",\n): \"left\" | \"center\" | \"right\" {\n if (typeof value !== \"string\") return fallback;\n const v = value.trim().toLowerCase();\n return v === \"left\" || v === \"center\" || v === \"right\" ? v : fallback;\n}\n\ntype CssBorder = {\n width: number;\n style: \"solid\" | \"dashed\" | \"dotted\";\n color: string;\n};\n\nfunction isSpace(ch: string): boolean {\n return ch === \" \" || ch === \"\\t\" || ch === \"\\n\" || ch === \"\\r\";\n}\n\nfunction skipSpaces(s: string, i: number): number {\n while (i < s.length && isSpace(s[i]!)) i++;\n return i;\n}\n\n/**\n * `\"2px solid #00a591\"` — width, style, colour.\n *\n * Linear: a regex whose last two tokens are \"spaces, then the rest of the\n * string\" is polynomial on a long run of spaces after the style word\n * (`js/polynomial-redos`). Every character here is visited at most once.\n */\nfunction parseCssBorder(value: unknown): CssBorder | undefined {\n if (isUnset(value) || typeof value !== \"string\") return undefined;\n const s = value.trim();\n let i = 0;\n const numStart = i;\n while (i < s.length && s[i]! >= \"0\" && s[i]! <= \"9\") i++;\n if (i < s.length && s[i] === \".\") {\n i++;\n while (i < s.length && s[i]! >= \"0\" && s[i]! <= \"9\") i++;\n }\n if (i === numStart) return undefined;\n const width = Math.round(parseFloat(s.slice(numStart, i)));\n if (s.slice(i, i + 2).toLowerCase() !== \"px\") return undefined;\n i += 2;\n i = skipSpaces(s, i);\n const styleStart = i;\n while (i < s.length && !isSpace(s[i]!)) i++;\n const styleWord = s.slice(styleStart, i).toLowerCase();\n if (\n styleWord !== \"solid\" &&\n styleWord !== \"dashed\" &&\n styleWord !== \"dotted\"\n ) {\n return undefined;\n }\n i = skipSpaces(s, i);\n if (i >= s.length) return undefined;\n const color = parseColor(s.slice(i));\n if (color === undefined || width === 0) return undefined;\n return { width, style: styleWord, color };\n}\n\n/** Parse a CSS border shorthand for outlined buttons / leaf borders. */\nexport function parseBorderShorthand(value: unknown): CssBorder | undefined {\n return parseCssBorder(value);\n}\n\n/** Parse a 2.0 divider `attrs.lineStyle` string into thickness/lineStyle/color. */\nexport function parseLineStyle(value: unknown):\n | {\n thickness: number;\n lineStyle: \"solid\" | \"dashed\" | \"dotted\";\n color: string;\n }\n | undefined {\n const border = parseCssBorder(value);\n if (!border) return undefined;\n return {\n thickness: border.width,\n lineStyle: border.style,\n color: border.color,\n };\n}\n\n/** The font stack to store, or `undefined` when `value` sets no font. */\nexport function fontStack(value: unknown): string | undefined {\n if (isUnset(value)) return undefined;\n return canonicalFontStack(value) || undefined;\n}\n\n/** A background image read from the source, with a note for each value that had no exact keyword. */\nexport interface ReadBackgroundImage {\n image: BackgroundImage;\n notes: string[];\n}\n\nconst X_GRID = [\"left\", \"center\", \"right\"] as const;\nconst Y_GRID = [\"top\", \"center\", \"bottom\"] as const;\nconst PERCENT = /^(-?(?:\\d+(?:\\.\\d+)?|\\.\\d+))%$/;\nconst LENGTH = /^-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)[a-z]*$/;\n\n/**\n * `background-size` as a keyword. A size with a side at 100%, or a px width\n * spanning the box, fills it as `cover` does; any other length or percentage\n * draws the image at a size of its own, which `auto` is closest to.\n */\nfunction backgroundSizeKeyword(\n raw: string,\n boxWidth: number | undefined,\n): { size: BackgroundSize; exact: boolean } {\n const parts = raw.toLowerCase().split(/\\s+/);\n const [first] = parts;\n if (parts.length === 1 && (first === \"cover\" || first === \"contain\")) {\n return { size: first, exact: true };\n }\n if (parts.every((part) => part === \"auto\")) {\n return { size: \"auto\", exact: true };\n }\n const px = /^(\\d+(?:\\.\\d+)?|\\.\\d+)px$/.exec(first);\n const fills =\n parts.includes(\"100%\") ||\n (px !== null && boxWidth !== undefined && Number(px[1]) >= boxWidth);\n return { size: fills ? \"cover\" : \"auto\", exact: false };\n}\n\n/**\n * `background-position` split into x and y the way MJML and CSS read it: a\n * lone `top` / `bottom` names y and centres x, any other lone value is x\n * with y centred, and a pair is x then y unless it leads with `top` /\n * `bottom` (or with `center` before `left` / `right`). Three- and four-value\n * edge offsets keep their edge keywords only.\n */\nfunction splitBackgroundPosition(raw: string): {\n x: string;\n y: string;\n exact: boolean;\n} {\n const parts = raw.toLowerCase().split(/\\s+/);\n if (parts.length === 1) {\n const [only] = parts;\n return only === \"top\" || only === \"bottom\"\n ? { x: \"center\", y: only, exact: true }\n : { x: only, y: \"center\", exact: true };\n }\n if (parts.length === 2) {\n const [a, b] = parts;\n const yFirst =\n a === \"top\" ||\n a === \"bottom\" ||\n (a === \"center\" && (b === \"left\" || b === \"right\"));\n return yFirst ? { x: b, y: a, exact: true } : { x: a, y: b, exact: true };\n }\n return {\n x: parts.find((part) => part === \"left\" || part === \"right\") ?? \"center\",\n y: parts.find((part) => part === \"top\" || part === \"bottom\") ?? \"center\",\n exact: false,\n };\n}\n\n/**\n * One axis as a grid point: its keyword; the point nearest a percentage,\n * ties going to the centre; or the start edge for a length, which is an\n * offset from it. `null` when the token is unreadable.\n */\nfunction backgroundAxisPoint<T extends string>(\n token: string,\n grid: readonly [T, T, T],\n): { point: T; exact: boolean } | null {\n const keyword = grid.find((point) => point === token);\n if (keyword) return { point: keyword, exact: true };\n const percent = PERCENT.exec(token);\n if (percent) {\n const n = Number(percent[1]);\n return {\n point: n < 25 ? grid[0] : n > 75 ? grid[2] : grid[1],\n exact: n === 0 || n === 50 || n === 100,\n };\n }\n if (LENGTH.test(token)) {\n return { point: grid[0], exact: parseFloat(token) === 0 };\n }\n return null;\n}\n\n/**\n * `background-repeat`: unrepeated only when every axis is `no-repeat`.\n * Anything but plain `repeat` tiles too, inexactly.\n */\nfunction backgroundRepeatFlag(raw: string): {\n repeat: boolean;\n exact: boolean;\n} {\n const parts = raw.toLowerCase().split(/\\s+/);\n if (parts.every((part) => part === \"no-repeat\")) {\n return { repeat: false, exact: true };\n }\n return { repeat: true, exact: parts.every((part) => part === \"repeat\") };\n}\n\n/** CSS's initial values: what a browser paints for a longhand Chamaileon leaves out. */\nconst CSS_BACKGROUND_DEFAULTS = {\n size: \"auto\",\n x: \"left\",\n y: \"top\",\n repeat: true,\n} as const;\n\n/**\n * The URL inside the first CSS `url(…)`, unquoted and trimmed; \"\" when there\n * is none or it is malformed. A forward scan rather than a regex, so a long\n * run of unclosed `url(` can't make it backtrack.\n */\nfunction cssUrl(value: string | undefined): string {\n const text = value ?? \"\";\n const start = text.search(/url\\(/i);\n if (start === -1) return \"\";\n let i = start + 4;\n while (i < text.length && /\\s/.test(text[i])) i++;\n const quote = text[i];\n if (quote === '\"' || quote === \"'\") {\n const close = text.indexOf(quote, i + 1);\n if (close === -1) return \"\";\n let j = close + 1;\n while (j < text.length && /\\s/.test(text[j])) j++;\n return text[j] === \")\" ? text.slice(i + 1, close).trim() : \"\";\n }\n const close = text.indexOf(\")\", i);\n if (close === -1) return \"\";\n const inner = text.slice(i, close);\n return /[\"']/.test(inner) ? \"\" : inner.trim();\n}\n\n/**\n * A background image from a Chamaileon style map, `null` when\n * `${prefix}Image` holds no `url()`. `prefix` picks the band\n * (`background*`, also a box's and the body's) or a fullwidth's content area\n * (`contentBackground*`). Unset longhands take CSS's initial values; a value\n * with no exact keyword takes the nearest one and adds a note naming it.\n */\nexport function readChamaileonBackgroundImage(\n style: Record<string, unknown>,\n prefix: \"background\" | \"contentBackground\",\n label: string,\n boxWidth: number | undefined,\n): ReadBackgroundImage | null {\n const value = (suffix: string): string | undefined => {\n const raw = styleValue(style, `${prefix}${suffix}`);\n return isUnset(raw) ? undefined : String(raw).trim();\n };\n\n const url = cssUrl(value(\"Image\"));\n if (!url) return null;\n\n const notes: string[] = [];\n const lossy = (name: string, raw: string, shown: string): void => {\n notes.push(\n `${label} ${name} \"${raw}\" has no exact Templatical equivalent; imported as \"${shown}\".`,\n );\n };\n\n let size: BackgroundSize = CSS_BACKGROUND_DEFAULTS.size;\n const rawSize = value(\"Size\");\n if (rawSize) {\n const parsed = backgroundSizeKeyword(rawSize, boxWidth);\n size = parsed.size;\n if (!parsed.exact) lossy(\"background-size\", rawSize, size);\n }\n\n let x: BackgroundPositionX = CSS_BACKGROUND_DEFAULTS.x;\n let y: BackgroundPositionY = CSS_BACKGROUND_DEFAULTS.y;\n const rawPosition = value(\"Position\");\n if (rawPosition) {\n const split = splitBackgroundPosition(rawPosition);\n const readX = backgroundAxisPoint(split.x, X_GRID);\n const readY = backgroundAxisPoint(split.y, Y_GRID);\n x = readX?.point ?? x;\n y = readY?.point ?? y;\n if (!split.exact || !readX?.exact || !readY?.exact) {\n lossy(\"background-position\", rawPosition, `${x} ${y}`);\n }\n }\n\n let repeat: boolean = CSS_BACKGROUND_DEFAULTS.repeat;\n const rawRepeat = value(\"Repeat\");\n if (rawRepeat) {\n const parsed = backgroundRepeatFlag(rawRepeat);\n repeat = parsed.repeat;\n if (!parsed.exact) {\n lossy(\"background-repeat\", rawRepeat, repeat ? \"repeat\" : \"no-repeat\");\n }\n }\n\n return { image: { url, size, position: { x, y }, repeat }, notes };\n}\n","import { findOpenTagEnd } from \"@templatical/types\";\nimport type { HeadingLevel } from \"@templatical/types\";\n\nexport type InferredText =\n | { kind: \"title\"; level: HeadingLevel; inner: string; clampedFrom?: number }\n | { kind: \"paragraph\"; html: string };\n\n/**\n * A single heading element wrapping the whole content, captured with its level\n * and inner markup. Anchored at both ends so a heading with a sibling does not\n * match — that case is a paragraph.\n */\nconst SOLE_HEADING = /^<h([1-6])(?:\\s[^>]*)?>([\\s\\S]*)<\\/h\\1>$/i;\n\nconst BLOCK_ROOT = /^<(p|h[1-6]|ul|ol|blockquote|div|table)\\b/i;\n\n/**\n * Resolve a Chamaileon `attrs.text` string to a Title or a Paragraph.\n *\n * Chamaileon has no menu or table construct for this arm, so this is a two-way\n * choice — the MJML importer needs four arms because `mj-text` is the output\n * of four different block renderers there. Paragraph is the terminal arm and\n * always reachable, so this function is total.\n */\nexport function inferTextBlock(content: string | undefined): InferredText {\n const trimmed = (content ?? \"\").trim();\n if (trimmed === \"\") return { kind: \"paragraph\", html: \"<p></p>\" };\n\n const heading = trimmed.match(SOLE_HEADING);\n if (heading) {\n const source = Number(heading[1]);\n const level = Math.min(source, 4) as HeadingLevel;\n return {\n kind: \"title\",\n level,\n inner: heading[2].trim(),\n ...(source > 4 ? { clampedFrom: source } : {}),\n };\n }\n\n return {\n kind: \"paragraph\",\n html: BLOCK_ROOT.test(trimmed) ? trimmed : `<p>${trimmed}</p>`,\n };\n}\n\nconst ENTITIES: Record<string, string> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n \""\": '\"',\n \"'\": \"'\",\n \"'\": \"'\",\n \" \": \" \",\n};\n\n/**\n * Remove every `<tag>` from `html`, keeping the text between them.\n *\n * A single-pass linear scan rather than `/<[^>]*>/g`: that regex is\n * polynomial over input with many `<` starts and no closing `>`, since\n * `[^>]*` backtracks at every one of them — `\"<\".repeat(n)` is quadratic in\n * `n`. `findOpenTagEnd` (from `@templatical/types`) resolves each `<` with a\n * bounded, quote-aware forward scan instead, so the walk stays O(n): every\n * character is visited at most once by `indexOf` and once by\n * `findOpenTagEnd`, because the cursor only moves forward and an\n * unterminated tag ends the walk rather than retrying from inside it. On\n * that unterminated case the remainder of the string — the dangling `<`\n * included — is emitted as-is, matching what `/<[^>]*>/g` does with\n * malformed markup: it can never complete a match without a `>`, so the\n * text is left untouched from that point on.\n */\nfunction removeTags(html: string): string {\n let out = \"\";\n let i = 0;\n while (i < html.length) {\n const open = html.indexOf(\"<\", i);\n if (open === -1) {\n out += html.substring(i);\n break;\n }\n out += html.substring(i, open);\n const tagEnd = findOpenTagEnd(html, open + 1);\n if (tagEnd === -1) {\n out += html.substring(open);\n break;\n }\n i = tagEnd + 1;\n }\n return out;\n}\n\n/**\n * The visible text of a markup fragment, for a button label — `ButtonBlock.text`\n * is a plain string, so the markup Chamaileon stores in `attrs.text` cannot\n * travel.\n */\nexport function stripTags(html: string | undefined): string {\n if (!html) return \"\";\n const text = removeTags(html);\n const decoded = text.replace(\n /&(?:amp|lt|gt|quot|#39|apos|nbsp);/g,\n (m) => ENTITIES[m] ?? m,\n );\n return decoded.replace(/\\s+/g, \" \").trim();\n}\n","import { createSocialIconsBlock, generateId } from \"@templatical/types\";\nimport type {\n BlockStyles,\n BlockVisibility,\n SocialIcon,\n SocialIconSize,\n SocialIconStyle,\n SocialPlatform,\n} from \"@templatical/types\";\nimport { parseAlignment, parsePadding, parsePx } from \"./attribute-parser\";\nimport type { Converted, MapContext } from \"./block-mapper\";\nimport { readAttrs, readStyle } from \"./normalize\";\nimport type {\n ChamaileonNode,\n ConversionStatus,\n ImportReportEntry,\n} from \"./types\";\n\n/**\n * Exhaustive over `SocialPlatform` on purpose: adding a member to that union\n * without adding it here is a compile error, so this cannot silently fall back\n * to \"website\" for a platform the block model gained.\n */\nconst KNOWN_PLATFORMS: Record<SocialPlatform, true> = {\n facebook: true,\n twitter: true,\n instagram: true,\n linkedin: true,\n youtube: true,\n tiktok: true,\n pinterest: true,\n email: true,\n whatsapp: true,\n telegram: true,\n discord: true,\n snapchat: true,\n reddit: true,\n github: true,\n dribbble: true,\n behance: true,\n website: true,\n};\n\nconst ICON_SIZES: Array<[number, SocialIconSize]> = [\n [24, \"small\"],\n [32, \"medium\"],\n [48, \"large\"],\n];\n\nconst KNOWN_STYLES = new Set<string>([\n \"solid\",\n \"outlined\",\n \"rounded\",\n \"square\",\n \"circle\",\n]);\n\nfunction normalizePlatform(raw: string): SocialPlatform | null {\n const cleaned = raw.trim().toLowerCase();\n if (!cleaned) return null;\n return cleaned in KNOWN_PLATFORMS ? (cleaned as SocialPlatform) : null;\n}\n\nfunction nearestSize(px: number): { size: SocialIconSize; exact: boolean } {\n let best = ICON_SIZES[1];\n let gap = Infinity;\n for (const candidate of ICON_SIZES) {\n const d = Math.abs(candidate[0] - px);\n if (d < gap) {\n gap = d;\n best = candidate;\n }\n }\n return { size: best[1], exact: gap === 0 };\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction asIconStyle(value: unknown): SocialIconStyle | undefined {\n if (typeof value !== \"string\") return undefined;\n const v = value.trim().toLowerCase();\n return KNOWN_STYLES.has(v) ? (v as SocialIconStyle) : undefined;\n}\n\n/**\n * Convert a Chamaileon `social` leaf into a `SocialIconsBlock`.\n *\n * `attrs.elements` is the source of truth for which icons render and in what\n * order. A row of images is not sniffed as social — only `type === \"social\"`.\n */\nexport function convertSocial(\n node: ChamaileonNode,\n ctx: MapContext,\n): Converted {\n const style = readStyle(node, ctx.variables, ctx.stats);\n const attrs = readAttrs(node, ctx.variables, ctx.stats);\n const rawElements = attrs.elements;\n const elements = Array.isArray(rawElements) ? rawElements : [];\n\n if (elements.length === 0) {\n return {\n block: null,\n entry: report(\"social\", null, \"skipped\", \"social has no elements\"),\n };\n }\n\n const notes: string[] = [];\n const icons: SocialIcon[] = [];\n\n for (const item of elements) {\n if (typeof item !== \"object\" || item === null || Array.isArray(item)) {\n continue;\n }\n const el = item as Record<string, unknown>;\n const rawType = asString(el.type) ?? \"\";\n const platform = normalizePlatform(rawType);\n if (!platform) {\n notes.push(\n `Unrecognised social platform \"${rawType}\" mapped to \"website\".`,\n );\n }\n icons.push({\n id: generateId(),\n platform: platform ?? \"website\",\n url: asString(el.link) ?? \"\",\n });\n }\n\n if (icons.length === 0) {\n return {\n block: null,\n entry: report(\"social\", null, \"skipped\", \"social has no elements\"),\n };\n }\n\n let iconSize: SocialIconSize | undefined;\n const sizePx = parsePx(attrs.size);\n if (sizePx !== undefined) {\n const resolved = nearestSize(sizePx);\n iconSize = resolved.size;\n if (!resolved.exact) {\n notes.push(\n `Icon size ${attrs.size} is not one of 24/32/48px; resolved to \"${resolved.size}\".`,\n );\n }\n }\n\n const iconStyle = asIconStyle(attrs.color) ?? asIconStyle(attrs.iconSet);\n const spacing = parsePx(attrs.spacing);\n\n const block = createSocialIconsBlock({\n icons,\n align: parseAlignment(attrs.align ?? style.align, \"center\"),\n ...(iconSize ? { iconSize } : {}),\n ...(iconStyle ? { iconStyle } : {}),\n ...(spacing !== undefined ? { spacing } : {}),\n styles: { padding: parsePadding(style) } satisfies BlockStyles,\n });\n\n const visibility = readVisibility(attrs);\n if (visibility) block.visibility = visibility;\n\n return {\n block,\n entry: report(\n \"social\",\n \"social\",\n notes.length > 0 ? \"approximated\" : \"converted\",\n notes.length > 0 ? notes.join(\" \") : undefined,\n ),\n };\n}\n\nfunction readVisibility(\n attrs: Record<string, unknown>,\n): BlockVisibility | undefined {\n const hideOnMobile = attrs.hideOnMobile === true;\n const hideOnDesktop = attrs.hideOnDesktop === true;\n if (!hideOnMobile && !hideOnDesktop) return undefined;\n return { desktop: !hideOnDesktop, mobile: !hideOnMobile };\n}\n\nfunction report(\n sourceTag: string,\n templaticalBlockType: string | null,\n status: ConversionStatus,\n note?: string,\n): ImportReportEntry {\n return {\n sourceTag,\n templaticalBlockType,\n status,\n ...(note ? { note } : {}),\n };\n}\n","import {\n createButtonBlock,\n createDividerBlock,\n createHtmlBlock,\n createImageBlock,\n createParagraphBlock,\n createTitleBlock,\n createVideoBlock,\n} from \"@templatical/types\";\nimport type {\n Block,\n BlockStyles,\n BlockVisibility,\n ButtonBlock,\n HeadingLevel,\n} from \"@templatical/types\";\nimport {\n fontStack,\n parseAlignment,\n parseColor,\n parseLineStyle,\n parsePadding,\n parsePx,\n} from \"./attribute-parser\";\nimport { inferTextBlock, stripTags } from \"./content-inference\";\nimport {\n camelKey,\n isUnset,\n readAttrs,\n readStyle,\n styleValue,\n unwrapValue,\n} from \"./normalize\";\nimport { convertSocial } from \"./social-mapper\";\nimport type {\n ChamaileonNode,\n ChamaileonVariable,\n ConversionStatus,\n ImportReportEntry,\n} from \"./types\";\n\nexport interface MapContext {\n bodyWidth: number;\n columnWidth: number;\n variables: ChamaileonVariable[];\n warnings: string[];\n stats?: { resolvedVariables: number };\n}\n\nexport interface Converted {\n block: Block | null;\n entry: ImportReportEntry;\n}\n\nconst LOOP_TYPES = new Set([\n \"block-level-loop\",\n \"block-level-conditional\",\n \"loop\",\n \"conditional\",\n \"branch\",\n]);\n\nexport function isLoopType(type: string | undefined): boolean {\n return typeof type === \"string\" && LOOP_TYPES.has(type);\n}\n\nexport function readLoopExpression(\n node: ChamaileonNode,\n ctx: MapContext,\n): string | undefined {\n const value = readAttrs(node, ctx.variables, ctx.stats).expression;\n return typeof value === \"string\" ? value : undefined;\n}\n\nexport function skippedLoopEntry(\n type: string,\n expression: string | undefined,\n): ImportReportEntry {\n return {\n sourceTag: type,\n templaticalBlockType: null,\n status: \"skipped\",\n note: expression ? `${type} ${expression}` : type,\n };\n}\n\nexport function markLoopApproximated(\n entries: ImportReportEntry[],\n from: number,\n type: string,\n expression: string | undefined,\n): void {\n const extra = expression ? `${type} ${expression}` : type;\n for (let i = from; i < entries.length; i++) {\n const entry = entries[i];\n if (entry.status === \"converted\") {\n entry.status = \"approximated\";\n }\n entry.note = entry.note ? `${entry.note} ${extra}` : extra;\n }\n}\n\n/**\n * Convert one Chamaileon leaf into a Templatical block.\n *\n * Structure nodes (`fullwidth` / `box` / `multicolumn` / `column`)\n * html-fallback if they reach this function.\n */\nexport function convertLeaf(node: ChamaileonNode, ctx: MapContext): Converted {\n switch (node.type) {\n case \"text\":\n return convertText(node, ctx);\n case \"typed-text\":\n return convertTypedText(node, ctx);\n case \"button\":\n return convertButton(node, ctx);\n case \"image\":\n return convertImage(node, ctx, \"converted\");\n case \"dynamic-image\":\n return convertImage(\n node,\n ctx,\n \"approximated\",\n \"dynamic-image imported as an image block\",\n );\n case \"divider\":\n return convertDivider(node, ctx);\n case \"video\":\n return convertVideo(node, ctx);\n case \"code\":\n return convertCode(node, ctx);\n case \"social\":\n return convertSocial(node, ctx);\n default:\n return convertUnknown(node, ctx);\n }\n}\n\nfunction convertText(node: ChamaileonNode, ctx: MapContext): Converted {\n const { style, attrs } = readNode(node, ctx);\n const raw = asString(attrs.text);\n const inferred = inferTextBlock(raw);\n\n if (inferred.kind === \"paragraph\") {\n return finish(\n createParagraphBlock({\n content: inferred.html,\n ...paddingStyles(style),\n }),\n attrs,\n report(\"text\", \"paragraph\", \"converted\"),\n );\n }\n\n const heading = headingLooks(style, inferred.level, ctx);\n return finish(\n createTitleBlock({\n content: inferred.inner,\n level: inferred.level,\n textAlign: parseAlignment(\n alignFromMarkup(raw) ?? attrs.align ?? style.align,\n \"left\",\n ),\n ...heading,\n ...paddingStyles(style),\n }),\n attrs,\n inferred.clampedFrom\n ? report(\n \"text\",\n \"title\",\n \"approximated\",\n `Heading level h${inferred.clampedFrom} clamped to 4 — Templatical titles support h1-h4.`,\n )\n : report(\"text\", \"title\", \"converted\"),\n );\n}\n\nfunction convertTypedText(node: ChamaileonNode, ctx: MapContext): Converted {\n const { style, attrs } = readNode(node, ctx);\n const raw = asString(attrs.text);\n const inferred = inferTextBlock(raw);\n const subType = asString(style.subType)?.trim().toLowerCase() ?? \"paragraph\";\n const titleLevel = typedTitleLevel(subType);\n\n if (titleLevel !== undefined) {\n const level = inferred.kind === \"title\" ? inferred.level : titleLevel;\n const color = parseColor(styleValue(style, \"color\"));\n const fontFamily = fontStack(styleValue(style, \"fontFamily\"));\n return finish(\n createTitleBlock({\n content: inferred.kind === \"title\" ? inferred.inner : stripTags(raw),\n level,\n textAlign: parseAlignment(\n style.align ?? attrs.align ?? alignFromMarkup(raw),\n \"left\",\n ),\n ...(color ? { color } : {}),\n ...(fontFamily ? { fontFamily } : {}),\n ...paddingStyles(style),\n }),\n attrs,\n report(\"typed-text\", \"title\", \"converted\"),\n );\n }\n\n const list = subType === \"list\";\n return finish(\n createParagraphBlock({\n content:\n inferred.kind === \"paragraph\"\n ? inferred.html\n : raw?.trim() || \"<p></p>\",\n ...paddingStyles(style),\n }),\n attrs,\n list\n ? report(\n \"typed-text\",\n \"paragraph\",\n \"approximated\",\n \"typed-text list imported as a paragraph\",\n )\n : report(\"typed-text\", \"paragraph\", \"converted\"),\n );\n}\n\nfunction convertButton(node: ChamaileonNode, ctx: MapContext): Converted {\n const { style, attrs } = readNode(node, ctx);\n const fill = parseColor(styleValue(style, \"backgroundColor\"));\n const textColor = parseColor(styleValue(style, \"color\"));\n const borderRadius = parsePx(styleValue(style, \"borderRadius\"));\n const fontSize = parsePx(styleValue(style, \"fontSize\"));\n const fontFamily = fontStack(styleValue(style, \"fontFamily\"));\n\n const block: ButtonBlock = createButtonBlock({\n text: stripTags(asString(attrs.text)),\n url: asString(attrs.href) ?? \"\",\n ...(fill ? { backgroundColor: fill } : {}),\n ...(textColor ? { textColor } : {}),\n ...(borderRadius !== undefined ? { borderRadius } : {}),\n ...(fontSize !== undefined ? { fontSize } : {}),\n ...(fontFamily ? { fontFamily } : {}),\n align: parseAlignment(attrs.align ?? style.align, \"center\"),\n ...paddingStyles(style),\n });\n\n // createButtonBlock defaults fill to #333333; an unset Chamaileon\n // background is an outlined button, which has no Templatical equivalent.\n if (!fill) {\n block.backgroundColor = \"#ffffff\";\n }\n\n return finish(\n block,\n attrs,\n fill\n ? report(\"button\", \"button\", \"converted\")\n : report(\n \"button\",\n \"button\",\n \"approximated\",\n \"outlined button has no Templatical equivalent\",\n ),\n );\n}\n\nfunction convertImage(\n node: ChamaileonNode,\n ctx: MapContext,\n status: \"converted\" | \"approximated\",\n note?: string,\n): Converted {\n const { style, attrs } = readNode(node, ctx);\n const sourceTag = node.type ?? \"image\";\n const src = asString(attrs.src) ?? asString(styleValue(style, \"src\")) ?? \"\";\n const width = resolvedWidth(style, ctx.columnWidth);\n const linkUrl = asString(attrs.link);\n const borderRadius = parsePx(styleValue(style, \"borderRadius\"));\n\n return finish(\n createImageBlock({\n src,\n alt: asString(attrs.altText) ?? \"\",\n ...(width !== undefined ? { width } : {}),\n align: parseAlignment(attrs.align ?? style.align, \"center\"),\n ...(linkUrl ? { linkUrl } : {}),\n ...(borderRadius && borderRadius > 0 ? { borderRadius } : {}),\n ...paddingStyles(style),\n }),\n attrs,\n report(sourceTag, \"image\", status, note),\n );\n}\n\nfunction convertDivider(node: ChamaileonNode, ctx: MapContext): Converted {\n const { style, attrs } = readNode(node, ctx);\n const fromLine = parseLineStyle(attrs.lineStyle);\n // 4.x `style.width` is line thickness, not DividerBlock.width.\n const thickness = fromLine?.thickness ?? parsePx(styleValue(style, \"width\"));\n const lineStyle = fromLine?.lineStyle ?? asLineStyle(style.type);\n const color = fromLine?.color ?? parseColor(styleValue(style, \"color\"));\n\n return finish(\n createDividerBlock({\n ...(thickness !== undefined ? { thickness } : {}),\n ...(lineStyle ? { lineStyle } : {}),\n ...(color ? { color } : {}),\n width: \"full\",\n ...paddingStyles(style),\n }),\n attrs,\n report(\"divider\", \"divider\", \"converted\"),\n );\n}\n\nfunction convertVideo(node: ChamaileonNode, ctx: MapContext): Converted {\n const { style, attrs } = readNode(node, ctx);\n const width = resolvedWidth(style, ctx.columnWidth);\n const alt = asString(attrs.altText);\n\n return finish(\n createVideoBlock({\n url: videoUrl(attrs),\n thumbnailUrl: asString(attrs.src) ?? \"\",\n ...(alt !== undefined ? { alt } : {}),\n ...(width !== undefined ? { width } : {}),\n align: parseAlignment(attrs.align ?? style.align, \"center\"),\n ...paddingStyles(style),\n }),\n attrs,\n report(\"video\", \"video\", \"converted\"),\n );\n}\n\nfunction videoUrl(attrs: Record<string, unknown>): string {\n const link = asString(attrs.link);\n if (link) return link;\n const videoId = asString(attrs.videoId);\n if (!videoId) return \"\";\n const provider = asString(attrs.videoProvider)?.trim().toLowerCase();\n if (provider === \"youtube\") {\n return `https://www.youtube.com/watch?v=${videoId}`;\n }\n if (provider === \"vimeo\") {\n return `https://vimeo.com/${videoId}`;\n }\n return \"\";\n}\n\nfunction convertCode(node: ChamaileonNode, ctx: MapContext): Converted {\n const { style, attrs } = readNode(node, ctx);\n return finish(\n createHtmlBlock({\n content: firstSetContent(attrs.html, attrs.code, attrs.content),\n ...paddingStyles(style),\n }),\n attrs,\n report(\"code\", \"html\", \"converted\"),\n );\n}\n\nfunction convertUnknown(node: ChamaileonNode, ctx: MapContext): Converted {\n const { style, attrs } = readNode(node, ctx);\n const sourceTag = node.type ?? \"unknown\";\n return finish(\n createHtmlBlock({\n content: JSON.stringify(node),\n ...paddingStyles(style),\n }),\n attrs,\n report(\n sourceTag,\n \"html\",\n \"html-fallback\",\n `${sourceTag} has no Templatical block equivalent; the original node is preserved as JSON.`,\n ),\n );\n}\n\nfunction readNode(\n node: ChamaileonNode,\n ctx: MapContext,\n): { style: Record<string, unknown>; attrs: Record<string, unknown> } {\n return {\n style: readStyle(node, ctx.variables, ctx.stats),\n attrs: readAttrs(node, ctx.variables, ctx.stats),\n };\n}\n\nfunction paddingStyles(style: Record<string, unknown>): {\n styles: BlockStyles;\n} {\n return { styles: { padding: parsePadding(style) } };\n}\n\nfunction finish(\n block: Block,\n attrs: Record<string, unknown>,\n entry: ImportReportEntry,\n): Converted {\n const visibility = readVisibility(attrs);\n if (visibility) block.visibility = visibility;\n return { block, entry };\n}\n\nfunction readVisibility(\n attrs: Record<string, unknown>,\n): BlockVisibility | undefined {\n const hideOnMobile = attrs.hideOnMobile === true;\n const hideOnDesktop = attrs.hideOnDesktop === true;\n if (!hideOnMobile && !hideOnDesktop) return undefined;\n return { desktop: !hideOnDesktop, mobile: !hideOnMobile };\n}\n\nfunction report(\n sourceTag: string,\n templaticalBlockType: string,\n status: ConversionStatus,\n note?: string,\n): ImportReportEntry {\n return {\n sourceTag,\n templaticalBlockType,\n status,\n ...(note ? { note } : {}),\n };\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction firstSetContent(...values: unknown[]): string {\n for (const value of values) {\n if (isUnset(value)) continue;\n if (typeof value === \"string\") return value;\n return JSON.stringify(value);\n }\n return \"\";\n}\n\nfunction resolvedWidth(\n style: Record<string, unknown>,\n columnWidth: number,\n): number | \"full\" | undefined {\n const px = parsePx(styleValue(style, \"width\"));\n if (px === undefined) return undefined;\n return px >= columnWidth ? \"full\" : px;\n}\n\nfunction asLineStyle(\n value: unknown,\n): \"solid\" | \"dashed\" | \"dotted\" | undefined {\n if (typeof value !== \"string\") return undefined;\n const v = value.trim().toLowerCase();\n return v === \"solid\" || v === \"dashed\" || v === \"dotted\" ? v : undefined;\n}\n\nfunction typedTitleLevel(subType: string): HeadingLevel | undefined {\n if (subType === \"title\" || subType === \"heading1\") return 1;\n if (subType === \"heading2\") return 2;\n if (subType === \"heading3\") return 3;\n return undefined;\n}\n\nfunction headingLooks(\n style: Record<string, unknown>,\n level: HeadingLevel,\n ctx: MapContext,\n): { color?: string; fontFamily?: string } {\n const nested = readNested(style[`h${level}`], ctx);\n const color = parseColor(style[`h${level}Color`] ?? nested.color);\n const fontFamily = fontStack(\n style[`h${level}FontFamily`] ?? nested.fontFamily,\n );\n return {\n ...(color ? { color } : {}),\n ...(fontFamily ? { fontFamily } : {}),\n };\n}\n\nfunction readNested(value: unknown, ctx: MapContext): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return {};\n }\n const out: Record<string, unknown> = {};\n for (const [key, raw] of Object.entries(value as Record<string, unknown>)) {\n const unwrapped = unwrapValue(raw, ctx.variables, ctx.stats);\n if (isUnset(unwrapped)) continue;\n out[camelKey(key)] = unwrapped;\n }\n return out;\n}\n\nfunction alignFromMarkup(html: string | undefined): string | undefined {\n if (!html) return undefined;\n const match = /text-align\\s*:\\s*(left|center|right)/i.exec(html);\n return match?.[1]?.toLowerCase();\n}\n","import type { ColumnLayout } from \"@templatical/types\";\n\nconst LAYOUT_SHAPES: Array<{ layout: ColumnLayout; percents: number[] }> = [\n { layout: \"1\", percents: [100] },\n { layout: \"2\", percents: [50, 50] },\n { layout: \"1-2\", percents: [33.33, 66.67] },\n { layout: \"2-1\", percents: [66.67, 33.33] },\n { layout: \"3\", percents: [33.33, 33.33, 33.34] },\n];\n\nconst WIDTH_TOLERANCE = 2;\n\nexport const COLUMN_COUNT: Record<ColumnLayout, number> = {\n \"1\": 1,\n \"2\": 2,\n \"3\": 3,\n \"2-1\": 2,\n \"1-2\": 2,\n};\n\nexport function columnPixels(\n layout: ColumnLayout,\n container: number,\n): number[] {\n switch (layout) {\n case \"2\":\n return [container / 2, container / 2];\n case \"3\":\n return [container / 3, container / 3, container / 3];\n case \"1-2\":\n return [container / 3, (container * 2) / 3];\n case \"2-1\":\n return [(container * 2) / 3, container / 3];\n default:\n return [container];\n }\n}\n\n/**\n * Resolve column widths to one of the five layouts `ColumnLayout` allows.\n *\n * `exact: false` means the caller reports `approximated` — Chamaileon permits\n * any column count at any width and this union permits five shapes, so this is\n * the importer's main irreducible loss.\n */\nexport function matchColumnLayout(percents: Array<number | null>): {\n layout: ColumnLayout;\n exact: boolean;\n} {\n const count = percents.length;\n if (count === 0) return { layout: \"1\", exact: true };\n\n // All-unset is \"distribute equally\", which is exactly what the n-column\n // layout means — not a missing value to approximate around.\n if (percents.every((p) => p === null)) {\n if (count === 1) return { layout: \"1\", exact: true };\n if (count === 2) return { layout: \"2\", exact: true };\n if (count === 3) return { layout: \"3\", exact: true };\n return { layout: \"3\", exact: false };\n }\n\n const known = percents.filter((p): p is number => p !== null);\n const unsetCount = count - known.length;\n const remainder =\n unsetCount === 0\n ? 0\n : Math.max(0, 100 - known.reduce((a, b) => a + b, 0)) / unsetCount;\n const resolved = percents.map((p) => p ?? remainder);\n\n const sameCount = LAYOUT_SHAPES.filter((s) => s.percents.length === count);\n\n for (const shape of sameCount) {\n if (\n shape.percents.every(\n (want, i) => Math.abs(want - resolved[i]) <= WIDTH_TOLERANCE,\n )\n ) {\n return { layout: shape.layout, exact: true };\n }\n }\n\n const candidates =\n sameCount.length > 0\n ? sameCount\n : LAYOUT_SHAPES.filter((s) => s.layout === \"3\");\n\n let best = candidates[0];\n let bestError = Infinity;\n for (const shape of candidates) {\n const error = shape.percents.reduce(\n (sum, want, i) => sum + Math.abs(want - (resolved[i] ?? 0)),\n 0,\n );\n if (error < bestError) {\n bestError = error;\n best = shape;\n }\n }\n\n return { layout: best.layout, exact: false };\n}\n\n/** Convert absolute px column widths to percents of `bodyWidth`. */\nexport function widthsToPercents(\n widthsPx: Array<number | undefined>,\n bodyWidth: number,\n): Array<number | null> {\n return widthsPx.map((w) => (w === undefined ? null : (w / bodyWidth) * 100));\n}\n","import { createSectionBlock } from \"@templatical/types\";\nimport type { Block, BlockVisibility, SpacingValue } from \"@templatical/types\";\nimport {\n parseColor,\n parsePadding,\n parsePx,\n readChamaileonBackgroundImage,\n} from \"./attribute-parser\";\nimport {\n convertLeaf,\n isLoopType,\n markLoopApproximated,\n readLoopExpression,\n skippedLoopEntry,\n type MapContext,\n} from \"./block-mapper\";\nimport {\n COLUMN_COUNT,\n columnPixels,\n matchColumnLayout,\n widthsToPercents,\n} from \"./column-layout\";\nimport { readAttrs, readStyle, styleValue } from \"./normalize\";\nimport type { ChamaileonNode, ImportReportEntry } from \"./types\";\n\n/**\n * Build a `SectionBlock` from a Chamaileon `fullwidth`. Returns an array so\n * an empty fullwidth produces nothing.\n *\n * Boxes flatten into the parent. A nested `multicolumn` flattens into the\n * parent column — never a nested `type: \"section\"`.\n */\nexport function buildFullwidth(\n node: ChamaileonNode,\n ctx: MapContext,\n entries: ImportReportEntry[],\n): Block[] {\n const style = readStyle(node, ctx.variables, ctx.stats);\n const attrs = readAttrs(node, ctx.variables, ctx.stats);\n const contentFill = parseColor(styleValue(style, \"contentBackgroundColor\"));\n const outerFill = parseColor(styleValue(style, \"backgroundColor\"));\n const content = readChamaileonBackgroundImage(\n style,\n \"contentBackground\",\n \"Section\",\n ctx.bodyWidth,\n );\n const band = readChamaileonBackgroundImage(\n style,\n \"background\",\n \"fullwidth\",\n ctx.bodyWidth,\n );\n\n const original = node.children ?? [];\n const sole =\n original.length === 1 && original[0].type === \"box\"\n ? original[0]\n : undefined;\n const soleStyle = sole ? readStyle(sole, ctx.variables, ctx.stats) : {};\n const copySoleFill =\n sole !== undefined && losesPaint(soleStyle) && contentFill === undefined;\n const soleImage =\n sole !== undefined && !content\n ? readChamaileonBackgroundImage(\n soleStyle,\n \"background\",\n \"Box\",\n ctx.bodyWidth,\n )\n : null;\n if (copySoleFill && !soleImage && hasBoxImage(soleStyle)) {\n // The lone box's fill goes up with the section's paint, so it stays\n // silent; an image the section doesn't keep is still lost and reported.\n entries.push({\n sourceTag: \"box\",\n templaticalBlockType: null,\n status: \"approximated\",\n note: BOX_IMAGE_DROPPED,\n });\n }\n\n const flattened = flattenBoxes(\n original,\n ctx,\n entries,\n copySoleFill ? sole : undefined,\n soleImage ? sole : undefined,\n );\n if (flattened.length === 0) return [];\n\n const mcIndex = flattened.findIndex((child) => child.type === \"multicolumn\");\n const reasons: string[] = [];\n\n const section = createSectionBlock();\n\n if (mcIndex === -1) {\n section.columns = \"1\";\n section.children = [\n convertColumnChildren(\n flattened,\n withColumnWidth(ctx, ctx.bodyWidth),\n entries,\n ),\n ];\n } else {\n const driving = flattened[mcIndex];\n const before = flattened.slice(0, mcIndex);\n const after = flattened.slice(mcIndex + 1);\n if (before.length > 0 || after.length > 0) {\n reasons.push(\n \"fullwidth mixes a multicolumn with sibling leaves; siblings were placed in column 0.\",\n );\n }\n\n const columns = (driving.children ?? []).filter(\n (child) => child.type === \"column\",\n );\n const widthsPx = columns.map((column) =>\n parsePx(styleValue(readStyle(column, ctx.variables, ctx.stats), \"width\")),\n );\n const { layout, exact } = matchColumnLayout(\n widthsToPercents(widthsPx, ctx.bodyWidth),\n );\n if (!exact) {\n reasons.push(\n `Column widths ${formatWidths(widthsPx)} have no exact Templatical layout; resolved to \"${layout}\".`,\n );\n }\n\n const stacking = asString(\n readAttrs(driving, ctx.variables, ctx.stats).stacking,\n );\n if (stacking === \"none\") {\n section.stackOnMobile = false;\n } else if (stacking !== undefined && stacking !== \"left-on-top\") {\n reasons.push(`unknown stacking \"${stacking}\" omitted`);\n }\n\n const slots = COLUMN_COUNT[layout];\n const pixels = columnPixels(layout, ctx.bodyWidth);\n const children: Block[][] = Array.from({ length: slots }, () => []);\n const slotCtx = (slot: number): MapContext =>\n withColumnWidth(ctx, Math.round(pixels[slot] ?? ctx.bodyWidth));\n\n children[0].push(...convertColumnChildren(before, slotCtx(0), entries));\n columns.forEach((column, index) => {\n const slot = Math.min(index, slots - 1);\n children[slot].push(\n ...convertColumnChildren(column.children ?? [], slotCtx(slot), entries),\n );\n });\n children[0].push(...convertColumnChildren(after, slotCtx(0), entries));\n\n section.columns = layout;\n section.children = children;\n }\n\n const soleFill = copySoleFill\n ? parseColor(\n styleValue(\n readStyle(sole, ctx.variables, ctx.stats),\n \"backgroundColor\",\n ),\n )\n : undefined;\n const fill = contentFill ?? soleFill;\n\n section.styles.padding = contentPadding(style);\n if (fill) section.styles.backgroundColor = fill;\n\n const own = content ?? soleImage;\n let backgroundImage = own?.image;\n if (own) reasons.push(...own.notes);\n if (band) {\n if (!own && fill === undefined) {\n backgroundImage = band.image;\n reasons.push(\n ...band.notes,\n \"fullwidth background image spans the full width in Chamaileon; the section paints it at the content width.\",\n );\n } else {\n reasons.push(\n \"fullwidth background image outside the content width was dropped.\",\n );\n }\n }\n if (backgroundImage) section.backgroundImage = backgroundImage;\n\n if (outerFill) {\n if (!backgroundImage) {\n section.wrapper = { backgroundColor: outerFill };\n } else if (backgroundImage === band?.image) {\n section.styles.backgroundColor = outerFill;\n } else {\n reasons.push(\n `fullwidth background colour ${outerFill} was dropped: Outlook desktop paints a wrapper colour over the section's background image.`,\n );\n }\n }\n\n const visibility = readVisibility(attrs);\n if (visibility) section.visibility = visibility;\n\n entries.push({\n sourceTag: \"fullwidth\",\n templaticalBlockType: \"section\",\n status: reasons.length > 0 ? \"approximated\" : \"converted\",\n ...(reasons.length > 0 ? { note: reasons.join(\" \") } : {}),\n });\n\n return [section];\n}\n\nconst BOX_IMAGE_DROPPED = \"box background image dropped\";\n\nfunction hasBoxImage(style: Record<string, unknown>): boolean {\n return (\n readChamaileonBackgroundImage(style, \"background\", \"Box\", undefined) !==\n null\n );\n}\n\nfunction flattenBoxes(\n nodes: ChamaileonNode[],\n ctx: MapContext,\n entries: ImportReportEntry[],\n silent: ChamaileonNode | undefined,\n imageKept?: ChamaileonNode,\n): ChamaileonNode[] {\n const out: ChamaileonNode[] = [];\n for (const child of nodes) {\n if (child.type === \"placeholder\") continue;\n if (child.type !== \"box\") {\n out.push(child);\n continue;\n }\n const keepsImage = child === imageKept;\n if (child !== silent && isPaintedBox(child, ctx, keepsImage)) {\n reportPaintedBox(child, ctx, entries, keepsImage);\n }\n out.push(...flattenBoxes(child.children ?? [], ctx, entries, undefined));\n }\n return out;\n}\n\nfunction convertColumnChildren(\n nodes: ChamaileonNode[],\n ctx: MapContext,\n entries: ImportReportEntry[],\n): Block[] {\n const blocks: Block[] = [];\n for (const child of nodes) {\n if (child.type === \"placeholder\") continue;\n if (child.type === \"box\") {\n if (isPaintedBox(child, ctx)) reportPaintedBox(child, ctx, entries);\n blocks.push(...convertColumnChildren(child.children ?? [], ctx, entries));\n continue;\n }\n if (child.type === \"multicolumn\") {\n const innerColumns = (child.children ?? []).filter(\n (column) => column.type === \"column\",\n );\n entries.push({\n sourceTag: \"multicolumn\",\n templaticalBlockType: null,\n status: \"approximated\",\n note: `nested multicolumn flattened (${innerColumns.length} columns)`,\n });\n for (const column of innerColumns) {\n blocks.push(\n ...convertColumnChildren(column.children ?? [], ctx, entries),\n );\n }\n continue;\n }\n if (child.type === \"column\") {\n blocks.push(...convertColumnChildren(child.children ?? [], ctx, entries));\n continue;\n }\n if (isLoopType(child.type)) {\n const type = child.type ?? \"loop\";\n const expression = readLoopExpression(child, ctx);\n const kids = child.children ?? [];\n if (kids.length === 0) {\n entries.push(skippedLoopEntry(type, expression));\n continue;\n }\n const from = entries.length;\n blocks.push(...convertColumnChildren(kids, ctx, entries));\n markLoopApproximated(entries, from, type, expression);\n continue;\n }\n const converted = convertLeaf(child, ctx);\n entries.push(converted.entry);\n if (converted.block) blocks.push(converted.block);\n }\n return blocks;\n}\n\n/**\n * Whether flattening the box loses paint: a fill, padding, a radius, or a\n * background image the section doesn't keep (`imageKept`).\n */\nfunction isPaintedBox(\n node: ChamaileonNode,\n ctx: MapContext,\n imageKept = false,\n): boolean {\n return losesPaint(readStyle(node, ctx.variables, ctx.stats), imageKept);\n}\n\nfunction losesPaint(\n style: Record<string, unknown>,\n imageKept = false,\n): boolean {\n if (parseColor(styleValue(style, \"backgroundColor\"))) return true;\n if (!imageKept && hasBoxImage(style)) return true;\n const padding = parsePadding(style);\n if (\n padding.top !== 0 ||\n padding.right !== 0 ||\n padding.bottom !== 0 ||\n padding.left !== 0\n ) {\n return true;\n }\n const radius = parsePx(styleValue(style, \"borderRadius\"));\n return radius !== undefined && radius > 0;\n}\n\nfunction reportPaintedBox(\n node: ChamaileonNode,\n ctx: MapContext,\n entries: ImportReportEntry[],\n imageKept = false,\n): void {\n const style = readStyle(node, ctx.variables, ctx.stats);\n const fill = parseColor(styleValue(style, \"backgroundColor\"));\n const image = !imageKept && hasBoxImage(style);\n const lost = [\n fill ? `box fill ${fill} dropped` : \"\",\n image ? BOX_IMAGE_DROPPED : \"\",\n ].filter(Boolean);\n entries.push({\n sourceTag: \"box\",\n templaticalBlockType: null,\n status: \"approximated\",\n note: lost.length > 0 ? lost.join(\"; \") : \"box padding/radius dropped\",\n });\n}\n\nfunction contentPadding(style: Record<string, unknown>): SpacingValue {\n return {\n top: parsePx(styleValue(style, \"contentPaddingTop\")) ?? 0,\n right: parsePx(styleValue(style, \"contentPaddingRight\")) ?? 0,\n bottom: parsePx(styleValue(style, \"contentPaddingBottom\")) ?? 0,\n left: parsePx(styleValue(style, \"contentPaddingLeft\")) ?? 0,\n };\n}\n\nfunction withColumnWidth(ctx: MapContext, columnWidth: number): MapContext {\n return { ...ctx, columnWidth };\n}\n\nfunction formatWidths(widthsPx: Array<number | undefined>): string {\n return widthsPx\n .map((width) => (width === undefined ? \"auto\" : `${width}px`))\n .join(\", \");\n}\n\nfunction readVisibility(\n attrs: Record<string, unknown>,\n): BlockVisibility | undefined {\n const hideOnMobile = attrs.hideOnMobile === true;\n const hideOnDesktop = attrs.hideOnDesktop === true;\n if (!hideOnMobile && !hideOnDesktop) return undefined;\n return { desktop: !hideOnDesktop, mobile: !hideOnMobile };\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n","import {\n createDefaultTemplateContent,\n DEFAULT_TEMPLATE_DEFAULTS,\n} from \"@templatical/types\";\nimport type {\n Block,\n TemplateContent,\n TemplateSettings,\n} from \"@templatical/types\";\nimport {\n parseColor,\n parsePx,\n readChamaileonBackgroundImage,\n} from \"./attribute-parser\";\nimport {\n convertLeaf,\n isLoopType,\n markLoopApproximated,\n readLoopExpression,\n skippedLoopEntry,\n type MapContext,\n} from \"./block-mapper\";\nimport { readStyle, styleValue } from \"./normalize\";\nimport { buildFullwidth } from \"./section-builder\";\nimport type {\n ChamaileonDocument,\n ChamaileonNode,\n ImportReportEntry,\n ImportResult,\n} from \"./types\";\n\nconst INVALID_MESSAGE =\n \"Invalid Chamaileon template: expected the document JSON object from getDocument().\";\nconst WRONG_BODY_MESSAGE =\n 'Invalid Chamaileon template: expected body.type to be \"body\".';\nconst EMPTY_WARNING =\n \"No convertible content was found in the Chamaileon document. Check that body.children holds at least one fullwidth.\";\n\n/**\n * `DEFAULT_TEMPLATE_DEFAULTS` is typed `Partial<TemplateSettings>` so a\n * consumer can override any subset, but its own literal always sets the six\n * required fields. Narrowing once here is what lets each fallback come out as\n * `number`/`string`/`boolean` rather than `| undefined`.\n */\nconst REQUIRED_DEFAULTS = DEFAULT_TEMPLATE_DEFAULTS as Required<\n Pick<\n TemplateSettings,\n | \"width\"\n | \"backgroundColor\"\n | \"textColor\"\n | \"linkUnderline\"\n | \"fontFamily\"\n | \"locale\"\n >\n>;\n\n/**\n * Convert a Chamaileon `getDocument()` tree into a Templatical template.\n *\n * The input is the document object itself — `{ body, variables?, … }` —\n * not `getEmailHtml()` markup and not an API envelope. That is documented\n * rather than sniffed: guessing between the persist JSON and compiled HTML\n * would send the HTML through the wrong package.\n *\n * @example\n * ```ts\n * const document = await editorInstance.methods.getDocument();\n * const { content, report } = convertChamaileonTemplate(document);\n * ```\n */\nexport function convertChamaileonTemplate(\n doc: ChamaileonDocument | string,\n): ImportResult {\n const root: unknown = typeof doc === \"string\" ? safeParse(doc) : doc;\n\n if (typeof root !== \"object\" || root === null || Array.isArray(root)) {\n throw new Error(INVALID_MESSAGE);\n }\n\n const document = root as ChamaileonDocument;\n const body = document.body;\n if (!body || body.type !== \"body\") {\n throw new Error(WRONG_BODY_MESSAGE);\n }\n\n const entries: ImportReportEntry[] = [];\n const warnings: string[] = [];\n const blocks: Block[] = [];\n const stats = { resolvedVariables: 0 };\n const variables = document.variables ?? [];\n\n const bodyStyle = readStyle(body, variables, stats);\n const width =\n parsePx(styleValue(bodyStyle, \"bodyWidth\")) ?? REQUIRED_DEFAULTS.width;\n const backgroundColor =\n parseColor(styleValue(bodyStyle, \"backgroundColor\")) ??\n REQUIRED_DEFAULTS.backgroundColor;\n const pageImage = readChamaileonBackgroundImage(\n bodyStyle,\n \"background\",\n \"Body\",\n width,\n );\n if (pageImage) {\n warnings.push(\n `The email background image \"${pageImage.image.url}\" was dropped; Templatical has no email-level background image.`,\n );\n }\n\n const previewRaw =\n typeof document.previewText === \"string\" ? document.previewText.trim() : \"\";\n const subjectRaw =\n typeof document.subjectLine === \"string\" ? document.subjectLine.trim() : \"\";\n if (subjectRaw) {\n warnings.push(\n `Dropped subjectLine (\"${document.subjectLine}\") — Templatical templates have no subject-line field.`,\n );\n }\n\n const fontFiles = document.fontFiles;\n if (\n fontFiles &&\n typeof fontFiles === \"object\" &&\n !Array.isArray(fontFiles) &&\n Object.keys(fontFiles).length > 0\n ) {\n warnings.push(\n \"Dropped fontFiles — Templatical templates have no document-level font-file table.\",\n );\n }\n\n const ctx: MapContext = {\n bodyWidth: width,\n columnWidth: width,\n variables,\n warnings,\n stats,\n };\n\n for (const child of body.children ?? []) {\n convertBodyChild(child, ctx, entries, blocks);\n }\n\n if (stats.resolvedVariables > 0) {\n warnings.push(\n `Resolved ${stats.resolvedVariables} colour/image variables to their default values.`,\n );\n }\n\n if (blocks.length === 0) {\n warnings.push(EMPTY_WARNING);\n }\n\n const defaults = createDefaultTemplateContent();\n const content: TemplateContent = {\n ...defaults,\n blocks,\n settings: {\n ...defaults.settings,\n width,\n backgroundColor,\n ...(previewRaw ? { preheaderText: previewRaw } : {}),\n },\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 return { content, report: { entries, warnings, summary } };\n}\n\nfunction convertBodyChild(\n node: ChamaileonNode,\n ctx: MapContext,\n entries: ImportReportEntry[],\n blocks: Block[],\n): void {\n if (node.type === \"fullwidth\") {\n blocks.push(...buildFullwidth(node, ctx, entries));\n return;\n }\n if (isLoopType(node.type)) {\n convertLoop(node, ctx, entries, blocks);\n return;\n }\n const converted = convertLeaf(node, ctx);\n entries.push(converted.entry);\n if (converted.block) blocks.push(converted.block);\n}\n\nfunction convertLoop(\n node: ChamaileonNode,\n ctx: MapContext,\n entries: ImportReportEntry[],\n blocks: Block[],\n): void {\n const type = node.type ?? \"loop\";\n const expression = readLoopExpression(node, ctx);\n const children = node.children ?? [];\n if (children.length === 0) {\n entries.push(skippedLoopEntry(type, expression));\n return;\n }\n const from = entries.length;\n for (const child of children) {\n convertBodyChild(child, ctx, entries, blocks);\n }\n markLoopApproximated(entries, from, type, expression);\n}\n\nfunction safeParse(source: string): unknown {\n try {\n return JSON.parse(source);\n } catch {\n throw new Error(INVALID_MESSAGE);\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@templatical/import-chamaileon",
|
|
3
3
|
"description": "Convert Chamaileon email templates to Templatical format",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.47.0",
|
|
5
5
|
"bugs": "https://github.com/templatical/sdk/issues",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@templatical/types": "0.
|
|
7
|
+
"@templatical/types": "0.47.0"
|
|
8
8
|
},
|
|
9
9
|
"devDependencies": {
|
|
10
10
|
"@types/node": "^25.9.9",
|
|
11
11
|
"typescript": "^6.0.3",
|
|
12
|
-
"vitest": "^5.0.
|
|
12
|
+
"vitest": "^5.0.4"
|
|
13
13
|
},
|
|
14
14
|
"exports": {
|
|
15
15
|
".": {
|