fix(blog): زبان ویرایش به‌ازای هر مقاله به‌خاطر سپرده می‌شود، شمارندهٔ متا روی زبان فعال، جهت محتوای ادیتور از زبان ویرایش

This commit is contained in:
aramesh-site deploy
2026-10-07 08:07:54 +00:00
parent bd310e8d18
commit 99ecb0fa9f
2 changed files with 33 additions and 4 deletions
+25 -3
View File
@@ -134,7 +134,7 @@ const STATUS_TEXT: Record<string, string> = {
* مسیرها: /admin/write (مقاله جدید) و /admin/write/:id (ویرایش). * مسیرها: /admin/write (مقاله جدید) و /admin/write/:id (ویرایش).
*/ */
export function ArticleWritePage() { export function ArticleWritePage() {
const { isAuthenticated, saveStatus, runSave, editLang, toggleEditLang, role } = useAdmin() const { isAuthenticated, saveStatus, runSave, editLang, toggleEditLang, syncEditLang, role } = useAdmin()
const { id } = useParams<{ id: string }>() const { id } = useParams<{ id: string }>()
const navigate = useNavigate() const navigate = useNavigate()
const { data, updateSite } = useSiteData() const { data, updateSite } = useSiteData()
@@ -219,6 +219,23 @@ export function ArticleWritePage() {
const article = existing ?? fetched const article = existing ?? fetched
// زبان ویرایش این مقاله به‌ازای خودش به‌خاطر سپرده می‌شود — رفرش تب را عوض نکند
useEffect(() => {
if (!id) return
const saved = localStorage.getItem(`aramesh_write_lang_${id}`)
if (saved === 'en' || saved === 'fa') {
syncEditLang(saved)
return
}
const art = existing ?? null
if (art) {
const hasEn = Boolean((art as Draft).title_en?.trim()) || Boolean((art as Draft).body_en as unknown)
const hasFa = Boolean((art as Draft).title?.trim())
if (hasEn && !hasFa) syncEditLang('en')
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id, Boolean(article)])
// وقتی مقاله موجود شناخته شد، فرم و شناسه ساخته‌شده یک‌بار مقداردهی می‌شود // وقتی مقاله موجود شناخته شد، فرم و شناسه ساخته‌شده یک‌بار مقداردهی می‌شود
useEffect(() => { useEffect(() => {
if (!article || createdRef.current) return if (!article || createdRef.current) return
@@ -338,7 +355,6 @@ export function ArticleWritePage() {
queueSave({ [name]: e.target.value }), queueSave({ [name]: e.target.value }),
}) })
const metaLength = (draft.meta_description ?? '').trim().length
// در زبان انگلیسی، فیلدهای متنی به ستون‌های _en می‌روند؛ اسلاگ/تصویر/وضعیت مشترک‌اند // در زبان انگلیسی، فیلدهای متنی به ستون‌های _en می‌روند؛ اسلاگ/تصویر/وضعیت مشترک‌اند
const suf = <N extends string>(name: N) => `${name}${editLang === 'en' ? '_en' : ''}` as keyof Draft const suf = <N extends string>(name: N) => `${name}${editLang === 'en' ? '_en' : ''}` as keyof Draft
// مقدار فعال فیلد دوزبانه — برای value ورودی‌ها و چک‌لیست سئو // مقدار فعال فیلد دوزبانه — برای value ورودی‌ها و چک‌لیست سئو
@@ -353,6 +369,7 @@ export function ArticleWritePage() {
const activeVal = (name: 'title' | 'slug' | 'author' | 'seo_keyphrase' | 'meta_description' | 'excerpt'): string => const activeVal = (name: 'title' | 'slug' | 'author' | 'seo_keyphrase' | 'meta_description' | 'excerpt'): string =>
String((editLang === 'en' ? draft[`${name}_en` as keyof Draft] : draft[name]) ?? '') String((editLang === 'en' ? draft[`${name}_en` as keyof Draft] : draft[name]) ?? '')
const metaLength = activeVal('meta_description').trim().length
// متن جایگزین تصویر شاخص — با watch('title') به‌صورت زنده ساخته می‌شود // متن جایگزین تصویر شاخص — با watch('title') به‌صورت زنده ساخته می‌شود
const imageAltFallback = [watchedTitle.trim(), FEATURED_IMAGE_ALT_SUFFIX] const imageAltFallback = [watchedTitle.trim(), FEATURED_IMAGE_ALT_SUFFIX]
@@ -376,7 +393,11 @@ export function ArticleWritePage() {
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<button <button
type="button" type="button"
onClick={toggleEditLang} onClick={() => {
const next = editLang === 'en' ? 'fa' : 'en'
toggleEditLang()
if (createdId) localStorage.setItem(`aramesh_write_lang_${createdId}`, next)
}}
title="تعیین می‌کند متن مقاله روی کدام زبان ذخیره شود" title="تعیین می‌کند متن مقاله روی کدام زبان ذخیره شود"
className={`px-4 py-2 text-xs font-bold transition-colors duration-200 ${ className={`px-4 py-2 text-xs font-bold transition-colors duration-200 ${
editLang === 'en' editLang === 'en'
@@ -505,6 +526,7 @@ export function ArticleWritePage() {
<div className="mt-8"> <div className="mt-8">
<ArticleEditor <ArticleEditor
contentKey={loadedContentKey} contentKey={loadedContentKey}
contentLang={editLang}
doc={(editLang === 'en' ? draft.body_en : draft.body) as unknown as Record<string, unknown>} doc={(editLang === 'en' ? draft.body_en : draft.body) as unknown as Record<string, unknown>}
onChange={(json) => { onChange={(json) => {
// زمان مطالعه به‌صورت پویا از متن سند محاسبه می‌شود — به زبان جاری // زمان مطالعه به‌صورت پویا از متن سند محاسبه می‌شود — به زبان جاری
@@ -85,6 +85,8 @@ type ArticleEditorProps = {
* (مثلاً وقتی مقاله از سرور می‌رسد). بدون این prop محتوا فقط یک‌بار مقداردهی می‌شود. * (مثلاً وقتی مقاله از سرور می‌رسد). بدون این prop محتوا فقط یک‌بار مقداردهی می‌شود.
*/ */
contentKey?: string | number contentKey?: string | number
/** زبان محتوای ویرایشگر — جهت و زبان عنصر ویرایش با آن همگام می‌شود */
contentLang?: 'fa' | 'en'
} }
/** نمونه ویرایشگر TipTap — همان چیزی که useEditor برمی‌گرداند */ /** نمونه ویرایشگر TipTap — همان چیزی که useEditor برمی‌گرداند */
@@ -186,7 +188,7 @@ const Divider = () => <span className="mx-1 h-5 w-px bg-ivory/10" aria-hidden="t
* ویرایشگر WYSIWYG مقاله (TipTap) — نود تصویر کپشن‌دار، مودال لینک سئو-محور، * ویرایشگر WYSIWYG مقاله (TipTap) — نود تصویر کپشن‌دار، مودال لینک سئو-محور،
* کلید راست‌چین/چپ‌چین (فقط روی متن اثر دارد، نه نوار ابزار) و آپلود تصویر به MinIO. * کلید راست‌چین/چپ‌چین (فقط روی متن اثر دارد، نه نوار ابزار) و آپلود تصویر به MinIO.
*/ */
export function ArticleEditor({ doc, onChange, contentKey }: ArticleEditorProps) { export function ArticleEditor({ doc, onChange, contentKey, contentLang = 'fa' }: ArticleEditorProps) {
const { runSave } = useAdmin() const { runSave } = useAdmin()
const fileRef = useRef<HTMLInputElement>(null) const fileRef = useRef<HTMLInputElement>(null)
const [uploading, setUploading] = useState(false) const [uploading, setUploading] = useState(false)
@@ -294,6 +296,11 @@ export function ArticleEditor({ doc, onChange, contentKey }: ArticleEditorProps)
onUpdate: ({ editor: instance }) => onChange(instance.getJSON()), onUpdate: ({ editor: instance }) => onChange(instance.getJSON()),
}) })
// جهت متن از زبان محتوای در حال ویرایش پیروی می‌کند — انگلیسی LTR، فارسی RTL
useEffect(() => {
setDirection(contentLang === 'en' ? 'ltr' : 'rtl')
}, [contentLang])
// جهت متن فقط روی ناحیه ویرایش اعمال می‌شود؛ نوار ابزار همیشه راست‌چین می‌ماند // جهت متن فقط روی ناحیه ویرایش اعمال می‌شود؛ نوار ابزار همیشه راست‌چین می‌ماند
useEffect(() => { useEffect(() => {
if (!editor) return if (!editor) return