はじめに
PloneのSPA CMSツールである、Voltoでは、Block編集を使っています。
そのBlock編集をになうのが、Facebook製のWYSIWYG構築ツールの draft-js
です。
draft-jsを理解することが、VoltoのBlockを理解することになります。
Block・HTML・JSONの関係
Block
draft-jsでは、JSON形式のデータを元に、Block構造を内部を表します。
ここでいうBlock構造は、複数のBlockからなる複数の要素ではなく、一つの箱であるBlockの内部要素をしめしています。
Voltoでは、複数のBlockを組み合わせる事で、コンテンツのページを作っていきますが、今回の話題は、一つのBlockの内部構造についてフォーカスします。
HTML
レンダリング結果はHTMLとして表現されます。
しかし、draft-jsが持つ内部構造は特殊なオブジェクトです。その中間地点として、JSONの構造があります。
このJSON構造を用いることで、データベースへの保存やデータベースからの読み込みを実現しています。
JSON
JSON構造の永続化したデータをdraft-jsの内部構造に変換したり、HTMLをdraft-jsの内部構造に変換したりすることをサポートしています。
各種変換
公式ドキュメント
https://draftjs.org/docs/api-reference-data-conversion/
内部構造 -> JSON
convertFromRaw()
以下の様な RawDraftContentState
を ContentState
というJSONに変換できる
{
contentBlocks: [ ContentBlock { _map: [Map] } ],
entityMap: {
getLastCreatedEntityKey: [Function: getLastCreatedEntityKey],
create: [Function: create],
add: [Function: add],
get: [Function: get],
mergeData: [Function: mergeData],
replaceData: [Function: replaceData],
__getLastCreatedEntityKey: [Function: __getLastCreatedEntityKey],
__create: [Function: __create],
__add: [Function: __add],
__get: [Function: __get],
__mergeData: [Function: __mergeData],
__replaceData: [Function: __replaceData]
}
}
変換後の ContentState
{
blocks: [
{
key: 'csete',
text: 'test',
type: 'unstyled',
depth: 0,
inlineStyleRanges: [],
entityRanges: [],
data: {}
}
],
entityMap: {}
}
JSON -> 内部構造
convertToRaw()
convertFromRawの反対に、ContentState
を RawDraftContentState
内部構造に変換できる
HTML -> 内部構造
convertFromHTML()
HTMLを Blockのオブジェクトに変換できる
<div>test</div>
移行用にHTMLからJSONを作る
関連パッケージのインストール
$ nvm use v12.14.1
$ npm init
$ npm install express --save
$ npm install body-parser --save
$ npm install draft-js --save
$ npm install react --save
$ npm install react-dom --save
$ npm install jsdom --save
コードサンプル
実際に変換に使ったコード
var { ContentState, convertToRaw, convertFromHTML } = require("draft-js");
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const dom = new JSDOM();
document = dom.window.document;
HTMLElement = require("jsdom/lib/jsdom/living/nodes/HTMLBodyElement-impl")
.implementation;
HTMLAnchorElement = require("jsdom/lib/jsdom/living/nodes/HTMLAnchorElement-impl")
.implementation;
Node = require("jsdom/lib/jsdom/living/nodes/Node-impl").implementation; // これが無いと不十分なBlockができた。
var blocksFromHTML = convertFromHTML("<div>123</div>");
var data2 = ContentState.createFromBlockArray(
blocksFromHTML.contentBlocks,
blocksFromHTML.entityMap
);
var r = convertToRaw(data2);
console.log(r);
$ node app.js
以下を参考にしました。(中国語で説明は読めませんでしたが、コードを参考にしました。)
https://blog.csdn.net/ISaiSai/article/details/73250473
今回は、手元のローカルマシンでnodeを使って動かしました。
そのため、jsdomというライブラリで、DOMを作ってあげる必要がありました。
jsdomのバージョンが最近上がったようで、以前のサンプルが動かなくなっていて、苦労しました。
APIサーバで変換後のJSONを受け取れるように
今回の要件だと、変換時やインポート時にPythonを用いています。(既存の試算を生かすためにも)
その為、この変換だけ外出しして、REST APIで変換後のデータを受け取れたら便利だと思っています。
ExpressライブラリでNodeで動くAPIサーバを書きました。
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
const port = 3000;
const { ContentState, convertToRaw, convertFromHTML } = require("draft-js");
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
app.post("/", (req, res, next) => {
const data = req.body.html;
const dom = new JSDOM();
document = dom.window.document;
HTMLElement = require("jsdom/lib/jsdom/living/nodes/HTMLBodyElement-impl")
.implementation;
HTMLAnchorElement = require("jsdom/lib/jsdom/living/nodes/HTMLAnchorElement-impl")
.implementation;
Node = require("jsdom/lib/jsdom/living/nodes/Node-impl").implementation; // これが無いと不十分なBlockができた。
const blockRawData = convertFromHTML(data);
console.log(blockRawData);
const blockRawData2 = ContentState.createFromBlockArray(
blockRawData.contentBlocks,
blockRawData.entityMap
);
const blockData = convertToRaw(blockRawData2);
console.log(blockData);
res.send(blockData);
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`));
$ curl -H "Content-Type: application/json" -X POST http://localhost:3000/ -d '{"html": "<div>test</div>"}'
{"blocks":[{"key":"s992","text":"test","type":"unstyled","depth":0,"inlineStyleRanges":[],"entityRanges":[],"data":{}}],"entityMap":{}}
{
blocks: [
{
key: 's992',
text: 'test',
type: 'unstyled',
depth: 0,
inlineStyleRanges: [],
entityRanges: [],
data: {}
}
],
entityMap: {}
}