Skip to content

Commit

Permalink
Merge pull request #116 from chrismwilliams/fix/toc-order
Browse files Browse the repository at this point in the history
  • Loading branch information
chrismwilliams authored Aug 30, 2023
2 parents 9502aa3 + 5e818fb commit 523e871
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 24 deletions.
21 changes: 21 additions & 0 deletions src/components/blog/TOC.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
import type { MarkdownHeading } from "astro";
import { generateToc } from "src/utils/generateToc";
import TOCHeading from "./TOCHeading.astro";
interface Props {
headings: Array<MarkdownHeading>;
}
const { headings } = Astro.props;
const toc = generateToc(headings);
---

<aside class="sticky top-20 order-2 -me-32 hidden basis-64 lg:block">
<h2 class="font-semibold">Table of Contents</h2>
<ul class="mt-4 text-xs">
{toc.map((heading) => <TOCHeading heading={heading} />)}
</ul>
</aside>
28 changes: 28 additions & 0 deletions src/components/blog/TOCHeading.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
import type { TocItem } from "@/utils";
interface Props {
heading: TocItem;
}
const {
heading: { slug, text, depth, subheadings },
} = Astro.props;
---

<li class={`${depth > 2 ? "ms-2" : ""}`}>
<a
class={`block line-clamp-2 hover:text-accent ${depth <= 2 ? "mt-3" : "mt-2 text-[0.6875rem]"}`}
href={`#${slug}`}
aria-label={`Scroll to section: ${text}`}><span class="me-0.5">#</span>{text}</a
>
{
!!subheadings.length && (
<ul>
{subheadings.map((subheading) => (
<Astro.self heading={subheading} />
))}
</ul>
)
}
</li>
2 changes: 0 additions & 2 deletions src/content/post/markdown-elements/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ _This is italic text_
> Blockquotes can also be nested...
>
> > ...by using additional greater-than signs right next to each other...
> >
> > > ...or with spaces between arrows.
## References

Expand Down
25 changes: 3 additions & 22 deletions src/layouts/BlogPost.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { CollectionEntry } from "astro:content";
import BaseLayout from "./Base.astro";
import BlogHero from "@/components/blog/Hero";
import TOC from "@/components/blog/TOC";
interface Props {
post: CollectionEntry<"post">;
Expand Down Expand Up @@ -39,31 +40,11 @@ const { headings } = await post.render();

<BaseLayout meta={{ title, description, articleDate, ogImage: socialImage }}>
<div class="gap-x-10 lg:flex lg:items-start">
{
!!headings.length && (
<aside class="sticky top-20 order-2 -me-32 hidden basis-64 lg:block">
<h2 class="font-semibold">Table of Contents</h2>
<ul class="mt-4 text-xs">
{headings.map(({ depth, slug, text }) => (
<li class="line-clamp-2 hover:text-accent">
<a
class={`block ${depth <= 2 ? "mt-3" : "mt-2 ps-3 text-[0.6875rem]"}`}
href={`#${slug}`}
aria-label={`Scroll to section: ${text}`}
>
<span class="me-1">{"#".repeat(depth)}</span>
{text}
</a>
</li>
))}
</ul>
</aside>
)
}
{!!headings.length && <TOC headings={headings} />}
<article class="flex-grow break-words" data-pagefind-body>
<div id="blog-hero"><BlogHero content={post} /></div>
<div
class="prose prose-sm prose-cactus mt-12 prose-headings:font-semibold prose-headings:before:absolute prose-headings:before:-ms-4 prose-headings:before:text-accent prose-headings:before:content-['#'] prose-th:before:content-none"
class="prose prose-sm prose-cactus mt-12 prose-headings:font-semibold prose-headings:text-accent-2 prose-headings:before:absolute prose-headings:before:-ms-4 prose-headings:before:text-accent prose-headings:before:content-['#'] prose-th:before:content-none"
>
<slot />
</div>
Expand Down
41 changes: 41 additions & 0 deletions src/utils/generateToc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { MarkdownHeading } from "astro";

export interface TocItem extends MarkdownHeading {
subheadings: Array<TocItem>;
}

function diveChildren(item: TocItem, depth: number): Array<TocItem> {
if (depth === 1 || !item.subheadings.length) {
return item.subheadings;
} else {
// e.g., 2
return diveChildren(item.subheadings[item.subheadings.length - 1] as TocItem, depth - 1);
}
}

export function generateToc(headings: ReadonlyArray<MarkdownHeading>) {
// this ignores/filters out h1 element(s)
const bodyHeadings = [...headings.filter(({ depth }) => depth > 1)];
const toc: Array<TocItem> = [];

bodyHeadings.forEach((h) => {
const heading: TocItem = { ...h, subheadings: [] };

// add h2 elements into the top level
if (heading.depth === 2) {
toc.push(heading);
} else {
const lastItemInToc = toc[toc.length - 1]!;
if (heading.depth < lastItemInToc.depth) {
throw new Error(`Orphan heading found: ${heading.text}.`);
}

// higher depth
// push into children, or children's children
const gap = heading.depth - lastItemInToc.depth;
const target = diveChildren(lastItemInToc, gap);
target.push(heading);
}
});
return toc;
}
2 changes: 2 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export { getFormattedDate } from "./date";
export { elementHasClass, toggleClass } from "./domElement";
export { sortMDByDate, getUniqueTags, getUniqueTagsWithCount } from "./post";
export { generateToc } from "./generateToc";
export type { TocItem } from "./generateToc";

0 comments on commit 523e871

Please sign in to comment.