Files
zeropost-web/app/archive/page.js
T
Alexey Pavlov 20b67f11e0 feat: TOC оглавление + SVG-обложки-фоллбеки + /archive
TOC:
- renderMarkdownWithToc: парсит h2/h3, генерит транслит-якоря для кириллицы, возвращает {html, toc}
- TableOfContents компонент: sticky на десктопе, раскрывающийся блок на мобиле
- IntersectionObserver-free подсветка активной секции через scroll listener
- Двухколоночный layout статьи на lg+: 240px TOC + контент

SVG-обложки:
- ArticleCoverSVG: процедурно сгенерированная композиция (curve/circle/arc/rect) по seed = id статьи
- 6 палитр на выбор (emerald/teal/yellow/blue/purple/orange), seed детерминированный
- Используется в ArticleCard как fallback когда cover_url пусто
- На странице статьи тоже SVG если обложки нет
- Тег статьи отображается лейблом в углу

Архив:
- /archive: все статьи сгруппированы по месяцам, компактный список
- В Header добавлен пункт Архив (desktop+mobile)
- В Footer ссылки на Архив, Заметки, О проекте
- В sitemap.xml включён /archive
2026-05-31 10:54:34 +03:00

91 lines
3.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Link from 'next/link';
import Header from '@/components/Header';
import Footer from '@/components/Footer';
import { listArticles } from '@/lib/engine';
import { formatDate } from '@/lib/markdown';
import { Archive, Clock } from 'lucide-react';
export const dynamic = 'force-dynamic';
export const metadata = { title: 'Архив статей' };
const MONTHS = [
'Январь','Февраль','Март','Апрель','Май','Июнь',
'Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь',
];
function groupByMonth(articles) {
const groups = new Map();
for (const a of articles) {
const d = new Date(a.published_at);
const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
if (!groups.has(key)) groups.set(key, { year: d.getFullYear(), month: d.getMonth(), items: [] });
groups.get(key).items.push(a);
}
return Array.from(groups.entries())
.sort(([a], [b]) => b.localeCompare(a))
.map(([, v]) => v);
}
export default async function ArchivePage() {
const articles = await listArticles({ limit: 500 });
const groups = groupByMonth(articles);
return (
<>
<Header />
<main className="container-wide pt-10 pb-16">
<div
className="inline-flex items-center gap-2 text-xs accent px-3 py-1.5 rounded-full mb-4"
style={{ background: 'rgb(var(--accent) / 0.1)', border: '1px solid rgb(var(--accent) / 0.2)' }}
>
<Archive className="w-3.5 h-3.5" /> Архив
</div>
<h1 className="text-3xl sm:text-5xl font-bold ink mb-3 leading-tight">
Все статьи
</h1>
<p className="mute text-base sm:text-lg mb-10 max-w-2xl">
Полный архив. {articles.length} {articles.length === 1 ? 'материал' : 'материалов'} с момента запуска.
</p>
{articles.length === 0 && (
<p className="mute">Архив пока пуст.</p>
)}
<div className="space-y-10 sm:space-y-12">
{groups.map(g => (
<section key={`${g.year}-${g.month}`}>
<h2 className="text-xs sm:text-sm font-medium uppercase tracking-widest mute mb-4 sm:mb-5 pb-2 border-b-soft">
{MONTHS[g.month]} {g.year}
<span className="mute ml-2 normal-case tracking-normal">· {g.items.length}</span>
</h2>
<ul className="divide-y divide-[rgb(var(--border))]">
{g.items.map(a => (
<li key={a.id}>
<Link
href={`/blog/${a.slug}`}
className="group flex items-baseline gap-3 sm:gap-4 py-3 sm:py-4 -mx-2 px-2 hover:surface-2 rounded-lg transition-colors"
>
<span className="text-xs mute tabular-nums w-12 flex-shrink-0 mt-0.5">
{new Date(a.published_at).getDate().toString().padStart(2, '0')}.{(new Date(a.published_at).getMonth() + 1).toString().padStart(2, '0')}
</span>
<span className="flex-1 ink group-hover:accent transition-colors text-sm sm:text-base font-medium leading-snug">
{a.title}
</span>
{a.reading_time && (
<span className="hidden sm:inline-flex items-center gap-1 text-xs mute flex-shrink-0">
<Clock className="w-3 h-3" /> {a.reading_time} мин
</span>
)}
</Link>
</li>
))}
</ul>
</section>
))}
</div>
</main>
<Footer />
</>
);
}