feat: implement article detail pages with SEO and layout components

This commit is contained in:
Esteban Paz 2026-05-07 21:08:46 -05:00
parent aa54417618
commit 6cea6fe48b
7 changed files with 86 additions and 22 deletions

View File

@ -7,6 +7,7 @@ const {
description = "",
image = null,
url = null,
date = null,
} = Astro.props;
const imageUrl = image ? new URL(image, Astro.site).toString() : null;
@ -35,6 +36,8 @@ const imageUrl = image ? new URL(image, Astro.site).toString() : null;
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
{date && <meta property="article:published_time" content={date instanceof Date ? date.toISOString() : date} />}
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />

View File

@ -15,27 +15,27 @@
<ul class="flex flex-col gap-4 bg-white/90 backdrop-blur-md py-4 px-4 rounded-r-2xl shadow-xl border border-gray-200">
<li class="border-b pb-3">
<a :href="twitterUrl" target="_blank">
<Icon icon="ph:x-logo-thin" class="text-2xl" />
<Icon icon="ph:x-logo-thin" class="text-2xl text-black" />
</a>
</li>
<li class="border-b pb-3">
<a :href="facebookUrl" target="_blank">
<Icon icon="ph:facebook-logo-thin" class="text-2xl" />
<Icon icon="ph:facebook-logo-thin" class="text-2xl text-black" />
</a>
</li>
<li class="border-b pb-3">
<a :href="whatsappUrl" target="_blank">
<Icon icon="ph:whatsapp-logo-thin" class="text-2xl" />
<Icon icon="ph:whatsapp-logo-thin" class="text-2xl text-black" />
</a>
</li>
<li class="border-b pb-3">
<a :href="linkedinUrl" target="_blank">
<Icon icon="ph:linkedin-logo-thin" class="text-2xl" />
<Icon icon="ph:linkedin-logo-thin" class="text-2xl text-black" />
</a>
</li>
<li>
<button @click="copyLink" class="cursor-pointer">
<Icon :icon="copied ? 'ph:check-thin' : 'ph:link-thin'" class="text-2xl" />
<Icon :icon="copied ? 'ph:check-thin' : 'ph:link-thin'" class="text-2xl text-black" />
</button>
</li>
</ul>

View File

@ -6,7 +6,7 @@ const { title } = Astro.props;
<div class="md:py-16 p-4 bg-white">
<div class="container mx-auto">
<div class="flex justify-between px-4">
<h2 class="text-tertiary font-secondary text-xl sm:text-2xl md:text-3xl lg:text-5xl font-bold">{title}</h2>
<h2 id="article-title" class="text-tertiary font-secondary text-xl sm:text-2xl md:text-3xl lg:text-5xl font-bold">{title}</h2>
<img class="md:w-20 md:h-20 w-10 h-10" src="/img/lion.svg" alt="Leon">
</div>
</div>

View File

