feat(admin): enhance editor UI (links, image captions) & fix categories
Added animated auto-opening link modal, custom image node with isolated caption deletion, fixed single-select category data binding, improved featured image layout, and updated README.
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { NodeViewWrapper } from '@tiptap/react'
|
||||
import type { NodeViewProps } from '@tiptap/react'
|
||||
import { PlusIcon, XIcon } from 'lucide-react'
|
||||
|
||||
type ImageAttrs = {
|
||||
src: string
|
||||
alt: string | null
|
||||
title: string | null
|
||||
caption: string | null
|
||||
}
|
||||
|
||||
const DELETE_BUTTON_CLASS =
|
||||
'flex h-7 w-7 shrink-0 items-center justify-center border border-red-400/50 bg-ink/85 text-red-300 backdrop-blur-sm transition-colors duration-150 hover:bg-red-500/20'
|
||||
|
||||
/**
|
||||
* نمایش تصویر مقاله در ویرایشگر — دقیقاً با همان ساختار خروجی سایت:
|
||||
* <figure><img><figcaption>کپشن</figcaption></figure>
|
||||
* دو دکمه حذف مستقل دارد: یکی برای خود تصویر و یکی برای حذف کامل کپشن.
|
||||
* با حذف کپشن، عنصر <figcaption> بهطور کامل از DOM حذف میشود و فقط تصویر میماند.
|
||||
*/
|
||||
export function ImageNodeView({ node, updateAttributes, deleteNode, selected }: NodeViewProps) {
|
||||
const { src, alt, title, caption } = node.attrs as ImageAttrs
|
||||
const hasCaption = Boolean((caption ?? '').trim())
|
||||
const [editingCaption, setEditingCaption] = useState(false)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const showCaption = hasCaption || editingCaption
|
||||
|
||||
// هنگام افزودن کپشن تازه، ورودی خودکار فوکوس میگیرد
|
||||
useEffect(() => {
|
||||
if (editingCaption && !hasCaption) inputRef.current?.focus()
|
||||
}, [editingCaption, hasCaption])
|
||||
|
||||
/** حذف کپشن: مقدار صفت پاک و عنصر figcaption کاملاً unmount میشود */
|
||||
const removeCaption = () => {
|
||||
updateAttributes({ caption: '' })
|
||||
setEditingCaption(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<NodeViewWrapper
|
||||
className={`article-figure-node ${selected ? 'is-selected' : ''}`}
|
||||
data-drag-handle
|
||||
>
|
||||
<figure className="article-figure relative">
|
||||
<img src={src} alt={alt ?? ''} title={title ?? ''} draggable={false} />
|
||||
|
||||
{/* حذف کل تصویر */}
|
||||
<button
|
||||
type="button"
|
||||
contentEditable={false}
|
||||
onClick={() => deleteNode()}
|
||||
aria-label="حذف تصویر"
|
||||
title="حذف تصویر"
|
||||
className={`absolute left-2 top-2 z-10 ${DELETE_BUTTON_CLASS}`}
|
||||
>
|
||||
<XIcon className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
|
||||
{showCaption && (
|
||||
<figcaption className="article-figure__caption" contentEditable={false}>
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={caption ?? ''}
|
||||
onChange={(event) => updateAttributes({ caption: event.target.value })}
|
||||
placeholder="توضیح تصویر (کپشن)…"
|
||||
aria-label="کپشن تصویر"
|
||||
className="w-full border border-ivory/15 bg-ink px-3 py-1.5 text-xs font-light text-ivory placeholder:text-ivory/30 focus:border-copper focus:outline-none"
|
||||
/>
|
||||
{/* حذف فقط کپشن — تصویر دستنخورده میماند */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={removeCaption}
|
||||
aria-label="حذف کپشن"
|
||||
title="حذف کپشن (تصویر حذف نمیشود)"
|
||||
className={DELETE_BUTTON_CLASS}
|
||||
>
|
||||
<XIcon className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</figcaption>
|
||||
)}
|
||||
</figure>
|
||||
|
||||
{/* وقتی کپشنی وجود ندارد، فقط یک دکمه افزودن نمایش داده میشود */}
|
||||
{!showCaption && (
|
||||
<button
|
||||
type="button"
|
||||
contentEditable={false}
|
||||
onClick={() => setEditingCaption(true)}
|
||||
className="mt-1.5 inline-flex items-center gap-1.5 border border-dashed border-ivory/25 px-3 py-1 text-[11px] font-light text-ivory/50 transition-colors duration-150 hover:border-copper hover:text-copper"
|
||||
>
|
||||
<PlusIcon className="h-3 w-3" aria-hidden="true" />
|
||||
افزودن کپشن
|
||||
</button>
|
||||
)}
|
||||
</NodeViewWrapper>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user