This component will render a Table of Contents section following your markdown heading structure. Each item is linked to the respective header
in the document, so you can click and jump to the section.
Astro
, when using fetchContent
/*.md on your markdown files, will expose to you their “content” which includes frontmatter
, and an astro.headers
section.
This component uses the headers
information to create a list of divs
and indent the header titles accordingly.
So when you assign a layout
to your markdown file
//-- /src/pages/mypost.md
---
layout: './../layouts/MyPostLayout.astro'
// ... (more frontmatter)
---
your markdown content will end up in the <slot/>
on the layout file.
At the layout level, you can work with the Astro.props.content.astro.headers
data and build a simple Table of Contents for your markdown file.
My Post layout setup contains:
//-- /src/layouts/MyPostLayout.astro
---
import BaseLayout from './BaseLayout.astro';
import TableOC from './../components/TableOC.vue';
const { content } = Astro.props;
---
<BaseLayout title={content.title} description={content.summary}>
<link rel="stylesheet" href="/css/prism-atom-dark.css" />
<div class="container post-md">
<TableOC toc={content.astro.headers} />
<slot />
</div>
</BaseLayout>
I am passing the data in content.astro.headers
into a TableOC.vue
component.
I coded the component in vue
because it will not require client-side
interactivity and I am still not familiar with astro
component syntax, conditionals feel weird at this point (for me). I ended up not using conditionals so… ¯\_(ツ)_/¯
//-- /src/components/TableOC.vue
<template>
<div class="toc" v-if="toc.length">
<div>Table of Contents</div>
<div v-for="(n, idx) in toc" :key="'toc-'+idx">
<span v-for="ii in n.depth-1"
:key="'i-'+idx+'-'+ii"
class="indent"
> </span>
<i class="fas fa-chevron-circle-right text-muted"></i>
<a :href="'#'+n.slug">{{n.text}}</a>
</div>
</div>
</template>
<script>
export default {
name: 'toc',
props: {
toc: { type: Array, required: true }
}
}
</script>
<style scoped>
.toc { font-size:.8em; background:linear-gradient(#cdf,#fff,#f2f6ff); padding:10px; border-radius:5px; margin-bottom:20px; }
.toc a { text-decoration:none; }
.toc .indent { display:inline-block;width:15px; }
</style>
The logic is just an iteration of each header
from the array sent in thru prop toc
, and rendering a “space” to produce indentation based on the header’s depth
property.
Each header
item has properties depth
, slug
, and text
.
Here is this markdown file TOC:
[
{
"depth": 1,
"slug": "introduction",
"text": "Introduction"
},
{
"depth": 2,
"slug": "astropropscontent",
"text": "Astro.props.content"
},
{
"depth": 3,
"slug": "component-in-layout",
"text": "Component in Layout"
},
{
"depth": 2,
"slug": "tableoc-component",
"text": "TableOC component"
},
{
"depth": 3,
"slug": "logic",
"text": "Logic"
}
]