provisioning-outreach/presentations/rust-laspalmas-250926/auroraframe/framework/optimizers/optimize-email.nu

361 lines
12 KiB
Text
Raw Permalink Normal View History

#!/usr/bin/env nu
# Email optimization and compatibility processor
# Transforms HTML for email client compatibility
# Main email processing function
export def process_email [html_content: string, config: record, dev: bool, verbose: bool] {
let email_config = if "email" in ($config | columns) { $config.email } else { { enabled: false } }
if not $email_config.enabled {
if $verbose { print "📧 Email optimization disabled, skipping..." }
return {
html: $html_content,
text_version: "",
size: 0
}
}
if $verbose { print "📧 Processing email optimizations..." }
# Apply email-specific transformations
let optimized_html = ($html_content
| inline_all_css $email_config $verbose
| optimize_for_email_clients $email_config $verbose
| convert_relative_urls $email_config $verbose
| strip_unsupported_elements $email_config $verbose
| add_email_fallbacks $email_config $verbose
)
# Generate plain text version if requested
let text_version = if "text_output" in ($email_config.output | columns) {
generate_text_version $optimized_html $verbose
} else {
""
}
{
html: $optimized_html,
text_version: $text_version,
size: ($optimized_html | str length)
}
}
# Inline all CSS for email compatibility
def inline_all_css [html_content: string, email_config: record, verbose: bool] {
if not $email_config.inline_css {
return $html_content
}
if $verbose { print " 🎨 Inlining all CSS for email compatibility..." }
# Extract CSS from <style> tags and <link> references
mut css_content = ""
# Find all <style> tags
let style_matches = ($html_content | str find-replace -ar '<style[^>]*>(.*?)</style>' '::STYLE::$1::STYLE::')
let style_parts = ($style_matches | split row '::STYLE::' | where { |part| not ($part | str starts-with '::') and ($part | str length) > 0 })
for part in $style_parts {
if not ($part | str starts-with '::') {
$css_content = ($css_content + "\n" + $part)
}
}
# Find all <link rel="stylesheet"> tags and read their content
let link_matches = ($html_content | str find-replace -ar '<link[^>]*rel="stylesheet"[^>]*href="([^"]*)"[^>]*>' '::LINK::$1::LINK::')
let link_parts = ($link_matches | split row '::LINK::' | where { |part| not ($part | str starts-with '::') and ($part | str length) > 0 })
for link_url in $link_parts {
if not ($link_url | str starts-with '::') {
# Try to read the CSS file
let css_path = if ($link_url | str starts-with 'http') {
# External URL - would need http get
continue
} else {
# Local file
$link_url
}
if ($css_path | path exists) {
let css_file_content = (open $css_path)
$css_content = ($css_content + "\n" + $css_file_content)
}
}
}
# Remove all <style> and <link> tags
let html_without_css = ($html_content
| str replace -ar '<style[^>]*>.*?</style>' ''
| str replace -ar '<link[^>]*rel="stylesheet"[^>]*>' ''
)
# Apply CSS inlining (simplified version - in production would use a proper CSS parser)
let inlined_html = (apply_css_inline $html_without_css $css_content $verbose)
$inlined_html
}
# Apply CSS rules inline to elements (simplified implementation)
def apply_css_inline [html_content: string, css_content: string, verbose: bool] {
# This is a simplified CSS inlining - in production, you'd use a proper CSS parser
# For now, just add the CSS as an inline style block for email clients that support it
let style_block = if ($css_content | str length) > 0 {
$"<style>($css_content)</style>"
} else {
""
}
# Insert the style block in the head
let result = ($html_content | str replace '</head>' $"($style_block)\n</head>")
if $verbose { print " ✅ CSS inlined (simplified implementation)" }
$result
}
# Optimize HTML for email client compatibility
def optimize_for_email_clients [html_content: string, email_config: record, verbose: bool] {
if $verbose { print " 🔧 Optimizing for email client compatibility..." }
mut optimized_html = $html_content
# Convert divs to tables for better Outlook compatibility
if $email_config.compatibility.outlook {
# This is a simplified conversion - in production would be more sophisticated
$optimized_html = ($optimized_html
| str replace -ar '<div([^>]*class="[^"]*container[^"]*"[^>]*)>' '<table$1 border="0" cellpadding="0" cellspacing="0" width="100%"><tr><td>'
| str replace -ar '</div>(\s*<!--[^>]*container[^>]*-->)?' '</td></tr></table>'
)
}
# Add Outlook-specific MSO conditionals
if $email_config.compatibility.outlook {
$optimized_html = ($optimized_html
| str replace '<body' '<!--[if mso]><table width="100%" border="0" cellpadding="0" cellspacing="0"><tr><td><![endif]--><body'
| str replace '</body>' '</body><!--[if mso]></td></tr></table><![endif]-->'
)
}
# Ensure proper width constraints for email
let max_width = $email_config.max_width
$optimized_html = ($optimized_html
| str replace -ar 'max-width:\s*\d+px' $"max-width: ($max_width)px"
| str replace -ar 'width:\s*100%' $"width: ($max_width)px; max-width: 100%"
)
if $verbose { print " ✅ Applied email client optimizations" }
$optimized_html
}
# Convert relative URLs to absolute URLs
def convert_relative_urls [html_content: string, email_config: record, verbose: bool] {
if not $email_config.absolute_urls {
return $html_content
}
if $verbose { print " 🔗 Converting relative URLs to absolute..." }
# This would need a base URL from config
let base_url = if "base_url" in ($email_config | columns) {
$email_config.base_url
} else {
"https://example.com"
}
let result = ($html_content
| str replace -ar 'href="(?!http|mailto|#)([^"]*)"' $'href="($base_url)/$1"'
| str replace -ar 'src="(?!http|data:)([^"]*)"' $'src="($base_url)/$1"'
)
if $verbose { print " ✅ Converted relative URLs" }
$result
}
# Remove unsupported HTML elements and CSS properties
def strip_unsupported_elements [html_content: string, email_config: record, verbose: bool] {
if not $email_config.remove_unsupported {
return $html_content
}
if $verbose { print " 🚫 Removing unsupported elements..." }
mut stripped_html = $html_content
# Remove JavaScript
if $email_config.strip_js {
$stripped_html = ($stripped_html
| str replace -ar '<script[^>]*>.*?</script>' ''
| str replace -ar 'on\w+="[^"]*"' ''
| str replace -ar "on\w+='[^']*'" ''
)
}
# Remove unsupported CSS properties
$stripped_html = ($stripped_html
| str replace -ar 'position:\s*(fixed|absolute|sticky);?' ''
| str replace -ar 'z-index:\s*\d+;?' ''
| str replace -ar 'transform:[^;]+;?' ''
| str replace -ar 'transition:[^;]+;?' ''
| str replace -ar 'animation:[^;]+;?' ''
| str replace -ar '@keyframes[^}]+}' ''
| str replace -ar '@media[^{]+{[^}]+}' ''
)
# Remove form elements (not well supported in email)
$stripped_html = ($stripped_html
| str replace -ar '<form[^>]*>.*?</form>' ''
| str replace -ar '<input[^>]*>' ''
| str replace -ar '<textarea[^>]*>.*?</textarea>' ''
| str replace -ar '<select[^>]*>.*?</select>' ''
| str replace -ar '<button[^>]*>.*?</button>' ''
)
if $verbose { print " ✅ Removed unsupported elements" }
$stripped_html
}
# Add fallbacks for email clients
def add_email_fallbacks [html_content: string, email_config: record, verbose: bool] {
if $verbose { print " 🔄 Adding email client fallbacks..." }
mut fallback_html = $html_content
# Add fallback fonts
let fallback_fonts = ($email_config.fallback_fonts | str join ", ")
$fallback_html = ($fallback_html
| str replace -ar 'font-family:\s*[^;]+' $"font-family: ($fallback_fonts)"
)
# Add alt text to images that don't have it
$fallback_html = ($fallback_html
| str replace -ar '<img([^>]+)src="([^"]*)"([^>]*?)>' '<img$1src="$2"$3 alt="Image">'
| str replace -ar 'alt="Image"([^>]*?)alt="([^"]*)"' 'alt="$2"$1'
)
# Add table-based layout fallbacks
$fallback_html = ($fallback_html
| str replace -ar '<div class="button">' '<table border="0" cellpadding="0" cellspacing="0"><tr><td style="background-color: #0066cc; border-radius: 4px; padding: 12px 24px;"><a style="color: #ffffff; text-decoration: none; font-weight: bold;">'
| str replace -ar '</div>(\s*<!--[^>]*button[^>]*-->)?' '</a></td></tr></table>'
)
if $verbose { print " ✅ Added email client fallbacks" }
$fallback_html
}
# Generate plain text version of the email
def generate_text_version [html_content: string, verbose: bool] {
if $verbose { print " 📝 Generating plain text version..." }
# Remove HTML tags and convert to plain text
let text_content = ($html_content
| str replace -ar '<script[^>]*>.*?</script>' ''
| str replace -ar '<style[^>]*>.*?</style>' ''
| str replace -ar '<[^>]+>' ''
| str replace -ar '&nbsp;' ' '
| str replace -ar '&amp;' '&'
| str replace -ar '&lt;' '<'
| str replace -ar '&gt;' '>'
| str replace -ar '&quot;' '"'
| str replace -ar '&#39;' "'"
| str replace -ar '\s+' ' '
| str trim
)
if $verbose { print " ✅ Generated plain text version" }
$text_content
}
# Save email outputs
export def save_email_outputs [html_content: string, text_content: string, config: record, verbose: bool] {
let email_config = $config.email
let output_config = $email_config.output
# Save HTML version
let html_output_path = $"($config.dist_dir)/($output_config.html_output)"
$html_content | save --force $html_output_path
if $verbose { print $" ✅ Saved email HTML: ($html_output_path)" }
# Save text version if configured
if "text_output" in ($output_config | columns) and ($text_content | str length) > 0 {
let text_output_path = $"($config.dist_dir)/($output_config.text_output)"
$text_content | save --force $text_output_path
if $verbose { print $" ✅ Saved email text: ($text_output_path)" }
}
# Generate preview version if configured
if "preview_output" in ($output_config | columns) {
let preview_html = (generate_email_preview $html_content $config)
let preview_output_path = $"($config.dist_dir)/($output_config.preview_output)"
$preview_html | save --force $preview_output_path
if $verbose { print $" ✅ Saved email preview: ($preview_output_path)" }
}
}
# Generate email preview with client simulation
def generate_email_preview [email_html: string, config: record] {
let preview_html = $"<!DOCTYPE html>
<html lang=\"en\">
<head>
<meta charset=\"utf-8\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<title>Email Preview</title>
<style>
body {
font-family: Arial, sans-serif;
background: #f5f5f5;
margin: 0;
padding: 20px;
}
.preview-container {
max-width: 800px;
margin: 0 auto;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.preview-header {
background: #2c3e50;
color: white;
padding: 15px 20px;
font-weight: bold;
}
.preview-content {
padding: 20px;
border: 2px dashed #ddd;
background: #fafafa;
}
.email-frame {
background: white;
border: 1px solid #ddd;
border-radius: 4px;
overflow: auto;
}
</style>
</head>
<body>
<div class=\"preview-container\">
<div class=\"preview-header\">
📧 Email Preview - Web Builder Framework
</div>
<div class=\"preview-content\">
<p><strong>Note:</strong> This is a preview of how your email might appear in email clients.</p>
<div class=\"email-frame\">
($email_html)
</div>
</div>
</div>
</body>
</html>"
$preview_html
}