2025-12-23 02:20:57 +01:00

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)
}
}