LoginSignup
29
19

More than 5 years have passed since last update.

[React] Draft.jsでRailsサービスにWysiwygエディタをつけてみた

Last updated at Posted at 2018-01-18

概要

今までは純粋なStringで管理していたBlogの記事内容を、Wysiwyg編集できるようにするときにした作業の中身についてまとめました!
Screen Shot 2018-01-18 at 19.10.31.png

Screen Shot 2018-01-18 at 19.03.33.png

技術背景

  • rails
  • react-rails

react-railsの導入方法については本題から逸れるため、この記事からは省きます。
上記技術背景のプロジェクトに置いて、下記技術を導入しました。

Draft.js

ReactでWysiwygエディタを作るとしたら「これ」というもの。
Facebook製なため人気も高く、Ecosystemが整いつつあり、簡単に導入できかつ要件に合わせて機能追加が容易です。

react-draft-wysiwyg

Draft.js製のStand Alone Editorは様々あり、Mediumのような形式からツールバーが上に出てる旧来の形式まで様々です。
https://github.com/nikgraf/awesome-draft-js#standalone-editors-built-on-draftjs

今回はその中でも、react-draft-wysiwygを選択しました。
理由はツールバーが文字上に出るMediumのような形式だとSPで使いづらかったこと、そしてツールバーが上部固定のものの中で好みで選択しました。

日本語のTranslationFileも足しておいたので、日本語にも対応してます!

実装

Draft.jsを入れる

npm install --save draft-js
npm install --save react-draft-wysiwyg

Wysiwyg化済みかどうか識別するFlagをテーブルに追加

既存の動作しているテーブルに対しての機能拡張なため、Flagを用意します。

XXXX_add_is_wysiwyg_to_blogs.rb
add_column :blogs, :is_wysiwyg, :boolean, default: false

編集Componentの作成

form_forをそのまま使えるようにtextareaでid、nameを入れながら出力するようにしました。

RichEditor.js
import { Editor } from 'react-draft-wysiwyg'
import { EditorState, ContentState, convertToRaw, convertFromRaw, convertFromHTML } from 'draft-js'

export default class RichEditor extends React.Component {
  constructor(props) {
    super(props)

    let editorState

    try {
      if (this.props.isJsonObject) {
        editorState = EditorState.createWithContent(convertFromRaw(JSON.parse(this.props.defaultValue)))
      } else {
        editorState = EditorState.createWithContent(ContentState.createFromText(this.props.defaultValue))
      }
    } catch (e) {
      editorState = EditorState.createEmpty()
    }

    this.state = {
      editorState: editorState
    }
  }

  render() {
    const { editorState } = this.state;
    return (
      <div>
        <Editor
          editorState={ editorState }
          onEditorStateChange={(editorState) => { this.setState({ editorState }) }}
        <textarea
          hidden
          name={ `${this.props.model}[${this.props.column}]` }
          id={ `${this.props.model}_${this.props.column}` }
          value={ JSON.stringify(convertToRaw(editorState.getCurrentContent())) }
        />
      </div>
    )
  }
}

RichEditor.propTypes = {
  model:        PropTypes.string,
  column:       PropTypes.string,
  defaultValue: PropTypes.string,
  isJsonObject: PropTypes.bool
}

RichEditor.defaultProps = {
  model:        '',
  column:       '',
  defaultValue: '',
  isJsonObject: true
}

Viewの修正

edit.slim
= form_for @blog, url: blog_path(@blog.public_id) do |f|
  = f.text_area :content

edit.slim
= form_for @blog, url: blog_path(@blog.public_id) do |f|
  = react_component 'RichEditor', {defaultValue: @blog.content, isJsonObject: @blog.is_wysiwyg, model: 'blog', column: 'content'}
  = f.hidden_field :is_wysiwyg, value: 1

表示Componentの作成

そのままだと見え方がEditorなのでclass指定してcssで調整しました。

RichViewer.js
import { EditorState, convertFromRaw } from 'draft-js'
import { Editor } from 'react-draft-wysiwyg'

export default class RichViewer extends React.Component {
  constructor(props) {
    super(props)
    let editorState

    try {
      editorState = EditorState.createWithContent(convertFromRaw(JSON.parse(this.props.value)))
    } catch (e) {
      editorState = EditorState.createEmpty()
    }

    this.state = {
      editorState: editorState
    }
  }

  render() {
    const { editorState } = this.state;
    return (
      <Editor
        toolbarHidden
        readOnly={ true }
        editorClassName="rdw-viewer-editor"
        toolbarClassName="rdw-viewer-toolbar"
        editorState={ editorState }
      />
    )
  }
}

RichViewer.propTypes = {
  value: PropTypes.string
}
RichViewer.defaultProps = {
  value: ''
}

※Editorで表示するのには違和感があるけど、方法論として間違ってはいなさそうです。

その他

Stringで表示したい対応

もともとStringで動いていたシステムなのでStringデータが欲しい箇所が出てきます。
(truncateしながら概要を一部出している箇所など)
そのままだとjson構文のまま生で出てしまうので、Stringで出力する関数も作成しました。

blog.rb
  def string_content
    if self.is_wysiwyg
      parsed_content = JSON.parse(self.content)
      if parsed_content['blocks'].present?
        parsed_content['blocks'].map{|block| block['text'].present? ? block['text'] : ''}.join("\n\n")
      else
        ''
      end
    else
      self.content
    end
  end

js側ならContentStateのgetPlainTextから取得可能です。
https://draftjs.org/docs/api-reference-content-state.html#getplaintext

完成!

Screen Shot 2018-01-18 at 19.03.33.png
※ツールバーやcssはよしなに調整しています。

form_forで使えるので、他のModelでも気軽に使い回せるEditor Componentができました!

参考

29
19
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
29
19