Add mdx support for Events content
This commit is contained in:
@@ -1,6 +1,23 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import type { FormEvent, ChangeEvent } from 'react';
|
||||
import { createBrowserSupabase } from '../../lib/supabase';
|
||||
import {
|
||||
MDXEditor,
|
||||
headingsPlugin,
|
||||
listsPlugin,
|
||||
linkPlugin,
|
||||
imagePlugin,
|
||||
quotePlugin,
|
||||
thematicBreakPlugin,
|
||||
markdownShortcutPlugin,
|
||||
toolbarPlugin,
|
||||
UndoRedo,
|
||||
BoldItalicUnderlineToggles,
|
||||
ListsToggle,
|
||||
BlockTypeSelect,
|
||||
InsertImage,
|
||||
} from '@mdxeditor/editor';
|
||||
import '@mdxeditor/editor/style.css';
|
||||
import type { Dict } from '../../lib/i18n';
|
||||
import type { EventRow } from '../../lib/db';
|
||||
|
||||
@@ -9,6 +26,11 @@ interface Props {
|
||||
event?: EventRow | null;
|
||||
}
|
||||
|
||||
interface ContentEditorProps {
|
||||
content: string;
|
||||
onChange: (content: string) => void;
|
||||
}
|
||||
|
||||
function pad(n: number): string {
|
||||
return String(n).padStart(2, '0');
|
||||
}
|
||||
@@ -30,6 +52,55 @@ function slugify(value: string): string {
|
||||
.slice(0, 80);
|
||||
}
|
||||
|
||||
async function imageUploadHandler(file: File): Promise<string> {
|
||||
try {
|
||||
const ext = file.name.split('.').pop()?.toLowerCase() || 'jpg';
|
||||
const path = `content-images/${crypto.randomUUID()}.${ext}`;
|
||||
const supabase = createBrowserSupabase();
|
||||
const { data, error: uploadError } = await supabase.storage
|
||||
.from('event-content-images')
|
||||
.upload(path, file, { upsert: false, contentType: file.type });
|
||||
|
||||
if (uploadError) throw uploadError;
|
||||
if (!data) throw new Error('No upload path');
|
||||
|
||||
const { data: publicUrl } = supabase.storage.from('event-content-images').getPublicUrl(data.path);
|
||||
return publicUrl.publicUrl;
|
||||
} catch {
|
||||
throw new Error('Image upload failed');
|
||||
}
|
||||
}
|
||||
|
||||
function ContentEditor({ content, onChange }: ContentEditorProps) {
|
||||
return (
|
||||
<MDXEditor
|
||||
markdown={content}
|
||||
onChange={onChange}
|
||||
plugins={[
|
||||
headingsPlugin(),
|
||||
listsPlugin(),
|
||||
linkPlugin(),
|
||||
imagePlugin({ imageUploadHandler: imageUploadHandler }),
|
||||
quotePlugin(),
|
||||
thematicBreakPlugin(),
|
||||
markdownShortcutPlugin(),
|
||||
toolbarPlugin({
|
||||
toolbarContents: () => (
|
||||
<>
|
||||
<UndoRedo />
|
||||
<BoldItalicUnderlineToggles />
|
||||
<BlockTypeSelect />
|
||||
<ListsToggle />
|
||||
|
||||
<InsertImage />
|
||||
</>
|
||||
),
|
||||
}),
|
||||
]}
|
||||
contentEditableClassName="prose prose-sm max-w-none textarea"
|
||||
/>
|
||||
);
|
||||
}
|
||||
export default function EventEditor({ dict: t, event }: Props) {
|
||||
const [titleUa, setTitleUa] = useState(event?.title?.ua ?? '');
|
||||
const [titleEn, setTitleEn] = useState(event?.title?.en ?? '');
|
||||
@@ -94,6 +165,10 @@ export default function EventEditor({ dict: t, event }: Props) {
|
||||
setError(t.common.error);
|
||||
return;
|
||||
}
|
||||
if (descriptionUa.trim().length > 20000 || descriptionEn.trim().length > 20000) {
|
||||
setError(t.common.error);
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
title: { ua: titleUa.trim(), en: titleEn.trim() },
|
||||
@@ -158,25 +233,13 @@ export default function EventEditor({ dict: t, event }: Props) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid-2">
|
||||
<div className="form-field">
|
||||
<label htmlFor="desc-ua">{t.admin.eventDescUa}</label>
|
||||
<textarea
|
||||
className="textarea"
|
||||
id="desc-ua"
|
||||
value={descriptionUa}
|
||||
onChange={(e) => setDescriptionUa(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-field">
|
||||
<label htmlFor="desc-en">{t.admin.eventDescEn}</label>
|
||||
<textarea
|
||||
className="textarea"
|
||||
id="desc-en"
|
||||
value={descriptionEn}
|
||||
onChange={(e) => setDescriptionEn(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-field">
|
||||
<label htmlFor="desc-ua">{t.admin.eventDescUa}</label>
|
||||
<ContentEditor content={descriptionUa} onChange={setDescriptionUa} />
|
||||
</div>
|
||||
<div className="form-field">
|
||||
<label htmlFor="desc-en">{t.admin.eventDescEn}</label>
|
||||
<ContentEditor content={descriptionEn} onChange={setDescriptionEn} />
|
||||
</div>
|
||||
|
||||
<div className="grid-2">
|
||||
|
||||
Reference in New Issue
Block a user