@ckeditor/ckeditor5-ckbox 40.0.0 → 40.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. package/LICENSE.md +6 -2
  2. package/build/ckbox.js +2 -2
  3. package/build/translations/ar.js +1 -1
  4. package/build/translations/az.js +1 -1
  5. package/build/translations/bg.js +1 -1
  6. package/build/translations/bn.js +1 -1
  7. package/build/translations/ca.js +1 -1
  8. package/build/translations/cs.js +1 -1
  9. package/build/translations/da.js +1 -1
  10. package/build/translations/de.js +1 -1
  11. package/build/translations/el.js +1 -1
  12. package/build/translations/en-au.js +1 -1
  13. package/build/translations/es-co.js +1 -1
  14. package/build/translations/es.js +1 -1
  15. package/build/translations/et.js +1 -1
  16. package/build/translations/fa.js +1 -1
  17. package/build/translations/fi.js +1 -1
  18. package/build/translations/fr.js +1 -1
  19. package/build/translations/gl.js +1 -1
  20. package/build/translations/he.js +1 -1
  21. package/build/translations/hi.js +1 -1
  22. package/build/translations/hr.js +1 -1
  23. package/build/translations/hu.js +1 -1
  24. package/build/translations/id.js +1 -1
  25. package/build/translations/it.js +1 -1
  26. package/build/translations/ja.js +1 -1
  27. package/build/translations/ko.js +1 -1
  28. package/build/translations/lt.js +1 -1
  29. package/build/translations/lv.js +1 -1
  30. package/build/translations/ms.js +1 -1
  31. package/build/translations/nl.js +1 -1
  32. package/build/translations/no.js +1 -1
  33. package/build/translations/pl.js +1 -1
  34. package/build/translations/pt-br.js +1 -1
  35. package/build/translations/pt.js +1 -1
  36. package/build/translations/ro.js +1 -1
  37. package/build/translations/ru.js +1 -1
  38. package/build/translations/sk.js +1 -1
  39. package/build/translations/sq.js +1 -1
  40. package/build/translations/sr-latn.js +1 -1
  41. package/build/translations/sr.js +1 -1
  42. package/build/translations/sv.js +1 -1
  43. package/build/translations/th.js +1 -1
  44. package/build/translations/tr.js +1 -1
  45. package/build/translations/ug.js +1 -1
  46. package/build/translations/uk.js +1 -1
  47. package/build/translations/ur.js +1 -1
  48. package/build/translations/uz.js +1 -1
  49. package/build/translations/vi.js +1 -1
  50. package/build/translations/zh-cn.js +1 -1
  51. package/build/translations/zh.js +1 -1
  52. package/ckeditor5-metadata.json +17 -0
  53. package/lang/contexts.json +6 -2
  54. package/lang/translations/ar.po +18 -2
  55. package/lang/translations/az.po +18 -2
  56. package/lang/translations/bg.po +18 -2
  57. package/lang/translations/bn.po +18 -2
  58. package/lang/translations/ca.po +18 -2
  59. package/lang/translations/cs.po +18 -2
  60. package/lang/translations/da.po +18 -2
  61. package/lang/translations/de.po +18 -2
  62. package/lang/translations/el.po +18 -2
  63. package/lang/translations/en-au.po +18 -2
  64. package/lang/translations/en.po +18 -2
  65. package/lang/translations/es-co.po +18 -2
  66. package/lang/translations/es.po +18 -2
  67. package/lang/translations/et.po +18 -2
  68. package/lang/translations/fa.po +18 -2
  69. package/lang/translations/fi.po +18 -2
  70. package/lang/translations/fr.po +18 -2
  71. package/lang/translations/gl.po +18 -2
  72. package/lang/translations/he.po +18 -2
  73. package/lang/translations/hi.po +18 -2
  74. package/lang/translations/hr.po +18 -2
  75. package/lang/translations/hu.po +18 -2
  76. package/lang/translations/id.po +18 -2
  77. package/lang/translations/it.po +18 -2
  78. package/lang/translations/ja.po +18 -2
  79. package/lang/translations/ko.po +18 -2
  80. package/lang/translations/lt.po +18 -2
  81. package/lang/translations/lv.po +18 -2
  82. package/lang/translations/ms.po +18 -2
  83. package/lang/translations/nl.po +18 -2
  84. package/lang/translations/no.po +18 -2
  85. package/lang/translations/pl.po +18 -2
  86. package/lang/translations/pt-br.po +18 -2
  87. package/lang/translations/pt.po +18 -2
  88. package/lang/translations/ro.po +18 -2
  89. package/lang/translations/ru.po +18 -2
  90. package/lang/translations/sk.po +18 -2
  91. package/lang/translations/sq.po +18 -2
  92. package/lang/translations/sr-latn.po +18 -2
  93. package/lang/translations/sr.po +18 -2
  94. package/lang/translations/sv.po +18 -2
  95. package/lang/translations/th.po +18 -2
  96. package/lang/translations/tr.po +18 -2
  97. package/lang/translations/ug.po +18 -2
  98. package/lang/translations/uk.po +18 -2
  99. package/lang/translations/ur.po +18 -2
  100. package/lang/translations/uz.po +18 -2
  101. package/lang/translations/vi.po +18 -2
  102. package/lang/translations/zh-cn.po +18 -2
  103. package/lang/translations/zh.po +18 -2
  104. package/package.json +4 -2
  105. package/src/augmentation.d.ts +32 -22
  106. package/src/augmentation.js +5 -5
  107. package/src/ckbox.d.ts +33 -33
  108. package/src/ckbox.js +37 -37
  109. package/src/ckboxcommand.d.ts +114 -110
  110. package/src/ckboxcommand.js +332 -302
  111. package/src/ckboxconfig.d.ts +325 -283
  112. package/src/ckboxconfig.js +5 -5
  113. package/src/ckboxediting.d.ts +45 -52
  114. package/src/ckboxediting.js +321 -362
  115. package/src/ckboximageedit/ckboximageeditcommand.d.ts +97 -0
  116. package/src/ckboximageedit/ckboximageeditcommand.js +298 -0
  117. package/src/ckboximageedit/ckboximageeditediting.d.ts +28 -0
  118. package/src/ckboximageedit/ckboximageeditediting.js +36 -0
  119. package/src/ckboximageedit/ckboximageeditui.d.ts +24 -0
  120. package/src/ckboximageedit/ckboximageeditui.js +48 -0
  121. package/src/ckboximageedit/utils.d.ts +10 -0
  122. package/src/ckboximageedit/utils.js +48 -0
  123. package/src/ckboximageedit.d.ts +24 -0
  124. package/src/ckboximageedit.js +28 -0
  125. package/src/ckboxui.d.ts +21 -21
  126. package/src/ckboxui.js +74 -47
  127. package/src/ckboxuploadadapter.d.ts +33 -38
  128. package/src/ckboxuploadadapter.js +130 -275
  129. package/src/ckboxutils.d.ts +50 -0
  130. package/src/ckboxutils.js +183 -0
  131. package/src/index.d.ts +17 -13
  132. package/src/index.js +14 -11
  133. package/src/utils.d.ts +63 -28
  134. package/src/utils.js +175 -49
  135. package/theme/ckboximageedit.css +53 -0
  136. package/theme/icons/ckbox-image-edit.svg +1 -0
  137. package/build/ckbox.js.map +0 -1
