環境
わけあって 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 に追記
window.CKEditor = require('@ckeditor/ckeditor5-vue');
Vue.use(CKEditor);
Vue.component('ckeditor-component', require('./components/CKEditorComponent.vue'));
ckeditor-component
は今回用につくるです。
日本語化
入れてみるとデフォルトが英語だったので日本語化します。
してって言われたのでします。
require('@ckeditor/ckeditor5-build-classic/build/translations/ja');
こちらも app.js
に追記します。
完全ではないのでCDNとかのほうが良いかも?
グローバル変数を使って国際化しているっぽいので、読み込むだけでOKです。
コンポーネントの作成
<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にコンポーネントを追加
<!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
に埋め込みます。
const token = '***';
const apiClient = axios.create({
baseURL: '/api/',
headers: {'Authorization': `Bearer ${token}`}
});
保存用の処理を methods
に追加。
onEditorInput() {
const postData = new URLSearchParams();
postData.append('content', this.editorData);
apiClient.post('news', postData).then(response => {});
}
勝手に保存されてるのがいいという要望のもとこういう形になっています。
ボタンとかで保存する場合はふつうに editorData
をPOSTしてやれば良いですね。
画像のアップロード
自前のサーバにアップロードするのでプラグインなしでやります。
向き先変えるだけでいけると思いますけど簡単そう・後々のことを考えて。
Adapterの作成
ここ にある通りつくります。
とりあえずはデフォルトの画像だけ…
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() {
}
}
登録する
作ったアダプタをエディタに登録します。
これもドキュメント通りにやっています。
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のドラッグ&ドロップを検知する
アダプタを追加したときのようにプラグインを追加します。
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を Writer
の create*
で作るとは思うのですが、調査に時間がかかりそうだったので、一旦ここで動画のアップロードは後日にしようということになりました。
ので、この記事もここまでとなります。中途半端ですみません。
3. ファイルをサーバにアップロードする
ここは普通に axios でアップロードするだけです。(割愛)