如何套用 CKEditor5 上傳圖片

大概所見即所得的編輯器,應該都是採用 CKEditor 的解決方案了,因為它真的還蠻好用的,CKEditor 已經來到版本 5,提供的編輯器也有很多種

Quick Start

官方有提供 vue.js 框架整合 CKEditor,可透過 npm 安裝套件

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

或者是利用 inline script 引用,相較於一般的版本,整合 vue.js 的 ckeditor 還需要額外載入一個ckeditor5-vueckeditor.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="app">
<ckeditor :editor="editor" v-model="editorData" :config="editorConfig"></ckeditor>
</div>
<script src='https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.min.js'></script>
<script src="./node_modules/@ckeditor/ckeditor5-build-classic/build/ckeditor.js"></script>
<script src="./node_modules/@ckeditor/ckeditor5-vue/dist/ckeditor.js"></script>
<script>
Vue.use(CKEditor);
const app = new Vue({
el: "#app",
data: {
editor: ClassicEditor,
editorData: "<p>Content of the editor.</p>",
editorConfig: {
// The configuration of the editor.
},
},
});
</script>

CKEditor5 Editor type

其實用第一個就夠了,如果想多了解一下這幾種編輯器的差異,可以直接瀏覽官方的範例會更清楚

  1. Classic Editor
  2. Inline Editor
  3. Ballon block Editor
  4. Ballon Editor
  5. Document Editor

CKEditor5 Editor Config

EditorConfig可以設定要用的功能、外掛,但其實如果不需要用的話,在一開始就不要打包那個版本,透過線上自訂工具取得 Lib 即可,這也是官方比較推薦的方式,畢竟你不需要的話,不要打包那些內容,速度也比較快

CKEditor5 Online Builder

可以在線自訂需要的 plugin 並下載 lib
Online Builder

上傳圖片 by simple upload adapter

上傳圖片官方有提供數種做法

  1. 使用 Easy Image (官方提供的雲端服務)
  2. 透過 CKFinder (官方提供的另外一套解決方案)
  3. simple upload adapter
  4. base64 upload adapter

這邊我們採用 simple upload adapter,在 online build lib 的時候,選擇 simple upload adapter 外掛,此一外掛可以透過設定,讓 CKEditor 加入圖片的時候,向指定 URI 發送 POST 請求將圖片上傳到後端

base64 upload adapter 並不需要圖片處理的後端,因為她是將圖片先轉為 base64 格式,與文章一起儲存,缺點是對於資料庫效能會有影響

simple upload adapter 設定

1
2
3
4
5
6
7
8
9
10
11
12
{
...editorConfig,
simpleUpload: {
// The URL that the images are uploaded to.
uploadUrl: '/Upload/Image',
// // Headers sent along with the XMLHttpRequest to the upload server.
headers: {
'X-CSRF-TOKEN': 'CSFR-Token',
Authorization: 'Bearer <JSON Web Token>'
}
}
}

後端回傳格式

正確會回傳一個 url,前端會依照此 url 的內容,將其內崁於img src之內,所以如果最終回傳的 url 內容是 base64 字串,也可以直接顯示圖片

1
2
3
{
"url": "http://localhost/xxxxxx"
}

錯誤需要回傳一個 error message ,結構如下,此錯誤訊息會直接在前端透過 alert 顯示

1
2
3
4
5
{
"error": {
"message": "錯誤訊息"
}
}

上傳圖片 自定義 upload adapter

採用 simple upload adapter 還是會有沒有辦法做到的事情,這個時候就需要自定義 adapter 了,官方也有提供完整的程式碼範例,可以直接修改拿來用即可

更詳細的東西還是參考一下官方文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
class MyUploadAdapter {
constructor(loader) {
// The file loader instance to use during the upload.
this.loader = loader;
}

// Starts the upload process.
upload() {
return this.loader.file.then(
file =>
new Promise((resolve, reject) => {
this._initRequest();
this._initListeners(resolve, reject, file);
this._sendRequest(file);
})
);
}

// Aborts the upload process.
abort() {
if (this.xhr) {
this.xhr.abort();
}
}

// Initializes the XMLHttpRequest object using the URL passed to the constructor.
_initRequest() {
const xhr = (this.xhr = new XMLHttpRequest());

// Note that your request may look different. It is up to you and your editor
// integration to choose the right communication channel. This example uses
// a POST request with JSON as a data structure but your configuration
// could be different.
xhr.open("POST", "http://example.com/image/upload/path", true);
xhr.responseType = "json";
}

// Initializes XMLHttpRequest listeners.
_initListeners(resolve, reject, file) {
const xhr = this.xhr;
const loader = this.loader;
const genericErrorText = `Couldn't upload file: ${file.name}.`;

xhr.addEventListener("error", () => reject(genericErrorText));
xhr.addEventListener("abort", () => reject());
xhr.addEventListener("load", () => {
const response = xhr.response;

// This example assumes the XHR server's "response" object will come with
// an "error" which has its own "message" that can be passed to reject()
// in the upload promise.
//
// Your integration may handle upload errors in a different way so make sure
// it is done properly. The reject() function must be called when the upload fails.
if (!response || response.error) {
return reject(response && response.error ? response.error.message : genericErrorText);
}

// If the upload is successful, resolve the upload promise with an object containing
// at least the "default" URL, pointing to the image on the server.
// This URL will be used to display the image in the content. Learn more in the
// UploadAdapter#upload documentation.
resolve({
default: response.url,
});
});

// Upload progress when it is supported. The file loader has the #uploadTotal and #uploaded
// properties which are used e.g. to display the upload progress bar in the editor
// user interface.
if (xhr.upload) {
xhr.upload.addEventListener("progress", evt => {
if (evt.lengthComputable) {
loader.uploadTotal = evt.total;
loader.uploaded = evt.loaded;
}
});
}
}

// Prepares the data and sends the request.
_sendRequest(file) {
// Prepare the form data.
const data = new FormData();

data.append("upload", file);

// Important note: This is the right place to implement security mechanisms
// like authentication and CSRF protection. For instance, you can use
// XMLHttpRequest.setRequestHeader() to set the request headers containing
// the CSRF token generated earlier by your application.

// Send the request.
this.xhr.send(data);
}
}

// ...

function MyCustomUploadAdapterPlugin(editor) {
editor.plugins.get("FileRepository").createUploadAdapter = loader => {
// Configure the URL to the upload script in your back-end here!
return new MyUploadAdapter(loader);
};
}

// ...

ClassicEditor.create(document.querySelector("#editor"), {
extraPlugins: [MyCustomUploadAdapterPlugin],

// ...
}).catch(error => {
console.log(error);
});

參考資料

  1. CKEditor5 CDN
  2. vue+CKEditor5 入門
  3. Vue Component CKEditor
  4. Setting the UI language
  5. EditorConfig
  6. CKFinder 3 下載網頁