notebook

都内でWEB系エンジニアやってます。

MkDocsにプレゼンテーション機能をつける

今回はMkDocsで生成したサイト内にプレゼンテーション機能をつけたいと思います

動機

  • MkDocsでスライド用資料も管理したい
  • 資料もプレゼンも一箇所で閲覧できるようにしたい(某wikiと同様)

ということで、すこし調べてみた結果割と簡単にできそうだったのでやってみました

MkDocsのインストール、カスタマイズは下記から

MkDocsでドキュメント管理

remark.js

markdownプレゼンで使うのはremark.jsを使います

理由は下記

  • jsとmarkdownを分離できる
  • buildが必要ない
  • ツール専用の書式を追加しなくて良い
    • カスタマイズをしない場合

htmlとmarkdownを分離して書けるのがいいところ

またレイアウトなどもCSSを読み込ませればいくらでもカスタマイズできるようなのであらかじめいくつか用意すればバリエーションは増やせそう

今回は特にCSSまでは手を出しません

slide用のページを作成する

共通ページを用意

/slide/のパスでスライド用のページを用意します

  • mkdocs.yml
pages:
+   - 'slide': 'slide.md'
  • docs/slide.md
pagetype: slide
  • custom/content.html
{% if meta.pagetype|join("") == 'slide' %}
  {% include "slide.html" %}
{% else %}
  {{ content }}
{% endif %}

meta情報をテンプレート側で参照できるのでそれを使って読み込む対象を変えます

カスタムテーマを使っているのでもはやフロントでできることは何でもできるのですが...

  • custom/slide.html

このページでremark.jsの読み込みとmdファイルの読み込みを行います

各ページからのURLにクエリパラメータを付与してあげてそれを元に対象のmarkdownファイルを特定します

bodyのstyleを上書きしているのはremarkのプレゼンではpaddingが必要ないので打ち消すため

<script src="http://gnab.github.io/remark/downloads/remark-latest.min.js" type="text/javascript"></script>
<script type="text/javascript">
var params         = parseParams();
var locationStr    = location.toString().replace(/\/slide.*/, "");
var mdFileLocation = paramToMdFileLocation(locationStr,params);

document.getElementsByTagName('body')[0].setAttribute("style","padding-top:0px");
var slideshow = remark.create({
  sourceUrl: mdFileLocation
});

function paramToMdFileLocation(locationStr,params){
  var mdFileLocation = ( params["c"] ) ?
    ( params["n"] )
      ? locationStr + "/" + params["c"] + "/" + params["n"] + ".md"
      : locationStr + "/" + params["c"] + ".md"
    : '/notfound.md';

  return mdFileLocation;
}

function parseParams(){
  var queryStr = window.location.search;
  var queryStrings = queryStr.slice(1).split('&');
  var params = [];

  queryStrings.forEach(function(query){
    var keyValue = query.split('=');
    params[ keyValue[0] ] = keyValue[1];
  });

  return params;
}
</script>

各記事の修正

スライドページへのリンクを作成

各ページからのリンクを生成します

各ページの共通部分なのでcontent.htmlに追記します

現在のページの情報を{{ current_page }}で参照できるのでそれを正規表現で分解してあげてmarkdownファイルのパスを取得させます

サブカテゴリがある場合にも対応させました

  • content.html
<a id="slideLink">
  <span class="label label-info">Presentation Mode</span>
</a>

<script type="text/javascript">
var current = (function() {/*
"{{ current_page }}"
*/}).toString();
var path = ( current.match(" - / ") )
    ? 'index'
    : current.replace(/\n|\r/g, "").replace(/.*- \/(.*)\/(.*)\/.*$/g, "$1");
var params = path.split("/");
var linkElement = document.getElementById("slideLink");
var locationStr = location.toString();
params.forEach(function(p){
  locationStr = locationStr.replace("/" + p,'');
});
var slideLocationStr = ( locationStr + "/slide/" ).replace("//slide", "/slide");
if ( params[1] ) {
  linkElement.setAttribute("href", slideLocationStr + "?c=" + params[0] + "&n=" + params[1] );
}
else {
  linkElement.setAttribute("href", slideLocationStr + "?c=" + params[0] );
}
</script>

....
....
{{ content }}
....
....

baseテンプレートの修正

  • base.html

サイト内の検索機能を提供するsearch.jsの読み込みでrequire.jsを使っていて、remark.jsでも使用していて競合していました

スライドページの表示ではそもそも検索機能を提供はしないのでページによって読み込むjsを制御できるように修正します

blockで囲む範囲を広げて通常ページとスライド用ページで読み込むjsを分割しました

{% block content %}
{% if meta.manage|join("") == 'slide' %}
  {% include "content.html" %}
{% else %}
  {% include "content.html" %}
  {# footerの描画処理 }
  {# jsの読み込み処理 }
  <script data-main="{{ base_url }}/mkdocs/js/search.js" src="{{ base_url }}/mkdocs/js/require.js"></script>
{% endif %}
{% endblock %}

patchを当てる

remark.jsではmarkdownファイルが必要なので読み込ませられる場所に配置してあげる必要がありました

これに関してはフロントだけでは解決できなかったのでやむなくパッチを作って当てました

cd ~/.anyenv/envs/pyenv/versions/2.7.10/lib/python2.7/site-packages/mkdocs/utils
patch -p1 < remark_slide.patch

記事の編集

スライドの区切り---を追加してあげると各ページのリンクから

f:id:swfz:20160302140805p:plain

見事スライドも見せることができるようになりました

f:id:swfz:20160302140751p:plain

まとめ

割と簡単に実装できましたが色々残念な部分も残ってしまいました

  • serveモードでは実現できない(mdファイルが読み込めない)
  • フロントのコードをちょっとサボったため.mdのファイルしか読み込めない(mkdocsでは他の拡張子も対応している)
  • スライドの方はCSSを適用させていないので簡素な感じ

何個か上げてみましたが、研修とかで資料とプレゼン資料を兼ねたい場合に使えるんじゃないかと思います

ただ、実際にremark.jsを使ってスライドを作っていないので実際にやってみたら不便があるかもしれません、、、

良くあるHTMLスライドでスライド専用の記述をしなくてはならなくなるのが嫌なので、それをせずにカスタマイズをどのくらいできるか今後試してみたいと思います

最後にサンプルへのリンクを張っておわり

mkdocs_sample