アウトラインの生成

2018-10-28

右側に表示されているアウトラインについて

h2 〜 h4 のヘッダが並ぶようにしている。 h1 や h5 は並ばない。 これは Hugo 標準の .TableOfContents では制御できなかったため、 以下のような partial を書いて実現している:

{{/* 空リンクを無視 */}}
{{- $headers := findRE "<h[2-4].*?>(.|\n])+?</h[2-4]>" .Content -}}
{{ .Scratch.Set "last_level" 1 }}

{{- $has_headers := ge (len $headers) 1 -}}
{{- if $has_headers -}}
<aside class="table-of-contents">
  {{- range $headers -}}
    {{- $header := . -}}
    {{- $base := ($.Page.File.LogicalName) -}}
    {{/* 正規表現のサブマッチで抽出する方法が見つからなかったので苦肉の策 */}}
    {{- $headerId := substr (index (findRE "id=\".*\"" $header) 0) 4 -1 -}}

    {{- range findRE "[2-4]" . 1 -}}
      {{- $next_heading := (int .) -}}
      {{- if gt $next_heading ($.Scratch.Get "last_level") -}}
        <ul class="toc-h{{ . }}">
      {{- else if lt $next_heading ($.Scratch.Get "last_level") -}}
        </ul>
        {{/* インデントが 2 段一気に降りた時対応 */}}
        {{- if lt $next_heading (sub ($.Scratch.Get "last_level") 1) -}}
           </ul>
        {{- end -}}
      {{- end -}}

      <li><a href="#{{ $headerId }}" class="al-toc">{{- $header | plainify | htmlUnescape -}}</a></li>
      {{ $.Scratch.Set "last_level" $next_heading }}

    {{- end -}}
  {{- end -}}
</aside>
{{- end -}}

partial は以下の投稿を参考にした:

※ いくつか要件を満たさない部分があったので一部修正している

  • ヘッダの id は、以下のような書き方をすると同じ名称のヘッダが複数あった場合にうまくいかない:
{{ $anchorId := ($header | plainify | htmlUnescape | anchorize) }}

Hugo のアンカー生成のテスト

ヘッダのテキストからいい感じに生成してくれるっぽい。日本語でも問題なく動いてそう。

同じテキストが出てきた場合は 1 から始まる番号が振られる模様。

H3 Example

  • これは h3-example という id になる
  • スペースはハイフン、大文字は小文字に補正されるようだ

hoge

  • hoge

fuga

  • fuga

hoge

  • これは hoge-1 になる

hoge

  • これは hoge-2

fuga

  • fuga-1

日本語 Header

  • 日本語-header になる

ヘッダにアンカーのリンクを貼る

ページ内の特定の位置に対してリンクを張れるように、 ヘッダ自体にそのアンカーのリンクをつけておくというのはよく行われる。 Hugo で書かれた Hugo の公式ドキュメント にもヘッダの横にリンクのアイコンがついている。

じゃあ Hugo 標準でそういう機能がついているのかというと残念ながらそうではないようで、 js の力 で後からつけているようだ。静的サイトジェネレータなのにこれは悲しい。

が、テンプレートで .Content に変換をかましてやったら要件を満たせた。 (サイト生成時に解決しているようになる):

{{- with .Content -}}
  {{ . | replaceRE "(<h[1-9] id=\"([^\"]+)\".+)(</h[1-9]+>)" `<a href="#${2}">${1}${3}</a>` | safeHTML }}
{{- end -}}

以下の Forum を参考にした:

アンカーに移動するときのスクロールを滑らかにする

これは js の力でやるやつ。以下のような partial を書いておいて読み込む:

<script type="text/javascript">
$(function(){
  $('a[href^="#"]').click(function() {
    var speed    = 400; // msec
    var href     = $(this).attr('href');
    href         = decodeURI(href);
    var target   = $(href == '#' || href == '' ? 'html' : href);
    var position = target.offset().top - 70;
    $('body,html').animate({scrollTop:position}, speed, 'swing');

    if (!$(this).hasClass('al-toc')) {
      window.history.pushState(null, null, this.hash);
    }
    return false;
  });
});
</script>

ToC をポチポチやった時にブラウザの遷移履歴に乗っていくのは、自分は使い勝手が悪いと感じる。 だがアンカーのリンクを得る場合などにそういう挙動になってほしい場合もある。 そこで自分は以下のようにしている:

  • 本文中のヘッダをクリックした場合は hitory.pushState する(一般的な挙動になる)
  • 右側の ToC をクリックした際には hitory.pushState しない
    • ToC の a タグには al-toc という class を付与してあるので、それを見て分岐する