@ckeditor/ckeditor5-ckbox 0.0.0-nightly-20231205.0 → 0.0.0-nightly-20231207.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. package/build/ckbox.js +2 -2
  2. package/build/translations/ar.js +1 -1
  3. package/build/translations/az.js +1 -1
  4. package/build/translations/bg.js +1 -1
  5. package/build/translations/bn.js +1 -1
  6. package/build/translations/ca.js +1 -1
  7. package/build/translations/cs.js +1 -1
  8. package/build/translations/da.js +1 -1
  9. package/build/translations/de.js +1 -1
  10. package/build/translations/el.js +1 -1
  11. package/build/translations/en-au.js +1 -1
  12. package/build/translations/es-co.js +1 -1
  13. package/build/translations/es.js +1 -1
  14. package/build/translations/et.js +1 -1
  15. package/build/translations/fa.js +1 -1
  16. package/build/translations/fi.js +1 -1
  17. package/build/translations/fr.js +1 -1
  18. package/build/translations/gl.js +1 -1
  19. package/build/translations/he.js +1 -1
  20. package/build/translations/hi.js +1 -1
  21. package/build/translations/hr.js +1 -1
  22. package/build/translations/hu.js +1 -1
  23. package/build/translations/id.js +1 -1
  24. package/build/translations/it.js +1 -1
  25. package/build/translations/ja.js +1 -1
  26. package/build/translations/ko.js +1 -1
  27. package/build/translations/lt.js +1 -1
  28. package/build/translations/lv.js +1 -1
  29. package/build/translations/ms.js +1 -1
  30. package/build/translations/nl.js +1 -1
  31. package/build/translations/no.js +1 -1
  32. package/build/translations/pl.js +1 -1
  33. package/build/translations/pt-br.js +1 -1
  34. package/build/translations/pt.js +1 -1
  35. package/build/translations/ro.js +1 -1
  36. package/build/translations/ru.js +1 -1
  37. package/build/translations/sk.js +1 -1
  38. package/build/translations/sq.js +1 -1
  39. package/build/translations/sr-latn.js +1 -1
  40. package/build/translations/sr.js +1 -1
  41. package/build/translations/sv.js +1 -1
  42. package/build/translations/th.js +1 -1
  43. package/build/translations/tr.js +1 -1
  44. package/build/translations/ug.js +1 -1
  45. package/build/translations/uk.js +1 -1
  46. package/build/translations/ur.js +1 -1
  47. package/build/translations/uz.js +1 -1
  48. package/build/translations/vi.js +1 -1
  49. package/build/translations/zh-cn.js +1 -1
  50. package/build/translations/zh.js +1 -1
  51. package/lang/contexts.json +2 -1
  52. package/lang/translations/ar.po +18 -2
  53. package/lang/translations/az.po +18 -2
  54. package/lang/translations/bg.po +18 -2
  55. package/lang/translations/bn.po +18 -2
  56. package/lang/translations/ca.po +18 -2
  57. package/lang/translations/cs.po +18 -2
  58. package/lang/translations/da.po +18 -2
  59. package/lang/translations/de.po +18 -2
  60. package/lang/translations/el.po +18 -2
  61. package/lang/translations/en-au.po +18 -2
  62. package/lang/translations/en.po +18 -2
  63. package/lang/translations/es-co.po +18 -2
  64. package/lang/translations/es.po +18 -2
  65. package/lang/translations/et.po +18 -2
  66. package/lang/translations/fa.po +18 -2
  67. package/lang/translations/fi.po +18 -2
  68. package/lang/translations/fr.po +18 -2
  69. package/lang/translations/gl.po +18 -2
  70. package/lang/translations/he.po +18 -2
  71. package/lang/translations/hi.po +18 -2
  72. package/lang/translations/hr.po +18 -2
  73. package/lang/translations/hu.po +18 -2
  74. package/lang/translations/id.po +18 -2
  75. package/lang/translations/it.po +18 -2
  76. package/lang/translations/ja.po +18 -2
  77. package/lang/translations/ko.po +18 -2
  78. package/lang/translations/lt.po +18 -2
  79. package/lang/translations/lv.po +18 -2
  80. package/lang/translations/ms.po +18 -2
  81. package/lang/translations/nl.po +18 -2
  82. package/lang/translations/no.po +18 -2
  83. package/lang/translations/pl.po +18 -2
  84. package/lang/translations/pt-br.po +18 -2
  85. package/lang/translations/pt.po +18 -2
  86. package/lang/translations/ro.po +18 -2
  87. package/lang/translations/ru.po +18 -2
  88. package/lang/translations/sk.po +18 -2
  89. package/lang/translations/sq.po +18 -2
  90. package/lang/translations/sr-latn.po +18 -2
  91. package/lang/translations/sr.po +18 -2
  92. package/lang/translations/sv.po +18 -2
  93. package/lang/translations/th.po +18 -2
  94. package/lang/translations/tr.po +18 -2
  95. package/lang/translations/ug.po +18 -2
  96. package/lang/translations/uk.po +18 -2
  97. package/lang/translations/ur.po +18 -2
  98. package/lang/translations/uz.po +18 -2
  99. package/lang/translations/vi.po +18 -2
  100. package/lang/translations/zh-cn.po +18 -2
  101. package/lang/translations/zh.po +18 -2
  102. package/package.json +2 -2
  103. package/src/ckboxconfig.d.ts +18 -0
  104. package/src/ckboxediting.d.ts +5 -12
  105. package/src/ckboxediting.js +7 -55
  106. package/src/ckboximageedit/ckboximageeditcommand.d.ts +10 -1
  107. package/src/ckboximageedit/ckboximageeditcommand.js +55 -23
  108. package/src/ckboximageedit/ckboximageeditediting.d.ts +3 -1
  109. package/src/ckboximageedit/ckboximageeditediting.js +3 -1
  110. package/src/ckboximageedit/utils.d.ts +10 -0
  111. package/src/ckboximageedit/utils.js +48 -0
  112. package/src/ckboximageedit.d.ts +1 -2
  113. package/src/ckboximageedit.js +1 -2
  114. package/src/ckboxui.js +28 -1
  115. package/src/ckboxuploadadapter.d.ts +0 -5
  116. package/src/ckboxuploadadapter.js +10 -111
  117. package/src/ckboxutils.d.ts +51 -0
  118. package/src/ckboxutils.js +184 -0
  119. package/src/utils.d.ts +8 -0
  120. package/src/utils.js +36 -0
  121. package/theme/icons/ckbox-image-edit.svg +1 -1
