@emabuild/email-renderer 0.1.2 → 0.1.4

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 CHANGED
@@ -27,7 +27,7 @@ function w(e, n, i) {
27
27
  </style>
28
28
  <!--<![endif]-->
29
29
  <style type="text/css">
30
- body { margin: 0; padding: 0; }
30
+ body { margin: 0; padding: 0; word-break: normal; }
31
31
  table, tr, td { vertical-align: top; border-collapse: collapse; }
32
32
  p { margin: 0; }
33
33
  a[x-apple-data-detectors='true'] { color: inherit !important; text-decoration: none !important; }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/layout/document-shell.ts","../src/layout/fluid-hybrid.ts","../src/utils/responsive-css.ts","../src/utils/html-sanitizer.ts","../src/render.ts","../src/utils/html-validator.ts"],"sourcesContent":["import type { BodyValues } from '@emabuild/types';\n\nexport function wrapInDocumentShell(bodyHtml: string, cssBlock: string, bodyValues: BodyValues): string {\n const bgColor = bodyValues.backgroundColor || '#e7e7e7';\n const contentWidth = bodyValues.contentWidth || '600px';\n const fontFamily = bodyValues.fontFamily?.value || 'arial,helvetica,sans-serif';\n const textColor = bodyValues.textColor || '#000000';\n const preheaderText = bodyValues.preheaderText || '';\n\n // Always include preheader wrapper — even if empty, the hidden div\n // prevents email clients from using the first visible text as preview\n const preheaderContent = preheaderText || '&zwnj;';\n const preheader = `<div style=\"display:none;font-size:1px;color:${bgColor};line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;\">${preheaderContent}${'&zwnj;&nbsp;'.repeat(80)}</div>`;\n\n return `<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:o=\"urn:schemas-microsoft-com:office:office\">\n<head>\n <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n <meta name=\"x-apple-disable-message-reformatting\">\n <meta name=\"format-detection\" content=\"telephone=no,address=no,email=no,date=no,url=no\">\n <meta name=\"color-scheme\" content=\"light dark\">\n <meta name=\"supported-color-schemes\" content=\"light dark\">\n <title></title>\n <!--[if mso]>\n <noscript><xml>\n <o:OfficeDocumentSettings>\n <o:AllowPNG/><o:PixelsPerInch>96</o:PixelsPerInch>\n </o:OfficeDocumentSettings>\n </xml></noscript>\n <style type=\"text/css\">\n table, td, th { font-family: ${fontFamily} !important; }\n </style>\n <![endif]-->\n <!--[if !mso]><!-->\n <style type=\"text/css\">\n ${cssBlock}\n </style>\n <!--<![endif]-->\n <style type=\"text/css\">\n body { margin: 0; padding: 0; }\n table, tr, td { vertical-align: top; border-collapse: collapse; }\n p { margin: 0; }\n a[x-apple-data-detectors='true'] { color: inherit !important; text-decoration: none !important; }\n </style>\n</head>\n<body class=\"clean-body u_body\" style=\"margin:0;padding:0;-webkit-text-size-adjust:100%;background-color:${bgColor};color:${textColor};\">\n ${preheader}\n <table id=\"u_body\" role=\"presentation\" style=\"border-collapse:collapse;border-spacing:0;margin:0 auto;background-color:${bgColor};width:100%;\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\">\n <tbody>\n <tr>\n <td style=\"vertical-align:top;\">\n <!--[if (mso)|(IE)]><table width=\"${parseInt(contentWidth)}\" align=\"center\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\"><tr><td><![endif]-->\n ${bodyHtml}\n <!--[if (mso)|(IE)]></td></tr></table><![endif]-->\n </td>\n </tr>\n </tbody>\n </table>\n</body>\n</html>`;\n}\n","import type { DesignRow, DesignColumn, BodyValues } from '@emabuild/types';\n\ntype ContentRenderer = (values: Record<string, unknown>, ctx: any) => string;\n\nexport function renderRow(\n row: DesignRow,\n bodyValues: BodyValues,\n toolRenderers: Map<string, ContentRenderer>,\n): string {\n const contentWidth = parseInt(bodyValues.contentWidth || '600');\n const bgColor = row.values.backgroundColor || '';\n const colsBgColor = row.values.columnsBackgroundColor || '';\n const padding = row.values.padding || '0px';\n const totalCells = row.cells.reduce((a, b) => a + b, 0);\n\n const bgStyle = bgColor ? `background-color:${bgColor};` : '';\n const bgImg = row.values.backgroundImage;\n let bgImage = '';\n if (bgImg?.url) {\n // repeat can be: true, false, \"repeat\", \"no-repeat\", or a CSS value\n const repeat = bgImg.repeat === true || bgImg.repeat === 'repeat' ? 'repeat' : 'no-repeat';\n const size = bgImg.cover === true ? 'cover' : bgImg.fullWidth === true ? '100% auto' : 'auto';\n const position = bgImg.center !== false ? 'center top' : 'left top';\n bgImage = `background-image:url('${bgImg.url}');background-repeat:${repeat};background-position:${position};background-size:${size};`;\n }\n\n // Render columns once, wrap with MSO ghost table conditionals for multi-column\n const needsGhostTable = row.columns.length > 1;\n\n const columnEntries = row.columns.map((col, i) => {\n const colWidthPx = Math.round((row.cells[i] / totalCells) * contentWidth);\n const colHtml = renderColumn(col, colWidthPx, colsBgColor, bodyValues, toolRenderers);\n return { colHtml, colWidthPx };\n });\n\n let innerHtml: string;\n if (needsGhostTable) {\n // Wrap each column with MSO conditional <td> for Outlook.\n // For modern clients: inline-block columns inside a font-size:0 parent\n // (eliminates whitespace gaps between inline-block elements).\n const wrappedCols = columnEntries.map(({ colHtml, colWidthPx }) =>\n `<!--[if (mso)|(IE)]><td align=\"center\" width=\"${colWidthPx}\" style=\"width:${colWidthPx}px;padding:0px;\" valign=\"top\"><![endif]-->${colHtml}<!--[if (mso)|(IE)]></td><![endif]-->`\n );\n\n innerHtml = `<!--[if (mso)|(IE)]><table role=\"presentation\" width=\"${contentWidth}\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\"><tr><![endif]-->${wrappedCols.join('')}<!--[if (mso)|(IE)]></tr></table><![endif]-->`;\n } else {\n innerHtml = columnEntries.map(({ colHtml }) => colHtml).join('');\n }\n\n // Visibility classes\n const hideDesktop = row.values.hideDesktop ? ' u_hide_desktop' : '';\n const hideMobile = row.values.hideMobile ? ' u_hide_mobile' : '';\n\n // font-size:0 on the wrapper eliminates whitespace between inline-block columns\n const fsZero = needsGhostTable ? 'font-size:0;' : '';\n\n return `<div class=\"u_row${hideDesktop}${hideMobile}\" style=\"padding:${padding};${bgStyle}${bgImage}\">\n <div style=\"margin:0 auto;max-width:${contentWidth}px;${fsZero}text-align:center;\">${innerHtml}</div>\n</div>`;\n}\n\nfunction renderColumn(\n col: DesignColumn,\n widthPx: number,\n colsBgColor: string,\n bodyValues: BodyValues,\n toolRenderers: Map<string, ContentRenderer>,\n): string {\n const bgColor = col.values.backgroundColor || colsBgColor || '';\n const padding = col.values.padding || '0px';\n const borderRadius = col.values.borderRadius || '0px';\n const bgStyle = bgColor ? `background-color:${bgColor};` : '';\n\n const contentsHtml = col.contents\n .map((content) => {\n const renderer = toolRenderers.get(content.type);\n if (!renderer) return `<!-- unknown tool: ${content.type} -->`;\n const ctx = {\n columnWidth: widthPx,\n displayMode: 'email',\n contentWidth: parseInt(bodyValues.contentWidth || '600'),\n bodyValues,\n };\n let html = renderer(content.values, ctx);\n\n // Wrap with visibility classes if hideDesktop/hideMobile is set\n const hideDesktop = !!content.values.hideDesktop;\n const hideMobile = !!content.values.hideMobile;\n if (hideDesktop || hideMobile) {\n const cls = [hideDesktop && 'u_hide_desktop', hideMobile && 'u_hide_mobile'].filter(Boolean).join(' ');\n html = `<div class=\"${cls}\">${html}</div>`;\n }\n\n return html;\n })\n .join('\\n');\n\n return `<div class=\"u_column\" style=\"display:inline-block;vertical-align:top;width:${widthPx}px;max-width:${widthPx}px;font-size:14px;text-align:left;\">\n <div style=\"width:100%;${bgStyle}${borderRadius !== '0px' ? `border-radius:${borderRadius};` : ''}\">\n <div style=\"padding:${padding};\">\n ${contentsHtml || '&nbsp;'}\n </div>\n </div>\n</div>`;\n}\n","export function getResponsiveCss(contentWidth: number): string {\n return `\n@media only screen and (min-width: ${contentWidth + 20}px) {\n .u_row .u_column { display: inline-block !important; }\n}\n\n@media only screen and (max-width: ${contentWidth + 20}px) {\n .u_row .u_column {\n display: block !important;\n width: 100% !important;\n max-width: 100% !important;\n }\n .u_row {\n width: 100% !important;\n }\n}\n\n@media only screen and (max-width: 620px) {\n .u_row-container {\n max-width: 100% !important;\n padding-left: 0 !important;\n padding-right: 0 !important;\n }\n}\n\n@media (prefers-color-scheme: dark) {\n /* Dark mode overrides — add per-client rules as needed */\n}\n\n/* Outlook dark mode */\n[data-ogsb] body,\n[data-ogsb] table,\n[data-ogsb] td {\n /* Preserve original colors */\n}\n\n.u_hide_desktop { display: block !important; }\n.u_hide_mobile { display: block !important; }\n\n@media only screen and (max-width: ${contentWidth + 20}px) {\n .u_hide_desktop { display: block !important; }\n .u_hide_mobile { display: none !important; }\n}\n\n@media only screen and (min-width: ${contentWidth + 21}px) {\n .u_hide_desktop { display: none !important; }\n .u_hide_mobile { display: block !important; }\n}`;\n}\n","/**\n * @module html-sanitizer\n *\n * Sanitizes user-generated HTML content for email compatibility.\n * Removes CSS properties that are not supported by email clients\n * and replaces CSS variables with fallback values.\n */\n\n/** CSS properties to strip from inline styles */\nconst UNSUPPORTED_PROPS = [\n 'box-sizing',\n 'float',\n 'overflow-wrap',\n 'word-break',\n 'word-wrap',\n 'outline',\n 'cursor',\n 'transition',\n 'animation',\n 'transform',\n 'position',\n 'z-index',\n 'display:\\\\s*flex',\n 'display:\\\\s*grid',\n 'gap',\n];\n\n/** Build a single regex that matches any unsupported property in a style attribute */\nconst PROP_REGEX = new RegExp(\n `(?:;\\\\s*|^\\\\s*)(${UNSUPPORTED_PROPS.join('|')})\\\\s*:[^;]*;?`,\n 'gi',\n);\n\n/** Replace CSS var() with empty string (email clients don't support CSS variables) */\nconst VAR_REGEX = /var\\(--[^)]*\\)/gi;\n\n/**\n * Clean inline styles in HTML content to remove email-unsupported CSS.\n * Processes `style=\"...\"` attributes and removes problematic properties.\n *\n * @param html - Raw HTML content (e.g. from a text/heading block)\n * @returns Cleaned HTML with email-safe inline styles\n */\nexport function sanitizeEmailHtml(html: string): string {\n // Process each style=\"...\" attribute\n return html.replace(/style=\"([^\"]*)\"/gi, (match, styleContent: string) => {\n let cleaned = styleContent;\n\n // Remove unsupported properties\n cleaned = cleaned.replace(PROP_REGEX, '');\n\n // Replace CSS variables with empty/inherit\n cleaned = cleaned.replace(VAR_REGEX, 'inherit');\n\n // Clean up multiple semicolons and whitespace\n cleaned = cleaned.replace(/;\\s*;/g, ';').replace(/^\\s*;\\s*/, '').replace(/;\\s*$/, '').trim();\n\n return cleaned ? `style=\"${cleaned}\"` : '';\n });\n}\n","import type { EmailDesign, ExportResult, ExportOptions, BodyValues } from '@emabuild/types';\nimport { wrapInDocumentShell } from './layout/document-shell.js';\nimport { renderRow } from './layout/fluid-hybrid.js';\nimport { getResponsiveCss } from './utils/responsive-css.js';\nimport { sanitizeEmailHtml } from './utils/html-sanitizer.js';\n\ntype ContentRenderer = (values: Record<string, unknown>, ctx: any) => string;\n\nexport function renderDesignToHtml(\n design: EmailDesign,\n toolRenderers: Map<string, ContentRenderer>,\n options?: ExportOptions,\n): ExportResult {\n const bodyValues = design.body.values;\n const contentWidth = parseInt(bodyValues.contentWidth || '600');\n\n // Render all rows\n const rowsHtml = design.body.rows\n .map((row) => renderRow(row, bodyValues, toolRenderers))\n .join('\\n');\n\n // Build responsive CSS\n const cssBlock = getResponsiveCss(contentWidth);\n\n // Sanitize user content (remove email-unsupported CSS)\n const cleanRowsHtml = sanitizeEmailHtml(rowsHtml);\n\n // Build full document\n let fullHtml = wrapInDocumentShell(cleanRowsHtml, cssBlock, bodyValues);\n\n // Process merge tags if provided\n if (options?.mergeTags) {\n for (const [tag, value] of Object.entries(options.mergeTags)) {\n fullHtml = fullHtml.replaceAll(`{{${tag}}}`, value);\n }\n }\n\n // Extract chunks\n const bodyMatch = fullHtml.match(/<body[^>]*>([\\s\\S]*)<\\/body>/i);\n const cssMatch = fullHtml.match(/<style[^>]*>([\\s\\S]*?)<\\/style>/gi);\n const fontsUsed: string[] = [];\n\n // Collect Google Fonts\n if (bodyValues.fontFamily?.url) {\n fontsUsed.push(bodyValues.fontFamily.url);\n }\n\n return {\n design: structuredClone(design),\n html: fullHtml,\n chunks: {\n body: bodyMatch?.[1] ?? rowsHtml,\n css: cssMatch?.map((s) => s.replace(/<\\/?style[^>]*>/gi, '')).join('\\n') ?? cssBlock,\n fonts: fontsUsed,\n js: '',\n },\n };\n}\n","/**\n * @module html-validator\n *\n * Validates exported email HTML against common email client compatibility rules.\n * Returns a list of warnings and errors that may cause rendering issues.\n */\n\nexport interface ValidationResult {\n valid: boolean;\n errors: ValidationIssue[];\n warnings: ValidationIssue[];\n score: number; // 0-100\n}\n\nexport interface ValidationIssue {\n rule: string;\n message: string;\n severity: 'error' | 'warning';\n line?: number;\n}\n\n/** Validate email HTML for cross-client compatibility */\nexport function validateEmailHtml(html: string): ValidationResult {\n const errors: ValidationIssue[] = [];\n const warnings: ValidationIssue[] = [];\n\n // ── Structure checks ──────────────────────────────────\n\n if (!html.includes('<!DOCTYPE')) {\n errors.push({ rule: 'doctype', message: 'Missing <!DOCTYPE> declaration. Required for consistent rendering.', severity: 'error' });\n }\n\n if (!html.includes('<meta') || !html.includes('charset')) {\n errors.push({ rule: 'charset', message: 'Missing charset meta tag. Add <meta charset=\"UTF-8\">.', severity: 'error' });\n }\n\n if (!html.includes('viewport')) {\n warnings.push({ rule: 'viewport', message: 'Missing viewport meta tag. Needed for responsive rendering on mobile.', severity: 'warning' });\n }\n\n // ── Table layout checks ───────────────────────────────\n\n const tableCount = (html.match(/<table/gi) || []).length;\n const presentationCount = (html.match(/role=\"presentation\"/gi) || []).length;\n if (tableCount > 0 && presentationCount < tableCount * 0.5) {\n warnings.push({ rule: 'table-role', message: `Only ${presentationCount}/${tableCount} tables have role=\"presentation\". Screen readers may interpret layout tables as data tables.`, severity: 'warning' });\n }\n\n // ── Image checks ──────────────────────────────────────\n\n const imgTags = html.match(/<img[^>]*>/gi) || [];\n for (const img of imgTags) {\n if (!img.includes('alt=')) {\n errors.push({ rule: 'img-alt', message: 'Image missing alt attribute. Required for accessibility and when images are blocked.', severity: 'error' });\n }\n if (!img.includes('style=') || !img.includes('display')) {\n warnings.push({ rule: 'img-display', message: 'Image without display:block may have a bottom gap in some email clients.', severity: 'warning' });\n }\n if (img.includes('width=\"100%\"') || (!img.includes('width=') && !img.includes('width:'))) {\n warnings.push({ rule: 'img-width', message: 'Image without explicit pixel width. Some email clients ignore percentage widths.', severity: 'warning' });\n }\n }\n\n // ── CSS checks ────────────────────────────────────────\n\n const styleBlocks = html.match(/<style[^>]*>[\\s\\S]*?<\\/style>/gi) || [];\n const hasInlineStyles = html.includes('style=\"');\n\n if (styleBlocks.length > 0 && !html.includes('@media')) {\n warnings.push({ rule: 'no-media-queries', message: 'Style block found but no @media queries. Consider adding responsive breakpoints.', severity: 'warning' });\n }\n\n if (!hasInlineStyles && tableCount > 0) {\n errors.push({ rule: 'inline-styles', message: 'No inline styles detected. Gmail and many clients strip <style> tags — inline styles are essential.', severity: 'error' });\n }\n\n // Check for unsupported CSS properties\n const unsupportedProps = ['position:fixed', 'position:absolute', 'display:flex', 'display:grid', 'gap:', 'transform:', 'animation:'];\n for (const prop of unsupportedProps) {\n if (html.includes(`style=\"`) && html.match(new RegExp(`style=\"[^\"]*${prop.replace(':', ':\\\\s*')}`, 'i'))) {\n warnings.push({ rule: 'unsupported-css', message: `CSS property \"${prop.replace(':', '')}\" not supported in most email clients.`, severity: 'warning' });\n }\n }\n\n // ── Outlook / MSO checks ──────────────────────────────\n\n if (!html.includes('<!--[if')) {\n warnings.push({ rule: 'mso-conditionals', message: 'No MSO conditional comments found. Outlook may not render multi-column layouts correctly.', severity: 'warning' });\n }\n\n if (!html.includes('xmlns:v') && !html.includes('xmlns:o')) {\n warnings.push({ rule: 'mso-namespace', message: 'Missing Microsoft Office XML namespaces. May affect Outlook rendering.', severity: 'warning' });\n }\n\n // ── Link checks ───────────────────────────────────────\n\n const links = html.match(/<a[^>]*>/gi) || [];\n const unstyledLinks = links.filter((link) => !link.includes('style='));\n if (unstyledLinks.length > 0) {\n warnings.push({ rule: 'link-color', message: `${unstyledLinks.length} link(s) without inline styles. Email clients override unstyled link colors.`, severity: 'warning' });\n }\n\n // ── Dark mode checks ──────────────────────────────────\n\n if (!html.includes('color-scheme') && !html.includes('prefers-color-scheme')) {\n warnings.push({ rule: 'dark-mode', message: 'No dark mode support detected. Consider adding color-scheme meta and prefers-color-scheme media query.', severity: 'warning' });\n }\n\n // ── Size checks ───────────────────────────────────────\n\n const sizeKB = Math.round(html.length / 1024);\n if (sizeKB > 102) {\n errors.push({ rule: 'size-limit', message: `Email HTML is ${sizeKB}KB. Gmail clips emails larger than 102KB.`, severity: 'error' });\n } else if (sizeKB > 80) {\n warnings.push({ rule: 'size-warning', message: `Email HTML is ${sizeKB}KB. Getting close to Gmail's 102KB clip limit.`, severity: 'warning' });\n }\n\n // ── Preheader check ───────────────────────────────────\n\n if (!html.includes('display:none') && !html.includes('max-height:0')) {\n warnings.push({ rule: 'preheader', message: 'No preheader text detected. Preheader text improves open rates in inbox previews.', severity: 'warning' });\n }\n\n // ── Score calculation ─────────────────────────────────\n\n const maxScore = 100;\n const errorPenalty = errors.length * 15;\n const warningPenalty = warnings.length * 5;\n const score = Math.max(0, maxScore - errorPenalty - warningPenalty);\n\n return {\n valid: errors.length === 0,\n errors,\n warnings,\n score,\n };\n}\n"],"names":["wrapInDocumentShell","bodyHtml","cssBlock","bodyValues","bgColor","contentWidth","fontFamily","textColor","preheaderContent","preheader","renderRow","row","toolRenderers","colsBgColor","padding","totalCells","a","b","bgStyle","bgImg","bgImage","repeat","size","position","needsGhostTable","columnEntries","col","i","colWidthPx","renderColumn","innerHtml","wrappedCols","colHtml","hideDesktop","hideMobile","widthPx","borderRadius","contentsHtml","content","renderer","ctx","html","getResponsiveCss","UNSUPPORTED_PROPS","PROP_REGEX","VAR_REGEX","sanitizeEmailHtml","match","styleContent","cleaned","renderDesignToHtml","design","options","rowsHtml","cleanRowsHtml","fullHtml","tag","value","bodyMatch","cssMatch","fontsUsed","s","validateEmailHtml","errors","warnings","tableCount","presentationCount","imgTags","img","styleBlocks","hasInlineStyles","unsupportedProps","prop","unstyledLinks","link","sizeKB","maxScore","errorPenalty","warningPenalty","score"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/layout/document-shell.ts","../src/layout/fluid-hybrid.ts","../src/utils/responsive-css.ts","../src/utils/html-sanitizer.ts","../src/render.ts","../src/utils/html-validator.ts"],"sourcesContent":["import type { BodyValues } from '@emabuild/types';\n\nexport function wrapInDocumentShell(bodyHtml: string, cssBlock: string, bodyValues: BodyValues): string {\n const bgColor = bodyValues.backgroundColor || '#e7e7e7';\n const contentWidth = bodyValues.contentWidth || '600px';\n const fontFamily = bodyValues.fontFamily?.value || 'arial,helvetica,sans-serif';\n const textColor = bodyValues.textColor || '#000000';\n const preheaderText = bodyValues.preheaderText || '';\n\n // Always include preheader wrapper — even if empty, the hidden div\n // prevents email clients from using the first visible text as preview\n const preheaderContent = preheaderText || '&zwnj;';\n const preheader = `<div style=\"display:none;font-size:1px;color:${bgColor};line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;\">${preheaderContent}${'&zwnj;&nbsp;'.repeat(80)}</div>`;\n\n return `<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:o=\"urn:schemas-microsoft-com:office:office\">\n<head>\n <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n <meta name=\"x-apple-disable-message-reformatting\">\n <meta name=\"format-detection\" content=\"telephone=no,address=no,email=no,date=no,url=no\">\n <meta name=\"color-scheme\" content=\"light dark\">\n <meta name=\"supported-color-schemes\" content=\"light dark\">\n <title></title>\n <!--[if mso]>\n <noscript><xml>\n <o:OfficeDocumentSettings>\n <o:AllowPNG/><o:PixelsPerInch>96</o:PixelsPerInch>\n </o:OfficeDocumentSettings>\n </xml></noscript>\n <style type=\"text/css\">\n table, td, th { font-family: ${fontFamily} !important; }\n </style>\n <![endif]-->\n <!--[if !mso]><!-->\n <style type=\"text/css\">\n ${cssBlock}\n </style>\n <!--<![endif]-->\n <style type=\"text/css\">\n body { margin: 0; padding: 0; word-break: normal; }\n table, tr, td { vertical-align: top; border-collapse: collapse; }\n p { margin: 0; }\n a[x-apple-data-detectors='true'] { color: inherit !important; text-decoration: none !important; }\n </style>\n</head>\n<body class=\"clean-body u_body\" style=\"margin:0;padding:0;-webkit-text-size-adjust:100%;background-color:${bgColor};color:${textColor};\">\n ${preheader}\n <table id=\"u_body\" role=\"presentation\" style=\"border-collapse:collapse;border-spacing:0;margin:0 auto;background-color:${bgColor};width:100%;\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\">\n <tbody>\n <tr>\n <td style=\"vertical-align:top;\">\n <!--[if (mso)|(IE)]><table width=\"${parseInt(contentWidth)}\" align=\"center\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\"><tr><td><![endif]-->\n ${bodyHtml}\n <!--[if (mso)|(IE)]></td></tr></table><![endif]-->\n </td>\n </tr>\n </tbody>\n </table>\n</body>\n</html>`;\n}\n","import type { DesignRow, DesignColumn, BodyValues } from '@emabuild/types';\n\ntype ContentRenderer = (values: Record<string, unknown>, ctx: any) => string;\n\nexport function renderRow(\n row: DesignRow,\n bodyValues: BodyValues,\n toolRenderers: Map<string, ContentRenderer>,\n): string {\n const contentWidth = parseInt(bodyValues.contentWidth || '600');\n const bgColor = row.values.backgroundColor || '';\n const colsBgColor = row.values.columnsBackgroundColor || '';\n const padding = row.values.padding || '0px';\n const totalCells = row.cells.reduce((a, b) => a + b, 0);\n\n const bgStyle = bgColor ? `background-color:${bgColor};` : '';\n const bgImg = row.values.backgroundImage;\n let bgImage = '';\n if (bgImg?.url) {\n // repeat can be: true, false, \"repeat\", \"no-repeat\", or a CSS value\n const repeat = bgImg.repeat === true || bgImg.repeat === 'repeat' ? 'repeat' : 'no-repeat';\n const size = bgImg.cover === true ? 'cover' : bgImg.fullWidth === true ? '100% auto' : 'auto';\n const position = bgImg.center !== false ? 'center top' : 'left top';\n bgImage = `background-image:url('${bgImg.url}');background-repeat:${repeat};background-position:${position};background-size:${size};`;\n }\n\n // Render columns once, wrap with MSO ghost table conditionals for multi-column\n const needsGhostTable = row.columns.length > 1;\n\n const columnEntries = row.columns.map((col, i) => {\n const colWidthPx = Math.round((row.cells[i] / totalCells) * contentWidth);\n const colHtml = renderColumn(col, colWidthPx, colsBgColor, bodyValues, toolRenderers);\n return { colHtml, colWidthPx };\n });\n\n let innerHtml: string;\n if (needsGhostTable) {\n // Wrap each column with MSO conditional <td> for Outlook.\n // For modern clients: inline-block columns inside a font-size:0 parent\n // (eliminates whitespace gaps between inline-block elements).\n const wrappedCols = columnEntries.map(({ colHtml, colWidthPx }) =>\n `<!--[if (mso)|(IE)]><td align=\"center\" width=\"${colWidthPx}\" style=\"width:${colWidthPx}px;padding:0px;\" valign=\"top\"><![endif]-->${colHtml}<!--[if (mso)|(IE)]></td><![endif]-->`\n );\n\n innerHtml = `<!--[if (mso)|(IE)]><table role=\"presentation\" width=\"${contentWidth}\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\"><tr><![endif]-->${wrappedCols.join('')}<!--[if (mso)|(IE)]></tr></table><![endif]-->`;\n } else {\n innerHtml = columnEntries.map(({ colHtml }) => colHtml).join('');\n }\n\n // Visibility classes\n const hideDesktop = row.values.hideDesktop ? ' u_hide_desktop' : '';\n const hideMobile = row.values.hideMobile ? ' u_hide_mobile' : '';\n\n // font-size:0 on the wrapper eliminates whitespace between inline-block columns\n const fsZero = needsGhostTable ? 'font-size:0;' : '';\n\n return `<div class=\"u_row${hideDesktop}${hideMobile}\" style=\"padding:${padding};${bgStyle}${bgImage}\">\n <div style=\"margin:0 auto;max-width:${contentWidth}px;${fsZero}text-align:center;\">${innerHtml}</div>\n</div>`;\n}\n\nfunction renderColumn(\n col: DesignColumn,\n widthPx: number,\n colsBgColor: string,\n bodyValues: BodyValues,\n toolRenderers: Map<string, ContentRenderer>,\n): string {\n const bgColor = col.values.backgroundColor || colsBgColor || '';\n const padding = col.values.padding || '0px';\n const borderRadius = col.values.borderRadius || '0px';\n const bgStyle = bgColor ? `background-color:${bgColor};` : '';\n\n const contentsHtml = col.contents\n .map((content) => {\n const renderer = toolRenderers.get(content.type);\n if (!renderer) return `<!-- unknown tool: ${content.type} -->`;\n const ctx = {\n columnWidth: widthPx,\n displayMode: 'email',\n contentWidth: parseInt(bodyValues.contentWidth || '600'),\n bodyValues,\n };\n let html = renderer(content.values, ctx);\n\n // Wrap with visibility classes if hideDesktop/hideMobile is set\n const hideDesktop = !!content.values.hideDesktop;\n const hideMobile = !!content.values.hideMobile;\n if (hideDesktop || hideMobile) {\n const cls = [hideDesktop && 'u_hide_desktop', hideMobile && 'u_hide_mobile'].filter(Boolean).join(' ');\n html = `<div class=\"${cls}\">${html}</div>`;\n }\n\n return html;\n })\n .join('\\n');\n\n return `<div class=\"u_column\" style=\"display:inline-block;vertical-align:top;width:${widthPx}px;max-width:${widthPx}px;font-size:14px;text-align:left;\">\n <div style=\"width:100%;${bgStyle}${borderRadius !== '0px' ? `border-radius:${borderRadius};` : ''}\">\n <div style=\"padding:${padding};\">\n ${contentsHtml || '&nbsp;'}\n </div>\n </div>\n</div>`;\n}\n","export function getResponsiveCss(contentWidth: number): string {\n return `\n@media only screen and (min-width: ${contentWidth + 20}px) {\n .u_row .u_column { display: inline-block !important; }\n}\n\n@media only screen and (max-width: ${contentWidth + 20}px) {\n .u_row .u_column {\n display: block !important;\n width: 100% !important;\n max-width: 100% !important;\n }\n .u_row {\n width: 100% !important;\n }\n}\n\n@media only screen and (max-width: 620px) {\n .u_row-container {\n max-width: 100% !important;\n padding-left: 0 !important;\n padding-right: 0 !important;\n }\n}\n\n@media (prefers-color-scheme: dark) {\n /* Dark mode overrides — add per-client rules as needed */\n}\n\n/* Outlook dark mode */\n[data-ogsb] body,\n[data-ogsb] table,\n[data-ogsb] td {\n /* Preserve original colors */\n}\n\n.u_hide_desktop { display: block !important; }\n.u_hide_mobile { display: block !important; }\n\n@media only screen and (max-width: ${contentWidth + 20}px) {\n .u_hide_desktop { display: block !important; }\n .u_hide_mobile { display: none !important; }\n}\n\n@media only screen and (min-width: ${contentWidth + 21}px) {\n .u_hide_desktop { display: none !important; }\n .u_hide_mobile { display: block !important; }\n}`;\n}\n","/**\n * @module html-sanitizer\n *\n * Sanitizes user-generated HTML content for email compatibility.\n * Removes CSS properties that are not supported by email clients\n * and replaces CSS variables with fallback values.\n */\n\n/** CSS properties to strip from inline styles */\nconst UNSUPPORTED_PROPS = [\n 'box-sizing',\n 'float',\n 'overflow-wrap',\n 'word-break',\n 'word-wrap',\n 'outline',\n 'cursor',\n 'transition',\n 'animation',\n 'transform',\n 'position',\n 'z-index',\n 'display:\\\\s*flex',\n 'display:\\\\s*grid',\n 'gap',\n];\n\n/** Build a single regex that matches any unsupported property in a style attribute */\nconst PROP_REGEX = new RegExp(\n `(?:;\\\\s*|^\\\\s*)(${UNSUPPORTED_PROPS.join('|')})\\\\s*:[^;]*;?`,\n 'gi',\n);\n\n/** Replace CSS var() with empty string (email clients don't support CSS variables) */\nconst VAR_REGEX = /var\\(--[^)]*\\)/gi;\n\n/**\n * Clean inline styles in HTML content to remove email-unsupported CSS.\n * Processes `style=\"...\"` attributes and removes problematic properties.\n *\n * @param html - Raw HTML content (e.g. from a text/heading block)\n * @returns Cleaned HTML with email-safe inline styles\n */\nexport function sanitizeEmailHtml(html: string): string {\n // Process each style=\"...\" attribute\n return html.replace(/style=\"([^\"]*)\"/gi, (match, styleContent: string) => {\n let cleaned = styleContent;\n\n // Remove unsupported properties\n cleaned = cleaned.replace(PROP_REGEX, '');\n\n // Replace CSS variables with empty/inherit\n cleaned = cleaned.replace(VAR_REGEX, 'inherit');\n\n // Clean up multiple semicolons and whitespace\n cleaned = cleaned.replace(/;\\s*;/g, ';').replace(/^\\s*;\\s*/, '').replace(/;\\s*$/, '').trim();\n\n return cleaned ? `style=\"${cleaned}\"` : '';\n });\n}\n","import type { EmailDesign, ExportResult, ExportOptions, BodyValues } from '@emabuild/types';\nimport { wrapInDocumentShell } from './layout/document-shell.js';\nimport { renderRow } from './layout/fluid-hybrid.js';\nimport { getResponsiveCss } from './utils/responsive-css.js';\nimport { sanitizeEmailHtml } from './utils/html-sanitizer.js';\n\ntype ContentRenderer = (values: Record<string, unknown>, ctx: any) => string;\n\nexport function renderDesignToHtml(\n design: EmailDesign,\n toolRenderers: Map<string, ContentRenderer>,\n options?: ExportOptions,\n): ExportResult {\n const bodyValues = design.body.values;\n const contentWidth = parseInt(bodyValues.contentWidth || '600');\n\n // Render all rows\n const rowsHtml = design.body.rows\n .map((row) => renderRow(row, bodyValues, toolRenderers))\n .join('\\n');\n\n // Build responsive CSS\n const cssBlock = getResponsiveCss(contentWidth);\n\n // Sanitize user content (remove email-unsupported CSS)\n const cleanRowsHtml = sanitizeEmailHtml(rowsHtml);\n\n // Build full document\n let fullHtml = wrapInDocumentShell(cleanRowsHtml, cssBlock, bodyValues);\n\n // Process merge tags if provided\n if (options?.mergeTags) {\n for (const [tag, value] of Object.entries(options.mergeTags)) {\n fullHtml = fullHtml.replaceAll(`{{${tag}}}`, value);\n }\n }\n\n // Extract chunks\n const bodyMatch = fullHtml.match(/<body[^>]*>([\\s\\S]*)<\\/body>/i);\n const cssMatch = fullHtml.match(/<style[^>]*>([\\s\\S]*?)<\\/style>/gi);\n const fontsUsed: string[] = [];\n\n // Collect Google Fonts\n if (bodyValues.fontFamily?.url) {\n fontsUsed.push(bodyValues.fontFamily.url);\n }\n\n return {\n design: structuredClone(design),\n html: fullHtml,\n chunks: {\n body: bodyMatch?.[1] ?? rowsHtml,\n css: cssMatch?.map((s) => s.replace(/<\\/?style[^>]*>/gi, '')).join('\\n') ?? cssBlock,\n fonts: fontsUsed,\n js: '',\n },\n };\n}\n","/**\n * @module html-validator\n *\n * Validates exported email HTML against common email client compatibility rules.\n * Returns a list of warnings and errors that may cause rendering issues.\n */\n\nexport interface ValidationResult {\n valid: boolean;\n errors: ValidationIssue[];\n warnings: ValidationIssue[];\n score: number; // 0-100\n}\n\nexport interface ValidationIssue {\n rule: string;\n message: string;\n severity: 'error' | 'warning';\n line?: number;\n}\n\n/** Validate email HTML for cross-client compatibility */\nexport function validateEmailHtml(html: string): ValidationResult {\n const errors: ValidationIssue[] = [];\n const warnings: ValidationIssue[] = [];\n\n // ── Structure checks ──────────────────────────────────\n\n if (!html.includes('<!DOCTYPE')) {\n errors.push({ rule: 'doctype', message: 'Missing <!DOCTYPE> declaration. Required for consistent rendering.', severity: 'error' });\n }\n\n if (!html.includes('<meta') || !html.includes('charset')) {\n errors.push({ rule: 'charset', message: 'Missing charset meta tag. Add <meta charset=\"UTF-8\">.', severity: 'error' });\n }\n\n if (!html.includes('viewport')) {\n warnings.push({ rule: 'viewport', message: 'Missing viewport meta tag. Needed for responsive rendering on mobile.', severity: 'warning' });\n }\n\n // ── Table layout checks ───────────────────────────────\n\n const tableCount = (html.match(/<table/gi) || []).length;\n const presentationCount = (html.match(/role=\"presentation\"/gi) || []).length;\n if (tableCount > 0 && presentationCount < tableCount * 0.5) {\n warnings.push({ rule: 'table-role', message: `Only ${presentationCount}/${tableCount} tables have role=\"presentation\". Screen readers may interpret layout tables as data tables.`, severity: 'warning' });\n }\n\n // ── Image checks ──────────────────────────────────────\n\n const imgTags = html.match(/<img[^>]*>/gi) || [];\n for (const img of imgTags) {\n if (!img.includes('alt=')) {\n errors.push({ rule: 'img-alt', message: 'Image missing alt attribute. Required for accessibility and when images are blocked.', severity: 'error' });\n }\n if (!img.includes('style=') || !img.includes('display')) {\n warnings.push({ rule: 'img-display', message: 'Image without display:block may have a bottom gap in some email clients.', severity: 'warning' });\n }\n if (img.includes('width=\"100%\"') || (!img.includes('width=') && !img.includes('width:'))) {\n warnings.push({ rule: 'img-width', message: 'Image without explicit pixel width. Some email clients ignore percentage widths.', severity: 'warning' });\n }\n }\n\n // ── CSS checks ────────────────────────────────────────\n\n const styleBlocks = html.match(/<style[^>]*>[\\s\\S]*?<\\/style>/gi) || [];\n const hasInlineStyles = html.includes('style=\"');\n\n if (styleBlocks.length > 0 && !html.includes('@media')) {\n warnings.push({ rule: 'no-media-queries', message: 'Style block found but no @media queries. Consider adding responsive breakpoints.', severity: 'warning' });\n }\n\n if (!hasInlineStyles && tableCount > 0) {\n errors.push({ rule: 'inline-styles', message: 'No inline styles detected. Gmail and many clients strip <style> tags — inline styles are essential.', severity: 'error' });\n }\n\n // Check for unsupported CSS properties\n const unsupportedProps = ['position:fixed', 'position:absolute', 'display:flex', 'display:grid', 'gap:', 'transform:', 'animation:'];\n for (const prop of unsupportedProps) {\n if (html.includes(`style=\"`) && html.match(new RegExp(`style=\"[^\"]*${prop.replace(':', ':\\\\s*')}`, 'i'))) {\n warnings.push({ rule: 'unsupported-css', message: `CSS property \"${prop.replace(':', '')}\" not supported in most email clients.`, severity: 'warning' });\n }\n }\n\n // ── Outlook / MSO checks ──────────────────────────────\n\n if (!html.includes('<!--[if')) {\n warnings.push({ rule: 'mso-conditionals', message: 'No MSO conditional comments found. Outlook may not render multi-column layouts correctly.', severity: 'warning' });\n }\n\n if (!html.includes('xmlns:v') && !html.includes('xmlns:o')) {\n warnings.push({ rule: 'mso-namespace', message: 'Missing Microsoft Office XML namespaces. May affect Outlook rendering.', severity: 'warning' });\n }\n\n // ── Link checks ───────────────────────────────────────\n\n const links = html.match(/<a[^>]*>/gi) || [];\n const unstyledLinks = links.filter((link) => !link.includes('style='));\n if (unstyledLinks.length > 0) {\n warnings.push({ rule: 'link-color', message: `${unstyledLinks.length} link(s) without inline styles. Email clients override unstyled link colors.`, severity: 'warning' });\n }\n\n // ── Dark mode checks ──────────────────────────────────\n\n if (!html.includes('color-scheme') && !html.includes('prefers-color-scheme')) {\n warnings.push({ rule: 'dark-mode', message: 'No dark mode support detected. Consider adding color-scheme meta and prefers-color-scheme media query.', severity: 'warning' });\n }\n\n // ── Size checks ───────────────────────────────────────\n\n const sizeKB = Math.round(html.length / 1024);\n if (sizeKB > 102) {\n errors.push({ rule: 'size-limit', message: `Email HTML is ${sizeKB}KB. Gmail clips emails larger than 102KB.`, severity: 'error' });\n } else if (sizeKB > 80) {\n warnings.push({ rule: 'size-warning', message: `Email HTML is ${sizeKB}KB. Getting close to Gmail's 102KB clip limit.`, severity: 'warning' });\n }\n\n // ── Preheader check ───────────────────────────────────\n\n if (!html.includes('display:none') && !html.includes('max-height:0')) {\n warnings.push({ rule: 'preheader', message: 'No preheader text detected. Preheader text improves open rates in inbox previews.', severity: 'warning' });\n }\n\n // ── Score calculation ─────────────────────────────────\n\n const maxScore = 100;\n const errorPenalty = errors.length * 15;\n const warningPenalty = warnings.length * 5;\n const score = Math.max(0, maxScore - errorPenalty - warningPenalty);\n\n return {\n valid: errors.length === 0,\n errors,\n warnings,\n score,\n };\n}\n"],"names":["wrapInDocumentShell","bodyHtml","cssBlock","bodyValues","bgColor","contentWidth","fontFamily","textColor","preheaderContent","preheader","renderRow","row","toolRenderers","colsBgColor","padding","totalCells","a","b","bgStyle","bgImg","bgImage","repeat","size","position","needsGhostTable","columnEntries","col","i","colWidthPx","renderColumn","innerHtml","wrappedCols","colHtml","hideDesktop","hideMobile","widthPx","borderRadius","contentsHtml","content","renderer","ctx","html","getResponsiveCss","UNSUPPORTED_PROPS","PROP_REGEX","VAR_REGEX","sanitizeEmailHtml","match","styleContent","cleaned","renderDesignToHtml","design","options","rowsHtml","cleanRowsHtml","fullHtml","tag","value","bodyMatch","cssMatch","fontsUsed","s","validateEmailHtml","errors","warnings","tableCount","presentationCount","imgTags","img","styleBlocks","hasInlineStyles","unsupportedProps","prop","unstyledLinks","link","sizeKB","maxScore","errorPenalty","warningPenalty","score"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emabuild/email-renderer",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Email HTML renderer — converts design JSON to cross-client email HTML",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,7 +17,7 @@
17
17
  "README.md"
18
18
  ],
19
19
  "dependencies": {
20
- "@emabuild/types": "0.1.2"
20
+ "@emabuild/types": "0.1.4"
21
21
  },
22
22
  "devDependencies": {
23
23
  "vite": "^6.2.0",