目次(TOC)を作る

ある程度記事にボリュームが出てくるようになったら、目次が欲しくなってきますよね。
ということで、目次を作ります。

ちなみに TOC とは、 Table of Contents の略で、目次という意味です。

nuxt/content で目次を作るのは超簡単

nuxt/content はコンテンツをパースすると自動的に目次用のオブジェクトを作ってくれます。
コンテンツドリブンモードの場合、useContent コンポーザブルの toc を使って取り出すことができます。

const { toc } = useContent()
console.log(toc)

たとえば、上記コードをこの記事で実行すると、こんなふうになります。

{
  "title": "",
  "searchDepth": 3,
  "depth": 3,
  "links": [
    {
      "id": "nuxtcontent-で目次を作るのは超簡単",
      "depth": 2,
      "text": "nuxt/content で目次を作るのは超簡単"
    },
    {
      "id": "目次生成コンポーネントを作る",
      "depth": 2,
      "text": "目次生成コンポーネントを作る",
      "children": [
        {
          "id": "コンポーネントのサンプル",
          "depth": 3,
          "text": "コンポーネントのサンプル"
        },
        {
          "id": "使い方",
          "depth": 3,
          "text": "使い方"
        }
      ]
    },
    {
      "id": "目次の設定",
      "depth": 2,
      "text": "目次の設定"
    }
  ]
}

config の content.markdown.toc の設定値っぽいのと、実際に生成された目次用のオブジェクトである、links が含まれてるようですね。

links の中には、更に飛び先のIDや現在の階層の深さ(?)見出しの文字列なんかがはいってるオブジェクトがあって、更にネストされた子見出しがある場合は、children があって、中身は links と同じ構造で入れ子構造になってるっぽいですね。

TS のインターフェイスにするとこんなかんじ?

interface ContentToc {
  id: string
  depth: number
  text: string
  children?: Array<ContentToc>
}

なので、あとは、この型にあわせてコンポーネントを作っていけばOKです。

目次生成コンポーネントを作る

上にも書いたとおりで、toc.links 以下は同じ構造が入れ子になってるようなので、 この部分だけを利用すればコンポーネントを再帰的に呼び出して何層あっても使える目次コンポーネントにできそうです。

コンポーネントのサンプル

components/ContentTocItem.vue

<script setup lang="ts">
import { PropType } from 'vue'

interface ContentToc {
  id: string
  depth: number
  text: string
  children?: Array<ContentToc>
}

defineProps({
  links: {
    type: Object as PropType<ContentToc>,
    default: () => {}
  }
})
</script>

<template>
  <ul class="content-toc-item">
    <li
      v-for="link in links"
      :key="link.id"
    >
      <a :href="`#${link.id}`">
        {{ link.text }}
      </a>
      <ContentTocItem
        v-if="link.children"
        :links="link.children"
      />
    </li>
  </ul>
</template>

使い方

私は、TOC部分を別コンポーネントにした上で呼び出すようにしてるのでちょっと段階が多いですが、直接埋め込んでもいいかもしれません。

<script setup lang="ts" >
const { toc } = useContent()
</script>

<template>
  <div class="toc">
    <h3>TOC</h3>
    <ContentTocItem :links="toc.links" />
  </div>
</template>

目次の設定

nuxt/content の TOC は、何層目までを生成対象にするかを決めることができます。
非常に入り組んだ文書で h6 まで全部目次にするとエライことになりそうな場合とか、あくまで大見出しだけ目次にしてその中に含まれる小さな見出しは目次に出さない(章と節みたいなかんじ?)みたいなときに便利です。

というよりかは、デフォルトだと depth: 2 / searchDepth:2 なので、それだと浅すぎるって場合に調整する、という使い方ですかね。

nuxt.config.tscontent.markdown に設定します。

export default defineNuxtConfig({
  // ...
  content: {
    markdown: {
      toc: {
        depth: 3,
        searchDepth: 3
      }
    }
  }
})

実際にどう変化するのかは……たぶん文章で説明するよりも実際に設定をいじりながら変化を確認したほうがわかりやすいですw

git log --format=%ct:%s

:add post 2023021501