@@ -7,12 +7,13 @@
7
7
  * @module ckbox/ckboximageedit/ckboximageeditcommand
8
8
  */
9
9
  import { Command, PendingActions } from 'ckeditor5/src/core';
10
- import { CKEditorError, createElement, retry } from 'ckeditor5/src/utils';
10
+ import { CKEditorError, abortableDebounce, createElement, retry } from 'ckeditor5/src/utils';
11
11
  import { Notification } from 'ckeditor5/src/ui';
12
12
  import { isEqual } from 'lodash-es';
13
- import CKBoxEditing from '../ckboxediting';
14
13
  import { sendHttpRequest } from '../utils';
15
14
  import { prepareImageAssetAttributes } from '../ckboxcommand';
15
+ import { createEditabilityChecker } from './utils';
16
+ import CKBoxUtils from '../ckboxutils';
16
17
  /**
17
18
  * The CKBox edit image command.
18
19
  *
@@ -31,8 +32,10 @@ export default class CKBoxImageEditCommand extends Command {
31
32
  /**
32
33
  * The states of image processing in progress.
33
34
  */
34
- this._processInProgress = new Map();
35
+ this._processInProgress = new Set();
35
36
  this.value = false;
37
+ this._canEdit = createEditabilityChecker(editor.config.get('ckbox.allowExternalImagesEditing'));
38
+ this._prepareOptions = abortableDebounce((signal, state) => this._prepareOptionsAbortable(signal, state));
36
39
  this._prepareListeners();
