116 lines
3.2 KiB
TypeScript
116 lines
3.2 KiB
TypeScript
import { useState } from 'react'
|
|
import type { MediaRef } from '@/types/nostr'
|
|
import { uploadNip95Media } from '@/lib/nip95'
|
|
|
|
interface MarkdownEditorProps {
|
|
value: string
|
|
onChange: (value: string) => void
|
|
onMediaAdd?: (media: MediaRef) => void
|
|
onBannerChange?: (url: string) => void
|
|
}
|
|
|
|
export function MarkdownEditor(props: MarkdownEditorProps) {
|
|
return <MarkdownEditorInner {...props} />
|
|
}
|
|
|
|
function MarkdownEditorInner({ value, onChange, onMediaAdd, onBannerChange }: MarkdownEditorProps) {
|
|
const [uploading, setUploading] = useState(false)
|
|
const [error, setError] = useState<string | null>(null)
|
|
const [preview, setPreview] = useState(false)
|
|
|
|
return (
|
|
<div className="space-y-3">
|
|
<MarkdownToolbar
|
|
preview={preview}
|
|
onTogglePreview={() => setPreview((p) => !p)}
|
|
onFileSelected={(file) => {
|
|
const handlers = {
|
|
setError,
|
|
setUploading,
|
|
...(onMediaAdd ? { onMediaAdd } : {}),
|
|
...(onBannerChange ? { onBannerChange } : {}),
|
|
}
|
|
void handleUpload(file, handlers)
|
|
}}
|
|
uploading={uploading}
|
|
error={error}
|
|
/>
|
|
{preview ? (
|
|
<MarkdownPreview value={value} />
|
|
) : (
|
|
<textarea
|
|
className="w-full border rounded p-3 h-64"
|
|
value={value}
|
|
onChange={(e) => onChange(e.target.value)}
|
|
/>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function MarkdownToolbar({
|
|
preview,
|
|
onTogglePreview,
|
|
onFileSelected,
|
|
uploading,
|
|
error,
|
|
}: {
|
|
preview: boolean
|
|
onTogglePreview: () => void
|
|
onFileSelected: (file: File) => void
|
|
uploading: boolean
|
|
error: string | null
|
|
}) {
|
|
return (
|
|
<div className="flex items-center gap-2">
|
|
<button type="button" className="px-3 py-1 text-sm rounded bg-gray-200" onClick={onTogglePreview}>
|
|
{preview ? 'Éditer' : 'Preview'}
|
|
</button>
|
|
<label className="px-3 py-1 text-sm rounded bg-blue-600 text-white cursor-pointer hover:bg-blue-700">
|
|
Upload media (NIP-95)
|
|
<input
|
|
type="file"
|
|
accept=".png,.jpg,.jpeg,.webp,.mp4,.webm,.mov,.qt"
|
|
className="hidden"
|
|
onChange={(e) => {
|
|
const file = e.target.files?.[0]
|
|
if (file) {
|
|
onFileSelected(file)
|
|
}
|
|
}}
|
|
/>
|
|
</label>
|
|
{uploading && <span className="text-sm text-gray-500">Upload en cours...</span>}
|
|
{error && <span className="text-sm text-red-600">{error}</span>}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function MarkdownPreview({ value }: { value: string }) {
|
|
return <div className="prose max-w-none border rounded p-3 bg-white whitespace-pre-wrap">{value}</div>
|
|
}
|
|
|
|
async function handleUpload(
|
|
file: File,
|
|
handlers: {
|
|
setError: (error: string | null) => void
|
|
setUploading: (uploading: boolean) => void
|
|
onMediaAdd?: (media: MediaRef) => void
|
|
onBannerChange?: (url: string) => void
|
|
}
|
|
) {
|
|
handlers.setError(null)
|
|
handlers.setUploading(true)
|
|
try {
|
|
const media = await uploadNip95Media(file)
|
|
handlers.onMediaAdd?.(media)
|
|
if (media.type === 'image') {
|
|
handlers.onBannerChange?.(media.url)
|
|
}
|
|
} catch (e) {
|
|
handlers.setError(e instanceof Error ? e.message : 'Upload failed')
|
|
} finally {
|
|
handlers.setUploading(false)
|
|
}
|
|
}
|