Polish portfolio, blog, and site chrome for clearer browsing.
Add patterned title bands, lightbox gallery, project link bar, footer stats/credit, gradient link hovers, and more reliable blog covers/body rendering. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
37d8e4be91
commit
908d86939c
@@ -1,6 +1,7 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { BlogCoverImage } from "@/components/BlogCoverImage";
|
||||
import { ContentPageShell } from "@/components/ContentPageShell";
|
||||
import { HtmlContent } from "@/components/HtmlContent";
|
||||
import {
|
||||
@@ -15,6 +16,28 @@ type BlogDetailPageProps = {
|
||||
params: Promise<{ slug: string }>;
|
||||
};
|
||||
|
||||
function stripLeadingCoverImage(html: string, cover: string | null): string {
|
||||
if (!html) return html;
|
||||
|
||||
let next = html.replace(
|
||||
/^\s*<p[^>]*>\s*<img[^>]*>\s*<\/p>/i,
|
||||
"",
|
||||
);
|
||||
|
||||
if (cover) {
|
||||
const escaped = cover.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
next = next.replace(
|
||||
new RegExp(
|
||||
`<p[^>]*>\\s*<img[^>]*src=["']${escaped}["'][^>]*>\\s*<\\/p>`,
|
||||
"i",
|
||||
),
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
return next.trim();
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: BlogDetailPageProps): Promise<Metadata> {
|
||||
@@ -45,54 +68,82 @@ export default async function BlogDetailPage({ params }: BlogDetailPageProps) {
|
||||
const cover = getBlogCoverImage(blog);
|
||||
const date = formatFaDate(blog.publishedAt);
|
||||
const author = authorName(blog.author);
|
||||
const category = blog.categoryName;
|
||||
const bodyHtml = blog.mainTextHtml
|
||||
? stripLeadingCoverImage(blog.mainTextHtml, cover)
|
||||
: null;
|
||||
const titleEn = category || "Blog";
|
||||
|
||||
return (
|
||||
<ContentPageShell>
|
||||
<ContentPageShell flushTop>
|
||||
<article className="pb-20 lg:pb-28">
|
||||
<div className="mx-auto max-w-3xl px-5 lg:px-8">
|
||||
<div className="reveal mb-8">
|
||||
<Link
|
||||
href="/blog"
|
||||
className="link-hover mb-6 inline-flex text-sm text-muted"
|
||||
<div className="page-title-band mb-8 md:mb-10">
|
||||
<div className="relative mx-auto max-w-7xl px-5 pb-4 pt-[calc(var(--header-height)+0.75rem)] lg:px-8 lg:pb-5 lg:pt-[calc(var(--header-height)+1rem)]">
|
||||
<p
|
||||
aria-hidden
|
||||
className="latin pointer-events-none absolute inset-x-5 top-[calc(var(--header-height)+2.4rem)] z-0 text-[clamp(1.75rem,5.5vw,3.75rem)] font-light uppercase leading-none tracking-[0.05em] text-ink/[0.07] lg:inset-x-8 lg:top-[calc(var(--header-height)+2.6rem)]"
|
||||
>
|
||||
بازگشت به مقالات
|
||||
</Link>
|
||||
{titleEn}
|
||||
</p>
|
||||
|
||||
{blog.categoryName ? (
|
||||
<p className="latin title-en mb-3 text-muted">
|
||||
{blog.categoryName}
|
||||
</p>
|
||||
) : null}
|
||||
<div className="reveal relative z-10">
|
||||
<nav
|
||||
aria-label="مسیر صفحه"
|
||||
className="mb-3 flex flex-wrap items-center gap-1.5 text-xs text-muted"
|
||||
>
|
||||
<Link href="/blog" className="link-hover">
|
||||
مقالات
|
||||
</Link>
|
||||
{category ? (
|
||||
<>
|
||||
<span aria-hidden className="text-line">
|
||||
/
|
||||
</span>
|
||||
<span className="latin uppercase tracking-[0.14em]">
|
||||
{category}
|
||||
</span>
|
||||
</>
|
||||
) : null}
|
||||
<span aria-hidden className="text-line">
|
||||
/
|
||||
</span>
|
||||
<span className="line-clamp-1 text-ink-soft">{blog.title}</span>
|
||||
</nav>
|
||||
|
||||
<h1 className="whitespace-normal text-[clamp(1.35rem,3.8vw,2.5rem)] font-normal leading-tight tracking-tight text-ink">
|
||||
{blog.title}
|
||||
</h1>
|
||||
<h1 className="whitespace-normal text-[clamp(1.35rem,3.8vw,2.5rem)] font-normal leading-tight tracking-tight text-ink">
|
||||
{blog.title}
|
||||
</h1>
|
||||
|
||||
{blog.abstract ? (
|
||||
<p className="mt-5 text-sm leading-8 text-muted md:text-base">
|
||||
{blog.abstract}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
<div className="mt-5 flex flex-wrap items-center gap-x-4 gap-y-2 text-xs text-muted">
|
||||
{date ? <time dateTime={blog.publishedAt ?? undefined}>{date}</time> : null}
|
||||
{author ? <span>نویسنده: {author}</span> : null}
|
||||
{blog.abstract ? (
|
||||
<p className="mt-2 text-justify text-sm leading-7 text-muted md:text-base">
|
||||
{blog.abstract}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{cover && blog.titleImageUrl ? (
|
||||
<div className="reveal reveal-delay-1 mb-10 overflow-hidden rounded-2xl bg-paper-deep">
|
||||
<img
|
||||
src={cover}
|
||||
alt=""
|
||||
className="mx-auto max-h-[28rem] w-full object-contain"
|
||||
/>
|
||||
<div className="mx-auto max-w-3xl px-5 lg:px-8">
|
||||
{cover ? (
|
||||
<div className="mb-5 overflow-hidden rounded-2xl">
|
||||
<BlogCoverImage src={cover} className="mx-auto max-h-[28rem]" />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{blog.mainTextHtml ? (
|
||||
<div className="reveal reveal-delay-2">
|
||||
<HtmlContent html={blog.mainTextHtml} />
|
||||
{(date || author) ? (
|
||||
<div className="mb-8 flex items-center justify-between gap-4 border-b border-line/70 pb-5 text-xs text-muted">
|
||||
{author ? <span>نویسنده: {author}</span> : <span />}
|
||||
{date ? (
|
||||
<time dateTime={blog.publishedAt ?? undefined}>{date}</time>
|
||||
) : null}
|
||||
</div>
|
||||
) : cover ? (
|
||||
<div className="mb-8 border-b border-line/70" />
|
||||
) : null}
|
||||
|
||||
{bodyHtml ? (
|
||||
<div className="reveal">
|
||||
<HtmlContent html={bodyHtml} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user