【Hugo】目次を最初の見出しの前に挿入する

投稿日:

カテゴリ:Web 制作

Hugo には .TableOfContents という、ページのコンテンツ内の見出しに対応した目次を出力できるタグが存在する。

以前 Blogger でこのブログをやっていたときは、JavaScript で投稿内の見出しから動的に目次を作成し、最初の見出し h2 の前に挿入していた。

Hugo でも同じように最初の見出しの前に目次を持っていきたかったので、その方法を考えてみた。

※Hugo v0.151.2 を使用しています。Hugo のバージョンによってはこの記事の方法が使えない場合がありますのでご了承ください。

この記事の目次

その1:.Summary と .ContentWithoutSummary を使う

投稿内で区切り線 <!--more--> をサマリーの終わりに追加すると、本文をサマリー部分 .Summaryと残りの部分 .ContentWithoutSummary に分けることができる。

この2つのタグを利用して以下のようなコードを single.html などの投稿用テンプレートに記述することで、サマリーと本文の間への目次の挿入が可能となる。

{{ if ne .TableOfContents `<nav id="TableOfContents"></nav>` }}
  {{ .Summary }}
  {{ .TableOfContents }}
  {{ .ContentWithoutSummary }}
{{ else }}
  {{ .Content }}
{{ end }}

.TableOfContents は投稿内に見出しがない場合でも HTML を生成してしまうので、以下の記事に書かれている方法を用いて、見出しがある場合にのみ目次を表示させている。

この目次の表示方法だと比較的簡単に導入ができるが、投稿内の区切り線が必須になるし、そもそもサマリーの区切り方によっては必ずしも最初の見出しの前に目次が来るとも限らない。

ということで次!

その2:.Content を書き換える

投稿本文 .Content から最初の見出し h2 を探し出し、その直前に目次を差し込む。

参考にした記事はこちら。

以下のようなコードを single.html などの投稿用テンプレートに記述することで、最初の見出しの前に目次を挿入できる。

{{ $h2s := findRE `(?s)<h2.*?>.*?</h2>` .Content }}
{{ if ge (len $h2s) 1 }}
  {{ $firstH2 := index $h2s 0 }}
  {{ replace .Content $firstH2 (printf "%s%s" .TableOfContents $firstH2) | safeHTML }}
{{ else }}
  {{ .Content }}
{{ end }}

.Content 内の見出し h2 を検索する方法は、以下のページの例に書いてあったものをそのまま使わせてもらった。正規表現まじで苦手なので助かる。

見出し h2 が1つ以上ある場合は最初の見出し $firstH2 を目次とその見出しを結合したものに置き換え、見出しが何もない場合は .Content をそのまま表示するという処理をしている。

.Content を直接いじるぶん多少のリスクはあるが、これなら区切り線の有無や位置に依存せず、確実に最初の見出しの前に目次を挿入できる。

このブログではこちらの方法を採用することにした。

見出しが2個以上のときだけ折りたたみ式の目次を表示する

前述のその2の方法を用いて、実際にブログに目次を表示していく。

以下のようなコードを single.html などの投稿用テンプレートに記述することで、投稿内に見出し h2 が2つ以上ある場合にのみ、details, summary を用いた折りたたみ式の目次を最初の見出しの前に挿入する。

{{ $h2s := findRE `(?s)<h2.*?>.*?</h2>` .Content }}
{{ if ge (len $h2s) 2 }}
  {{ $firstH2 := index $h2s 0 }}
  {{ $toc := printf `<details class="toc"><summary>この記事の目次</summary>%s</details>` .TableOfContents }}
  {{ replace .Content $firstH2 (printf "%s%s" $toc $firstH2) | safeHTML }}
{{ else }}
  {{ .Content }}
{{ end }}

Blogger のときのようにわざわざ JS で動的に目次を生成しなくてもいい代わりに、不慣れな Hugo(というか GO 言語)の記述方法に苦戦した。なんとか最初の見出しの前に目次を持っていけてよかった。

同じように目次の表示位置で悩んでいる人の参考になれば幸いです。