LoginSignup
1
0

More than 3 years have passed since last update.

draft-jsというFacebook製のWYSIWYG構築ツールのBlockオブジェクトに変換

Last updated at Posted at 2020-01-27

はじめに

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()

以下の様な RawDraftContentStateContentState という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の反対に、ContentStateRawDraftContentState 内部構造に変換できる

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

コードサンプル

実際に変換に使ったコード

app.js
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サーバを書きました。

server.js
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: {}
}
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0