2026-01-14 13:47:03 +01:00

166 lines
5.2 KiB
TypeScript

import { useState } from 'react'
import { Button } from '../ui'
import type { MediaRef, Page } from '@/types/nostr'
import { t } from '@/lib/i18n'
import { createPagesHandlers, PagesManager } from './PagesManager'
import { createImageUploadHandler } from './imageUpload'
export interface MarkdownEditorTwoColumnsProps {
value: string
onChange: (value: string) => void
pages?: Page[]
onPagesChange?: (pages: Page[]) => void
onMediaAdd?: (media: MediaRef) => void
onBannerChange?: (url: string) => void
}
export function MarkdownEditorTwoColumns(props: MarkdownEditorTwoColumnsProps): React.ReactElement {
const [uploading, setUploading] = useState(false)
const [error, setError] = useState<string | null>(null)
const pages = props.pages ?? []
const pagesHandlers = createPagesHandlers({ pages, onPagesChange: props.onPagesChange })
const handleImageUpload = createImageUploadHandler({
setError,
setUploading,
onMediaAdd: props.onMediaAdd,
onBannerChange: props.onBannerChange,
onSetPageImageUrl: pagesHandlers.setPageContent,
})
return (
<div className="space-y-4">
<MarkdownToolbar
onFileSelected={(file) => {
void handleImageUpload({ file })
}}
uploading={uploading}
error={error}
{...(props.onPagesChange ? { onAddPage: pagesHandlers.addPage } : {})}
/>
<div className="grid grid-cols-2 gap-4">
<EditorColumn value={props.value} onChange={props.onChange} />
<PreviewColumn value={props.value} />
</div>
{props.onPagesChange ? (
<PagesManager
pages={pages}
onPageContentChange={pagesHandlers.setPageContent}
onPageTypeChange={pagesHandlers.setPageType}
onRemovePage={pagesHandlers.removePage}
onImageUpload={async (file, pageNumber) => {
await handleImageUpload({ file, pageNumber })
}}
/>
) : null}
</div>
)
}
function MarkdownToolbar(params: {
onFileSelected: (file: File) => void
uploading: boolean
error: string | null
onAddPage?: (type: 'markdown' | 'image') => void
}): React.ReactElement {
return (
<div className="flex items-center gap-2 flex-wrap">
<ToolbarUploadButton onFileSelected={params.onFileSelected} />
<ToolbarAddPageButtons onAddPage={params.onAddPage} />
<ToolbarStatus uploading={params.uploading} error={params.error} />
</div>
)
}
function EditorColumn(params: { value: string; onChange: (value: string) => void }): React.ReactElement {
return (
<div className="space-y-2">
<label className="block text-sm font-semibold text-gray-800">{t('markdown.editor')}</label>
<textarea
className="w-full border rounded p-3 h-96 font-mono text-sm"
value={params.value}
onChange={(e) => params.onChange(e.target.value)}
placeholder={t('markdown.placeholder')}
/>
</div>
)
}
function PreviewColumn(params: { value: string }): React.ReactElement {
return (
<div className="space-y-2">
<label className="block text-sm font-semibold text-gray-800">{t('markdown.preview')}</label>
<MarkdownPreview value={params.value} />
</div>
)
}
function MarkdownPreview(params: { value: string }): React.ReactElement {
return (
<div className="prose max-w-none border rounded p-3 bg-white h-96 overflow-y-auto whitespace-pre-wrap">
{params.value || <span className="text-gray-400">{t('markdown.preview.empty')}</span>}
</div>
)
}
function ToolbarUploadButton(params: { onFileSelected: (file: File) => void }): React.ReactElement {
return (
<label className="cursor-pointer">
<span className="inline-flex items-center justify-center px-3 py-1 text-sm rounded bg-blue-600 text-white hover:bg-blue-700 transition-colors">
{t('markdown.upload.media')}
</span>
<input
type="file"
accept=".png,.jpg,.jpeg,.webp"
className="hidden"
onChange={(e) => {
const file = e.target.files?.[0]
if (file) {
params.onFileSelected(file)
}
}}
/>
</label>
)
}
function ToolbarAddPageButtons(params: { onAddPage: ((type: 'markdown' | 'image') => void) | undefined }): React.ReactElement | null {
if (!params.onAddPage) {
return null
}
return (
<>
<Button
type="button"
variant="success"
size="small"
onClick={() => params.onAddPage?.('markdown')}
className="px-3 py-1 text-sm rounded bg-green-600 text-white hover:bg-green-700"
>
{t('page.add.markdown')}
</Button>
<Button
type="button"
variant="primary"
size="small"
onClick={() => params.onAddPage?.('image')}
className="px-3 py-1 text-sm rounded bg-purple-600 text-white hover:bg-purple-700"
>
{t('page.add.image')}
</Button>
</>
)
}
function ToolbarStatus(params: { uploading: boolean; error: string | null }): React.ReactElement | null {
if (!params.uploading && !params.error) {
return null
}
return (
<>
{params.uploading ? <span className="text-sm text-gray-500">{t('markdown.upload.uploading')}</span> : null}
{params.error ? <span className="text-sm text-red-600">{params.error}</span> : null}
</>
)
}