メインコンテンツへスキップ

microCMS で作成した記事に Gatsby で目次を付ける

·3 分
Gatsby

microCMS + Gatsby なブログに目次を付けました。
前に シンタックスハイライトを行なったように、HTML パーサーである cheerio を使用しました。
また、スタイルの適用にあたっては、 styled components を用いました。

準備:styled components のインストール
#

  • これまで、スタイルの適用には、外部のCSS module を使って、 TypeScript のファイルと CSS ファイルを分けて管理して適用していましたが、目次にスタイルを適用するにあたって上手く行かなかったので、styled components を使用しました。

styled components の特徴
#

  • JavaScript でスタイルを記述する CSS in JS のライブラリ
    • コンポーネントとstyleのマッピングが無くなる
    • ローカルスコープができる
  • 書き方の例としては以下のようになります:
 1const Button = styled.a`
 2  /* This renders the buttons above... Edit me! */
 3  display: inline-block;
 4  border-radius: 3px;
 5  padding: 0.5rem 0;
 6  margin: 0.5rem 1rem;
 7  width: 11rem;
 8  background: transparent;
 9  color: white;
10  border: 2px solid white;
11
12  /* The GitHub button is a primary button
13   * edit this to target it specifically! */
14  ${props => props.primary && css`
15    background: white;
16    color: black;
17  `}
18`
19
20render(
21  <div>
22    <Button
23      href="https://github.com/styled-components/styled-components"
24      target="_blank"
25      rel="noopener"
26      primary
27    >
28      GitHub
29    </Button>
30
31    <Button as={Link} href="/docs">
32      Documentation
33    </Button>
34  </div>
35)

Gatsby での styled components の利用
#

  • Gatsby で styled components を使用するために、以下のようにプラグインをインストールします:
1$ yarn add gatsby-plugin-styled-components styled-components babel-plugin-styled-components
  • gatsby-config.js に以下のように記述します:
1module.exports = {
2  plugins: [`gatsby-plugin-styled-components`],
3}
  • 再ビルドすると使用できるようになります。

目次の作成方法
#

  • microCMS のブログ記事を参考に HTML パーサーである cheerio を使用します。
  • 具体的には…
    • microCMS の API 経由で取得した記事(リッチエディタで作成)の HTML から h1, h2, h3 の要素を抜き出します。
    • スタイルを適用するために、抜き出した要素にクラス名を付与します。
    • インデントをするために Styled Components を用います。

要素の抜き出し
#

  • 以下のようにして要素を抜き出します
    • htmlString には、 microCMS の API 経由で取得した記事の内容(HTML)をそのまま入れます
    • 配列形式で取得できるので、整形してテキスト、ID、タグ名の配列に変換します。
1const $ = cheerio.load(htmlString)
2  const toc = $("h1, h2, h3")
3    .toArray()
4    .map(data => ({
5      text: data.children[0].data,
6      id: data.attribs.id,
7      name: data.name,
8    }))
  • 以下のような形で取得できます:
1[
2  { text: '目次の作り方', id: 'hkY7o3DlYB2', name: 'h1' },
3  { text: '目次機能のオンオフ', id: 'h4AL9B4qAV6', name: 'h2' },
4  { text: 'おわりに', id: 'hBwwL6rm8B7', name: 'h1' }
5]

表示方法
#

  • 以下のように要素を返して目次を表示します。
    • スタイルの適用を見越して、リストの項目一つ一つに list h1, list h2, list h3 といったクラス名を付与しています。
 1return (
 2    <>
 3      {toc.length ? (
 4        <div id="create-table-of-contents">
 5          <h4>目次</h4>
 6            <ul id="lists">
 7              {toc.map((toc, index) => {
 8                return (
 9                  <li className={"list " + toc.name} key={toc.id}>
10                    <a href={"#" + toc.id}>{toc.text}</a>
11                  </li>
12                )
13              })}
14            </ul>
15        </div>
16      ) : (
17        ""
18      )}
19    </>
20  )

字下げを行う
#

  • h2, h3 の要素に対しては字下げを行うよう、スタイルを適用します。
 1import styled from "styled-components"
 2
 3const StyledToc = styled.div`
 4  & .list.h2 {
 5    margin-left: 1em;
 6  }
 7
 8  & .list.h3 {
 9    margin-left: 2em;
10  }
11`
  • 最終的に、目次のコンポーネントは以下のように作成できました:
 1import React from "react"
 2import cheerio from "cheerio"
 3import styled from "styled-components"
 4
 5export type TocTypes = {
 6  text: string
 7  id: string
 8  name: string
 9}
10
11interface Props {
12  htmlString: string
13}
14
15const StyledToc = styled.div`
16  & .list.h2 {
17    margin-left: 1em;
18  }
19
20  & .list.h3 {
21    margin-left: 2em;
22  }
23`
24
25const Toc: React.FC<Props> = props => {
26  const { htmlString } = props
27  const $ = cheerio.load(htmlString)
28  const toc = $("h1, h2, h3")
29    .toArray()
30    .map(data => ({
31      text: data.children[0].data,
32      id: data.attribs.id,
33      name: data.name,
34    }))
35
36  return (
37    <>
38      {toc.length ? (
39        <div id="create-table-of-contents">
40          <h4>目次</h4>
41          <StyledToc>
42            <ul id="lists">
43              {toc.map((toc, index) => {
44                return (
45                  <li className={"list " + toc.name} key={toc.id}>
46                    <a href={"#" + toc.id}>{toc.text}</a>
47                  </li>
48                )
49              })}
50            </ul>
51          </StyledToc>
52        </div>
53      ) : (
54        ""
55      )}
56    </>
57  )
58}
59
60export default Toc

参考資料
#