mono/packages/ui/src/lib/emailExporter.ts
babayaga 8ec419b87e ui
2026-01-29 17:57:27 +01:00

130 lines
5.1 KiB
TypeScript

import { PageLayout, LayoutContainer, WidgetInstance } from './unifiedLayoutManager';
import { widgetRegistry } from './widgetRegistry';
import { template } from '@/lib/variables';
import { marked } from 'marked';
export const generateEmailHtml = async (layout: PageLayout, rootTemplateUrl: string): Promise<string> => {
try {
// 1. Fetch Root Template
const rootRes = await fetch(rootTemplateUrl);
if (!rootRes.ok) throw new Error(`Failed to load root template: ${rootTemplateUrl}`);
let rootHtml = await rootRes.text();
// 2. Resolve Root Template Variables (e.g. ${title})
// Map layout properties to variables. We map 'name' to 'title' for common usage.
const rootVars: Record<string, any> = {
...layout,
title: layout.name,
SOURCE: '${SOURCE}'
};
rootHtml = template(rootHtml, rootVars, false);
// 3. Generate Content Body
const contentHtml = await generateContainerHtml(layout.containers);
// 4. Inject Content
if (rootHtml.includes('${SOURCE}')) {
return rootHtml.replace('${SOURCE}', contentHtml);
} else {
return rootHtml.replace('</body>', `${contentHtml}</body>`);
}
} catch (error) {
console.error("Email generation failed:", error);
throw error;
}
};
const generateContainerHtml = async (containers: LayoutContainer[]): Promise<string> => {
let html = '';
// Sort containers
const sortedContainers = [...containers].sort((a, b) => (a.order || 0) - (b.order || 0));
for (const container of sortedContainers) {
const gap = container.gap || 0;
// Container Table
html += `<table width="100%" border="0" cellpadding="0" cellspacing="0" style="margin-bottom: ${gap}px; min-width: 100%;">`;
html += `<tr><td align="center" valign="top">`;
html += `<table width="100%" border="0" cellpadding="0" cellspacing="0"><tr>`;
if (container.columns === 1) {
// Stacked vertical
html += `<td width="100%" valign="top">`;
const sortedWidgets = [...container.widgets].sort((a, b) => (a.order || 0) - (b.order || 0));
for (const widget of sortedWidgets) {
html += await generateWidgetHtml(widget);
}
if (container.children?.length > 0) {
html += await generateContainerHtml(container.children);
}
html += `</td>`;
} else {
// Grid Layout
const colWidth = Math.floor(100 / container.columns);
const sortedWidgets = [...container.widgets].sort((a, b) => (a.order || 0) - (b.order || 0));
for (let i = 0; i < sortedWidgets.length; i += container.columns) {
const rowWidgets = sortedWidgets.slice(i, i + container.columns);
if (i > 0) html += `</tr><tr>`;
for (const widget of rowWidgets) {
html += `<td width="${colWidth}%" valign="top">`;
html += await generateWidgetHtml(widget);
html += `</td>`;
}
// Filler cells
if (rowWidgets.length < container.columns) {
for (let j = rowWidgets.length; j < container.columns; j++) {
html += `<td width="${colWidth}%">&nbsp;</td>`;
}
}
}
}
html += `</tr></table>`;
html += `</td></tr>`;
html += `</table>`;
}
return html;
};
const generateWidgetHtml = async (widget: WidgetInstance): Promise<string> => {
const def = widgetRegistry.get(widget.widgetId);
if (!def) return `<!-- Missing Widget: ${widget.widgetId} -->`;
const templateUrl = def.metadata.defaultProps?.__templateUrl;
if (templateUrl) {
try {
const res = await fetch(templateUrl);
if (res.ok) {
const content = await res.text();
// Process props for markdown conversion
const processedProps = { ...(widget.props || {}) };
if (def.metadata.configSchema) {
for (const [key, schema] of Object.entries(def.metadata.configSchema)) {
if ((schema as any).type === 'markdown' && typeof processedProps[key] === 'string') {
try {
processedProps[key] = await marked.parse(processedProps[key]);
} catch (e) {
console.warn(`Failed to parse markdown for widget ${widget.widgetId} prop ${key}`, e);
}
}
}
}
// Perform Substitution using helper
return template(content, processedProps, false);
}
} catch (e) {
console.error(`Failed to fetch template for ${widget.widgetId}`, e);
}
}
return `<!-- Widget Content: ${widget.widgetId} -->`;
};