fix(en): هشت اصلاح سایت انگلیسی — دکمهٔ هیرو با ps منطقی، پیوند درباره ما زیر تیتر، justify متن درباره ما، متای مقاله (نویسنده/تاریخ/زمان مطالعه) دوزبانه، تیتر فهرست مطالب، H1 انگلیسی در ادیتور + چک‌لیست سئو زبان‌آگاه، بازگشت به وبلاگ با حفظ زبان، دسته‌بندی انگلیسی

This commit is contained in:
aramesh-site deploy
2026-10-01 16:54:44 +00:00
parent d56dff2a2f
commit 632edaf081
7 changed files with 74 additions and 33 deletions
+33 -22
View File
@@ -327,7 +327,7 @@ export function ArticleWritePage() {
setDeleting(true)
await runSave(async () => {
await adminApi.remove('articles', createdId)
navigate('/blog')
navigate(`${editLang === 'en' ? '/en' : ''}/blog`)
})
setDeleting(false)
}
@@ -353,7 +353,7 @@ export function ArticleWritePage() {
<header className="sticky top-0 z-40 border-b border-ivory/10 bg-ink/95 backdrop-blur-md">
<div className="mx-auto flex max-w-4xl items-center justify-between gap-4 px-6 py-3">
<Link
to="/blog"
to={`${editLang === 'en' ? '/en' : ''}/blog`}
data-admin-link
className="flex items-center gap-2 text-sm font-light text-ivory/70 transition-colors hover:text-copper"
>
@@ -451,7 +451,7 @@ export function ArticleWritePage() {
render={({ field }) => (
<input
type="text"
{...field}
value={editLang === 'en' ? String(draft.title_en ?? '') : field.value}
onChange={(event) => {
field.onChange(event.target.value)
queueSave({ [suf('title')]: event.target.value })
@@ -548,10 +548,10 @@ export function ArticleWritePage() {
{/* چکیده و متادیتا */}
<SeoChecklist
input={{
title: draft.title,
title: (editLang === 'en' ? draft.title_en : draft.title) ?? '',
slug: draft.slug,
excerpt: draft.meta_description || draft.excerpt,
body: draft.body,
excerpt: draft.meta_description || (editLang === 'en' ? draft.excerpt_en : draft.excerpt) || '',
body: (editLang === 'en' ? draft.body_en : draft.body) ?? draft.body,
keyphrase: draft.seo_keyphrase,
}}
/>
@@ -594,23 +594,34 @@ export function ArticleWritePage() {
<div className="grid grid-cols-1 gap-5 lg:grid-cols-2">
<div>
<label className="text-xs font-light text-ivory/50">دسته‌بندی مقاله (Category)</label>
<label className="text-xs font-light text-ivory/50">
دسته‌بندی مقاله (Category){editLang === 'en' ? ' — نسخه انگلیسی' : ''}
</label>
<div className="mt-2">
{/* تک‌انتخابی: ورودی/خروجی Controller یک {value,label} یا null است */}
<Controller
name="category"
control={control}
render={({ field }) => (
<CategorySelect
value={field.value}
options={ARTICLE_CATEGORIES}
onChange={(option) => {
field.onChange(option)
queueSave({ category: categoryValue(option) })
}}
/>
)}
/>
{editLang === 'en' ? (
<input
type="text"
{...textField('category_en')}
placeholder={draft.category ? `English category (فارسی: ${draft.category})…` : 'English category…'}
className="w-full border border-ivory/15 bg-char px-3 py-2 text-sm font-light text-ivory focus:border-copper focus:outline-none"
/>
) : (
/* تک‌انتخابی: ورودی/خروجی Controller یک {value,label} یا null است */
<Controller
name="category"
control={control}
render={({ field }) => (
<CategorySelect
value={field.value}
options={ARTICLE_CATEGORIES}
onChange={(option) => {
field.onChange(option)
queueSave({ category: categoryValue(option) })
}}
/>
)}
/>
)}
</div>
</div>
<div>
+10 -5
View File
@@ -2,6 +2,7 @@ import { CalendarIcon, ClockIcon } from 'lucide-react'
import type { Article } from '../data/site'
import { JALALI_TIME_FORMATTER, formatJalali } from '../utils/date'
import { toPersianDigits } from '../utils/digits'
import { useLang } from '../i18n/lang'
type ArticleMetaProps = {
article: Article
@@ -13,13 +14,17 @@ type ArticleMetaProps = {
* آواتار حرف اول نویسنده، نام نویسنده، تاریخ جلالی و زمان مطالعه.
*/
export function ArticleMeta({ article, className = '' }: ArticleMetaProps) {
const { t, lang } = useLang()
const rawDate = (article.date ?? '').trim()
const author = (article.author ?? '').trim() || 'گروه ساختمانی آرامش'
const author = (article.author ?? '').trim() || t('brandFallback')
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 todayLabel = `${formatJalali(new Date())} — ساعت ${JALALI_TIME_FORMATTER.format(new Date())}`
const dateLabel = formatJalali(rawDate, todayLabel)
const dateLabel = lang === 'en' ? enDateLabel : formatJalali(rawDate, todayLabel)
return (
<div className={`flex flex-wrap items-center gap-x-6 gap-y-4 ${className}`}>
@@ -30,7 +35,7 @@ export function ArticleMeta({ article, className = '' }: ArticleMetaProps) {
</span>
<span className="flex flex-col">
<span className="text-sm font-bold text-ivory">{author}</span>
<span className="text-[11px] font-light text-ivory/45">نویسنده</span>
<span className="text-[11px] font-light text-ivory/45">{t('author')}</span>
</span>
</span>
@@ -46,7 +51,7 @@ export function ArticleMeta({ article, className = '' }: ArticleMetaProps) {
{article.read && (
<span className="flex items-center gap-2 text-xs font-light text-ivory/60">
<ClockIcon className="h-3.5 w-3.5 text-copper" aria-hidden="true" />
{toPersianDigits(article.read)}
{lang === 'fa' ? toPersianDigits(article.read) : article.read}
</span>
)}
</div>
+18 -1
View File
@@ -1,5 +1,6 @@
import React from 'react'
import { motion, useScroll, useTransform } from 'framer-motion'
import { Link } from 'react-router-dom'
import { ArrowLeftIcon, ChevronDownIcon } from 'lucide-react'
import { ButtonLink } from '../Button'
import { useSite } from '../../api'
@@ -77,6 +78,22 @@ export function Hero() {
<InlineEdit multiline value={hero.subtitle} onSave={(v) => saveHero('subtitle', v)} />
</motion.p>
{/* پیوند درباره ما — زیر تیتر، در هر دو زبان */}
<motion.div
initial={{ opacity: 0, y: 18 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.24, ease: [0.23, 1, 0.32, 1] }}
className="mt-4"
>
<Link
to={href('/about')}
className="inline-flex items-center gap-1.5 border-b border-ivory/25 pb-0.5 text-sm font-light text-ivory/70 transition-colors duration-200 hover:border-copper hover:text-copper"
>
{t('aboutUs')}
<ArrowLeftIcon className="h-3.5 w-3.5 ltr:rotate-180" aria-hidden="true" />
</Link>
</motion.div>
{/* داک دکمه‌ها: استایل کپسولی، شیشه‌ای و دایره‌ای */}
<motion.div
initial={{ opacity: 0, y: 22 }}
@@ -88,7 +105,7 @@ export function Hero() {
<ButtonLink
to={href(hero.cta_primary_href)}
variant="copper"
className="!p-1.5 !pr-7 !rounded-full shadow-lg shadow-black/40 hover:shadow-copper/25 transition-all duration-300"
className="!p-1.5 !ps-7 !rounded-full shadow-lg shadow-black/40 hover:shadow-copper/25 transition-all duration-300"
>
<span className="text-sm font-medium tracking-wide text-ivory">
<InlineEdit value={hero.cta_primary_label} onSave={(v) => saveHero('cta_primary_label', v)} />
+1 -1
View File
@@ -47,7 +47,7 @@ export function Story({ showLink = true, showLabel = true, showQuote = false, al
<Reveal delay={0.08}>
{story.paragraphs.map((paragraph, i) => (
<p key={i} className={`text-base font-light leading-9 text-ivory/65 ${i === 0 ? 'mt-8' : 'mt-5'}`}>
<p key={i} className={`text-base font-light leading-9 text-ivory/65 text-justify ${i === 0 ? 'mt-8' : 'mt-5'}`}>
<InlineEdit multiline value={paragraph} onSave={(v) => saveParagraph(i, v)} />
</p>
))}
+6
View File
@@ -84,6 +84,9 @@ export const fa = {
relatedArticlesAria: 'مقالات مرتبط',
relatedArticles: 'مقالات مرتبط',
readArticlePrefix: 'خواندن مقاله',
author: 'نویسنده',
tableOfContents: 'فهرست مطالب',
aboutUs: 'درباره ما',
articleImagePrefix: 'تصویر مقاله',
featuredArticle: 'مقاله ویژه',
readMore: 'ادامه مطلب',
@@ -204,6 +207,9 @@ export const en: Dict = {
relatedArticlesAria: 'Related articles',
relatedArticles: 'Related articles',
readArticlePrefix: 'Read article',
author: 'Author',
tableOfContents: 'Table of Contents',
aboutUs: 'About Us',
articleImagePrefix: 'Image of article',
featuredArticle: 'Featured article',
readMore: 'Read more',
+1 -1
View File
@@ -164,7 +164,7 @@ export function ArticleDetail() {
dir={dir}
className={`order-first lg:order-none lg:row-start-1 ${tocColumns}`}
>
<TableOfContents items={toc} className="lg:sticky lg:top-28" />
<TableOfContents items={toc} title={t('tableOfContents')} className="lg:sticky lg:top-28" />
</aside>
)}
</div>
+5 -3
View File
@@ -26,7 +26,7 @@ function DraftBadge({ className = '' }: { className?: string }) {
export function Blog() {
const { articles } = useSite()
const { t, href } = useLang()
const { t, href, lang } = useLang()
const { isEditMode } = useAdmin()
// دکمه «نوشتن مقاله جدید» فقط در حالت ویرایش ادمین دیده می‌شود
@@ -96,10 +96,12 @@ 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>{formatJalali(featured.date)}</span>
<span>{lang === 'en'
? new Intl.DateTimeFormat('en-GB', { day: 'numeric', month: 'long', year: 'numeric' }).format(new Date(featured.date))
: formatJalali(featured.date)}</span>
<span className="flex items-center gap-1.5">
<ClockIcon className="h-3.5 w-3.5" />
{toPersianDigits(featured.read)}
{lang === 'en' ? featured.read : toPersianDigits(featured.read)}
</span>
</span>
<span className="flex items-center gap-2 text-copper">