fix(en): هشت اصلاح سایت انگلیسی — دکمهٔ هیرو با ps منطقی، پیوند درباره ما زیر تیتر، justify متن درباره ما، متای مقاله (نویسنده/تاریخ/زمان مطالعه) دوزبانه، تیتر فهرست مطالب، H1 انگلیسی در ادیتور + چکلیست سئو زبانآگاه، بازگشت به وبلاگ با حفظ زبان، دستهبندی انگلیسی
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)} />
|
||||
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user