@ckeditor/ckeditor5-ckbox 39.0.2 → 40.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,333 +1,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 { 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
- const MAX_NUMBER_OF_ATTEMPTS_TO_FOCUS = 50;
136
- focusCKBoxItem(MAX_NUMBER_OF_ATTEMPTS_TO_FOCUS);
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
- });
146
- // Handle choosing the assets.
147
- this.on('ckbox:choose', (evt, assets) => {
148
- if (!this.isEnabled) {
149
- return;
150
- }
151
- const imageCommand = editor.commands.get('insertImage');
152
- const linkCommand = editor.commands.get('link');
153
- const assetsToProcess = prepareAssets({
154
- assets,
155
- isImageAllowed: imageCommand.isEnabled,
156
- isLinkAllowed: linkCommand.isEnabled
157
- });
158
- if (assetsToProcess.length === 0) {
159
- return;
160
- }
161
- // All assets are inserted in one undo step.
162
- model.change(writer => {
163
- for (const asset of assetsToProcess) {
164
- const isLastAsset = asset === assetsToProcess[assetsToProcess.length - 1];
165
- this._insertAsset(asset, isLastAsset, writer);
166
- // If asset ID must be set for the inserted model element, store the asset temporarily and remove it automatically
167
- // after the timeout.
168
- if (shouldInsertDataId) {
169
- setTimeout(() => this._chosenAssets.delete(asset), ASSET_INSERTION_WAIT_TIMEOUT);
170
- this._chosenAssets.add(asset);
171
- }
172
- }
173
- });
174
- });
175
- // Clean up after the editor is destroyed.
176
- this.listenTo(editor, 'destroy', () => {
177
- this.fire('ckbox:close');
178
- this._chosenAssets.clear();
179
- });
180
- }
181
- /**
182
- * Inserts the asset into the model.
183
- *
184
- * @param asset The asset to be inserted.
185
- * @param isLastAsset Indicates if the current asset is the last one from the chosen set.
186
- * @param writer An instance of the model writer.
187
- */
188
- _insertAsset(asset, isLastAsset, writer) {
189
- const editor = this.editor;
190
- const model = editor.model;
191
- const selection = model.document.selection;
192
- // Remove the `linkHref` attribute to not affect the asset to be inserted.
193
- writer.removeSelectionAttribute('linkHref');
194
- if (asset.type === 'image') {
195
- this._insertImage(asset);
196
- }
197
- else {
198
- this._insertLink(asset, writer);
199
- }
200
- // Except for the last chosen asset, move the selection to the end of the current range to avoid overwriting other, already
201
- // inserted assets.
202
- if (!isLastAsset) {
203
- writer.setSelection(selection.getLastPosition());
204
- }
205
- }
206
- /**
207
- * Inserts the image by calling the `insertImage` command.
208
- *
209
- * @param asset The asset to be inserted.
210
- */
211
- _insertImage(asset) {
212
- const editor = this.editor;
213
- const { imageFallbackUrl, imageSources, imageTextAlternative } = asset.attributes;
214
- editor.execute('insertImage', {
215
- source: {
216
- src: imageFallbackUrl,
217
- sources: imageSources,
218
- alt: imageTextAlternative
219
- }
220
- });
221
- }
222
- /**
223
- * Inserts the link to the asset by calling the `link` command.
224
- *
225
- * @param asset The asset to be inserted.
226
- * @param writer An instance of the model writer.
227
- */
228
- _insertLink(asset, writer) {
229
- const editor = this.editor;
230
- const model = editor.model;
231
- const selection = model.document.selection;
232
- const { linkName, linkHref } = asset.attributes;
233
- // If the selection is collapsed, insert the asset name as the link label and select it.
234
- if (selection.isCollapsed) {
235
- const selectionAttributes = toMap(selection.getAttributes());
236
- const textNode = writer.createText(linkName, selectionAttributes);
237
- const range = model.insertContent(textNode);
238
- writer.setSelection(range);
239
- }
240
- editor.execute('link', linkHref);
241
- }
242
- }
243
- /**
244
- * Parses the chosen assets into the internal data format. Filters out chosen assets that are not allowed.
245
- */
246
- function prepareAssets({ assets, isImageAllowed, isLinkAllowed }) {
247
- return assets
248
- .map(asset => isImage(asset) ?
249
- {
250
- id: asset.data.id,
251
- type: 'image',
252
- attributes: prepareImageAssetAttributes(asset)
253
- } :
254
- {
255
- id: asset.data.id,
256
- type: 'link',
257
- attributes: prepareLinkAssetAttributes(asset)
258
- })
259
- .filter(asset => asset.type === 'image' ? isImageAllowed : isLinkAllowed);
260
- }
261
- /**
262
- * Parses the assets attributes into the internal data format.
263
- *
264
- * @param origin The base URL for assets inserted into the editor.
265
- */
266
- function prepareImageAssetAttributes(asset) {
267
- const { imageFallbackUrl, imageSources } = getImageUrls(asset.data.imageUrls);
268
- return {
269
- imageFallbackUrl,
270
- imageSources,
271
- imageTextAlternative: asset.data.metadata.description || ''
272
- };
273
- }
274
- /**
275
- * Parses the assets attributes into the internal data format.
276
- *
277
- * @param origin The base URL for assets inserted into the editor.
278
- */
279
- function prepareLinkAssetAttributes(asset) {
280
- return {
281
- linkName: asset.data.name,
282
- linkHref: getAssetUrl(asset)
283
- };
284
- }
285
- /**
286
- * Checks whether the asset is an image.
287
- */
288
- function isImage(asset) {
289
- const metadata = asset.data.metadata;
290
- if (!metadata) {
291
- return false;
292
- }
293
- return metadata.width && metadata.height;
294
- }
295
- /**
296
- * Creates the URL for the asset.
297
- *
298
- * @param origin The base URL for assets inserted into the editor.
299
- */
300
- function getAssetUrl(asset) {
301
- const url = new URL(asset.data.url);
302
- url.searchParams.set('download', 'true');
303
- return url.toString();
304
- }
305
- /**
306
- * Focuses the CKBox first item in gallery.
307
- * This is a temporary fix. A permanent solution to this issue will be provided soon.
308
- *
309
- * @param limiter Max number of attempts to focus the ckbox item.
310
- */
311
- function focusCKBoxItem(limiter) {
312
- // Trying every 100 ms get access to the CKBox component until component will be loaded.
313
- setTimeout(() => {
314
- if (limiter === 0) {
315
- return;
316
- }
317
- const ckboxGalleryFirstItem = document.querySelector('.ckbox-gallery .ckbox-gallery-item');
318
- // In case there is no items, "upload button" will be appeared in "div" with
319
- // classname ".ckbox-empty-view".
320
- const uploadButton = document.querySelector('.ckbox-empty-view .ckbox-btn');
321
- // In case "upload button" is loaded in ".ckbox-empty-view" we focus actual button.
322
- if (uploadButton && uploadButton instanceof HTMLElement) {
323
- uploadButton.focus();
324
- return;
325
- }
326
- if (ckboxGalleryFirstItem && ckboxGalleryFirstItem instanceof HTMLElement) {
327
- ckboxGalleryFirstItem.focus();
328
- }
329
- else {
330
- focusCKBoxItem(limiter - 1);
331
- }
332
- }, 100);
333
- }
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
+ }