361 lines
No EOL
12 KiB
Text
361 lines
No EOL
12 KiB
Text
#!/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 ' ' ' '
|
|
| str replace -ar '&' '&'
|
|
| str replace -ar '<' '<'
|
|
| str replace -ar '>' '>'
|
|
| str replace -ar '"' '"'
|
|
| str replace -ar ''' "'"
|
|
| 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
|
|
} |