當一篇文章的篇幅太長時,若能及早知道他的結構,會更幫助讀者快速了解哪部分是自己當下需要閱讀的,這時候若在文章側邊有附上一個綱要與連結,就能滿足這個需求。
在落實這個功能前,先透過前幾天提到的〈將常用的格式元件化〉重構 @/components/TheArticles.vue
,將其內容拆成 <PostHeader />
、<PostContent />
、<PostFooter />
三個元件,讓日後維護更能快速找到想要修改的地方。
<template>
<article class="pb-4">
<PostHeader :post="post"/>
<PostContent :post="post"/>
<PostFooter :post="post"/>
</article>
</template>
<script setup lang="ts">
import { Post } from '@/types/index'
defineProps<{
post: Post
}>()
</script>
來到 @/components/Post/Content.vue
,原本的程式碼在重構後是長這樣:
<template>
<div class="my-4 break-word ">
<ContentRenderer :value="post" class="prose" />
</div>
</template>
<script setup lang="ts">
import { Post } from '@/types/index'
defineProps<{
post: Post
}>()
</script>
接著透過 TailwindCSS 的 Flex Direction .flex .flex-row-reverse
和 Basis,將內容的區塊分成四等分,其中一等分 .basis-1/4
提供給綱要,另外三等份 .basis-3/4
用在文章的檢視上。
並在那四分之一中建立 <PostTableOfContent />
的元件,並預期會傳入一個 toc
的 Props 進去。這邊可以採用 Nuxt Content 在 MarkdownParsedContent
內建的 body.toc.links
參數傳進去,只要是內容來自於 Markdown 類型的檔案,都會提供這個參數,而這個參數是 TocLink
型別,晚點會提到。
<template>
<div class="my-4 break-word flex flex-row-reverse">
<div class="basis-1/4">
<PostTableOfContent
class="my-4"
:toc="post.body.toc.links"
></PostTableOfContent>
</div>
<div class="basis-3/4 mr-4">
<ContentRenderer :value="post" class="prose" />
</div>
</div>
</template>
<script setup lang="ts">
import { Post } from '@/types/index'
defineProps<{
post: Post
}>()
</script>
然後就來實作 <TableOfContent />
,在介紹程式碼前,先講一下上面提到的 TocLink 型別:
export interface TocLink {
id: string
text: string
depth: number
children?: TocLink[]
}
每一個 TocLink
,也都會有一個 TocLink
型別的 children
屬性。也就是每個二級標題之下的三級標題,都是其 children
,三級之下的四級標題也是其 children
,以此類推。
在 Markdown,通常一級標題 #
就是該文章的標題,所以在內文中的標題會從二級標題開始 ##
使用。
了解其概念後,開始建立 @/components/Post/TableOfContent.vue
:
<template>
<div v-if="toc.length > 0">
<div class="text-2xl">ToC</div>
<ol class="list-outside text-sm">
<template v-for="primaryLink of toc" :key="primaryLink.id">
<li>
<NuxtLink
class="font-normal text-slate-700"
:to="`#${primaryLink.id}`"
>
{{ primaryLink.text }}
</NuxtLink>
</li>
<ol v-if="primaryLink.children" class="list-outside text-sm">
<li
v-for="secondaryLink of primaryLink.children"
:key="secondaryLink.id"
>
<NuxtLink
class="font-normal text-slate-700"
:to="`#${secondaryLink.id}`"
>
{{ secondaryLink.text }}
</NuxtLink>
</li>
</ol>
</template>
</ol>
</div>
</template>
<script setup lang="ts">
import type { TocLink } from '@nuxt/content/dist/runtime/types'
const props = defineProps<{
toc: TocLink[]
}>()
</script>
為了使用 TocLink
型別,可以從 '@nuxt/content/dist/runtime/types'
引入。
要為綱要設立連結,可以善用 TocLink.id
,Nuxt Content 在渲染 Markdown 的標題時,會使用該 id
在標題標籤上。
接著只要透過 Vue 本身的 List Rendering 就可以將目錄給渲染出來了。效果如下圖:
點選綱要的標題連結,就會跳到該標題的位置: