アウトラインの生成
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 を付与してあるので、それを見て分岐する
- ToC の a タグには