✅ 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
92 lines
2.6 KiB
TypeScript
92 lines
2.6 KiB
TypeScript
import React, { useEffect, useRef } from 'react';
|
|
import { EditorView, basicSetup } from 'codemirror';
|
|
import { markdown } from '@codemirror/lang-markdown';
|
|
import { oneDark } from '@codemirror/theme-one-dark';
|
|
import { MarkdownToolbar } from './markdown-toolbar';
|
|
|
|
interface CodeEditorProps {
|
|
value: string;
|
|
onChange: (value: string) => void;
|
|
placeholder?: string;
|
|
supportMarkdown?: boolean; // Keep for backward compatibility but always use markdown
|
|
}
|
|
|
|
export function CodeEditor({ value, onChange, placeholder }: CodeEditorProps) {
|
|
const editorRef = useRef<HTMLDivElement>(null);
|
|
const viewRef = useRef<EditorView | null>(null);
|
|
|
|
// Handle markdown insertions from toolbar
|
|
const handleInsert = (before: string, after: string = '') => {
|
|
if (!viewRef.current) return;
|
|
|
|
const view = viewRef.current;
|
|
const selection = view.state.selection.main;
|
|
const selectedText = view.state.doc.sliceString(selection.from, selection.to);
|
|
|
|
// Insert the markdown syntax
|
|
const newText = before + selectedText + after;
|
|
view.dispatch({
|
|
changes: { from: selection.from, to: selection.to, insert: newText },
|
|
selection: { anchor: selection.from + before.length + selectedText.length }
|
|
});
|
|
|
|
// Focus back to editor
|
|
view.focus();
|
|
};
|
|
|
|
// Initialize editor once
|
|
useEffect(() => {
|
|
if (!editorRef.current) return;
|
|
|
|
const view = new EditorView({
|
|
doc: value,
|
|
extensions: [
|
|
basicSetup,
|
|
markdown(),
|
|
oneDark,
|
|
EditorView.updateListener.of((update) => {
|
|
if (update.docChanged) {
|
|
const content = update.state.doc.toString();
|
|
onChange(content);
|
|
}
|
|
}),
|
|
],
|
|
parent: editorRef.current,
|
|
});
|
|
|
|
viewRef.current = view;
|
|
|
|
return () => {
|
|
view.destroy();
|
|
};
|
|
}, []); // Only run once on mount
|
|
|
|
// Update editor when value prop changes from external source
|
|
useEffect(() => {
|
|
if (viewRef.current && value !== viewRef.current.state.doc.toString()) {
|
|
viewRef.current.dispatch({
|
|
changes: {
|
|
from: 0,
|
|
to: viewRef.current.state.doc.length,
|
|
insert: value,
|
|
},
|
|
});
|
|
}
|
|
}, [value]);
|
|
|
|
return (
|
|
<div className="space-y-2">
|
|
<div className="border rounded-md overflow-hidden">
|
|
<MarkdownToolbar onInsert={handleInsert} />
|
|
<div
|
|
ref={editorRef}
|
|
className="min-h-[400px] font-mono text-sm"
|
|
/>
|
|
</div>
|
|
<p className="text-xs text-muted-foreground">
|
|
💡 Use the toolbar above or type markdown directly: **bold**, ## headings, [card]...[/card], [button]...[/button]
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|