@ckeditor/ckeditor5-ckbox 41.3.0-alpha.1 → 41.3.0-alpha.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. package/dist/translations/ar.d.ts +8 -0
  2. package/dist/translations/ar.js +5 -0
  3. package/dist/translations/az.d.ts +8 -0
  4. package/dist/translations/az.js +5 -0
  5. package/dist/translations/bg.d.ts +8 -0
  6. package/dist/translations/bg.js +5 -0
  7. package/dist/translations/bn.d.ts +8 -0
  8. package/dist/translations/bn.js +5 -0
  9. package/dist/translations/ca.d.ts +8 -0
  10. package/dist/translations/ca.js +5 -0
  11. package/dist/translations/cs.d.ts +8 -0
  12. package/dist/translations/cs.js +5 -0
  13. package/dist/translations/da.d.ts +8 -0
  14. package/dist/translations/da.js +5 -0
  15. package/dist/translations/de.d.ts +8 -0
  16. package/dist/translations/de.js +5 -0
  17. package/dist/translations/el.d.ts +8 -0
  18. package/dist/translations/el.js +5 -0
  19. package/dist/translations/en-au.d.ts +8 -0
  20. package/dist/translations/en-au.js +5 -0
  21. package/dist/translations/en.d.ts +8 -0
  22. package/dist/translations/en.js +5 -0
  23. package/dist/translations/es-co.d.ts +8 -0
  24. package/dist/translations/es-co.js +5 -0
  25. package/dist/translations/es.d.ts +8 -0
  26. package/dist/translations/es.js +5 -0
  27. package/dist/translations/et.d.ts +8 -0
  28. package/dist/translations/et.js +5 -0
  29. package/dist/translations/fa.d.ts +8 -0
  30. package/dist/translations/fa.js +5 -0
  31. package/dist/translations/fi.d.ts +8 -0
  32. package/dist/translations/fi.js +5 -0
  33. package/dist/translations/fr.d.ts +8 -0
  34. package/dist/translations/fr.js +5 -0
  35. package/dist/translations/gl.d.ts +8 -0
  36. package/dist/translations/gl.js +5 -0
  37. package/dist/translations/he.d.ts +8 -0
  38. package/dist/translations/he.js +5 -0
  39. package/dist/translations/hi.d.ts +8 -0
  40. package/dist/translations/hi.js +5 -0
  41. package/dist/translations/hr.d.ts +8 -0
  42. package/dist/translations/hr.js +5 -0
  43. package/dist/translations/hu.d.ts +8 -0
  44. package/dist/translations/hu.js +5 -0
  45. package/dist/translations/id.d.ts +8 -0
  46. package/dist/translations/id.js +5 -0
  47. package/dist/translations/it.d.ts +8 -0
  48. package/dist/translations/it.js +5 -0
  49. package/dist/translations/ja.d.ts +8 -0
  50. package/dist/translations/ja.js +5 -0
  51. package/dist/translations/ko.d.ts +8 -0
  52. package/dist/translations/ko.js +5 -0
  53. package/dist/translations/lt.d.ts +8 -0
  54. package/dist/translations/lt.js +5 -0
  55. package/dist/translations/lv.d.ts +8 -0
  56. package/dist/translations/lv.js +5 -0
  57. package/dist/translations/ms.d.ts +8 -0
  58. package/dist/translations/ms.js +5 -0
  59. package/dist/translations/nl.d.ts +8 -0
  60. package/dist/translations/nl.js +5 -0
  61. package/dist/translations/no.d.ts +8 -0
  62. package/dist/translations/no.js +5 -0
  63. package/dist/translations/pl.d.ts +8 -0
  64. package/dist/translations/pl.js +5 -0
  65. package/dist/translations/pt-br.d.ts +8 -0
  66. package/dist/translations/pt-br.js +5 -0
  67. package/dist/translations/pt.d.ts +8 -0
  68. package/dist/translations/pt.js +5 -0
  69. package/dist/translations/ro.d.ts +8 -0
  70. package/dist/translations/ro.js +5 -0
  71. package/dist/translations/ru.d.ts +8 -0
  72. package/dist/translations/ru.js +5 -0
  73. package/dist/translations/sk.d.ts +8 -0
  74. package/dist/translations/sk.js +5 -0
  75. package/dist/translations/sq.d.ts +8 -0
  76. package/dist/translations/sq.js +5 -0
  77. package/dist/translations/sr-latn.d.ts +8 -0
  78. package/dist/translations/sr-latn.js +5 -0
  79. package/dist/translations/sr.d.ts +8 -0
  80. package/dist/translations/sr.js +5 -0
  81. package/dist/translations/sv.d.ts +8 -0
  82. package/dist/translations/sv.js +5 -0
  83. package/dist/translations/th.d.ts +8 -0
  84. package/dist/translations/th.js +5 -0
  85. package/dist/translations/tr.d.ts +8 -0
  86. package/dist/translations/tr.js +5 -0
  87. package/dist/translations/ug.d.ts +8 -0
  88. package/dist/translations/ug.js +5 -0
  89. package/dist/translations/uk.d.ts +8 -0
  90. package/dist/translations/uk.js +5 -0
  91. package/dist/translations/ur.d.ts +8 -0
  92. package/dist/translations/ur.js +5 -0
  93. package/dist/translations/uz.d.ts +8 -0
  94. package/dist/translations/uz.js +5 -0
  95. package/dist/translations/vi.d.ts +8 -0
  96. package/dist/translations/vi.js +5 -0
  97. package/dist/translations/zh-cn.d.ts +8 -0
  98. package/dist/translations/zh-cn.js +5 -0
  99. package/dist/translations/zh.d.ts +8 -0
  100. package/dist/translations/zh.js +5 -0
  101. package/dist/types/augmentation.d.ts +4 -0
  102. package/dist/types/ckbox.d.ts +4 -0
  103. package/dist/types/ckboxcommand.d.ts +4 -0
  104. package/dist/types/ckboxconfig.d.ts +4 -0
  105. package/dist/types/ckboxediting.d.ts +4 -0
  106. package/dist/types/ckboximageedit/ckboximageeditcommand.d.ts +4 -0
  107. package/dist/types/ckboximageedit/ckboximageeditediting.d.ts +4 -0
  108. package/dist/types/ckboximageedit/ckboximageeditui.d.ts +4 -0
  109. package/dist/types/ckboximageedit/utils.d.ts +4 -0
  110. package/dist/types/ckboximageedit.d.ts +4 -0
  111. package/dist/types/ckboxui.d.ts +4 -0
  112. package/dist/types/ckboxuploadadapter.d.ts +4 -0
  113. package/dist/types/ckboxutils.d.ts +4 -0
  114. package/dist/types/index.d.ts +4 -0
  115. package/dist/types/utils.d.ts +4 -0
  116. package/package.json +2 -2
  117. package/dist/index.js +0 -1713
  118. package/dist/index.js.map +0 -1
