リアルタイム共同編集可能なMarkdownエディタ「HackMD」をハックしてみた

家で野生のカビゴンが出てきて無事にGetできて、ここ最近で一番興奮した中村です、こんにちは。

今日は、リアルタイムでテキスト共同編集ができるMarkdownエディタHackMDというOSSツールをhackしてみたので、そのhack方法についてご紹介します! HackMDは、ヌーラボのリモートワークやミーティングでの議事録ツールとしても活用しており、導入の背景や他のツールとの使用比較についてもまとめました。

( CC BY-SA : https://www.flickr.com/photos/dreamsjung/6212494746 )( CC BY-SA : https://www.flickr.com/photos/dreamsjung/6212494746 )

背景:オンラインでのリアルタイムコミュニケーションを円滑に

ヌーラボは、国内だけでも福岡・東京・京都、さらに海外も含めるとニューヨーク・台湾・シンガポールなど、各地域に拠点があり、たくさんのスタッフがいます。拠点間でも、朝会など日常的にコミュニケーションが発生するので、face to faceじゃなくともオンラインでうまくコミュニケーションできる仕組みを必要としています。

今ヌーラボで使っている、リアルタイムコミュニケーションをサポートするツールは、以下の通りです。

分散拠点開発・リモートワークを支える技術として、これらを一つ一つ紹介していくのも面白いとは思いますが、今回はテキスト共同編集ツールについて取り上げます。

HackMDとは

HackMDとは、Web上で共同編集機能付きのMarkdownエディタです。

ヌーラボでは、オンラインでのミーティングなどで利用しています。ミーティングが始まる際にHackMD上にノートを用意しておき、リアルタイムでそのノートにミーティング内容を書き出していくことによって、双方の認識ずれを防ぎます(テキストだけでは表現しづらいものについては、Cacooなどを使ってお互いのイメージをすりあわせていくこともあります)。

上記の用途に、以前はEtherpadというツールを使っていました。Etherpadと比べたHackMDの利点は、以下の通りです。

  • 日本語を同時入力可能!
  • Markdownで、構造化したドキュメントを表現可能(見出し・画像埋め込み・コードハイライトなど)

(現在のEtherpadはちゃんと検証していないのですが)リリース当初の古いEtherpadは、複数人が同時に日本語を入力しようとすると入力内容が崩れるという、かなりフラストレーションがたまる不具合がありました。また、Etherpadでは箇条書きなどいくつかのシンプルな記法はサポートされていたのですが、例えば画像埋め込みなどのより表現力が高い記法が使えず、長いテキストだと見づらくなるという欠点がありました。

それらの欠点を解消できるようなエディタを探していたとき、HackMDを触ってみて「これイイね!」となり、ついかっとなって社内に導入しました。

capture-2016-09-19-20-44-48(画像や見出し入りノートの実サンプル)

インハウスHackMD

HackMDは「HackMD – 共同編集できるMarkdownエディタ」 で提供されており、事前準備することなく利用することができます。ですが、ここに書いた内容は(公開設定を変更しない限り) 全員に見えるところに公開されてしまいます。業務用途には、セキュリティを考慮してインハウスに独自に立てたほうがよいでしょう。Dockerイメージも提供されており、数コマンドで自社の環境に展開することができます。

ヌーラボでは、公式提供されているdocker-composeファイルをカスタマイズしてAmazon ECS上に展開しています。AWSから公式でecs-cliが提供されており、ecs-cliを使うとdocker-composeの構成をECS上でそのまま展開できます。

hack方針

hack方針として、既存のソースコード(JavaScript)自体は極力直接修正しない方針としています。これは、HackMDの動作環境丸ごとDockerイメージ化されているので中身のJavaScriptファイルは修正しにくいためです。代わりに、Docker実行時のエントリポイントであるdocker-compose.ymlは、結構手を入れています。

また、JavaScriptファイルを修正しないとどうしてもカスタマイズが難しい場合は、今後のHackMDのアップデートでぶっ壊れないように考慮したやり方を取っています。

下記で詳細説明していく、カスタマイズしたdocker-composeファイルの全コードは以下に置いています。

hack1 : Basic認証

上述したように、デフォルトだとHackMD上で作成したノートは、全部公開状態となっています。各ノートにはランダムなURLが生成されるため推測されづらいのは確かですが、より安全に運用するためにBasic認証をかけています。

具体的には、docker-composeファイルでnginxを起動するように追加します。

nginx:
  image: nginx
  links:
    - hackmd
  ports:
    - "80:80"
  volumes:
    - /home/ec2-user/docker-hackmd/customize/nginx.conf:/etc/nginx/conf.d/default.conf
    - /home/ec2-user/docker-hackmd/customize/.htpasswd:/etc/nginx/.htpasswd

ここで参照されるnginx.confは、下記の通り最低限のシンプルなものです。.htpasswdでBasic認証をかけた上で、hackmd:3000にproxyしています。また、HackMDではWebSocketを使うので、UpgradeヘッダなどWebSocket用の設定も追加しています。

server {
    listen 80 default_server;

    location / {
        auth_basic "hackmd";
        auth_basic_user_file /etc/nginx/.htpasswd;

        proxy_pass http://hackmd:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

なお、当初はBasic認証の代わりに、各オフィスのIPアドレスでアクセス制限をしていました。ですが、社外の人や、オフィス外からのアクセスがしづらいという意見があったため、Basic認証にしています。

hack2 : 課題キーに対して Backlog への自動リンク

社内のタスク管理にはもちろんBacklogを使っており、HackMDでやりとりする内容にもBacklogの課題キー(チケット番号のようなもの、”BLG-1234″といった表記で課題を一意に表す)が多く記載されます。このキーを、自動で”https://demo.backlog.jp/view/BLG-1234″にリンクするようにしました。

これはどちらかというと、HackMD自身というより、HackMDで利用しているmarkdown-itというライブラリのカスタマイズ。結構右往左往したのですが、markdown-itのドキュメントに書いている通り、rendererをフックしてやって、該当の文字列だったらリンクを付与するというやり方で実現できます。

// --- customize --- //

// link to backlog (only demo space)
var defaultRender = md.renderer.rules.text;
md.renderer.rules.text = function (tokens, idx) {
    var defaultContent = defaultRender(tokens, idx);

    // part of other link, like 'https://demo.backlog.jp/BLG-1'
    if (tokens[idx].level > 0 && tokens[idx-1] && tokens[idx-1].type == "link_open")
        return defaultContent;

    return defaultContent.replace(/([A-Z][A-Z0-9_]+-\d+)/g, '<a href="https://demo.backlog.jp/view/$1">$1</a>');
};

上記のファイルを’link-to-backlog.js’としておき、docker-compose実行時に適切なJSファイルへ追記するように、docker-composeファイルを修正します。

(2016/12/29 追記:HackMD 0.4.6からwebpackを使うようになって、JSファイルの構成が変わったのに合わせて修正)

volumes:
    - /home/ec2-user/docker-hackmd/customize/link-to-backlog.js:/tmp/link-to-backlog.js
command: >
  bash -c "
    # link to backlog
    if ! grep -q 'customize ---' /hackmd/public/build/*.index.*.js ; then
      cat /tmp/link-to-backlog.js >> /hackmd/public/build/*.index.*.js
      echo 'add customize process to *.index.*.js'
    fi

    # default command
    /bin/bash /hackmd/docker-entrypoint.sh
  "

hack3 : 現在日付入りのノートテンプレート

新規ノート作成時、テンプレート的なものを用意したいということはよくあります。私が作成するノートは、ほとんどのものが以下のように’tag’と現在日付を用意する形式を取っていました。

###### tags: `backlog`

# Sprint Review Meeting - 2016/09/19

毎回このようなテンプレートを作成するのもちょっとした手間だったので、何かできないかを試行錯誤していました。現在日付が入らなければ固定のテンプレートで問題なかったのですが、現在日付は動的に決まるので、どうしたものかというところで悩んでいました。

最終的には、以下のスクリプトを用意しました。HackMDでは”public/default.md”というファイルがテンプレートファイルとして指定されているので、そのファイルを書き換えるスクリプトです。このスクリプトをcronで毎時実行してやると、日付が変わったタイミングでテンプレートも更新されます。

#!/bin/sh

TEMPLATE=$(cat << _EOT_
###### tags: \`backlog\` \`cacoo\` \`typetalk\` \`nulab account\`

# XXX Meeting - $(TZ=Asia/Tokyo date +'%Y/%m/%d')
_EOT_
)

docker exec $(docker ps | grep hackmdio/hackmd | awk '{print $1}') sh -c "echo '$TEMPLATE' > /hackmd/public/default.md"

hack4 : imgur の特定アカウントへのイメージアップロード

HackMDでは、ドラッグアンドドロップやクリップボードからの画像貼り付けに対応した、imgurへの画像アップロード機能が備わっています。ですが、どうやらこれは匿名アカウントでアップロードされるため、誤って公開すべきでない画像をアップロードした場合でも削除ができないようでした。なので、自身が指定したimgurアカウントへアップロードされるようにして、削除含めてアップロードした画像を管理できるようにしました。

具体的には、これまたdocker-composeの実行時に、app.js中の”imgur.setClientId”を”imgur.setCredentials”に置き換えています。

command: >
  sh -c "
    # link to backlog
    ...

    # imgur credentials ( to upload an image to specific account )
    USERNAME='XXXXXXXX'
    PASSWORD='YYYYYYYY'
    CLIENTID='ZZZZZZZZ'
    sed -i \"s/imgur.setClientId(config.imgur.clientID);/imgur.setCredentials('$$USERNAME', '$$PASSWORD', '$$CLIENTID');/\" app.js
    echo 'set imgur credentials to all.js'

    # default command
    /bin/bash /hackmd/docker-entrypoint.sh
  "

なお、HackMDの設定項目にimgurのclientIdを指定する項目がありますが、これだけでは不十分でした。これは、”imgur.setClientId”にのみ影響する設定であり、あくまでclientIdだけ変更できるもので、アカウントを指定してアップロードするようにはできなかったためです。そのため、JavaScriptファイルの中身をsedで置き換えているというわけです。

(2016/12/29 追記:社内ではimgurからs3にアップロードするようにしたので、大本のgistファイルからは削除)


この記事では、ヌーラボでHackMDを使うようになった背景と他のツールとの使用比較、そして利用するにあたってのhack方法を紹介しました。Markdownでのドキュメント管理に関する記事「インフラエンジニアのための本を執筆しました&執筆環境を紹介します 」もよろしければ参考にしてみてください。

ヌーラボでは、ついかっとなってhackしちゃうエンジニアを募集しています。実際の応募の前に「ちょっと話を聞いてみたい」という要望も受け付けていますので、みなさんの気軽な応募をお待ちしています。

より良いチームワークを生み出す

チームの創造力を高めるコラボレーションツール

製品をみる