- 6 pages (o-nas, nashi-druzya, 4 миры) получили свой description в frontmatter; раньше [slug].astro для типа page передавал description=undefined → fallback на SITE_DESCRIPTION даёт 6 одинаковых meta-description. - [slug].astro: для постов в SEO-title подставляется год публикации (или полная дата, если есть второй пост с тем же title в том же году). Покрывает дубли «Внимание! Технические работы!» (×5) и «С 23 февраля!» (×2). - index.astro: свой description для главной (вместо SITE_DESCRIPTION). - category/[slug].astro: вычисляемый description с количеством публикаций и диапазоном лет — на случай если категории попадут в индексацию.
80 lines
2.9 KiB
Plaintext
80 lines
2.9 KiB
Plaintext
---
|
||
import { getCollection, render } from 'astro:content';
|
||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||
import { CATEGORY_COLORS } from '../consts';
|
||
|
||
export async function getStaticPaths() {
|
||
const posts = await getCollection('posts');
|
||
const pages = await getCollection('pages');
|
||
|
||
// Считаем сколько постов с одинаковым {title, year} — если >1, в SEO-title
|
||
// подставим полную дату, иначе только год.
|
||
const titleYearCount = new Map<string, number>();
|
||
for (const p of posts) {
|
||
const key = `${p.data.title}|${p.data.pubDate.getFullYear()}`;
|
||
titleYearCount.set(key, (titleYearCount.get(key) ?? 0) + 1);
|
||
}
|
||
|
||
return [
|
||
...posts.map((p) => {
|
||
const year = p.data.pubDate.getFullYear();
|
||
const key = `${p.data.title}|${year}`;
|
||
const sameYearDup = (titleYearCount.get(key) ?? 1) > 1;
|
||
return { params: { slug: p.data.slug }, props: { entry: p, kind: 'post' as const, sameYearDup } };
|
||
}),
|
||
...pages.map((p) => ({ params: { slug: p.data.slug }, props: { entry: p, kind: 'page' as const, sameYearDup: false } })),
|
||
];
|
||
}
|
||
|
||
const { entry, kind, sameYearDup } = Astro.props;
|
||
const { Content } = await render(entry);
|
||
// Буквица — только для постов с телом длиннее короткого порога.
|
||
const bodyLen = entry.body?.length ?? 0;
|
||
const useDropCap = kind === 'post' && bodyLen > 240;
|
||
|
||
const fmtDate = (d: Date) =>
|
||
d.toLocaleDateString('ru-RU', { year: 'numeric', month: 'long', day: 'numeric' });
|
||
|
||
// Короткая дата без "г." для SEO-title постов с одинаковым названием в одном году.
|
||
const fmtDateForTitle = (d: Date) =>
|
||
d.toLocaleDateString('ru-RU', { year: 'numeric', month: 'long', day: 'numeric' }).replace(/\sг\.?$/, '');
|
||
|
||
const seoTitle = kind === 'post'
|
||
? `${entry.data.title} (${sameYearDup ? fmtDateForTitle(entry.data.pubDate) : entry.data.pubDate.getFullYear()})`
|
||
: entry.data.title;
|
||
---
|
||
<BaseLayout
|
||
title={seoTitle}
|
||
description={entry.data.description || undefined}
|
||
ogType={kind === 'post' ? 'article' : 'website'}
|
||
>
|
||
<article class="post">
|
||
<header>
|
||
{kind === 'post' && entry.data.categories.length > 0 && (
|
||
<a
|
||
href={`/category/${entry.data.categorySlugs[0]}/`}
|
||
class="cat-tag"
|
||
style={`--cat-color: ${CATEGORY_COLORS[entry.data.categorySlugs[0]] ?? 'var(--accent)'}`}
|
||
>
|
||
{entry.data.categories[0]}
|
||
</a>
|
||
)}
|
||
<h1>{entry.data.title}</h1>
|
||
{kind === 'post' && (
|
||
<div class="post-meta">
|
||
<time datetime={entry.data.pubDate.toISOString()}>{fmtDate(entry.data.pubDate)}</time>
|
||
</div>
|
||
)}
|
||
</header>
|
||
<div class={`post-body ${useDropCap ? 'has-dropcap' : ''}`}>
|
||
<Content />
|
||
</div>
|
||
</article>
|
||
|
||
{kind === 'post' && (
|
||
<nav class="pagination">
|
||
<a href="/">← Вернуться в хронику</a>
|
||
</nav>
|
||
)}
|
||
</BaseLayout>
|