package/dist/index.js DELETED
@@ -1,1713 +0,0 @@
1
- /**
2
- * @license Copyright (c) 2003-2024, 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 { Plugin, icons, Command, PendingActions } from '@ckeditor/ckeditor5-core/dist/index.js';
6
- import { ButtonView, Notification } from '@ckeditor/ckeditor5-ui/dist/index.js';
7
- import { Range } from '@ckeditor/ckeditor5-engine/dist/index.js';
8
- import { createElement, toMap, CKEditorError, logError, global, abortableDebounce, retry } from '@ckeditor/ckeditor5-utils/dist/index.js';
9
- import { decode } from 'blurhash';
10
- import { FileRepository } from '@ckeditor/ckeditor5-upload/dist/index.js';
11
- import { isEqual } from 'lodash-es';
12
-
13
- /**
14
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
15
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
16
- */
17
- /**
18
- * @module ckbox/ckboxui
19
- */
20
- /**
21
- * The CKBoxUI plugin. It introduces the `'ckbox'` toolbar button.
22
- */
23
- class CKBoxUI extends Plugin {
24
- /**
25
- * @inheritDoc
26
- */
27
- static get pluginName() {
28
- return 'CKBoxUI';
29
- }
30
- /**
31
- * @inheritDoc
32
- */
33
- afterInit() {
34
- const editor = this.editor;
35
- // Do not register the `ckbox` button if the command does not exist.
36
- // This might happen when CKBox library is not loaded on the page.
37
- if (!editor.commands.get('ckbox')) {
38
- return;
39
- }
40
- const t = editor.t;
41
- const componentFactory = editor.ui.componentFactory;
42
- componentFactory.add('ckbox', locale => {
43
- const command = editor.commands.get('ckbox');
44
- const button = new ButtonView(locale);
45
- button.set({
46
- label: t('Open file manager'),
47
- icon: icons.browseFiles,
48
- tooltip: true
49
- });
50
- button.bind('isOn', 'isEnabled').to(command, 'value', 'isEnabled');
51
- button.on('execute', () => {
52
- editor.execute('ckbox');
53
- });
54
- return button;
55
- });
56
- if (editor.plugins.has('ImageInsertUI')) {
57
- const imageInsertUI = editor.plugins.get('ImageInsertUI');
58
- imageInsertUI.registerIntegration({
59
- name: 'assetManager',
60
- observable: () => editor.commands.get('ckbox'),
61
- buttonViewCreator: () => {
62
- const button = this.editor.ui.componentFactory.create('ckbox');
63
- button.icon = icons.imageAssetManager;
64
- button.bind('label').to(imageInsertUI, 'isImageSelected', isImageSelected => isImageSelected ?
65
- t('Replace image with file manager') :
66
- t('Insert image with file manager'));
67
- return button;
68
- },
69
- formViewCreator: () => {
70
- const button = this.editor.ui.componentFactory.create('ckbox');
71
- button.icon = icons.imageAssetManager;
72
- button.withText = true;
73
- button.bind('label').to(imageInsertUI, 'isImageSelected', isImageSelected => isImageSelected ?
74
- t('Replace with file manager') :
75
- t('Insert with file manager'));
76
- button.on('execute', () => {
77
- imageInsertUI.dropdownView.isOpen = false;
78
- });
79
- return button;
80
- }
81
- });
82
- }
83
- }
84
- }
85
-
86
- /**
87
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
88
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
89
- */
90
- /**
91
- * Converts image source set provided by the CKBox into an object containing:
92
- * - responsive URLs for the "webp" image format,
93
- * - one fallback URL for browsers that do not support the "webp" format.
94
- */
95
- function getImageUrls(imageUrls) {
96
- const responsiveUrls = [];
97
- let maxWidth = 0;
98
- for (const key in imageUrls) {
99
- const width = parseInt(key, 10);
100
- if (!isNaN(width)) {
101
- if (width > maxWidth) {
102
- maxWidth = width;
103
- }
104
- responsiveUrls.push(`${imageUrls[key]} ${key}w`);
105
- }
106
- }
107
- const imageSources = [{
108
- srcset: responsiveUrls.join(','),
109
- sizes: `(max-width: ${maxWidth}px) 100vw, ${maxWidth}px`,
110
- type: 'image/webp'
111
- }];
112
- return {
113
- imageFallbackUrl: imageUrls.default,
114
- imageSources
115
- };
116
- }
117
- /**
118
- * Returns a workspace id to use for communication with the CKBox service.
119
- *
120
- * @param defaultWorkspaceId The default workspace to use taken from editor config.
121
- */
122
- function getWorkspaceId(token, defaultWorkspaceId) {
123
- const [, binaryTokenPayload] = token.value.split('.');
124
- const payload = JSON.parse(atob(binaryTokenPayload));
125
- const workspaces = (payload.auth && payload.auth.ckbox && payload.auth.ckbox.workspaces) || [payload.aud];
126
- if (!defaultWorkspaceId) {
127
- return workspaces[0];
128
- }
129
- const role = payload.auth && payload.auth.ckbox && payload.auth.ckbox.role;
130
- if (role == 'superadmin' || workspaces.includes(defaultWorkspaceId)) {
131
- return defaultWorkspaceId;
132
- }
133
- return null;
134
- }
135
- /**
136
- * Default resolution for decoding blurhash values.
137
- * Relatively small values must be used in order to ensure acceptable performance.
138
- */
139
- const BLUR_RESOLUTION = 32;
140
- /**
141
- * Generates an image data URL from its `blurhash` representation.
142
- */
143
- function blurHashToDataUrl(hash) {
144
- if (!hash) {
145
- return;
146
- }
147
- try {
148
- const resolutionInPx = `${BLUR_RESOLUTION}px`;
149
- const canvas = document.createElement('canvas');
150
- canvas.setAttribute('width', resolutionInPx);
151
- canvas.setAttribute('height', resolutionInPx);
152
- const ctx = canvas.getContext('2d');
153
- /* istanbul ignore next -- @preserve */
154
- if (!ctx) {
155
- return;
156
- }
157
- const imageData = ctx.createImageData(BLUR_RESOLUTION, BLUR_RESOLUTION);
158
- const decoded = decode(hash, BLUR_RESOLUTION, BLUR_RESOLUTION);
159
- imageData.data.set(decoded);
160
- ctx.putImageData(imageData, 0, 0);
161
- return canvas.toDataURL();
162
- }
163
- catch (e) {
164
- return undefined;
165
- }
166
- }
167
- /**
168
- * Sends the HTTP request.
169
- *
170
- * @internal
171
- * @param config.url the URL where the request will be sent.
172
- * @param config.method The HTTP method.
173
- * @param config.data Additional data to send.
174
- * @param config.onUploadProgress A callback informing about the upload progress.
175
- */
176
- function sendHttpRequest({ url, method = 'GET', data, onUploadProgress, signal, authorization }) {
177
- const xhr = new XMLHttpRequest();
178
- xhr.open(method, url.toString());
179
- xhr.setRequestHeader('Authorization', authorization);
180
- xhr.setRequestHeader('CKBox-Version', 'CKEditor 5');
181
- xhr.responseType = 'json';
182
- // The callback is attached to the `signal#abort` event.
183
- const abortCallback = () => {
184
- xhr.abort();
185
- };
186
- return new Promise((resolve, reject) => {
187
- signal.throwIfAborted();
188
- signal.addEventListener('abort', abortCallback);
189
- xhr.addEventListener('loadstart', () => {
190
- signal.addEventListener('abort', abortCallback);
191
- });
192
- xhr.addEventListener('loadend', () => {
193
- signal.removeEventListener('abort', abortCallback);
194
- });
195
- xhr.addEventListener('error', () => {
196
- reject();
197
- });
198
- xhr.addEventListener('abort', () => {
199
- reject();
200
- });
201
- xhr.addEventListener('load', () => {
202
- const response = xhr.response;
203
- if (!response || response.statusCode >= 400) {
204
- return reject(response && response.message);
205
- }
206
- resolve(response);
207
- });
208
- /* istanbul ignore else -- @preserve */
209
- if (onUploadProgress) {
210
- xhr.upload.addEventListener('progress', evt => {
211
- onUploadProgress(evt);
212
- });
213
- }
214
- // Send the request.
215
- xhr.send(data);
216
- });
217
- }
218
- const MIME_TO_EXTENSION = {
219
- 'image/gif': 'gif',
220
- 'image/jpeg': 'jpg',
221
- 'image/png': 'png',
222
- 'image/webp': 'webp',
223
- 'image/bmp': 'bmp',
224
- 'image/tiff': 'tiff'
225
- };
226
- /**
227
- * Returns an extension a typical file in the specified `mimeType` format would have.
228
- */
229
- function convertMimeTypeToExtension(mimeType) {
230
- return MIME_TO_EXTENSION[mimeType];
231
- }
232
- /**
233
- * Tries to fetch the given `url` and returns 'content-type' of the response.
234
- */
235
- async function getContentTypeOfUrl(url, options) {
236
- try {
237
- const response = await fetch(url, {
238
- method: 'HEAD',
239
- cache: 'force-cache',
240
- ...options
241
- });
242
- if (!response.ok) {
243
- return '';
244
- }
245
- return response.headers.get('content-type') || '';
246
- }
247
- catch {
248
- return '';
249
- }
250
- }
251
- /**
252
- * Returns an extension from the given value.
253
- */
254
- function getFileExtension(file) {
255
- const fileName = file.name;
256
- const extensionRegExp = /\.(?<ext>[^.]+)$/;
257
- const match = fileName.match(extensionRegExp);
258
- return match.groups.ext.toLowerCase();
259
- }
260
-
261
- /**
262
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
263
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
264
- */
265
- // Defines the waiting time (in milliseconds) for inserting the chosen asset into the model. The chosen asset is temporarily stored in the
266
- // `CKBoxCommand#_chosenAssets` and it is removed from there automatically after this time. See `CKBoxCommand#_chosenAssets` for more
267
- // details.
268
- const ASSET_INSERTION_WAIT_TIMEOUT = 1000;
269
- /**
270
- * The CKBox command. It is used by the {@link module:ckbox/ckboxediting~CKBoxEditing CKBox editing feature} to open the CKBox file manager.
271
- * The file manager allows inserting an image or a link to a file into the editor content.
272
- *
273
- * ```ts
274
- * editor.execute( 'ckbox' );
275
- * ```
276
- *
277
- * **Note:** This command uses other features to perform the following tasks:
278
- * - To insert images it uses the {@link module:image/image/insertimagecommand~InsertImageCommand 'insertImage'} command from the
279
- * {@link module:image/image~Image Image feature}.
280
- * - To insert links to other files it uses the {@link module:link/linkcommand~LinkCommand 'link'} command from the
281
- * {@link module:link/link~Link Link feature}.
282
- */
283
- class CKBoxCommand extends Command {
284
- /**
285
- * @inheritDoc
286
- */
287
- constructor(editor) {
288
- super(editor);
289
- /**
290
- * A set of all chosen assets. They are stored temporarily and they are automatically removed 1 second after being chosen.
291
- * Chosen assets have to be "remembered" for a while to be able to map the given asset with the element inserted into the model.
292
- * This association map is then used to set the ID on the model element.
293
- *
294
- * All chosen assets are automatically removed after the timeout, because (theoretically) it may happen that they will never be
295
- * inserted into the model, even if the {@link module:link/linkcommand~LinkCommand `'link'`} command or the
296
- * {@link module:image/image/insertimagecommand~InsertImageCommand `'insertImage'`} command is enabled. Such a case may arise when
297
- * another plugin blocks the command execution. Then, in order not to keep the chosen (but not inserted) assets forever, we delete
298
- * them automatically to prevent memory leakage. The 1 second timeout is enough to insert the asset into the model and extract the
299
- * ID from the chosen asset.
300
- *
301
- * The assets are stored only if
302
- * the {@link module:ckbox/ckboxconfig~CKBoxConfig#ignoreDataId `config.ckbox.ignoreDataId`} option is set to `false` (by default).
303
- *
304
- * @internal
305
- */
306
- this._chosenAssets = new Set();
307
- /**
308
- * The DOM element that acts as a mounting point for the CKBox dialog.
309
- */
310
- this._wrapper = null;
311
- this._initListeners();
312
- }
313
- /**
314
- * @inheritDoc
315
- */
316
- refresh() {
317
- this.value = this._getValue();
318
- this.isEnabled = this._checkEnabled();
319
- }
320
- /**
321
- * @inheritDoc
322
- */
323
- execute() {
324
- this.fire('ckbox:open');
325
- }
326
- /**
327
- * Indicates if the CKBox dialog is already opened.
328
- *
329
- * @protected
330
- * @returns {Boolean}
331
- */
332
- _getValue() {
333
- return this._wrapper !== null;
334
- }
335
- /**
336
- * Checks whether the command can be enabled in the current context.
337
- */
338
- _checkEnabled() {
339
- const imageCommand = this.editor.commands.get('insertImage');
340
- const linkCommand = this.editor.commands.get('link');
341
- if (!imageCommand.isEnabled && !linkCommand.isEnabled) {
342
- return false;
343
- }
344
- return true;
345
- }
346
- /**
347
- * Creates the options object for the CKBox dialog.
348
- *
349
- * @returns The object with properties:
350
- * - theme The theme for CKBox dialog.
351
- * - language The language for CKBox dialog.
352
- * - tokenUrl The token endpoint URL.
353
- * - serviceOrigin The base URL of the API service.
354
- * - forceDemoLabel Whether to force "Powered by CKBox" link.
355
- * - dialog.onClose The callback function invoked after closing the CKBox dialog.
356
- * - assets.onChoose The callback function invoked after choosing the assets.
357
- */
358
- _prepareOptions() {
359
- const editor = this.editor;
360
- const ckboxConfig = editor.config.get('ckbox');
361
- return {
362
- theme: ckboxConfig.theme,
363
- language: ckboxConfig.language,
364
- tokenUrl: ckboxConfig.tokenUrl,
365
- serviceOrigin: ckboxConfig.serviceOrigin,
366
- forceDemoLabel: ckboxConfig.forceDemoLabel,
367
- dialog: {
368
- onClose: () => this.fire('ckbox:close')
369
- },
370
- assets: {
371
- onChoose: (assets) => this.fire('ckbox:choose', assets)
372
- }
373
- };
374
- }
375
- /**
376
- * Initializes various event listeners for the `ckbox:*` events, because all functionality of the `ckbox` command is event-based.
377
- */
378
- _initListeners() {
379
- const editor = this.editor;
380
- const model = editor.model;
381
- const shouldInsertDataId = !editor.config.get('ckbox.ignoreDataId');
382
- // Refresh the command after firing the `ckbox:*` event.
383
- this.on('ckbox', () => {
384
- this.refresh();
385
- }, { priority: 'low' });
386
- // Handle opening of the CKBox dialog.
387
- this.on('ckbox:open', () => {
388
- if (!this.isEnabled || this.value) {
389
- return;
390
- }
391
- this._wrapper = createElement(document, 'div', { class: 'ck ckbox-wrapper' });
392
- document.body.appendChild(this._wrapper);
393
- window.CKBox.mount(this._wrapper, this._prepareOptions());
394
- });
395
- // Handle closing of the CKBox dialog.
396
- this.on('ckbox:close', () => {
397
- if (!this.value) {
398
- return;
399
- }
400
- this._wrapper.remove();
401
- this._wrapper = null;
402
- editor.editing.view.focus();
403
- });
404
- // Handle choosing the assets.
405
- this.on('ckbox:choose', (evt, assets) => {
406
- if (!this.isEnabled) {
407
- return;
408
- }
409
- const imageCommand = editor.commands.get('insertImage');
410
- const linkCommand = editor.commands.get('link');
411
- const assetsToProcess = prepareAssets({
412
- assets,
413
- isImageAllowed: imageCommand.isEnabled,
414
- isLinkAllowed: linkCommand.isEnabled
415
- });
416
- const assetsCount = assetsToProcess.length;
417
- if (assetsCount === 0) {
418
- return;
419
- }
420
- // All assets are inserted in one undo step.
421
- model.change(writer => {
422
- for (const asset of assetsToProcess) {
423
- const isLastAsset = asset === assetsToProcess[assetsCount - 1];
424
- const isSingleAsset = assetsCount === 1;
425
- this._insertAsset(asset, isLastAsset, writer, isSingleAsset);
426
- // If asset ID must be set for the inserted model element, store the asset temporarily and remove it automatically
427
- // after the timeout.
428
- if (shouldInsertDataId) {
429
- setTimeout(() => this._chosenAssets.delete(asset), ASSET_INSERTION_WAIT_TIMEOUT);
430
- this._chosenAssets.add(asset);
431
- }
432
- }
433
- });
434
- editor.editing.view.focus();
435
- });
436
- // Clean up after the editor is destroyed.
437
- this.listenTo(editor, 'destroy', () => {
438
- this.fire('ckbox:close');
439
- this._chosenAssets.clear();
440
- });
441
- }
442
- /**
443
- * Inserts the asset into the model.
444
- *
445
- * @param asset The asset to be inserted.
446
- * @param isLastAsset Indicates if the current asset is the last one from the chosen set.
447
- * @param writer An instance of the model writer.
448
- * @param isSingleAsset It's true when only one asset is processed.
449
- */
450
- _insertAsset(asset, isLastAsset, writer, isSingleAsset) {
451
- const editor = this.editor;
452
- const model = editor.model;
453
- const selection = model.document.selection;
454
- // Remove the `linkHref` attribute to not affect the asset to be inserted.
455
- writer.removeSelectionAttribute('linkHref');
456
- if (asset.type === 'image') {
457
- this._insertImage(asset);
458
- }
459
- else {
460
- this._insertLink(asset, writer, isSingleAsset);
461
- }
462
- // Except for the last chosen asset, move the selection to the end of the current range to avoid overwriting other, already
463
- // inserted assets.
464
- if (!isLastAsset) {
465
- writer.setSelection(selection.getLastPosition());
466
- }
467
- }
468
- /**
469
- * Inserts the image by calling the `insertImage` command.
470
- *
471
- * @param asset The asset to be inserted.
472
- */
473
- _insertImage(asset) {
474
- const editor = this.editor;
475
- const { imageFallbackUrl, imageSources, imageTextAlternative, imageWidth, imageHeight, imagePlaceholder } = asset.attributes;
476
- editor.execute('insertImage', {
477
- source: {
478
- src: imageFallbackUrl,
479
- sources: imageSources,
480
- alt: imageTextAlternative,
481
- width: imageWidth,
482
- height: imageHeight,
483
- ...(imagePlaceholder ? { placeholder: imagePlaceholder } : null)
484
- }
485
- });
486
- }
487
- /**
488
- * Inserts the link to the asset by calling the `link` command.
489
- *
490
- * @param asset The asset to be inserted.
491
- * @param writer An instance of the model writer.
492
- * @param isSingleAsset It's true when only one asset is processed.
493
- */
494
- _insertLink(asset, writer, isSingleAsset) {
495
- const editor = this.editor;
496
- const model = editor.model;
497
- const selection = model.document.selection;
498
- const { linkName, linkHref } = asset.attributes;
499
- // If the selection is collapsed, insert the asset name as the link label and select it.
500
- if (selection.isCollapsed) {
501
- const selectionAttributes = toMap(selection.getAttributes());
502
- const textNode = writer.createText(linkName, selectionAttributes);
503
- if (!isSingleAsset) {
504
- const selectionLastPosition = selection.getLastPosition();
505
- const parentElement = selectionLastPosition.parent;
506
- // Insert new `paragraph` when selection is not in an empty `paragraph`.
507
- if (!(parentElement.name === 'paragraph' && parentElement.isEmpty)) {
508
- editor.execute('insertParagraph', {
509
- position: selectionLastPosition
510
- });
511
- }
512
- const range = model.insertContent(textNode);
513
- writer.setSelection(range);
514
- editor.execute('link', linkHref);
515
- return;
516
- }
517
- const range = model.insertContent(textNode);
518
- writer.setSelection(range);
519
- }
520
- editor.execute('link', linkHref);
521
- }
522
- }
523
- /**
524
- * Parses the chosen assets into the internal data format. Filters out chosen assets that are not allowed.
525
- */
526
- function prepareAssets({ assets, isImageAllowed, isLinkAllowed }) {
527
- return assets
528
- .map(asset => isImage(asset) ?
529
- {
530
- id: asset.data.id,
531
- type: 'image',
532
- attributes: prepareImageAssetAttributes(asset)
533
- } :
534
- {
535
- id: asset.data.id,
536
- type: 'link',
537
- attributes: prepareLinkAssetAttributes(asset)
538
- })
539
- .filter(asset => asset.type === 'image' ? isImageAllowed : isLinkAllowed);
540
- }
541
- /**
542
- * Parses the assets attributes into the internal data format.
543
- *
544
- * @internal
545
- */
546
- function prepareImageAssetAttributes(asset) {
547
- const { imageFallbackUrl, imageSources } = getImageUrls(asset.data.imageUrls);
548
- const { description, width, height, blurHash } = asset.data.metadata;
549
- const imagePlaceholder = blurHashToDataUrl(blurHash);
550
- return {
551
- imageFallbackUrl,
552
- imageSources,
553
- imageTextAlternative: description || '',
554
- imageWidth: width,
555
- imageHeight: height,
556
- ...(imagePlaceholder ? { imagePlaceholder } : null)
557
- };
558
- }
559
- /**
560
- * Parses the assets attributes into the internal data format.
561
- *
562
- * @param origin The base URL for assets inserted into the editor.
563
- */
564
- function prepareLinkAssetAttributes(asset) {
565
- return {
566
- linkName: asset.data.name,
567
- linkHref: getAssetUrl(asset)
568
- };
569
- }
570
- /**
571
- * Checks whether the asset is an image.
572
- */
573
- function isImage(asset) {
574
- const metadata = asset.data.metadata;
575
- if (!metadata) {
576
- return false;
577
- }
578
- return metadata.width && metadata.height;
579
- }
580
- /**
581
- * Creates the URL for the asset.
582
- *
583
- * @param origin The base URL for assets inserted into the editor.
584
- */
585
- function getAssetUrl(asset) {
586
- const url = new URL(asset.data.url);
587
- url.searchParams.set('download', 'true');
588
- return url.toString();
589
- }
590
-
591
- /**
592
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
593
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
594
- */
595
- const DEFAULT_CKBOX_THEME_NAME = 'lark';
596
- /**
597
- * The CKBox utilities plugin.
598
- */
599
- class CKBoxUtils extends Plugin {
600
- /**
601
- * @inheritDoc
602
- */
603
- static get pluginName() {
604
- return 'CKBoxUtils';
605
- }
606
- /**
607
- * @inheritDoc
608
- */
609
- static get requires() {
610
- return ['CloudServices'];
611
- }
612
- /**
613
- * @inheritDoc
614
- */
615
- async init() {
616
- const editor = this.editor;
617
- const hasConfiguration = !!editor.config.get('ckbox');
618
- const isLibraryLoaded = !!window.CKBox;
619
- // Proceed with plugin initialization only when the integrator intentionally wants to use it, i.e. when the `config.ckbox` exists or
620
- // the CKBox JavaScript library is loaded.
621
- if (!hasConfiguration && !isLibraryLoaded) {
622
- return;
623
- }
624
- editor.config.define('ckbox', {
625
- serviceOrigin: 'https://api.ckbox.io',
626
- defaultUploadCategories: null,
627
- ignoreDataId: false,
628
- language: editor.locale.uiLanguage,
629
- theme: DEFAULT_CKBOX_THEME_NAME,
630
- tokenUrl: editor.config.get('cloudServices.tokenUrl')
631
- });
632
- const cloudServices = editor.plugins.get('CloudServices');
633
- const cloudServicesTokenUrl = editor.config.get('cloudServices.tokenUrl');
634
- const ckboxTokenUrl = editor.config.get('ckbox.tokenUrl');
635
- if (!ckboxTokenUrl) {
636
- /**
637
- * The {@link module:ckbox/ckboxconfig~CKBoxConfig#tokenUrl `config.ckbox.tokenUrl`} or the
638
- * {@link module:cloud-services/cloudservicesconfig~CloudServicesConfig#tokenUrl `config.cloudServices.tokenUrl`}
639
- * configuration is required for the CKBox plugin.
640
- *
641
- * ```ts
642
- * ClassicEditor.create( document.createElement( 'div' ), {
643
- * ckbox: {
644
- * tokenUrl: "YOUR_TOKEN_URL"
645
- * // ...
646
- * }
647
- * // ...
648
- * } );
649
- * ```
650
- *
651
- * @error ckbox-plugin-missing-token-url
652
- */
653
- throw new CKEditorError('ckbox-plugin-missing-token-url', this);
654
- }
655
- if (ckboxTokenUrl == cloudServicesTokenUrl) {
656
- this._token = cloudServices.token;
657
- }
658
- else {
659
- this._token = await cloudServices.registerTokenUrl(ckboxTokenUrl);
660
- }
661
- }
662
- /**
663
- * Returns a token used by the CKBox plugin for communication with the CKBox service.
664
- */
665
- getToken() {
666
- return this._token;
667
- }
668
- /**
669
- * The ID of workspace to use when uploading an image.
670
- */
671
- getWorkspaceId() {
672
- const t = this.editor.t;
673
- const cannotAccessDefaultWorkspaceError = t('Cannot access default workspace.');
674
- const defaultWorkspaceId = this.editor.config.get('ckbox.defaultUploadWorkspaceId');
675
- const workspaceId = getWorkspaceId(this._token, defaultWorkspaceId);
676
- if (workspaceId == null) {
677
- /**
678
- * The user is not authorized to access the workspace defined in the`ckbox.defaultUploadWorkspaceId` configuration.
679
- *
680
- * @error ckbox-access-default-workspace-error
681
- */
682
- logError('ckbox-access-default-workspace-error');
683
- throw cannotAccessDefaultWorkspaceError;
684
- }
685
- return workspaceId;
686
- }
687
- /**
688
- * Resolves a promise with an object containing a category with which the uploaded file is associated or an error code.
689
- */
690
- async getCategoryIdForFile(fileOrUrl, options) {
691
- const t = this.editor.t;
692
- const cannotFindCategoryError = t('Cannot determine a category for the uploaded file.');
693
- const defaultCategories = this.editor.config.get('ckbox.defaultUploadCategories');
694
- const allCategoriesPromise = this._getAvailableCategories(options);
695
- const extension = typeof fileOrUrl == 'string' ?
696
- convertMimeTypeToExtension(await getContentTypeOfUrl(fileOrUrl, options)) :
697
- getFileExtension(fileOrUrl);
698
- const allCategories = await allCategoriesPromise;
699
- // Couldn't fetch all categories. Perhaps the authorization token is invalid.
700
- if (!allCategories) {
701
- throw cannotFindCategoryError;
702
- }
703
- // If a user specifies the plugin configuration, find the first category that accepts the uploaded file.
704
- if (defaultCategories) {
705
- const userCategory = Object.keys(defaultCategories).find(category => {
706
- return defaultCategories[category].find(e => e.toLowerCase() == extension);
707
- });
708
- // If found, return its ID if the category exists on the server side.
709
- if (userCategory) {
710
- const serverCategory = allCategories.find(category => category.id === userCategory || category.name === userCategory);
711
- if (!serverCategory) {
712
- throw cannotFindCategoryError;
713
- }
714
- return serverCategory.id;
715
- }
716
- }
717
- // Otherwise, find the first category that accepts the uploaded file and returns its ID.
718
- const category = allCategories.find(category => category.extensions.find(e => e.toLowerCase() == extension));
719
- if (!category) {
720
- throw cannotFindCategoryError;
721
- }
722
- return category.id;
723
- }
724
- /**
725
- * Resolves a promise with an array containing available categories with which the uploaded file can be associated.
726
- *
727
- * If the API returns limited results, the method will collect all items.
728
- */
729
- async _getAvailableCategories(options) {
730
- const ITEMS_PER_REQUEST = 50;
731
- const editor = this.editor;
732
- const token = this._token;
733
- const { signal } = options;
734
- const serviceOrigin = editor.config.get('ckbox.serviceOrigin');
735
- const workspaceId = this.getWorkspaceId();
736
- try {
737
- const result = [];
738
- let offset = 0;
739
- let remainingItems;
740
- do {
741
- const data = await fetchCategories(offset);
742
- result.push(...data.items);
743
- remainingItems = data.totalCount - (offset + ITEMS_PER_REQUEST);
744
- offset += ITEMS_PER_REQUEST;
745
- } while (remainingItems > 0);
746
- return result;
747
- }
748
- catch {
749
- signal.throwIfAborted();
750
- /**
751
- * Fetching a list of available categories with which an uploaded file can be associated failed.
752
- *
753
- * @error ckbox-fetch-category-http-error
754
- */
755
- logError('ckbox-fetch-category-http-error');
756
- return undefined;
757
- }
758
- function fetchCategories(offset) {
759
- const categoryUrl = new URL('categories', serviceOrigin);
760
- categoryUrl.searchParams.set('limit', ITEMS_PER_REQUEST.toString());
761
- categoryUrl.searchParams.set('offset', offset.toString());
762
- categoryUrl.searchParams.set('workspaceId', workspaceId);
763
- return sendHttpRequest({
764
- url: categoryUrl,
765
- signal,
766
- authorization: token.value
767
- });
768
- }
769
- }
770
- }
771
-
772
- /**
773
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
774
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
775
- */
776
- /* globals AbortController, FormData, URL, window */
777
- /**
778
- * @module ckbox/ckboxuploadadapter
779
- */
780
- /**
781
- * A plugin that enables file uploads in CKEditor 5 using the CKBox server–side connector.
782
- * See the {@glink features/file-management/ckbox CKBox file manager integration} guide to learn how to configure
783
- * and use this feature as well as find out more about the full integration with the file manager
784
- * provided by the {@link module:ckbox/ckbox~CKBox} plugin.
785
- *
786
- * Check out the {@glink features/images/image-upload/image-upload Image upload overview} guide to learn about
787
- * other ways to upload images into CKEditor 5.
788
- */
789
- class CKBoxUploadAdapter extends Plugin {
790
- /**
791
- * @inheritDoc
792
- */
793
- static get requires() {
794
- return ['ImageUploadEditing', 'ImageUploadProgress', FileRepository, CKBoxEditing];
795
- }
796
- /**
797
- * @inheritDoc
798
- */
799
- static get pluginName() {
800
- return 'CKBoxUploadAdapter';
801
- }
802
- /**
803
- * @inheritDoc
804
- */
805
- async afterInit() {
806
- const editor = this.editor;
807
- const hasConfiguration = !!editor.config.get('ckbox');
808
- const isLibraryLoaded = !!window.CKBox;
809
- // Editor supports only one upload adapter. Register the CKBox upload adapter (and potentially overwrite other one) only when the
810
- // integrator intentionally wants to use the CKBox plugin, i.e. when the `config.ckbox` exists or the CKBox JavaScript library is
811
- // loaded.
812
- if (!hasConfiguration && !isLibraryLoaded) {
813
- return;
814
- }
815
- const fileRepository = editor.plugins.get(FileRepository);
816
- const ckboxUtils = editor.plugins.get(CKBoxUtils);
817
- fileRepository.createUploadAdapter = loader => new Adapter(loader, editor, ckboxUtils);
818
- const shouldInsertDataId = !editor.config.get('ckbox.ignoreDataId');
819
- const imageUploadEditing = editor.plugins.get('ImageUploadEditing');
820
- // Mark uploaded assets with the `ckboxImageId` attribute. Its value represents an ID in CKBox.
821
- if (shouldInsertDataId) {
822
- imageUploadEditing.on('uploadComplete', (evt, { imageElement, data }) => {
823
- editor.model.change(writer => {
824
- writer.setAttribute('ckboxImageId', data.ckboxImageId, imageElement);
825
- });
826
- });
827
- }
828
- }
829
- }
830
- /**
831
- * Upload adapter for CKBox.
832
- */
833
- class Adapter {
834
- /**
835
- * Creates a new adapter instance.
836
- */
837
- constructor(loader, editor, ckboxUtils) {
838
- this.loader = loader;
839
- this.token = ckboxUtils.getToken();
840
- this.ckboxUtils = ckboxUtils;
841
- this.editor = editor;
842
- this.controller = new AbortController();
843
- this.serviceOrigin = editor.config.get('ckbox.serviceOrigin');
844
- }
845
- /**
846
- * Starts the upload process.
847
- *
848
- * @see module:upload/filerepository~UploadAdapter#upload
849
- */
850
- async upload() {
851
- const ckboxUtils = this.ckboxUtils;
852
- const t = this.editor.t;
853
- const file = (await this.loader.file);
854
- const category = await ckboxUtils.getCategoryIdForFile(file, { signal: this.controller.signal });
855
- const uploadUrl = new URL('assets', this.serviceOrigin);
856
- const formData = new FormData();
857
- uploadUrl.searchParams.set('workspaceId', ckboxUtils.getWorkspaceId());
858
- formData.append('categoryId', category);
859
- formData.append('file', file);
860
- const requestConfig = {
861
- method: 'POST',
862
- url: uploadUrl,
863
- data: formData,
864
- onUploadProgress: (evt) => {
865
- /* istanbul ignore else -- @preserve */
866
- if (evt.lengthComputable) {
867
- this.loader.uploadTotal = evt.total;
868
- this.loader.uploaded = evt.loaded;
869
- }
870
- },
871
- signal: this.controller.signal,
872
- authorization: this.token.value
873
- };
874
- return sendHttpRequest(requestConfig)
875
- .then(async (data) => {
876
- const imageUrls = getImageUrls(data.imageUrls);
877
- return {
878
- ckboxImageId: data.id,
879
- default: imageUrls.imageFallbackUrl,
880
- sources: imageUrls.imageSources
881
- };
882
- })
883
- .catch(() => {
884
- const genericError = t('Cannot upload file:') + ` ${file.name}.`;
885
- return Promise.reject(genericError);
886
- });
887
- }
888
- /**
889
- * Aborts the upload process.
890
- *
891
- * @see module:upload/filerepository~UploadAdapter#abort
892
- */
893
- abort() {
894
- this.controller.abort();
895
- }
896
- }
897
-
898
- /**
899
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
900
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
901
- */
902
- /**
903
- * The CKBox editing feature. It introduces the {@link module:ckbox/ckboxcommand~CKBoxCommand CKBox command} and
904
- * {@link module:ckbox/ckboxuploadadapter~CKBoxUploadAdapter CKBox upload adapter}.
905
- */
906
- class CKBoxEditing extends Plugin {
907
- /**
908
- * @inheritDoc
909
- */
910
- static get pluginName() {
911
- return 'CKBoxEditing';
912
- }
913
- /**
914
- * @inheritDoc
915
- */
916
- static get requires() {
917
- return ['LinkEditing', 'PictureEditing', CKBoxUploadAdapter, CKBoxUtils];
918
- }
919
- /**
920
- * @inheritDoc
921
- */
922
- init() {
923
- const editor = this.editor;
924
- if (!this._shouldBeInitialised()) {
925
- return;
926
- }
927
- this._checkImagePlugins();
928
- // Registering the `ckbox` command makes sense only if the CKBox library is loaded, as the `ckbox` command opens the CKBox dialog.
929
- if (isLibraryLoaded()) {
930
- editor.commands.add('ckbox', new CKBoxCommand(editor));
931
- }
932
- }
933
- /**
934
- * @inheritDoc
935
- */
936
- afterInit() {
937
- const editor = this.editor;
938
- if (!this._shouldBeInitialised()) {
939
- return;
940
- }
941
- // Extending the schema, registering converters and applying fixers only make sense if the configuration option to assign
942
- // the assets ID with the model elements is enabled.
943
- if (!editor.config.get('ckbox.ignoreDataId')) {
944
- this._initSchema();
945
- this._initConversion();
946
- this._initFixers();
947
- }
948
- }
949
- /**
950
- * Returns true only when the integrator intentionally wants to use the plugin, i.e. when the `config.ckbox` exists or
951
- * the CKBox JavaScript library is loaded.
952
- */
953
- _shouldBeInitialised() {
954
- const editor = this.editor;
955
- const hasConfiguration = !!editor.config.get('ckbox');
956
- return hasConfiguration || isLibraryLoaded();
957
- }
958
- /**
959
- * Checks if at least one image plugin is loaded.
960
- */
961
- _checkImagePlugins() {
962
- const editor = this.editor;
963
- if (!editor.plugins.has('ImageBlockEditing') && !editor.plugins.has('ImageInlineEditing')) {
964
- /**
965
- * The CKBox feature requires one of the following plugins to be loaded to work correctly:
966
- *
967
- * * {@link module:image/imageblock~ImageBlock},
968
- * * {@link module:image/imageinline~ImageInline},
969
- * * {@link module:image/image~Image} (loads both `ImageBlock` and `ImageInline`)
970
- *
971
- * Please make sure your editor configuration is correct.
972
- *
973
- * @error ckbox-plugin-image-feature-missing
974
- * @param {module:core/editor/editor~Editor} editor
975
- */
976
- logError('ckbox-plugin-image-feature-missing', editor);
977
- }
978
- }
979
- /**
980
- * Extends the schema to allow the `ckboxImageId` and `ckboxLinkId` attributes for links and images.
981
- */
982
- _initSchema() {
983
- const editor = this.editor;
984
- const schema = editor.model.schema;
985
- schema.extend('$text', { allowAttributes: 'ckboxLinkId' });
986
- if (schema.isRegistered('imageBlock')) {
987
- schema.extend('imageBlock', { allowAttributes: ['ckboxImageId', 'ckboxLinkId'] });
988
- }
989
- if (schema.isRegistered('imageInline')) {
990
- schema.extend('imageInline', { allowAttributes: ['ckboxImageId', 'ckboxLinkId'] });
991
- }
992
- schema.addAttributeCheck((context, attributeName) => {
993
- const isLink = !!context.last.getAttribute('linkHref');
994
- if (!isLink && attributeName === 'ckboxLinkId') {
995
- return false;
996
- }
997
- });
998
- }
999
- /**
1000
- * Configures the upcast and downcast conversions for the `ckboxImageId` and `ckboxLinkId` attributes.
1001
- */
1002
- _initConversion() {
1003
- const editor = this.editor;
1004
- // Convert `ckboxLinkId` => `data-ckbox-resource-id`.
1005
- editor.conversion.for('downcast').add(dispatcher => {
1006
- // Due to custom converters for linked block images, handle the `ckboxLinkId` attribute manually.
1007
- dispatcher.on('attribute:ckboxLinkId:imageBlock', (evt, data, conversionApi) => {
1008
- const { writer, mapper, consumable } = conversionApi;
1009
- if (!consumable.consume(data.item, evt.name)) {
1010
- return;
1011
- }
1012
- const viewFigure = mapper.toViewElement(data.item);
1013
- const linkInImage = [...viewFigure.getChildren()]
1014
- .find((child) => child.name === 'a');
1015
- // No link inside an image - no conversion needed.
1016
- if (!linkInImage) {
1017
- return;
1018
- }
1019
- if (data.item.hasAttribute('ckboxLinkId')) {
1020
- writer.setAttribute('data-ckbox-resource-id', data.item.getAttribute('ckboxLinkId'), linkInImage);
1021
- }
1022
- else {
1023
- writer.removeAttribute('data-ckbox-resource-id', linkInImage);
1024
- }
1025
- }, { priority: 'low' });
1026
- dispatcher.on('attribute:ckboxLinkId', (evt, data, conversionApi) => {
1027
- const { writer, mapper, consumable } = conversionApi;
1028
- if (!consumable.consume(data.item, evt.name)) {
1029
- return;
1030
- }
1031
- // Remove the previous attribute value if it was applied.
1032
- if (data.attributeOldValue) {
1033
- const viewElement = createLinkElement(writer, data.attributeOldValue);
1034
- writer.unwrap(mapper.toViewRange(data.range), viewElement);
1035
- }
1036
- // Add the new attribute value if specified in a model element.
1037
- if (data.attributeNewValue) {
1038
- const viewElement = createLinkElement(writer, data.attributeNewValue);
1039
- if (data.item.is('selection')) {
1040
- const viewSelection = writer.document.selection;
1041
- writer.wrap(viewSelection.getFirstRange(), viewElement);
1042
- }
1043
- else {
1044
- writer.wrap(mapper.toViewRange(data.range), viewElement);
1045
- }
1046
- }
1047
- }, { priority: 'low' });
1048
- });
1049
- // Convert `data-ckbox-resource-id` => `ckboxLinkId`.
1050
- //
1051
- // The helper conversion does not handle all cases, so take care of the `data-ckbox-resource-id` attribute manually for images
1052
- // and links.
1053
- editor.conversion.for('upcast').add(dispatcher => {
1054
- dispatcher.on('element:a', (evt, data, conversionApi) => {
1055
- const { writer, consumable } = conversionApi;
1056
- // Upcast the `data-ckbox-resource-id` attribute only for valid link elements.
1057
- if (!data.viewItem.getAttribute('href')) {
1058
- return;
1059
- }
1060
- const consumableAttributes = { attributes: ['data-ckbox-resource-id'] };
1061
- if (!consumable.consume(data.viewItem, consumableAttributes)) {
1062
- return;
1063
- }
1064
- const attributeValue = data.viewItem.getAttribute('data-ckbox-resource-id');
1065
- // Missing the `data-ckbox-resource-id` attribute.
1066
- if (!attributeValue) {
1067
- return;
1068
- }
1069
- if (data.modelRange) {
1070
- // If the `<a>` element contains more than single children (e.g. a linked image), set the `ckboxLinkId` for each
1071
- // allowed child.
1072
- for (let item of data.modelRange.getItems()) {
1073
- if (item.is('$textProxy')) {
1074
- item = item.textNode;
1075
- }
1076
- // Do not copy the `ckboxLinkId` attribute when wrapping an element in a block element, e.g. when
1077
- // auto-paragraphing.
1078
- if (shouldUpcastAttributeForNode(item)) {
1079
- writer.setAttribute('ckboxLinkId', attributeValue, item);
1080
- }
1081
- }
1082
- }
1083
- else {
1084
- // Otherwise, just set the `ckboxLinkId` for the model element.
1085
- const modelElement = data.modelCursor.nodeBefore || data.modelCursor.parent;
1086
- writer.setAttribute('ckboxLinkId', attributeValue, modelElement);
1087
- }
1088
- }, { priority: 'low' });
1089
- });
1090
- // Convert `ckboxImageId` => `data-ckbox-resource-id`.
1091
- editor.conversion.for('downcast').attributeToAttribute({
1092
- model: 'ckboxImageId',
1093
- view: 'data-ckbox-resource-id'
1094
- });
1095
- // Convert `data-ckbox-resource-id` => `ckboxImageId`.
1096
- editor.conversion.for('upcast').elementToAttribute({
1097
- model: {
1098
- key: 'ckboxImageId',
1099
- value: (viewElement) => viewElement.getAttribute('data-ckbox-resource-id')
1100
- },
1101
- view: {
1102
- attributes: {
1103
- 'data-ckbox-resource-id': /[\s\S]+/
1104
- }
1105
- }
1106
- });
1107
- const replaceImageSourceCommand = editor.commands.get('replaceImageSource');
1108
- if (replaceImageSourceCommand) {
1109
- this.listenTo(replaceImageSourceCommand, 'cleanupImage', (_, [writer, image]) => {
1110
- writer.removeAttribute('ckboxImageId', image);
1111
- });
1112
- }
1113
- }
1114
- /**
1115
- * Registers post-fixers that add or remove the `ckboxLinkId` and `ckboxImageId` attributes.
1116
- */
1117
- _initFixers() {
1118
- const editor = this.editor;
1119
- const model = editor.model;
1120
- const selection = model.document.selection;
1121
- // Registers the post-fixer to sync the asset ID with the model elements.
1122
- model.document.registerPostFixer(syncDataIdPostFixer(editor));
1123
- // Registers the post-fixer to remove the `ckboxLinkId` attribute from the model selection.
1124
- model.document.registerPostFixer(injectSelectionPostFixer(selection));
1125
- }
1126
- }
1127
- /**
1128
- * A post-fixer that synchronizes the asset ID with the model element.
1129
- */
1130
- function syncDataIdPostFixer(editor) {
1131
- return (writer) => {
1132
- let changed = false;
1133
- const model = editor.model;
1134
- const ckboxCommand = editor.commands.get('ckbox');
1135
- // The ID from chosen assets are stored in the `CKBoxCommand#_chosenAssets`. If there is no command, it makes no sense to check
1136
- // for changes in the model.
1137
- if (!ckboxCommand) {
1138
- return changed;
1139
- }
1140
- for (const entry of model.document.differ.getChanges()) {
1141
- if (entry.type !== 'insert' && entry.type !== 'attribute') {
1142
- continue;
1143
- }
1144
- const range = entry.type === 'insert' ?
1145
- new Range(entry.position, entry.position.getShiftedBy(entry.length)) :
1146
- entry.range;
1147
- const isLinkHrefAttributeRemoval = entry.type === 'attribute' &&
1148
- entry.attributeKey === 'linkHref' &&
1149
- entry.attributeNewValue === null;
1150
- for (const item of range.getItems()) {
1151
- // If the `linkHref` attribute has been removed, sync the change with the `ckboxLinkId` attribute.
1152
- if (isLinkHrefAttributeRemoval && item.hasAttribute('ckboxLinkId')) {
1153
- writer.removeAttribute('ckboxLinkId', item);
1154
- changed = true;
1155
- continue;
1156
- }
1157
- // Otherwise, the change concerns either a new model element or an attribute change. Try to find the assets for the modified
1158
- // model element.
1159
- const assets = findAssetsForItem(item, ckboxCommand._chosenAssets);
1160
- for (const asset of assets) {
1161
- const attributeName = asset.type === 'image' ? 'ckboxImageId' : 'ckboxLinkId';
1162
- if (asset.id === item.getAttribute(attributeName)) {
1163
- continue;
1164
- }
1165
- writer.setAttribute(attributeName, asset.id, item);
1166
- changed = true;
1167
- }
1168
- }
1169
- }
1170
- return changed;
1171
- };
1172
- }
1173
- /**
1174
- * A post-fixer that removes the `ckboxLinkId` from the selection if it does not represent a link anymore.
1175
- */
1176
- function injectSelectionPostFixer(selection) {
1177
- return (writer) => {
1178
- const shouldRemoveLinkIdAttribute = !selection.hasAttribute('linkHref') && selection.hasAttribute('ckboxLinkId');
1179
- if (shouldRemoveLinkIdAttribute) {
1180
- writer.removeSelectionAttribute('ckboxLinkId');
1181
- return true;
1182
- }
1183
- return false;
1184
- };
1185
- }
1186
- /**
1187
- * Tries to find the asset that is associated with the model element by comparing the attributes:
1188
- * - the image fallback URL with the `src` attribute for images,
1189
- * - the link URL with the `href` attribute for links.
1190
- *
1191
- * For any model element, zero, one or more than one asset can be found (e.g. a linked image may be associated with the link asset and the
1192
- * image asset).
1193
- */
1194
- function findAssetsForItem(item, assets) {
1195
- const isImageElement = item.is('element', 'imageInline') || item.is('element', 'imageBlock');
1196
- const isLinkElement = item.hasAttribute('linkHref');
1197
- return [...assets].filter(asset => {
1198
- if (asset.type === 'image' && isImageElement) {
1199
- return asset.attributes.imageFallbackUrl === item.getAttribute('src');
1200
- }
1201
- if (asset.type === 'link' && isLinkElement) {
1202
- return asset.attributes.linkHref === item.getAttribute('linkHref');
1203
- }
1204
- });
1205
- }
1206
- /**
1207
- * Creates view link element with the requested ID.
1208
- */
1209
- function createLinkElement(writer, id) {
1210
- // Priority equal 5 is needed to merge adjacent `<a>` elements together.
1211
- const viewElement = writer.createAttributeElement('a', { 'data-ckbox-resource-id': id }, { priority: 5 });
1212
- writer.setCustomProperty('link', true, viewElement);
1213
- return viewElement;
1214
- }
1215
- /**
1216
- * Checks if the model element may have the `ckboxLinkId` attribute.
1217
- */
1218
- function shouldUpcastAttributeForNode(node) {
1219
- if (node.is('$text')) {
1220
- return true;
1221
- }
1222
- if (node.is('element', 'imageInline') || node.is('element', 'imageBlock')) {
1223
- return true;
1224
- }
1225
- return false;
1226
- }
1227
- /**
1228
- * Returns true if the CKBox library is loaded, false otherwise.
1229
- */
1230
- function isLibraryLoaded() {
1231
- return !!window.CKBox;
1232
- }
1233
-
1234
- /**
1235
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
1236
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
1237
- */
1238
- /**
1239
- * @module ckbox/ckbox
1240
- */
1241
- /**
1242
- * The CKBox feature, a bridge between the CKEditor 5 WYSIWYG editor and the CKBox file manager and uploader.
1243
- *
1244
- * This is a "glue" plugin which enables:
1245
- *
1246
- * * {@link module:ckbox/ckboxediting~CKBoxEditing},
1247
- * * {@link module:ckbox/ckboxui~CKBoxUI},
1248
- *
1249
- * See the {@glink features/file-management/ckbox CKBox integration} guide to learn how to configure and use this feature.
1250
- *
1251
- * Check out the {@glink features/images/image-upload/image-upload Image upload} guide to learn about other ways to upload
1252
- * images into CKEditor 5.
1253
- */
1254
- class CKBox extends Plugin {
1255
- /**
1256
- * @inheritDoc
1257
- */
1258
- static get pluginName() {
1259
- return 'CKBox';
1260
- }
1261
- /**
1262
- * @inheritDoc
1263
- */
1264
- static get requires() {
1265
- return [CKBoxEditing, CKBoxUI];
1266
- }
1267
- }
1268
-
1269
- /**
1270
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
1271
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
1272
- */
1273
- /**
1274
- * @module ckbox/ckboximageedit/utils
1275
- */
1276
- /**
1277
- * @internal
1278
- */
1279
- function createEditabilityChecker(allowExternalImagesEditing) {
1280
- const checkUrl = createUrlChecker(allowExternalImagesEditing);
1281
- return element => {
1282
- const isImageElement = element.is('element', 'imageInline') ||
1283
- element.is('element', 'imageBlock');
1284
- if (!isImageElement) {
1285
- return false;
1286
- }
1287
- if (element.hasAttribute('ckboxImageId')) {
1288
- return true;
1289
- }
1290
- if (element.hasAttribute('src')) {
1291
- return checkUrl(element.getAttribute('src'));
1292
- }
1293
- return false;
1294
- };
1295
- }
1296
- function createUrlChecker(allowExternalImagesEditing) {
1297
- if (Array.isArray(allowExternalImagesEditing)) {
1298
- const urlMatchers = allowExternalImagesEditing.map(createUrlChecker);
1299
- return src => urlMatchers.some(matcher => matcher(src));
1300
- }
1301
- if (allowExternalImagesEditing == 'origin') {
1302
- const origin = global.window.location.origin;
1303
- return src => new URL(src, global.document.baseURI).origin == origin;
1304
- }
1305
- if (typeof allowExternalImagesEditing == 'function') {
1306
- return allowExternalImagesEditing;
1307
- }
1308
- if (allowExternalImagesEditing instanceof RegExp) {
1309
- return src => !!(src.match(allowExternalImagesEditing) ||
1310
- src.replace(/^https?:\/\//, '').match(allowExternalImagesEditing));
1311
- }
1312
- return () => false;
1313
- }
1314
-
1315
- /**
1316
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
1317
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
1318
- */
1319
- /* globals document, console, AbortController, URL, window */
1320
- /**
1321
- * @module ckbox/ckboximageedit/ckboximageeditcommand
1322
- */
1323
- /**
1324
- * The CKBox edit image command.
1325
- *
1326
- * Opens the CKBox dialog for editing the image.
1327
- */
1328
- class CKBoxImageEditCommand extends Command {
1329
- /**
1330
- * @inheritDoc
1331
- */
1332
- constructor(editor) {
1333
- super(editor);
1334
- /**
1335
- * The DOM element that acts as a mounting point for the CKBox Edit Image dialog.
1336
- */
1337
- this._wrapper = null;
1338
- /**
1339
- * The states of image processing in progress.
1340
- */
1341
- this._processInProgress = new Set();
1342
- this.value = false;
1343
- this._canEdit = createEditabilityChecker(editor.config.get('ckbox.allowExternalImagesEditing'));
1344
- this._prepareOptions = abortableDebounce((signal, state) => this._prepareOptionsAbortable(signal, state));
1345
- this._prepareListeners();
1346
- }
1347
- /**
1348
- * @inheritDoc
1349
- */
1350
- refresh() {
1351
- const editor = this.editor;
1352
- this.value = this._getValue();
1353
- const selectedElement = editor.model.document.selection.getSelectedElement();
1354
- this.isEnabled =
1355
- !!selectedElement &&
1356
- this._canEdit(selectedElement) &&
1357
- !this._checkIfElementIsBeingProcessed(selectedElement);
1358
- }
1359
- /**
1360
- * Opens the CKBox Image Editor dialog for editing the image.
1361
- */
1362
- execute() {
1363
- if (this._getValue()) {
1364
- return;
1365
- }
1366
- const wrapper = createElement(document, 'div', { class: 'ck ckbox-wrapper' });
1367
- this._wrapper = wrapper;
1368
- this.value = true;
1369
- document.body.appendChild(this._wrapper);
1370
- const imageElement = this.editor.model.document.selection.getSelectedElement();
1371
- const processingState = {
1372
- element: imageElement,
1373
- controller: new AbortController()
1374
- };
1375
- this._prepareOptions(processingState).then(options => window.CKBox.mountImageEditor(wrapper, options), error => {
1376
- const editor = this.editor;
1377
- const t = editor.t;
1378
- const notification = editor.plugins.get(Notification);
1379
- notification.showWarning(t('Failed to determine category of edited image.'), {
1380
- namespace: 'ckbox'
1381
- });
1382
- console.error(error);
1383
- this._handleImageEditorClose();
1384
- });
1385
- }
1386
- /**
1387
- * @inheritDoc
1388
- */
1389
- destroy() {
1390
- this._handleImageEditorClose();
1391
- this._prepareOptions.abort();
1392
- for (const state of this._processInProgress.values()) {
1393
- state.controller.abort();
1394
- }
1395
- super.destroy();
1396
- }
1397
- /**
1398
- * Indicates if the CKBox Image Editor dialog is already opened.
1399
- */
1400
- _getValue() {
1401
- return this._wrapper !== null;
1402
- }
1403
- /**
1404
- * Creates the options object for the CKBox Image Editor dialog.
1405
- */
1406
- async _prepareOptionsAbortable(signal, state) {
1407
- const editor = this.editor;
1408
- const ckboxConfig = editor.config.get('ckbox');
1409
- const ckboxUtils = editor.plugins.get(CKBoxUtils);
1410
- const { element } = state;
1411
- let imageMountOptions;
1412
- const ckboxImageId = element.getAttribute('ckboxImageId');
1413
- if (ckboxImageId) {
1414
- imageMountOptions = {
1415
- assetId: ckboxImageId
1416
- };
1417
- }
1418
- else {
1419
- const imageUrl = new URL(element.getAttribute('src'), document.baseURI).href;
1420
- const uploadCategoryId = await ckboxUtils.getCategoryIdForFile(imageUrl, { signal });
1421
- imageMountOptions = {
1422
- imageUrl,
1423
- uploadCategoryId
1424
- };
1425
- }
1426
- return {
1427
- ...imageMountOptions,
1428
- imageEditing: {
1429
- allowOverwrite: false
1430
- },
1431
- tokenUrl: ckboxConfig.tokenUrl,
1432
- ...(ckboxConfig.serviceOrigin && { serviceOrigin: ckboxConfig.serviceOrigin }),
1433
- onClose: () => this._handleImageEditorClose(),
1434
- onSave: (asset) => this._handleImageEditorSave(state, asset)
1435
- };
1436
- }
1437
- /**
1438
- * Initializes event lister for an event of removing an image.
1439
- */
1440
- _prepareListeners() {
1441
- // Abort editing processing when the image has been removed.
1442
- this.listenTo(this.editor.model.document, 'change:data', () => {
1443
- const processingStates = this._getProcessingStatesOfDeletedImages();
1444
- processingStates.forEach(processingState => {
1445
- processingState.controller.abort();
1446
- });
1447
- });
1448
- }
1449
- /**
1450
- * Gets processing states of images that have been deleted in the mean time.
1451
- */
1452
- _getProcessingStatesOfDeletedImages() {
1453
- const states = [];
1454
- for (const state of this._processInProgress.values()) {
1455
- if (state.element.root.rootName == '$graveyard') {
1456
- states.push(state);
1457
- }
1458
- }
1459
- return states;
1460
- }
1461
- _checkIfElementIsBeingProcessed(selectedElement) {
1462
- for (const { element } of this._processInProgress) {
1463
- if (isEqual(element, selectedElement)) {
1464
- return true;
1465
- }
1466
- }
1467
- return false;
1468
- }
1469
- /**
1470
- * Closes the CKBox Image Editor dialog.
1471
- */
1472
- _handleImageEditorClose() {
1473
- if (!this._wrapper) {
1474
- return;
1475
- }
1476
- this._wrapper.remove();
1477
- this._wrapper = null;
1478
- this.editor.editing.view.focus();
1479
- this.refresh();
1480
- }
1481
- /**
1482
- * Save edited image. In case server respond with "success" replace with edited image,
1483
- * otherwise show notification error.
1484
- */
1485
- _handleImageEditorSave(state, asset) {
1486
- const t = this.editor.locale.t;
1487
- const notification = this.editor.plugins.get(Notification);
1488
- const pendingActions = this.editor.plugins.get(PendingActions);
1489
- const action = pendingActions.add(t('Processing the edited image.'));
1490
- this._processInProgress.add(state);
1491
- this._showImageProcessingIndicator(state.element, asset);
1492
- this.refresh();
1493
- this._waitForAssetProcessed(asset.data.id, state.controller.signal)
1494
- .then(asset => {
1495
- this._replaceImage(state.element, asset);
1496
- }, error => {
1497
- // Remove processing indicator. It was added only to ViewElement.
1498
- this.editor.editing.reconvertItem(state.element);
1499
- if (state.controller.signal.aborted) {
1500
- return;
1501
- }
1502
- if (!error || error instanceof CKEditorError) {
1503
- notification.showWarning(t('Server failed to process the image.'), {
1504
- namespace: 'ckbox'
1505
- });
1506
- }
1507
- else {
1508
- console.error(error);
1509
- }
1510
- }).finally(() => {
1511
- this._processInProgress.delete(state);
1512
- pendingActions.remove(action);
1513
- this.refresh();
1514
- });
1515
- }
1516
- /**
1517
- * Get asset's status on server. If server responds with "success" status then
1518
- * image is already proceeded and ready for saving.
1519
- */
1520
- async _getAssetStatusFromServer(id, signal) {
1521
- const ckboxUtils = this.editor.plugins.get(CKBoxUtils);
1522
- const url = new URL('assets/' + id, this.editor.config.get('ckbox.serviceOrigin'));
1523
- const response = await sendHttpRequest({
1524
- url,
1525
- signal,
1526
- authorization: ckboxUtils.getToken().value
1527
- });
1528
- const status = response.metadata.metadataProcessingStatus;
1529
- if (!status || status == 'queued') {
1530
- /**
1531
- * Image has not been processed yet.
1532
- *
1533
- * @error ckbox-image-not-processed
1534
- */
1535
- throw new CKEditorError('ckbox-image-not-processed');
1536
- }
1537
- return { data: { ...response } };
1538
- }
1539
- /**
1540
- * Waits for an asset to be processed.
1541
- * It retries retrieving asset status from the server in case of failure.
1542
- */
1543
- async _waitForAssetProcessed(id, signal) {
1544
- const result = await retry(() => this._getAssetStatusFromServer(id, signal), {
1545
- signal,
1546
- maxAttempts: 5
1547
- });
1548
- if (result.data.metadata.metadataProcessingStatus != 'success') {
1549
- /**
1550
- * The image processing failed.
1551
- *
1552
- * @error ckbox-image-processing-failed
1553
- */
1554
- throw new CKEditorError('ckbox-image-processing-failed');
1555
- }
1556
- return result;
1557
- }
1558
- /**
1559
- * Shows processing indicator while image is processing.
1560
- *
1561
- * @param asset Data about certain asset.
1562
- */
1563
- _showImageProcessingIndicator(element, asset) {
1564
- const editor = this.editor;
1565
- editor.editing.view.change(writer => {
1566
- const imageElementView = editor.editing.mapper.toViewElement(element);
1567
- const imageUtils = this.editor.plugins.get('ImageUtils');
1568
- const img = imageUtils.findViewImgElement(imageElementView);
1569
- writer.removeStyle('aspect-ratio', img);
1570
- writer.setAttribute('width', asset.data.metadata.width, img);
1571
- writer.setAttribute('height', asset.data.metadata.height, img);
1572
- writer.setStyle('width', `${asset.data.metadata.width}px`, img);
1573
- writer.setStyle('height', `${asset.data.metadata.height}px`, img);
1574
- writer.addClass('image-processing', imageElementView);
1575
- });
1576
- }
1577
- /**
1578
- * Replace the edited image with the new one.
1579
- */
1580
- _replaceImage(element, asset) {
1581
- const editor = this.editor;
1582
- const { imageFallbackUrl, imageSources, imageWidth, imageHeight, imagePlaceholder } = prepareImageAssetAttributes(asset);
1583
- const previousSelectionRanges = Array.from(editor.model.document.selection.getRanges());
1584
- editor.model.change(writer => {
1585
- writer.setSelection(element, 'on');
1586
- editor.execute('insertImage', {
1587
- source: {
1588
- src: imageFallbackUrl,
1589
- sources: imageSources,
1590
- width: imageWidth,
1591
- height: imageHeight,
1592
- ...(imagePlaceholder ? { placeholder: imagePlaceholder } : null),
1593
- ...(element.hasAttribute('alt') ? { alt: element.getAttribute('alt') } : null)
1594
- }
1595
- });
1596
- const previousChildren = element.getChildren();
1597
- element = editor.model.document.selection.getSelectedElement();
1598
- for (const child of previousChildren) {
1599
- writer.append(writer.cloneElement(child), element);
1600
- }
1601
- writer.setAttribute('ckboxImageId', asset.data.id, element);
1602
- writer.setSelection(previousSelectionRanges);
1603
- });
1604
- }
1605
- }
1606
-
1607
- /**
1608
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
1609
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
1610
- */
1611
- /**
1612
- * @module ckbox/ckboximageedit/ckboximageeditediting
1613
- */
1614
- /**
1615
- * The CKBox image edit editing plugin.
1616
- */
1617
- class CKBoxImageEditEditing extends Plugin {
1618
- /**
1619
- * @inheritDoc
1620
- */
1621
- static get pluginName() {
1622
- return 'CKBoxImageEditEditing';
1623
- }
1624
- /**
1625
- * @inheritDoc
1626
- */
1627
- static get requires() {
1628
- return [CKBoxEditing, CKBoxUtils, PendingActions, Notification, 'ImageUtils', 'ImageEditing'];
1629
- }
1630
- /**
1631
- * @inheritDoc
1632
- */
1633
- init() {
1634
- const { editor } = this;
1635
- editor.commands.add('ckboxImageEdit', new CKBoxImageEditCommand(editor));
1636
- }
1637
- }
1638
-
1639
- var ckboxImageEditIcon = "<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M1.201 1C.538 1 0 1.47 0 2.1v14.363c0 .64.534 1.037 1.186 1.037H5.06l5.058-5.078L6.617 9.15a.696.696 0 0 0-.957-.033L1.5 13.6V2.5h15v4.354a3.478 3.478 0 0 1 1.5.049V2.1c0-.63-.547-1.1-1.2-1.1H1.202Zm11.713 2.803a2.147 2.147 0 0 0-2.049 1.992 2.14 2.14 0 0 0 1.28 2.096 2.13 2.13 0 0 0 2.642-3.11 2.129 2.129 0 0 0-1.873-.978ZM8.089 17.635v2.388h2.389l7.046-7.046-2.39-2.39-7.045 7.048Zm11.282-6.507a.637.637 0 0 0 .139-.692.603.603 0 0 0-.139-.205l-1.49-1.488a.63.63 0 0 0-.899 0l-1.166 1.163 2.39 2.39 1.165-1.168Z\"/></svg>";
1640
-
1641
- /**
1642
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
1643
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
1644
- */
1645
- /**
1646
- * @module ckbox/ckboximageedit/ckboximageeditui
1647
- */
1648
- /**
1649
- * The UI plugin of the CKBox image edit feature.
1650
- *
1651
- * It registers the `'ckboxImageEdit'` UI button in the editor's {@link module:ui/componentfactory~ComponentFactory component factory}
1652
- * that allows you to open the CKBox dialog and edit the image.
1653
- */
1654
- class CKBoxImageEditUI extends Plugin {
1655
- /**
1656
- * @inheritDoc
1657
- */
1658
- static get pluginName() {
1659
- return 'CKBoxImageEditUI';
1660
- }
1661
- /**
1662
- * @inheritDoc
1663
- */
1664
- init() {
1665
- const editor = this.editor;
1666
- editor.ui.componentFactory.add('ckboxImageEdit', locale => {
1667
- const command = editor.commands.get('ckboxImageEdit');
1668
- const view = new ButtonView(locale);
1669
- const t = locale.t;
1670
- view.set({
1671
- label: t('Edit image'),
1672
- icon: ckboxImageEditIcon,
1673
- tooltip: true
1674
- });
1675
- view.bind('isOn').to(command, 'value', command, 'isEnabled', (value, isEnabled) => value && isEnabled);
1676
- view.bind('isEnabled').to(command);
1677
- // Execute the command.
1678
- this.listenTo(view, 'execute', () => {
1679
- editor.execute('ckboxImageEdit');
1680
- editor.editing.view.focus();
1681
- });
1682
- return view;
1683
- });
1684
- }
1685
- }
1686
-
1687
- /**
1688
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
1689
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
1690
- */
1691
- /**
1692
- * @module ckbox/ckboximageedit
1693
- */
1694
- /**
1695
- * The CKBox image edit feature.
1696
- */
1697
- class CKBoxImageEdit extends Plugin {
1698
- /**
1699
- * @inheritDoc
1700
- */
1701
- static get pluginName() {
1702
- return 'CKBoxImageEdit';
1703
- }
1704
- /**
1705
- * @inheritDoc
1706
- */
1707
- static get requires() {
1708
- return [CKBoxImageEditEditing, CKBoxImageEditUI];
1709
- }
1710
- }
1711
-
1712
- export { CKBox, CKBoxEditing, CKBoxImageEdit, CKBoxImageEditEditing, CKBoxImageEditUI, CKBoxUI };
1713
- //# sourceMappingURL=index.js.map