37
40
  }
38
41
  /**
@@ -42,16 +45,10 @@ export default class CKBoxImageEditCommand extends Command {
42
45
  const editor = this.editor;
43
46
  this.value = this._getValue();
44
47
  const selectedElement = editor.model.document.selection.getSelectedElement();
45
- const isImageElement = selectedElement && (selectedElement.is('element', 'imageInline') ||
46
- selectedElement.is('element', 'imageBlock'));
47
- const isBeingProcessed = Array.from(this._processInProgress.values())
48
- .some(({ element }) => isEqual(element, selectedElement));
49
- if (isImageElement && selectedElement.hasAttribute('ckboxImageId') && !isBeingProcessed) {
50
- this.isEnabled = true;
51
- }
52
- else {
53
- this.isEnabled = false;
54
- }
48
+ this.isEnabled =
49
+ !!selectedElement &&
50
+ this._canEdit(selectedElement) &&
51
+ !this._checkIfElementIsBeingProcessed(selectedElement);
55
52
  }
56
53
  /**
57
54
  * Opens the CKBox Image Editor dialog for editing the image.
@@ -60,23 +57,32 @@ export default class CKBoxImageEditCommand extends Command {
60
57
  if (this._getValue()) {
61
58
  return;
62
59
  }
60
+ const wrapper = createElement(document, 'div', { class: 'ck ckbox-wrapper' });
61
+ this._wrapper = wrapper;
63
62
  this.value = true;
64
- this._wrapper = createElement(document, 'div', { class: 'ck ckbox-wrapper' });
65
63
  document.body.appendChild(this._wrapper);
66
64
  const imageElement = this.editor.model.document.selection.getSelectedElement();
67
- const ckboxImageId = imageElement.getAttribute('ckboxImageId');
68
65
  const processingState = {
69
- ckboxImageId,
70
66
  element: imageElement,
71
67
  controller: new AbortController()
72
68
  };
73
- window.CKBox.mountImageEditor(this._wrapper, this._prepareOptions(processingState));
69
+ this._prepareOptions(processingState).then(options => window.CKBox.mountImageEditor(wrapper, options), error => {
70
+ const editor = this.editor;
71
+ const t = editor.t;
72
+ const notification = editor.plugins.get(Notification);
73
+ notification.showWarning(t('Failed to determine category of edited image.'), {
74
+ namespace: 'ckbox'
75
+ });
76
+ console.error(error);
77
+ this._handleImageEditorClose();
78
+ });
74
79
  }
75
80
  /**
76
81
  * @inheritDoc
77
82
  */
