かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

TOC(Table of Contents)を自動生成して表示させる

Translate to English
https://translate.google.com/translate?sl=auto&tl=en&u=https%3A%2F%2Fksby.hatenablog.com%2Fentry%2F2021%2F01%2F16%2F180857

概要

記事一覧はこちらです。

AsciiDoc で作成されたドキュメントを見ると右か左に TOC(Table of Contents)が表示されています。Spring Boot や JUnit 5 のドキュメントだと下にスクロールさせると TOC が自動的に展開されて現在見ている section が分かるようになっています。TOC(Table of Contents)があるとドキュメントが見やすいので、同じように表示させてみます。

個人的には以下のドキュメントの TOC(Table of Contents)の方が好みなので、この TOC の実現方法も調べてみます。

参照したサイト・書籍

  1. Automatic Table of Contents
    https://docs.asciidoctor.org/asciidoc/latest/toc/

  2. unit-team / junit5/documentation/src/docs/asciidoc/
    https://github.com/junit-team/junit5/tree/main/documentation/src/docs/asciidoc

  3. Add expandable/collapsable TOC
    https://github.com/asciidoctor/asciidoctor/issues/699

  4. Tocbot
    https://tscanlin.github.io/tocbot/

  5. copyfiles
    https://www.npmjs.com/package/copyfiles

  6. Asciidoctor Gradle Examples
    https://asciidoctor.github.io/asciidoctor-gradle-examples/

    • 今回の記事とは直接関係がありませんが、見つけたのでメモとして書いておきます。

目次

  1. document header に :toc: left:toclevels: 4 を追加して TOC を表示させる
  2. Tocbot を導入して TOC が自動展開される+参照中の section が分かるようにする
    1. npm init -y を実行する
    2. npm install --save tocbot を実行する
    3. src/docs/asciidoc/js ディレクトリを作成して Tocbot の js ファイルをコピーする
    4. asciidoctor タスク実行時に js ファイルが build ディレクトリにコピーされるようにする
    5. docinfo.html、docinfo-footer.html に Tocbot を使用するために必要なコードを記述する
    6. asciidoctor タスクを実行して動作確認する
  3. AsciiDoc Language Documentation、Asciidoctor Documentation と同じ TOC の実現は次回へ

手順

document header に :toc: left:toclevels: 4 を追加して TOC を表示させる

Spring Boot Reference Documentation の adoc ファイルを参考に document header を追加する で追加しなかった :toc: left:toclevels: 4 を document header に追加して TOC を自動生成させてみます。

src/docs/asciidoc/02_include/index.adoc を以下のように変更します。

= include directive で章毎にファイルを分けてみる
:lang: ja
:doctype: book
:idprefix:
:idseparator: -
:toc: left
:toclevels: 4
:tabsize: 4
:sectanchors:
:sectnums:
:icons: font
:hide-uri-scheme:
:docinfo: shared,private
:docinfodir: ../../docinfo

include::01.adoc[leveloffset=+1]
include::02.adoc[leveloffset=+1]
  • 以下の2行を追加します。
    • :toc: left
    • :toclevels: 4

asciidoctor タスクを実行して html を表示させてみると左側に TOC が表示されましたが、TOC は展開済みの状態で、下にスクロールした時に TOC が自動的に展開されて現在見ている section が分かるようにはなっていませんでした。:toc: を追加するだけでは実現できないようです。

f:id:ksby:20210113232953p:plain

Tocbot を導入して TOC が自動展開される+参照中の section が分かるようにする

TOC を自動的に展開して現在見ている section が分かるようにする方法を調べたところ、JUnit 5 のドキュメントのソース https://github.com/junit-team/junit5/tree/main/documentation/src/docs/asciidocTocbot を使って実現していることが分かりました。

asciidoctor の GitHub にも Add expandable/collapsable TOC の Issue がありました。

同じようにすれば実現できそうです。

npm init -y を実行する

Tocbot のファイルは npm でダウンロードするので、まずは npm init -y を実行します(Node.js、npm のインストールは以下の記事参照)。

f:id:ksby:20210114093003p:plain

npm install --save tocbot を実行する

