feat: Migrate markdown editor to MDXEditor

This commit is contained in:
gpt-engineer-app[bot]
2025-10-17 18:07:37 +00:00
parent 45ae66384c
commit 53f8da2703
5 changed files with 1929 additions and 1096 deletions

2805
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,7 @@
"@dnd-kit/utilities": "^3.2.2",
"@hookform/resolvers": "^3.10.0",
"@marsidev/react-turnstile": "^1.3.1",
"@mdxeditor/editor": "^3.47.0",
"@novu/react": "^3.10.1",
"@radix-ui/react-accordion": "^1.2.11",
"@radix-ui/react-alert-dialog": "^1.1.14",
@@ -47,8 +48,6 @@
"@supabase/supabase-js": "^2.57.4",
"@tanstack/react-query": "^5.83.0",
"@tanstack/react-query-devtools": "^5.90.2",
"@uiw/react-markdown-preview": "^5.1.5",
"@uiw/react-md-editor": "^4.0.8",
"@uppy/core": "^5.0.2",
"@uppy/dashboard": "^5.0.2",
"@uppy/image-editor": "^4.0.1",

View File

@@ -1,10 +1,36 @@
import { useEffect, useState } from 'react';
import MDEditor from '@uiw/react-md-editor';
import { useEffect, useState, useRef } from 'react';
import {
MDXEditor,
headingsPlugin,
listsPlugin,
quotePlugin,
thematicBreakPlugin,
markdownShortcutPlugin,
linkPlugin,
linkDialogPlugin,
imagePlugin,
tablePlugin,
codeBlockPlugin,
codeMirrorPlugin,
diffSourcePlugin,
toolbarPlugin,
UndoRedo,
BoldItalicUnderlineToggles,
CodeToggle,
ListsToggle,
BlockTypeSelect,
CreateLink,
InsertImage,
InsertTable,
InsertThematicBreak,
DiffSourceToggleWrapper,
type MDXEditorMethods
} from '@mdxeditor/editor';
import '@mdxeditor/editor/style.css';
import { useTheme } from '@/components/theme/ThemeProvider';
import { useAutoSave } from '@/hooks/useAutoSave';
import { CheckCircle2, Loader2, AlertCircle } from 'lucide-react';
import { cn } from '@/lib/utils';
import rehypeSanitize from 'rehype-sanitize';
interface MarkdownEditorProps {
value: string;
@@ -26,6 +52,7 @@ export function MarkdownEditor({
const { theme } = useTheme();
const [mounted, setMounted] = useState(false);
const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>('light');
const editorRef = useRef<MDXEditorMethods>(null);
// Resolve "system" theme to actual theme based on OS preference
useEffect(() => {
@@ -85,22 +112,68 @@ export function MarkdownEditor({
return (
<div className="space-y-2">
<div data-color-mode={resolvedTheme}>
<div className="wmde-markdown-var" />
<MDEditor
value={value}
onChange={(val) => onChange(val || '')}
height={height}
preview="live"
hideToolbar={false}
enableScroll={true}
textareaProps={{
placeholder
}}
previewOptions={{
rehypePlugins: [[rehypeSanitize]],
className: 'prose dark:prose-invert max-w-none p-4'
}}
<div
className={cn(
'border border-input rounded-lg overflow-hidden',
resolvedTheme === 'dark' && 'dark'
)}
style={{ minHeight: height }}
>
<MDXEditor
ref={editorRef}
markdown={value}
onChange={onChange}
placeholder={placeholder}
contentEditableClassName="prose dark:prose-invert max-w-none p-4 min-h-[500px]"
plugins={[
headingsPlugin(),
listsPlugin(),
quotePlugin(),
thematicBreakPlugin(),
markdownShortcutPlugin(),
linkPlugin(),
linkDialogPlugin(),
imagePlugin({
imageUploadHandler: async () => {
return Promise.resolve('https://placeholder.com/image.jpg');
}
}),
tablePlugin(),
codeBlockPlugin({ defaultCodeBlockLanguage: 'js' }),
codeMirrorPlugin({
codeBlockLanguages: {
js: 'JavaScript',
ts: 'TypeScript',
tsx: 'TypeScript (React)',
jsx: 'JavaScript (React)',
css: 'CSS',
html: 'HTML',
python: 'Python',
bash: 'Bash',
json: 'JSON',
sql: 'SQL'
}
}),
diffSourcePlugin({ viewMode: 'rich-text', diffMarkdown: '' }),
toolbarPlugin({
toolbarContents: () => (
<>
<UndoRedo />
<BoldItalicUnderlineToggles />
<CodeToggle />
<BlockTypeSelect />
<ListsToggle />
<CreateLink />
<InsertImage />
<InsertTable />
<InsertThematicBreak />
<DiffSourceToggleWrapper>
<span className="text-sm">Source</span>
</DiffSourceToggleWrapper>
</>
)
})
]}
/>
</div>

View File

@@ -19,9 +19,6 @@ import { extractCloudflareImageId } from '@/lib/cloudflareImageUtils';
import { Edit, Trash2, Eye, Plus } from 'lucide-react';
import { toast } from 'sonner';
import { formatDistanceToNow } from 'date-fns';
import '@uiw/react-md-editor/markdown-editor.css';
import '@uiw/react-markdown-preview/markdown.css';
import '@/styles/markdown-editor.css';
interface BlogPost {
id: string;

View File

@@ -1,103 +0,0 @@
/* MDEditor theme integration with shadcn/ui using data-color-mode */
/* Light mode - explicit HSL values from theme */
[data-color-mode="light"] {
--color-fg-default: hsl(240, 10%, 3.9%);
--color-canvas-default: hsl(0, 0%, 100%);
--color-border-default: hsl(240, 5.9%, 90%);
--color-neutral-muted: hsl(240, 4.8%, 95.9%);
}
/* Dark mode - explicit HSL values from theme */
[data-color-mode="dark"] {
--color-fg-default: hsl(0, 0%, 98%);
--color-canvas-default: hsl(240, 10%, 3.9%);
--color-border-default: hsl(240, 6%, 15%);
--color-neutral-muted: hsl(240, 5%, 20%);
}
/* Container styling */
.w-md-editor {
@apply rounded-lg border;
border-color: hsl(var(--border)) !important;
background-color: hsl(var(--background)) !important;
font-family: inherit;
}
/* Toolbar styling */
.w-md-editor-toolbar {
background-color: hsl(var(--muted)) !important;
border-color: hsl(var(--border)) !important;
padding: 8px;
}
.w-md-editor-toolbar button {
color: hsl(var(--foreground)) !important;
border-radius: 4px;
}
.w-md-editor-toolbar button:hover {
@apply bg-accent;
}
/* Editor area - let library handle text colors via CSS variables */
.w-md-editor-content {
background-color: hsl(var(--background)) !important;
}
.w-md-editor-text-pre,
.w-md-editor-text-input {
background-color: hsl(var(--background)) !important;
color: hsl(var(--foreground)) !important;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important;
}
/* Preview area */
.w-md-editor-preview {
background-color: hsl(var(--background)) !important;
}
.wmde-markdown {
background-color: hsl(var(--background)) !important;
}
/* Scrollbar styling */
.w-md-editor-text::-webkit-scrollbar,
.w-md-editor-preview::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.w-md-editor-text::-webkit-scrollbar-track,
.w-md-editor-preview::-webkit-scrollbar-track {
@apply bg-muted/30;
}
.w-md-editor-text::-webkit-scrollbar-thumb,
.w-md-editor-preview::-webkit-scrollbar-thumb {
@apply bg-muted-foreground/30 rounded;
}
.w-md-editor-text::-webkit-scrollbar-thumb:hover,
.w-md-editor-preview::-webkit-scrollbar-thumb:hover {
@apply bg-muted-foreground/50;
}
/* Typography in preview */
.w-md-editor-preview .wmde-markdown h1,
.w-md-editor-preview .wmde-markdown h2,
.w-md-editor-preview .wmde-markdown h3 {
@apply font-bold tracking-tight;
}
.w-md-editor-preview .wmde-markdown a {
@apply text-primary hover:underline;
}
.w-md-editor-preview .wmde-markdown code {
@apply bg-muted px-1.5 py-0.5 rounded text-sm;
}
.w-md-editor-preview .wmde-markdown pre {
@apply bg-muted border border-border rounded;
}