first
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
import { useState } from 'react';
|
||||
import md from 'markdown-it';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Code, Eye } from 'lucide-react';
|
||||
|
||||
type MarkdownEditorProps = {
|
||||
content: string;
|
||||
onChange: (content: string) => void;
|
||||
placeholder?: string;
|
||||
greeting?: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
};
|
||||
|
||||
export function MarkdownEditor({
|
||||
content,
|
||||
onChange,
|
||||
placeholder = 'Enter markdown...',
|
||||
greeting,
|
||||
firstName,
|
||||
lastName,
|
||||
}: MarkdownEditorProps) {
|
||||
const [showPreview, setShowPreview] = useState(false);
|
||||
|
||||
const markdown = md({
|
||||
html: true,
|
||||
breaks: true,
|
||||
linkify: true,
|
||||
});
|
||||
|
||||
const processGreeting = (text: string) => {
|
||||
return text
|
||||
.replace(/\{\{firstName\}\}/g, firstName || '')
|
||||
.replace(/\{\{fullName\}\}/g, `${firstName || ''} ${lastName || ''}`.trim());
|
||||
};
|
||||
|
||||
const processedGreeting = greeting ? processGreeting(greeting) : '';
|
||||
const greetingHtml = processedGreeting ? `<p>${processedGreeting}</p>` : '';
|
||||
const preview = markdown.render(content);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col rounded-lg border border-border bg-background overflow-hidden">
|
||||
{/* Toolbar */}
|
||||
<div className="flex items-center gap-2 border-b border-border bg-muted px-4 py-2">
|
||||
<Button size="sm" variant={!showPreview ? 'default' : 'outline'} onClick={() => setShowPreview(false)}>
|
||||
<Code className="h-4 w-4 mr-2" />
|
||||
Markdown
|
||||
</Button>
|
||||
<Button size="sm" variant={showPreview ? 'default' : 'outline'} onClick={() => setShowPreview(true)}>
|
||||
<Eye className="h-4 w-4 mr-2" />
|
||||
Preview
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Content Area */}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
{!showPreview ? (
|
||||
<textarea
|
||||
rows={15}
|
||||
name="content"
|
||||
value={content}
|
||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => onChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
className="w-full h-full resize-none p-4 font-mono text-sm focus:outline-none bg-background text-foreground placeholder:text-muted-foreground overflow-auto"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full overflow-auto bg-slate-50 dark:bg-slate-900 p-4">
|
||||
{/* Email wrapper simulation */}
|
||||
<div className="mx-auto max-w-2xl rounded-lg bg-white dark:bg-slate-800 p-8 shadow-lg">
|
||||
{/* Logo */}
|
||||
<div className="mb-8 text-center">
|
||||
<img src="https://app.pertento.ai/logo.png" alt="Pertento" className="h-16 mx-auto" />
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="space-y-4">
|
||||
{processedGreeting && <p className="text-base leading-relaxed">{processedGreeting}</p>}
|
||||
<style>{`
|
||||
.markdown-preview h1 { font-size: 2rem; font-weight: bold; margin: 1.5rem 0 1rem 0; }
|
||||
.markdown-preview h2 { font-size: 1.5rem; font-weight: bold; margin: 1.25rem 0 0.75rem 0; }
|
||||
.markdown-preview h3 { font-size: 1.25rem; font-weight: bold; margin: 1rem 0 0.5rem 0; }
|
||||
.markdown-preview p { margin: 0.75rem 0; line-height: 1.6; }
|
||||
.markdown-preview ul, .markdown-preview ol { margin: 1rem 0; padding-left: 2rem; }
|
||||
.markdown-preview li { margin: 0.5rem 0; line-height: 1.6; }
|
||||
.markdown-preview code { background-color: rgb(229, 229, 229); color: rgb(31, 41, 55); padding: 0.25rem 0.5rem; border-radius: 0.25rem; font-size: 0.875rem; }
|
||||
.markdown-preview pre { background-color: rgb(17, 24, 39); color: rgb(243, 244, 246); padding: 1rem; border-radius: 0.5rem; overflow-x: auto; margin: 1rem 0; }
|
||||
.markdown-preview pre code { background-color: transparent; color: inherit; padding: 0; }
|
||||
.markdown-preview a { color: rgb(37, 99, 235); text-decoration: underline; }
|
||||
.markdown-preview blockquote { border-left: 4px solid rgb(209, 213, 219); padding-left: 1rem; font-style: italic; margin: 1rem 0; }
|
||||
.dark .markdown-preview code { background-color: rgb(55, 65, 81); color: rgb(243, 244, 246); }
|
||||
.dark .markdown-preview a { color: rgb(96, 165, 250); }
|
||||
.dark .markdown-preview blockquote { border-left-color: rgb(75, 85, 99); }
|
||||
`}</style>
|
||||
<div className="markdown-preview">
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
|
||||
{content}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user