@ -13,7 +13,8 @@ const {
title,
description,
image,
url
url,
date
} = Astro.props;
const currentLocale = Astro.currentLocale ?? 'es';
@ -29,6 +30,7 @@ const isNewsPage = newsSegments.some(segment => Astro.url.pathname.includes(`/${
description={description}
image={image}
url={url}
date={date}
/>
<script>
document.addEventListener('contextmenu', (event) => {

View File

@ -47,14 +47,41 @@ const words = plainText
.filter((w) => w.length > 0)
.slice(0, 35);
const excerpt = words.join(" ") + (words.length === 35 ? "..." : "");
const canonicalUrl = new URL(`/${locale}/${news_slug}/${post.id}`, Astro.site);
const imageUrl = post.data.thumbnail
? new URL(post.data.thumbnail, Astro.site).toString()
: null;
const localeDate = new Intl.DateTimeFormat(locale || "es", {
year: "numeric",
month: "long",
day: "numeric",
}).format(post.data.date);
---
<MainLayout
title={post.data.title}
description={excerpt}
image={post.data.thumbnail}
url={new URL(`/${locale}/${news_slug}/${post.id}`, Astro.site)}
url={canonicalUrl}
date={post.data.date}
>
<script
type="application/ld+json"
set:html={JSON.stringify({
"@context": "https://schema.org",
"@type": "NewsArticle",
headline: post.data.title,
datePublished: post.data.date,
description: excerpt,
image: imageUrl,
url: canonicalUrl.toString(),
author: {
"@type": "Organization",
name: "Centro del Reino de Paz y Justicia",
},
})}
></script>
<div class="container mx-auto md:py-16 py-8">
<Header />
</div>
@ -78,14 +105,21 @@ const excerpt = words.join(" ") + (words.length === 35 ? "..." : "");
}
</a>
</div>
<div class="grid md:grid-cols-10">
<div class="grid md:grid-cols-10 container mx-auto">
<div
id="article-content"
class="md:col-span-7 content bg-white p-8 md:p-20 prose-p:mb-4 text-[#003421] text-justify"
>
<!-- <p class="text-lg font-semibold text-tertiary mb-8 pb-6 border-b border-tertiary/20 italic">
{excerpt}
</p> -->
<Content />
<article id="article-body">
<time
id="article-date"
class="text-center text-[#003421]/60 text-sm hidden"
datetime={post.data.date.toISOString()}
>
{localeDate}
</time>
<Content />
</article>
</div>
<div class="md:col-span-3 bg-tertiary md:sticky top-0 h-fit">
{post.data.youtube && <YouTube id={post.data.youtube} />}

View File

@ -29,6 +29,8 @@ const baseUrl = Astro.site ?? "https://mk8nrc8p-4321.brs.devtunnels.ms";
const pageUrl = new URL(`/${post.data.locale}/${news_slug}/${post.id}`, baseUrl).toString();
const imageUrl = post.data.thumbnail ? new URL(post.data.thumbnail, baseUrl).toString() : null;
const localeDate = new Intl.DateTimeFormat(post.data.locale || 'es', { year: 'numeric', month: 'long', day: 'numeric' }).format(post.data.date);
---
@ -39,7 +41,10 @@ const pageUrl = new URL(`/${post.data.locale}/${news_slug}/${post.id}`, baseUrl)
}
</style>
<MainLayout>
<MainLayout
title={post.data.title}
date={post.data.date}
>
<Fragment slot="head">
<!-- Título -->
<title>{post.data.title}</title>
@ -60,18 +65,37 @@ const pageUrl = new URL(`/${post.data.locale}/${news_slug}/${post.id}`, baseUrl)
<meta property="og:image:type" content={`image/${post.data.gallery[0].image.format}`} />
</>
)}
<meta property="article:published_time" content={post.data.date.toISOString()} />
</Fragment>
<script type="application/ld+json" set:html={JSON.stringify({
"@context": "https://schema.org",
"@type": "NewsArticle",
"headline": post.data.title,
"datePublished": post.data.date,
"image": imageUrl,
"url": pageUrl,
"author": {
"@type": "Organization",
"name": "Centro del Reino de Paz y Justicia"
}
})} />
<div class="container mx-auto py-16">
<Header />
</div>
<TitleSection title={post.data.title} />
<time id="article-date" class="block text-center text-[#003421]/60 text-sm mt-4 mb-8" datetime={post.data.date.toISOString()}>
{localeDate}
</time>
<div class="container mx-auto">
{post.data.gallery && <CarouselSection images={post.data.gallery} />}
<div class="grid md:grid-cols-10">
<div class="md:col-span-7 content bg-white p-8 md:p-20 prose-p:mb-4 text-[#003421]">
<Content />
<div id="article-content" class="md:col-span-7 content bg-white p-8 md:p-20 prose-p:mb-4 text-[#003421]">
<article id="article-body">
<Content />
</article>
</div>
<div class="md:col-span-3 bg-tertiary md:sticky top-0 h-fit">
{ post.data.youtube && (

View File

@ -24,24 +24,24 @@ body {
background-color: var(--background);
}
.content h2{
.content h2 {
font-size: 22px;
margin-bottom: 10px;
line-height: 100%;
font-weight: 700;
}
.content h3{
font-size: 20px;
.content h3 {
font-size: 19px;
margin-bottom: 10px;
line-height: 100%;
font-weight: 700;
}
.content h1{
.content h1 {
font-size: 32px;
margin-bottom: 30px;
line-height: 100%;
line-height: 150%;
font-weight: 700;
text-align: left;
font-family: var(--font-secondary);
@ -60,6 +60,7 @@ body {
font-size: 22px;
margin-bottom: 20px;
line-height: 120%;
text-align: left; /* puedes cambiar a center si quieres */
text-align: left;
/* puedes cambiar a center si quieres */
}
}