@ckeditor/ckeditor5-upload 40.0.0 → 40.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,383 +1,383 @@
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 upload/filerepository
7
- */
8
- import { Plugin, PendingActions } from '@ckeditor/ckeditor5-core';
9
- import { CKEditorError, Collection, ObservableMixin, logWarning, uid } from '@ckeditor/ckeditor5-utils';
10
- import FileReader from './filereader';
11
- /**
12
- * File repository plugin. A central point for managing file upload.
13
- *
14
- * To use it, first you need an upload adapter. Upload adapter's job is to handle communication with the server
15
- * (sending the file and handling server's response). You can use one of the existing plugins introducing upload adapters
16
- * (e.g. {@link module:easy-image/cloudservicesuploadadapter~CloudServicesUploadAdapter} or
17
- * {@link module:adapter-ckfinder/uploadadapter~CKFinderUploadAdapter}) or write your own one – see
18
- * the {@glink framework/deep-dive/upload-adapter Custom image upload adapter deep-dive} guide.
19
- *
20
- * Then, you can use {@link module:upload/filerepository~FileRepository#createLoader `createLoader()`} and the returned
21
- * {@link module:upload/filerepository~FileLoader} instance to load and upload files.
22
- */
23
- export default class FileRepository extends Plugin {
24
- constructor() {
25
- super(...arguments);
26
- /**
27
- * Collection of loaders associated with this repository.
28
- */
29
- this.loaders = new Collection();
30
- /**
31
- * Loaders mappings used to retrieve loaders references.
32
- */
33
- this._loadersMap = new Map();
34
- /**
35
- * Reference to a pending action registered in a {@link module:core/pendingactions~PendingActions} plugin
36
- * while upload is in progress. When there is no upload then value is `null`.
37
- */
38
- this._pendingAction = null;
39
- }
40
- /**
41
- * @inheritDoc
42
- */
43
- static get pluginName() {
44
- return 'FileRepository';
45
- }
46
- /**
47
- * @inheritDoc
48
- */
49
- static get requires() {
50
- return [PendingActions];
51
- }
52
- /**
53
- * @inheritDoc
54
- */
55
- init() {
56
- // Keeps upload in a sync with pending actions.
57
- this.loaders.on('change', () => this._updatePendingAction());
58
- this.set('uploaded', 0);
59
- this.set('uploadTotal', null);
60
- this.bind('uploadedPercent').to(this, 'uploaded', this, 'uploadTotal', (uploaded, total) => {
61
- return total ? (uploaded / total * 100) : 0;
62
- });
63
- }
64
- /**
65
- * Returns the loader associated with specified file or promise.
66
- *
67
- * To get loader by id use `fileRepository.loaders.get( id )`.
68
- *
69
- * @param fileOrPromise Native file or promise handle.
70
- */
71
- getLoader(fileOrPromise) {
72
- return this._loadersMap.get(fileOrPromise) || null;
73
- }
74
- /**
75
- * Creates a loader instance for the given file.
76
- *
77
- * Requires {@link #createUploadAdapter} factory to be defined.
78
- *
79
- * @param fileOrPromise Native File object or native Promise object which resolves to a File.
80
- */
81
- createLoader(fileOrPromise) {
82
- if (!this.createUploadAdapter) {
83
- /**
84
- * You need to enable an upload adapter in order to be able to upload files.
85
- *
86
- * This warning shows up when {@link module:upload/filerepository~FileRepository} is being used
87
- * without {@link module:upload/filerepository~FileRepository#createUploadAdapter defining an upload adapter}.
88
- *
89
- * **If you see this warning when using one of the {@glink installation/getting-started/predefined-builds
90
- * CKEditor 5 Builds}**
91
- * it means that you did not configure any of the upload adapters available by default in those builds.
92
- *
93
- * See the {@glink features/images/image-upload/image-upload comprehensive "Image upload overview"} to learn which upload
94
- * adapters are available in the builds and how to configure them.
95
- *
96
- * **If you see this warning when using a custom build** there is a chance that you enabled
97
- * a feature like {@link module:image/imageupload~ImageUpload},
98
- * or {@link module:image/imageupload/imageuploadui~ImageUploadUI} but you did not enable any upload adapter.
99
- * You can choose one of the existing upload adapters listed in the
100
- * {@glink features/images/image-upload/image-upload "Image upload overview"}.
101
- *
102
- * You can also implement your {@glink framework/deep-dive/upload-adapter own image upload adapter}.
103
- *
104
- * @error filerepository-no-upload-adapter
105
- */
106
- logWarning('filerepository-no-upload-adapter');
107
- return null;
108
- }
109
- const loader = new FileLoader(Promise.resolve(fileOrPromise), this.createUploadAdapter);
110
- this.loaders.add(loader);
111
- this._loadersMap.set(fileOrPromise, loader);
112
- // Store also file => loader mapping so loader can be retrieved by file instance returned upon Promise resolution.
113
- if (fileOrPromise instanceof Promise) {
114
- loader.file
115
- .then(file => {
116
- this._loadersMap.set(file, loader);
117
- })
118
- // Every then() must have a catch().
119
- // File loader state (and rejections) are handled in read() and upload().
120
- // Also, see the "does not swallow the file promise rejection" test.
121
- .catch(() => { });
122
- }
123
- loader.on('change:uploaded', () => {
124
- let aggregatedUploaded = 0;
125
- for (const loader of this.loaders) {
126
- aggregatedUploaded += loader.uploaded;
127
- }
128
- this.uploaded = aggregatedUploaded;
129
- });
130
- loader.on('change:uploadTotal', () => {
131
- let aggregatedTotal = 0;
132
- for (const loader of this.loaders) {
133
- if (loader.uploadTotal) {
134
- aggregatedTotal += loader.uploadTotal;
135
- }
136
- }
137
- this.uploadTotal = aggregatedTotal;
138
- });
139
- return loader;
140
- }
141
- /**
142
- * Destroys the given loader.
143
- *
144
- * @param fileOrPromiseOrLoader File or Promise associated with that loader or loader itself.
145
- */
146
- destroyLoader(fileOrPromiseOrLoader) {
147
- const loader = fileOrPromiseOrLoader instanceof FileLoader ? fileOrPromiseOrLoader : this.getLoader(fileOrPromiseOrLoader);
148
- loader._destroy();
149
- this.loaders.remove(loader);
150
- this._loadersMap.forEach((value, key) => {
151
- if (value === loader) {
152
- this._loadersMap.delete(key);
153
- }
154
- });
155
- }
156
- /**
157
- * Registers or deregisters pending action bound with upload progress.
158
- */
159
- _updatePendingAction() {
160
- const pendingActions = this.editor.plugins.get(PendingActions);
161
- if (this.loaders.length) {
162
- if (!this._pendingAction) {
163
- const t = this.editor.t;
164
- const getMessage = (value) => `${t('Upload in progress')} ${parseInt(value)}%.`;
165
- this._pendingAction = pendingActions.add(getMessage(this.uploadedPercent));
166
- this._pendingAction.bind('message').to(this, 'uploadedPercent', getMessage);
167
- }
168
- }
169
- else {
170
- pendingActions.remove(this._pendingAction);
171
- this._pendingAction = null;
172
- }
173
- }
174
- }
175
- /**
176
- * File loader class.
177
- *
178
- * It is used to control the process of reading the file and uploading it using the specified upload adapter.
179
- */
180
- class FileLoader extends ObservableMixin() {
181
- /**
182
- * Creates a new instance of `FileLoader`.
183
- *
184
- * @param filePromise A promise which resolves to a file instance.
185
- * @param uploadAdapterCreator The function which returns {@link module:upload/filerepository~UploadAdapter} instance.
186
- */
187
- constructor(filePromise, uploadAdapterCreator) {
188
- super();
189
- this.id = uid();
190
- this._filePromiseWrapper = this._createFilePromiseWrapper(filePromise);
191
- this._adapter = uploadAdapterCreator(this);
192
- this._reader = new FileReader();
193
- this.set('status', 'idle');
194
- this.set('uploaded', 0);
195
- this.set('uploadTotal', null);
196
- this.bind('uploadedPercent').to(this, 'uploaded', this, 'uploadTotal', (uploaded, total) => {
197
- return total ? (uploaded / total * 100) : 0;
198
- });
199
- this.set('uploadResponse', null);
200
- }
201
- /**
202
- * A `Promise` which resolves to a `File` instance associated with this file loader.
203
- */
204
- get file() {
205
- if (!this._filePromiseWrapper) {
206
- // Loader was destroyed, return promise which resolves to null.
207
- return Promise.resolve(null);
208
- }
209
- else {
210
- // The `this._filePromiseWrapper.promise` is chained and not simply returned to handle a case when:
211
- //
212
- // * The `loader.file.then( ... )` is called by external code (returned promise is pending).
213
- // * Then `loader._destroy()` is called (call is synchronous) which destroys the `loader`.
214
- // * Promise returned by the first `loader.file.then( ... )` call is resolved.
215
- //
216
- // Returning `this._filePromiseWrapper.promise` will still resolve to a `File` instance so there
217
- // is an additional check needed in the chain to see if `loader` was destroyed in the meantime.
218
- return this._filePromiseWrapper.promise.then(file => this._filePromiseWrapper ? file : null);
219
- }
220
- }
221
- /**
222
- * Returns the file data. To read its data, you need for first load the file
223
- * by using the {@link module:upload/filerepository~FileLoader#read `read()`} method.
224
- */
225
- get data() {
226
- return this._reader.data;
227
- }
228
- /**
229
- * Reads file using {@link module:upload/filereader~FileReader}.
230
- *
231
- * Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `filerepository-read-wrong-status` when status
232
- * is different than `idle`.
233
- *
234
- * Example usage:
235
- *
236
- * ```ts
237
- * fileLoader.read()
238
- * .then( data => { ... } )
239
- * .catch( err => {
240
- * if ( err === 'aborted' ) {
241
- * console.log( 'Reading aborted.' );
242
- * } else {
243
- * console.log( 'Reading error.', err );
244
- * }
245
- * } );
246
- * ```
247
- *
248
- * @returns Returns promise that will be resolved with read data. Promise will be rejected if error
249
- * occurs or if read process is aborted.
250
- */
251
- read() {
252
- if (this.status != 'idle') {
253
- /**
254
- * You cannot call read if the status is different than idle.
255
- *
256
- * @error filerepository-read-wrong-status
257
- */
258
- throw new CKEditorError('filerepository-read-wrong-status', this);
259
- }
260
- this.status = 'reading';
261
- return this.file
262
- .then(file => this._reader.read(file))
263
- .then(data => {
264
- // Edge case: reader was aborted after file was read - double check for proper status.
265
- // It can happen when image was deleted during its upload.
266
- if (this.status !== 'reading') {
267
- throw this.status;
268
- }
269
- this.status = 'idle';
270
- return data;
271
- })
272
- .catch(err => {
273
- if (err === 'aborted') {
274
- this.status = 'aborted';
275
- throw 'aborted';
276
- }
277
- this.status = 'error';
278
- throw this._reader.error ? this._reader.error : err;
279
- });
280
- }
281
- /**
282
- * Reads file using the provided {@link module:upload/filerepository~UploadAdapter}.
283
- *
284
- * Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `filerepository-upload-wrong-status` when status
285
- * is different than `idle`.
286
- * Example usage:
287
- *
288
- * ```ts
289
- * fileLoader.upload()
290
- * .then( data => { ... } )
291
- * .catch( e => {
292
- * if ( e === 'aborted' ) {
293
- * console.log( 'Uploading aborted.' );
294
- * } else {
295
- * console.log( 'Uploading error.', e );
296
- * }
297
- * } );
298
- * ```
299
- *
300
- * @returns Returns promise that will be resolved with response data. Promise will be rejected if error
301
- * occurs or if read process is aborted.
302
- */
303
- upload() {
304
- if (this.status != 'idle') {
305
- /**
306
- * You cannot call upload if the status is different than idle.
307
- *
308
- * @error filerepository-upload-wrong-status
309
- */
310
- throw new CKEditorError('filerepository-upload-wrong-status', this);
311
- }
312
- this.status = 'uploading';
313
- return this.file
314
- .then(() => this._adapter.upload())
315
- .then(data => {
316
- this.uploadResponse = data;
317
- this.status = 'idle';
318
- return data;
319
- })
320
- .catch(err => {
321
- if (this.status === 'aborted') {
322
- throw 'aborted';
323
- }
324
- this.status = 'error';
325
- throw err;
326
- });
327
- }
328
- /**
329
- * Aborts loading process.
330
- */
331
- abort() {
332
- const status = this.status;
333
- this.status = 'aborted';
334
- if (!this._filePromiseWrapper.isFulfilled) {
335
- // Edge case: file loader is aborted before read() is called
336
- // so it might happen that no one handled the rejection of this promise.
337
- // See https://github.com/ckeditor/ckeditor5-upload/pull/100
338
- this._filePromiseWrapper.promise.catch(() => { });
339
- this._filePromiseWrapper.rejecter('aborted');
340
- }
341
- else if (status == 'reading') {
342
- this._reader.abort();
343
- }
344
- else if (status == 'uploading' && this._adapter.abort) {
345
- this._adapter.abort();
346
- }
347
- this._destroy();
348
- }
349
- /**
350
- * Performs cleanup.
351
- *
352
- * @internal
353
- */
354
- _destroy() {
355
- this._filePromiseWrapper = undefined;
356
- this._reader = undefined;
357
- this._adapter = undefined;
358
- this.uploadResponse = undefined;
359
- }
360
- /**
361
- * Wraps a given file promise into another promise giving additional
362
- * control (resolving, rejecting, checking if fulfilled) over it.
363
- *
364
- * @param filePromise The initial file promise to be wrapped.
365
- */
366
- _createFilePromiseWrapper(filePromise) {
367
- const wrapper = {};
368
- wrapper.promise = new Promise((resolve, reject) => {
369
- wrapper.rejecter = reject;
370
- wrapper.isFulfilled = false;
371
- filePromise
372
- .then(file => {
373
- wrapper.isFulfilled = true;
374
- resolve(file);
375
- })
376
- .catch(err => {
377
- wrapper.isFulfilled = true;
378
- reject(err);
379
- });
380
- });
381
- return wrapper;
382
- }
383
- }
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 upload/filerepository
7
+ */
8
+ import { Plugin, PendingActions } from '@ckeditor/ckeditor5-core';
9
+ import { CKEditorError, Collection, ObservableMixin, logWarning, uid } from '@ckeditor/ckeditor5-utils';
10
+ import FileReader from './filereader';
11
+ /**
12
+ * File repository plugin. A central point for managing file upload.
13
+ *
14
+ * To use it, first you need an upload adapter. Upload adapter's job is to handle communication with the server
15
+ * (sending the file and handling server's response). You can use one of the existing plugins introducing upload adapters
16
+ * (e.g. {@link module:easy-image/cloudservicesuploadadapter~CloudServicesUploadAdapter} or
17
+ * {@link module:adapter-ckfinder/uploadadapter~CKFinderUploadAdapter}) or write your own one – see
18
+ * the {@glink framework/deep-dive/upload-adapter Custom image upload adapter deep-dive} guide.
19
+ *
20
+ * Then, you can use {@link module:upload/filerepository~FileRepository#createLoader `createLoader()`} and the returned
21
+ * {@link module:upload/filerepository~FileLoader} instance to load and upload files.
22
+ */
23
+ export default class FileRepository extends Plugin {
24
+ constructor() {
25
+ super(...arguments);
26
+ /**
27
+ * Collection of loaders associated with this repository.
28
+ */
29
+ this.loaders = new Collection();
30
+ /**
31
+ * Loaders mappings used to retrieve loaders references.
32
+ */
33
+ this._loadersMap = new Map();
34
+ /**
35
+ * Reference to a pending action registered in a {@link module:core/pendingactions~PendingActions} plugin
36
+ * while upload is in progress. When there is no upload then value is `null`.
37
+ */
38
+ this._pendingAction = null;
39
+ }
40
+ /**
41
+ * @inheritDoc
42
+ */
43
+ static get pluginName() {
44
+ return 'FileRepository';
45
+ }
46
+ /**
47
+ * @inheritDoc
48
+ */
49
+ static get requires() {
50
+ return [PendingActions];
51
+ }
52
+ /**
53
+ * @inheritDoc
54
+ */
55
+ init() {
56
+ // Keeps upload in a sync with pending actions.
57
+ this.loaders.on('change', () => this._updatePendingAction());
58
+ this.set('uploaded', 0);
59
+ this.set('uploadTotal', null);
60
+ this.bind('uploadedPercent').to(this, 'uploaded', this, 'uploadTotal', (uploaded, total) => {
61
+ return total ? (uploaded / total * 100) : 0;
62
+ });
63
+ }
64
+ /**
65
+ * Returns the loader associated with specified file or promise.
66
+ *
67
+ * To get loader by id use `fileRepository.loaders.get( id )`.
68
+ *
69
+ * @param fileOrPromise Native file or promise handle.
70
+ */
71
+ getLoader(fileOrPromise) {
72
+ return this._loadersMap.get(fileOrPromise) || null;
73
+ }
74
+ /**
75
+ * Creates a loader instance for the given file.
76
+ *
77
+ * Requires {@link #createUploadAdapter} factory to be defined.
78
+ *
79
+ * @param fileOrPromise Native File object or native Promise object which resolves to a File.
80
+ */
81
+ createLoader(fileOrPromise) {
82
+ if (!this.createUploadAdapter) {
83
+ /**
84
+ * You need to enable an upload adapter in order to be able to upload files.
85
+ *
86
+ * This warning shows up when {@link module:upload/filerepository~FileRepository} is being used
87
+ * without {@link module:upload/filerepository~FileRepository#createUploadAdapter defining an upload adapter}.
88
+ *
89
+ * **If you see this warning when using one of the {@glink installation/getting-started/predefined-builds
90
+ * CKEditor 5 Builds}**
91
+ * it means that you did not configure any of the upload adapters available by default in those builds.
92
+ *
93
+ * See the {@glink features/images/image-upload/image-upload comprehensive "Image upload overview"} to learn which upload
94
+ * adapters are available in the builds and how to configure them.
95
+ *
96
+ * **If you see this warning when using a custom build** there is a chance that you enabled
97
+ * a feature like {@link module:image/imageupload~ImageUpload},
98
+ * or {@link module:image/imageupload/imageuploadui~ImageUploadUI} but you did not enable any upload adapter.
99
+ * You can choose one of the existing upload adapters listed in the
100
+ * {@glink features/images/image-upload/image-upload "Image upload overview"}.
101
+ *
102
+ * You can also implement your {@glink framework/deep-dive/upload-adapter own image upload adapter}.
103
+ *
104
+ * @error filerepository-no-upload-adapter
105
+ */
106
+ logWarning('filerepository-no-upload-adapter');
107
+ return null;
108
+ }
109
+ const loader = new FileLoader(Promise.resolve(fileOrPromise), this.createUploadAdapter);
110
+ this.loaders.add(loader);
111
+ this._loadersMap.set(fileOrPromise, loader);
112
+ // Store also file => loader mapping so loader can be retrieved by file instance returned upon Promise resolution.
113
+ if (fileOrPromise instanceof Promise) {
114
+ loader.file
115
+ .then(file => {
116
+ this._loadersMap.set(file, loader);
117
+ })
118
+ // Every then() must have a catch().
119
+ // File loader state (and rejections) are handled in read() and upload().
120
+ // Also, see the "does not swallow the file promise rejection" test.
121
+ .catch(() => { });
122
+ }
123
+ loader.on('change:uploaded', () => {
124
+ let aggregatedUploaded = 0;
125
+ for (const loader of this.loaders) {
126
+ aggregatedUploaded += loader.uploaded;
127
+ }
128
+ this.uploaded = aggregatedUploaded;
129
+ });
130
+ loader.on('change:uploadTotal', () => {
131
+ let aggregatedTotal = 0;
132
+ for (const loader of this.loaders) {
133
+ if (loader.uploadTotal) {
134
+ aggregatedTotal += loader.uploadTotal;
135
+ }
136
+ }
137
+ this.uploadTotal = aggregatedTotal;
138
+ });
139
+ return loader;
140
+ }
141
+ /**
142
+ * Destroys the given loader.
143
+ *
144
+ * @param fileOrPromiseOrLoader File or Promise associated with that loader or loader itself.
145
+ */
146
+ destroyLoader(fileOrPromiseOrLoader) {
147
+ const loader = fileOrPromiseOrLoader instanceof FileLoader ? fileOrPromiseOrLoader : this.getLoader(fileOrPromiseOrLoader);
148
+ loader._destroy();
149
+ this.loaders.remove(loader);
150
+ this._loadersMap.forEach((value, key) => {
151
+ if (value === loader) {
152
+ this._loadersMap.delete(key);
153
+ }
154
+ });
155
+ }
156
+ /**
157
+ * Registers or deregisters pending action bound with upload progress.
158
+ */
159
+ _updatePendingAction() {
160
+ const pendingActions = this.editor.plugins.get(PendingActions);
161
+ if (this.loaders.length) {
162
+ if (!this._pendingAction) {
163
+ const t = this.editor.t;
164
+ const getMessage = (value) => `${t('Upload in progress')} ${parseInt(value)}%.`;
165
+ this._pendingAction = pendingActions.add(getMessage(this.uploadedPercent));
166
+ this._pendingAction.bind('message').to(this, 'uploadedPercent', getMessage);
167
+ }
168
+ }
169
+ else {
170
+ pendingActions.remove(this._pendingAction);
171
+ this._pendingAction = null;
172
+ }
173
+ }
174
+ }
175
+ /**
176
+ * File loader class.
177
+ *
178
+ * It is used to control the process of reading the file and uploading it using the specified upload adapter.
179
+ */
180
+ class FileLoader extends ObservableMixin() {
181
+ /**
182
+ * Creates a new instance of `FileLoader`.
183
+ *
184
+ * @param filePromise A promise which resolves to a file instance.
185
+ * @param uploadAdapterCreator The function which returns {@link module:upload/filerepository~UploadAdapter} instance.
186
+ */
187
+ constructor(filePromise, uploadAdapterCreator) {
188
+ super();
189
+ this.id = uid();
190
+ this._filePromiseWrapper = this._createFilePromiseWrapper(filePromise);
191
+ this._adapter = uploadAdapterCreator(this);
192
+ this._reader = new FileReader();
193
+ this.set('status', 'idle');
194
+ this.set('uploaded', 0);
195
+ this.set('uploadTotal', null);
196
+ this.bind('uploadedPercent').to(this, 'uploaded', this, 'uploadTotal', (uploaded, total) => {
197
+ return total ? (uploaded / total * 100) : 0;
198
+ });
199
+ this.set('uploadResponse', null);
200
+ }
201
+ /**
202
+ * A `Promise` which resolves to a `File` instance associated with this file loader.
203
+ */
204
+ get file() {
205
+ if (!this._filePromiseWrapper) {
206
+ // Loader was destroyed, return promise which resolves to null.
207
+ return Promise.resolve(null);
208
+ }
209
+ else {
210
+ // The `this._filePromiseWrapper.promise` is chained and not simply returned to handle a case when:
211
+ //
212
+ // * The `loader.file.then( ... )` is called by external code (returned promise is pending).
213
+ // * Then `loader._destroy()` is called (call is synchronous) which destroys the `loader`.
214
+ // * Promise returned by the first `loader.file.then( ... )` call is resolved.
215
+ //
216
+ // Returning `this._filePromiseWrapper.promise` will still resolve to a `File` instance so there
217
+ // is an additional check needed in the chain to see if `loader` was destroyed in the meantime.
218
+ return this._filePromiseWrapper.promise.then(file => this._filePromiseWrapper ? file : null);
219
+ }
220
+ }
221
+ /**
222
+ * Returns the file data. To read its data, you need for first load the file
223
+ * by using the {@link module:upload/filerepository~FileLoader#read `read()`} method.
224
+ */
225
+ get data() {
226
+ return this._reader.data;
227
+ }
228
+ /**
229
+ * Reads file using {@link module:upload/filereader~FileReader}.
230
+ *
231
+ * Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `filerepository-read-wrong-status` when status
232
+ * is different than `idle`.
233
+ *
234
+ * Example usage:
235
+ *
236
+ * ```ts
237
+ * fileLoader.read()
238
+ * .then( data => { ... } )
239
+ * .catch( err => {
240
+ * if ( err === 'aborted' ) {
241
+ * console.log( 'Reading aborted.' );
242
+ * } else {
243
+ * console.log( 'Reading error.', err );
244
+ * }
245
+ * } );
246
+ * ```
247
+ *
248
+ * @returns Returns promise that will be resolved with read data. Promise will be rejected if error
249
+ * occurs or if read process is aborted.
250
+ */
251
+ read() {
252
+ if (this.status != 'idle') {
253
+ /**
254
+ * You cannot call read if the status is different than idle.
255
+ *
256
+ * @error filerepository-read-wrong-status
257
+ */
258
+ throw new CKEditorError('filerepository-read-wrong-status', this);
259
+ }
260
+ this.status = 'reading';
261
+ return this.file
262
+ .then(file => this._reader.read(file))
263
+ .then(data => {
264
+ // Edge case: reader was aborted after file was read - double check for proper status.
265
+ // It can happen when image was deleted during its upload.
266
+ if (this.status !== 'reading') {
267
+ throw this.status;
268
+ }
269
+ this.status = 'idle';
270
+ return data;
271
+ })
272
+ .catch(err => {
273
+ if (err === 'aborted') {
274
+ this.status = 'aborted';
275
+ throw 'aborted';
276
+ }
277
+ this.status = 'error';
278
+ throw this._reader.error ? this._reader.error : err;
279
+ });
280
+ }
281
+ /**
282
+ * Reads file using the provided {@link module:upload/filerepository~UploadAdapter}.
283
+ *
284
+ * Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `filerepository-upload-wrong-status` when status
285
+ * is different than `idle`.
286
+ * Example usage:
287
+ *
288
+ * ```ts
289
+ * fileLoader.upload()
290
+ * .then( data => { ... } )
291
+ * .catch( e => {
292
+ * if ( e === 'aborted' ) {
293
+ * console.log( 'Uploading aborted.' );
294
+ * } else {
295
+ * console.log( 'Uploading error.', e );
296
+ * }
297
+ * } );
298
+ * ```
299
+ *
300
+ * @returns Returns promise that will be resolved with response data. Promise will be rejected if error
301
+ * occurs or if read process is aborted.
302
+ */
303
+ upload() {
304
+ if (this.status != 'idle') {
305
+ /**
306
+ * You cannot call upload if the status is different than idle.
307
+ *
308
+ * @error filerepository-upload-wrong-status
309
+ */
310
+ throw new CKEditorError('filerepository-upload-wrong-status', this);
311
+ }
312
+ this.status = 'uploading';
313
+ return this.file
314
+ .then(() => this._adapter.upload())
315
+ .then(data => {
316
+ this.uploadResponse = data;
317
+ this.status = 'idle';
318
+ return data;
319
+ })
320
+ .catch(err => {
321
+ if (this.status === 'aborted') {
322
+ throw 'aborted';
323
+ }
324
+ this.status = 'error';
325
+ throw err;
326
+ });
327
+ }
328
+ /**
329
+ * Aborts loading process.
330
+ */
331
+ abort() {
332
+ const status = this.status;
333
+ this.status = 'aborted';
334
+ if (!this._filePromiseWrapper.isFulfilled) {
335
+ // Edge case: file loader is aborted before read() is called
336
+ // so it might happen that no one handled the rejection of this promise.
337
+ // See https://github.com/ckeditor/ckeditor5-upload/pull/100
338
+ this._filePromiseWrapper.promise.catch(() => { });
339
+ this._filePromiseWrapper.rejecter('aborted');
340
+ }
341
+ else if (status == 'reading') {
342
+ this._reader.abort();
343
+ }
344
+ else if (status == 'uploading' && this._adapter.abort) {
345
+ this._adapter.abort();
346
+ }
347
+ this._destroy();
348
+ }
349
+ /**
350
+ * Performs cleanup.
351
+ *
352
+ * @internal
353
+ */
354
+ _destroy() {
355
+ this._filePromiseWrapper = undefined;
356
+ this._reader = undefined;
357
+ this._adapter = undefined;
358
+ this.uploadResponse = undefined;
359
+ }
360
+ /**
361
+ * Wraps a given file promise into another promise giving additional
362
+ * control (resolving, rejecting, checking if fulfilled) over it.
363
+ *
364
+ * @param filePromise The initial file promise to be wrapped.
365
+ */
366
+ _createFilePromiseWrapper(filePromise) {
367
+ const wrapper = {};
368
+ wrapper.promise = new Promise((resolve, reject) => {
369
+ wrapper.rejecter = reject;
370
+ wrapper.isFulfilled = false;
371
+ filePromise
372
+ .then(file => {
373
+ wrapper.isFulfilled = true;
374
+ resolve(file);
375
+ })
376
+ .catch(err => {
377
+ wrapper.isFulfilled = true;
378
+ reject(err);
379
+ });
380
+ });
381
+ return wrapper;
382
+ }
383
+ }