fix: button/card syntax mismatch between blocksToMarkdown and markdownToBlocks

ROOT CAUSE: Complete flow trace revealed syntax mismatch:
- blocksToMarkdown outputs NEW syntax: [card:type], [button:style](url)Text[/button]
- markdownToBlocks ONLY parsed OLD syntax: [card type="..."], [button url="..."]

This caused buttons/cards to be lost when:
1. User adds button in Visual mode
2. blocksToMarkdown converts to [button:solid]({url})Text[/button]
3. handleBlocksChange stores this in markdownContent
4. When switching tabs/previewing, markdownToBlocks runs
5. It FAILED to parse new syntax, buttons disappear!

FIX: Added handlers for NEW syntax in markdownToBlocks (converter.ts):
- [card:type]...[/card] pattern (before old syntax)
- [button:style](url)Text[/button] pattern (before old syntax)

Now both syntaxes work correctly in round-trip conversion.
This commit is contained in:
Dwindi Ramadhana
2026-01-01 21:57:58 +07:00
parent 70006beeb9
commit 5a831ddf9d

View File

@@ -320,7 +320,24 @@ export function markdownToBlocks(markdown: string): EmailBlock[] {
const id = `block-${Date.now()}-${blockId++}`;
// Check for [card] blocks - match with proper boundaries
// Check for [card] blocks - NEW syntax [card:type]...[/card]
const newCardMatch = remaining.match(/^\[card:(\w+)\]([\s\S]*?)\[\/card\]/);
if (newCardMatch) {
const cardType = newCardMatch[1] as CardType;
const content = newCardMatch[2].trim();
blocks.push({
id,
type: 'card',
cardType,
content,
});
remaining = remaining.substring(newCardMatch[0].length);
continue;
}
// Check for [card] blocks - OLD syntax [card type="..."]...[/card]
const cardMatch = remaining.match(/^\[card([^\]]*)\]([\s\S]*?)\[\/card\]/);
if (cardMatch) {
const attributes = cardMatch[1].trim();
@@ -347,7 +364,24 @@ export function markdownToBlocks(markdown: string): EmailBlock[] {
continue;
}
// Check for [button] blocks
// Check for [button] blocks - NEW syntax [button:style](url)Text[/button]
const newButtonMatch = remaining.match(/^\[button:(\w+)\]\(([^)]+)\)([^\[]+)\[\/button\]/);
if (newButtonMatch) {
blocks.push({
id,
type: 'button',
text: newButtonMatch[3].trim(),
link: newButtonMatch[2],
style: newButtonMatch[1] as ButtonStyle,
align: 'center',
widthMode: 'fit',
});
remaining = remaining.substring(newButtonMatch[0].length);
continue;
}
// Check for [button] blocks - OLD syntax [button url="..." style="..."]Text[/button]
const buttonMatch = remaining.match(/^\[button\s+url=["']([^"']+)["'](?:\s+style=["'](solid|outline)["'])?\]([^\[]+)\[\/button\]/);
if (buttonMatch) {
blocks.push({