diff --git a/apps/web/src/components/EmailPreviewModal.tsx b/apps/web/src/components/EmailPreviewModal.tsx index 52df741..27029d1 100644 --- a/apps/web/src/components/EmailPreviewModal.tsx +++ b/apps/web/src/components/EmailPreviewModal.tsx @@ -16,6 +16,391 @@ interface EmailPreviewModalProps { type PreviewDevice = 'mobile' | 'tablet' | 'desktop'; +// Detects if HTML contains custom patterns that indicate it was written in the HTML editor +// rather than the visual editor. Custom HTML should render as-is without prose wrapper. +const detectCustomHtmlPatterns = (html: string): boolean => { + if (!html || html.trim() === '') return false; + + // Check for common patterns that indicate custom HTML + const hasInlineStyles = /<[^>]+style\s*=\s*["'][^"']*["']/i.test(html); + + // Check for custom classes (exclude TipTap's generated classes) + const classMatches = html.matchAll(/class\s*=\s*["']([^"']*)["']/gi); + let hasCustomClasses = false; + for (const match of classMatches) { + const classValue = match[1]; + if (!classValue) continue; + // Split by whitespace to get individual classes + const classes = classValue.split(/\s+/).filter((c) => c.length > 0); + // Check if any class is NOT in the allowed list + const allowedPrefixes = [ + 'prose', + 'variable-', + 'email-image', + 'ProseMirror', + 'resizable-image', + 'selected', + 'resize-handle', + ]; + const hasDisallowedClass = classes.some((cls) => !allowedPrefixes.some((prefix) => cls.startsWith(prefix))); + if (hasDisallowedClass) { + hasCustomClasses = true; + break; + } + } + + const hasCustomAttributes = /<[^>]+(?:data-|aria-|role=|id=)/i.test(html); + const hasComplexTables = /