Reactでマークダウンエディタ作成とマークダウンからHTMLに変換（ハイライト付き）

Reactでマークダウンエディタの作成と、マークダウン表記をHTMLに変換（ハイライト付き）してみました。
ライブラリを組み合わせることで、楽に実装することができます。

## 環境

- OS : macOS Catalina 10.15.3
- node.js : 12.13.1
- React : 16.12.0

Reactのライブラリは以下を使用します。

- react-simplemde-editor(EasyMDE) : 4.1.0
- marked : 0.8.0
- highitjs : 9.16.2

参考までに、紹介するコードはGitHubにあります。

[https://github.com/txkxyx/react-mde](https://github.com/txkxyx/react-mde)

## マークダウンエディタを作成する

Webの画面にマークダウンエディタを埋め込む場合、元々`SimpleMDE`というライブラリが使用されていましたが、今は`EasyMDE`という`SimpleMDE`をフォークして作られたライブラリが使用されています。

↓EasyMDE

[https://github.com/Ionaru/easy-markdown-editor](https://github.com/Ionaru/easy-markdown-editor)

今回は`EasyMDE`のReactラッパーコンポーネントである`react-simplemde-editor`を使用してマークダウンエディタを作成します。

↓react-simplemde-editor

[https://www.npmjs.com/package/react-simplemde-editor](https://www.npmjs.com/package/react-simplemde-editor)

まずは使用するパッケージをインストールします。

```console
$ npm install react-simplemde-editor
```

コンポーネントを作成します。
実装はとてもシンプルです。`SimpleMDE`のコンポーネントと`EasyMDE`のcssファイルをインポートします。
`SimpleMDE`コンポーネントには`onChange`イベントで起動する関数を渡す必要があります。この関数は合成イベントを受け取り、その値がエディターで入力したマークダウンになります。

```jsx
import React, { useState } from "react";
import SimpleMDE from 'react-simplemde-editor';
import 'easymde/dist/easymde.min.css';

const MarkDownEditor = () => {

    const [markdown, setMarkdown] = useState('');

    return(
        <SimpleMDE onChange={(e) => setMarkdown(e)}/>
    )
}
export default MarkDownEditor;
```

![video1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239179/12dec225-58a4-ebec-c1e6-03bb6c598da1.gif)


ツールバーにメニューが表示されています。デフォルトでは以下のメニューが表示されます。

| メニュー | 説明 | 表記 |
| --- | --- | --- |
| bold | 強調 | `****` |
| italic | 強勢 | `**` |
| heading | 見出し | `#` |
| quote | 引用 | `>` |
| unordered-list | 箇条書き | `*`|
| ordered-list | 番号付きリスト | `1.` |
| link | リンク挿入　| `[](https://)` |
| image | 画像挿入　| `![](https://)`|
| preview | プレビューモード| なし |
| side-by-side | 入力とプレビューで画面分割| なし |
| fullscreen | 全画面でプレビューモード| なし |
| guide | マークダウン記法ガイド | なし　|

メニューの一覧は以下を確認してください。

[https://github.com/Ionaru/easy-markdown-editor#toolbar-icons](https://github.com/Ionaru/easy-markdown-editor#toolbar-icons)

メニューを追加する場合は以下のように実装します。
メニューを追加するとデフォルトのメニューが消えてしまうので、デフォルトのメニューを追加したい場合は別途追加します。

```jsx
import React, { useState } from "react";
import SimpleMDE from 'react-simplemde-editor';
import 'easymde/dist/easymde.min.css';

const toolbar = [
        {
            name: "save",
            action: function customFunction(editor) {
                alert(editor.value())
                // save action
            },
            className: "fa fa-save",
            title: "Save"
        },
        '|',
        'bold',
        'italic',
        'heading',
        '|',
        'quote',
        'unordered-list',
        'ordered-list',
        '|',
        'link',
        'image',
        '|',
        'preview',
        'side-by-side',
        'fullscreen',
        '|',
        'guide',
    ]

const MarkDownEditor = () => {

    const [markdown, setMarkdown] = useState('');

    return(
        <SimpleMDE onChange={(e) => setMarkdown(e)} options={{toolbar:toolbar}}/>
    )
}
export default MarkDownEditor;
```

![video2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239179/6ec3ba44-d4bd-806a-c27d-d20788409187.gif)


### マークダウンエディタを拡張する

`EasyMDE`は機能を拡張することができます。その一つの`CodeMirror`のイベント処理を利用して、JavaScriptのイベントをハンドリングしてみます。

`CodeMirror`のイベントは以下から確認してください。

[https://codemirror.net/doc/manual.html#events](https://codemirror.net/doc/manual.html#events)

画像などのファイルをマークダウンエディタにドロップして、そのデータを取得してみます。

```jsx
import React, { useState } from "react";
import SimpleMDE from 'react-simplemde-editor';
import 'easymde/dist/easymde.min.css';

const MarkDownEditor = () => {

    const [markdown, setMarkdown] = useState('');

    function handleDrop(data, e){
        let files = e.dataTransfer.files;
        if(files.length > 0){
            let file = files[0];
            alert('FileName : ' + file.name );
            // any action
        }
    } 

    return(
        <SimpleMDE onChange={(e) => setMarkdown(e)} events={{'drop':handleDrop}}/>
    )
}
export default MarkDownEditor;
```

![video3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239179/da95fc78-42bd-3754-5104-bda42d451f4f.gif)


`SimpleMDE`のコンポーネントの`events`のpropsにドロップ時に起動する関数を指定します。
この関数内でファイルオブジェクトを取得することができます。この関数内で画像アップロードをすると、画像アップロード込みのマークダウンエディタを実装することができます。

## マークダウン表記をHTMLに変換する

マークダウン形式の文字列をHTMLに変換するために、`marked`ライブラリを使用します。

```console
$ npm install marked
```

先ほど作成したエディタで入力されたマークダウンを`marked`でHTMLに変換し画面に表示します。
インポートした`marked`の引数に、マークダウン形式の文字列を渡すだけです。

```jsx
import React, { useState } from "react";
import SimpleMDE from 'react-simplemde-editor';
import 'easymde/dist/easymde.min.css';
import marked from "marked";

const MarkDownEditor = () => {

    const [markdown, setMarkdown] = useState('');

    return(
        <div>
            <SimpleMDE onChange={(e) => setMarkdown(e)}/>
            <div id="body" >
                <span dangerouslySetInnerHTML={{ __html: marked(markdown)}}/>
            </div>
        </div>
        
    )
}
export default MarkDownEditor;
```

![video4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239179/4646b0bc-2947-de54-33f4-34ced48cb8bb.gif)


これで、エディタで入力したマークダウンをHTMLに変換して表示することができます。

ではコードを挿入している部分にハイライトを追加していきます。

## ハイライトをつける

コードを挿入している部分にハイライトを付けるために、`highlightjs`を導入します。
JavaScriptのハイライトのライブラリの中でも対応言語が多く、パレットの種類も豊富です。(185言語、91スタイル)

[https://highlightjs.org/static/demo/](https://highlightjs.org/static/demo/)

ライブラリをインストールします。

```console
$ npm install highlightjs
```

実装は以下のようになります。

```jsx
import React, { useState } from "react";
import SimpleMDE from 'react-simplemde-editor';
import 'easymde/dist/easymde.min.css';
import marked from "marked";
import highlight from 'highlightjs';
import 'highlightjs/styles/docco.css';

const MarkDownEditor = () => {

    const [markdown, setMarkdown] = useState('');

    return(
        <div>
            <SimpleMDE onChange={(e) => setMarkdown(e)}/>
            <div id="body" >
                <span dangerouslySetInnerHTML={{ __html: marked(markdown)}}/>
            </div>
        </div>
        
    )
}
export default MarkDownEditor;
```

![video5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239179/0ce14877-be4b-53f5-1988-50f32655b4fa.gif)


`highlightjs`と適用したいスタイルのCSSファイルをインポートします。今回は`docco`というスタイルを使用したので、`docco.css`をインポートしています。
CSSファイルは`highlightjs/styles/スタイル名.css`で指定できます。たまに違う場合もありますので、その時はハイフンを付けたりしてみてください。
`highlightjs`は表示する`<code>`タグの`class`属性に指定されている言語名に応じてスタイルを適用します。

例えば、マークダウンで記載したPythonのコードは以下のような流れで表示されます。

- マークダウン形式

> \`\`\`python
>
> コード
>
> \`\`\`

- markedでHTMLに変換

> \<span>\<code class="language-python">コード\</code>\</span>

マークダウンで言語を`Python`に指定しているので、`marked`でHTMLに変換したい際にcodeタグのclass属性に`language-python`が指定され、`highlightjs`のスタイルが適用されます。
\`\`\`python:test.pyのように言語名の後にファイル名を記載している場合、class属性にファイル名まで適用されてしまうため、`marked`のオプションを使用しレンダリング前にファイル名を除去することもできます。

実装は以下のようになります。

```jsx

import React, { useState } from "react";
import SimpleMDE from 'react-simplemde-editor';
import 'easymde/dist/easymde.min.css';
import marked from "marked";
import highlight from 'highlightjs';
import 'highlightjs/styles/docco.css';

// delete file name
marked.setOptions({
    highlight: function (code, lang) {
        return highlight.highlightAuto(code, [lang.split(':')[0]]).value;
    }
});

const MarkDownEditor = () => {

    const [markdown, setMarkdown] = useState('');

    return(
        <div>
            <SimpleMDE onChange={(e) => setMarkdown(e)}/>
            <div id="body" >
                <span dangerouslySetInnerHTML={{ __html: marked(markdown)}}/>
            </div>
        </div>
        
    )
}
export default MarkDownEditor;
```

スタイルは豊富に用意されているので、サイトに合わせたハイライトを選択してみてください。

## まとめ

Reactでライブラリを使用して、マークダウンエディタとマークダウンをHTMLに変換してみました。
コンポーネント一つ追加するだけで、簡単にマークダウンエディタを導入できます。拡張もできるので自由度はかなり高いかなと思っています。
他にも`react-mde`などのエディタがありますが、デザインなどの面から`react-simplemde-editor`の方が個人的には好みです。
もし使用する機会があれば検討してみてください。