Draft.jsのデータ保存について
概要
Reactの復習ついでに、Draft.jsを使っていたら躓いたのでメモ。
Draft.js自体はReactで作られたリッチエディタフレームワークです。(https://github.com/facebook/draft-js)
やりたいこと
- エディタで編集した内容をファイルに保存すること
- ファイルに保存したデータからエディタの状態を復元すること
Editor自体の実装は以下のようにしておきます。
Electronを使って実装をしていたので、fs
でファイルの保存・読み込みを行います。
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をしてくれるライブラリを使用しました。
// 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を利用しているので、そのままファイルに保存してもうまく行きません。
convertFromRaw
とconvertToRaw
を使用しますが、どちらも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});
};
まとめ
もうおわかりかもしれませんが、既に用意されている関数で十分ですね。ドキュメントにも書いてあります。
きちんとドキュメントは読みましょう(戒め)
Discussion