function processExcerptShortcodes(excerptHtml) { if (!excerptHtml) return ''; let processed = excerptHtml; // First, decode HTML entities to regular characters // Handle both numeric entities (”) and named entities (") processed = processed // Decode numeric HTML entities first .replace(/&#(\d+);/g, (match, dec) => String.fromCharCode(dec)) // Then handle any remaining Unicode characters .replace(/”/g, '"') // ” - Right double quote .replace(/“/g, '"') // “ - Left double quote .replace(/„/g, ',') // „ - Low double quote .replace(/‟/g, '"') // ‟ - High double quote .replace(/‘/g, "'") // ‘ - Left single quote .replace(/’/g, "'") // ’ - Right single quote .replace(/–/g, '-') // – - En dash .replace(/—/g, '—') // — - Em dash .replace(/…/g, '…') // … - Ellipsis .replace(/″/g, '"') // ″ - Inches/Prime .replace(/′/g, "'") // ′ - Feet/Prime .replace(/‚/g, ',') // ‚ - Single low quote .replace(/‛/g, '`') // ‛ - Single high reversed quote .replace(/•/g, '•') // • - Bullet .replace(/€/g, '€') // € - Euro // Named HTML entities .replace(/"/g, '"') .replace(/'/g, "'") .replace(/‘/g, "'") .replace(/’/g, "'") .replace(/“/g, '"') .replace(/”/g, '"') .replace(/–/g, '-') .replace(/—/g, '—') .replace(/…/g, '…') .replace(/•/g, '•') .replace(/€/g, '€'); // Process WPBakery shortcodes with HTML entities processed = processed // vc_row - convert to div with classes .replace(/\[vc_row([^\]]*)\]/gi, (match, attrs) => { const classes = ['vc-row']; if (attrs.includes('full_width_background')) classes.push('full-width-bg'); if (attrs.includes('in_container')) classes.push('in-container'); if (attrs.includes('full_width_content')) classes.push('full-width-content'); return `
`; }) .replace(/\[\/vc_row\]/gi, '
') // vc_column - convert to div with classes // Handle both complete and incomplete (truncated) shortcodes .replace(/\[vc_column([^\]]*)\]/gi, (match, attrs) => { const classes = ['vc-column']; if (attrs.includes('1/1')) classes.push('col-1-1'); if (attrs.includes('1/2')) classes.push('col-1-2'); if (attrs.includes('1/3')) classes.push('col-1-3'); if (attrs.includes('2/3')) classes.push('col-2-3'); if (attrs.includes('1/4')) classes.push('col-1-4'); if (attrs.includes('3/4')) classes.push('col-3-4'); if (attrs.includes('5/12')) classes.push('col-5-12'); if (attrs.includes('7/12')) classes.push('col-7-12'); return `
`; }) // Also handle incomplete vc_column shortcodes (truncated at end of excerpt) .replace(/\[vc_column([^\]]*)$/gi, (match, attrs) => { const classes = ['vc-column']; if (attrs.includes('1/1')) classes.push('col-1-1'); if (attrs.includes('1/2')) classes.push('col-1-2'); if (attrs.includes('1/3')) classes.push('col-1-3'); if (attrs.includes('2/3')) classes.push('col-2-3'); if (attrs.includes('1/4')) classes.push('col-1-4'); if (attrs.includes('3/4')) classes.push('col-3-4'); if (attrs.includes('5/12')) classes.push('col-5-12'); if (attrs.includes('7/12')) classes.push('col-7-12'); return `
`; }) .replace(/\[\/vc_column\]/gi, '
') // vc_column_text - convert to div .replace(/\[vc_column_text([^\]]*)\]/gi, '
') .replace(/\[\/vc_column_text\]/gi, '
') // nectar_cta - convert to button .replace(/\[nectar_cta([^\]]*)link_text="([^"]*)"(.*?)url="([^"]*)"(.*?)\]/gi, '$2') // nectar_highlighted_text - convert to span .replace(/\[nectar_highlighted_text([^\]]*)\](.*?)\[\/nectar_highlighted_text\]/gi, '$2') // nectar_responsive_text - convert to span .replace(/\[nectar_responsive_text([^\]]*)\](.*?)\[\/nectar_responsive_text\]/gi, '$2') // nectar_icon_list - convert to ul .replace(/\[nectar_icon_list([^\]]*)\]/gi, '') // nectar_icon_list_item - convert to li .replace(/\[nectar_icon_list_item([^\]]*)header="([^"]*)"(.*?)text="([^"]*)"(.*?)\]/gi, '
  • $2: $4
  • ') // nectar_btn - convert to button .replace(/\[nectar_btn([^\]]*)text="([^"]*)"(.*?)url="([^"]*)"(.*?)\]/gi, '$2') // split_line_heading - convert to heading .replace(/\[split_line_heading([^\]]*)text_content="([^"]*)"(.*?)\]/gi, '

    $2

    ') // vc_row_inner - convert to div .replace(/\[vc_row_inner([^\]]*)\]/gi, '
    ') .replace(/\[\/vc_row_inner\]/gi, '
    ') // vc_column_inner - convert to div .replace(/\[vc_column_inner([^\]]*)\]/gi, '
    ') .replace(/\[\/vc_column_inner\]/gi, '
    ') // divider - convert to hr .replace(/\[divider([^\]]*)\]/gi, '
    ') // vc_gallery - convert to div (placeholder) .replace(/\[vc_gallery([^\]]*)\]/gi, '') // vc_raw_js - remove or convert to div .replace(/\[vc_raw_js\](.*?)\[\/vc_raw_js\]/gi, '
    [JavaScript]
    ') // nectar_gmap - convert to div .replace(/\[nectar_gmap([^\]]*)\]/gi, '
    [Google Map]
    '); // Remove any remaining shortcodes processed = processed.replace(/\[.*?\]/g, ''); // Clean up any HTML that might be broken processed = processed.replace(/]*>\s*<\/p>/gi, ''); processed = processed.replace(/]*>\s*<\/div>/gi, ''); // Normalize whitespace processed = processed.replace(/\s+/g, ' ').trim(); return processed; } // Extract excerpt from content module.exports = processExcerptShortcodes;