マークダウン、皆さん使っているでしょうか。使っていますよね、GitHub(GFM)やSlack(一部記法)、個人的なメモアプリ等など、マークダウンを使えるサービスは多いので、使ったことがある人は多いんじゃないかと思います。
そして弊社のDocBaseというサービスは、マークダウンを使って情報共有をするサービスです。これも皆さん使っていますね?
この度は、そんなDocBaseのメモ編集画面で、ハイブリッドエディタなるものをリリースしました。これはDocBaseに存在したいくつもの不満、問題を解消してくれるもので、これが一体どういったもので、どうやって作られたのかを書いていきます。
ハイブリッドエディタとは??
「ハイブリッド」何やらガソリンと電気を使って動きそうな印象を受ける言葉ですが、もちろんそういった意味はありません。
次の動画を見てください。
左側がマークダウン、右側がHTMLのプレビューとなっています。よくあるマークダウンエディタに見えるかもしれません。
「うーん、ハイブリッドと言っても、普通じゃね?」と思ったそこのあなた!もう一度、よく見てみてください、右のプレビュー側をクリックして、直接クリックした位置の文章を編集していますよね。そしてその変更は左側のマークダウンに反映されています。
実は右側の表示はただのプレビューではなく、WYSIWYGエディターになっているんです。左側のテキストエディタがいつでも編集できる。さらに右側のWYSIWYGエディターも何時でも編集できる。これが「ハイブリッド」と名付けた所以です。
細かいことは置いておいて「ハイブリッド」って感じがしますよね。声に出したくなると思います。「ハイブリッド」「ハイブリッドエディタ」どうでしょうか?声に出しましたか?「俺は今日からハイブリッドエディタを使うぜ!」「俺、またなんかやっちゃいました?ただのハイブリッドエディタですけど」など、声に出して言ってみてください。かっこいいですよね。
DocBaseの従来の問題点
DocBaseの従来のエディタにはいくつかの問題点があり、それがUXの低下やサービスの質を低下させる原因になっていました。大まかには次のような問題点が上げられます。
CommonMarkに準規していない
マークダウンと言っても、それには統一的な仕様書が無かったです。そのために細かい動作は実装次第となっている部分があります。実際にDocBaseでも、GithubFlavoredMarkdown(GFM)というマークダウンの方言のパーサを使っており、しかもパーサ自体の開発、更新が終わっているバージョン古すぎるものを使っていました。
そして、そんな「マークダウンの仕様が無い状況を解決しよう」として策定されたCommonMarkという仕様があります。これが広く使われ出しており、これに従ってサービスを作ることによりCommonMarkに準規しているアプリやサービス間での情報のポータビリティが上がります。これはユーザにとっては非常に大きなメリットですよね。
けれども、DocBaseはCommonMarkには移行していませんでした。GFMに追加して差し込みメモ(#{}
を書くと他のメモを埋め込める機能)を使えるようにしたり、画像のマークダウン![](url)
を拡張して画像サイズ指定を可能にするなどの独自の拡張をしてしまっているために、単純にマークダウンパーサをCommonMarkパーサに変更すれば良いというものでは無かったのです。
ちょっとマークダウンの種類に関して整理しておきます。
- 従来のDocBase
- CommonMarkに準規する前のGFMに、差し込みメモや画像サイズ指定などの改造を加えた実装を使っていた
- 今後のDocBase
- CommonMarkに準規した後のGFMに、差し込みメモや画像サイズ指定などの改造を加えた実装を使うようにしたい
プレビューが遅い
従来の方法では、マークダウンをhttpでサーバに送り、サーバでマークダウンをパースしてからブラウザに返しているため通信の時間がかかりました。また、ブラウザからサーバにマークダウンを送信するタイミングは、サーバの負荷を考慮してユーザの入力が終わってから500ミリ秒経過後となっていました。そのため、ユーザが入力したマークダウンがHTML化され、それがブラウザ上で表示されるまでに数秒かかってしまっているのです。ユーザは、入力したマークダウンが想定した結果通りになるかどうかを確認するためには数秒待つ必要があるので、UXの低下を引き起こします。
マークダウンを学習する必要がある
そもそも、マークダウンを知らないと、サービスを使うことを躊躇してしまいます。マークダウンを知らないと使えないサービスというのは、サービスを使える人が限られてしまいます。これは情報共有サービスとしては致命的な問題です。情報共有サービスとしてはチームの誰もが、簡単に、情報を共有できるサービスである必要があります。
ハイブリッドエディタにむけて
これまでに上げたような問題を解決するためには何が必要なのかを考えました。
「CommonMarkに準規していない」この問題を解決する方法は単純で、パーサをCommonMarkにすることです。しかしそのパーサは、DocBaseの差し込みメモなどの独自実装に対応する必要があるため、パース部分がブラックボックスになっているようなパーサは使えません。AbstractSyntaxTree(AST)を組み立て、それに変更を加えることにより出力を柔軟に変更できる、そんなCommonMarkパーサのライブラリを探すことにしました。
次に、「プレビューが遅い」問題の解決方法を考えます。理想的にはユーザが入力した途端にプレビューが更新されて欲しいです。それにはサーバ側にパーサがあるのでは絶対にダメです。クライアント側にCommonMarkパーサがある必要があります。
最後に「マークダウンを学習する必要がある」の件。これを解決するには究極的には「マークダウンを書かなくても良いこと」です。つまりWYSIWYGエディタです。WYSIWYGエディタであれば、マークダウンの文法を知らなくても、UIによって見出しやリスト、装飾などを付けることもできれば完璧です。しかし、現在マークダウンエディタを気に入って使っているユーザはWYSIWYGは使いたくないかもしれません。それを両立する必要があります。
マークダウンとWYSIWYGを扱えるライブラリは、ProseMirrorやTOAST UI Editorなどが存在し、これらは、マークダウンとWYSIWYGを切り替えて使えます。
ただ、どうせなら切り替えるのではなく、同時に使いたいですよね!そう、ハイブリッドエディターとして最初に見せたように、プレビュー側を直接編集したいのです!それにはマークダウンのテキストエディタとWYSIWYGエディタを同時に扱える「ハイブリッドエディタ」を開発する必要があるのです!というわけで、WYSIWYGエディタをフルスクラッチで開発することにしました。
無謀だと思いましたか?
はい、そう思います。ちょっとチャレンジングで面白そうだったので開発を始めてみました。正直、開発初めて最初の1年ほどは、これを本当にリリースできる品質まで持って行けるのか不安でした。
パーサの選定
WYSIWYGエディタはフルスクラッチと言っても、CommonMarkパーサはさすがにライブラリを使います。独立した機能で、仕様もはっきりしていますからね。
パーサへの要件を整理しましょう。必要な要件はこのようになります。
- ASTを扱えるCommonMark+gfmパーサであること
- それはクライアント側で動作すること
クライアント側で動作するという要件があるので、JavaScriptを使うことが考えられます。そしてJavaScriptのCommonMarkパーサといえば、commonmark.jsですね。これはASTも取得でき、完全にCommonMark準規と、要件を満たしているように思えます。
ですが、DocBaseで投稿できるマークダウンは10万文字までという制約があります。はたしてcommonmark.jsは10万文字のパースという試練に耐えることができるのか・・・。10万文字規模のマークダウンをcommonmark.jsでパースしてみたところ、パースの度にブラウザがフリーズします。うーん、これでは文字入力の度にブラウザがフリーズしてしまいます。パーサの要件に「10万文字の文書を入力中にブラウザがフリーズしないこと」を追加しなければいけません。
このフリーズを解決するのにWASMを使うという案が上がりました。WASMで実行すれば非同期で処理されるので、パースに時間がかかったとしてもユーザの操作がフリーズすることは無くなります。WASMとして使えるマークダウンパーサを調査したところ、要件全てを満たしているであろうcomrakというRustのCommonMarkパーサが見つかったため、これをWASMとしてビルドして使用することにしました。
WYSIWYGどうする?
ここまでの話では、マークダウンパーサをCommonMark準規のものにしたというだけです。WYSIWYGエディタの話にはまだ入っていないですね。では、WYSIWYGエディタはどのように実装したら良いでしょうか。
ハイブリッドにするために、右側のプレビューのどこかを選択したらエディタのその位置を選択してほしい、逆に、左側のエディタのどこかを選択したら、プレビュー側のその位置を選択してほしいです。なので、マークダウン位置とHTML位置の双方向の対応付けが必要です。今回使用することにしたパーサのcomrakでは、--sourcepos
オプションを使うことにより、それぞれのHTMLタグにdata-sourcepos
という属性を埋め込んでくれる機能があります。
たとえば# heading
という文字列をパースした出力は<h1 data-sourcepos="1:1-1:9">heading</h1>
になります。これは、そのHTMLタグがマークダウンの何行目の何文字目から何行目の何文字目までに記載されたものなのかという情報が入っています。多少工夫が必要ですが、これでマークダウンとHTMLの対応付けをすることはできました。
空行の扱い
悩ましいのが空行の扱いです。実はCommonMarkは改行しただけでは成果物は改行されません。CommonMarkでは、その行の最後が2つ以上のスペース、もしくはバックスラッシュ\
で終わっている場合に改行タグが出力されるという仕様になっています。
それではさすがに使いにくいということで、GFMでは改行をしただけで改行タグが出力されます。便利ですね。ただそれは、2連続で改行をしたとしても、HTMLに改行タグが2つ入るわけではありません。1つになります。
これはWYSIWYGで編集するときに、非常に違和感を感じる原因になります。WYSIWYGを使っていて、改行を何度も入力しているにもかかわらず、改行が1回しか反映されないのはおかしいですよね?そのため、空行に改行を入れるように、パーサに渡す前のマークダウンに手を入れています。
その他諸々
ここまでくればそう悩ましい問題は少ないです。WYSIWYGに必要なこと、たとえば下記のようなことを使えるように実装していきます。
- 選択文字を太字にしたり色を付けたり装飾する機能
- UIによる画像のサイズ変更
- UIによるテーブル編集
- UIによるリストアイテムの並び変え
- 諸々・・
マークダウンエディタをハイブリッドにするために必要なこと
話をまとめます。DocBaseで言うハイブリッドエディタとは左側のテキストエディタの編集が即座に右側のプレビューに反映され、また逆に右側のプレビューを選択もできるし編集もできて、それが即座に左側のテキストエディタに反映されるものです。そしてそれを実現するために必要なことは
- ブラウザ上で高速にマークダウンをパースできるパーサ
- マークダウンとhtmlの位置の完全な対応付け
- そしてWYSIWYGを実装するための気合いと根性、時間とお金(2年かかった・・)
でした。このハイブリッドエディタはついにβ版ではなくなり、本リリースとなりました。さらなる進化をご期待ください。
このエントリーに対するコメント
日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)
- トラックバック
「いいね!」で応援よろしくお願いします!