@ckeditor/ckeditor5-media-embed 0.0.0-nightly-20240509.0 → 0.0.0-nightly-20240510.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of @ckeditor/ckeditor5-media-embed might be problematic. Click here for more details.

package/dist/index.js CHANGED
@@ -4,8 +4,8 @@
4
4
  */
5
5
  import { Command, Plugin, icons } from '@ckeditor/ckeditor5-core/dist/index.js';
6
6
  import { toWidget, isWidget, findOptimalInsertionRange, Widget, WidgetToolbarRepository } from '@ckeditor/ckeditor5-widget/dist/index.js';
7
- import { toArray, logWarning, first, global, FocusTracker, KeystrokeHandler } from '@ckeditor/ckeditor5-utils/dist/index.js';
8
- import { IconView, Template, View, submitHandler, LabeledFieldView, createLabeledInputText, ButtonView, ViewCollection, FocusCycler, createDropdown, CssTransitionDisablerMixin } from '@ckeditor/ckeditor5-ui/dist/index.js';
7
+ import { logWarning, toArray, first, global, FocusTracker, KeystrokeHandler } from '@ckeditor/ckeditor5-utils/dist/index.js';
8
+ import { IconView, Template, View, ViewCollection, FocusCycler, submitHandler, LabeledFieldView, createLabeledInputText, ButtonView, createDropdown, CssTransitionDisablerMixin } from '@ckeditor/ckeditor5-ui/dist/index.js';
9
9
  import { LivePosition, LiveRange } from '@ckeditor/ckeditor5-engine/dist/index.js';
10
10
  import { Clipboard } from '@ckeditor/ckeditor5-clipboard/dist/index.js';
11
11
  import { Delete } from '@ckeditor/ckeditor5-typing/dist/index.js';
@@ -15,6 +15,8 @@ import { Undo } from '@ckeditor/ckeditor5-undo/dist/index.js';
15
15
  * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
