🐳

Draft.jsのデータ保存について

2024/12/31に公開

概要

Reactの復習ついでに、Draft.jsを使っていたら躓いたのでメモ。

Draft.js自体はReactで作られたリッチエディタフレームワークです。(https://github.com/facebook/draft-js)

やりたいこと

  1. エディタで編集した内容をファイルに保存すること
  2. ファイルに保存したデータからエディタの状態を復元すること

Editor自体の実装は以下のようにしておきます。
Electronを使って実装をしていたので、fsでファイルの保存・読み込みを行います。

Editor.js
import React, {Component} from 'react';
import {Editor, EditorState, RichUtils} from 'draft-js';

const editorStyle = {
    height: "300px", 
    width: "300px", 
    backgroundColor: "#eeeeee", 
    textAlign: "left",
    padding: "10px",
    border: "1px solid #e0e0e0"
};

export default class EditorSample extends Component {

  editor;

  constructor(props) {
    super(props);
    
    this.state = {
      editorState: EditorState.createEmpty()
    };
  }

  render() {

    return(
        <div>
            <h2>EditorSample</h2>
            <div>
                <StyleButton label="H1" style="header-one" onToggle={this.onToggle}/>
            </div>
            <div onClick={this.onClick} style={editorStyle}>
            <Editor ref={(el) => {this.editor = el;}} 
                    editorState={this.state.editorState} 
                    onChange={this.onChange}/>
            </div>
            <div style={{marginTop: "10px"}}>
                <button onClick={this.save}>Save</button>
                <button onClick={this.read}>Read</button>
            </div>
        </div>
    );
  }

  onClick = () => {
    if (this.editor) {
       this.editor.focus();
    }
  };

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

  onToggle = (style) => {
    const editorState = RichUtils.toggleBlockType(this.state.editorState, style);
    this.setState({editorState});
  };

  save = () => {
    // 実装は後述
  };
  read = () => {
    // 実装は後述
  };
}

class StyleButton extends Component {

    render() {
      return (
        <span onMouseDown={this.onToggle}>
          {this.props.label}
        </span>
      );
    }

    onToggle = (e) => {
        e.preventDefault();
        this.props.onToggle(this.props.style);
    };
}

<img width="402" alt="EditorSample_normal.png" src="/images/1db87a945e2bbb99505b/EditorSample_normal.png" />
<img width="408" alt="EditorSample_Block_H1.png" src="/images/1db87a945e2bbb99505b/EditorSample_Block_H1.png" />

HTMLファイルとして保存・復元

結論から言うとうまくいきません。改行が取り除かれたりして、正確に復元できませんでした。BlockやInlineStyleなどをカスタマイズした場合を考えると頭が痛くなるやり方。

htmlとしてexport/importをしてくれるライブラリを使用しました。
https://github.com/sstur/draft-js-utils

// export 
import {stateToHTML} from 'draft-js-export-html';
import fs from 'fs';

// class内に定義
save = () => {
  const contentState = this.state.editorState.getCurrentContent();
  const content = stateToHTML(contentState);
  const filePath = "xxxxx";
    // ファイル保存処理 
    fs.writeFileSync(filePath, content, 'utf-8');
};


// import
import {stateFromHTML} from 'draft-js-import-html';
import fs from 'fs';

// class内に定義
read = () => {
  const filePath = "xxxx";
  // ファイル読み込み
  const content = fs.readFileSync(filePath, 'utf-8');

  const contentState = stateFromHTML(content);
  const editorState = EditorState.createWithContent(contentState);
  this.setState({editorState});
};

JSONとしてそのまま保存する

ファイルとして出力することを目的とせず、復元することに重きを置くのであればcontentStateをそのままファイル保存するのが良いと思いました。

Editor自体がImmutable.jsを利用しているので、そのままファイルに保存してもうまく行きません。
convertFromRawconvertToRawを使用しますが、どちらもDraft.js標準の関数です。これを使うとJSONとして適切に変換を行ってくれます。

// export 
import {convertToRaw} from 'draft-js';
import fs from 'fs';

// class内に定義
save = () => {
  const contentState = this.state.editorState.getCurrentContent();
  const content = convertToRaw(contentState);
  const filePath = "xxxxx";
    // ファイル保存処理 
    fs.writeFileSync(filePath, JSON.stringify(content), 'utf-8');
};

// import
import {convertFromRaw} from 'draft-js';
import fs from 'fs';

// class内に定義
read = () => {
  const filePath = "xxxx";
  // ファイル読み込み
  const content = fs.readFileSync(filePath, 'utf-8');

  const contentState = convertFromRaw(JSON.parse(content));
  const editorState = EditorState.createWithContent(contentState);
  this.setState({editorState});
};

まとめ

もうおわかりかもしれませんが、既に用意されている関数で十分ですね。ドキュメントにも書いてあります。
https://draftjs.org/docs/api-reference-data-conversion.html#content

きちんとドキュメントは読みましょう(戒め)

Discussion