jointrashposs/components/docs/TocLinks.vue

75 lines
2.0 KiB
Vue
Raw Normal View History

2023-07-11 19:19:32 +02:00
<template>
<ul class="toc-links mb-4 space-y-2">
<li
v-for="link in links"
:key="link.text"
:class="[`depth-${link.depth}`]"
>
<a
:href="`#${link.id}`"
2023-07-15 10:48:12 +02:00
@click.prevent="emit('child-click'); scrollToHeading(link.id);"
2023-07-11 19:19:32 +02:00
:class="['hover:text-accent-600', activeHeadings.includes(link.id) ? 'font-bold text-accent-600' : '']"
>
{{ link.text }}
</a>
<TocLinks
class="mt-2"
2023-11-11 13:59:03 +01:00
v-if="link.children && (link.depth ?? 0 + 1) < maxDepth"
2023-07-11 19:19:32 +02:00
:links="link.children"
@move="childMove($event)"
/>
</li>
</ul>
</template>
<script setup lang="ts">
2023-11-11 13:59:03 +01:00
import type { TocLink } from '@nuxt/content/dist/runtime/types';
2023-07-11 19:19:32 +02:00
2023-11-11 13:59:03 +01:00
const props = withDefaults(defineProps<{
links?: TocLink[];
maxDepth?: number;
}>(), {
maxDepth: Infinity,
2023-07-11 19:19:32 +02:00
});
2023-07-15 10:48:12 +02:00
const emit = defineEmits(['move', 'child-click']);
2023-07-11 19:19:32 +02:00
const { activeHeadings, updateHeadings } = useScrollspy();
if (process.client) {
setTimeout(() => {
updateHeadings([
...document.querySelectorAll('.markdown-body h1'),
...document.querySelectorAll('.markdown-body h2'),
...document.querySelectorAll('.markdown-body h3'),
...document.querySelectorAll('.markdown-body h4'),
]);
}, 300);
}
function scrollToHeading (id: string) {
if (process.client) {
if (!decodeURIComponent(location.href).includes(`#${id}`)) {
// ページ遷移させずにハッシュだけ置き換えるために、history APIに直接書き込み
history.pushState({}, '', `#${id}`);
}
document.getElementById(id)?.scrollIntoView({
behavior: 'smooth'
});
}
emit('move', id)
}
function childMove(id: string) {
emit('move', id)
}
</script>
<style scoped>
.toc-links ::v-deep(.depth-3) {
@apply ml-2;
}
.toc-links ::v-deep(.depth-4) {
@apply ml-4;
}
</style>