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

こんにちは、タロウです。
コンポーネント思想のAtomicDesignで設計されているNuxtJSのプロジェクトで、
Editor.jsを導入する際に少し困ったので、解消した方法を紹介します。
解決方法案として1つのtipsになればと思います!
Editor.jsの公式はこちら
実現したいこと

エディタ自体はAtomsのコンポーネントで定義しており、
そのエディタで編集したデータをPagesで保持しているデータへ反映させます。
前提条件
- Pagesに配置したボタンを押下すること
- エディタへは複数コンポーネントを跨ぐこと
- エディタへ入力したデータは親コンポーネントで取得すること
エディタコンポーネントに対して、
親コンポーネントから保存ファンクションを実行し、親コンポーネントへ値を受け渡します。
背景
CMSのようなシステムをスクラッチで開発した際に、
Editor.jsを導入してブロックエディタを実現しました。
プロジェクトのフロントエンドはAtomicDesignの思想に沿って
やや細かくコンポーネント管理していたので、
どうしてもコードからエディタを操作するにはコンポーネントを跨ぐ必要が出てきてしまいました。
親コンポーネントから子コンポーネント内の関数を実行

親コンポーネントから$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は拡張ライブラリを入れることで、さまざまな装飾や表現を加えることができます。
さいごに
もしかすると他にも実現する方法があるのかもしれませんが、
当時の最善策として実装例の紹介でした。
誰かの解決の一案になれば嬉しいです!