Hugoでドキュメント作るときに手っ取り早く痒いところに手が届くようになるTips

WordPressの代替など、ブログ用途として紹介されていることが多いHugoだが、Googleが提供しているテーマである[Docsy](https://www.docsy.dev/)を見ればわかる通り、ドキュメント用途としても十分に使うことができる。
そこで、今回いろいろ検証してみて悩んだ部分やいまいち微妙だと思った部分やその改善方法について、なるべく作り込まず管理を複雑にせずという方針で大雑把にまとめてみた。

ここに書いたことを使うと、↓のような見た目の静的サイトが生成されるようになる。
なお、痒いところに手が届かないことを四字熟語で『隔靴掻痒』というらしいです。為になるなぁ。
![hugo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116209/e77fa748-57a8-cb2a-581e-e04d64caaf9f.png)


# テーマの選び方

[公式のテーマ一覧のページ](https://themes.gohugo.io/)を見て良い感じのを実際に試しながら探せばいいが、公開されているテーマはブログ用途のものがほとんどなため、ドキュメント用途に使えるテーマはおそらく[このあたり](https://themes.gohugo.io/tags/documentation/)の20個程度に限られる。
特に理由がなければ冒頭での例にも挙げた[Docsy](https://www.docsy.dev/)が最も無難なように思えるが、DocsyはHugoのバイナリ以外にもnpmでインストールの必要なものがあり、利用したい環境によってはこれがネックになってしまう。

なお、今回は諸事情でファイルサーバにGitリポジトリを作ってGitのフックスクリプトを使ってCIっぽく自動的にHugoのビルドをさせる仕組みで使いたかったため、Docsyは見送って[Learn](https://themes.gohugo.io/hugo-theme-learn/)を使うことにした。


# lastmodにGitのコミット時刻を使うときの注意

lastmod（最終変更時刻）にGitのコミット時刻を使うようにすると確実に最後にいつ変更したのかがわかるようになって良い。具体的には以下のことを行うとコミット時刻が使われるようになる。

* configで`enableGitInfo`を`True`にする
* Markdownファイル内のlastmodの時刻を表示したい位置に`{{< param "lastmod" >}}`と書く

しかし、ここで1つ注意点があり、ローカルで新規に作ったまだコミットしていないMarkdownファイルに上記を書いた場合、ビルドに失敗してしまうという問題が発生する。
このままではローカルでの確認がめんどくさいため、Configに以下の設定を記載すると解消することができる。

```toml:config.toml
[frontmatter]
lastmod = [":git", "lastmod", "date", "publishDate", ":fileModTime"]
```

なお、上記の設定はデフォルトでは`lastmod = [":git", "lastmod", "date", "publishDate"]`となっており、左から順の優先度で値が参照されるようになっている。いずれも未定義だった場合にビルドでエラーが発生するが、絶対に定義されている`fileModTime`（ファイルのタイムスタンプ）を最後に付け足すことでコミット前でも未定義の場合が存在しないようにしている。


# ページ内目次の無いテーマでページ内目次を表示する

冒頭の画像の右の方にある見た目の目次が表示されるようになる。
よく見ると若干いまいちだがCSSあまり詳しくないのでこれが限界だった。

１. `layouts/shortcodes/`に`toc.html`という名前で以下を作成する。

```html:toc.html
<style>
  #float-toc-box {
    position: fixed;
    right: 5em;
    width: 25em;
    background-color: rgba(255,255,255,0.5);
    border: 1px solid;
    z-index: 10000;
    box-shadow: 10px 10px 10px rgba(0,0,0,0.4);
  }
  #TableOfContents ul {
    counter-reset: section;
    list-style-type: none;
    margin: 0;
    padding: 0 1em;
  }
  #TableOfContents li::before {
    counter-increment: section;
    content: counters(section, ".") " ";
  }
</style>
<script>
  toggleTOC = () => {
    let e = document.getElementById('TableOfContents-box');
    if(e.style.display == "none"){
      e.style.display = "inline"
      document.getElementById('toc-toggle').innerHTML = "▼"
    }else{
      e.style.display = "none"
      document.getElementById('toc-toggle').innerHTML = "▲"
    }
  }
</script>
<aside id="float-toc-box">
  <center><strong>TableOfContents <a id="toc-toggle" onClick="toggleTOC()">▼</a></strong></center>
  <div id="TableOfContents-box">
    {{ .Page.TableOfContents }}
  </div>
</aside>
```

２. Markdownファイルで目次を表示したい高さの位置に`{{< toc >}}`と記載する

`toc`の部分は`shortcodes`内に作成したファイル名に対応しているので好きな名前に変更することも可能
HugoではテンプレートとShortcodeでのみ`{{ .Page.TableOfContents }}`でページ内目次を参照できるようになっており、これを表示させればページ内目次を表示できる。その際、`TableOfContents`というIDの要素で表示されるため、このCSSを適当に書き換えることで見た目のカスタマイズが行える。
上記では主に以下のことを行っているが、お好みで書き換え可能

* 目次のボックスを右の方にフローティングさせてスクロールに付いてこさせる
* ▼を押すと表示/非表示を切り替えられる（Javascript部分）
* リストの頭の「・」を「1.1.2.」のような項目番号になるように変更（ulとliの部分）


# ページを読むのにかかる時間を自動計算して表示する

よく見かける「この記事は約○分で読めます」みたいなのをページ内の文字量から自動計算して表示されるようにする。あくまで参考時間なのであまり信頼性のある値にはならないが、表記してあると書く側がページを分けるかどうかの指標にできたり、読む側が今読むか後で読むかの判断に使えたりして意外と役立つ。
Shortcodeで実装するため、基本的な作り方は上記の目次追加と同じ。
なお、日本語で文章を書く場合はConfigで`hasCJKLanguage`を`True`にしておいた方がいい。

１. `layouts/shortcodes/`に`reading-time.html`という名前で以下を作成する。

```golang:reading-time.html
{{ $runes := (.Page.RawContent | strings.RuneCount) }}
約 {{ math.Round (div $runes 500) }} 分
```

Hugoではページ内の文字数をカウントする方法がいくつかあり、それぞれ微妙にカウント条件が異なっているが、適当に試してみたら上記の方法が最もそれっぽい値になった。詳細理解していないのでもしかしたらより良い方法があるかもしれない。
500で割っているのは、人が1分間で読める平均の文字数がだいたい400～600らしいため。

２. Markdownファイルで読了時間を表示したい位置に`{{< reading-time >}}`と記載する

`toc`と同様、`reading-time`の部分はShortcodeのファイル名に対応しているので変更は可能

# Learnテーマでサイドメニューを折り畳みメニューにする

文章だと説明が難しいが、Learnテーマのサイドメニューはトップの階層のみ「開いているページ以下のリンクしか表示しない」ようになっており、2層目以降を開いた場合は常にすべてのリンクが表示される仕様になっている。
ページ数が少ない場合はこれでもよいが、多くなってきた場合に見づらくなってしまうため、常に今見ているページ以下のリンクだけが表示されるようにできると嬉しい。以下の方法で行うのがもっとも手っ取り早い。
やっていることはLearnのCSSの定義の上書きなので、他にも変更したい部分があれば同様の方法で書き換えられる。直にthemes内のファイルを書き換えなくてよいので管理で困らない。

１. `/static/css/`に以下の内容でCSSファイルを作成する。（ファイル名はおそらくなんでもいい）

```css:custom.css
#sidebar ul.topics li.parent ul, #sidebar ul.topics > li.active ul {
  display: none;
}
#sidebar ul.topics li.parent > ul {
  display: block;
}
```

２. Configで以下を記載する

```toml:config.toml
[params]
  custom_css = ["css/<上記のファイル名>"]
```

参考：https://github.com/matcornic/hugo-theme-learn/issues/88


# 直にhtmlファイルを開いてページ移動できるようにする

特殊な用途だが、今回の用途ではビルドしたファイルをWebサーバで公開するのではなく、ファイルサーバに置いて直にhtmlファイルを開いて見られるようにしたかったため、デフォルト設定ではリンクが正常に機能しなくなってしまうという問題が発生する。
以下の設定をConfigに記載することで解消可能
なお、この設定のままでもWebサーバで動かす際に支障はないため、URLに`.html`まで含まれるようになることに特に抵抗がなければ同じ設定で両方に使い回すことも可能

```toml:config.toml
uglyURLs = true
canonifyURLs = true
relativeURLs = true
```

※実は今回最初に[Bookのテーマ](https://themes.gohugo.io/hugo-book/)を使おうとしていたが、このテーマだとWebサーバを使わずに上記の使い方をした場合、なぜかCSSを参照できなかったので諦めた。


# Draw.ioの図を使う

これは別にHugo関係ないが、とてつもなく便利でどこかに書きたかったのでついでに記載
VSCodeのアドオンを使うとVSCode上でDraw.ioの図の編集が行え、さらにエクスポートせずに直接MarkdownでSVG画像として読み込んで表示できる。
エクスポートの手間や二重管理を防ぐことができるため、運用や管理のコストを下げることができる。
以下の手順で利用可能

1. VSCodeの拡張機能のメニューから「Draw.io Integration」を検索してインストールする
2. 拡張子を`.drawio.svg`としてファイルを作成する  
   　⇒ VSCode内でDraw.ioの編集画面が開かれる
3. 適当に図を作成して保存する
4. Markdown内で上記ファイルを通常の画像ファイルと同じ記法で参照する  
   　⇒ 通常は`![](img_path)`
6. ブラウザで表示確認をする

VSCodeを使わない場合はおそらく普通にSVGやPNGにエクスポートして参照させるしかないのではないかと思う（もしくはそれに相当する自動化を頑張ってなにかしらの方法で組むか）

参考：https://qiita.com/oruharo/items/f5bdeedad28731da1b11