diff --git a/src/components/admin/MarkdownEditor.tsx b/src/components/admin/MarkdownEditor.tsx index ec963564..750f5164 100644 --- a/src/components/admin/MarkdownEditor.tsx +++ b/src/components/admin/MarkdownEditor.tsx @@ -27,7 +27,10 @@ import { type MDXEditorMethods } from '@mdxeditor/editor'; import '@mdxeditor/editor/style.css'; +import '@/styles/mdx-editor-theme.css'; import { useTheme } from '@/components/theme/ThemeProvider'; +import { supabase } from '@/integrations/supabase/client'; +import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils'; import { useAutoSave } from '@/hooks/useAutoSave'; import { CheckCircle2, Loader2, AlertCircle } from 'lucide-react'; import { cn } from '@/lib/utils'; @@ -115,7 +118,7 @@ export function MarkdownEditor({
@@ -124,7 +127,7 @@ export function MarkdownEditor({ markdown={value} onChange={onChange} placeholder={placeholder} - contentEditableClassName="prose dark:prose-invert max-w-none p-4 min-h-[500px]" + contentEditableClassName="prose dark:prose-invert max-w-none p-4 min-h-[500px] mdx-content-area" plugins={[ headingsPlugin(), listsPlugin(), @@ -134,8 +137,26 @@ export function MarkdownEditor({ linkPlugin(), linkDialogPlugin(), imagePlugin({ - imageUploadHandler: async () => { - return Promise.resolve('https://placeholder.com/image.jpg'); + imageUploadHandler: async (file: File) => { + try { + const formData = new FormData(); + formData.append('file', file); + + const { data, error } = await supabase.functions.invoke('upload-image', { + body: formData + }); + + if (error) throw error; + + // Return CloudFlare imagedelivery.net URL + const imageUrl = getCloudflareImageUrl(data.id, 'public'); + if (!imageUrl) throw new Error('Failed to generate image URL'); + + return imageUrl; + } catch (error) { + console.error('Image upload failed:', error); + throw new Error(error instanceof Error ? error.message : 'Failed to upload image'); + } } }), tablePlugin(), diff --git a/src/styles/mdx-editor-theme.css b/src/styles/mdx-editor-theme.css new file mode 100644 index 00000000..025c60d3 --- /dev/null +++ b/src/styles/mdx-editor-theme.css @@ -0,0 +1,167 @@ +/** + * MDXEditor Custom Theme + * Integrates with ThrillWiki's design system using semantic HSL tokens + */ + +.mdxeditor { + /* Base colors - light mode */ + --baseBase: hsl(var(--background)); + --baseBgSubtle: hsl(var(--muted)); + --baseBg: hsl(var(--muted) / 0.5); + --baseBgHover: hsl(var(--muted) / 0.8); + --baseBgActive: hsl(var(--muted)); + --baseLine: hsl(var(--border) / 0.3); + --baseBorder: hsl(var(--border)); + --baseBorderHover: hsl(var(--border) / 0.8); + --baseSolid: hsl(var(--muted-foreground)); + --baseSolidHover: hsl(var(--foreground) / 0.8); + --baseText: hsl(var(--foreground)); + --baseTextContrast: hsl(var(--foreground)); + + /* Accent colors - primary purple/blue */ + --accentBase: hsl(var(--primary) / 0.1); + --accentBgSubtle: hsl(var(--primary) / 0.15); + --accentBg: hsl(var(--primary) / 0.2); + --accentBgHover: hsl(var(--primary) / 0.3); + --accentBgActive: hsl(var(--primary) / 0.4); + --accentLine: hsl(var(--primary) / 0.5); + --accentBorder: hsl(var(--primary) / 0.6); + --accentBorderHover: hsl(var(--primary) / 0.8); + --accentSolid: hsl(var(--primary)); + --accentSolidHover: hsl(var(--primary) / 0.9); + --accentText: hsl(var(--primary)); + --accentTextContrast: hsl(var(--primary-foreground)); + + /* Font configuration */ + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + + /* Page background */ + --basePageBg: hsl(var(--background)); + background: var(--basePageBg); + color: var(--baseText); +} + +/* Dark mode theme */ +.mdxeditor.dark-theme { + /* Base colors - dark mode */ + --baseBase: hsl(var(--background)); + --baseBgSubtle: hsl(var(--muted)); + --baseBg: hsl(var(--muted) / 0.3); + --baseBgHover: hsl(var(--muted) / 0.5); + --baseBgActive: hsl(var(--muted) / 0.7); + --baseLine: hsl(var(--border) / 0.3); + --baseBorder: hsl(var(--border)); + --baseBorderHover: hsl(var(--border) / 0.8); + --baseSolid: hsl(var(--muted-foreground)); + --baseSolidHover: hsl(var(--foreground) / 0.8); + --baseText: hsl(var(--foreground)); + --baseTextContrast: hsl(var(--foreground)); + + /* Accent colors remain consistent but adapt to dark bg */ + --accentBase: hsl(var(--primary) / 0.15); + --accentBgSubtle: hsl(var(--primary) / 0.2); + --accentBg: hsl(var(--primary) / 0.25); + --accentBgHover: hsl(var(--primary) / 0.35); + --accentBgActive: hsl(var(--primary) / 0.45); + --accentLine: hsl(var(--primary) / 0.5); + --accentBorder: hsl(var(--primary) / 0.7); + --accentBorderHover: hsl(var(--primary) / 0.9); + --accentSolid: hsl(var(--primary)); + --accentSolidHover: hsl(var(--primary) / 0.85); + --accentText: hsl(var(--primary)); + --accentTextContrast: hsl(var(--primary-foreground)); + + /* Page background */ + --basePageBg: hsl(var(--background)); +} + +/* Popup container z-index override */ +.mdxeditor-popup-container { + z-index: 9999; +} + +/* Content area styling */ +.mdx-content-area { + font-family: inherit; + line-height: 1.75; +} + +.mdx-content-area h1, +.mdx-content-area h2, +.mdx-content-area h3, +.mdx-content-area h4, +.mdx-content-area h5, +.mdx-content-area h6 { + font-weight: 600; + margin-top: 1.5em; + margin-bottom: 0.5em; + color: hsl(var(--foreground)); +} + +.mdx-content-area a { + color: hsl(var(--primary)); + text-decoration: underline; + text-decoration-color: hsl(var(--primary) / 0.3); + transition: text-decoration-color 0.2s; +} + +.mdx-content-area a:hover { + text-decoration-color: hsl(var(--primary)); +} + +.mdx-content-area code { + background: hsl(var(--muted)); + padding: 0.125rem 0.25rem; + border-radius: 0.25rem; + font-size: 0.875em; + font-family: var(--font-mono); +} + +.mdx-content-area pre { + background: hsl(var(--muted)); + border: 1px solid hsl(var(--border)); + border-radius: 0.5rem; + padding: 1rem; + overflow-x: auto; +} + +.mdx-content-area pre code { + background: transparent; + padding: 0; +} + +.mdx-content-area blockquote { + border-left: 4px solid hsl(var(--primary)); + padding-left: 1rem; + color: hsl(var(--muted-foreground)); + font-style: italic; +} + +.mdx-content-area table { + border-collapse: collapse; + width: 100%; +} + +.mdx-content-area table th, +.mdx-content-area table td { + border: 1px solid hsl(var(--border)); + padding: 0.5rem; +} + +.mdx-content-area table th { + background: hsl(var(--muted)); + font-weight: 600; +} + +/* Mobile responsiveness */ +@media (max-width: 768px) { + .mdxeditor-toolbar { + flex-wrap: wrap; + gap: 0.25rem; + } + + .mdxeditor-select-content { + max-height: 50vh; + } +}