npm install --save tocbot を実行して Tocbot のファイルをダウンロードします。

f:id:ksby:20210114093741p:plain

node_modules ディレクトリが作成されたので .gitignore に無視するよう設定を追加します。

# Gradle
.gradle/
build/

# Intellij project files
*.iml
*.ipr
*.iws
.idea/

# Node.js, npm
node_modules/
npm-debug.log

src/docs/asciidoc/js ディレクトリを作成して Tocbot の js ファイルをコピーする

npm scripts でファイルをコピーするのに以前は cpx をインストールして使っていましたが、最近使われているモジュールを調べてみるとダウンロード数が多いのは ncp らしいです。

copy vs copyfiles vs cpx vs ncp vs npm-build-tools というサイトを見つけました。ncp は最後のリリースは 6年前ですが圧倒的にダウンロード数が多く、次の copyfiles は最後のリリースが2ヶ月前ですが ncp よりダウンロード数は少ない模様。

今回は copyfiles をインストールして使うことにします。また rimraf、npm-run-all もインストールしたいので、以下のコマンドを実行します。

  • npm install --save-dev copyfiles
  • npm install --save-dev rimraf
  • npm install --save-dev npm-run-all

f:id:ksby:20210114205734p:plain

package.jsonpostinstallcopy:tocbot の npm scripts を追加してから、

  ..........
  "scripts": {
    "postinstall": "run-s copy:tocbot",
    "copy:tocbot": "copyfiles -f node_modules/tocbot/dist/*.js src/docs/asciidoc/js"
  },
  ..........

npm ci コマンドを実行すると、

f:id:ksby:20210114211240p:plain

src/docs/asciidoc/js ディレクトリが作成されて、その下に tocbot.js、tocbot.min.js がコピーされます。

f:id:ksby:20210114211405p:plain:w300

asciidoctor タスク実行時に js ファイルが build ディレクトリにコピーされるようにする

build.gradle を以下のように変更します。

..........

asciidoctor {
    sourceDir file("src/docs/asciidoc")
    baseDirFollowsSourceFile()
    sources {
        include "**/index.adoc"
    }
    resources {
        from("${sourceDir}") {
            include "**/*.png", 
                    "**/*.js"
        }
    }
}

..........
  • asciidoctor block の resources の中に "**/*.js" を追加します。

docinfo.html、docinfo-footer.html に Tocbot を使用するために必要なコードを記述する

Add expandable/collapsable TOC に docinfo.html、docinfo-footer.html に記述するサンプルがありますので、それを流用して記述します(ほとんどコピーですが流用元の URL の追加と Tocbot の最新版 4.12.1 を使うための設定の追加をしています)。

まずは docinfo.html から。

<style>
..........

/* Source: https://github.com/asciidoctor/asciidoctor/issues/699#issuecomment-321066006 */
#tocbot a.toc-link.node-name--H1{ font-style: italic }
@media screen{
    #tocbot > ul.toc-list{ margin-bottom: 0.5em; margin-left: 0.125em }
    #tocbot ul.sectlevel0, #tocbot a.toc-link.node-name--H1 + ul{
        padding-left: 0 }
    #tocbot a.toc-link{ height:100% }
    .is-collapsible{ max-height:3000px; overflow:hidden; }
    .is-collapsed{ max-height:0 }
    .is-active-link{ font-weight:700 }
}
@media print{
    #tocbot a.toc-link.node-name--H4{ display:none }
}
</style>

次に docinfo-footer.html。ファイル自体がないので src/docs/docinfo ディレクトリの下に新規作成してから以下の内容を記述します。こちらは Ctrl+Alt+L でフォーマットした後、先頭のコードを <script src="/assets/js/tocbot.js"></script><script src="../js/tocbot.min.js"></script> へ、tocbot のバージョン番号を 3.0.24.12.1 へ変更しています。

