#!/usr/bin/env nu # Highlight.js syntax highlighting optimizer # Automatically detects code blocks and includes necessary highlight.js assets # Main highlight.js processing function export def process_highlight [html_content: string, config: record, dev: bool, verbose: bool] { let highlight_config = if "highlight" in ($config | columns) { $config.highlight } else { { enabled: false } } # Handle different enabled values: "auto", true, false let is_enabled = match $highlight_config.enabled { "auto" => true # Will be determined by auto-detection true => true false => false _ => false } if not $is_enabled { if $verbose { print "🖍️ Syntax highlighting disabled, skipping..." } return { html: $html_content, css_assets: [], js_assets: [], size: 0 } } if $verbose { print "🖍️ Processing syntax highlighting..." } # Auto-detect code blocks if enabled let auto_detect = if "auto_detect" in ($highlight_config | columns) { $highlight_config.auto_detect } else { true } let detected_languages = if $auto_detect { detect_code_languages $html_content $verbose } else { if "languages" in ($highlight_config | columns) { $highlight_config.languages } else { [] } } if ($detected_languages | length) == 0 { if $verbose { print " ⚠️ No code blocks detected, skipping highlight.js" } # Return original HTML without changes return { html: $html_content, css_assets: [], js_assets: [], size: 0 } } # For "auto" mode, only proceed if we actually detected code blocks if $highlight_config.enabled == "auto" and ($detected_languages | length) == 0 { return { html: $html_content, css_assets: [], js_assets: [], size: 0 } } if $verbose { print $" 📝 Detected languages: ($detected_languages | str join ', ')" } # Prepare highlight.js assets let assets = (prepare_highlight_assets $detected_languages $highlight_config $dev $verbose) # Enhance HTML with highlight.js integration let enhanced_html = (enhance_html_with_highlight $html_content $highlight_config $verbose) { html: $enhanced_html, css_assets: $assets.css_assets, js_assets: $assets.js_assets, size: $assets.total_size } } # Detect programming languages in HTML code blocks def detect_code_languages [html_content: string, verbose: bool] { mut languages = [] # Check for code blocks with language classes let has_code_blocks = ($html_content | str contains '' or ($html_content | str contains '')) if $has_generic_code and ($languages | length) == 0 { # Default to common languages for generic code blocks $languages = ["javascript", "bash", "json"] if $verbose { print " 💡 Found generic code blocks, assuming common languages" } } # Normalize language names to highlight.js format $languages | each { |lang| match $lang { "js" => "javascript" "ts" => "typescript" "sh" => "bash" "shell" => "bash" "yml" => "yaml" "py" => "python" _ => $lang } } | uniq } # Prepare highlight.js assets based on detected languages def prepare_highlight_assets [languages: list, highlight_config: record, dev: bool, verbose: bool] { let theme = if "theme" in ($highlight_config | columns) { $highlight_config.theme } else { "github-dark" } let use_cdn = if "cdn" in ($highlight_config | columns) { $highlight_config.cdn } else { false } let copy_button = if "copy_button" in ($highlight_config | columns) { $highlight_config.copy_button } else { true } mut css_assets = [] mut js_assets = [] mut total_size = 0 if $use_cdn { # Use CDN assets let cdn_url = if "cdn_url" in ($highlight_config | columns) { $highlight_config.cdn_url } else { "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/" } # Add CDN CSS $css_assets = ($css_assets | append { type: "cdn", url: $"($cdn_url)styles/($theme).min.css", inline: false }) # Add CDN JS $js_assets = ($js_assets | append { type: "cdn", url: $"($cdn_url)highlight.min.js", inline: false }) # Add language-specific modules for lang in $languages { $js_assets = ($js_assets | append { type: "cdn", url: $"($cdn_url)languages/($lang).min.js", inline: false }) } if $verbose { print $" 🌐 Using CDN assets for theme: ($theme)" } } else { # Use local assets - download and bundle let assets_result = (download_highlight_assets $languages $theme $dev $verbose) $css_assets = $assets_result.css_assets $js_assets = $assets_result.js_assets $total_size = $assets_result.total_size } # Add copy button functionality if enabled if $copy_button { let copy_js = (generate_copy_button_script) $js_assets = ($js_assets | append { type: "inline", content: $copy_js, inline: true }) } # Add highlight.js initialization let init_script = (generate_highlight_init_script $highlight_config) $js_assets = ($js_assets | append { type: "inline", content: $init_script, inline: true }) { css_assets: $css_assets, js_assets: $js_assets, total_size: $total_size } } # Download and prepare local highlight.js assets def download_highlight_assets [languages: list, theme: string, dev: bool, verbose: bool] { # Create vendor directory for highlight.js let vendor_dir = "shared/vendor/highlight" mkdir $vendor_dir mkdir ($vendor_dir | path join "styles") mkdir ($vendor_dir | path join "languages") mut css_assets = [] mut js_assets = [] mut total_size = 0 let base_url = "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build" # Download theme CSS let theme_css_path = ($vendor_dir | path join "styles" $"($theme).min.css") if not ($theme_css_path | path exists) { if $verbose { print $" 📥 Downloading theme: ($theme)" } try { http get $"($base_url)/styles/($theme).min.css" | save $theme_css_path } catch { if $verbose { print $" ⚠️ Failed to download theme ($theme), using github-dark fallback" } try { http get $"($base_url)/styles/github-dark.min.css" | save $theme_css_path } catch { if $verbose { print $" ❌ Failed to download highlight.js theme" } } } } if ($theme_css_path | path exists) { let css_size = (ls $theme_css_path | get size | first | into int) $total_size = ($total_size + $css_size) $css_assets = ($css_assets | append { type: "local", path: $theme_css_path, inline: false }) } # Download core highlight.js let core_js_path = ($vendor_dir | path join "highlight.min.js") if not ($core_js_path | path exists) { if $verbose { print " 📥 Downloading highlight.js core" } try { http get $"($base_url)/highlight.min.js" | save $core_js_path } catch { if $verbose { print " ❌ Failed to download highlight.js core" } } } if ($core_js_path | path exists) { let js_size = (ls $core_js_path | get size | first | into int) $total_size = ($total_size + $js_size) $js_assets = ($js_assets | append { type: "local", path: $core_js_path, inline: false }) } # Download language modules for lang in $languages { let lang_js_path = ($vendor_dir | path join "languages" $"($lang).min.js") if not ($lang_js_path | path exists) { if $verbose { print $" 📥 Downloading language: ($lang)" } try { http get $"($base_url)/languages/($lang).min.js" | save $lang_js_path } catch { if $verbose { print $" ⚠️ Failed to download language: ($lang)" } } } if ($lang_js_path | path exists) { let lang_size = (ls $lang_js_path | get size | first | into int) $total_size = ($total_size + $lang_size) $js_assets = ($js_assets | append { type: "local", path: $lang_js_path, inline: false }) } } { css_assets: $css_assets, js_assets: $js_assets, total_size: $total_size } } # Enhance HTML with highlight.js classes and copy buttons def enhance_html_with_highlight [html_content: string, highlight_config: record, verbose: bool] { let copy_button = if "copy_button" in ($highlight_config | columns) { $highlight_config.copy_button } else { true } let line_numbers = if "line_numbers" in ($highlight_config | columns) { $highlight_config.line_numbers } else { false } mut enhanced_html = $html_content # Add copy buttons to code blocks if enabled if $copy_button { # Replace
 blocks with enhanced version
        $enhanced_html = ($enhanced_html | str replace -ar '
]*)>' '
')
        $enhanced_html = ($enhanced_html | str replace -ar '
' '
') } # Add line numbers wrapper if enabled if $line_numbers { $enhanced_html = ($enhanced_html | str replace -ar '
]*class=)' ' {
        const originalText = button.textContent;
        button.textContent = '✅';
        button.style.color = '#10b981';

        setTimeout(() => {
            button.textContent = originalText;
            button.style.color = '';
        }, 2000);
    }).catch(() => {
        // Fallback for older browsers
        const textArea = document.createElement('textarea');
        textArea.value = text;
        document.body.appendChild(textArea);
        textArea.select();
        document.execCommand('copy');
        document.body.removeChild(textArea);

        const originalText = button.textContent;
        button.textContent = '✅';
        setTimeout(() => {
            button.textContent = originalText;
        }, 2000);
    });
}
"
}

