Gatsbyの記事に目次を作成してみる

目次(table-of-contents)を作成してみる

目次

目次を作るには?

目次を作るには、gatsby-remark-table-of-contents と gatsby-remark-autolink-headers のプラグインを使えば、サクッと出来ちゃいそうですが…

Graphql のクエリの中に tableOfContents という項目があるので、それと gatsby-remark-autolink-headers で目次を作成してみたいと思います。

gatsby-remark-autolink-headers の使い方は、いろんな記事になっているので、割愛。

以前のブログでも

gatsby-starter-blog を使っていた時は以下の様な手順でやっていました。

コードは以下に掲載されているものでやってみます。

export const pageQuery = graphql`
  query ($id: String!) {
    markdownRemark(id: { eq: $id }) {
      html
      tableOfContents
      frontmatter {
        date(formatString: "MMMM DD, YYYY")
        slug
        title
      }
    }
  }
`;

http://localhost:8000/\_\_\_graphql で実行すると…

{
  "data": {
    "markdownRemark": {
      "tableOfContents": "<ul>
      <li><a href="#%E4%BB%A5%E5%89%8D%E3%81%AE%E3%83%96%E3%83%AD%E3%82%B0%E3%81%AF">以前のブログは</a></li>
      <li><a href="#3-%E3%81%8B%E3%82%89-4-%E3%81%AB%E3%82%A2%E3%83%83%E3%83%97%E3%83%87%E3%83%BC%E3%83%88">3 から 4 にアップデート</a></li>
      <li><a href="#allmdx-%E3%82%92%E4%BD%BF%E3%81%86">allMdx を使う</a></li>
      <li><a href="#staticimage-%E3%81%8C%E4%BE%BF%E5%88%A9%E3%81%9D%E3%81%86">StaticImage が便利そう</a></li>
      <li><a href="#css-%E3%81%AF-styled-components">CSS は styled components</a></li>
      <li><a href="#%E4%BB%8A%E5%BE%8C%E3%81%AF">今後は</a></li>
      </ul>"
    }
  },
  "extensions": {}
}

この様な感じで tableOfContents の値を取得出来ます。

import React from "react";
import { graphql } from "gatsby";
export default function Template({
  data, // this prop will be injected by the GraphQL query below.
}) {
  const { markdownRemark } = data; // data.markdownRemark holds your post data
  const { frontmatter, html, tableOfContents } = markdownRemark;
  return (
    <div className="blog-post-container">
      <div className="blog-post">
        <h1>{frontmatter.title}</h1>
        <div
          className="XXXXXX"
          dangerouslySetInnerHTML={{ __html: tableOfContents }}
        />
        <h2>{frontmatter.date}</h2>
        <div
          className="blog-post-content"
          dangerouslySetInnerHTML={{ __html: html }}
        />
      </div>
    </div>
  );
}

後はコード内にある html を表示する手順に倣って、tableOfContents を表示させます。

allMdx で同じことを試すと

チュートリアルページに掲載されている、src/pages/blog/{mdx.slug}.js で同じことをすると…

export const query = graphql`
  query ($id: String) {
    mdx(id: { eq: $id }) {
      frontmatter {
        title
        date(formatString: "MMMM D, YYYY")
      }
      body
      tableOfContents
    }
  }
`;

tableOfContents をクエリに追加して、

import * as React from "react";
import { graphql } from "gatsby";
import { MDXRenderer } from "gatsby-plugin-mdx";
import Layout from "../../components/layout";
const BlogPost = ({ data }) => {
  return (
    <Layout pageTitle={data.mdx.frontmatter.title}>
      <div
        className="XXXXXX"
        dangerouslySetInnerHTML={{ __html: data.mdx.tableOfContents }}
      />
      <p>{data.mdx.frontmatter.date}</p>
      <MDXRenderer>{data.mdx.body}</MDXRenderer>
    </Layout>
  );
};

markdownRemark の時と同じ様にして、実行してみると…

[object Object]と表示されてしまいます。

http://localhost:8000/\_\_\_graphql を開いて、tableOfContents の中を確かめてみると…

{
  "data": {
    "mdx": {
      "tableOfContents": {
        "items": [
          {
            "url": "#以前のブログは",
            "title": "以前のブログは"
          },
          {
            "url": "#3-から-4-にアップデート",
            "title": "3 から 4 にアップデート"
          },
          {
            "url": "#allmdx-を使う",
            "title": "allMdx を使う"
          },
          {
            "url": "#staticimage-が便利そう",
            "title": "StaticImage が便利そう"
          },
          {
            "url": "#css-は-styled-components",
            "title": "CSS は styled components"
          },
          {
            "url": "#今後は",
            "title": "今後は"
          }
        ]
      }
    }
  },
  "extensions": {}
}

そりゃ、表示されないわ…ということで、コンポーネント化してみます。

src/pages/blog/{mdx.slug}.js の記述を変更

import * as React from "react";
import { graphql } from "gatsby";
import { MDXRenderer } from "gatsby-plugin-mdx";
import Layout from "../../components/layout";
import Toc from "../components/toc";

const BlogPost = ({ data }) => {
  return (
    <Layout pageTitle={data.mdx.frontmatter.title}>
      <Toc tableOfContents={data.mdx.tableOfContents.items} />
      <p>{data.mdx.frontmatter.date}</p>
      <MDXRenderer>{data.mdx.body}</MDXRenderer>
    </Layout>
  );
};

ざっくりとですが、目次用のコンポーネントを用意してあげると

import React from "react";

const TOC = (props) => {
  const data = props.tableOfContents;
  return (
    <div>
      <h4>目次</h4>
      <ul>
        {data.map((item) => (
          <li>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default TOC;

こんな感じで目次用のコンポーネントを作成してあげたら良さそうです。

参考にさせて頂いた記事

(自分は JS ですが)TypeScript ではこう書けばいいのか…と勉強させていただきました。