feat: blog pubblico con filtri, paginazione e articolo
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
---
|
||||
import { getDb } from '../../lib/db';
|
||||
import { listRecent, listArchiveMonths } from '../../lib/posts';
|
||||
import { CATEGORIES } from '../../lib/blog-const';
|
||||
const db = getDb();
|
||||
const recent = listRecent(db, 4);
|
||||
const months = listArchiveMonths(db);
|
||||
const monthLabel = (m: string) => new Intl.DateTimeFormat('it-IT', { month: 'long', year: 'numeric' }).format(new Date(`${m}-01`));
|
||||
---
|
||||
<aside class="bside">
|
||||
<h3>Categorie</h3>
|
||||
{CATEGORIES.map((c) => <p><a href={`/blog?categoria=${encodeURIComponent(c)}`}>{c}</a></p>)}
|
||||
<h3>Ultimi articoli</h3>
|
||||
{recent.map((p) => (
|
||||
<p><a href={`/blog/${p.slug}`}>{p.title}</a></p>
|
||||
))}
|
||||
<h3>Archivio</h3>
|
||||
{months.map((m) => <p class="bside__month">{monthLabel(m.month)} ({m.count})</p>)}
|
||||
</aside>
|
||||
|
||||
<style>
|
||||
.bside h3 { font-size: .85rem; text-transform: uppercase; letter-spacing: .16em; margin-top: 30px; }
|
||||
.bside a { text-decoration: none; color: var(--c-text); }
|
||||
.bside a:hover { color: var(--c-accent-dark); }
|
||||
.bside__month { text-transform: capitalize; }
|
||||
</style>
|
||||
@@ -0,0 +1,6 @@
|
||||
export const CATEGORIES = [
|
||||
'Articoli tecnici & scientifici',
|
||||
'Contenuti educativi',
|
||||
'LinkedIn / Insight',
|
||||
'Eventi & Presidi',
|
||||
] as const;
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
import Base from '../../layouts/Base.astro';
|
||||
import Sidebar from '../../components/blog/Sidebar.astro';
|
||||
import { getDb } from '../../lib/db';
|
||||
import { getPublishedBySlug } from '../../lib/posts';
|
||||
export const prerender = false;
|
||||
|
||||
const post = getPublishedBySlug(getDb(), Astro.params.slug!);
|
||||
if (!post) return Astro.redirect('/404');
|
||||
const fmt = new Intl.DateTimeFormat('it-IT', { dateStyle: 'long' });
|
||||
---
|
||||
<Base title={post.title} description={post.excerpt}>
|
||||
<section class="section">
|
||||
<div class="container art">
|
||||
<article>
|
||||
{post.cover && <img class="art__cover" src={post.cover} alt="" />}
|
||||
<p class="art__meta">{post.published_at && fmt.format(new Date(post.published_at))} · {post.category}</p>
|
||||
<h1>{post.title}</h1>
|
||||
<div class="art__body" set:html={post.body_html} />
|
||||
</article>
|
||||
<Sidebar />
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
|
||||
<style>
|
||||
.art { display: grid; grid-template-columns: 2.4fr 1fr; gap: 60px; }
|
||||
.art__cover { width: 100%; margin-bottom: 24px; }
|
||||
.art__meta { font-size: .8rem; color: var(--c-text-light); }
|
||||
.art__body :global(img) { margin-block: 20px; }
|
||||
.art__body :global(blockquote) { border-left: 3px solid var(--c-accent); margin: 24px 0; padding: 6px 0 6px 20px; font-style: italic; color: var(--c-heading); }
|
||||
@media (max-width: 991px) { .art { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
@@ -0,0 +1,81 @@
|
||||
---
|
||||
import Base from '../../layouts/Base.astro';
|
||||
import PageHero from '../../components/PageHero.astro';
|
||||
import { getDb } from '../../lib/db';
|
||||
import { listPublished } from '../../lib/posts';
|
||||
import { CATEGORIES } from '../../lib/blog-const';
|
||||
export const prerender = false;
|
||||
|
||||
const PER_PAGE = 9;
|
||||
const url = Astro.url;
|
||||
const category = url.searchParams.get('categoria') ?? undefined;
|
||||
const page = Math.max(1, Number(url.searchParams.get('pagina') ?? '1') || 1);
|
||||
const { items, total } = listPublished(getDb(), {
|
||||
category: category && (CATEGORIES as readonly string[]).includes(category) ? category : undefined,
|
||||
page, perPage: PER_PAGE,
|
||||
});
|
||||
const pages = Math.max(1, Math.ceil(total / PER_PAGE));
|
||||
const fmt = (d: string | null) => d ? new Intl.DateTimeFormat('it-IT', { dateStyle: 'long' }).format(new Date(d)) : '';
|
||||
const linkFor = (cat?: string, p = 1) => {
|
||||
const q = new URLSearchParams();
|
||||
if (cat) q.set('categoria', cat);
|
||||
if (p > 1) q.set('pagina', String(p));
|
||||
const s = q.toString();
|
||||
return `/blog${s ? `?${s}` : ''}`;
|
||||
};
|
||||
---
|
||||
<Base title="Blog & Edugo" description="Edugo è lo spazio di InsanityLab dedicato alla conoscenza: articoli, guide e approfondimenti su allenamento, nutrizione e salute.">
|
||||
<PageHero title="Blog & Edugo" />
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="section-heading">
|
||||
<h2>Blog & Edugo</h2>
|
||||
<p>Edugo è lo spazio di InsanityLab dedicato alla conoscenza: articoli, guide e approfondimenti su allenamento, nutrizione e salute per capire il corpo e migliorare il proprio benessere. Perché la conoscenza è il primo passo del cambiamento.</p>
|
||||
</div>
|
||||
<nav class="bfilters" aria-label="categorie">
|
||||
<a class:list={['bfilters__item', { 'is-active': !category }]} href={linkFor()}>Mostra tutto</a>
|
||||
{CATEGORIES.map((c) => (
|
||||
<a class:list={['bfilters__item', { 'is-active': category === c }]} href={linkFor(c)}>{c}</a>
|
||||
))}
|
||||
</nav>
|
||||
{items.length === 0 ? (
|
||||
<p style="text-align:center">Nessun articolo pubblicato{category ? ' in questa categoria' : ''}, torna a trovarci presto.</p>
|
||||
) : (
|
||||
<div class="bgrid">
|
||||
{items.map((post) => (
|
||||
<article class="bcard">
|
||||
{post.cover && <a href={`/blog/${post.slug}`}><img src={post.cover} alt="" loading="lazy" /></a>}
|
||||
<p class="bcard__date">{fmt(post.published_at)}</p>
|
||||
<h3><a href={`/blog/${post.slug}`}>{post.title}</a></h3>
|
||||
<p>{post.excerpt}</p>
|
||||
<a class="bcard__more" href={`/blog/${post.slug}`}>Per saperne di più →</a>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{pages > 1 && (
|
||||
<nav class="bpages" aria-label="paginazione">
|
||||
{Array.from({ length: pages }, (_, i) => (
|
||||
<a class:list={['bpages__n', { 'is-active': page === i + 1 }]} href={linkFor(category, i + 1)}>{i + 1}</a>
|
||||
))}
|
||||
</nav>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
|
||||
<style>
|
||||
.bfilters { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; margin-bottom: 50px; }
|
||||
.bfilters__item { font-family: var(--font-heading); font-size: .7rem; letter-spacing: .16em; text-transform: uppercase; text-decoration: none; color: var(--c-text); padding: 10px 16px; }
|
||||
.bfilters__item.is-active { background: var(--c-accent); color: #fff; }
|
||||
.bgrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 50px 40px; }
|
||||
.bcard img { aspect-ratio: 4/3; object-fit: cover; width: 100%; margin-bottom: 14px; }
|
||||
.bcard__date { font-size: .78rem; color: var(--c-text-light); margin-bottom: 4px; }
|
||||
.bcard h3 a { text-decoration: none; }
|
||||
.bcard__more { font-family: var(--font-heading); font-size: .7rem; font-weight: 600; letter-spacing: .18em; text-transform: uppercase; text-decoration: none; color: var(--c-heading); }
|
||||
.bpages { display: flex; gap: 8px; justify-content: center; margin-top: 50px; }
|
||||
.bpages__n { font-family: var(--font-heading); text-decoration: none; padding: 8px 14px; color: var(--c-text); }
|
||||
.bpages__n.is-active { background: var(--c-dark); color: #fff; }
|
||||
@media (max-width: 991px) { .bgrid { grid-template-columns: 1fr 1fr; } }
|
||||
@media (max-width: 600px) { .bgrid { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
Reference in New Issue
Block a user