Ghost Blogにテーブル表示を追加する

Ghost Blog は MarkdownレンダリングにShowdownモジュールを利用しているが、何故かそれに含まれるtable拡張を利用していない。幾度か要望がでたり議論もあったりしたのだが、一向に進展しないためテーマレベルで代替策を講じることにしてみた。


jquery.ex-mark-table.js

このスクリプトはjQuery Extensionになっていて、ロードすると jQuery.markTable() メソッドが追加される。これはMarkdown形式のtable書式を含むオブジェクトを渡してやるとHTML-Tableにオンデマンドで整形する。

//
// jquery.ex-mark-table.js
//
// $Id: mark-table.js 143 2015-05-08 09:31:06Z askn $
//
(function ($) {
    $.fn.markTable = function (config) {
        var defaults = {
            className: 'mark-table'
        }
        var options = $.extend(defaults, config);
        return this.each( function (i) {
            var title = $(this).attr("title");
            var $exported = $("<table/>");
            var $body = $("<tbody/>");
            var table = [], align = [], header = [];
            var imported = $(this).html().split(/([^\n]*\n)/);
            while (imported.length) {
                var line = imported.shift();
                if (line) {
                    if (line.length == 0) continue;
                    var column = line.match(/([^\|]*\|)/g);
                    if (column && column.length > 0) {
                        column.shift();
                        table.push($.map(column, function (val, index) {
                            return val.replace(/\|$/, "");
                        }));
                    }
                }
            }
            if (title != undefined) {
                $("<caption/>").text(title).appendTo($exported);
            }
            if (table.length && table[0].length && !table[0][0].match(/(^\:-|-\:$)/)) {
                header = table.shift();
            }
            if (table.length && table[0].length && table[0][0].match(/(^\:-|-\:$)/)) {
                align = $.map(table.shift(), function (val, index) {
                    if (val.match(/^\:\-*\:$/)) {
                        return "center";
                    }
                    else if (val.match(/^\-*\:$/)) {
                        return "right";
                    }
                    else {
                        return "left";
                    }
                });
            }
            if (header.length) {
                var $head = $("<tr/>");
                $.each(header, function (key, val) {
                    var $column = $("<th/>").html(val);
                    if (align[key]) {
                        $column.addClass(align[key]);
                    }
                    $head.append($column);
                });
                $("<thead/>").append($head).appendTo($exported);
            }
            if (table.length) {
                while (table.length) {
                    var $line = $("<tr/>");
                    $.each(table.shift(), function (key, val) {
                        var $column = $("<td/>").html(val);
                        if (align[key]) {
                            $column.addClass(align[key]);
                        }
                        $line.append($column);
                    });
                    $line.appendTo($body);
                }
                $body.appendTo($exported);
            }
            $(this).replaceWith($("<div/>").addClass(options.className).append($exported));
        });
    };
})(jQuery);

// End of Script

これをさらに次のようにしてindex.js等から呼び出す。1

(function pageInit () {
    $('code[class^="language-"]').each(function () {
        var $this = $(this);
        var attr = $this.attr('class').replace(/^language-?/, '');
        var match = attr.match(/title:(.*)/);
        if (match) {
            $this.parent().attr('title', match[1]);
            attr = attr.replace(/title:.*/, '');
        }
        if (attr != null && attr.match(/^table/)) {
            // マークダウンテーブル
            $this.parent().html($this.html()).markTable();
        }
    });
})();

Markdownの方では、ネイティブにMarkdownテーブル書式を書くことが出来ないので、全体をコード書式で括るようにする。このとき三連バッククォート2の直後にtableというキーワードを付与する。更にtaitle:キーワードを足すとここから行末までをテーブルキャプションにすることが出来る。3

  ```table title:テーブルキャプション
  |foo |bar   |baz  |
  |:---|:----:|----:|
  |Left|center|right|
  ```

これを保存するとGhostは次のHTMLコードに変換する。

  

{gfm-js-extract-pre-1}

付与したキーワードにlanguage-前置詞が付いたclass名として埋め込まれるのがミソで、ようはこれをjQueryで検出してクライアントサイドでpreをまるごとtableセットに書き換えてやる。あとはCSSで表示を整形4してやればよい。

|foo |bar   |baz  |
|:---|:----:|----:|
|Left|center|right|

これの実際のHTMLはつぎのように展開されている。

  <div class="mark-table">
    <table>
      <caption>テーブルキャプション</caption>
      <thead>
        <tr>
          <th class="left">foo </th>
          <th class="center">bar   </th>
          <th class="right">baz  </th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td class="left">Left</td>
          <td class="center">center</td>
          <td class="right">right</td>
        </tr>
      </tbody>
    </table>
  </div>

今後ネイティブにMarkdownテーブル記法が実装された場合は(本実装との互換性はともかく)三連バッククォートを除去するだけで対応できるはずだ。


  1. 実際のコードではここで同時に SyntaxHighliter の変換処理も行っている。

  2. プレビュー内容が改行なしで詰め込み表示になるのを避けるため、コードブロックで括るようにした。現状、これ以外の方法で改行をプレビューに反映させることはできない。

  3. class名としてHTMLに埋め込まれる都合上、titleにダブルクォートは記述できない。

  4. markTable()のオプションにtableタグのclassNameを指定でき、省略値はtable.mark-tableになっている。

RECENT LINKS