On gists
16 TOC
•
cdruc vue
Toc.vue
Raw
#
// https://tallpad.com/series/vuejs-misc/lessons/table-of-contents-component-using-vuejs
<script setup>
import {onMounted, ref, computed} from "vue";
import TocList from "./TocList.vue";
import {slugifyWithCounter} from "@sindresorhus/slugify";
const props = defineProps({
contentSelector: {
type: String,
required: true,
},
});
const slugify = slugifyWithCounter();
const headings = ref([]);
onMounted(() => {
window.document
.querySelector(props.contentSelector)
.querySelectorAll("h1, h2, h3, h4, h5, h6")
.forEach(el => {
let id = slugify(el.innerText);
el.setAttribute("id", id);
headings.value.push({
id: id,
level: parseInt(el.tagName.charAt(1), 10),
content: el.innerText,
subheadings: [],
});
});
});
const groupedHeadings = computed(() => {
let items = [...headings.value];
for (let i = items.length - 1; i >= 0; i--) {
let currentItem = items[i];
let parentItem = items.findLast((item, index) => {
return item.level < currentItem.level && index < i;
});
if (parentItem) {
parentItem.subheadings.unshift(currentItem);
items.splice(i, 1);
}
}
return items;
});
</script>
<template>
<div class="bg-slate-50 -mx-6 p-6" v-if="groupedHeadings.length">
<h3
class="border-b-2 border-slate-300 inline-block uppercase font-bold tracking-wide text-slate-800 mb-5"
>
Contents:
</h3>
<TocList :items="groupedHeadings" />
</div>
</template>
TocList.vue
Raw
#
<script setup>
const props = defineProps({
items: Array,
});
</script>
<template>
<ul class="space-y-3">
<li v-for="item in items">
<a
:href="`#${item.id}`"
class="font-semibold group-[.subheadings]:font-normal hover:text-orange-500 hover:underline underline-offset-4"
>
{{ item.content }}
</a>
<TocList
class="mt-3 ml-5 group subheadings"
v-if="item.subheadings.length"
:items="item.subheadings"
/>
</li>
</ul>
</template>