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;
+ }
+}