Skip to content

Commit a8a2436

Browse files
added post count on home page, added blog filter by month/year, added dynamic TOC, added scroll to top
1 parent 2879b72 commit a8a2436

4 files changed

Lines changed: 340 additions & 34 deletions

File tree

src/layouts/Layout.astro

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,56 @@ const { title, description = "Just a dude who enjoys writing code & learning;" }
4141
</head>
4242
<body class="min-h-screen flex flex-col">
4343
<Header />
44-
<main class="flex-grow pt-20 px-4 sm:px-6 lg:px-8 w-full max-w-4xl mx-auto">
44+
<main data-site-main class="flex-grow pt-20 px-4 sm:px-6 lg:px-8 w-full max-w-4xl mx-auto">
4545
<slot />
4646
</main>
47+
<button
48+
type="button"
49+
aria-label="Scroll to top"
50+
data-scroll-top
51+
style="right: 1rem;"
52+
class="fixed bottom-6 z-40 rounded-full border border-blog-border bg-blog-code-bg/80 px-3 py-2 text-blog-accent shadow-sm transition-all duration-200 hover:text-blog-link hover:border-blog-accent opacity-0 translate-y-2 pointer-events-none cursor-pointer"
53+
>
54+
Top
55+
</button>
4756
<Footer />
57+
<script is:inline>
58+
(() => {
59+
const wireScrollTop = () => {
60+
const button = document.querySelector('[data-scroll-top]');
61+
const main = document.querySelector('[data-site-main]');
62+
if (!button || !main || button.dataset.scrollTopReady === 'true') return;
63+
button.dataset.scrollTopReady = 'true';
64+
65+
const threshold = 320;
66+
const updateOffset = () => {
67+
const rect = main.getBoundingClientRect();
68+
const minRight = 16;
69+
const insetFromContentEdge = -50;
70+
const nextRight = Math.max(minRight, window.innerWidth - rect.right + insetFromContentEdge);
71+
button.style.right = `${nextRight}px`;
72+
};
73+
74+
const updateVisibility = () => {
75+
const shouldShow = window.scrollY > threshold;
76+
button.classList.toggle('opacity-0', !shouldShow);
77+
button.classList.toggle('translate-y-2', !shouldShow);
78+
button.classList.toggle('pointer-events-none', !shouldShow);
79+
};
80+
81+
button.addEventListener('click', () => {
82+
window.scrollTo({ top: 0, behavior: 'smooth' });
83+
});
84+
85+
window.addEventListener('scroll', updateVisibility, { passive: true });
86+
window.addEventListener('resize', updateOffset);
87+
updateOffset();
88+
updateVisibility();
89+
};
90+
91+
wireScrollTop();
92+
document.addEventListener('astro:page-load', wireScrollTop);
93+
})();
94+
</script>
4895
</body>
4996
</html>

src/pages/blog/[...slug].astro

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,35 +13,57 @@ export async function getStaticPaths() {
1313
type Props = CollectionEntry<'blog'>;
1414
1515
const post = Astro.props;
16-
const { Content } = await post.render();
16+
const { Content, headings } = await post.render();
17+
const tocHeadings = headings.filter((heading) => heading.depth >= 2 && heading.depth <= 3);
1718
---
1819

1920
<PostHogLayout>
2021
<Layout title={post.data.title} description={post.data.description}>
21-
<article class="py-10 max-w-3xl mx-auto">
22-
<div class="mb-10 text-center">
23-
<div class="mb-4 text-blog-text">
24-
<time datetime={post.data.pubDate.toISOString()}>
25-
{post.data.pubDate.toLocaleDateString('en-us', {
26-
year: 'numeric',
27-
month: 'long',
28-
day: 'numeric',
29-
})}
30-
</time>
31-
</div>
32-
33-
<h1 class="text-3xl md:text-5xl font-bold mb-6 text-blog-accent leading-tight">
34-
{post.data.title}
35-
</h1>
36-
<h2>{post.data.description}</h2>
37-
</div>
22+
<section class="py-10 max-w-3xl mx-auto">
23+
<article>
24+
<div class="mb-10 text-center">
25+
<div class="mb-4 text-blog-text">
26+
<time datetime={post.data.pubDate.toISOString()}>
27+
{post.data.pubDate.toLocaleDateString('en-us', {
28+
year: 'numeric',
29+
month: 'long',
30+
day: 'numeric',
31+
})}
32+
</time>
33+
</div>
3834

39-
<div class="prose prose-invert prose-lg max-w-none
40-
prose-headings:font-bold prose-headings:tracking-tight
41-
prose-a:text-blog-link prose-a:no-underline hover:prose-a:no-underline
42-
prose-img:rounded-xl prose-img:shadow-md">
43-
<Content />
44-
</div>
45-
</article>
35+
<h1 class="text-3xl md:text-5xl font-bold mb-6 text-blog-accent leading-tight">
36+
{post.data.title}
37+
</h1>
38+
<h2>{post.data.description}</h2>
39+
</div>
40+
41+
{tocHeadings.length > 0 && (
42+
<details class="mb-8 rounded-2xl border border-blog-border bg-blog-code-bg/60 p-4">
43+
<summary class="cursor-pointer text-sm font-semibold uppercase tracking-wide text-blog-accent">
44+
Table of Contents
45+
</summary>
46+
<nav class="mt-4">
47+
<ul class="space-y-2 text-sm text-blog-text">
48+
{tocHeadings.map((heading) => (
49+
<li class:list={[heading.depth === 3 ? 'pl-4' : 'pl-0']}>
50+
<a href={`#${heading.slug}`} class="text-blog-text hover:text-blog-link transition-colors">
51+
{heading.text}
52+
</a>
53+
</li>
54+
))}
55+
</ul>
56+
</nav>
57+
</details>
58+
)}
59+
60+
<div class="prose prose-invert prose-lg max-w-none
61+
prose-headings:font-bold prose-headings:tracking-tight
62+
prose-a:text-blog-link prose-a:no-underline hover:prose-a:no-underline
63+
prose-img:rounded-xl prose-img:shadow-md">
64+
<Content />
65+
</div>
66+
</article>
67+
</section>
4668
</Layout>
47-
</PostHogLayout>
69+
</PostHogLayout>

0 commit comments

Comments
 (0)