LoginSignup
10
7

More than 3 years have passed since last update.

Laravel5.5+Vue.js環境でCKEditorを使ってみる

Last updated at Posted at 2019-06-26

環境

わけあって PHP 7.0 環境なので Laravel 5.5 です。
基本は変わらないと思います。
動作確認用のコードなので適当なところが多いです。

  • Laravel 5.5
  • vue.js 2.5.7
  • ckeditor5-build-classic 12.2.0
  • ckeditor5-vue 1.0.0-beta.2

まだβなのでできれば Vue.js 環境では使わないほうが良いのかもしれない…

GET /api/news
POST /api/news
POST /api/images

Laravelには上記の API を定義しています。

vue用のやつをインストール

npm install --save @ckeditor/ckeditor5-vue @ckeditor/ckeditor5-build-classic

app.js に追記

app.js
window.CKEditor = require('@ckeditor/ckeditor5-vue');
Vue.use(CKEditor);
Vue.component('ckeditor-component', require('./components/CKEditorComponent.vue'));

ckeditor-component は今回用につくるです。

日本語化

入れてみるとデフォルトが英語だったので日本語化します。
してって言われたのでします。

app.js
require('@ckeditor/ckeditor5-build-classic/build/translations/ja');

こちらも app.js に追記します。
完全ではないのでCDNとかのほうが良いかも?
グローバル変数を使って国際化しているっぽいので、読み込むだけでOKです。

コンポーネントの作成

CKEditorComponent.vue
<template>
    <div>
        <ckeditor
            :editor="editor"
            v-model="editorData"
            :config="editorConfig"
        ></ckeditor>
    </div>
</template>

<script>
    import ClassicEditor from '@ckeditor/ckeditor5-build-classic';

    export default {
        name: 'ckeditor-component',
        data() {
            return {
                editor: ClassicEditor,
                editorData: '',
                editorConfig: {
                    language: 'ja'
                }
            };
        }
    }
</script>

サンプルそのままですので特筆すべきところもないですね。

bladeにコンポーネントを追加

ckeditor.blade.php
<!doctype html>
<html lang="{{ app()->getLocale() }}">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Laravel</title>

        <link rel="stylesheet" href="{{ mix('/css/app.css') }}">
        <meta name="csrf-token" content="{{ csrf_token() }}">
    </head>
    <body>
        <div id="app">
            <ckeditor-component></ckeditor-component>
        </div>
        <script src="{{ mix('js/app.js') }}"></script>
    </body>
</html>

CKEditor関係ないですが、注意点は2点です。

<meta name="csrf-token" content="{{ csrf_token() }}">
<script src="{{ mix('js/app.js') }}"></script>

POST用のCSRFトークンと mix です。
この辺はLaravelの機能ですね。

実際にVueを初期化しているのは resouces/assets/js/app.js です。
laravel-mix によって public配下に移動・コンパイルする定義が書かれている模様。

内容を保存する

Autosave がうまいこと動かなかったので @input を使って保存します。

APIの認証にトークンが必要なので axios に埋め込みます。

CKEditorComponent.vue
const token = '***';
const apiClient = axios.create({
    baseURL: '/api/',
    headers: {'Authorization': `Bearer ${token}`}
});

保存用の処理を methods に追加。

CKEditorComponent.vue
onEditorInput() {
    const postData = new URLSearchParams();
    postData.append('content', this.editorData);
    apiClient.post('news', postData).then(response => {});
}

勝手に保存されてるのがいいという要望のもとこういう形になっています。
ボタンとかで保存する場合はふつうに editorData をPOSTしてやれば良いですね。

画像のアップロード

自前のサーバにアップロードするのでプラグインなしでやります。
向き先変えるだけでいけると思いますけど簡単そう・後々のことを考えて。

Adapterの作成

ここ にある通りつくります。
とりあえずはデフォルトの画像だけ…

MediaUploadAdapter.js
export default class MediaUploadAdapter {
    constructor(loader, token) {
        this.loader = loader;
        this.token = token;
    }

    upload() {
        const token = this.token;
        return this.loader.file.then(uploadedFile => {
            return new Promise((resolve, reject) => {
                const formData = new FormData();
                formData.append('upload', uploadedFile);
                axios({
                    url: '/api/images',
                    method: 'post',
                    formData,
                    headers: {
                        'Content-Type': 'multipart/form-data;',
                        'Authorization': `Bearer ${token}`,
                    },
                    withCredentials: false
                }).then(response => {
                    if (response.data.result === 'success') {
                        resolve({default: response.data.url});
                    } else {
                        reject(response.data.message);
                    }
                }).catch(error => {
                    // console.log(error.response.headers);
                    console.log(error.response.status, error.response.data.message);
                });
            });
        });
    }

    abort() {
    }
}

登録する

作ったアダプタをエディタに登録します。
これもドキュメント通りにやっています。

CKEditorComponent.vue
export default {
    data() {
        return {
            editor: ClassicEditor,
            editorData: '',
            editorConfig: {
                language: 'ja',
                extraPlugins: [this.uploadAdapter]
            }
        };
    },
    methods: {
        uploadAdapter(editor) {
            editor.plugins.get('FileRepository').createUploadAdapter = loader => {
                return new MediaUploadAdapter(loader, token);
            };
        }
    }
}

動画の埋め込み(途中まで)

動画を画像のように埋め込みたいという要望があるので調べるだけ調べました。
あまり頻度は高くないとのことだったので、途中までです。

1. mp4のドラッグ&ドロップを検知する

アダプタを追加したときのようにプラグインを追加します。

CKEditorComponent.vue
export default {
    data() {
        return {
            editor: ClassicEditor,
            editorData: '',
            editorConfig: {
                language: 'ja',
                extraPlugins: [this.handleDropEvent]
            }
        };
    },
    methods: {
        handleDropEvent(editor) {
            editor.editing.view.document.on('drop', (event, data) => {
                if (data.dataTransfer.files.length > 0) {
                    data.dataTransfer.files.forEach(file => {
                        if (file.type === 'video/mp4') {
                            event.stop();
                            data.preventDefault();
                            // ここでファイルのアップロード処理&ローディング埋め込み
                            // 同時にファイルをエディタに埋め込む処理を記載する
                        }
                    });
                }
            }, {priority: 'high'});
        }
    }
}

2. エディタ内に埋め込む

エディタに埋め込むには対象と場所を指定してやる必要があります。
この辺から CKEditor のマニュアルとコードとにらめっこしながらすすめています。

drop イベント内で取れる Position は View モジュールのものなので、
Writer に渡してやるために Model の Position に変更する必要があります。
そのためのメソッドが Mapper に用意されており、そいつは EditorController に生えていますので、それを使ってエディタ内に埋め込みします。

editor.model.change(writer => {
    const insertPosition = editor.editing.mapper.toModelPosition(data.dropRange.end);
    // ここでローディング画像を作る
    const element = writer.createElement();
    // こんな感じで埋め込む
    writer.insert(element, insertPosition);
});

アップロードが完了するまではローディングのgifでも読み込ませておいて、
アップロードが完了後にEmbedを Writercreate* で作るとは思うのですが、調査に時間がかかりそうだったので、一旦ここで動画のアップロードは後日にしようということになりました。
ので、この記事もここまでとなります。中途半端ですみません。

3. ファイルをサーバにアップロードする

ここは普通に axios でアップロードするだけです。(割愛)

10
7
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
10
7