# Generate highlight.js initialization script
def generate_highlight_init_script [highlight_config: record] {
    let line_numbers = if "line_numbers" in ($highlight_config | columns) { $highlight_config.line_numbers } else { false }

    let init_code = if $line_numbers {
        "
document.addEventListener('DOMContentLoaded', function() {
    hljs.highlightAll();
    hljs.initLineNumbersOnLoad();
});
"
    } else {
        "
document.addEventListener('DOMContentLoaded', function() {
    hljs.highlightAll();
});
"
    }

    $init_code
}

# Inject highlight.js assets into HTML head and body
export def inject_highlight_assets [html_content: string, css_assets: list, js_assets: list] {
    mut updated_html = $html_content

    # Inject CSS in head
    let css_tags = ($css_assets | each { |asset|
        match $asset.type {
            "cdn" => $""
            "local" => $""
            "inline" => $""
        }
    } | str join "\n")

    if ($css_tags | str length) > 0 {
        $updated_html = ($updated_html | str replace '' $"($css_tags)\n")
    }

    # Inject JS before 
    let js_tags = ($js_assets | each { |asset|
        match $asset.type {
            "cdn" => $""
            "local" => $""
            "inline" => $""
        }
    } | str join "\n")

    if ($js_tags | str length) > 0 {
        $updated_html = ($updated_html | str replace '' $"($js_tags)\n")
    }

    $updated_html
}

# Generate additional CSS for highlight.js styling
export def generate_highlight_css [highlight_config: record] {
    let copy_button = if "copy_button" in ($highlight_config | columns) { $highlight_config.copy_button } else { true }

    mut css = ""

    if $copy_button {
        $css = $css + "
/* Highlight.js code block styling with copy button */
.code-block-container {
    position: relative;
    margin: 1rem 0;
}

.copy-button {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    background: rgba(255, 255, 255, 0.1);
    border: 1px solid rgba(255, 255, 255, 0.2);
    border-radius: 4px;
    color: #fff;
    cursor: pointer;
    font-size: 0.9rem;
    padding: 0.25rem 0.5rem;
    transition: all 0.2s ease;
    z-index: 10;
}

.copy-button:hover {
    background: rgba(255, 255, 255, 0.2);
    border-color: rgba(255, 255, 255, 0.3);
}

.code-block-container pre {
    margin: 0;
    padding-top: 2.5rem;
}
"
    }

    # Add general code styling
    $css = $css + "
/* General code block styling */
pre code.hljs {
    border-radius: 8px;
    overflow-x: auto;
    padding: 1rem;
}

code:not(pre code) {
    background: rgba(255, 255, 255, 0.1);
    border-radius: 3px;
    font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
    font-size: 0.85em;
    padding: 0.2em 0.4em;
}

/* Line numbers styling */
pre.line-numbers {
    position: relative;
    padding-left: 3em;
}

pre.line-numbers::before {
    content: counter(line-number);
    counter-increment: line-number;
    position: absolute;
    left: 0;
    color: rgba(255, 255, 255, 0.4);
    text-align: right;
    width: 2.5em;
    padding-right: 0.5em;
}
"

    $css
}