Hugo のテーマを作ってみます 6(Menu Templates)

前回の記事では、 Custom 404 Page を使ってみました。 今回は、 Menu Templates を使ってみます。

最初に、この記事の通り、サイトとテーマの骨組みを作成しておきました。

環境

  • Hugo Static Site Generator v0.38.2

Menu Templates

Menus are a powerful but simple feature for content management but can be easily manipulated in your templates to meet your design needs.

Menu Templates | Hugo

シンプルにコンテンツを管理できるようです。

Section Menu for Lazy Bloggers

To enable this menu, configure sectionPagesMenu in your site config:

sectionPagesMenu = "main"

Menu Templates | Hugo

Section Menu は、 Section (content/ ディレクトリー直下のディレクトリー) に合わせてメニューに関する情報を作ってくれるもののようです。

config.toml

設定ファイル (config.(toml|yaml|json)) に次の内容を追加しました。 TOML の例です。

sectionPagesMenu = "main"

これで Hugo が自動的に Section に合わせてメニューに関する情報を作ってくれるようです。

sidebar.html

メニューを表示するレイアウトのファイル (theme/<THEME>/layouts/partial/sidebar.html) を次の通り作成しました。

<nav>
  <ul>
    {{ $currentPage := . }}
    {{ range .Site.Menus.main }}
      <li>
        <a href="{{ .URL }}" class="{{ if $currentPage.IsMenuCurrent "main" . }}active{{ else if $currentPage.HasMenuCurrent "main" . }}has-menu{{ end }}">
          {{ .Name }}
          ({
            IsMenuCurrent: {{ $currentPage.IsMenuCurrent "main" . }},
            HasMenuCurrent: {{ $currentPage.HasMenuCurrent "main" . }}
          })
        </a>
      </li>
    {{ end }}
  </ul>
</nav>

Hugo が自動的に作ってくれたメニューに関する情報は、 .Site.Menus.main の変数で参照できるようです。 この main と、設定ファイルに記述した sectionPagesMenu = "main"main が対応するようです。

$currentPage.IsMenuCurrent "main" . は、表示しているページが、 {{ range }} で処理しているメニュー (Section) と 一致する 場合に true を返すようです。

$currentPage.HasMenuCurrent "main" . は、表示しているページが、 {{ range }} で処理しているメニュー (Section) に 含まれる 場合に true を返すようです。

Partial Templates

Partials are smaller, context-aware components in your list and page templates that can be used economically to keep your templating DRY.

Partial Templates | Hugo

