@ckeditor/ckeditor5-ckbox 38.1.1 → 38.2.0-alpha.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,277 +1,277 @@
1
- /**
2
- * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
- */
5
- /* globals AbortController, FormData, URL, Image, XMLHttpRequest, window */
6
- /**
7
- * @module ckbox/ckboxuploadadapter
8
- */
9
- import { Plugin } from 'ckeditor5/src/core';
10
- import { FileRepository } from 'ckeditor5/src/upload';
11
- import { logError } from 'ckeditor5/src/utils';
12
- import CKBoxEditing from './ckboxediting';
13
- import { getImageUrls } from './utils';
14
- /**
15
- * A plugin that enables file uploads in CKEditor 5 using the CKBox server–side connector.
16
- * See the {@glink features/file-management/ckbox CKBox file manager integration} guide to learn how to configure
17
- * and use this feature as well as find out more about the full integration with the file manager
18
- * provided by the {@link module:ckbox/ckbox~CKBox} plugin.
19
- *
20
- * Check out the {@glink features/images/image-upload/image-upload Image upload overview} guide to learn about
21
- * other ways to upload images into CKEditor 5.
22
- */
23
- export default class CKBoxUploadAdapter extends Plugin {
24
- /**
25
- * @inheritDoc
26
- */
27
- static get requires() {
28
- return ['ImageUploadEditing', 'ImageUploadProgress', FileRepository, CKBoxEditing];
29
- }
30
- /**
31
- * @inheritDoc
32
- */
33
- static get pluginName() {
34
- return 'CKBoxUploadAdapter';
35
- }
36
- /**
37
- * @inheritDoc
38
- */
39
- async afterInit() {
40
- const editor = this.editor;
41
- const hasConfiguration = !!editor.config.get('ckbox');
42
- const isLibraryLoaded = !!window.CKBox;
43
- // Editor supports only one upload adapter. Register the CKBox upload adapter (and potentially overwrite other one) only when the
44
- // integrator intentionally wants to use the CKBox plugin, i.e. when the `config.ckbox` exists or the CKBox JavaScript library is
45
- // loaded.
46
- if (!hasConfiguration && !isLibraryLoaded) {
47
- return;
48
- }
49
- const fileRepository = editor.plugins.get(FileRepository);
50
- const ckboxEditing = editor.plugins.get(CKBoxEditing);
51
- fileRepository.createUploadAdapter = loader => {
52
- return new Adapter(loader, ckboxEditing.getToken(), editor);
53
- };
54
- const shouldInsertDataId = !editor.config.get('ckbox.ignoreDataId');
55
- const imageUploadEditing = editor.plugins.get('ImageUploadEditing');
56
- // Mark uploaded assets with the `ckboxImageId` attribute. Its value represents an ID in CKBox.
57
- if (shouldInsertDataId) {
58
- imageUploadEditing.on('uploadComplete', (evt, { imageElement, data }) => {
59
- editor.model.change(writer => {
60
- writer.setAttribute('ckboxImageId', data.ckboxImageId, imageElement);
61
- });
62
- });
63
- }
64
- }
65
- }
66
- /**
67
- * Upload adapter for CKBox.
68
- */
69
- class Adapter {
70
- /**
71
- * Creates a new adapter instance.
72
- */
73
- constructor(loader, token, editor) {
74
- this.loader = loader;
75
- this.token = token;
76
- this.editor = editor;
77
- this.controller = new AbortController();
78
- this.serviceOrigin = editor.config.get('ckbox.serviceOrigin');
79
- this.assetsOrigin = editor.config.get('ckbox.assetsOrigin');
80
- }
81
- /**
82
- * Resolves a promise with an array containing available categories with which the uploaded file can be associated.
83
- *
84
- * If the API returns limited results, the method will collect all items.
85
- */
86
- async getAvailableCategories(offset = 0) {
87
- const ITEMS_PER_REQUEST = 50;
88
- const categoryUrl = new URL('categories', this.serviceOrigin);
89
- categoryUrl.searchParams.set('limit', ITEMS_PER_REQUEST.toString());
90
- categoryUrl.searchParams.set('offset', offset.toString());
91
- return this._sendHttpRequest({ url: categoryUrl })
92
- .then(async (data) => {
93
- const remainingItems = data.totalCount - (offset + ITEMS_PER_REQUEST);
94
- if (remainingItems > 0) {
95
- const offsetItems = await this.getAvailableCategories(offset + ITEMS_PER_REQUEST);
96
- return [
97
- ...data.items,
98
- ...offsetItems
99
- ];
100
- }
101
- return data.items;
102
- })
103
- .catch(() => {
104
- this.controller.signal.throwIfAborted();
105
- /**
106
- * Fetching a list of available categories with which an uploaded file can be associated failed.
107
- *
108
- * @error ckbox-fetch-category-http-error
109
- */
110
- logError('ckbox-fetch-category-http-error');
111
- });
112
- }
113
- /**
114
- * Resolves a promise with an object containing a category with which the uploaded file is associated or an error code.
115
- */
116
- async getCategoryIdForFile(file) {
117
- const extension = getFileExtension(file.name);
118
- const allCategories = await this.getAvailableCategories();
119
- // Couldn't fetch all categories. Perhaps the authorization token is invalid.
120
- if (!allCategories) {
121
- return null;
122
- }
123
- // The plugin allows defining to which category the uploaded file should be assigned.
124
- const defaultCategories = this.editor.config.get('ckbox.defaultUploadCategories');
125
- // If a user specifies the plugin configuration, find the first category that accepts the uploaded file.
126
- if (defaultCategories) {
127
- const userCategory = Object.keys(defaultCategories).find(category => {
128
- return defaultCategories[category].includes(extension);
129
- });
130
- // If found, return its ID if the category exists on the server side.
131
- if (userCategory) {
132
- const serverCategory = allCategories.find(category => category.id === userCategory || category.name === userCategory);
133
- if (!serverCategory) {
134
- return null;
135
- }
136
- return serverCategory.id;
137
- }
138
- }
139
- // Otherwise, find the first category that accepts the uploaded file and returns its ID.
140
- const category = allCategories.find(category => category.extensions.includes(extension));
141
- if (!category) {
142
- return null;
143
- }
144
- return category.id;
145
- }
146
- /**
147
- * Starts the upload process.
148
- *
149
- * @see module:upload/filerepository~UploadAdapter#upload
150
- */
151
- async upload() {
152
- const t = this.editor.t;
153
- const cannotFindCategoryError = t('Cannot determine a category for the uploaded file.');
154
- const file = (await this.loader.file);
155
- const category = await this.getCategoryIdForFile(file);
156
- if (!category) {
157
- return Promise.reject(cannotFindCategoryError);
158
- }
159
- const uploadUrl = new URL('assets', this.serviceOrigin);
160
- const formData = new FormData();
161
- formData.append('categoryId', category);
162
- formData.append('file', file);
163
- const requestConfig = {
164
- method: 'POST',
165
- url: uploadUrl,
166
- data: formData,
167
- onUploadProgress: (evt) => {
168
- /* istanbul ignore else -- @preserve */
169
- if (evt.lengthComputable) {
170
- this.loader.uploadTotal = evt.total;
171
- this.loader.uploaded = evt.loaded;
172
- }
173
- }
174
- };
175
- return this._sendHttpRequest(requestConfig)
176
- .then(async (data) => {
177
- const width = await this._getImageWidth();
178
- const extension = getFileExtension(file.name);
179
- const imageUrls = getImageUrls({
180
- token: this.token,
181
- id: data.id,
182
- origin: this.assetsOrigin,
183
- width,
184
- extension
185
- });
186
- return {
187
- ckboxImageId: data.id,
188
- default: imageUrls.imageFallbackUrl,
189
- sources: imageUrls.imageSources
190
- };
191
- })
192
- .catch(() => {
193
- const genericError = t('Cannot upload file:') + ` ${file.name}.`;
194
- return Promise.reject(genericError);
195
- });
196
- }
197
- /**
198
- * Aborts the upload process.
199
- *
200
- * @see module:upload/filerepository~UploadAdapter#abort
201
- */
202
- abort() {
203
- this.controller.abort();
204
- }
205
- /**
206
- * Sends the HTTP request.
207
- *
208
- * @param config.url the URL where the request will be sent.
209
- * @param config.method The HTTP method.
210
- * @param config.data Additional data to send.
211
- * @param config.onUploadProgress A callback informing about the upload progress.
212
- */
213
- _sendHttpRequest({ url, method = 'GET', data, onUploadProgress }) {
214
- const signal = this.controller.signal;
215
- const xhr = new XMLHttpRequest();
216
- xhr.open(method, url.toString(), true);
217
- xhr.setRequestHeader('Authorization', this.token.value);
218
- xhr.setRequestHeader('CKBox-Version', 'CKEditor 5');
219
- xhr.responseType = 'json';
220
- // The callback is attached to the `signal#abort` event.
221
- const abortCallback = () => {
222
- xhr.abort();
223
- };
224
- return new Promise((resolve, reject) => {
225
- signal.addEventListener('abort', abortCallback);
226
- xhr.addEventListener('loadstart', () => {
227
- signal.addEventListener('abort', abortCallback);
228
- });
229
- xhr.addEventListener('loadend', () => {
230
- signal.removeEventListener('abort', abortCallback);
231
- });
232
- xhr.addEventListener('error', () => {
233
- reject();
234
- });
235
- xhr.addEventListener('abort', () => {
236
- reject();
237
- });
238
- xhr.addEventListener('load', async () => {
239
- const response = xhr.response;
240
- if (!response || response.statusCode >= 400) {
241
- return reject(response && response.message);
242
- }
243
- return resolve(response);
244
- });
245
- /* istanbul ignore else -- @preserve */
246
- if (onUploadProgress) {
247
- xhr.upload.addEventListener('progress', evt => {
248
- onUploadProgress(evt);
249
- });
250
- }
251
- // Send the request.
252
- xhr.send(data);
253
- });
254
- }
255
- /**
256
- * Resolves a promise with a number representing the width of a given image file.
257
- */
258
- _getImageWidth() {
259
- return new Promise(resolve => {
260
- const image = new Image();
261
- image.onload = () => {
262
- // Let the browser know that it should not keep the reference any longer to avoid memory leeks.
263
- URL.revokeObjectURL(image.src);
264
- resolve(image.width);
265
- };
266
- image.src = this.loader.data;
267
- });
268
- }
269
- }
270
- /**
271
- * Returns an extension from the given value.
272
- */
273
- function getFileExtension(value) {
274
- const extensionRegExp = /\.(?<ext>[^.]+)$/;
275
- const match = value.match(extensionRegExp);
276
- return match.groups.ext;
277
- }
1
+ /**
2
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /* globals AbortController, FormData, URL, Image, XMLHttpRequest, window */
6
+ /**
7
+ * @module ckbox/ckboxuploadadapter
8
+ */
9
+ import { Plugin } from 'ckeditor5/src/core.js';
10
+ import { FileRepository } from 'ckeditor5/src/upload.js';
11
+ import { logError } from 'ckeditor5/src/utils.js';
12
+ import CKBoxEditing from './ckboxediting.js';
13
+ import { getImageUrls } from './utils.js';
14
+ /**
15
+ * A plugin that enables file uploads in CKEditor 5 using the CKBox server–side connector.
16
+ * See the {@glink features/file-management/ckbox CKBox file manager integration} guide to learn how to configure
17
+ * and use this feature as well as find out more about the full integration with the file manager
18
+ * provided by the {@link module:ckbox/ckbox~CKBox} plugin.
19
+ *
20
+ * Check out the {@glink features/images/image-upload/image-upload Image upload overview} guide to learn about
21
+ * other ways to upload images into CKEditor 5.
22
+ */
23
+ export default class CKBoxUploadAdapter extends Plugin {
24
+ /**
25
+ * @inheritDoc
26
+ */
27
+ static get requires() {
28
+ return ['ImageUploadEditing', 'ImageUploadProgress', FileRepository, CKBoxEditing];
29
+ }
30
+ /**
31
+ * @inheritDoc
32
+ */
33
+ static get pluginName() {
34
+ return 'CKBoxUploadAdapter';
35
+ }
36
+ /**
37
+ * @inheritDoc
38
+ */
39
+ async afterInit() {
40
+ const editor = this.editor;
41
+ const hasConfiguration = !!editor.config.get('ckbox');
42
+ const isLibraryLoaded = !!window.CKBox;
43
+ // Editor supports only one upload adapter. Register the CKBox upload adapter (and potentially overwrite other one) only when the
44
+ // integrator intentionally wants to use the CKBox plugin, i.e. when the `config.ckbox` exists or the CKBox JavaScript library is
45
+ // loaded.
46
+ if (!hasConfiguration && !isLibraryLoaded) {
47
+ return;
48
+ }
49
+ const fileRepository = editor.plugins.get(FileRepository);
50
+ const ckboxEditing = editor.plugins.get(CKBoxEditing);
51
+ fileRepository.createUploadAdapter = loader => {
52
+ return new Adapter(loader, ckboxEditing.getToken(), editor);
53
+ };
54
+ const shouldInsertDataId = !editor.config.get('ckbox.ignoreDataId');
55
+ const imageUploadEditing = editor.plugins.get('ImageUploadEditing');
56
+ // Mark uploaded assets with the `ckboxImageId` attribute. Its value represents an ID in CKBox.
57
+ if (shouldInsertDataId) {
58
+ imageUploadEditing.on('uploadComplete', (evt, { imageElement, data }) => {
59
+ editor.model.change(writer => {
60
+ writer.setAttribute('ckboxImageId', data.ckboxImageId, imageElement);
61
+ });
62
+ });
63
+ }
64
+ }
65
+ }
66
+ /**
67
+ * Upload adapter for CKBox.
68
+ */
69
+ class Adapter {
70
+ /**
71
+ * Creates a new adapter instance.
72
+ */
73
+ constructor(loader, token, editor) {
74
+ this.loader = loader;
75
+ this.token = token;
76
+ this.editor = editor;
77
+ this.controller = new AbortController();
78
+ this.serviceOrigin = editor.config.get('ckbox.serviceOrigin');
79
+ this.assetsOrigin = editor.config.get('ckbox.assetsOrigin');
80
+ }
81
+ /**
82
+ * Resolves a promise with an array containing available categories with which the uploaded file can be associated.
83
+ *
84
+ * If the API returns limited results, the method will collect all items.
85
+ */
86
+ async getAvailableCategories(offset = 0) {
87
+ const ITEMS_PER_REQUEST = 50;
88
+ const categoryUrl = new URL('categories', this.serviceOrigin);
89
+ categoryUrl.searchParams.set('limit', ITEMS_PER_REQUEST.toString());
90
+ categoryUrl.searchParams.set('offset', offset.toString());
91
+ return this._sendHttpRequest({ url: categoryUrl })
92
+ .then(async (data) => {
93
+ const remainingItems = data.totalCount - (offset + ITEMS_PER_REQUEST);
94
+ if (remainingItems > 0) {
95
+ const offsetItems = await this.getAvailableCategories(offset + ITEMS_PER_REQUEST);
96
+ return [
97
+ ...data.items,
98
+ ...offsetItems
99
+ ];
100
+ }
101
+ return data.items;
102
+ })
103
+ .catch(() => {
104
+ this.controller.signal.throwIfAborted();
105
+ /**
106
+ * Fetching a list of available categories with which an uploaded file can be associated failed.
107
+ *
108
+ * @error ckbox-fetch-category-http-error
109
+ */
110
+ logError('ckbox-fetch-category-http-error');
111
+ });
112
+ }
113
+ /**
114
+ * Resolves a promise with an object containing a category with which the uploaded file is associated or an error code.
115
+ */
116
+ async getCategoryIdForFile(file) {
117
+ const extension = getFileExtension(file.name);
118
+ const allCategories = await this.getAvailableCategories();
119
+ // Couldn't fetch all categories. Perhaps the authorization token is invalid.
120
+ if (!allCategories) {
121
+ return null;
122
+ }
123
+ // The plugin allows defining to which category the uploaded file should be assigned.
124
+ const defaultCategories = this.editor.config.get('ckbox.defaultUploadCategories');
125
+ // If a user specifies the plugin configuration, find the first category that accepts the uploaded file.
126
+ if (defaultCategories) {
127
+ const userCategory = Object.keys(defaultCategories).find(category => {
128
+ return defaultCategories[category].find(e => e.toLowerCase() == extension);
129
+ });
130
+ // If found, return its ID if the category exists on the server side.
131
+ if (userCategory) {
132
+ const serverCategory = allCategories.find(category => category.id === userCategory || category.name === userCategory);
133
+ if (!serverCategory) {
134
+ return null;
135
+ }
136
+ return serverCategory.id;
137
+ }
138
+ }
139
+ // Otherwise, find the first category that accepts the uploaded file and returns its ID.
140
+ const category = allCategories.find(category => category.extensions.find(e => e.toLowerCase() == extension));
141
+ if (!category) {
142
+ return null;
143
+ }
144
+ return category.id;
145
+ }
146
+ /**
147
+ * Starts the upload process.
148
+ *
149
+ * @see module:upload/filerepository~UploadAdapter#upload
150
+ */
151
+ async upload() {
152
+ const t = this.editor.t;
153
+ const cannotFindCategoryError = t('Cannot determine a category for the uploaded file.');
154
+ const file = (await this.loader.file);
155
+ const category = await this.getCategoryIdForFile(file);
156
+ if (!category) {
157
+ return Promise.reject(cannotFindCategoryError);
158
+ }
159
+ const uploadUrl = new URL('assets', this.serviceOrigin);
160
+ const formData = new FormData();
161
+ formData.append('categoryId', category);
162
+ formData.append('file', file);
163
+ const requestConfig = {
164
+ method: 'POST',
165
+ url: uploadUrl,
166
+ data: formData,
167
+ onUploadProgress: (evt) => {
168
+ /* istanbul ignore else -- @preserve */
169
+ if (evt.lengthComputable) {
170
+ this.loader.uploadTotal = evt.total;
171
+ this.loader.uploaded = evt.loaded;
172
+ }
173
+ }
174
+ };
175
+ return this._sendHttpRequest(requestConfig)
176
+ .then(async (data) => {
177
+ const width = await this._getImageWidth();
178
+ const extension = getFileExtension(file.name);
179
+ const imageUrls = getImageUrls({
180
+ token: this.token,
181
+ id: data.id,
182
+ origin: this.assetsOrigin,
183
+ width,
184
+ extension
185
+ });
186
+ return {
187
+ ckboxImageId: data.id,
188
+ default: imageUrls.imageFallbackUrl,
189
+ sources: imageUrls.imageSources
190
+ };
191
+ })
192
+ .catch(() => {
193
+ const genericError = t('Cannot upload file:') + ` ${file.name}.`;
194
+ return Promise.reject(genericError);
195
+ });
196
+ }
197
+ /**
198
+ * Aborts the upload process.
199
+ *
200
+ * @see module:upload/filerepository~UploadAdapter#abort
201
+ */
202
+ abort() {
203
+ this.controller.abort();
204
+ }
205
+ /**
206
+ * Sends the HTTP request.
207
+ *
208
+ * @param config.url the URL where the request will be sent.
209
+ * @param config.method The HTTP method.
210
+ * @param config.data Additional data to send.
211
+ * @param config.onUploadProgress A callback informing about the upload progress.
212
+ */
213
+ _sendHttpRequest({ url, method = 'GET', data, onUploadProgress }) {
214
+ const signal = this.controller.signal;
215
+ const xhr = new XMLHttpRequest();
216
+ xhr.open(method, url.toString(), true);
217
+ xhr.setRequestHeader('Authorization', this.token.value);
218
+ xhr.setRequestHeader('CKBox-Version', 'CKEditor 5');
219
+ xhr.responseType = 'json';
220
+ // The callback is attached to the `signal#abort` event.
221
+ const abortCallback = () => {
222
+ xhr.abort();
223
+ };
224
+ return new Promise((resolve, reject) => {
225
+ signal.addEventListener('abort', abortCallback);
226
+ xhr.addEventListener('loadstart', () => {
227
+ signal.addEventListener('abort', abortCallback);
228
+ });
229
+ xhr.addEventListener('loadend', () => {
230
+ signal.removeEventListener('abort', abortCallback);
231
+ });
232
+ xhr.addEventListener('error', () => {
233
+ reject();
234
+ });
235
+ xhr.addEventListener('abort', () => {
236
+ reject();
237
+ });
238
+ xhr.addEventListener('load', async () => {
239
+ const response = xhr.response;
240
+ if (!response || response.statusCode >= 400) {
241
+ return reject(response && response.message);
242
+ }
243
+ return resolve(response);
244
+ });
245
+ /* istanbul ignore else -- @preserve */
246
+ if (onUploadProgress) {
247
+ xhr.upload.addEventListener('progress', evt => {
248
+ onUploadProgress(evt);
249
+ });
250
+ }
251
+ // Send the request.
252
+ xhr.send(data);
253
+ });
254
+ }
255
+ /**
256
+ * Resolves a promise with a number representing the width of a given image file.
257
+ */
258
+ _getImageWidth() {
259
+ return new Promise(resolve => {
260
+ const image = new Image();
261
+ image.onload = () => {
262
+ // Let the browser know that it should not keep the reference any longer to avoid memory leeks.
263
+ URL.revokeObjectURL(image.src);
264
+ resolve(image.width);
265
+ };
266
+ image.src = this.loader.data;
267
+ });
268
+ }
269
+ }
270
+ /**
271
+ * Returns an extension from the given value.
272
+ */
273
+ function getFileExtension(value) {
274
+ const extensionRegExp = /\.(?<ext>[^.]+)$/;
275
+ const match = value.match(extensionRegExp);
276
+ return match.groups.ext.toLowerCase();
277
+ }
package/src/index.d.ts CHANGED
@@ -1,13 +1,13 @@
1
- /**
2
- * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
- */
5
- /**
6
- * @module ckbox
7
- */
8
- export { default as CKBox } from './ckbox';
9
- export { default as CKBoxEditing } from './ckboxediting';
10
- export { default as CKBoxUI } from './ckboxui';
11
- export type { default as CKBoxCommand } from './ckboxcommand';
12
- export type { CKBoxConfig } from './ckboxconfig';
13
- import './augmentation';
1
+ /**
2
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * @module ckbox
7
+ */
8
+ export { default as CKBox } from './ckbox.js';
9
+ export { default as CKBoxEditing } from './ckboxediting.js';
10
+ export { default as CKBoxUI } from './ckboxui.js';
11
+ export type { default as CKBoxCommand } from './ckboxcommand.js';
12
+ export type { CKBoxConfig } from './ckboxconfig.js';
13
+ import './augmentation.js';
package/src/index.js CHANGED
@@ -1,11 +1,11 @@
1
- /**
2
- * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
- */
5
- /**
6
- * @module ckbox
7
- */
8
- export { default as CKBox } from './ckbox';
9
- export { default as CKBoxEditing } from './ckboxediting';
10
- export { default as CKBoxUI } from './ckboxui';
11
- import './augmentation';
1
+ /**
2
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * @module ckbox
7
+ */
8
+ export { default as CKBox } from './ckbox.js';
9
+ export { default as CKBoxEditing } from './ckboxediting.js';
10
+ export { default as CKBoxUI } from './ckboxui.js';
11
+ import './augmentation.js';