Slate.js入門！リッチテキストエディタを作る

# 最初に

テキストエディタが作成できるライブラリはたくさんあります。例えばfacebookが公開した[Draft.js](https://github.com/facebook/draft-js)はReactで使いやすいため有名だと思います。最近ではMicrosoftが公開した[rooster.js](https://github.com/Microsoft/roosterjs)などもあります。今回はたくさんのテキストエディタフレームワークの中から、[Slate](https://github.com/ianstormtaylor/slate)というものを選択してみました。Slateに関する日本語記事をほとんど見かけなかったので、今回はこちらをネタに書いてみます。（Draft.jsはぼちぼち見かけました）

### Slate.jsとは
SlateはReactとImmutableの上に構築されたフレームワークです。Reactを使っている開発者であれば、かなり直感的にテキストエディタを構築できます。まだBeta版なので突然APIが消えたりする事もありますが…。

**GitHub**
https://github.com/ianstormtaylor/slate
**ドキュメント**
https://docs.slatejs.org

### できあがりのイメージ
こんな感じのエディタが簡単に仕上がります。

<img width="400" src="https://qiita-image-store.s3.amazonaws.com/0/10499/ecdc12c9-98c4-e14b-0407-80a08c07768a.gif">


# エディタをつくる！..ための準備をする

### プロジェクト作成
さっそくエディタをつくっていきましょう。とはいえ、Reactが動く環境をまず整えないといけません。[create-react-app](https://github.com/facebook/create-react-app)を使ってプロジェクトを生成します。

```terminal
$ create-react-app 201812-react-slate-app
$ cd 201812-react-slate-app
```

Slateをインストールします。Slateは[immutable](https://github.com/facebook/immutable-js)にも依存しているので、それもインストールします。

```terminal
$ yarn add slate slate-react immutable
```

### App.js,index.cssを編集する
初期のApp.jsはReactのロゴを描画していますが、まるっと削除してしまいます。この後、`src/components/TextEditor.js`を作成して、そこでSlateの設定を行います。なのでApp.jsは`<TextEditor />`をただレンダリングするだけにしておきます。

```jsx:App.js
import React from "react";
import TextEditor from "./components/TextEditor";

class App extends React.Component {
  render() {
    return <TextEditor />; /* TextEditor.jsをレンダリングする */
  }
}

export default App;
```

ちなみにindex.cssはこんな感じにしてあります。好みですが、等幅フォントに設定してあります。

```css:index.css
body {
  margin: 0;
  padding: 0;
  font-family: Consolas, monospace;
  background-color: rgb(240, 240, 240);
}
```

# エディタをつくる！
ここからSlateを使ってエディタを作成していきます。いったん下記の状態を作ります。

<img width="350" src="https://qiita-image-store.s3.amazonaws.com/0/10499/51044e0a-ea72-46d6-1e18-6e2dfd5941ff.gif">

### 動かしてみる
まずは**とりあえずエディタが動く**ようにしましょう。ちょっと長くなりますが、以下のようになります。とりあえずこれを貼り付けて`yarn start`すれば、エディタが動くようになっているはずです！

```jsx:TextEditor.js
import React from "react";
import { Editor } from "slate-react";
import { Value } from "slate";

const initialValue = Value.fromJSON({
  document: {
    nodes: [
      {
        object: "block",
        type: "paragraph",
        nodes: [
          {
            object: "text",
            leaves: [{ text: "Hello Slate.js!!!" }]
          }
        ]
      }
    ]
  }
});

class TextEditor extends React.Component {
  state = {
    value: initialValue
  };

  onChange = ({ value }) => {
    this.setState({ value });
  };

  onKeyDown = (e, editor, next) => {
    return next();
  };

  render() {
    return (
      <Editor
        value={this.state.value}
        onChange={this.onChange}
        onKeyDown={this.onKeyDown}
        renderMark={this.renderMark}
      />
    );
  }
}

export default TextEditor;

```

### 解説
TextEditor.jsから重要な部分だけピックアップして解説を記載します。
読んでいただければわかると思うのですが、`render()`で描画しているのは`<Editor />`コンポーネントのみです。これがレンダリングされる部分がエディタとなります。プロパティもたった３つだけです。

先頭に設定した`value={this.state.value}`はstateから取得した値をセットしています。`state.value`にはエディタの状態（テキストの文字列や修飾された内容）を保存してあります。これの型はSlateのValueオブジェクトとなっています。このサンプルでは`initialValue`というValueオブジェクトの初期値を定義してからReactのstateにセットしています。

```jsx:Editorコンポーネント
state = {
  value: initialValue /* 初期値はSlateのオブジェクトを渡す */
};

render() {
  return (
    <Editor
      value={this.state.value}   /* エディタの値。Reactのstateに保持させている */
      onChange={this.onChange}   /* エディタの中身が更新されたとき */
      onKeyDown={this.onKeyDown} /* キーが押されたとき */
    />
  );
}
```

以下の2つのアロー関数はEditorコンポーネントに渡す必要があるものです。`onChange`はEditorに設定したvalue（SlateのValueオブジェクト）が更新されたときに呼ばれます。ここでstateに渡して保持させています。
`onKeyDown`はキー入力されたときです。ここでは`return next()`としています。これがないと改行やバックスペースを受け付けてくれません。

```jsx:アロー関数
/* Editorの中身が更新される（テキスト入力がなされる）ときに呼ばれる */
onChange = ({ value }) => {
  this.setState({ value });
};

/* キー入力の受付。使っていない引数は、あとで使う予定 */
onKeyDown = (e, editor, next) => {
  return next();
};
```

ただし、まだこの状態だとtextareaタグと同等くらいにしかなりません。ここから、リッチテキストエディタらしい振る舞いを実装していきます。

### マウスで選択したエリアを太文字にする

テキストの一部を選択し、Cmd + bと入力することで太文字にしてみます。TextEditorを以下のように修正してみます。

* onKeyDownを修正
* renderMark関数を追加
* EditorコンポーネントにrenderMark関数を渡す

<img width="350" src="https://qiita-image-store.s3.amazonaws.com/0/10499/91c239de-318b-05db-8518-a5187f2591d5.gif">

```jsx:TextEditor.jsを修正
// 修正する
onKeyDown = (e, editor, next) => {
  if (!e.metaKey) {
    // cmdキーが入力されていなければ、エディタとしてそのままの動作
    // （キー入力がそのままエディタに反映される）
    return next();
  }

  // ブラウザの処理を制止（よく使います）
  e.preventDefault();

  switch (e.key) {
    case "b": { // cmd + bのときに太文字
      editor.toggleMark("bold");
      return;
    }
    default: {
      return;
    }
  }
};

// 新しく定義
renderMark = (props, next) => {
  switch (props.mark.type) {
    case "bold": { // 太文字にするため<strong>で囲う
      return <strong {...props.attributes}>{props.children}</strong>;
    }
    default: {
      return;
    }
  }
};

render() {
  return (
    <Editor
      value={this.state.value}
      onChange={this.onChange}
      onKeyDown={this.onKeyDown}
      renderMark={this.renderMark} // テキストを修飾するときに呼ばれる
    />
  );
}
```

### 解説
新しく`renderMark`関数が追加になりました。これがリッチテキストエディタを作るに欠かせない重要な関数で、テキストを修飾する役割を持ちます。ここでは、引数に`"bold"`が渡ってきたときに`<strong>...</strong>`として修飾を行います。

```jsx:文字を修飾する例
onKeyDown = (e, editor, next) => {
  // 省略
  editor.toggleMark("bold"); // キー入力条件でrenderMarkを呼び出す
};

renderMark = (props, next) => {
  switch (props.mark.type) {
    case "bold": { // 引数を判定し、マッチしたら<strong>で囲う
      return <strong {...props.attributes}>{props.children}</strong>;
    }
  }
};
```

つまり、`renderMark`にパターンをどんどん追加していけば、いろいろな修飾を実装することが可能になります。

```jsx:パターンによって異なる修飾をさせる例
renderMark = (props, next) => {
  switch (props.mark.type) {
    case "bold": {
      // 太文字にする
      return <strong {...props.attributes}>{props.children}</strong>;
    }
    case "italic": {
      // イタリック体にする
      return <em {...props.attributes} property="italic">{props.children}</em>;
    }
    case "underlined": {
      // 下線を引く
      return <u {...props.attributes}>{props.children}</u>;
    }
  }
};
```

# 冒頭のエディタを実装する
この記事の一番上でお見せしたgifのエディタを作成します。Slateの振る舞いに関してはだいたい説明が終わったので、後は使える修飾子を増やしたり、アイコンのクリックを追加したり、デザインを整えればOKです。Material UIのアイコンを使えば、よりいっそうテキストエディタっぽくなります。

<img width="350" src="https://qiita-image-store.s3.amazonaws.com/0/10499/ecdc12c9-98c4-e14b-0407-80a08c07768a.gif">

完成した[ソースコード](https://github.com/ksakiyama/slate-sample-app)はこちらに置いておきますので、よかったら参考にしてください。

### 修正内容をざっくりと紹介

```jsx:TextEditor.jsの修正内容
// アイコンがクリックされたらtoggleMark
onIconClick = (e, name) => {
  e.preventDefault();
  this.editor.toggleMark(name);
};

// エディタの内容を保持するための関数
ref = editor => {
  this.editor = editor;
};

render() {
  const { classes } = this.props;
  return (
    <div className={classes.root}>
      {/* Material UIのPaperを使って影をつける */}
      <Paper className={classes.iconsPaper} elevation={6}>
        <FormatBoldIcon
          className={classes.icon}
          onClick={e => this.onIconClick(e, "bold")} // onClickで修飾する
        />
        <FormatItalicIcon
          className={classes.icon}
          onClick={e => this.onIconClick(e, "italic")} // onClickで修飾する
        />

        {/* 省略 */}

      </Paper>
      <Paper className={classes.editorPaper} elevation={6}>
        <Editor
          className={classes.editor}
          value={this.state.value}
          onChange={this.onChange}
          onKeyDown={this.onKeyDown}
          renderMark={this.renderMark}
          ref={this.ref}
        />
      </Paper>
    </div>
  );
}
```

# 参考
[Medium - Let’s build a fast, slick and customizable rich text editor with Slate.js and React](https://medium.freecodecamp.org/lets-build-a-customizable-rich-text-editor-with-slate-and-react-beefd5d441f2)