ファイルを partials ディレクトリーに作成しましたが、これは Partial Templates というもののようです。 themes/<THEME>/layouts/partials/*<PARTIALNAME>.html のようなファイルを作成しておくと、共通的なレイアウトを二度記述する手間を省くことができるようです。

All partials are called within your templates using the following pattern:

{{ partial "<PATH>/<PARTIAL>.html" . }}

Use Partials in your Templates

Partial Templates を使うには、 {{ partial "sidebar.html" . }} のように記述すればよいみたいです。

baseof.html

以前の記事を参考に、 baseof.html ファイル (theme/<THEME>/layout/_default/baseof.html) を次のように作成しました。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>
      {{ block "title" . }}
        {{ .Site.Title }}
      {{ end }}
    </title>
    <style>
      .active {
        color: darkorange;
      }
      .has-menu {
        color: darkgreen;
      }
    </style>
  </head>
  <body>
    {{ block "header" . }}
    {{ end }}
    {{ block "main" . }}
    {{ end }}
    {{ block "footer" . }}
    {{ end }}
  </body>
</html>

.active.has-menu の文字の色を変えるようにしてみました。 一致する場合はオレンジで、 含まれる場合は緑にしました。

list.html

以前の記事を参考に、 list.html ファイル (theme/<THEME>/layout/_default/list.html) を次のように作成しました。

{{ define "main" }}
  <main>
    {{ partial "sidebar.html" . }}
    <h1>{{ .Title | humanize }}</h1>
    {{ range .Data.Pages }}
      <article>
        <a href="{{ .RelPermalink }}"><h2>{{ .Title }}</h2></a>
        <div>{{ .Date.Format "2006/01/02" }}</div>
      </article>
    {{ end }}
  </main>
{{ end }}

{{ partial "sidebar.html" . }} の箇所でメニューを表示してみました。 Partial Templates を使っています。

single.html

以前の記事を参考に、 single.html ファイル (theme/<THEME>/layout/_default/single.html) を次のように作成しました。

{{ define "title" }}
  {{ .Title }} &ndash; {{ .Site.Title }}
{{ end }}
{{ define "main" }}
  <main>
    {{ partial "sidebar.html" . }}
    <h1>{{ .Title }}</h1>
    <article>
      <div>{{ .Date.Format "2006/01/02" }}</div>
      {{ .Content }}
    </article>
  </main>
{{ end }}

list.html と同様に、 {{ partial "sidebar.html" . }} の箇所でメニューを表示してみました。

content

content/post ディレクトリーに、次の 2 つの examples の記事を作成しました。

  • hello-hugo.md
  • another-post.md

次のコマンドでダウンロードしました。

$ install -d content/post
$ curl -L -o content/post/hello-hugo.md 'https://raw.githubusercontent.com/gohugoio/hugo/master/examples/blog/content/post/hello-hugo.md'
$ curl -L -o content/post/another-post.md 'https://raw.githubusercontent.com/gohugoio/hugo/master/examples/blog/content/post/another-post.md'

それから、 content/blog ディレクトリーに記事を 3 つ作成しました。 次のコマンドで作成しました。

$ install -d content/blog
$ for i in {1..3}
do
cat <<word > content/blog/blog-${i}.md
---
title: blog-${i}
date: 2018-01-0${i}
---
This is blog-${i}.
word
done

Hugo のサーバーで確認

Hugo のサーバーで確認するために次のコマンドを入力します。

$ hugo -t <THEMENAME> server

http://localhost:1313/ にアクセスします。 すると、メニューが表示されました。

Blogs の方だけ確認します。 IsMenuCurrentfalse で、 HasMenuCurrentfalse のようです。 このページはどこの Section にも一致しない・含まれないようです。

次に、 http://localhost:1313/blog/ (メニューの Blogs リンク)にアクセスします。 すると、 Blogs Section の記事の一覧が表示されました。

メニューの Blogs の IsMenuCurrenttrue で、 HasMenuCurrenttrue のようです。 Blogs のリンクの文字の色がオレンジになっていました。 このページは Blogs Section に 一致する ようです。

次に、 http://localhost:1313/blog/blog-3/ (一覧の blog-3 リンク)にアクセスします。 すると、 blog-3 の記事の内容が表示されました。

メニューの Blogs の IsMenuCurrentfalse で、 HasMenuCurrenttrue のようです。 Blogs のリンクの文字の色が緑になっていました。 このページは Blogs Section に 含まれる ようです。

Section Menu のまとめ

Section をネストしない、シンプルな階層であれば、 Section Menu で十分そうに感じました。

Site Config menus

The above is all that’s needed. But if you want custom menu items, e.g. changing weight, name, or link title attribute, you can define them manually in the site config file:

config.toml

[menu]

  [[menu.main]]
    identifier = "blog"
    name = "This is the blog section"
    title = "blog section"
    url = "/blog/"
    weight = -110

The identifier must match the section name.

Menu Templates | Hugo

Section から自動的にメニューに関する情報を作るのではなく、設定ファイルにメニューに関する情報を記述することもできるようです。

config.toml

最初に、 Section Menu for Lazy Bloggers で設定した sectionPagesMenu = "main" を削除しました。

それから、次の内容を追加しました。 TOML の例です。

[menu]

  [[menu.main]]
    identifier = "blog"
    name = "This is the blog section"
    title = "blog section"
    url = "/blog/"
    weight = -110

  [[menu.main]]
    identifier = "child1"
    name = "This is the child1 section"
    title = "child1 section"
    url = "/blog/child1/"
    weight = -130
    parent = "blog"

  [[menu.main]]
    identifier = "child2"
    name = "This is the child2 section"
    title = "child2 section"
    url = "/blog/child2/"
    weight = -120
    parent = "blog"

  [[menu.main]]
    identifier = "post"
    name = "This is the post section"
    title = "post section"
    url = "/post/"
    weight = -100

TOML の記述にあまり慣れていないのですが、 menu.main の配列に key, value を持ったオブジェクトを設定しているようです。 identifier は content/xxx ディレクトリー (Section) と一致させるようです。

ネストした Section は、 parent に親の identifier(Section) を指定するようです。

sidebar.html

メニューのファイル (theme/<THEME>/layouts/partial/sidebar.html) を次の通り修正しました。

<!-- sidebar start -->
<aside>
  <ul>
    {{ $currentPage := . }}
    {{ range .Site.Menus.main }}
      <li>
        <a href="{{ .URL }}" class="{{ if $currentPage.IsMenuCurrent "main" . }}active{{ else if $currentPage.HasMenuCurrent "main" . }}has-menu{{ end }}">
          {{ .Name }}
          ({
            IsMenuCurrent: {{ $currentPage.IsMenuCurrent "main" . }},
            HasMenuCurrent: {{ $currentPage.HasMenuCurrent "main" . }}
          })
        </a>
        {{ if .HasChildren }}
          <ul>
            {{ range .Children }}
              <li>
                <a href="{{ .URL }}" class="{{ if $currentPage.IsMenuCurrent "main" . }}active{{ else if $currentPage.HasMenuCurrent "main" . }}has-menu{{ end }}">
                  {{ .Name }}
                  ({
                    IsMenuCurrent: {{ $currentPage.IsMenuCurrent "main" . }},
                    HasMenuCurrent: {{ $currentPage.HasMenuCurrent "main" . }}
                  })
                </a>
              </li>
            {{ end }}
          </ul>
        {{end}}
      </li>
    {{end}}
  </ul>
</aside>

{{ if .HasChildren }} でネストしたメニューの存在を判断できるようです。 {{ range .Children }} でネストしたメニューを繰り返し処理できるようです。 1 階層ネストしただけで記述が面倒な感じになってしまいました。

layouts

baseof.html, list.html, single.html は Section Menu for Lazy Bloggers で作成したものをそのまま使います。

content

Site Config menus では、メニューをネストすることができるようですので、 content を入れ替えます。 最初に Section Menu for Lazy Bloggers で作成した content を削除しておきました。

$ rm -r ./content/*

blog Section の記事を作成します。

$ install -d content/blog
$ for i in $(seq -f %02g 1 3)
do
cat <<word > content/blog/blog-${i}.md
---
title: blog-${i}
date: 2018-01-${i}
---
This is blog-${i}.
word
done
$ cat <<word > content/blog/_index.md
---
title: blog section index
---
This is blog section.
word

child1 Section の記事を作成します。

$ install -d content/blog/child1
$ for i in $(seq -f %02g 4 6)
do
cat <<word > content/blog/child1/child1-${i}.md
---
title: child1-${i}
date: 2018-01-${i}
---
This is child1-${i}.
word
done
$ cat <<word > content/blog/child1/_index.md
---
title: child1 section
---
This is child1 section.
word

child2 Section の記事を作成します。

$ install -d content/blog/child2
$ for i in $(seq -f %02g 7 9)
do
cat <<word > content/blog/child2/child2-${i}.md
---
title: child2-${i}
date: 2018-01-${i}
---
This is child2-${i}.
word
done
$ cat <<word > content/blog/child2/_index.md
---
title: child2 section
---
This is child2 section.
word

post Section の記事を作成します。

$ install -d content/post
$ for i in $(seq -f %02g 10 12)
do
cat <<word > content/post/post-${i}.md
---
title: post-${i}
date: 2018-01-${i}
---
This is post-${i}.
word
done
$ cat <<word > content/post/_index.md
---
title: post section
---
This is post section.
word

Hugo のサーバーで確認

Hugo のサーバーで確認するために次のコマンドを入力します。

$ hugo -t <THEMENAME> server

http://localhost:1313/ にアクセスします。 すると、メニューが表示されました。

Blogs の方だけ確認しようと思います。 IsMenuCurrentfalse で、 HasMenuCurrentfalse のようです。 このページはどこの Section にも一致しない・含まれないようです。

次に、 http://localhost:1313/blog/ (メニューの This is the blog section リンク)にアクセスします。 すると、 Blog section の記事の一覧が表示されました。

メニューの This is the blog section の IsMenuCurrenttrue で、 HasMenuCurrentfalse のようです。 Section Menu の場合と違って、 HasMenuCurrentfalse になるようでした。

次に、 http://localhost:1313/blog/child1/ (メニューの This is the child1 section リンク)にアクセスします。 すると、 Child1 section の記事の一覧が表示されました。

メニューの This is the blog section の IsMenuCurrentfalse で、 HasMenuCurrenttrue のようです。 メニューの This is the child1 section の IsMenuCurrenttrue で、 HasMenuCurrentfalse のようです。 こちらも Section Menu の場合と違っていました。

次に、 http://localhost:1313/blog/child1/child1-06/ (一覧の child1-06 リンク)にアクセスします。 すると、 child1-06 の記事の内容が表示されました。

メニューの This is the blog section の IsMenuCurrentfalse で、 HasMenuCurrentfalse のようです。 メニューの This is the child1 section の IsMenuCurrentfalse で、 HasMenuCurrentfalse のようです。 こちらも Section Menu の場合と違っていました。

Site Config menus のまとめ

IsMenuCurrentHasMenuCurrent がうまく使えませんでした。 設定ファイルが悪いのかな。

今回は、 Section をネストするために、 content/blog/child1/_index.md のようなファイルを作成しました。 Branch Bundles と言うそうですが、これを作成しておくと、ネストしたディレクトリーも Section として扱ってくれるようです。 Hugo は標準では content/ の直下のディレクトリーしか Section として扱ってくれません。 でも、 _index.md (拡張子は .html でも .json でも、妥当な MIME タイプのものならいいみたい)のようなファイルを作成しておくと、ネストしたディレクトリーも Section として扱ってくれるようです。

でも、ネストしたディレクトリーを Section とすると、複雑になるので、あまりしない方が良さそうに感じました。

終わり

この記事を書いている途中で、 Nested Section が思った通りに表示できなくて、 Hugo のバージョンをアップしました。 v0.31.1 から v0.38.2 になっていました。 前回 Hugo をインストールしてから、まだ、半年も経過していないと思うのですが、頻繁にメンテナンスされているようでした。

今回は扱わなかったのですが、 Menu Entries from the Page’s front matter でも、各記事の front matter にメニューに関する記述をすることもできるようです。

参考

15 April 2018 追記

次の記事を書きました。