fix(blog): تاریخ/زمان مطالعه کارتها در /en انگلیسی شد؛ کرسر فیلدهای تصویر دیگر به آخر متن نمیپرد (مقدار محلی با همگامسازی فوکوسآگاه)
This commit is contained in:
@@ -2,12 +2,12 @@ import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { ArrowLeftIcon, ClockIcon } from 'lucide-react'
|
||||
import type { Article } from '../data/site'
|
||||
import { formatJalali } from '../utils/date'
|
||||
import { formatJalali, formatEnDate } from '../utils/date'
|
||||
import { useLang } from '../i18n/lang'
|
||||
import { toPersianDigits } from '../utils/digits'
|
||||
|
||||
export function ArticleCard({ article }: { article: Article }) {
|
||||
const { t, href } = useLang()
|
||||
const { t, href, lang } = useLang()
|
||||
return (
|
||||
<Link
|
||||
to={href(`/blog/${article.slug}`)}
|
||||
@@ -39,10 +39,10 @@ export function ArticleCard({ article }: { article: Article }) {
|
||||
|
||||
<div className="mt-auto flex items-center justify-between border-t border-white/10 pt-6 md:pt-8 text-xs font-light text-ivory/50">
|
||||
<span className="flex items-center gap-4">
|
||||
<span>{formatJalali(article.date)}</span>
|
||||
<span>{lang === 'en' ? formatEnDate(article.date) : formatJalali(article.date)}</span>
|
||||
<span className="flex items-center gap-2">
|
||||
<ClockIcon className="h-3.5 w-3.5 text-copper/80" />
|
||||
{toPersianDigits(article.read)}
|
||||
{lang === 'en' ? article.read : toPersianDigits(article.read)}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CalendarIcon, ClockIcon } from 'lucide-react'
|
||||
import type { Article } from '../data/site'
|
||||
import { JALALI_TIME_FORMATTER, formatJalali } from '../utils/date'
|
||||
import { JALALI_TIME_FORMATTER, formatJalali, formatEnDate } from '../utils/date'
|
||||
import { toPersianDigits } from '../utils/digits'
|
||||
import { useLang } from '../i18n/lang'
|
||||
|
||||
@@ -20,9 +20,8 @@ export function ArticleMeta({ article, className = '' }: ArticleMetaProps) {
|
||||
const initial = author.charAt(0)
|
||||
|
||||
// فارسی: تاریخ شمسی با ارقام فارسی؛ انگلیسی: تاریخ میلادی لاتین
|
||||
const enDateLabel = rawDate
|
||||
? new Intl.DateTimeFormat('en-GB', { day: 'numeric', month: 'long', year: 'numeric' }).format(new Date(rawDate))
|
||||
: new Intl.DateTimeFormat('en-GB', { day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit' }).format(new Date())
|
||||
const enDateLabel = formatEnDate(rawDate)
|
||||
|| new Intl.DateTimeFormat('en-GB', { day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit' }).format(new Date())
|
||||
const todayLabel = `${formatJalali(new Date())} — ساعت ${JALALI_TIME_FORMATTER.format(new Date())}`
|
||||
const dateLabel = lang === 'en' ? enDateLabel : formatJalali(rawDate, todayLabel)
|
||||
|
||||
|
||||
@@ -19,6 +19,35 @@ const DELETE_BUTTON_CLASS =
|
||||
const INLINE_FIELD_CLASS =
|
||||
'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'
|
||||
|
||||
/**
|
||||
* فیلد متصل به attrs گره — چون مقدار جدید از پروسسمیرور با یک رندر تأخیر
|
||||
* میرسد، ورودی کنترلشدهٔ مستقیم کرسر را به آخر متن میاندازد. اینجا مقدار
|
||||
* محلی نگه داشته میشود و فقط وقتی ورودی فوکوس ندارد از بیرون همگام میشود.
|
||||
*/
|
||||
function useAttrField(
|
||||
attrValue: string,
|
||||
commit: (value: string) => void,
|
||||
externalRef?: React.MutableRefObject<HTMLInputElement | null>,
|
||||
) {
|
||||
const ownRef = useRef<HTMLInputElement | null>(null)
|
||||
const ref = externalRef ?? ownRef
|
||||
const [local, setLocal] = useState(attrValue)
|
||||
|
||||
useEffect(() => {
|
||||
if (document.activeElement !== ref.current) setLocal(attrValue)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [attrValue])
|
||||
|
||||
return {
|
||||
ref,
|
||||
value: local,
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setLocal(event.target.value)
|
||||
commit(event.target.value)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const ADD_CAPTION_CLASS =
|
||||
'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'
|
||||
|
||||
@@ -52,6 +81,10 @@ export function ImageNodeView({ node, updateAttributes, deleteNode, selected }:
|
||||
const [captionOpen, setCaptionOpen] = useState(true)
|
||||
const [rehosting, setRehosting] = useState(false)
|
||||
const captionInputRef = useRef<HTMLInputElement>(null)
|
||||
const altField = useAttrField(alt ?? '', (v) => updateAttributes({ alt: v }))
|
||||
const titleField = useAttrField(title ?? '', (v) => updateAttributes({ title: v }))
|
||||
const captionField = useAttrField(caption ?? '', (v) => updateAttributes({ caption: v }), captionInputRef)
|
||||
const captionHrefField = useAttrField(captionHref ?? '', (v) => updateAttributes({ caption_href: v }))
|
||||
const focusOnOpen = useRef(false)
|
||||
const showCaption = captionOpen || hasCaption
|
||||
const external = isExternalImage(src)
|
||||
@@ -124,16 +157,14 @@ export function ImageNodeView({ node, updateAttributes, deleteNode, selected }:
|
||||
<div contentEditable={false} className="mt-1.5 grid gap-1.5 sm:grid-cols-2">
|
||||
<input
|
||||
type="text"
|
||||
value={alt ?? ''}
|
||||
onChange={(event) => updateAttributes({ alt: event.target.value })}
|
||||
{...altField}
|
||||
placeholder="نام تصویر / متن جایگزین (Alt)…"
|
||||
aria-label="متن جایگزین تصویر"
|
||||
className={INLINE_FIELD_CLASS}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={title ?? ''}
|
||||
onChange={(event) => updateAttributes({ title: event.target.value })}
|
||||
{...titleField}
|
||||
placeholder="عنوان تصویر (Title)…"
|
||||
aria-label="عنوان تصویر"
|
||||
className={INLINE_FIELD_CLASS}
|
||||
@@ -143,18 +174,15 @@ export function ImageNodeView({ node, updateAttributes, deleteNode, selected }:
|
||||
{showCaption ? (
|
||||
<figcaption className="article-figure__caption" contentEditable={false}>
|
||||
<input
|
||||
ref={captionInputRef}
|
||||
type="text"
|
||||
value={caption ?? ''}
|
||||
onChange={(event) => updateAttributes({ caption: event.target.value })}
|
||||
{...captionField}
|
||||
placeholder="توضیح تصویر (کپشن)…"
|
||||
aria-label="کپشن تصویر"
|
||||
className={INLINE_FIELD_CLASS}
|
||||
/>
|
||||
<input
|
||||
type="url"
|
||||
value={captionHref ?? ''}
|
||||
onChange={(event) => updateAttributes({ caption_href: event.target.value })}
|
||||
{...captionHrefField}
|
||||
placeholder="لینک کپشن (اختیاری) — مثل منبع عکس…"
|
||||
aria-label="لینک کپشن تصویر"
|
||||
dir="ltr"
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useSite } from '../api'
|
||||
import { useLang } from '../i18n/lang'
|
||||
import { EditableItem } from '../admin/Editable'
|
||||
import { useAdmin } from '../admin/AdminContext'
|
||||
import { formatJalali } from '../utils/date'
|
||||
import { formatJalali, formatEnDate } from '../utils/date'
|
||||
import { toPersianDigits } from '../utils/digits'
|
||||
import { PlusIcon } from 'lucide-react'
|
||||
|
||||
@@ -96,9 +96,7 @@ export function Blog() {
|
||||
<p className="mt-6 text-sm font-light leading-10 text-ivory/65">{featured.excerpt}</p>
|
||||
<div className="mt-10 flex items-center justify-between border-t border-ivory/10 pt-8 text-xs font-light text-ivory/50">
|
||||
<span className="flex items-center gap-4">
|
||||
<span>{lang === 'en'
|
||||
? new Intl.DateTimeFormat('en-GB', { day: 'numeric', month: 'long', year: 'numeric' }).format(new Date(featured.date))
|
||||
: formatJalali(featured.date)}</span>
|
||||
<span>{lang === 'en' ? formatEnDate(featured.date) : formatJalali(featured.date)}</span>
|
||||
<span className="flex items-center gap-1.5">
|
||||
<ClockIcon className="h-3.5 w-3.5" />
|
||||
{lang === 'en' ? featured.read : toPersianDigits(featured.read)}
|
||||
|
||||
@@ -97,3 +97,11 @@ export function toPublishedISO(value?: string | null): string | null {
|
||||
})
|
||||
return jalali.isValid ? toISODate(jalali) : null
|
||||
}
|
||||
|
||||
/** تاریخ میلادی انگلیسی برای سایت /en — با محافظ مقادیر نامعتبر/خالی */
|
||||
export function formatEnDate(value?: string | null): string {
|
||||
if (!value) return ''
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) return ''
|
||||
return new Intl.DateTimeFormat('en-GB', { day: 'numeric', month: 'long', year: 'numeric' }).format(date)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user