mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 06:11:11 -05:00
feat: Migrate markdown editor to MDXEditor
This commit is contained in:
2805
package-lock.json
generated
2805
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,7 @@
|
|||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@hookform/resolvers": "^3.10.0",
|
"@hookform/resolvers": "^3.10.0",
|
||||||
"@marsidev/react-turnstile": "^1.3.1",
|
"@marsidev/react-turnstile": "^1.3.1",
|
||||||
|
"@mdxeditor/editor": "^3.47.0",
|
||||||
"@novu/react": "^3.10.1",
|
"@novu/react": "^3.10.1",
|
||||||
"@radix-ui/react-accordion": "^1.2.11",
|
"@radix-ui/react-accordion": "^1.2.11",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.14",
|
"@radix-ui/react-alert-dialog": "^1.1.14",
|
||||||
@@ -47,8 +48,6 @@
|
|||||||
"@supabase/supabase-js": "^2.57.4",
|
"@supabase/supabase-js": "^2.57.4",
|
||||||
"@tanstack/react-query": "^5.83.0",
|
"@tanstack/react-query": "^5.83.0",
|
||||||
"@tanstack/react-query-devtools": "^5.90.2",
|
"@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/core": "^5.0.2",
|
||||||
"@uppy/dashboard": "^5.0.2",
|
"@uppy/dashboard": "^5.0.2",
|
||||||
"@uppy/image-editor": "^4.0.1",
|
"@uppy/image-editor": "^4.0.1",
|
||||||
|
|||||||
@@ -1,10 +1,36 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState, useRef } from 'react';
|
||||||
import MDEditor from '@uiw/react-md-editor';
|
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 { useTheme } from '@/components/theme/ThemeProvider';
|
||||||
import { useAutoSave } from '@/hooks/useAutoSave';
|
import { useAutoSave } from '@/hooks/useAutoSave';
|
||||||
import { CheckCircle2, Loader2, AlertCircle } from 'lucide-react';
|
import { CheckCircle2, Loader2, AlertCircle } from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import rehypeSanitize from 'rehype-sanitize';
|
|
||||||
|
|
||||||
interface MarkdownEditorProps {
|
interface MarkdownEditorProps {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -26,6 +52,7 @@ export function MarkdownEditor({
|
|||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>('light');
|
const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>('light');
|
||||||
|
const editorRef = useRef<MDXEditorMethods>(null);
|
||||||
|
|
||||||
// Resolve "system" theme to actual theme based on OS preference
|
// Resolve "system" theme to actual theme based on OS preference
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -85,22 +112,68 @@ export function MarkdownEditor({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div data-color-mode={resolvedTheme}>
|
<div
|
||||||
<div className="wmde-markdown-var" />
|
className={cn(
|
||||||
<MDEditor
|
'border border-input rounded-lg overflow-hidden',
|
||||||
value={value}
|
resolvedTheme === 'dark' && 'dark'
|
||||||
onChange={(val) => onChange(val || '')}
|
)}
|
||||||
height={height}
|
style={{ minHeight: height }}
|
||||||
preview="live"
|
>
|
||||||
hideToolbar={false}
|
<MDXEditor
|
||||||
enableScroll={true}
|
ref={editorRef}
|
||||||
textareaProps={{
|
markdown={value}
|
||||||
placeholder
|
onChange={onChange}
|
||||||
}}
|
placeholder={placeholder}
|
||||||
previewOptions={{
|
contentEditableClassName="prose dark:prose-invert max-w-none p-4 min-h-[500px]"
|
||||||
rehypePlugins: [[rehypeSanitize]],
|
plugins={[
|
||||||
className: 'prose dark:prose-invert max-w-none p-4'
|
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>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,6 @@ import { extractCloudflareImageId } from '@/lib/cloudflareImageUtils';
|
|||||||
import { Edit, Trash2, Eye, Plus } from 'lucide-react';
|
import { Edit, Trash2, Eye, Plus } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { formatDistanceToNow } from 'date-fns';
|
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 {
|
interface BlogPost {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user