Adds table of contents. Removes now from the top level of the sidebar.
This commit is contained in:
@@ -12,7 +12,6 @@ import SocialLinks from './SocialLinks.astro';
|
||||
<SidebarLink href="/">Home</SidebarLink>
|
||||
<SidebarLink href="/blog">Blog</SidebarLink>
|
||||
<SidebarLink href="/about">About</SidebarLink>
|
||||
<SidebarLink href="/now">Now</SidebarLink>
|
||||
<NotesLinks />
|
||||
</div>
|
||||
<div class="social-links">
|
||||
|
||||
31
src/components/TableOfContents.astro
Normal file
31
src/components/TableOfContents.astro
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
import TableOfContentsTree from "./TableOfContentsTree.astro";
|
||||
import type { MarkdownHeading } from "astro";
|
||||
import { makeHeadingsTree, removeEmptyLevels } from "../lib/headings";
|
||||
|
||||
type Props = {
|
||||
headings: Array<MarkdownHeading>;
|
||||
};
|
||||
|
||||
const {headings} = Astro.props;
|
||||
const headingsTree = removeEmptyLevels(makeHeadingsTree(headings));
|
||||
---
|
||||
|
||||
<div class="table-of-contents">
|
||||
<TableOfContentsTree headingsTree={headingsTree}></TableOfContentsTree>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.table-of-contents {
|
||||
border: 1px solid var(--color-space-blue-light);
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
& > ul {
|
||||
padding: 0;
|
||||
margin: 8px 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
15
src/components/TableOfContentsTree.astro
Normal file
15
src/components/TableOfContentsTree.astro
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
type Props = {
|
||||
headingsTree: HeadingsTree;
|
||||
};
|
||||
|
||||
const {headingsTree} = Astro.props;
|
||||
---
|
||||
|
||||
<ul>
|
||||
{headingsTree.map(node =>
|
||||
Array.isArray(node)
|
||||
? <Astro.self headingsTree={node} />
|
||||
: <li><a href={`#${node.slug}`}>{node.text}</a></li>
|
||||
)}
|
||||
</ul>
|
||||
@@ -1,13 +1,15 @@
|
||||
---
|
||||
import type { MarkdownHeading } from "astro";
|
||||
import type { CollectionEntry } from 'astro:content';
|
||||
import RootLayout from '../layouts/RootLayout.astro';
|
||||
import FormattedDate from '../components/FormattedDate.astro';
|
||||
import NotchedBox from '../components/NotchedBox.astro';
|
||||
import TableOfContents from '../components/TableOfContents.astro';
|
||||
import { Image } from 'astro:assets';
|
||||
|
||||
type Props = CollectionEntry<'blog'>['data'] & { slug: string; };
|
||||
type Props = CollectionEntry<'blog'>['data'] & { slug: string; headings: MarkdownHeading[] };
|
||||
|
||||
const { slug, title, description, pubDate, updatedDate, heroImage, tags } = Astro.props;
|
||||
const { slug, title, description, pubDate, updatedDate, heroImage, tags, headings } = Astro.props;
|
||||
---
|
||||
|
||||
<style>
|
||||
@@ -77,6 +79,7 @@ const { slug, title, description, pubDate, updatedDate, heroImage, tags } = Astr
|
||||
</div>
|
||||
}
|
||||
<hr />
|
||||
<TableOfContents headings={headings} />
|
||||
<div class="e-content">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
49
src/lib/headings.ts
Normal file
49
src/lib/headings.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import type { MarkdownHeading } from "astro";
|
||||
|
||||
export type HeadingsTree = Array<MarkdownHeading | HeadingsTree>;
|
||||
|
||||
// could take a list like [5, 1, 2, 2, 3, 5, 1] and should return
|
||||
// [ [ [ [ [ 5 ] ] ] ], 1, [ 2, 2, [3, [[5]]]], 1]
|
||||
export function makeHeadingsTree(headings: MarkdownHeading[]): HeadingsTree {
|
||||
let index = 0;
|
||||
let currDepth = 1;
|
||||
const treeStack = [[]] as Array<HeadingsTree>;
|
||||
|
||||
while (index < headings.length) {
|
||||
const currHeading = headings[index];
|
||||
|
||||
// If the next heading is at the current depth, push it into the tree
|
||||
if (currHeading.depth == currDepth) {
|
||||
treeStack[treeStack.length - 1].push(currHeading);
|
||||
index += 1;
|
||||
}
|
||||
|
||||
// If the next heading is deeper, go deeper
|
||||
if (currHeading.depth > currDepth) {
|
||||
const nextLevel = [] as HeadingsTree;
|
||||
treeStack[treeStack.length - 1].push(nextLevel);
|
||||
treeStack.push(nextLevel);
|
||||
currDepth += 1;
|
||||
}
|
||||
|
||||
// If the next heading is shallower, back out
|
||||
if (currHeading.depth < currDepth) {
|
||||
treeStack.pop();
|
||||
currDepth -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
return treeStack[0];
|
||||
}
|
||||
|
||||
export function removeEmptyLevels(tree: HeadingsTree): HeadingsTree {
|
||||
const newLevel = tree.map((node) =>
|
||||
Array.isArray(node) ? removeEmptyLevels(node) : node,
|
||||
);
|
||||
|
||||
if (tree.every(Array.isArray)) {
|
||||
return tree.flatMap((node) => node);
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
@@ -14,9 +14,9 @@ type Props = CollectionEntry<'blog'>;
|
||||
|
||||
const post = Astro.props;
|
||||
const { slug } = Astro.params;
|
||||
const { Content } = await render(post);
|
||||
const { Content, headings } = await render(post);
|
||||
---
|
||||
|
||||
<BlogPost slug={slug} {...post.data} >
|
||||
<BlogPost slug={slug} headings={headings} {...post.data} >
|
||||
<Content />
|
||||
</BlogPost>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import RootLayout from '../../layouts/RootLayout.astro';
|
||||
import NotchedBox from '../../components/NotchedBox.astro';
|
||||
import FormattedDate from '../../components/FormattedDate.astro';
|
||||
import TableOfContents from '../../components/TableOfContents.astro';
|
||||
import { type CollectionEntry, getCollection } from 'astro:content';
|
||||
import { render } from 'astro:content';
|
||||
|
||||
@@ -15,7 +16,7 @@ export async function getStaticPaths() {
|
||||
type Props = CollectionEntry<'notes'>;
|
||||
|
||||
const note = Astro.props;
|
||||
const { Content } = await render(note);
|
||||
const { Content, headings } = await render(note);
|
||||
---
|
||||
|
||||
<RootLayout>
|
||||
@@ -24,6 +25,7 @@ const { Content } = await render(note);
|
||||
<div class="last-updated">
|
||||
Last updated <FormattedDate date={note.data.updated} />
|
||||
</div>
|
||||
<TableOfContents headings={headings}/>
|
||||
<Content />
|
||||
</NotchedBox>
|
||||
</RootLayout>
|
||||
|
||||
Reference in New Issue
Block a user