Update blog editor features

This commit is contained in:
fatemeh sane
2026-09-13 10:39:25 +03:30
parent 20c95e73b3
commit bb6c586d99
21 changed files with 1235 additions and 187 deletions
+116 -17
View File
@@ -7,6 +7,8 @@ import { useAdmin, useFieldSaver } from './AdminContext'
import { adminApi } from './adminClient'
import type { EntityConfig, FieldConfig } from './entities'
import { ImageField } from '../components/admin/ImageField'
import { JalaliDatePicker } from '../components/admin/JalaliDatePicker'
import { LuxeSelect, LuxeTagsSelect } from '../components/admin/LuxeSelect'
import { ArticleEditor } from '../components/admin/ArticleEditor'
type EntityDrawerProps = {
@@ -111,21 +113,33 @@ export function EntityDrawer({ config, item, onClose, deletable = false }: Entit
<div className="flex-1 overflow-y-auto px-6 py-6">
<div className="grid gap-6">
{config.fields.map((field) => (
<FieldControl
key={field.name}
field={field}
value={item[field.name]}
onSave={(value) => saveField(field.name, value)}
registerFlush={
field.type === 'wysiwyg'
? (fn) => {
flushRef.current = fn
}
: undefined
}
/>
))}
{config.fields.map((field) => {
// فیلد Alt تصویر: برای گالری همان ستون alt و برای بقیه <نام فیلد>_alt
const altKey =
field.type === 'image'
? field.name === 'src' && 'alt' in item
? 'alt'
: `${field.name}_alt`
: ''
return (
<FieldControl
key={field.name}
field={field}
value={item[field.name]}
altValue={altKey ? String(item[altKey] ?? '') : undefined}
altFallback={String(item[config.titleKey] ?? '')}
onSave={(value) => saveField(field.name, value)}
onSaveAlt={altKey ? (alt) => saveField(altKey, alt) : undefined}
registerFlush={
field.type === 'wysiwyg'
? (fn) => {
flushRef.current = fn
}
: undefined
}
/>
)
})}
</div>
</div>
@@ -150,12 +164,19 @@ export function EntityDrawer({ config, item, onClose, deletable = false }: Entit
function FieldControl({
field,
value,
altValue,
altFallback,
onSave,
onSaveAlt,
registerFlush,
}: {
field: FieldConfig
value: unknown
/** مقدار Alt تصویر — فقط برای فیلدهای image */
altValue?: string
altFallback?: string
onSave: (value: unknown) => Promise<boolean>
onSaveAlt?: (alt: string) => Promise<boolean>
registerFlush?: (fn: () => void) => void
}) {
const [text, setText] = useState<string>(String(value ?? ''))
@@ -167,7 +188,53 @@ function FieldControl({
}
if (field.type === 'image') {
return <ImageField label={field.label} value={String(value ?? '')} onSave={(url) => onSave(url)} />
return (
<ImageField
label={field.label}
value={String(value ?? '')}
alt={altValue}
altFallback={altFallback}
onSave={(url) => onSave(url)}
onSaveAlt={onSaveAlt}
/>
)
}
if (field.type === 'select') {
return (
<div>
<p className="text-xs font-bold text-ivory/80">{field.label}</p>
{field.hint && <p className="mt-1 text-[10px] font-light text-ivory/40">{field.hint}</p>}
<div className="mt-2">
<LuxeSelect
value={String(value ?? '')}
options={field.options ?? []}
onChange={(next) => {
setText(next)
void onSave(next)
}}
/>
</div>
</div>
)
}
if (field.type === 'date') {
return (
<div>
<p className="text-xs font-bold text-ivory/80">{field.label}</p>
{field.hint && <p className="mt-1 text-[10px] font-light text-ivory/40">{field.hint}</p>}
<div className="mt-2">
<JalaliDatePicker
value={String(value ?? '')}
onChange={(isoDate) => {
setText(isoDate)
void onSave(isoDate)
}}
/>
</div>
</div>
)
}
if (field.type === 'wysiwyg') {
@@ -175,7 +242,11 @@ function FieldControl({
}
if (field.type === 'list') {
return <ListField field={field} value={value} onSave={onSave} />
return field.ui === 'tags' ? (
<TagsField field={field} value={value} onSave={onSave} />
) : (
<ListField field={field} value={value} onSave={onSave} />
)
}
if (field.type === 'textarea') {
@@ -210,6 +281,34 @@ function FieldControl({
)
}
/** فیلد هشتگ‌ها — react-select قابل ساخت (Creatable) با ذخیره فوری */
function TagsField({
field,
value,
onSave,
}: {
field: FieldConfig
value: unknown
onSave: (value: unknown) => Promise<boolean>
}) {
const [tags, setTags] = useState<string[]>(Array.isArray(value) ? (value as string[]) : [])
const update = (next: string[]) => {
setTags(next)
void onSave(next)
}
return (
<div>
<p className="text-xs font-bold text-ivory/80">{field.label}</p>
{field.hint && <p className="mt-1 text-[10px] font-light text-ivory/40">{field.hint}</p>}
<div className="mt-2">
<LuxeTagsSelect value={tags} onChange={update} />
</div>
</div>
)
}
/** ویرایش آرایه خطی (پاراگراف‌ها، گالری، نکات) — هر تغییر ذخیره می‌شود */
function ListField({
field,