@@ -1,302 +1,332 @@
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 { Command } from 'ckeditor5/src/core';
6
- import { createElement, toMap } from 'ckeditor5/src/utils';
7
- import { getImageUrls } from './utils';
8
- // Defines the waiting time (in milliseconds) for inserting the chosen asset into the model. The chosen asset is temporarily stored in the
9
- // `CKBoxCommand#_chosenAssets` and it is removed from there automatically after this time. See `CKBoxCommand#_chosenAssets` for more
10
- // details.
11
- const ASSET_INSERTION_WAIT_TIMEOUT = 1000;
12
- /**
13
- * The CKBox command. It is used by the {@link module:ckbox/ckboxediting~CKBoxEditing CKBox editing feature} to open the CKBox file manager.
14
- * The file manager allows inserting an image or a link to a file into the editor content.
15
- *
16
- * ```ts
17
- * editor.execute( 'ckbox' );
18
- * ```
19
- *
20
- * **Note:** This command uses other features to perform the following tasks:
21
- * - To insert images it uses the {@link module:image/image/insertimagecommand~InsertImageCommand 'insertImage'} command from the
22
- * {@link module:image/image~Image Image feature}.
23
- * - To insert links to other files it uses the {@link module:link/linkcommand~LinkCommand 'link'} command from the
24
- * {@link module:link/link~Link Link feature}.
25
- */
26
- export default class CKBoxCommand extends Command {
27
- /**
28
- * @inheritDoc
29
- */
30
- constructor(editor) {
31
- super(editor);
32
- /**
33
- * A set of all chosen assets. They are stored temporarily and they are automatically removed 1 second after being chosen.
34
- * Chosen assets have to be "remembered" for a while to be able to map the given asset with the element inserted into the model.
35
- * This association map is then used to set the ID on the model element.
36
- *
37
- * All chosen assets are automatically removed after the timeout, because (theoretically) it may happen that they will never be
38
- * inserted into the model, even if the {@link module:link/linkcommand~LinkCommand `'link'`} command or the
39
- * {@link module:image/image/insertimagecommand~InsertImageCommand `'insertImage'`} command is enabled. Such a case may arise when
40
- * another plugin blocks the command execution. Then, in order not to keep the chosen (but not inserted) assets forever, we delete
41
- * them automatically to prevent memory leakage. The 1 second timeout is enough to insert the asset into the model and extract the
42
- * ID from the chosen asset.
43
- *
44
- * The assets are stored only if
45
- * the {@link module:ckbox/ckboxconfig~CKBoxConfig#ignoreDataId `config.ckbox.ignoreDataId`} option is set to `false` (by default).
46
- *
47
- * @internal
48
- */
49
- this._chosenAssets = new Set();
50
- /**
51
- * The DOM element that acts as a mounting point for the CKBox dialog.
52
- */
53
- this._wrapper = null;
54
- this._initListeners();
55
- }
56
- /**
57
- * @inheritDoc
58
- */
59
- refresh() {
60
- this.value = this._getValue();
61
- this.isEnabled = this._checkEnabled();
62
- }
63
- /**
64
- * @inheritDoc
65
- */
66
- execute() {
67
- this.fire('ckbox:open');
68
- }
69
- /**
70
- * Indicates if the CKBox dialog is already opened.
71
- *
72
- * @protected
73
- * @returns {Boolean}
74
- */
75
- _getValue() {
76
- return this._wrapper !== null;
77
- }
78
- /**
79
- * Checks whether the command can be enabled in the current context.
80
- */
81
- _checkEnabled() {
82
- const imageCommand = this.editor.commands.get('insertImage');
83
- const linkCommand = this.editor.commands.get('link');
84
- if (!imageCommand.isEnabled && !linkCommand.isEnabled) {
85
- return false;
86
- }
87
- return true;
88
- }
89
- /**
90
- * Creates the options object for the CKBox dialog.
91
- *
92
- * @returns The object with properties:
93
- * - theme The theme for CKBox dialog.
94
- * - language The language for CKBox dialog.
95
- * - tokenUrl The token endpoint URL.
96
- * - serviceOrigin The base URL of the API service.
97
- * - dialog.onClose The callback function invoked after closing the CKBox dialog.
98
- * - assets.onChoose The callback function invoked after choosing the assets.
99
- */
100
- _prepareOptions() {
101
- const editor = this.editor;
102
- const ckboxConfig = editor.config.get('ckbox');
103
- return {
104
- theme: ckboxConfig.theme,
105
- language: ckboxConfig.language,
106
- tokenUrl: ckboxConfig.tokenUrl,
107
- serviceOrigin: ckboxConfig.serviceOrigin,
108
- dialog: {
109
- onClose: () => this.fire('ckbox:close')
110
- },
111
- assets: {
112
- onChoose: (assets) => this.fire('ckbox:choose', assets)
113
- }
114
- };
115
- }
116
- /**
117
- * Initializes various event listeners for the `ckbox:*` events, because all functionality of the `ckbox` command is event-based.
118
- */
119
- _initListeners() {
120
- const editor = this.editor;
121
- const model = editor.model;
122
- const shouldInsertDataId = !editor.config.get('ckbox.ignoreDataId');
123
- // Refresh the command after firing the `ckbox:*` event.
124
- this.on('ckbox', () => {
125
- this.refresh();
126
- }, { priority: 'low' });
127
- // Handle opening of the CKBox dialog.
128
- this.on('ckbox:open', () => {
129
- if (!this.isEnabled || this.value) {
130
- return;
131
- }
132
- this._wrapper = createElement(document, 'div', { class: 'ck ckbox-wrapper' });
133
- document.body.appendChild(this._wrapper);
134
- window.CKBox.mount(this._wrapper, this._prepareOptions());
135
- });
136
- // Handle closing of the CKBox dialog.
137
- this.on('ckbox:close', () => {
138
- if (!this.value) {
139
- return;
140
- }
141
- this._wrapper.remove();
142
- this._wrapper = null;
143
- });
144
- // Handle choosing the assets.
145
- this.on('ckbox:choose', (evt, assets) => {
146
- if (!this.isEnabled) {
147
- return;
148
- }
149
- const imageCommand = editor.commands.get('insertImage');
150
- const linkCommand = editor.commands.get('link');
151
- const assetsToProcess = prepareAssets({
152
- assets,
153
- isImageAllowed: imageCommand.isEnabled,
154
- isLinkAllowed: linkCommand.isEnabled
155
- });
156
- if (assetsToProcess.length === 0) {
157
- return;
158
- }
159
- // All assets are inserted in one undo step.
160
- model.change(writer => {
161
- for (const asset of assetsToProcess) {
162
- const isLastAsset = asset === assetsToProcess[assetsToProcess.length - 1];
163
- this._insertAsset(asset, isLastAsset, writer);
164
- // If asset ID must be set for the inserted model element, store the asset temporarily and remove it automatically
165
- // after the timeout.
166
- if (shouldInsertDataId) {
167
- setTimeout(() => this._chosenAssets.delete(asset), ASSET_INSERTION_WAIT_TIMEOUT);
168
- this._chosenAssets.add(asset);
169
- }
170
- }
171
- });
172
- });
173
- // Clean up after the editor is destroyed.
174
- this.listenTo(editor, 'destroy', () => {
175
- this.fire('ckbox:close');
176
- this._chosenAssets.clear();
177
- });
178
- }
179
- /**
180
- * Inserts the asset into the model.
181
- *
182
- * @param asset The asset to be inserted.
183
- * @param isLastAsset Indicates if the current asset is the last one from the chosen set.
184
- * @param writer An instance of the model writer.
185
- */
186
- _insertAsset(asset, isLastAsset, writer) {
187
- const editor = this.editor;
188
- const model = editor.model;
189
- const selection = model.document.selection;
190
- // Remove the `linkHref` attribute to not affect the asset to be inserted.
191
- writer.removeSelectionAttribute('linkHref');
192
- if (asset.type === 'image') {
193
- this._insertImage(asset);
194
- }
195
- else {
196
- this._insertLink(asset, writer);
197
- }
198
- // Except for the last chosen asset, move the selection to the end of the current range to avoid overwriting other, already
199
- // inserted assets.
200
- if (!isLastAsset) {
201
- writer.setSelection(selection.getLastPosition());
202
- }
203
- }
204
- /**
205
- * Inserts the image by calling the `insertImage` command.
206
- *
207
- * @param asset The asset to be inserted.
208
- */
209
- _insertImage(asset) {
210
- const editor = this.editor;
211
- const { imageFallbackUrl, imageSources, imageTextAlternative } = asset.attributes;
212
- editor.execute('insertImage', {
213
- source: {
214
- src: imageFallbackUrl,
215
- sources: imageSources,
216
- alt: imageTextAlternative
217
- }
218
- });
219
- }
220
- /**
221
- * Inserts the link to the asset by calling the `link` command.
222
- *
223
- * @param asset The asset to be inserted.
224
- * @param writer An instance of the model writer.
225
- */
226
- _insertLink(asset, writer) {
227
- const editor = this.editor;
228
- const model = editor.model;
229
- const selection = model.document.selection;
230
- const { linkName, linkHref } = asset.attributes;
231
- // If the selection is collapsed, insert the asset name as the link label and select it.
232
- if (selection.isCollapsed) {
233
- const selectionAttributes = toMap(selection.getAttributes());
234
- const textNode = writer.createText(linkName, selectionAttributes);
235
- const range = model.insertContent(textNode);
236
- writer.setSelection(range);
237
- }
238
- editor.execute('link', linkHref);
239
- }
240
- }
241
- /**
242
- * Parses the chosen assets into the internal data format. Filters out chosen assets that are not allowed.
243
- */
244
- function prepareAssets({ assets, isImageAllowed, isLinkAllowed }) {
245
- return assets
246
- .map(asset => isImage(asset) ?
247
- {
248
- id: asset.data.id,
249
- type: 'image',
250
- attributes: prepareImageAssetAttributes(asset)
251
- } :
252
- {
253
- id: asset.data.id,
254
- type: 'link',
255
- attributes: prepareLinkAssetAttributes(asset)
256
- })
257
- .filter(asset => asset.type === 'image' ? isImageAllowed : isLinkAllowed);
258
- }
259
- /**
260
- * Parses the assets attributes into the internal data format.
261
- *
262
- * @param origin The base URL for assets inserted into the editor.
263
- */
264
- function prepareImageAssetAttributes(asset) {
265
- const { imageFallbackUrl, imageSources } = getImageUrls(asset.data.imageUrls);
266
- return {
267
- imageFallbackUrl,
268
- imageSources,
269
- imageTextAlternative: asset.data.metadata.description || ''
270
- };
271
- }
272
- /**
273
- * Parses the assets attributes into the internal data format.
274
- *
275
- * @param origin The base URL for assets inserted into the editor.
276
- */
277
- function prepareLinkAssetAttributes(asset) {
278
- return {
279
- linkName: asset.data.name,
280
- linkHref: getAssetUrl(asset)
281
- };
282
- }
283
- /**
284
- * Checks whether the asset is an image.
285
- */
286
- function isImage(asset) {
287
- const metadata = asset.data.metadata;
288
- if (!metadata) {
289
- return false;
290
- }
291
- return metadata.width && metadata.height;
292
- }
293
- /**
294
- * Creates the URL for the asset.
295
- *
296
- * @param origin The base URL for assets inserted into the editor.
297
- */
298
- function getAssetUrl(asset) {
299
- const url = new URL(asset.data.url);
300
- url.searchParams.set('download', 'true');
301
- return url.toString();
302
- }
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 { Command } from 'ckeditor5/src/core';
6
+ import { createElement, toMap } from 'ckeditor5/src/utils';
7
+ import { blurHashToDataUrl, getImageUrls } from './utils';
8
+ // Defines the waiting time (in milliseconds) for inserting the chosen asset into the model. The chosen asset is temporarily stored in the
9
+ // `CKBoxCommand#_chosenAssets` and it is removed from there automatically after this time. See `CKBoxCommand#_chosenAssets` for more
10
+ // details.
11
+ const ASSET_INSERTION_WAIT_TIMEOUT = 1000;
12
+ /**
13
+ * The CKBox command. It is used by the {@link module:ckbox/ckboxediting~CKBoxEditing CKBox editing feature} to open the CKBox file manager.
14
+ * The file manager allows inserting an image or a link to a file into the editor content.
15
+ *
16
+ * ```ts
17
+ * editor.execute( 'ckbox' );
18
+ * ```
19
+ *
20
+ * **Note:** This command uses other features to perform the following tasks:
21
+ * - To insert images it uses the {@link module:image/image/insertimagecommand~InsertImageCommand 'insertImage'} command from the
22
+ * {@link module:image/image~Image Image feature}.
23
+ * - To insert links to other files it uses the {@link module:link/linkcommand~LinkCommand 'link'} command from the
24
+ * {@link module:link/link~Link Link feature}.
25
+ */
26
+ export default class CKBoxCommand extends Command {
27
+ /**
28
+ * @inheritDoc
29
+ */
30
+ constructor(editor) {
31
+ super(editor);
32
+ /**
33
+ * A set of all chosen assets. They are stored temporarily and they are automatically removed 1 second after being chosen.
34
+ * Chosen assets have to be "remembered" for a while to be able to map the given asset with the element inserted into the model.
35
+ * This association map is then used to set the ID on the model element.
36
+ *
37
+ * All chosen assets are automatically removed after the timeout, because (theoretically) it may happen that they will never be
38
+ * inserted into the model, even if the {@link module:link/linkcommand~LinkCommand `'link'`} command or the
39
+ * {@link module:image/image/insertimagecommand~InsertImageCommand `'insertImage'`} command is enabled. Such a case may arise when
40
+ * another plugin blocks the command execution. Then, in order not to keep the chosen (but not inserted) assets forever, we delete
41
+ * them automatically to prevent memory leakage. The 1 second timeout is enough to insert the asset into the model and extract the
42
+ * ID from the chosen asset.
43
+ *
44
+ * The assets are stored only if
45
+ * the {@link module:ckbox/ckboxconfig~CKBoxConfig#ignoreDataId `config.ckbox.ignoreDataId`} option is set to `false` (by default).
46
+ *
47
+ * @internal
48
+ */
49
+ this._chosenAssets = new Set();
50
+ /**
51
+ * The DOM element that acts as a mounting point for the CKBox dialog.
52
+ */
53
+ this._wrapper = null;
54
+ this._initListeners();
55
+ }
56
+ /**
57
+ * @inheritDoc
58
+ */
59
+ refresh() {
60
+ this.value = this._getValue();
61
+ this.isEnabled = this._checkEnabled();
62
+ }
63
+ /**
64
+ * @inheritDoc
65
+ */
66
+ execute() {
67
+ this.fire('ckbox:open');
68
+ }
69
+ /**
70
+ * Indicates if the CKBox dialog is already opened.
71
+ *
72
+ * @protected
73
+ * @returns {Boolean}
74
+ */
75
+ _getValue() {
76
+ return this._wrapper !== null;
77
+ }
78
+ /**
79
+ * Checks whether the command can be enabled in the current context.
80
+ */
81
+ _checkEnabled() {
82
+ const imageCommand = this.editor.commands.get('insertImage');
83
+ const linkCommand = this.editor.commands.get('link');
84
+ if (!imageCommand.isEnabled && !linkCommand.isEnabled) {
85
+ return false;
86
+ }
87
+ return true;
88
+ }
89
+ /**
90
+ * Creates the options object for the CKBox dialog.
91
+ *
92
+ * @returns The object with properties:
93
+ * - theme The theme for CKBox dialog.
94
+ * - language The language for CKBox dialog.
95
+ * - tokenUrl The token endpoint URL.
96
+ * - serviceOrigin The base URL of the API service.
97
+ * - forceDemoLabel Whether to force "Powered by CKBox" link.
98
+ * - dialog.onClose The callback function invoked after closing the CKBox dialog.
99
+ * - assets.onChoose The callback function invoked after choosing the assets.
100
+ */
101
+ _prepareOptions() {
102
+ const editor = this.editor;
103
+ const ckboxConfig = editor.config.get('ckbox');
104
+ return {
105
+ theme: ckboxConfig.theme,
106
+ language: ckboxConfig.language,
107
+ tokenUrl: ckboxConfig.tokenUrl,
108
+ serviceOrigin: ckboxConfig.serviceOrigin,
109
+ forceDemoLabel: ckboxConfig.forceDemoLabel,
110
+ dialog: {
111
+ onClose: () => this.fire('ckbox:close')
112
+ },
113
+ assets: {
114
+ onChoose: (assets) => this.fire('ckbox:choose', assets)
115
+ }
116
+ };
117
+ }
118
+ /**
119
+ * Initializes various event listeners for the `ckbox:*` events, because all functionality of the `ckbox` command is event-based.
120
+ */
121
+ _initListeners() {
122
+ const editor = this.editor;
123
+ const model = editor.model;
124
+ const shouldInsertDataId = !editor.config.get('ckbox.ignoreDataId');
125
+ // Refresh the command after firing the `ckbox:*` event.
126
+ this.on('ckbox', () => {
127
+ this.refresh();
128
+ }, { priority: 'low' });
129
+ // Handle opening of the CKBox dialog.
130
+ this.on('ckbox:open', () => {
131
+ if (!this.isEnabled || this.value) {
132
+ return;
133
+ }
134
+ this._wrapper = createElement(document, 'div', { class: 'ck ckbox-wrapper' });
135
+ document.body.appendChild(this._wrapper);
136
+ window.CKBox.mount(this._wrapper, this._prepareOptions());
137
+ });
138
+ // Handle closing of the CKBox dialog.
139
+ this.on('ckbox:close', () => {
140
+ if (!this.value) {
141
+ return;
142
+ }
143
+ this._wrapper.remove();
144
+ this._wrapper = null;
145
+ editor.editing.view.focus();
146
+ });
147
+ // Handle choosing the assets.
148
+ this.on('ckbox:choose', (evt, assets) => {
149
+ if (!this.isEnabled) {
150
+ return;
151
+ }
152
+ const imageCommand = editor.commands.get('insertImage');
153
+ const linkCommand = editor.commands.get('link');
154
+ const assetsToProcess = prepareAssets({
155
+ assets,
156
+ isImageAllowed: imageCommand.isEnabled,
157
+ isLinkAllowed: linkCommand.isEnabled
158
+ });
159
+ const assetsCount = assetsToProcess.length;
160
+ if (assetsCount === 0) {
161
+ return;
162
+ }
163
+ // All assets are inserted in one undo step.
164
+ model.change(writer => {
165
+ for (const asset of assetsToProcess) {
166
+ const isLastAsset = asset === assetsToProcess[assetsCount - 1];
167
+ const isSingleAsset = assetsCount === 1;
168
+ this._insertAsset(asset, isLastAsset, writer, isSingleAsset);
169
+ // If asset ID must be set for the inserted model element, store the asset temporarily and remove it automatically
170
+ // after the timeout.
171
+ if (shouldInsertDataId) {
172
+ setTimeout(() => this._chosenAssets.delete(asset), ASSET_INSERTION_WAIT_TIMEOUT);
173
+ this._chosenAssets.add(asset);
174
+ }
175
+ }
176
+ });
177
+ editor.editing.view.focus();
178
+ });
179
+ // Clean up after the editor is destroyed.
180
+ this.listenTo(editor, 'destroy', () => {
181
+ this.fire('ckbox:close');
182
+ this._chosenAssets.clear();
183
+ });
184
+ }
185
+ /**
186
+ * Inserts the asset into the model.
187
+ *
188
+ * @param asset The asset to be inserted.
189
+ * @param isLastAsset Indicates if the current asset is the last one from the chosen set.
190
+ * @param writer An instance of the model writer.
191
+ * @param isSingleAsset It's true when only one asset is processed.
192
+ */
193
+ _insertAsset(asset, isLastAsset, writer, isSingleAsset) {
194
+ const editor = this.editor;
195
+ const model = editor.model;
196
+ const selection = model.document.selection;
197
+ // Remove the `linkHref` attribute to not affect the asset to be inserted.
198
+ writer.removeSelectionAttribute('linkHref');
199
+ if (asset.type === 'image') {
200
+ this._insertImage(asset);
201
+ }
202
+ else {
203
+ this._insertLink(asset, writer, isSingleAsset);
204
+ }
205
+ // Except for the last chosen asset, move the selection to the end of the current range to avoid overwriting other, already
206
+ // inserted assets.
207
+ if (!isLastAsset) {
208
+ writer.setSelection(selection.getLastPosition());
209
+ }
210
+ }
211
+ /**
212
+ * Inserts the image by calling the `insertImage` command.
213
+ *
214
+ * @param asset The asset to be inserted.
215
+ */
216
+ _insertImage(asset) {
217
+ const editor = this.editor;
218
+ const { imageFallbackUrl, imageSources, imageTextAlternative, imageWidth, imageHeight, imagePlaceholder } = asset.attributes;
219
+ editor.execute('insertImage', {
220
+ source: {
221
+ src: imageFallbackUrl,
222
+ sources: imageSources,
223
+ alt: imageTextAlternative,
224
+ width: imageWidth,
225
+ height: imageHeight,
226
+ ...(imagePlaceholder ? { placeholder: imagePlaceholder } : null)
227
+ }
228
+ });
229
+ }
230
+ /**
231
+ * Inserts the link to the asset by calling the `link` command.
232
+ *
233
+ * @param asset The asset to be inserted.
234
+ * @param writer An instance of the model writer.
235
+ * @param isSingleAsset It's true when only one asset is processed.
236
+ */
237
+ _insertLink(asset, writer, isSingleAsset) {
238
+ const editor = this.editor;
239
+ const model = editor.model;
240
+ const selection = model.document.selection;
241
+ const { linkName, linkHref } = asset.attributes;
242
+ // If the selection is collapsed, insert the asset name as the link label and select it.
243
+ if (selection.isCollapsed) {
244
+ const selectionAttributes = toMap(selection.getAttributes());
245
+ const textNode = writer.createText(linkName, selectionAttributes);
246
+ if (!isSingleAsset) {
247
+ const selectionLastPosition = selection.getLastPosition();
248
+ const parentElement = selectionLastPosition.parent;
249
+ // Insert new `paragraph` when selection is not in an empty `paragraph`.
250
+ if (!(parentElement.name === 'paragraph' && parentElement.isEmpty)) {
251
+ editor.execute('insertParagraph', {
252
+ position: selectionLastPosition
253
+ });
254
+ }
255
+ const range = model.insertContent(textNode);
256
+ writer.setSelection(range);
257
+ editor.execute('link', linkHref);
258
+ return;
259
+ }
260
+ const range = model.insertContent(textNode);
261
+ writer.setSelection(range);
262
+ }
263
+ editor.execute('link', linkHref);
264
+ }
265
+ }
266
+ /**
267
+ * Parses the chosen assets into the internal data format. Filters out chosen assets that are not allowed.
268
+ */
269
+ function prepareAssets({ assets, isImageAllowed, isLinkAllowed }) {
270
+ return assets
271
+ .map(asset => isImage(asset) ?
272
+ {
273
+ id: asset.data.id,
274
+ type: 'image',
275
+ attributes: prepareImageAssetAttributes(asset)
276
+ } :
277
+ {
278
+ id: asset.data.id,
279
+ type: 'link',
280
+ attributes: prepareLinkAssetAttributes(asset)
281
+ })
282
+ .filter(asset => asset.type === 'image' ? isImageAllowed : isLinkAllowed);
283
+ }
284
+ /**
285
+ * Parses the assets attributes into the internal data format.
286
+ *
287
+ * @internal
288
+ */
289
+ export function prepareImageAssetAttributes(asset) {
290
+ const { imageFallbackUrl, imageSources } = getImageUrls(asset.data.imageUrls);
291
+ const { description, width, height, blurHash } = asset.data.metadata;
292
+ const imagePlaceholder = blurHashToDataUrl(blurHash);
293
+ return {
294
+ imageFallbackUrl,
295
+ imageSources,
296
+ imageTextAlternative: description || '',
297
+ imageWidth: width,
298
+ imageHeight: height,
299
+ ...(imagePlaceholder ? { imagePlaceholder } : null)
300
+ };
301
+ }
302
+ /**
303
+ * Parses the assets attributes into the internal data format.
304
+ *
305
+ * @param origin The base URL for assets inserted into the editor.
306
+ */
307
+ function prepareLinkAssetAttributes(asset) {
308
+ return {
309
+ linkName: asset.data.name,
310
+ linkHref: getAssetUrl(asset)
311
+ };
312
+ }
313
+ /**
314
+ * Checks whether the asset is an image.
315
+ */
316
+ function isImage(asset) {
317
+ const metadata = asset.data.metadata;
318
+ if (!metadata) {
319
+ return false;
320
+ }
321
+ return metadata.width && metadata.height;
322
+ }
323
+ /**
324
+ * Creates the URL for the asset.
325
+ *
326
+ * @param origin The base URL for assets inserted into the editor.
327
+ */
328
+ function getAssetUrl(asset) {
329
+ const url = new URL(asset.data.url);
330
+ url.searchParams.set('download', 'true');
331
+ return url.toString();
332
+ }