はじめに
Astro Conents Collectionは見出しを配列にしてくれるので、それを使って階層状の目次を作ります。
目次を作る
見出しを取得する
記事詳細ページではgetStaticPaths()
で各記事をMarkdownから取得しているので、render()
でheadings
を取り出します。
---
// 〜前略〜
const { post } = Astro.props
const { Content, headings } = await post.render()
---
headings
の中にはこのような配列が格納されています。
[
{ depth: 2, slug: 'はじめに', text: 'はじめに' },
{ depth: 2, slug: '目次を作る', text: '目次を作る' },
{ depth: 3, slug: '見出しを取得する', text: '見出しを取得する' }
]
これをこれから作成する目次コンポーネントに渡します。
目次コンポーネントを作る
私が作ったのはこんな感じのコンポーネントです。headings
は階層化されていないため、新しく配列を作って小見出しを大見出しのsubheadings
に格納します。Astro.self
を使っているのがミソで、小見出しの目次は自分自身(コンポーネント)を呼び出して同じ処理を繰り返します。こうすることで、簡単に入れ子のリストを作れます。見出しレベル4より上は含めないようにしていますが、これは好みで変更して構いません。
TableOfContents.astro
---
import { MarkdownHeading } from 'astro'
interface HeadingWithSubheadings extends MarkdownHeading {
depth: number
subheadings: MarkdownHeading[]
}
export interface Props {
headings: MarkdownHeading[]
depth?: number
}
const { headings, depth = 2 } = Astro.props
const hierarchy = headings.reduce((array, heading) => {
if (depth > 4) {
return array
}
if (heading.depth === depth) {
array.push({ ...heading, subheadings: [] })
} else {
array.at(-1)?.subheadings.push(heading)
}
return array
}, [] as HeadingWithSubheadings[])
---
{
hierarchy && (
<ol class={depth > 2 && 'pl-4'}>
{hierarchy.map(({ slug, text, subheadings }) => (
<li class="">
<a href={`#${slug}`}>{text}</a>
{subheadings.length > 0 && (
<Astro.self headings={subheadings} depth={depth + 1} />
)}
</li>
))}
</ol>
)
}