16
16
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
17
17
  */ /**
18
+ * @module media-embed/converters
19
+ */ /**
18
20
  * Returns a function that converts the model "url" attribute to the view representation.
19
21
  *
20
22
  * Depending on the configuration, the view representation can be "semantic" (for the data pipeline):
@@ -145,10 +147,20 @@ import { Undo } from '@ckeditor/ckeditor5-undo/dist/index.js';
145
147
  });
146
148
  }
147
149
 
148
- class MediaEmbedCommand extends Command {
150
+ /**
151
+ * The insert media command.
152
+ *
153
+ * The command is registered by the {@link module:media-embed/mediaembedediting~MediaEmbedEditing} as `'mediaEmbed'`.
154
+ *
155
+ * To insert media at the current selection, execute the command and specify the URL:
156
+ *
157
+ * ```ts
158
+ * editor.execute( 'mediaEmbed', 'http://url.to.the/media' );
159
+ * ```
160
+ */ class MediaEmbedCommand extends Command {
149
161
  /**
150
- * @inheritDoc
151
- */ refresh() {
162
+ * @inheritDoc
163
+ */ refresh() {
152
164
  const model = this.editor.model;
153
165
  const selection = model.document.selection;
154
166
  const selectedMedia = getSelectedMediaModelWidget(selection);
@@ -156,14 +168,14 @@ class MediaEmbedCommand extends Command {
156
168
  this.isEnabled = isMediaSelected(selection) || isAllowedInParent(selection, model);
157
169
  }
158
170
  /**
159
- * Executes the command, which either:
160
- *
161
- * * updates the URL of the selected media,
162
- * * inserts the new media into the editor and puts the selection around it.
163
- *
164
- * @fires execute
165
- * @param url The URL of the media.
166
- */ execute(url) {
171
+ * Executes the command, which either:
172
+ *
173
+ * * updates the URL of the selected media,
174
+ * * inserts the new media into the editor and puts the selection around it.
175
+ *
176
+ * @fires execute
177
+ * @param url The URL of the media.
178
+ */ execute(url) {
167
179
  const model = this.editor.model;
168
180
  const selection = model.document.selection;
169
181
  const selectedMedia = getSelectedMediaModelWidget(selection);
@@ -197,31 +209,72 @@ class MediaEmbedCommand extends Command {
197
209
  var mediaPlaceholderIcon = "<svg viewBox=\"0 0 64 42\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M47.426 17V3.713L63.102 0v19.389h-.001l.001.272c0 1.595-2.032 3.43-4.538 4.098-2.506.668-4.538-.083-4.538-1.678 0-1.594 2.032-3.43 4.538-4.098.914-.244 2.032-.565 2.888-.603V4.516L49.076 7.447v9.556A1.014 1.014 0 0 0 49 17h-1.574zM29.5 17h-8.343a7.073 7.073 0 1 0-4.657 4.06v3.781H3.3a2.803 2.803 0 0 1-2.8-2.804V8.63a2.803 2.803 0 0 1 2.8-2.805h4.082L8.58 2.768A1.994 1.994 0 0 1 10.435 1.5h8.985c.773 0 1.477.448 1.805 1.149l1.488 3.177H26.7c1.546 0 2.8 1.256 2.8 2.805V17zm-11.637 0H17.5a1 1 0 0 0-1 1v.05A4.244 4.244 0 1 1 17.863 17zm29.684 2c.97 0 .953-.048.953.889v20.743c0 .953.016.905-.953.905H19.453c-.97 0-.953.048-.953-.905V19.89c0-.937-.016-.889.97-.889h28.077zm-4.701 19.338V22.183H24.154v16.155h18.692zM20.6 21.375v1.616h1.616v-1.616H20.6zm0 3.231v1.616h1.616v-1.616H20.6zm0 3.231v1.616h1.616v-1.616H20.6zm0 3.231v1.616h1.616v-1.616H20.6zm0 3.231v1.616h1.616v-1.616H20.6zm0 3.231v1.616h1.616V37.53H20.6zm24.233-16.155v1.616h1.615v-1.616h-1.615zm0 3.231v1.616h1.615v-1.616h-1.615zm0 3.231v1.616h1.615v-1.616h-1.615zm0 3.231v1.616h1.615v-1.616h-1.615zm0 3.231v1.616h1.615v-1.616h-1.615zm0 3.231v1.616h1.615V37.53h-1.615zM29.485 25.283a.4.4 0 0 1 .593-.35l9.05 4.977a.4.4 0 0 1 0 .701l-9.05 4.978a.4.4 0 0 1-.593-.35v-9.956z\"/></svg>";
198
210
 
199
211
  const mediaPlaceholderIconViewBox = '0 0 64 42';
200
- class MediaRegistry {
212
+ /**
213
+ * A bridge between the raw media content provider definitions and the editor view content.
214
+ *
215
+ * It helps translating media URLs to corresponding {@link module:engine/view/element~Element view elements}.
216
+ *
217
+ * Mostly used by the {@link module:media-embed/mediaembedediting~MediaEmbedEditing} plugin.
218
+ */ class MediaRegistry {
219
+ /**
220
+ * The {@link module:utils/locale~Locale} instance.
221
+ */ locale;
222
+ /**
223
+ * The media provider definitions available for the registry. Usually corresponding with the
224
+ * {@link module:media-embed/mediaembedconfig~MediaEmbedConfig media configuration}.
225
+ */ providerDefinitions;
226
+ /**
227
+ * Creates an instance of the {@link module:media-embed/mediaregistry~MediaRegistry} class.
228
+ *
229
+ * @param locale The localization services instance.
230
+ * @param config The configuration of the media embed feature.
231
+ */ constructor(locale, config){
232
+ const providers = config.providers;
233
+ const extraProviders = config.extraProviders || [];
234
+ const removedProviders = new Set(config.removeProviders);
235
+ const providerDefinitions = providers.concat(extraProviders).filter((provider)=>{
236
+ const name = provider.name;
237
+ if (!name) {
238
+ /**
239
+ * One of the providers (or extra providers) specified in the media embed configuration
240
+ * has no name and will not be used by the editor. In order to get this media
241
+ * provider working, double check your editor configuration.
242
+ *
243
+ * @error media-embed-no-provider-name
244
+ */ logWarning('media-embed-no-provider-name', {
245
+ provider
246
+ });
247
+ return false;
248
+ }
249
+ return !removedProviders.has(name);
250
+ });
251
+ this.locale = locale;
252
+ this.providerDefinitions = providerDefinitions;
253
+ }
201
254
  /**
202
- * Checks whether the passed URL is representing a certain media type allowed in the editor.
203
- *
204
- * @param url The URL to be checked
205
- */ hasMedia(url) {
255
+ * Checks whether the passed URL is representing a certain media type allowed in the editor.
256
+ *
257
+ * @param url The URL to be checked
258
+ */ hasMedia(url) {
206
259
  return !!this._getMedia(url);
207
260
  }
208
261
  /**
209
- * For the given media URL string and options, it returns the {@link module:engine/view/element~Element view element}
210
- * representing that media.
211
- *
212
- * **Note:** If no URL is specified, an empty view element is returned.
213
- *
214
- * @param writer The view writer used to produce a view element.
215
- * @param url The URL to be translated into a view element.
216
- */ getMediaViewElement(writer, url, options) {
262
+ * For the given media URL string and options, it returns the {@link module:engine/view/element~Element view element}
263
+ * representing that media.
264
+ *
265
+ * **Note:** If no URL is specified, an empty view element is returned.
266
+ *
267
+ * @param writer The view writer used to produce a view element.
268
+ * @param url The URL to be translated into a view element.
269
+ */ getMediaViewElement(writer, url, options) {
217
270
  return this._getMedia(url).getViewElement(writer, options);
218
271
  }
219
272
  /**
220
- * Returns a `Media` instance for the given URL.
221
- *
222
- * @param url The URL of the media.
223
- * @returns The `Media` instance or `null` when there is none.
224
- */ _getMedia(url) {
273
+ * Returns a `Media` instance for the given URL.
274
+ *
275
+ * @param url The URL of the media.
276
+ * @returns The `Media` instance or `null` when there is none.
277
+ */ _getMedia(url) {
225
278
  if (!url) {
226
279
  return new Media(this.locale);
227
280
  }
@@ -239,11 +292,11 @@ class MediaRegistry {
239
292
  return null;
240
293
  }
241
294
  /**
242
- * Tries to match `url` to `pattern`.
243
- *
244
- * @param url The URL of the media.
245
- * @param pattern The pattern that should accept the media URL.
246
- */ _getUrlMatches(url, pattern) {
295
+ * Tries to match `url` to `pattern`.
296
+ *
297
+ * @param url The URL of the media.
298
+ * @param pattern The pattern that should accept the media URL.
299
+ */ _getUrlMatches(url, pattern) {
247
300
  // 1. Try to match without stripping the protocol and "www" subdomain.
248
301
  let match = url.match(pattern);
249
302
  if (match) {
@@ -263,34 +316,6 @@ class MediaRegistry {
263
316
  }
264
317
  return null;
265
318
  }
266
- /**
267
- * Creates an instance of the {@link module:media-embed/mediaregistry~MediaRegistry} class.
268
- *
269
- * @param locale The localization services instance.
270
- * @param config The configuration of the media embed feature.
271
- */ constructor(locale, config){
272
- const providers = config.providers;
273
- const extraProviders = config.extraProviders || [];
274
- const removedProviders = new Set(config.removeProviders);
275
- const providerDefinitions = providers.concat(extraProviders).filter((provider)=>{
276
- const name = provider.name;
277
- if (!name) {
278
- /**
279
- * One of the providers (or extra providers) specified in the media embed configuration
280
- * has no name and will not be used by the editor. In order to get this media
281
- * provider working, double check your editor configuration.
282
- *
283
- * @error media-embed-no-provider-name
284
- */ logWarning('media-embed-no-provider-name', {
285
- provider
286
- });
287
- return false;
288
- }
289
- return !removedProviders.has(name);
290
- });
291
- this.locale = locale;
292
- this.providerDefinitions = providerDefinitions;
293
- }
294
319
  }
295
320
  /**
296
321
  * Represents media defined by the provider configuration.
@@ -298,10 +323,30 @@ class MediaRegistry {
298
323
  * It can be rendered to the {@link module:engine/view/element~Element view element} and used in the editing or data pipeline.
299
324
  */ class Media {
300
325
  /**
301
- * Returns the view element representation of the media.
302
- *
303
- * @param writer The view writer used to produce a view element.
304
- */ getViewElement(writer, options) {
326
+ * The URL this Media instance represents.
327
+ */ url;
328
+ /**
329
+ * Shorthand for {@link module:utils/locale~Locale#t}.
330
+ *
331
+ * @see module:utils/locale~Locale#t
332
+ */ _locale;
333
+ /**
334
+ * The output of the `RegExp.match` which validated the {@link #url} of this media.
335
+ */ _match;
336
+ /**
337
+ * The function returning the HTML string preview of this media.
338
+ */ _previewRenderer;
339
+ constructor(locale, url, match, previewRenderer){
340
+ this.url = this._getValidUrl(url);
341
+ this._locale = locale;
342
+ this._match = match;
343
+ this._previewRenderer = previewRenderer;
344
+ }
345
+ /**
346
+ * Returns the view element representation of the media.
347
+ *
348
+ * @param writer The view writer used to produce a view element.
349
+ */ getViewElement(writer, options) {
305
350
  const attributes = {};
306
351
  let viewElement;
307
352
  if (options.renderForEditingView || options.renderMediaPreview && this.url && this._previewRenderer) {
@@ -325,8 +370,8 @@ class MediaRegistry {
325
370
  return viewElement;
326
371
  }
327
372
  /**
328
- * Returns the HTML string of the media content preview.
329
- */ _getPreviewHtml(options) {
373
+ * Returns the HTML string of the media content preview.
374
+ */ _getPreviewHtml(options) {
330
375
  if (this._previewRenderer) {
331
376
  return this._previewRenderer(this._match);
332
377
  } else {
@@ -339,8 +384,8 @@ class MediaRegistry {
339
384
  }
340
385
  }
341
386
  /**
342
- * Returns the placeholder HTML when the media has no content preview.
343
- */ _getPlaceholderHtml() {
387
+ * Returns the placeholder HTML when the media has no content preview.
388
+ */ _getPlaceholderHtml() {
344
389
  const icon = new IconView();
345
390
  const t = this._locale.t;
346
391
  icon.content = mediaPlaceholderIcon;
@@ -386,10 +431,10 @@ class MediaRegistry {
386
431
  return placeholder.outerHTML;
387
432
  }
388
433
  /**
389
- * Returns the full URL to the specified media.
390
- *
391
- * @param url The URL of the media.
392
- */ _getValidUrl(url) {
434
+ * Returns the full URL to the specified media.
435
+ *
436
+ * @param url The URL of the media.
437
+ */ _getValidUrl(url) {
393
438
  if (!url) {
394
439
  return null;
395
440
  }
@@ -398,23 +443,111 @@ class MediaRegistry {
398
443
  }
399
444
  return 'https://' + url;
400
445
  }
401
- constructor(locale, url, match, previewRenderer){
402
- this.url = this._getValidUrl(url);
403
- this._locale = locale;
404
- this._match = match;
405
- this._previewRenderer = previewRenderer;
406
- }
407
446
  }
408
447
 
409
- class MediaEmbedEditing extends Plugin {
448
+ /**
449
+ * The media embed editing feature.
450
+ */ class MediaEmbedEditing extends Plugin {
410
451
  /**
411
- * @inheritDoc
412
- */ static get pluginName() {
452
+ * @inheritDoc
453
+ */ static get pluginName() {
413
454
  return 'MediaEmbedEditing';
414
455
  }
415
456
  /**
416
- * @inheritDoc
417
- */ init() {
457
+ * The media registry managing the media providers in the editor.
458
+ */ registry;
459
+ /**
460
+ * @inheritDoc
461
+ */ constructor(editor){
462
+ super(editor);
463
+ editor.config.define('mediaEmbed', {
464
+ elementName: 'oembed',
465
+ providers: [
466
+ {
467
+ name: 'dailymotion',
468
+ url: [
469
+ /^dailymotion\.com\/video\/(\w+)/,
470
+ /^dai.ly\/(\w+)/
471
+ ],
472
+ html: (match)=>{
473
+ const id = match[1];
474
+ return '<div style="position: relative; padding-bottom: 100%; height: 0; ">' + `<iframe src="https://www.dailymotion.com/embed/video/${id}" ` + 'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' + 'frameborder="0" width="480" height="270" allowfullscreen allow="autoplay">' + '</iframe>' + '</div>';
475
+ }
476
+ },
477
+ {
478
+ name: 'spotify',
479
+ url: [
480
+ /^open\.spotify\.com\/(artist\/\w+)/,
481
+ /^open\.spotify\.com\/(album\/\w+)/,
482
+ /^open\.spotify\.com\/(track\/\w+)/
483
+ ],
484
+ html: (match)=>{
485
+ const id = match[1];
486
+ return '<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 126%;">' + `<iframe src="https://open.spotify.com/embed/${id}" ` + 'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' + 'frameborder="0" allowtransparency="true" allow="encrypted-media">' + '</iframe>' + '</div>';
487
+ }
488
+ },
489
+ {
490
+ name: 'youtube',
491
+ url: [
492
+ /^(?:m\.)?youtube\.com\/watch\?v=([\w-]+)(?:&t=(\d+))?/,
493
+ /^(?:m\.)?youtube\.com\/v\/([\w-]+)(?:\?t=(\d+))?/,
494
+ /^youtube\.com\/embed\/([\w-]+)(?:\?start=(\d+))?/,
495
+ /^youtu\.be\/([\w-]+)(?:\?t=(\d+))?/
496
+ ],
497
+ html: (match)=>{
498
+ const id = match[1];
499
+ const time = match[2];
500
+ return '<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 56.2493%;">' + `<iframe src="https://www.youtube.com/embed/${id}${time ? `?start=${time}` : ''}" ` + 'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' + 'frameborder="0" allow="autoplay; encrypted-media" allowfullscreen>' + '</iframe>' + '</div>';
501
+ }
502
+ },
503
+ {
504
+ name: 'vimeo',
505
+ url: [
506
+ /^vimeo\.com\/(\d+)/,
507
+ /^vimeo\.com\/[^/]+\/[^/]+\/video\/(\d+)/,
508
+ /^vimeo\.com\/album\/[^/]+\/video\/(\d+)/,
509
+ /^vimeo\.com\/channels\/[^/]+\/(\d+)/,
510
+ /^vimeo\.com\/groups\/[^/]+\/videos\/(\d+)/,
511
+ /^vimeo\.com\/ondemand\/[^/]+\/(\d+)/,
512
+ /^player\.vimeo\.com\/video\/(\d+)/
513
+ ],
514
+ html: (match)=>{
515
+ const id = match[1];
516
+ return '<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 56.2493%;">' + `<iframe src="https://player.vimeo.com/video/${id}" ` + 'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' + 'frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen>' + '</iframe>' + '</div>';
517
+ }
518
+ },
519
+ {
520
+ name: 'instagram',
521
+ url: /^instagram\.com\/p\/(\w+)/
522
+ },
523
+ {
524
+ name: 'twitter',
525
+ url: /^twitter\.com/
526
+ },
527
+ {
528
+ name: 'googleMaps',
529
+ url: [
530
+ /^google\.com\/maps/,
531
+ /^goo\.gl\/maps/,
532
+ /^maps\.google\.com/,
533
+ /^maps\.app\.goo\.gl/
534
+ ]
535
+ },
536
+ {
537
+ name: 'flickr',
538
+ url: /^flickr\.com/
539
+ },
540
+ {
541
+ name: 'facebook',
542
+ url: /^facebook\.com/
543
+ }
544
+ ]
545
+ });
546
+ this.registry = new MediaRegistry(editor.locale, editor.config.get('mediaEmbed'));
547
+ }
548
+ /**
549
+ * @inheritDoc
550
+ */ init() {
418
551
  const editor = this.editor;
419
552
  const schema = editor.model.schema;
420
553
  const t = editor.t;
@@ -522,102 +655,16 @@ class MediaEmbedEditing extends Plugin {
522
655
  dispatcher.on('element:figure', converter);
523
656
  });
524
657
  }
525
- /**
526
- * @inheritDoc
527
- */ constructor(editor){
528
- super(editor);
529
- editor.config.define('mediaEmbed', {
530
- elementName: 'oembed',
531
- providers: [
532
- {
533
- name: 'dailymotion',
534
- url: [
535
- /^dailymotion\.com\/video\/(\w+)/,
536
- /^dai.ly\/(\w+)/
537
- ],
538
- html: (match)=>{
539
- const id = match[1];
540
- return '<div style="position: relative; padding-bottom: 100%; height: 0; ">' + `<iframe src="https://www.dailymotion.com/embed/video/${id}" ` + 'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' + 'frameborder="0" width="480" height="270" allowfullscreen allow="autoplay">' + '</iframe>' + '</div>';
541
- }
542
- },
543
- {
544
- name: 'spotify',
545
- url: [
546
- /^open\.spotify\.com\/(artist\/\w+)/,
547
- /^open\.spotify\.com\/(album\/\w+)/,
548
- /^open\.spotify\.com\/(track\/\w+)/
549
- ],
550
- html: (match)=>{
551
- const id = match[1];
552
- return '<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 126%;">' + `<iframe src="https://open.spotify.com/embed/${id}" ` + 'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' + 'frameborder="0" allowtransparency="true" allow="encrypted-media">' + '</iframe>' + '</div>';
553
- }
554
- },
555
- {
556
- name: 'youtube',
557
- url: [
558
- /^(?:m\.)?youtube\.com\/watch\?v=([\w-]+)(?:&t=(\d+))?/,
559
- /^(?:m\.)?youtube\.com\/v\/([\w-]+)(?:\?t=(\d+))?/,
560
- /^youtube\.com\/embed\/([\w-]+)(?:\?start=(\d+))?/,
561
- /^youtu\.be\/([\w-]+)(?:\?t=(\d+))?/
562
- ],
563
- html: (match)=>{
564
- const id = match[1];
565
- const time = match[2];
566
- return '<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 56.2493%;">' + `<iframe src="https://www.youtube.com/embed/${id}${time ? `?start=${time}` : ''}" ` + 'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' + 'frameborder="0" allow="autoplay; encrypted-media" allowfullscreen>' + '</iframe>' + '</div>';
567
- }
568
- },
569
- {
570
- name: 'vimeo',
571
- url: [
572
- /^vimeo\.com\/(\d+)/,
573
- /^vimeo\.com\/[^/]+\/[^/]+\/video\/(\d+)/,
574
- /^vimeo\.com\/album\/[^/]+\/video\/(\d+)/,
575
- /^vimeo\.com\/channels\/[^/]+\/(\d+)/,
576
- /^vimeo\.com\/groups\/[^/]+\/videos\/(\d+)/,
577
- /^vimeo\.com\/ondemand\/[^/]+\/(\d+)/,
578
- /^player\.vimeo\.com\/video\/(\d+)/
579
- ],
580
- html: (match)=>{
581
- const id = match[1];
582
- return '<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 56.2493%;">' + `<iframe src="https://player.vimeo.com/video/${id}" ` + 'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' + 'frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen>' + '</iframe>' + '</div>';
583
- }
584
- },
585
- {
586
- name: 'instagram',
587
- url: /^instagram\.com\/p\/(\w+)/
588
- },
589
- {
590
- name: 'twitter',
591
- url: /^twitter\.com/
592
- },
593
- {
594
- name: 'googleMaps',
595
- url: [
596
- /^google\.com\/maps/,
597
- /^goo\.gl\/maps/,
598
- /^maps\.google\.com/,
599
- /^maps\.app\.goo\.gl/
600
- ]
601
- },
602
- {
603
- name: 'flickr',
604
- url: /^flickr\.com/
605
- },
606
- {
607
- name: 'facebook',
608
- url: /^facebook\.com/
609
- }
610
- ]
611
- });
612
- this.registry = new MediaRegistry(editor.locale, editor.config.get('mediaEmbed'));
613
- }
614
658
  }
615
659
 
616
660
  const URL_REGEXP = /^(?:http(s)?:\/\/)?[\w-]+\.[\w-.~:/?#[\]@!$&'()*+,;=%]+$/;
617
- class AutoMediaEmbed extends Plugin {
661
+ /**
662
+ * The auto-media embed plugin. It recognizes media links in the pasted content and embeds
663
+ * them shortly after they are injected into the document.
664
+ */ class AutoMediaEmbed extends Plugin {
618
665
  /**
619
- * @inheritDoc
620
- */ static get requires() {
666
+ * @inheritDoc
667
+ */ static get requires() {
621
668
  return [
622
669
  Clipboard,
623
670
  Delete,
@@ -625,13 +672,28 @@ class AutoMediaEmbed extends Plugin {
625
672
  ];
626
673
  }
627
674
  /**
628
- * @inheritDoc
629
- */ static get pluginName() {
675
+ * @inheritDoc
676
+ */ static get pluginName() {
630
677
  return 'AutoMediaEmbed';
631
678
  }
632
679
  /**
633
- * @inheritDoc
634
- */ init() {
680
+ * The paste–to–embed `setTimeout` ID. Stored as a property to allow
681
+ * cleaning of the timeout.
682
+ */ _timeoutId;
683
+ /**
684
+ * The position where the `<media>` element will be inserted after the timeout,
685
+ * determined each time the new content is pasted into the document.
686
+ */ _positionToInsert;
687
+ /**
688
+ * @inheritDoc
689
+ */ constructor(editor){
690
+ super(editor);
691
+ this._timeoutId = null;
692
+ this._positionToInsert = null;
693
+ }
694
+ /**
695
+ * @inheritDoc
696
+ */ init() {
635
697
  const editor = this.editor;
636
698
  const modelDocument = editor.model.document;
637
699
  // We need to listen on `Clipboard#inputTransformation` because we need to save positions of selection.
@@ -665,12 +727,12 @@ class AutoMediaEmbed extends Plugin {
665
727
  });
666
728
  }
667
729
  /**
668
- * Analyzes the part of the document between provided positions in search for a URL representing media.
669
- * When the URL is found, it is automatically converted into media.
670
- *
671
- * @param leftPosition Left position of the selection.
672
- * @param rightPosition Right position of the selection.
673
- */ _embedMediaBetweenPositions(leftPosition, rightPosition) {
730
+ * Analyzes the part of the document between provided positions in search for a URL representing media.
731
+ * When the URL is found, it is automatically converted into media.
732
+ *
733
+ * @param leftPosition Left position of the selection.
734
+ * @param rightPosition Right position of the selection.
735
+ */ _embedMediaBetweenPositions(leftPosition, rightPosition) {
674
736
  const editor = this.editor;
675
737
  const mediaRegistry = editor.plugins.get(MediaEmbedEditing).registry;
676
738
  // TODO: Use marker instead of LiveRange & LivePositions.
@@ -722,19 +784,90 @@ class AutoMediaEmbed extends Plugin {
722
784
  editor.plugins.get(Delete).requestUndoOnBackspace();
723
785
  }, 100);
724
786
  }
725
- /**
726
- * @inheritDoc
727
- */ constructor(editor){
728
- super(editor);
729
- this._timeoutId = null;
730
- this._positionToInsert = null;
731
- }
732
787
  }
733
788
 
734
- class MediaFormView extends View {
789
+ /**
790
+ * The media form view controller class.
791
+ *
792
+ * See {@link module:media-embed/ui/mediaformview~MediaFormView}.
793
+ */ class MediaFormView extends View {
794
+ /**
795
+ * Tracks information about the DOM focus in the form.
796
+ */ focusTracker;
797
+ /**
798
+ * An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
799
+ */ keystrokes;
800
+ /**
801
+ * The URL input view.
802
+ */ urlInputView;
803
+ /**
804
+ * The Save button view.
805
+ */ saveButtonView;
806
+ /**
807
+ * The Cancel button view.
808
+ */ cancelButtonView;
809
+ /**
810
+ * A collection of views that can be focused in the form.
811
+ */ _focusables;
812
+ /**
813
+ * Helps cycling over {@link #_focusables} in the form.
814
+ */ _focusCycler;
815
+ /**
816
+ * An array of form validators used by {@link #isValid}.
817
+ */ _validators;
818
+ /**
819
+ * The default info text for the {@link #urlInputView}.
820
+ */ _urlInputViewInfoDefault;
821
+ /**
822
+ * The info text with an additional tip for the {@link #urlInputView},
823
+ * displayed when the input has some value.
824
+ */ _urlInputViewInfoTip;
735
825
  /**
736
- * @inheritDoc
737
- */ render() {
826
+ * @param validators Form validators used by {@link #isValid}.
827
+ * @param locale The localization services instance.
828
+ */ constructor(validators, locale){
829
+ super(locale);
830
+ const t = locale.t;
831
+ this.focusTracker = new FocusTracker();
832
+ this.keystrokes = new KeystrokeHandler();
833
+ this.set('mediaURLInputValue', '');
834
+ this.urlInputView = this._createUrlInput();
835
+ this.saveButtonView = this._createButton(t('Save'), icons.check, 'ck-button-save');
836
+ this.saveButtonView.type = 'submit';
837
+ this.cancelButtonView = this._createButton(t('Cancel'), icons.cancel, 'ck-button-cancel', 'cancel');
838
+ this._focusables = new ViewCollection();
839
+ this._focusCycler = new FocusCycler({
840
+ focusables: this._focusables,
841
+ focusTracker: this.focusTracker,
842
+ keystrokeHandler: this.keystrokes,
843
+ actions: {
844
+ // Navigate form fields backwards using the <kbd>Shift</kbd> + <kbd>Tab</kbd> keystroke.
845
+ focusPrevious: 'shift + tab',
846
+ // Navigate form fields forwards using the <kbd>Tab</kbd> key.
847
+ focusNext: 'tab'
848
+ }
849
+ });
850
+ this._validators = validators;
851
+ this.setTemplate({
852
+ tag: 'form',
853
+ attributes: {
854
+ class: [
855
+ 'ck',
856
+ 'ck-media-form',
857
+ 'ck-responsive-form'
858
+ ],
859
+ tabindex: '-1'
860
+ },
861
+ children: [
862
+ this.urlInputView,
863
+ this.saveButtonView,
864
+ this.cancelButtonView
865
+ ]
866
+ });
867
+ }
868
+ /**
869
+ * @inheritDoc
870
+ */ render() {
738
871
  super.render();
739
872
  submitHandler({
740
873
  view: this
@@ -762,31 +895,31 @@ class MediaFormView extends View {
762
895
  this.keystrokes.set('arrowdown', stopPropagation);
763
896
  }
764
897
  /**
765
- * @inheritDoc
766
- */ destroy() {
898
+ * @inheritDoc
899
+ */ destroy() {
767
900
  super.destroy();
768
901
  this.focusTracker.destroy();
769
902
  this.keystrokes.destroy();
770
903
  }
771
904
  /**
772
- * Focuses the fist {@link #_focusables} in the form.
773
- */ focus() {
905
+ * Focuses the fist {@link #_focusables} in the form.
906
+ */ focus() {
774
907
  this._focusCycler.focusFirst();
775
908
  }
776
909
  /**
777
- * The native DOM `value` of the {@link #urlInputView} element.
778
- *
779
- * **Note**: Do not confuse it with the {@link module:ui/inputtext/inputtextview~InputTextView#value}
780
- * which works one way only and may not represent the actual state of the component in the DOM.
781
- */ get url() {
910
+ * The native DOM `value` of the {@link #urlInputView} element.
911
+ *
912
+ * **Note**: Do not confuse it with the {@link module:ui/inputtext/inputtextview~InputTextView#value}
913
+ * which works one way only and may not represent the actual state of the component in the DOM.
914
+ */ get url() {
782
915
  return this.urlInputView.fieldView.element.value.trim();
783
916
  }
784
917
  set url(url) {
785
918
  this.urlInputView.fieldView.element.value = url.trim();
786
919
  }
787
920
  /**
788
- * Validates the form and returns `false` when some fields are invalid.
789
- */ isValid() {
921
+ * Validates the form and returns `false` when some fields are invalid.
922
+ */ isValid() {
790
923
  this.resetFormStatus();
791
924
  for (const validator of this._validators){
792
925
  const errorText = validator(this);
@@ -800,19 +933,19 @@ class MediaFormView extends View {
800
933
  return true;
801
934
  }
802
935
  /**
803
- * Cleans up the supplementary error and information text of the {@link #urlInputView}
804
- * bringing them back to the state when the form has been displayed for the first time.
805
- *
806
- * See {@link #isValid}.
807
- */ resetFormStatus() {
936
+ * Cleans up the supplementary error and information text of the {@link #urlInputView}
937
+ * bringing them back to the state when the form has been displayed for the first time.
938
+ *
939
+ * See {@link #isValid}.
940
+ */ resetFormStatus() {
808
941
  this.urlInputView.errorText = null;
809
942
  this.urlInputView.infoText = this._urlInputViewInfoDefault;
810
943
  }
811
944
  /**
812
- * Creates a labeled input view.
813
- *
814
- * @returns Labeled input view instance.
815
- */ _createUrlInput() {
945
+ * Creates a labeled input view.
946
+ *
947
+ * @returns Labeled input view instance.
948
+ */ _createUrlInput() {
816
949
  const t = this.locale.t;
817
950
  const labeledInput = new LabeledFieldView(this.locale, createLabeledInputText);
818
951
  const inputField = labeledInput.fieldView;
@@ -828,14 +961,14 @@ class MediaFormView extends View {
828
961
  return labeledInput;
829
962
  }
830
963
  /**
831
- * Creates a button view.
832
- *
833
- * @param label The button label.
834
- * @param icon The button icon.
835
- * @param className The additional button CSS class name.
836
- * @param eventName An event name that the `ButtonView#execute` event will be delegated to.
837
- * @returns The button view instance.
838
- */ _createButton(label, icon, className, eventName) {
964
+ * Creates a button view.
965
+ *
966
+ * @param label The button label.
967
+ * @param icon The button icon.
968
+ * @param className The additional button CSS class name.
969
+ * @param eventName An event name that the `ButtonView#execute` event will be delegated to.
970
+ * @returns The button view instance.
971
+ */ _createButton(label, icon, className, eventName) {
839
972
  const button = new ButtonView(this.locale);
840
973
  button.set({
841
974
  label,
@@ -852,69 +985,28 @@ class MediaFormView extends View {
852
985
  }
853
986
  return button;
854
987
  }
855
- /**
856
- * @param validators Form validators used by {@link #isValid}.
857
- * @param locale The localization services instance.
858
- */ constructor(validators, locale){
859
- super(locale);
860
- const t = locale.t;
861
- this.focusTracker = new FocusTracker();
862
- this.keystrokes = new KeystrokeHandler();
863
- this.set('mediaURLInputValue', '');
864
- this.urlInputView = this._createUrlInput();
865
- this.saveButtonView = this._createButton(t('Save'), icons.check, 'ck-button-save');
866
- this.saveButtonView.type = 'submit';
867
- this.cancelButtonView = this._createButton(t('Cancel'), icons.cancel, 'ck-button-cancel', 'cancel');
868
- this._focusables = new ViewCollection();
869
- this._focusCycler = new FocusCycler({
870
- focusables: this._focusables,
871
- focusTracker: this.focusTracker,
872
- keystrokeHandler: this.keystrokes,
873
- actions: {
874
- // Navigate form fields backwards using the <kbd>Shift</kbd> + <kbd>Tab</kbd> keystroke.
875
- focusPrevious: 'shift + tab',
876
- // Navigate form fields forwards using the <kbd>Tab</kbd> key.
877
- focusNext: 'tab'
878
- }
879
- });
880
- this._validators = validators;
881
- this.setTemplate({
882
- tag: 'form',
883
- attributes: {
884
- class: [
885
- 'ck',
886
- 'ck-media-form',
887
- 'ck-responsive-form'
888
- ],
889
- tabindex: '-1'
890
- },
891
- children: [
892
- this.urlInputView,
893
- this.saveButtonView,
894
- this.cancelButtonView
895
- ]
896
- });
897
- }
898
988
  }
899
989
 
900
990
  var mediaIcon = "<svg viewBox=\"0 0 22 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M1.587 1.5c-.612 0-.601-.029-.601.551v14.84c0 .59-.01.559.591.559h18.846c.602 0 .591.03.591-.56V2.052c0-.58.01-.55-.591-.55H1.587Zm.701.971h1.003v1H2.288v-1Zm16.448 0h1.003v1h-1.003v-1Zm-14.24 1h13.008v12H4.467l.029-12Zm-2.208 1h1.003v1H2.288v-1Zm16.448 0h1.003v1h-1.003v-1Zm-16.448 2h1.003v1H2.288v-1Zm16.448 0h1.003v1h-1.003v-1Zm-16.448 2h1.003v1H2.288v-1Zm16.448 0h1.003v1h-1.003v-1Zm-16.448 2h1.003v1H2.288v-1Zm16.448 0h1.003v1h-1.003v-1Zm-16.448 2h1.003l-.029 1h-.974v-1Zm16.448 0h1.003v1h-1.003v-1Zm-16.448 2h.974v1h-.974v-1Zm16.448 0h1.003v1h-1.003v-1Z\"/><path d=\"M8.374 6.648a.399.399 0 0 1 .395-.4.402.402 0 0 1 .2.049l5.148 2.824a.4.4 0 0 1 0 .7l-5.148 2.824a.403.403 0 0 1-.595-.35V6.648Z\"/></svg>";
901
991
 
902
- class MediaEmbedUI extends Plugin {
992
+ /**
993
+ * The media embed UI plugin.
994
+ */ class MediaEmbedUI extends Plugin {
903
995
  /**
904
- * @inheritDoc
905
- */ static get requires() {
996
+ * @inheritDoc
997
+ */ static get requires() {
906
998
  return [
907
999
  MediaEmbedEditing
908
1000
  ];
909
1001
  }
910
1002
  /**
911
- * @inheritDoc
912
- */ static get pluginName() {
1003
+ * @inheritDoc
1004
+ */ static get pluginName() {
913
1005
  return 'MediaEmbedUI';
914
1006
  }
915
1007
  /**
916
- * @inheritDoc
917
- */ init() {
1008
+ * @inheritDoc
1009
+ */ init() {
918
1010
  const editor = this.editor;
919
1011
  const command = editor.commands.get('mediaEmbed');
920
1012
  editor.ui.componentFactory.add('mediaEmbed', (locale)=>{
@@ -989,10 +1081,20 @@ function getFormValidators(t, registry) {
989
1081
  ];
990
1082
  }
991
1083
 
992
- class MediaEmbed extends Plugin {
1084
+ /**
1085
+ * The media embed plugin.
1086
+ *
1087
+ * For a detailed overview, check the {@glink features/media-embed Media Embed feature documentation}.
1088
+ *
1089
+ * This is a "glue" plugin which loads the following plugins:
1090
+ *
1091
+ * * The {@link module:media-embed/mediaembedediting~MediaEmbedEditing media embed editing feature},
1092
+ * * The {@link module:media-embed/mediaembedui~MediaEmbedUI media embed UI feature} and
1093
+ * * The {@link module:media-embed/automediaembed~AutoMediaEmbed auto-media embed feature}.
1094
+ */ class MediaEmbed extends Plugin {
993
1095
  /**
994
- * @inheritDoc
995
- */ static get requires() {
1096
+ * @inheritDoc
1097
+ */ static get requires() {
996
1098
  return [
997
1099
  MediaEmbedEditing,
998
1100
  MediaEmbedUI,
@@ -1001,28 +1103,33 @@ class MediaEmbed extends Plugin {
1001
1103
  ];
1002
1104
  }
1003
1105
  /**
1004
- * @inheritDoc
1005
- */ static get pluginName() {
1106
+ * @inheritDoc
1107
+ */ static get pluginName() {
1006
1108
  return 'MediaEmbed';
1007
1109
  }
1008
1110
  }
1009
1111
 
1010
- class MediaEmbedToolbar extends Plugin {
1112
+ /**
1113
+ * The media embed toolbar plugin. It creates a toolbar for media embed that shows up when the media element is selected.
1114
+ *
1115
+ * Instances of toolbar components (e.g. buttons) are created based on the
1116
+ * {@link module:media-embed/mediaembedconfig~MediaEmbedConfig#toolbar `media.toolbar` configuration option}.
1117
+ */ class MediaEmbedToolbar extends Plugin {
1011
1118
  /**
1012
- * @inheritDoc
1013
- */ static get requires() {
1119
+ * @inheritDoc
1120
+ */ static get requires() {
1014
1121
  return [
1015
1122
  WidgetToolbarRepository
1016
1123
  ];
1017
1124
  }
1018
1125
  /**
1019
- * @inheritDoc
1020
- */ static get pluginName() {
1126
+ * @inheritDoc
1127
+ */ static get pluginName() {
1021
1128
  return 'MediaEmbedToolbar';
1022
1129
  }
1023
1130
  /**
1024
- * @inheritDoc
1025
- */ afterInit() {
1131
+ * @inheritDoc
1132
+ */ afterInit() {
1026
1133
  const editor = this.editor;
1027
1134
  const t = editor.t;
1028
1135
  const widgetToolbarRepository = editor.plugins.get(WidgetToolbarRepository);