microCMSで書いたコンテンツに目次を追加してみたよ
目次
開発環境
- Next.js ^13.4.4
- cheerio ^1.0.0-rc.12
完成コード
export const createTableOfContents = (richText: string) => {
const $ = cheerio.load(richText);
const headings = $('h1, h2, h3').toArray();
const tableOfContents = headings
.map((heading) => {
if (heading.type === 'tag') {
const id = heading.attribs.id;
const name = heading.name;
const text = $(heading).text();
return { id, name, text };
}
})
.filter(Boolean);
return tableOfContents;
};
export default function Article({ data }: Props) {
const tableOfContents = createTableOfContents(data.content);
return (
{/* 省略 */}
{tableOfContents.length > 0 && (
<div className={styles.toc}>
<p>目次</p>
<ul>
{tableOfContents.map((toc) => (
<li key={toc?.id} className={styles[`toc-${toc?.name}`]}>
<Link href={`#${toc?.id}`}>{toc?.text}</Link>
</li>
))}
</ul>
</div>
)}
{/* 省略 */}
)
}
完成イメージ
解説
cheerioを使ってHTMLをパースし、目次になるタグを取得する
🤔 HTMLをパースするとは?
「<h1>
で囲まれたテキストは見出しですよ」
「<p>
で囲まれたテキストは段落ですよ」
という感じで、HTML文書を解析して要素や構造を把握できるようにすることをいうよ✌🏻
const $ = cheerio.load(richText);
これはお決まりのような感じ。
パースの結果を$
に格納し、$
を使ってHTMLを操作します。
jQueryライクなAPIなので$
オブジェクトを使って取得するみたい。
const headings = $('h1, h2, h3').toArray();
HTMLから必要なタグを取得し、配列に格納します。
今回は目次なのでheadingの要素に絞り込み🐒
取得したタグから必要な情報を取り出し、返却用のデータを作成する
const tableOfContents = headings
.map((heading) => {
if (heading.type === 'tag') {
const id = heading.attribs.id;
const name = heading.name;
const text = $(heading).text();
return { id, name, text };
}
}).filter(Boolean);
map関数を使って各ヘッダー要素から必要な情報を取り出し、新しいオブジェクトを作成します。
今回取得した情報は以下の用途で使用するよ!
- id:目次のhref属性
microCMSでは各heading要素にあらかじめidが振られているので、目次の項目をクリックしたら該当箇所にページ内リンクできるようにします。
- name:目次のclass名
目次の階層構造を表現するために、name(h2、h3など)を取得してclass名を付与します。
- text:目次の項目
.filter(Boolean);
で、undefinedが返された要素、つまりタグではない要素を配列から排除するようにしているよ。
コンポーネントで呼び出す
const tableOfContents = createTableOfContents(data.content);
先ほど作った関数を呼び出してtableOfContents
に代入します。
(data.content
の部分にはmicroCMSから取得したコンテンツをいれてね)
{tableOfContents.length > 0 && (
<div className={styles.toc}>
<p>目次</p>
<ul>
{tableOfContents.map((toc) => (
<li key={toc?.id} className={styles[`toc-${toc?.name}`]}>
<Link href={`#${toc?.id}`}>{toc?.text}</Link>
</li>
))}
</ul>
</div>
)}
あとはtableOfContents
を使って目次を描写してあげれば完了!
CSSモジュールで動的なクラスをつけるにはどうしたらいいのかはChatGPTさまが教えてくれました。🙏🏻
<li key={toc?.id} className={styles[`toc-${toc?.name}`]}>
こんな感じでいいようです。
CSSも一応置いておく✌🏻
.toc {
width: 100%;
margin-bottom: 2.8em;
}
.toc p {
display: inline-block;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 4px 4px 0 0;
padding: 5px 25px;
}
.toc ul {
background: #f1f2f3;
padding: 20px;
}
.toc li {
margin-bottom: 5px;
}
.toc li a:hover {
opacity: 0.7;
}
.toc-h3 {
padding-left: 16px;
}
.toc-h4 {
padding-left: 16px;
}
感想
目次つくるの結構大変そうと思ってたけど、cheerio
がいい感じにお仕事してくれるし、
microCMSがheading要素に自動でidつけるようにしてくれていたから思ってたより簡単にできました😊
そのうち目次の表示位置を変えたいな〜〜
最後まで読んでいただきありがとうございます!
もしよければ「読んだよ!」の代わりに↑の紙飛行機をクリックで飛ばしてください。わたしの元に届きます。
ありがとうございました