Adds consultation leads, section editors, and the one-time content reset for this release. Co-authored-by: Cursor <cursoragent@cursor.com>
94 lines
4.7 KiB
Vue
94 lines
4.7 KiB
Vue
<template>
|
|
<article v-if="post" class="py-24 relative overflow-hidden">
|
|
<div class="absolute top-0 right-0 w-[500px] h-[500px] bg-brand-100/50 dark:bg-brand-600/15 rounded-full blur-[140px] pointer-events-none -z-10" />
|
|
<div class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 text-right">
|
|
<nav aria-label="مسیر صفحه" class="text-xs font-bold text-slate-400 dark:text-slate-500 mb-6">
|
|
<ol class="flex flex-wrap items-center gap-2">
|
|
<li><NuxtLink to="/" class="hover:text-brand-600 dark:hover:text-brand-400 transition-colors">خانه</NuxtLink></li>
|
|
<li aria-hidden="true">/</li>
|
|
<li><NuxtLink to="/blog" class="hover:text-brand-600 dark:hover:text-brand-400 transition-colors">بلاگ</NuxtLink></li>
|
|
<li aria-hidden="true">/</li>
|
|
<li aria-current="page" class="text-slate-500 dark:text-slate-400">{{ post.title }}</li>
|
|
</ol>
|
|
</nav>
|
|
|
|
<header class="space-y-4 mb-10">
|
|
<h1 class="text-3xl sm:text-4xl lg:text-5xl font-black text-slate-900 dark:text-white leading-[1.35] tracking-tight">{{ post.title }}</h1>
|
|
<p v-if="post.excerpt" class="text-base sm:text-lg text-slate-600 dark:text-slate-300 leading-relaxed">{{ post.excerpt }}</p>
|
|
<time v-if="post.publishedAt" :datetime="post.publishedAt" class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-slate-100/80 dark:bg-slate-800/80 border border-slate-200/70 dark:border-slate-700/70 text-xs font-semibold text-slate-500 dark:text-slate-300">
|
|
<span class="w-2 h-2 rounded-full bg-brand-500" />
|
|
{{ formatFaDate(post.publishedAt) }}
|
|
</time>
|
|
</header>
|
|
|
|
<div v-if="post.coverUrl" class="mb-10 rounded-3xl overflow-hidden border border-slate-200/90 dark:border-slate-800 shadow-soft-card aspect-[16/9] bg-slate-100 dark:bg-slate-900">
|
|
<img :src="post.coverUrl" :alt="post.title" width="1200" height="675" fetchpriority="high" class="w-full h-full object-cover">
|
|
</div>
|
|
|
|
<div class="bg-white dark:bg-slate-900/90 rounded-3xl p-6 sm:p-10 border border-slate-200/90 dark:border-slate-800 shadow-soft-card">
|
|
<div class="whitespace-pre-line text-base leading-loose text-slate-700 dark:text-slate-300">{{ plainContent }}</div>
|
|
</div>
|
|
|
|
<div class="pt-10">
|
|
<NuxtLink to="/blog" class="inline-flex items-center gap-2.5 px-6 py-3 rounded-full border border-slate-200 dark:border-slate-700 text-slate-700 dark:text-slate-200 font-bold text-sm hover:border-brand-600 dark:hover:border-brand-400 hover:text-brand-600 dark:hover:text-brand-400 hover:bg-brand-50/50 dark:hover:bg-slate-800/50 transition-all duration-300">
|
|
<svg class="w-4 h-4 text-brand-600 dark:text-brand-400 rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" /></svg>
|
|
<span>بازگشت به بلاگ</span>
|
|
</NuxtLink>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
const route = useRoute();
|
|
const slug = String(route.params.slug);
|
|
const { data: post } = await useAsyncData(`blog-${slug}`, () => apiFetch<BlogPost>(`/api/public/blog/${encodeURIComponent(slug)}`).catch(() => null));
|
|
|
|
if (!post.value) {
|
|
throw createError({ statusCode: 404, statusMessage: "پست پیدا نشد", fatal: true });
|
|
}
|
|
|
|
const plainContent = computed(() =>
|
|
(post.value?.content || "")
|
|
.replace(/<\/p>/gi, "\n\n")
|
|
.replace(/<br\s*\/?>/gi, "\n")
|
|
.replace(/<[^>]+>/g, "")
|
|
.trim(),
|
|
);
|
|
|
|
const { canonical, siteUrl } = usePageSeo(
|
|
() => ({
|
|
title: post.value?.seoTitle || post.value?.title,
|
|
description: post.value?.seoDesc || post.value?.excerpt,
|
|
ogImage: post.value?.ogImage || post.value?.coverUrl,
|
|
canonical: post.value?.canonical,
|
|
}),
|
|
{ type: "article" },
|
|
);
|
|
|
|
useSeoMeta({
|
|
articlePublishedTime: () => post.value?.publishedAt || undefined,
|
|
articleModifiedTime: () => post.value?.updatedAt || undefined,
|
|
});
|
|
|
|
useBreadcrumbJsonLd([
|
|
{ name: "خانه", path: "/" },
|
|
{ name: "بلاگ", path: "/blog" },
|
|
{ name: post.value.title, path: `/blog/${slug}` },
|
|
]);
|
|
|
|
useJsonLd(() => ({
|
|
"@context": "https://schema.org",
|
|
"@type": "BlogPosting",
|
|
headline: post.value?.title,
|
|
description: post.value?.seoDesc || post.value?.excerpt,
|
|
datePublished: post.value?.publishedAt || undefined,
|
|
dateModified: post.value?.updatedAt || post.value?.publishedAt || undefined,
|
|
inLanguage: "fa-IR",
|
|
mainEntityOfPage: canonical.value,
|
|
...(post.value?.coverUrl ? { image: new URL(post.value.coverUrl, siteUrl).toString() } : {}),
|
|
author: { "@id": `${siteUrl}/#organization` },
|
|
publisher: { "@id": `${siteUrl}/#organization` },
|
|
}));
|
|
</script>
|