78
83
  destroy() {
79
84
  this._handleImageEditorClose();
85
+ this._prepareOptions.abort();
80
86
  for (const state of this._processInProgress.values()) {
81
87
  state.controller.abort();
82
88
  }
@@ -91,11 +97,28 @@ export default class CKBoxImageEditCommand extends Command {
91
97
  /**
92
98
  * Creates the options object for the CKBox Image Editor dialog.
93
99
  */
94
- _prepareOptions(state) {
100
+ async _prepareOptionsAbortable(signal, state) {
95
101
  const editor = this.editor;
96
102
  const ckboxConfig = editor.config.get('ckbox');
103
+ const ckboxUtils = editor.plugins.get(CKBoxUtils);
104
+ const { element } = state;
105
+ let imageMountOptions;
106
+ const ckboxImageId = element.getAttribute('ckboxImageId');
107
+ if (ckboxImageId) {
108
+ imageMountOptions = {
109
+ assetId: ckboxImageId
110
+ };
111
+ }
112
+ else {
113
+ const imageUrl = element.getAttribute('src');
114
+ const uploadCategoryId = await ckboxUtils.getCategoryIdForFile(imageUrl, { signal });
115
+ imageMountOptions = {
116
+ imageUrl,
117
+ uploadCategoryId
118
+ };
119
+ }
97
120
  return {
98
- assetId: state.ckboxImageId,
121
+ ...imageMountOptions,
99
122
  imageEditing: {
100
123
  allowOverwrite: false
101
124
  },
@@ -128,6 +151,14 @@ export default class CKBoxImageEditCommand extends Command {
128
151
  }
129
152
  return states;
130
153
  }
154
+ _checkIfElementIsBeingProcessed(selectedElement) {
155
+ for (const { element } of this._processInProgress) {
156
+ if (isEqual(element, selectedElement)) {
157
+ return true;
158
+ }
159
+ }
160
+ return false;
161
+ }
131
162
  /**
132
163
  * Closes the CKBox Image Editor dialog.
133
164
  */
@@ -138,6 +169,7 @@ export default class CKBoxImageEditCommand extends Command {
138
169
  this._wrapper.remove();
139
170
  this._wrapper = null;
140
171
  this.editor.editing.view.focus();
172
+ this.refresh();
141
173
  }
142
174
  /**
143
175
  * Save edited image. In case server respond with "success" replace with edited image,
@@ -148,7 +180,7 @@ export default class CKBoxImageEditCommand extends Command {
148
180
  const notification = this.editor.plugins.get(Notification);
149
181
  const pendingActions = this.editor.plugins.get(PendingActions);
150
182
  const action = pendingActions.add(t('Processing the edited image.'));
151
- this._processInProgress.set(state.ckboxImageId, state);
183
+ this._processInProgress.add(state);
152
184
  this._showImageProcessingIndicator(state.element, asset);
153
185
  this.refresh();
154
186
  this._waitForAssetProcessed(asset.data.id, state.controller.signal)
@@ -169,7 +201,7 @@ export default class CKBoxImageEditCommand extends Command {
169
201
  console.error(error);
170
202
  }
171
203
  }).finally(() => {
172
- this._processInProgress.delete(state.ckboxImageId);
204
+ this._processInProgress.delete(state);
173
205
  pendingActions.remove(action);
174
206
  this.refresh();
175
207
  });
@@ -179,12 +211,12 @@ export default class CKBoxImageEditCommand extends Command {
179
211
  * image is already proceeded and ready for saving.
180
212
  */
181
213
  async _getAssetStatusFromServer(id, signal) {
182
- const ckboxEditing = this.editor.plugins.get(CKBoxEditing);
214
+ const ckboxUtils = this.editor.plugins.get(CKBoxUtils);
183
215
  const url = new URL('assets/' + id, this.editor.config.get('ckbox.serviceOrigin'));
184
216
  const response = await sendHttpRequest({
185
217
  url,
186
218
  signal,
187
- authorization: ckboxEditing.getToken().value
219
+ authorization: ckboxUtils.getToken().value
188
220
  });
189
221
  const status = response.metadata.metadataProcessingStatus;
190
222
  if (!status || status == 'queued') {
@@ -7,6 +7,8 @@
7
7
  */
8
8
  import { PendingActions, Plugin } from 'ckeditor5/src/core';
9
9
  import { Notification } from 'ckeditor5/src/ui';
10
+ import CKBoxEditing from '../ckboxediting';
11
+ import CKBoxUtils from '../ckboxutils';
10
12
  /**
11
13
  * The CKBox image edit editing plugin.
12
14
  */
@@ -18,7 +20,7 @@ export default class CKBoxImageEditEditing extends Plugin {
18
20
  /**
19
21
  * @inheritDoc
20
22
  */
21
- static get requires(): readonly [typeof PendingActions, typeof Notification, "ImageUtils", "ImageEditing"];
23
+ static get requires(): readonly [typeof CKBoxEditing, typeof CKBoxUtils, typeof PendingActions, typeof Notification, "ImageUtils", "ImageEditing"];
22
24
  /**
23
25
  * @inheritDoc
24
26
  */
@@ -8,6 +8,8 @@
8
8
  import { PendingActions, Plugin } from 'ckeditor5/src/core';
9
9
  import { Notification } from 'ckeditor5/src/ui';
10
10
  import CKBoxImageEditCommand from './ckboximageeditcommand';
11
+ import CKBoxEditing from '../ckboxediting';
12
+ import CKBoxUtils from '../ckboxutils';
11
13
  /**
12
14
  * The CKBox image edit editing plugin.
13
15
  */
@@ -22,7 +24,7 @@ export default class CKBoxImageEditEditing extends Plugin {
22
24
  * @inheritDoc
23
25
  */
24
26
  static get requires() {
25
- return [PendingActions, Notification, 'ImageUtils', 'ImageEditing'];
27
+ return [CKBoxEditing, CKBoxUtils, PendingActions, Notification, 'ImageUtils', 'ImageEditing'];
26
28
  }
27
29
  /**
28
30
  * @inheritDoc
@@ -0,0 +1,10 @@
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
+ import type { Element } from 'ckeditor5/src/engine';
6
+ import type { CKBoxConfig } from '../ckboxconfig';
7
+ /**
8
+ * @internal
9
+ */
10
+ export declare function createEditabilityChecker(allowExternalImagesEditing: CKBoxConfig['allowExternalImagesEditing']): (element: Element) => boolean;
@@ -0,0 +1,48 @@
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/ckboximageedit/utils
7
+ */
8
+ import { global } from 'ckeditor5/src/utils';
9
+ /**
10
+ * @internal
11
+ */
12
+ export function createEditabilityChecker(allowExternalImagesEditing) {
13
+ const checkUrl = createUrlChecker(allowExternalImagesEditing);
14
+ return element => {
15
+ const isImageElement = element.is('element', 'imageInline') ||
16
+ element.is('element', 'imageBlock');
17
+ if (!isImageElement) {
18
+ return false;
19
+ }
20
+ if (element.hasAttribute('ckboxImageId')) {
21
+ return true;
22
+ }
23
+ if (element.hasAttribute('src')) {
24
+ return checkUrl(element.getAttribute('src'));
25
+ }
26
+ return false;
27
+ };
28
+ }
29
+ function createUrlChecker(allowExternalImagesEditing) {
30
+ if (Array.isArray(allowExternalImagesEditing)) {
31
+ const urlMatchers = allowExternalImagesEditing.map(createUrlChecker);
32
+ return src => urlMatchers.some(matcher => matcher(src));
33
+ }
34
+ if (allowExternalImagesEditing == 'origin') {
35
+ const origin = global.window.location.origin;
36
+ return src => src.startsWith(origin + '/');
37
+ }
38
+ if (typeof allowExternalImagesEditing == 'function') {
39
+ return allowExternalImagesEditing;
40
+ }
41
+ if (allowExternalImagesEditing instanceof RegExp) {
42
+ return src => !!(src.match(allowExternalImagesEditing) ||
43
+ src.replace(/^https?:\/\//, '').match(allowExternalImagesEditing));
44
+ }
45
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
46
+ const shouldBeUndefned = allowExternalImagesEditing;
47
+ return () => false;
48
+ }
@@ -6,7 +6,6 @@
6
6
  * @module ckbox/ckboximageedit
7
7
  */
8
8
  import { Plugin } from 'ckeditor5/src/core';
9
- import CKBoxEditing from './ckboxediting';
10
9
  import CKBoxImageEditEditing from './ckboximageedit/ckboximageeditediting';
11
10
  import CKBoxImageEditUI from './ckboximageedit/ckboximageeditui';
12
11
  import '../theme/ckboximageedit.css';
@@ -21,5 +20,5 @@ export default class CKBoxImageEdit extends Plugin {
21
20
  /**
22
21
  * @inheritDoc
23
22
  */
24
- static get requires(): readonly [typeof CKBoxEditing, typeof CKBoxImageEditEditing, typeof CKBoxImageEditUI];
23
+ static get requires(): readonly [typeof CKBoxImageEditEditing, typeof CKBoxImageEditUI];
25
24
  }
@@ -6,7 +6,6 @@
6
6
  * @module ckbox/ckboximageedit
7
7
  */
8
8
  import { Plugin } from 'ckeditor5/src/core';
9
- import CKBoxEditing from './ckboxediting';
10
9
  import CKBoxImageEditEditing from './ckboximageedit/ckboximageeditediting';
11
10
  import CKBoxImageEditUI from './ckboximageedit/ckboximageeditui';
12
11
  import '../theme/ckboximageedit.css';
@@ -24,6 +23,6 @@ export default class CKBoxImageEdit extends Plugin {
24
23
  * @inheritDoc
25
24
  */
26
25
  static get requires() {
27
- return [CKBoxEditing, CKBoxImageEditEditing, CKBoxImageEditUI];
26
+ return [CKBoxImageEditEditing, CKBoxImageEditUI];
28
27
  }
29
28
  }
package/src/ckboxui.js CHANGED
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * @module ckbox/ckboxui
7
7
  */
8
- import { Plugin } from 'ckeditor5/src/core';
8
+ import { icons, Plugin } from 'ckeditor5/src/core';
9
9
  import { ButtonView } from 'ckeditor5/src/ui';
10
10
  import browseFilesIcon from '../theme/icons/browse-files.svg';
11
11
  /**
@@ -43,5 +43,32 @@ export default class CKBoxUI extends Plugin {
43
43
  });
44
44
  return button;
45
45
  });
46
+ if (editor.plugins.has('ImageInsertUI')) {
47
+ const imageInsertUI = editor.plugins.get('ImageInsertUI');
48
+ imageInsertUI.registerIntegration({
49
+ name: 'assetManager',
50
+ observable: command,
51
+ buttonViewCreator: () => {
52
+ const button = this.editor.ui.componentFactory.create('ckbox');
53
+ button.icon = icons.imageAssetManager;
54
+ button.bind('label').to(imageInsertUI, 'isImageSelected', isImageSelected => isImageSelected ?
55
+ t('Replace image with file manager') :
56
+ t('Insert image with file manager'));
57
+ return button;
58
+ },
59
+ formViewCreator: () => {
60
+ const button = this.editor.ui.componentFactory.create('ckbox');
61
+ button.icon = icons.imageAssetManager;
62
+ button.withText = true;
63
+ button.bind('label').to(imageInsertUI, 'isImageSelected', isImageSelected => isImageSelected ?
64
+ t('Replace with file manager') :
65
+ t('Insert with file manager'));
66
+ button.on('execute', () => {
67
+ imageInsertUI.dropdownView.isOpen = false;
68
+ });
69
+ return button;
70
+ }
71
+ });
72
+ }
46
73
  }
47
74
  }
@@ -31,8 +31,3 @@ export default class CKBoxUploadAdapter extends Plugin {
31
31
  */
32
32
  afterInit(): Promise<void>;
33
33
  }
34
- export interface AvailableCategory {
35
- id: string;
36
- name: string;
37
- extensions: Array<string>;
38
- }
@@ -8,9 +8,9 @@
8
8
  */
9
9
  import { Plugin } from 'ckeditor5/src/core';
10
10
  import { FileRepository } from 'ckeditor5/src/upload';
11
- import { logError } from 'ckeditor5/src/utils';
12
11
  import CKBoxEditing from './ckboxediting';
13
- import { getImageUrls, getWorkspaceId, sendHttpRequest } from './utils';
12
+ import { getImageUrls, sendHttpRequest } from './utils';
13
+ import CKBoxUtils from './ckboxutils';
14
14
  /**
15
15
  * A plugin that enables file uploads in CKEditor 5 using the CKBox server–side connector.
16
16
  * See the {@glink features/file-management/ckbox CKBox file manager integration} guide to learn how to configure
@@ -47,10 +47,8 @@ export default class CKBoxUploadAdapter extends Plugin {
47
47
  return;
48
48
  }
49
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
- };
50
+ const ckboxUtils = editor.plugins.get(CKBoxUtils);
51
+ fileRepository.createUploadAdapter = loader => new Adapter(loader, editor, ckboxUtils);
54
52
  const shouldInsertDataId = !editor.config.get('ckbox.ignoreDataId');
55
53
  const imageUploadEditing = editor.plugins.get('ImageUploadEditing');
56
54
  // Mark uploaded assets with the `ckboxImageId` attribute. Its value represents an ID in CKBox.
@@ -70,118 +68,27 @@ class Adapter {
70
68
  /**
71
69
  * Creates a new adapter instance.
72
70
  */
73
- constructor(loader, token, editor) {
71
+ constructor(loader, editor, ckboxUtils) {
74
72
  this.loader = loader;
75
- this.token = token;
73
+ this.token = ckboxUtils.getToken();
74
+ this.ckboxUtils = ckboxUtils;
76
75
  this.editor = editor;
77
76
  this.controller = new AbortController();
78
77
  this.serviceOrigin = editor.config.get('ckbox.serviceOrigin');
79
78
  }
80
- /**
81
- * The ID of workspace to use.
82
- */
83
- getWorkspaceId() {
84
- const t = this.editor.t;
85
- const cannotAccessDefaultWorkspaceError = t('Cannot access default workspace.');
86
- const defaultWorkspaceId = this.editor.config.get('ckbox.defaultUploadWorkspaceId');
87
- const workspaceId = getWorkspaceId(this.token, defaultWorkspaceId);
88
- if (workspaceId == null) {
89
- /**
90
- * The user is not authorized to access the workspace defined in the`ckbox.defaultUploadWorkspaceId` configuration.
91
- *
92
- * @error ckbox-access-default-workspace-error
93
- */
94
- logError('ckbox-access-default-workspace-error');
95
- throw cannotAccessDefaultWorkspaceError;
96
- }
97
- return workspaceId;
98
- }
99
- /**
100
- * Resolves a promise with an array containing available categories with which the uploaded file can be associated.
101
- *
102
- * If the API returns limited results, the method will collect all items.
103
- */
104
- async getAvailableCategories(offset = 0) {
105
- const ITEMS_PER_REQUEST = 50;
106
- const categoryUrl = new URL('categories', this.serviceOrigin);
107
- categoryUrl.searchParams.set('limit', ITEMS_PER_REQUEST.toString());
108
- categoryUrl.searchParams.set('offset', offset.toString());
109
- categoryUrl.searchParams.set('workspaceId', this.getWorkspaceId());
110
- return sendHttpRequest({
111
- url: categoryUrl,
112
- signal: this.controller.signal,
113
- authorization: this.token.value
114
- })
115
- .then(async (data) => {
116
- const remainingItems = data.totalCount - (offset + ITEMS_PER_REQUEST);
117
- if (remainingItems > 0) {
118
- const offsetItems = await this.getAvailableCategories(offset + ITEMS_PER_REQUEST);
119
- return [
120
- ...data.items,
121
- ...offsetItems
122
- ];
123
- }
124
- return data.items;
125
- })
126
- .catch(() => {
127
- this.controller.signal.throwIfAborted();
128
- /**
129
- * Fetching a list of available categories with which an uploaded file can be associated failed.
130
- *
131
- * @error ckbox-fetch-category-http-error
132
- */
133
- logError('ckbox-fetch-category-http-error');
134
- });
135
- }
136
- /**
137
- * Resolves a promise with an object containing a category with which the uploaded file is associated or an error code.
138
- */
139
- async getCategoryIdForFile(file) {
140
- const extension = getFileExtension(file.name);
141
- const allCategories = await this.getAvailableCategories();
142
- // Couldn't fetch all categories. Perhaps the authorization token is invalid.
143
- if (!allCategories) {
144
- return null;
145
- }
146
- // The plugin allows defining to which category the uploaded file should be assigned.
147
- const defaultCategories = this.editor.config.get('ckbox.defaultUploadCategories');
148
- // If a user specifies the plugin configuration, find the first category that accepts the uploaded file.
149
- if (defaultCategories) {
150
- const userCategory = Object.keys(defaultCategories).find(category => {
151
- return defaultCategories[category].find(e => e.toLowerCase() == extension);
152
- });
153
- // If found, return its ID if the category exists on the server side.
154
- if (userCategory) {
155
- const serverCategory = allCategories.find(category => category.id === userCategory || category.name === userCategory);
156
- if (!serverCategory) {
157
- return null;
158
- }
159
- return serverCategory.id;
160
- }
161
- }
162
- // Otherwise, find the first category that accepts the uploaded file and returns its ID.
163
- const category = allCategories.find(category => category.extensions.find(e => e.toLowerCase() == extension));
164
- if (!category) {
165
- return null;
166
- }
167
- return category.id;
168
- }
169
79
  /**
170
80
  * Starts the upload process.
171
81
  *
172
82
  * @see module:upload/filerepository~UploadAdapter#upload
173
83
  */
174
84
  async upload() {
85
+ const ckboxUtils = this.ckboxUtils;
175
86
  const t = this.editor.t;
176
- const cannotFindCategoryError = t('Cannot determine a category for the uploaded file.');
177
87
  const file = (await this.loader.file);
178
- const category = await this.getCategoryIdForFile(file);
179
- if (!category) {
180
- return Promise.reject(cannotFindCategoryError);
181
- }
88
+ const category = await ckboxUtils.getCategoryIdForFile(file, { signal: this.controller.signal });
182
89
  const uploadUrl = new URL('assets', this.serviceOrigin);
183
90
  const formData = new FormData();
184
- uploadUrl.searchParams.set('workspaceId', this.getWorkspaceId());
91
+ uploadUrl.searchParams.set('workspaceId', ckboxUtils.getWorkspaceId());
185
92
  formData.append('categoryId', category);
186
93
  formData.append('file', file);
187
94
  const requestConfig = {
@@ -221,11 +128,3 @@ class Adapter {
221
128
  this.controller.abort();
222
129
  }
223
130
  }
224
- /**
225
- * Returns an extension from the given value.
226
- */
227
- function getFileExtension(value) {
228
- const extensionRegExp = /\.(?<ext>[^.]+)$/;
229
- const match = value.match(extensionRegExp);
230
- return match.groups.ext.toLowerCase();
231
- }
@@ -0,0 +1,51 @@
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/ckboxutils
7
+ */
8
+ import type { InitializedToken } from '@ckeditor/ckeditor5-cloud-services';
9
+ import { Plugin } from 'ckeditor5/src/core';
10
+ /**
11
+ * The CKBox editing feature. It introduces the {@link module:ckbox/ckboxcommand~CKBoxCommand CKBox command} and
12
+ * {@link module:ckbox/ckboxuploadadapter~CKBoxUploadAdapter CKBox upload adapter}.
13
+ */
14
+ export default class CKBoxUtils extends Plugin {
15
+ /**
16
+ * CKEditor Cloud Services access token.
17
+ */
18
+ private _token;
19
+ /**
20
+ * @inheritDoc
21
+ */
22
+ static get pluginName(): "CKBoxUtils";
23
+ /**
24
+ * @inheritDoc
25
+ */
26
+ static get requires(): readonly ["CloudServices"];
27
+ /**
28
+ * @inheritDoc
29
+ */
30
+ init(): Promise<void>;
31
+ /**
32
+ * Returns a token used by the CKBox plugin for communication with the CKBox service.
33
+ */
34
+ getToken(): InitializedToken;
35
+ /**
36
+ * The ID of workspace to use when uploading an image.
37
+ */
38
+ getWorkspaceId(): string;
39
+ /**
40
+ * Resolves a promise with an object containing a category with which the uploaded file is associated or an error code.
41
+ */
42
+ getCategoryIdForFile(fileOrUrl: File | string, options: {
43
+ signal: AbortSignal;
44
+ }): Promise<string>;
45
+ /**
46
+ * Resolves a promise with an array containing available categories with which the uploaded file can be associated.
47
+ *
48
+ * If the API returns limited results, the method will collect all items.
49
+ */
50
+ private _getAvailableCategories;
51
+ }