From 47f6370ce0f83daf6b33bddfefc3ee952da9c476 Mon Sep 17 00:00:00 2001 From: Dwindi Ramadhana Date: Thu, 1 Jan 2026 23:31:54 +0700 Subject: [PATCH] fix: TipTap button conversion in card save flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ROOT CAUSE: When saving card edit in EmailBuilder, htmlToMarkdown() was called. The old code at line 26 converted ALL tags to markdown links: text → [text](url) This lost TipTap button data-button attributes, converting buttons to plain text instead of [button:style](url)Text[/button] shortcode. FIX: Added TipTap button detection BEFORE generic link conversion in html-to-markdown.ts: - Detects elements - Extracts style from data-style or class attribute - Extracts URL from data-href or href attribute - Converts to [button:style](url)Text[/button] format FLOW NOW WORKS: 1. User adds button via TipTap toolbar 2. TipTap renders 3. User clicks Save Changes 4. htmlToMarkdown detects data-button → [button:solid](url)Text[/button] 5. Card content saved with proper button shortcode 6. On re-edit, button shortcode converted back to TipTap button --- admin-spa/src/lib/html-to-markdown.ts | 56 ++++++++++++++++++++------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/admin-spa/src/lib/html-to-markdown.ts b/admin-spa/src/lib/html-to-markdown.ts index 728e0e7..e468138 100644 --- a/admin-spa/src/lib/html-to-markdown.ts +++ b/admin-spa/src/lib/html-to-markdown.ts @@ -5,26 +5,52 @@ export function htmlToMarkdown(html: string): string { if (!html) return ''; - + let markdown = html; - + // Headings markdown = markdown.replace(/

(.*?)<\/h1>/gi, '# $1\n\n'); markdown = markdown.replace(/

(.*?)<\/h2>/gi, '## $1\n\n'); markdown = markdown.replace(/

(.*?)<\/h3>/gi, '### $1\n\n'); markdown = markdown.replace(/

(.*?)<\/h4>/gi, '#### $1\n\n'); - + // Bold markdown = markdown.replace(/(.*?)<\/strong>/gi, '**$1**'); markdown = markdown.replace(/(.*?)<\/b>/gi, '**$1**'); - + // Italic markdown = markdown.replace(/(.*?)<\/em>/gi, '*$1*'); markdown = markdown.replace(/(.*?)<\/i>/gi, '*$1*'); - - // Links + + // TipTap buttons - detect by data-button attribute, BEFORE generic links + // Format: text + // or: text + markdown = markdown.replace(/]*data-button[^>]*>(.*?)<\/a>/gi, (match, text) => { + // Extract style from data-style or class + let style = 'solid'; + const styleMatch = match.match(/data-style=["'](\w+)["']/); + if (styleMatch) { + style = styleMatch[1]; + } else if (match.includes('button-outline') || match.includes('outline')) { + style = 'outline'; + } + + // Extract href from data-href or href attribute + let url = '#'; + const dataHrefMatch = match.match(/data-href=["']([^"']+)["']/); + const hrefMatch = match.match(/href=["']([^"']+)["']/); + if (dataHrefMatch) { + url = dataHrefMatch[1]; + } else if (hrefMatch) { + url = hrefMatch[1]; + } + + return `[button:${style}](${url})${text.trim()}[/button]`; + }); + + // Regular links (not buttons) markdown = markdown.replace(/]*>(.*?)<\/a>/gi, '[$2]($1)'); - + // Lists markdown = markdown.replace(/]*>(.*?)<\/ul>/gis, (match, content) => { const items = content.match(/]*>(.*?)<\/li>/gis) || []; @@ -33,7 +59,7 @@ export function htmlToMarkdown(html: string): string { return `- ${text}`; }).join('\n') + '\n\n'; }); - + markdown = markdown.replace(/]*>(.*?)<\/ol>/gis, (match, content) => { const items = content.match(/]*>(.*?)<\/li>/gis) || []; return items.map((item: string, index: number) => { @@ -41,24 +67,24 @@ export function htmlToMarkdown(html: string): string { return `${index + 1}. ${text}`; }).join('\n') + '\n\n'; }); - + // Paragraphs - convert to double newlines markdown = markdown.replace(/]*>(.*?)<\/p>/gis, '$1\n\n'); - + // Line breaks markdown = markdown.replace(//gi, '\n'); - + // Horizontal rules markdown = markdown.replace(//gi, '\n---\n\n'); - + // Remove remaining HTML tags markdown = markdown.replace(/<[^>]+>/g, ''); - + // Clean up excessive newlines markdown = markdown.replace(/\n{3,}/g, '\n\n'); - + // Trim markdown = markdown.trim(); - + return markdown; }