From e84fa969bbaecd64b64676d9bf9b15a83a5f7d46 Mon Sep 17 00:00:00 2001 From: Dwindi Ramadhana Date: Thu, 1 Jan 2026 21:37:55 +0700 Subject: [PATCH] fix: button rendering from RichEditor to markdown to HTML - Added multiple htmlToMarkdown patterns for TipTap button output: 1. data-button with data-href/data-style attributes 2. Alternate attribute order (data-style before data-href) 3. Simple data-button fallback with href and class 4. Buttons wrapped in p tags (from preview HTML) 5. Direct button links without p wrapper - Button shortcodes now correctly roundtrip: RichEditor -> HTML -> [button url=... style=...] -> Preview/Email - All patterns now explicitly include style=solid for consistency --- admin-spa/src/lib/markdown-utils.ts | 33 +++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/admin-spa/src/lib/markdown-utils.ts b/admin-spa/src/lib/markdown-utils.ts index 6ba1597..3377fa1 100644 --- a/admin-spa/src/lib/markdown-utils.ts +++ b/admin-spa/src/lib/markdown-utils.ts @@ -87,7 +87,7 @@ export function markdownToHtml(markdown: string): string { const parsedContent = parseMarkdownBasics(content.trim()); return `
${parsedContent}
`; }); - + // Parse [card type="..."] blocks (old syntax - backward compatibility) html = html.replace(/\[card(?:\s+type="([^"]+)")?\]([\s\S]*?)\[\/card\]/g, (match, type, content) => { const cardClass = type ? `card card-${type}` : 'card'; @@ -100,7 +100,7 @@ export function markdownToHtml(markdown: string): string { const buttonClass = style === 'outline' ? 'button-outline' : 'button'; return `

${text.trim()}

`; }); - + // Parse [button url="..."] shortcodes (old syntax - backward compatibility) html = html.replace(/\[button\s+url="([^"]+)"(?:\s+style="([^"]+)")?\]([^\[]+)\[\/button\]/g, (match, url, style, text) => { const buttonClass = style === 'outline' ? 'button-outline' : 'button'; @@ -155,7 +155,7 @@ export function parseMarkdownBasics(text: string): string { const buttonClass = style === 'outline' ? 'button-outline' : 'button'; return `

${text.trim()}

`; }); - + // Parse [button url="..."] shortcodes (old syntax - backward compatibility) html = html.replace(/\[button\s+url="([^"]+)"(?:\s+style="([^"]+)")?\]([^\[]+)\[\/button\]/g, (match, url, style, text) => { const buttonClass = style === 'outline' ? 'button-outline' : 'button'; @@ -267,8 +267,33 @@ export function htmlToMarkdown(html: string): string { }); // Convert buttons back to [button] syntax + // TipTap button format with data attributes: text + markdown = markdown.replace(/]*data-button[^>]*data-href="([^"]+)"[^>]*data-style="([^"]*)"[^>]*>([^<]+)<\/a>/gi, (match, url, style, text) => { + const styleAttr = style === 'outline' ? ' style="outline"' : ' style="solid"'; + return `[button url="${url}"${styleAttr}]${text.trim()}[/button]`; + }); + + // Alternate order: data-style before data-href + markdown = markdown.replace(/]*data-button[^>]*data-style="([^"]*)"[^>]*data-href="([^"]+)"[^>]*>([^<]+)<\/a>/gi, (match, style, url, text) => { + const styleAttr = style === 'outline' ? ' style="outline"' : ' style="solid"'; + return `[button url="${url}"${styleAttr}]${text.trim()}[/button]`; + }); + + // Simple data-button fallback (just has href and class) + markdown = markdown.replace(/]*href="([^"]+)"[^>]*class="(button[^"]*)"[^>]*data-button[^>]*>([^<]+)<\/a>/gi, (match, url, className, text) => { + const style = className.includes('outline') ? ' style="outline"' : ' style="solid"'; + return `[button url="${url}"${style}]${text.trim()}[/button]`; + }); + + // Buttons wrapped in p tags (from preview HTML):

text

markdown = markdown.replace(/]*>]*>([^<]+)<\/a><\/p>/g, (match, url, className, text) => { - const style = className.includes('outline') ? ' style="outline"' : ''; + const style = className.includes('outline') ? ' style="outline"' : ' style="solid"'; + return `[button url="${url}"${style}]${text.trim()}[/button]`; + }); + + // Direct button links without p wrapper + markdown = markdown.replace(/]*>([^<]+)<\/a>/g, (match, url, className, text) => { + const style = className.includes('outline') ? ' style="outline"' : ' style="solid"'; return `[button url="${url}"${style}]${text.trim()}[/button]`; });