BLOG

ブログ

【NuxtJS】AtomicDesignでのEditor.jsの使い方

こんにちは、タロウです。

コンポーネント思想のAtomicDesignで設計されているNuxtJSのプロジェクトで、
Editor.jsを導入する際に少し困ったので、解消した方法を紹介します。

解決方法案として1つのtipsになればと思います!

Editor.jsの公式はこちら

実現したいこと

コンポーネント思想をAtomic Designで表現

エディタ自体はAtomsのコンポーネントで定義しており、
そのエディタで編集したデータをPagesで保持しているデータへ反映させます。

前提条件

  • Pagesに配置したボタンを押下すること
  • エディタへは複数コンポーネントを跨ぐこと
  • エディタへ入力したデータは親コンポーネントで取得すること

エディタコンポーネントに対して、
親コンポーネントから保存ファンクションを実行し、親コンポーネントへ値を受け渡します。

背景

CMSのようなシステムをスクラッチで開発した際に、
Editor.jsを導入してブロックエディタを実現しました。

プロジェクトのフロントエンドはAtomicDesignの思想に沿って
やや細かくコンポーネント管理していたので、
どうしてもコードからエディタを操作するにはコンポーネントを跨ぐ必要が出てきてしまいました。

親コンポーネントから子コンポーネント内の関数を実行

$refsと$emitを駆使する

親コンポーネントから$refsを使ってAtomsで配置しているエディタに対して、
記入したテキストを保存するファンクションを実行します。

保存したテキストを$emitを使用して親コンポーネントへ連携していきます。

間に挟まるコンポーネントでは、
値を受け渡すだけの単純な操作のみを行います。

サンプルコード

親コンポーネント

<template>
  <editor
    ref="editor"
    @changeContent="editValue = $event"
  />
</template>

<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
  name: 'ParentComponent',
  data() {
    return {
      // 入力したデータが入る
      editValue: "",
    }
  },
  methods: {
    /**
     * 親コンポーネント保存ボタン押下
     */
    async submitButton() {
      const refs: any = this.$refs
      // エディタコンポーネントのエディタ内容取得するファンクションを実行
      await refs.editor.getContent()
    },
  },
})
</script>

<style lang="scss" scoped>
// 省略
</style>

エディタコンポーネント

<template>
  <div>
    <div id="editor" class="editor" />
  </div>
</template>

<script lang="ts">
import Vue from 'vue'
import EditorJS from '@editorjs/editorjs'
type DataDef = {
  editor: EditorJS | null
  editContent: string
}
export default Vue.extend({
  name: 'Editor',
  data(): DataDef {
    return {
      editor: null,
      editContent: '',
    }
  },
  mounted() {
    // @ts-ignore
    this.editor = this.$editor.EditorJS({
      holder: 'editor',
      placeholder: '編集してください',
      data: this.editContent ? JSON.parse(this.editContent) : '',
    })
  },
  methods: {
    /**
     * エディタ内容取得
     * 親コンポーネントから$refsを使用して実行
     */
    async getContent() {
      await this.editor
        ?.save()
        .then((output) => {
          // 値を親へ渡す
          this.$emit('changeContent', JSON.stringify(output))
        })
        .catch(() => {
          // 保存に失敗
          this.$emit('changeContent', '')
        })
    },
  },
})
</script>

<style lang="scss" scoped>
// 省略
</style>

親コンポーネントからEditor.jsが入っているコンポーネントの内容取得ファンクションを実行し、
取得した結果を親コンポーネントで受け取る流れとなります。

【おまけ】保存したエディタ内容を表示用に変換

Editor.jsで取得した入力データは、
再編集できるようにjson形式を保ったままDBなどに保存することがほとんどですが、
ここで困るのが、内容を表示する部分となります。

保存したjsonには余計やタグや情報が残っているので、
表示用に変換する必要があります。

下記のような自作プラグインでjsonを分解して表示用に変換しました。

  /**
   * 記事データを表示用にコンバートする
   * @param editContent
   * @returns
   */
  convertContent(editContent: string): string {
    let content: string = ''
    // データが存在しない場合は空文字返却
    if (!editContent) {
      return content
    }

    try {
      // 取得データをJSONへパース
      const contentJson = JSON.parse(editContent)
      // ブロックごとに適切なタグを付与
      for (const data of contentJson.blocks) {
        switch (data.type) {
          // 画像埋め込み
          case 'image':
            content += '<figure>'
            content += `<img src=${data.data.file.url}>`
            if (data.data.caption !== '') {
              content += `<figcaption>${data.data.caption}</figcaption>`
            }
            content += '</figure>'
            break
          // 文章のみ(装飾含み)
          case 'paragraph':
            content += `<p>${data.data.text}</p>`
            break
          // ヘッダー要素
          case 'header':
            content += `<h${data.data.level}>${data.data.text}</h${data.data.level}>`
            break
          case 'list':
            if (data.data.items.length === 0) {
              break
            }
            content += '<ul>'
            for (const item of data.data.items) {
              content += `<li>${item}</li>`
            }
            content += '</ul>'
            break
        }
      }
    } catch (error) {
      // 例外発生時は受け取った文字列をそのまま返却する
      content = editContent
    }

    return content
  }

ここでは、画像・テキスト・ヘッダー・リストの4つのみの変換ですが、
Editor.jsは拡張ライブラリを入れることで、さまざまな装飾や表現を加えることができます。

さいごに

もしかすると他にも実現する方法があるのかもしれませんが、
当時の最善策として実装例の紹介でした。

誰かの解決の一案になれば嬉しいです!

RELATED ARTICLE