diff --git a/admin-spa/src/components/EmailBuilder/converter.ts b/admin-spa/src/components/EmailBuilder/converter.ts
index f0a4b72..ad1ad0e 100644
--- a/admin-spa/src/components/EmailBuilder/converter.ts
+++ b/admin-spa/src/components/EmailBuilder/converter.ts
@@ -56,27 +56,27 @@ export function blocksToMarkdown(blocks: EmailBlock[]): string {
const cardSyntax = cardBlock.cardType !== 'default' ? `[card:${cardBlock.cardType}]` : '[card]';
return `${cardSyntax}\n\n${cardBlock.content}\n\n[/card]`;
}
-
+
case 'button': {
const buttonBlock = block as ButtonBlock;
// Use new [button:style](url)Text[/button] syntax
const style = buttonBlock.style || 'solid';
return `[button:${style}](${buttonBlock.link})${buttonBlock.text}[/button]`;
}
-
+
case 'image': {
const imageBlock = block as ImageBlock;
return `[image src="${imageBlock.src}" alt="${imageBlock.alt || ''}" width="${imageBlock.widthMode}" align="${imageBlock.align}"]`;
}
-
+
case 'divider':
return '---';
-
+
case 'spacer': {
const spacerBlock = block as SpacerBlock;
return `[spacer height="${spacerBlock.height}"]`;
}
-
+
default:
return '';
}
@@ -94,7 +94,7 @@ export function blocksToHTML(blocks: EmailBlock[]): string {
return `[card]\n${block.content}\n[/card]`;
}
return `[card type="${block.cardType}"]\n${block.content}\n[/card]`;
-
+
case 'button': {
const buttonClass = block.style === 'solid' ? 'button' : 'button-outline';
const align = block.align || 'center';
@@ -118,13 +118,13 @@ export function blocksToHTML(blocks: EmailBlock[]): string {
}
return `

`;
}
-
+
case 'divider':
return `
`;
-
+
case 'spacer':
return ``;
-
+
default:
return '';
}
@@ -137,39 +137,39 @@ export function blocksToHTML(blocks: EmailBlock[]): string {
export function htmlToBlocks(html: string): EmailBlock[] {
const blocks: EmailBlock[] = [];
let blockId = 0;
-
+
// Match both [card] syntax and HTML
const cardRegex = /(?:\[card([^\]]*)\]([\s\S]*?)\[\/card\]|
]*>([\s\S]*?)<\/div>)/gs;
const parts: string[] = [];
let lastIndex = 0;
let match;
-
+
while ((match = cardRegex.exec(html)) !== null) {
// Add content before card
if (match.index > lastIndex) {
const beforeContent = html.substring(lastIndex, match.index).trim();
if (beforeContent) parts.push(beforeContent);
}
-
+
// Add card
parts.push(match[0]);
lastIndex = match.index + match[0].length;
}
-
+
// Add remaining content
if (lastIndex < html.length) {
const remaining = html.substring(lastIndex).trim();
if (remaining) parts.push(remaining);
}
-
+
// Process each part
for (const part of parts) {
const id = `block-${Date.now()}-${blockId++}`;
-
+
// Check if it's a card - match [card:type], [card type="..."], and
let content = '';
let cardType = 'default';
-
+
// Try new [card:type] syntax first
let cardMatch = part.match(/\[card:(\w+)\]([\s\S]*?)\[\/card\]/s);
if (cardMatch) {
@@ -185,7 +185,7 @@ export function htmlToBlocks(html: string): EmailBlock[] {
cardType = (typeMatch ? typeMatch[1] : 'default');
}
}
-
+
if (!cardMatch) {
//
HTML syntax
const htmlCardMatch = part.match(/
]*>([\s\S]*?)<\/div>/s);
@@ -194,7 +194,7 @@ export function htmlToBlocks(html: string): EmailBlock[] {
content = htmlCardMatch[2].trim();
}
}
-
+
if (content) {
// Convert HTML content to markdown for clean editing
// But only if it actually contains HTML tags
@@ -208,14 +208,14 @@ export function htmlToBlocks(html: string): EmailBlock[] {
});
continue;
}
-
+
// Check if it's a button - try new syntax first
let buttonMatch = part.match(/\[button:(\w+)\]\(([^)]+)\)([^\[]+)\[\/button\]/);
if (buttonMatch) {
const style = buttonMatch[1] as ButtonStyle;
const url = buttonMatch[2];
const text = buttonMatch[3].trim();
-
+
blocks.push({
id,
type: 'button',
@@ -227,14 +227,14 @@ export function htmlToBlocks(html: string): EmailBlock[] {
});
continue;
}
-
+
// Try old [button url="..."] syntax
buttonMatch = part.match(/\[button\s+url=["']([^"']+)["'](?:\s+style=["'](\w+)["'])?\]([^\[]+)\[\/button\]/);
if (buttonMatch) {
const url = buttonMatch[1];
const style = (buttonMatch[2] || 'solid') as ButtonStyle;
const text = buttonMatch[3].trim();
-
+
blocks.push({
id,
type: 'button',
@@ -246,7 +246,7 @@ export function htmlToBlocks(html: string): EmailBlock[] {
});
continue;
}
-
+
// Check HTML button syntax
if (part.includes('class="button"') || part.includes('class="button-outline"')) {
const buttonMatch = part.match(/
]*href="([^"]*)"[^>]*class="(button[^"]*)"[^>]*style="([^"]*)"[^>]*>([^<]*)<\/a>/) ||
@@ -286,13 +286,13 @@ export function htmlToBlocks(html: string): EmailBlock[] {
continue;
}
}
-
+
// Check if it's a divider
if (part.includes('
0) {
remaining = remaining.trim();
if (!remaining) break;
-
+
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();
const content = cardMatch[2].trim();
-
+
// Extract card type
const typeMatch = attributes.match(/type\s*=\s*["']([^"']+)["']/);
const cardType = (typeMatch?.[1] || 'default') as CardType;
-
+
// Extract background
const bgMatch = attributes.match(/bg=["']([^"']+)["']/);
const bg = bgMatch?.[1];
-
+
blocks.push({
id,
type: 'card',
@@ -341,13 +358,30 @@ export function markdownToBlocks(markdown: string): EmailBlock[] {
content,
bg,
});
-
+
// Advance past this card
remaining = remaining.substring(cardMatch[0].length);
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({
@@ -359,11 +393,11 @@ export function markdownToBlocks(markdown: string): EmailBlock[] {
align: 'center',
widthMode: 'fit',
});
-
+
remaining = remaining.substring(buttonMatch[0].length);
continue;
}
-
+
// Check for [image] blocks
const imageMatch = remaining.match(/^\[image\s+src=["']([^"']+)["'](?:\s+alt=["']([^"']*)["'])?(?:\s+width=["']([^"']+)["'])?(?:\s+align=["']([^"']+)["'])?\]/);
if (imageMatch) {
@@ -375,11 +409,11 @@ export function markdownToBlocks(markdown: string): EmailBlock[] {
widthMode: (imageMatch[3] || 'fit') as ContentWidth,
align: (imageMatch[4] || 'center') as ContentAlign,
});
-
+
remaining = remaining.substring(imageMatch[0].length);
continue;
}
-
+
// Check for [spacer] blocks
const spacerMatch = remaining.match(/^\[spacer\s+height=["'](\d+)["']\]/);
if (spacerMatch) {
@@ -388,25 +422,25 @@ export function markdownToBlocks(markdown: string): EmailBlock[] {
type: 'spacer',
height: parseInt(spacerMatch[1]),
});
-
+
remaining = remaining.substring(spacerMatch[0].length);
continue;
}
-
+
// Check for horizontal rule
if (remaining.startsWith('---')) {
blocks.push({
id,
type: 'divider',
});
-
+
remaining = remaining.substring(3);
continue;
}
-
+
// If nothing matches, skip this character to avoid infinite loop
remaining = remaining.substring(1);
}
-
+
return blocks;
}