✅ New cleaner syntax implemented: - [card:type] instead of [card type='type'] - [button:style](url)Text[/button] instead of [button url='...' style='...'] - Standard markdown images:  ✅ Variable protection from markdown parsing: - Variables with underscores (e.g., {order_items_table}) now protected - HTML comment placeholders prevent italic/bold parsing - All variables render correctly in preview ✅ Button rendering fixes: - Buttons work in Visual mode inside cards - Buttons work in Preview mode - Button clicks prevented in visual editor - Proper styling for solid and outline buttons ✅ Backward compatibility: - Old syntax still supported - No breaking changes ✅ Bug fixes: - Fixed order_item_table → order_items_table naming - Fixed button regex to match across newlines - Added button/image parsing to parseMarkdownBasics - Prevented button clicks on .button and .button-outline classes 📚 Documentation: - NEW_MARKDOWN_SYNTAX.md - Complete user guide - MARKDOWN_SYNTAX_AND_VARIABLES.md - Technical analysis
74 lines
2.1 KiB
TypeScript
74 lines
2.1 KiB
TypeScript
import { EmailBlock, CardType, ButtonStyle } from './types';
|
|
|
|
/**
|
|
* Convert markdown to blocks - respects [card]...[/card] boundaries
|
|
*/
|
|
export function markdownToBlocks(markdown: string): EmailBlock[] {
|
|
const blocks: EmailBlock[] = [];
|
|
let blockId = 0;
|
|
let pos = 0;
|
|
|
|
while (pos < markdown.length) {
|
|
// Skip whitespace
|
|
while (pos < markdown.length && /\s/.test(markdown[pos])) pos++;
|
|
if (pos >= markdown.length) break;
|
|
|
|
const id = `block-${Date.now()}-${blockId++}`;
|
|
|
|
// Check for [card]
|
|
if (markdown.substr(pos, 5) === '[card') {
|
|
const cardStart = pos;
|
|
const cardOpenEnd = markdown.indexOf(']', pos);
|
|
const cardClose = markdown.indexOf('[/card]', pos);
|
|
|
|
if (cardOpenEnd !== -1 && cardClose !== -1) {
|
|
const attributes = markdown.substring(pos + 5, cardOpenEnd);
|
|
const content = markdown.substring(cardOpenEnd + 1, cardClose).trim();
|
|
|
|
// Parse type
|
|
const typeMatch = attributes.match(/type\s*=\s*["']([^"']+)["']/);
|
|
const cardType = (typeMatch?.[1] || 'default') as CardType;
|
|
|
|
blocks.push({
|
|
id,
|
|
type: 'card',
|
|
cardType,
|
|
content,
|
|
});
|
|
|
|
pos = cardClose + 7; // Skip [/card]
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Check for [button]
|
|
if (markdown.substr(pos, 7) === '[button') {
|
|
const buttonEnd = markdown.indexOf('[/button]', pos);
|
|
if (buttonEnd !== -1) {
|
|
const fullButton = markdown.substring(pos, buttonEnd + 9);
|
|
const match = fullButton.match(/\[button\s+url=["']([^"']+)["'](?:\s+style=["'](solid|outline)["'])?\]([^\[]+)\[\/button\]/);
|
|
|
|
if (match) {
|
|
blocks.push({
|
|
id,
|
|
type: 'button',
|
|
text: match[3].trim(),
|
|
link: match[1],
|
|
style: (match[2] || 'solid') as ButtonStyle,
|
|
align: 'center',
|
|
widthMode: 'fit',
|
|
});
|
|
|
|
pos = buttonEnd + 9;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Skip unknown content
|
|
pos++;
|
|
}
|
|
|
|
return blocks;
|
|
}
|