<!--Source: https://github.com/asciidoctor/asciidoctor/issues/699#issuecomment-321066006-->
<script src="../js/tocbot.min.js"></script>
<script>
  /* Tocbot dynamic TOC, works with tocbot 4.12.1 */
  var oldtoc = document.getElementById('toctitle').nextElementSibling;
  var newtoc = document.createElement('div');
  newtoc.setAttribute('id', 'tocbot');
  newtoc.setAttribute('class', 'js-toc');
  oldtoc.parentNode.replaceChild(newtoc, oldtoc);
  tocbot.init({
    contentSelector: '#content',
    headingSelector: 'h1, h2, h3, h4',
    smoothScroll: false
  });
  var handleTocOnResize = function () {
    var width = window.innerWidth
      || document.documentElement.clientWidth
      || document.body.clientWidth;
    if (width < 768) {
      tocbot.refresh({
        contentSelector: '#content',
        headingSelector: 'h1, h2, h3, h4',
        collapseDepth: 6,
        activeLinkClass: 'ignoreactive',
        throttleTimeout: 1000,
        smoothScroll: false
      });
    } else {
      tocbot.refresh({
        contentSelector: '#content',
        headingSelector: 'h1, h2, h3, h4',
        smoothScroll: false
      });
    }
  };
  window.addEventListener('resize', handleTocOnResize);
  handleTocOnResize();
</script>

asciidoctor タスクを実行して動作確認する

asciidoctor タスクを実行すると BUILD SUCCESSFUL のメッセージが出力されて、

f:id:ksby:20210115082654p:plain

build/docs/asciidoc/js が作成されて、その下に tocbot.js がコピーされており、

f:id:ksby:20210115082842p:plain

index.html を開くと左側に TOC が表示されて、下にスクロールすると TOC が自動的に展開されて現在見ている section が分かるようになりました。

f:id:ksby:20210115084226g:plain

実現できた!と思ったのですが、よく見ると一番上位の階層になぜか番号が余計に付いています。。。

f:id:ksby:20210115084505p:plain

http://tscanlin.github.io/tocbot/ の「API」-「Options」を見ると orderedList という項目があり orderedList can be set to false to generate unordered lists (ul) instead of ordered lists (ol) との記述がありました。おそらくこれを false に設定すれば番号は付かなくなるはず。

src/docs/docinfo/docinfo-footer.html を以下のように変更します。

<!--Source: https://github.com/asciidoctor/asciidoctor/issues/699#issuecomment-321066006-->
<script src="../js/tocbot.min.js"></script>
<script>
  /* Tocbot dynamic TOC, works with tocbot 4.12.1 */
  var oldtoc = document.getElementById('toctitle').nextElementSibling;
  var newtoc = document.createElement('div');
  newtoc.setAttribute('id', 'tocbot');
  newtoc.setAttribute('class', 'js-toc');
  oldtoc.parentNode.replaceChild(newtoc, oldtoc);
  tocbot.init({
    contentSelector: '#content',
    headingSelector: 'h1, h2, h3, h4',
    smoothScroll: false
  });
  var handleTocOnResize = function () {
    var width = window.innerWidth
      || document.documentElement.clientWidth
      || document.body.clientWidth;
    if (width < 768) {
      tocbot.refresh({
        contentSelector: '#content',
        headingSelector: 'h1, h2, h3, h4',
        collapseDepth: 6,
        activeLinkClass: 'ignoreactive',
        throttleTimeout: 1000,
        smoothScroll: false,
        orderedList: false
      });
    } else {
      tocbot.refresh({
        contentSelector: '#content',
        headingSelector: 'h1, h2, h3, h4',
        smoothScroll: false,
        orderedList: false
      });
    }
  };
  window.addEventListener('resize', handleTocOnResize);
  handleTocOnResize();
</script>
  • tocbot.refresh({ ... }); を呼び出しているところ(2ヶ所)に orderedList: false を追加しました。

asciidoctor タスクを実行して index.html を開くと、今度は番号が付いていませんでした。

f:id:ksby:20210115091424p:plain

AsciiDoc Language Documentation、Asciidoctor Documentation と同じ TOC の実現は次回へ

AsciiDoc Language Documentation のソース https://github.com/asciidoctor/asciidoc-docs の記述を見るとと The documentation site is built using Antora. の記述があり、Antora で構築されているとのこと。

Antora で検索すると以下のサイトが見つかりました。

今回はここで区切って Antora を使用するのは次回にします。

履歴

2021/01/16
初版発行。