Footer quotes + right-hand table of contents
- two static quotes above the name line in every footer - Toc component renders h1-h3 from Astro's render() headings as a sticky right column on >=88rem screens (hidden below); scroll-position highlighting via a small passive listener; shown only when a page has 2+ headings, so listings and short notes stay clean Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,47 @@
|
|||||||
|
---
|
||||||
|
interface Props {
|
||||||
|
headings: { depth: number; slug: string; text: string }[];
|
||||||
|
}
|
||||||
|
const { headings } = Astro.props;
|
||||||
|
const shown = headings.filter((h) => h.depth <= 3);
|
||||||
|
const min = Math.min(...shown.map((h) => h.depth));
|
||||||
|
---
|
||||||
|
|
||||||
|
<nav class="toc">
|
||||||
|
<p class="toc-title">On this page</p>
|
||||||
|
<ul>
|
||||||
|
{
|
||||||
|
shown.map((h) => (
|
||||||
|
<li class={`toc-d${h.depth - min}`}>
|
||||||
|
<a href={'#' + h.slug}>{h.text}</a>
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// highlight the section currently in view
|
||||||
|
const toc = document.querySelector('.toc');
|
||||||
|
if (toc) {
|
||||||
|
const links = [...toc.querySelectorAll('a[href^="#"]')] as HTMLAnchorElement[];
|
||||||
|
const heads = links
|
||||||
|
.map((a) => ({
|
||||||
|
a,
|
||||||
|
el: document.getElementById(decodeURIComponent(a.hash.slice(1))),
|
||||||
|
}))
|
||||||
|
.filter((x) => x.el);
|
||||||
|
const update = () => {
|
||||||
|
let cur = heads[0];
|
||||||
|
for (const h of heads) {
|
||||||
|
if (h.el!.getBoundingClientRect().top <= 110) cur = h;
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
heads.forEach((h) => h.a.classList.toggle('toc-active', h === cur));
|
||||||
|
};
|
||||||
|
if (heads.length) {
|
||||||
|
document.addEventListener('scroll', update, { passive: true });
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
+83
-1
@@ -3,14 +3,17 @@ import 'katex/dist/katex.min.css';
|
|||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from 'astro:content';
|
||||||
import { buildNavTree } from '../lib/nav.mjs';
|
import { buildNavTree } from '../lib/nav.mjs';
|
||||||
import NavTree from '../components/NavTree.astro';
|
import NavTree from '../components/NavTree.astro';
|
||||||
|
import Toc from '../components/Toc.astro';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
headings?: { depth: number; slug: string; text: string }[];
|
||||||
}
|
}
|
||||||
const { title, description } = Astro.props;
|
const { title, description, headings = [] } = Astro.props;
|
||||||
const isHome = Astro.url.pathname === '/';
|
const isHome = Astro.url.pathname === '/';
|
||||||
const navTree = buildNavTree(await getCollection('garden'));
|
const navTree = buildNavTree(await getCollection('garden'));
|
||||||
|
const showToc = headings.filter((h) => h.depth <= 3).length >= 2;
|
||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
@@ -35,6 +38,13 @@ const navTree = buildNavTree(await getCollection('garden'));
|
|||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
<footer>
|
<footer>
|
||||||
|
<div class="footer-quotes">
|
||||||
|
<p>
|
||||||
|
Failure is unique among sin, in that we can turn it into a
|
||||||
|
virtue when it drives us to succeed.
|
||||||
|
</p>
|
||||||
|
<p>The living diminish, but the machine endures...</p>
|
||||||
|
</div>
|
||||||
<p>
|
<p>
|
||||||
Wesley Ray · <a href="https://blog.c0smere.net">blog</a> ·
|
Wesley Ray · <a href="https://blog.c0smere.net">blog</a> ·
|
||||||
<a href="https://git.c0smere.net/wes">git</a> ·
|
<a href="https://git.c0smere.net/wes">git</a> ·
|
||||||
@@ -53,6 +63,13 @@ const navTree = buildNavTree(await getCollection('garden'));
|
|||||||
<p class="sidebar-title"><a href="/">the garden</a></p>
|
<p class="sidebar-title"><a href="/">the garden</a></p>
|
||||||
<NavTree nodes={navTree} currentPath={Astro.url.pathname} />
|
<NavTree nodes={navTree} currentPath={Astro.url.pathname} />
|
||||||
</aside>
|
</aside>
|
||||||
|
{
|
||||||
|
showToc && (
|
||||||
|
<aside class="toc-col">
|
||||||
|
<Toc headings={headings} />
|
||||||
|
</aside>
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -164,6 +181,10 @@ const navTree = buildNavTree(await getCollection('garden'));
|
|||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
border-top: 1px solid var(--faint);
|
border-top: 1px solid var(--faint);
|
||||||
}
|
}
|
||||||
|
/* TOC only exists as a right column on wide screens */
|
||||||
|
.toc-col {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
@media (min-width: 72rem) {
|
@media (min-width: 72rem) {
|
||||||
.layout {
|
.layout {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -184,6 +205,60 @@ const navTree = buildNavTree(await getCollection('garden'));
|
|||||||
padding: 1.5rem 0.5rem 1.5rem 0;
|
padding: 1.5rem 0.5rem 1.5rem 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@media (min-width: 88rem) {
|
||||||
|
.layout {
|
||||||
|
grid-template-columns: 17rem minmax(0, 44rem) 13rem;
|
||||||
|
max-width: 80rem;
|
||||||
|
}
|
||||||
|
.toc-col {
|
||||||
|
display: block;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
align-self: start;
|
||||||
|
max-height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 1.5rem 0 1.5rem 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toc-title {
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
.toc ul {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
border-left: 1px solid var(--faint);
|
||||||
|
}
|
||||||
|
.toc li {
|
||||||
|
margin: 0.2rem 0;
|
||||||
|
}
|
||||||
|
.toc li a {
|
||||||
|
display: block;
|
||||||
|
padding-left: 0.75rem;
|
||||||
|
color: var(--muted);
|
||||||
|
text-decoration: none;
|
||||||
|
border-left: 2px solid transparent;
|
||||||
|
margin-left: -1px;
|
||||||
|
}
|
||||||
|
.toc li.toc-d1 a {
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
}
|
||||||
|
.toc li.toc-d2 a {
|
||||||
|
padding-left: 2.25rem;
|
||||||
|
}
|
||||||
|
.toc li a:hover {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
.toc li a.toc-active {
|
||||||
|
color: var(--accent);
|
||||||
|
border-left-color: var(--accent);
|
||||||
|
}
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
scroll-margin-top: 1rem;
|
||||||
|
}
|
||||||
.sidebar-title {
|
.sidebar-title {
|
||||||
margin: 0 0 0.5rem;
|
margin: 0 0 0.5rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -253,6 +328,13 @@ const navTree = buildNavTree(await getCollection('garden'));
|
|||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
|
.footer-quotes {
|
||||||
|
font-style: italic;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
.footer-quotes p {
|
||||||
|
margin: 0.25rem 0;
|
||||||
|
}
|
||||||
.odo-display {
|
.odo-display {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
@@ -59,10 +59,15 @@ const { entry, listing } = Astro.props;
|
|||||||
const title = entry
|
const title = entry
|
||||||
? (entry.data.title ?? entry.id.split('/').pop())
|
? (entry.data.title ?? entry.id.split('/').pop())
|
||||||
: listing.name;
|
: listing.name;
|
||||||
const Content = entry ? (await render(entry)).Content : null;
|
const rendered = entry ? await render(entry) : null;
|
||||||
|
const Content = rendered?.Content ?? null;
|
||||||
---
|
---
|
||||||
|
|
||||||
<Base title={title} description={entry?.data.description}>
|
<Base
|
||||||
|
title={title}
|
||||||
|
description={entry?.data.description}
|
||||||
|
headings={rendered?.headings ?? []}
|
||||||
|
>
|
||||||
<h1>{title}</h1>
|
<h1>{title}</h1>
|
||||||
{Content && <Content />}
|
{Content && <Content />}
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,10 +4,15 @@ import Base from '../layouts/Base.astro';
|
|||||||
|
|
||||||
const entries = await getCollection('garden');
|
const entries = await getCollection('garden');
|
||||||
const home = entries.find((e) => e.id === 'index');
|
const home = entries.find((e) => e.id === 'index');
|
||||||
const Content = home ? (await render(home)).Content : null;
|
const rendered = home ? await render(home) : null;
|
||||||
|
const Content = rendered?.Content ?? null;
|
||||||
const title = home?.data.title ?? 'Wesley Ray';
|
const title = home?.data.title ?? 'Wesley Ray';
|
||||||
---
|
---
|
||||||
|
|
||||||
<Base title={title} description={home?.data.description}>
|
<Base
|
||||||
|
title={title}
|
||||||
|
description={home?.data.description}
|
||||||
|
headings={rendered?.headings ?? []}
|
||||||
|
>
|
||||||
{Content && <Content />}
|
{Content && <Content />}
|
||||||
</Base>
|
</Base>
|
||||||
|
|||||||
Reference in New Issue
Block a user