5月ぐらいにdraft.jsを使って、Markdownエディタを作ってみた。普段技術ネタはBlogに書かないのだけど、まだあまり日本語情報も無いのでメモを公開してみる。 draft.jsはいまも活発に開発が進んでる状況なので、この数ヶ月で古くなってる部分があるかもしれません。
draft.jsとは何か
facebookが作っているReact用のリッチテキストエディタ (WYSIWYG) のフレームワーク。
facebookのnote機能や、メッセンジャーのエディタに使われてるらしく、今年に入ってオープンソースになった。ってことでまだあまり情報が無いが、react本家のfacebookが作ってるのもあり、エコシステムができつつある (後述) 。
draft.jsの特徴
draft.jsはあくまでフレームワークなので、そのままReactコンポーネントとして使えるものでは無い。draft.jsは、素のtextareaではなく、contenteditable
属性を使ったdiv
をベースにする。
contenteditable
はブラウザ毎の挙動の違いがあって、扱いが難しい(らしい)が、そこをうまく吸収しつつ、後述するいくつかの概念によってcontenteditable
内を統一的に操作できるようなデータ構造を提供するのがdraft.jsの役割。
ということで、学習コストかかるものの、素のtextareaをゴリゴリやるよりは、保守性と拡張性を保った上でエディタが書けると使ってみて感じた。
draft.jsに登場する概念
だいたい https://facebook.github.io/draft-js/docs/api-reference-editor.html あたりにまとまってる。理解があってるかはまだあまり自信がない。
EditorState
エディタそのものの状態を表す。そのものというのは、入力されている内容物の状態はもちろん、キャレットの位置や、反転選択の状態、undo/redo のための変更履歴などを含めてエディタに対するほぼ全ての変更を指す。
ContentState
入力されている内容物の状態と、キャレットの状態を保持する。EditorState
の一部。内容物は、後述する幾つかのContentBlock
から構成させる (これをblockMap
と呼ぶ)。キャレットの状態は、SelectionState
で構成される。
ContentBlock
基本的にはエディタ内の、1行が1 Blockに相当する。ContentBlock
はcharacterList
という、CharacterMetadata
からなる配列を保持する。
CharacterMetadata
元々draft.jsはリッチテキストエディタ用なので、プレビュー欄みたいなのは無くて、例えば文字列をboldにしたらエディタ内で太字にする必要がある。
ここで、bold
が太字であるという情報を持つのが、CharacterMetadata
Entity
さっきの例でいうと、太字
というのが一つのEntity
に相当する。Entity
は、mutability
という属性を持っていて、ここにIMMUTABLE
という値をセットすると、その文字列 (Entity
) はまとめて削除される。
例えば、メンション補完をして、入力した文字列を、バックスペースで一度に削除するみたいなことができる。
SelectionState
エディタ内のキャレットの状態を指す。
Key / Offset
Key
は、エディタ内のどのブロックにキャレットがいるかを示す。
Offset
は、ブロック内のどの位置にキャレットがいるかを示す。
Anchor / Focus
Anchor
はキャレットを打ち込んだ位置。Focus
は反転選択した際の終点。これにより、forwardで選択してるのか、backfowardで選択してるのかがわかる。
Modifier
ContentState
を操作するための、function郡。基本的にこれを通して、エディタの内容物に変更を加える。
draft.jsとMarkdown
結論からいうと、draft.jsはまだMarkdownをサポートしていない。
理想的には、リッチテキストエディタで視覚的にサポートされた状態で書いたMarkdownテキストを、Markdown記法でエクスポートされると良さそう (Preview欄が不要になる)。 react-rte というReact Componentがそれをやろうとしているが、実際使うとまだいまいちなとこはあって、もう少しdraft.js側でMarkdownのことを考慮されるとよさそう (計画はあるみたい)
ということで、自分が作ったエディタでは、EditorState
にプレーンテキストを出力させて、それをMarkdownとしてレンダリングするようにしている。そして、Markdownの入力をサポートするためのModifier
を自前で書くことにした。
draft.jsのエコシステム
draft.jsをプラガブルに拡張した、draft-js-plugins というものがある。メンション補完や絵文字補完は、これを通して実現できた。
その他にもプラグインや、draft.jsを使ったReact Componentがどんどん出てきてる。有名っぽいのは、 https://github.com/nikgraf/awesome-draft-js にまとまってる。
おわり
draft.jsがリリースされたのが2016年2月末とかなので、まだまだこれからな感じはするけど、勢いがある。職業柄エディタは身近なものなので、つい自分の考えた最強のエディタを作りたくなるけど、実際やってみると改善点が山程あってエディタ沼にはまって出られなくなる。フレームワークに乗っかることで本質的な部分だけに集中して早く沼を抜けられるようになるといいな。