@ckeditor/ckeditor5-media-embed 0.0.0-nightly-20240604.1 → 0.0.0-nightly-20240605.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/README.md +0 -6
- package/build/media-embed.js +1 -1
- package/dist/index-editor.css +0 -5
- package/dist/index.css +0 -6
- package/dist/index.css.map +1 -1
- package/dist/index.js +379 -436
- package/dist/index.js.map +1 -1
- package/dist/types/mediaembedui.d.ts +2 -9
- package/dist/types/ui/mediaformview.d.ts +28 -2
- package/lang/contexts.json +0 -1
- package/package.json +3 -3
- package/src/mediaembedui.d.ts +2 -9
- package/src/mediaembedui.js +50 -73
- package/src/ui/mediaformview.d.ts +28 -2
- package/src/ui/mediaformview.js +69 -8
- package/theme/mediaform.css +0 -6
package/dist/index.js
CHANGED
@@ -2,10 +2,10 @@
|
|
2
2
|
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
4
4
|
*/
|
5
|
-
import { Command, Plugin } from '@ckeditor/ckeditor5-core/dist/index.js';
|
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 {
|
8
|
-
import { IconView, Template, View, submitHandler, LabeledFieldView, createLabeledInputText,
|
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';
|
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,8 +15,6 @@ 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
|
-
*/ /**
|
20
18
|
* Returns a function that converts the model "url" attribute to the view representation.
|
21
19
|
*
|
22
20
|
* Depending on the configuration, the view representation can be "semantic" (for the data pipeline):
|
@@ -147,20 +145,10 @@ import { Undo } from '@ckeditor/ckeditor5-undo/dist/index.js';
|
|
147
145
|
});
|
148
146
|
}
|
149
147
|
|
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 {
|
148
|
+
class MediaEmbedCommand extends Command {
|
161
149
|
/**
|
162
|
-
|
163
|
-
|
150
|
+
* @inheritDoc
|
151
|
+
*/ refresh() {
|
164
152
|
const model = this.editor.model;
|
165
153
|
const selection = model.document.selection;
|
166
154
|
const selectedMedia = getSelectedMediaModelWidget(selection);
|
@@ -168,14 +156,14 @@ import { Undo } from '@ckeditor/ckeditor5-undo/dist/index.js';
|
|
168
156
|
this.isEnabled = isMediaSelected(selection) || isAllowedInParent(selection, model);
|
169
157
|
}
|
170
158
|
/**
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
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) {
|
179
167
|
const model = this.editor.model;
|
180
168
|
const selection = model.document.selection;
|
181
169
|
const selectedMedia = getSelectedMediaModelWidget(selection);
|
@@ -209,72 +197,31 @@ import { Undo } from '@ckeditor/ckeditor5-undo/dist/index.js';
|
|
209
197
|
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>";
|
210
198
|
|
211
199
|
const mediaPlaceholderIconViewBox = '0 0 64 42';
|
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;
|
200
|
+
class MediaRegistry {
|
226
201
|
/**
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
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
|
-
}
|
254
|
-
/**
|
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) {
|
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) {
|
259
206
|
return !!this._getMedia(url);
|
260
207
|
}
|
261
208
|
/**
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
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) {
|
270
217
|
return this._getMedia(url).getViewElement(writer, options);
|
271
218
|
}
|
272
219
|
/**
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
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) {
|
278
225
|
if (!url) {
|
279
226
|
return new Media(this.locale);
|
280
227
|
}
|
@@ -292,11 +239,11 @@ const mediaPlaceholderIconViewBox = '0 0 64 42';
|
|
292
239
|
return null;
|
293
240
|
}
|
294
241
|
/**
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
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) {
|
300
247
|
// 1. Try to match without stripping the protocol and "www" subdomain.
|
301
248
|
let match = url.match(pattern);
|
302
249
|
if (match) {
|
@@ -316,6 +263,34 @@ const mediaPlaceholderIconViewBox = '0 0 64 42';
|
|
316
263
|
}
|
317
264
|
return null;
|
318
265
|
}
|
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
|
+
}
|
319
294
|
}
|
320
295
|
/**
|
321
296
|
* Represents media defined by the provider configuration.
|
@@ -323,30 +298,10 @@ const mediaPlaceholderIconViewBox = '0 0 64 42';
|
|
323
298
|
* It can be rendered to the {@link module:engine/view/element~Element view element} and used in the editing or data pipeline.
|
324
299
|
*/ class Media {
|
325
300
|
/**
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
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) {
|
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) {
|
350
305
|
const attributes = {};
|
351
306
|
let viewElement;
|
352
307
|
if (options.renderForEditingView || options.renderMediaPreview && this.url && this._previewRenderer) {
|
@@ -370,8 +325,8 @@ const mediaPlaceholderIconViewBox = '0 0 64 42';
|
|
370
325
|
return viewElement;
|
371
326
|
}
|
372
327
|
/**
|
373
|
-
|
374
|
-
|
328
|
+
* Returns the HTML string of the media content preview.
|
329
|
+
*/ _getPreviewHtml(options) {
|
375
330
|
if (this._previewRenderer) {
|
376
331
|
return this._previewRenderer(this._match);
|
377
332
|
} else {
|
@@ -384,8 +339,8 @@ const mediaPlaceholderIconViewBox = '0 0 64 42';
|
|
384
339
|
}
|
385
340
|
}
|
386
341
|
/**
|
387
|
-
|
388
|
-
|
342
|
+
* Returns the placeholder HTML when the media has no content preview.
|
343
|
+
*/ _getPlaceholderHtml() {
|
389
344
|
const icon = new IconView();
|
390
345
|
const t = this._locale.t;
|
391
346
|
icon.content = mediaPlaceholderIcon;
|
@@ -431,10 +386,10 @@ const mediaPlaceholderIconViewBox = '0 0 64 42';
|
|
431
386
|
return placeholder.outerHTML;
|
432
387
|
}
|
433
388
|
/**
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
389
|
+
* Returns the full URL to the specified media.
|
390
|
+
*
|
391
|
+
* @param url The URL of the media.
|
392
|
+
*/ _getValidUrl(url) {
|
438
393
|
if (!url) {
|
439
394
|
return null;
|
440
395
|
}
|
@@ -443,111 +398,23 @@ const mediaPlaceholderIconViewBox = '0 0 64 42';
|
|
443
398
|
}
|
444
399
|
return 'https://' + url;
|
445
400
|
}
|
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
|
+
}
|
446
407
|
}
|
447
408
|
|
448
|
-
|
449
|
-
* The media embed editing feature.
|
450
|
-
*/ class MediaEmbedEditing extends Plugin {
|
409
|
+
class MediaEmbedEditing extends Plugin {
|
451
410
|
/**
|
452
|
-
|
453
|
-
|
411
|
+
* @inheritDoc
|
412
|
+
*/ static get pluginName() {
|
454
413
|
return 'MediaEmbedEditing';
|
455
414
|
}
|
456
415
|
/**
|
457
|
-
|
458
|
-
|
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() {
|
416
|
+
* @inheritDoc
|
417
|
+
*/ init() {
|
551
418
|
const editor = this.editor;
|
552
419
|
const schema = editor.model.schema;
|
553
420
|
const t = editor.t;
|
@@ -655,16 +522,102 @@ const mediaPlaceholderIconViewBox = '0 0 64 42';
|
|
655
522
|
dispatcher.on('element:figure', converter);
|
656
523
|
});
|
657
524
|
}
|
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
|
+
}
|
658
614
|
}
|
659
615
|
|
660
616
|
const URL_REGEXP = /^(?:http(s)?:\/\/)?[\w-]+\.[\w-.~:/?#[\]@!$&'()*+,;=%]+$/;
|
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 {
|
617
|
+
class AutoMediaEmbed extends Plugin {
|
665
618
|
/**
|
666
|
-
|
667
|
-
|
619
|
+
* @inheritDoc
|
620
|
+
*/ static get requires() {
|
668
621
|
return [
|
669
622
|
Clipboard,
|
670
623
|
Delete,
|
@@ -672,28 +625,13 @@ const URL_REGEXP = /^(?:http(s)?:\/\/)?[\w-]+\.[\w-.~:/?#[\]@!$&'()*+,;=%]+$/;
|
|
672
625
|
];
|
673
626
|
}
|
674
627
|
/**
|
675
|
-
|
676
|
-
|
628
|
+
* @inheritDoc
|
629
|
+
*/ static get pluginName() {
|
677
630
|
return 'AutoMediaEmbed';
|
678
631
|
}
|
679
632
|
/**
|
680
|
-
|
681
|
-
|
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() {
|
633
|
+
* @inheritDoc
|
634
|
+
*/ init() {
|
697
635
|
const editor = this.editor;
|
698
636
|
const modelDocument = editor.model.document;
|
699
637
|
// We need to listen on `Clipboard#inputTransformation` because we need to save positions of selection.
|
@@ -727,12 +665,12 @@ const URL_REGEXP = /^(?:http(s)?:\/\/)?[\w-]+\.[\w-.~:/?#[\]@!$&'()*+,;=%]+$/;
|
|
727
665
|
});
|
728
666
|
}
|
729
667
|
/**
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
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) {
|
736
674
|
const editor = this.editor;
|
737
675
|
const mediaRegistry = editor.plugins.get(MediaEmbedEditing).registry;
|
738
676
|
// TODO: Use marker instead of LiveRange & LivePositions.
|
@@ -784,95 +722,71 @@ const URL_REGEXP = /^(?:http(s)?:\/\/)?[\w-]+\.[\w-.~:/?#[\]@!$&'()*+,;=%]+$/;
|
|
784
722
|
editor.plugins.get(Delete).requestUndoOnBackspace();
|
785
723
|
}, 100);
|
786
724
|
}
|
787
|
-
}
|
788
|
-
|
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
725
|
/**
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
/**
|
810
|
-
* The info text with an additional tip for the {@link #urlInputView},
|
811
|
-
* displayed when the input has some value.
|
812
|
-
*/ _urlInputViewInfoTip;
|
813
|
-
/**
|
814
|
-
* @param validators Form validators used by {@link #isValid}.
|
815
|
-
* @param locale The localization services instance.
|
816
|
-
*/ constructor(validators, locale){
|
817
|
-
super(locale);
|
818
|
-
this.focusTracker = new FocusTracker();
|
819
|
-
this.keystrokes = new KeystrokeHandler();
|
820
|
-
this.set('mediaURLInputValue', '');
|
821
|
-
this.urlInputView = this._createUrlInput();
|
822
|
-
this._validators = validators;
|
823
|
-
this.setTemplate({
|
824
|
-
tag: 'form',
|
825
|
-
attributes: {
|
826
|
-
class: [
|
827
|
-
'ck',
|
828
|
-
'ck-media-form',
|
829
|
-
'ck-responsive-form'
|
830
|
-
],
|
831
|
-
tabindex: '-1'
|
832
|
-
},
|
833
|
-
children: [
|
834
|
-
this.urlInputView
|
835
|
-
]
|
836
|
-
});
|
726
|
+
* @inheritDoc
|
727
|
+
*/ constructor(editor){
|
728
|
+
super(editor);
|
729
|
+
this._timeoutId = null;
|
730
|
+
this._positionToInsert = null;
|
837
731
|
}
|
732
|
+
}
|
733
|
+
|
734
|
+
class MediaFormView extends View {
|
838
735
|
/**
|
839
|
-
|
840
|
-
|
736
|
+
* @inheritDoc
|
737
|
+
*/ render() {
|
841
738
|
super.render();
|
842
739
|
submitHandler({
|
843
740
|
view: this
|
844
741
|
});
|
845
|
-
|
846
|
-
|
742
|
+
const childViews = [
|
743
|
+
this.urlInputView,
|
744
|
+
this.saveButtonView,
|
745
|
+
this.cancelButtonView
|
746
|
+
];
|
747
|
+
childViews.forEach((v)=>{
|
748
|
+
// Register the view as focusable.
|
749
|
+
this._focusables.add(v);
|
750
|
+
// Register the view in the focus tracker.
|
751
|
+
this.focusTracker.add(v.element);
|
752
|
+
});
|
847
753
|
// Start listening for the keystrokes coming from #element.
|
848
754
|
this.keystrokes.listenTo(this.element);
|
755
|
+
const stopPropagation = (data)=>data.stopPropagation();
|
756
|
+
// Since the form is in the dropdown panel which is a child of the toolbar, the toolbar's
|
757
|
+
// keystroke handler would take over the key management in the URL input. We need to prevent
|
758
|
+
// this ASAP. Otherwise, the basic caret movement using the arrow keys will be impossible.
|
759
|
+
this.keystrokes.set('arrowright', stopPropagation);
|
760
|
+
this.keystrokes.set('arrowleft', stopPropagation);
|
761
|
+
this.keystrokes.set('arrowup', stopPropagation);
|
762
|
+
this.keystrokes.set('arrowdown', stopPropagation);
|
849
763
|
}
|
850
764
|
/**
|
851
|
-
|
852
|
-
|
765
|
+
* @inheritDoc
|
766
|
+
*/ destroy() {
|
853
767
|
super.destroy();
|
854
768
|
this.focusTracker.destroy();
|
855
769
|
this.keystrokes.destroy();
|
856
770
|
}
|
857
771
|
/**
|
858
|
-
|
859
|
-
|
860
|
-
this.
|
772
|
+
* Focuses the fist {@link #_focusables} in the form.
|
773
|
+
*/ focus() {
|
774
|
+
this._focusCycler.focusFirst();
|
861
775
|
}
|
862
776
|
/**
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
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() {
|
868
782
|
return this.urlInputView.fieldView.element.value.trim();
|
869
783
|
}
|
870
784
|
set url(url) {
|
871
|
-
this.urlInputView.fieldView.value = url.trim();
|
785
|
+
this.urlInputView.fieldView.element.value = url.trim();
|
872
786
|
}
|
873
787
|
/**
|
874
|
-
|
875
|
-
|
788
|
+
* Validates the form and returns `false` when some fields are invalid.
|
789
|
+
*/ isValid() {
|
876
790
|
this.resetFormStatus();
|
877
791
|
for (const validator of this._validators){
|
878
792
|
const errorText = validator(this);
|
@@ -886,19 +800,19 @@ const URL_REGEXP = /^(?:http(s)?:\/\/)?[\w-]+\.[\w-.~:/?#[\]@!$&'()*+,;=%]+$/;
|
|
886
800
|
return true;
|
887
801
|
}
|
888
802
|
/**
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
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() {
|
894
808
|
this.urlInputView.errorText = null;
|
895
809
|
this.urlInputView.infoText = this._urlInputViewInfoDefault;
|
896
810
|
}
|
897
811
|
/**
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
812
|
+
* Creates a labeled input view.
|
813
|
+
*
|
814
|
+
* @returns Labeled input view instance.
|
815
|
+
*/ _createUrlInput() {
|
902
816
|
const t = this.locale.t;
|
903
817
|
const labeledInput = new LabeledFieldView(this.locale, createLabeledInputText);
|
904
818
|
const inputField = labeledInput.fieldView;
|
@@ -906,7 +820,6 @@ const URL_REGEXP = /^(?:http(s)?:\/\/)?[\w-]+\.[\w-.~:/?#[\]@!$&'()*+,;=%]+$/;
|
|
906
820
|
this._urlInputViewInfoTip = t('Tip: Paste the URL into the content to embed faster.');
|
907
821
|
labeledInput.label = t('Media URL');
|
908
822
|
labeledInput.infoText = this._urlInputViewInfoDefault;
|
909
|
-
inputField.inputMode = 'url';
|
910
823
|
inputField.on('input', ()=>{
|
911
824
|
// Display the tip text only when there is some value. Otherwise fall back to the default info text.
|
912
825
|
labeledInput.infoText = inputField.element.value ? this._urlInputViewInfoTip : this._urlInputViewInfoDefault;
|
@@ -914,106 +827,151 @@ const URL_REGEXP = /^(?:http(s)?:\/\/)?[\w-]+\.[\w-.~:/?#[\]@!$&'()*+,;=%]+$/;
|
|
914
827
|
});
|
915
828
|
return labeledInput;
|
916
829
|
}
|
830
|
+
/**
|
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) {
|
839
|
+
const button = new ButtonView(this.locale);
|
840
|
+
button.set({
|
841
|
+
label,
|
842
|
+
icon,
|
843
|
+
tooltip: true
|
844
|
+
});
|
845
|
+
button.extendTemplate({
|
846
|
+
attributes: {
|
847
|
+
class: className
|
848
|
+
}
|
849
|
+
});
|
850
|
+
if (eventName) {
|
851
|
+
button.delegate('execute').to(this, eventName);
|
852
|
+
}
|
853
|
+
return button;
|
854
|
+
}
|
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
|
+
}
|
917
898
|
}
|
918
899
|
|
919
900
|
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>";
|
920
901
|
|
921
|
-
|
922
|
-
* The media embed UI plugin.
|
923
|
-
*/ class MediaEmbedUI extends Plugin {
|
902
|
+
class MediaEmbedUI extends Plugin {
|
924
903
|
/**
|
925
|
-
|
926
|
-
|
904
|
+
* @inheritDoc
|
905
|
+
*/ static get requires() {
|
927
906
|
return [
|
928
|
-
MediaEmbedEditing
|
929
|
-
Dialog
|
907
|
+
MediaEmbedEditing
|
930
908
|
];
|
931
909
|
}
|
932
910
|
/**
|
933
|
-
|
934
|
-
|
911
|
+
* @inheritDoc
|
912
|
+
*/ static get pluginName() {
|
935
913
|
return 'MediaEmbedUI';
|
936
914
|
}
|
937
|
-
_formView;
|
938
|
-
/**
|
939
|
-
* @inheritDoc
|
940
|
-
*/ init() {
|
941
|
-
const editor = this.editor;
|
942
|
-
editor.ui.componentFactory.add('mediaEmbed', ()=>{
|
943
|
-
const t = this.editor.locale.t;
|
944
|
-
const button = this._createDialogButton(ButtonView);
|
945
|
-
button.tooltip = true;
|
946
|
-
button.label = t('Insert media');
|
947
|
-
return button;
|
948
|
-
});
|
949
|
-
editor.ui.componentFactory.add('menuBar:mediaEmbed', ()=>{
|
950
|
-
const t = this.editor.locale.t;
|
951
|
-
const button = this._createDialogButton(MenuBarMenuListItemButtonView);
|
952
|
-
button.label = t('Media');
|
953
|
-
return button;
|
954
|
-
});
|
955
|
-
}
|
956
915
|
/**
|
957
|
-
|
958
|
-
|
916
|
+
* @inheritDoc
|
917
|
+
*/ init() {
|
959
918
|
const editor = this.editor;
|
960
|
-
const buttonView = new ButtonClass(editor.locale);
|
961
919
|
const command = editor.commands.get('mediaEmbed');
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
if (dialogPlugin.id === 'mediaEmbed') {
|
967
|
-
dialogPlugin.hide();
|
968
|
-
} else {
|
969
|
-
this._showDialog();
|
970
|
-
}
|
920
|
+
editor.ui.componentFactory.add('mediaEmbed', (locale)=>{
|
921
|
+
const dropdown = createDropdown(locale);
|
922
|
+
this._setUpDropdown(dropdown, command);
|
923
|
+
return dropdown;
|
971
924
|
});
|
972
|
-
return buttonView;
|
973
925
|
}
|
974
|
-
|
926
|
+
_setUpDropdown(dropdown, command) {
|
975
927
|
const editor = this.editor;
|
976
|
-
const
|
977
|
-
const
|
978
|
-
const
|
979
|
-
|
980
|
-
const
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
class: 'ck-button-action',
|
1003
|
-
withText: true,
|
1004
|
-
onExecute: ()=>this._handleSubmitForm()
|
928
|
+
const t = editor.t;
|
929
|
+
const button = dropdown.buttonView;
|
930
|
+
const registry = editor.plugins.get(MediaEmbedEditing).registry;
|
931
|
+
dropdown.once('change:isOpen', ()=>{
|
932
|
+
const form = new (CssTransitionDisablerMixin(MediaFormView))(getFormValidators(editor.t, registry), editor.locale);
|
933
|
+
dropdown.panelView.children.add(form);
|
934
|
+
// Note: Use the low priority to make sure the following listener starts working after the
|
935
|
+
// default action of the drop-down is executed (i.e. the panel showed up). Otherwise, the
|
936
|
+
// invisible form/input cannot be focused/selected.
|
937
|
+
button.on('open', ()=>{
|
938
|
+
form.disableCssTransitions();
|
939
|
+
// Make sure that each time the panel shows up, the URL field remains in sync with the value of
|
940
|
+
// the command. If the user typed in the input, then canceled (`urlInputView#fieldView#value` stays
|
941
|
+
// unaltered) and re-opened it without changing the value of the media command (e.g. because they
|
942
|
+
// didn't change the selection), they would see the old value instead of the actual value of the
|
943
|
+
// command.
|
944
|
+
form.url = command.value || '';
|
945
|
+
form.urlInputView.fieldView.select();
|
946
|
+
form.enableCssTransitions();
|
947
|
+
}, {
|
948
|
+
priority: 'low'
|
949
|
+
});
|
950
|
+
dropdown.on('submit', ()=>{
|
951
|
+
if (form.isValid()) {
|
952
|
+
editor.execute('mediaEmbed', form.url);
|
953
|
+
editor.editing.view.focus();
|
1005
954
|
}
|
1006
|
-
|
955
|
+
});
|
956
|
+
dropdown.on('change:isOpen', ()=>form.resetFormStatus());
|
957
|
+
dropdown.on('cancel', ()=>{
|
958
|
+
editor.editing.view.focus();
|
959
|
+
});
|
960
|
+
form.delegate('submit', 'cancel').to(dropdown);
|
961
|
+
form.urlInputView.fieldView.bind('value').to(command, 'value');
|
962
|
+
// Update balloon position when form error changes.
|
963
|
+
form.urlInputView.on('change:errorText', ()=>{
|
964
|
+
editor.ui.update();
|
965
|
+
});
|
966
|
+
// Form elements should be read-only when corresponding commands are disabled.
|
967
|
+
form.urlInputView.bind('isEnabled').to(command, 'isEnabled');
|
968
|
+
});
|
969
|
+
dropdown.bind('isEnabled').to(command);
|
970
|
+
button.set({
|
971
|
+
label: t('Insert media'),
|
972
|
+
icon: mediaIcon,
|
973
|
+
tooltip: true
|
1007
974
|
});
|
1008
|
-
}
|
1009
|
-
_handleSubmitForm() {
|
1010
|
-
const editor = this.editor;
|
1011
|
-
const dialog = editor.plugins.get('Dialog');
|
1012
|
-
if (this._formView.isValid()) {
|
1013
|
-
editor.execute('mediaEmbed', this._formView.url);
|
1014
|
-
dialog.hide();
|
1015
|
-
editor.editing.view.focus();
|
1016
|
-
}
|
1017
975
|
}
|
1018
976
|
}
|
1019
977
|
function getFormValidators(t, registry) {
|
@@ -1031,20 +989,10 @@ function getFormValidators(t, registry) {
|
|
1031
989
|
];
|
1032
990
|
}
|
1033
991
|
|
1034
|
-
|
1035
|
-
* The media embed plugin.
|
1036
|
-
*
|
1037
|
-
* For a detailed overview, check the {@glink features/media-embed Media Embed feature documentation}.
|
1038
|
-
*
|
1039
|
-
* This is a "glue" plugin which loads the following plugins:
|
1040
|
-
*
|
1041
|
-
* * The {@link module:media-embed/mediaembedediting~MediaEmbedEditing media embed editing feature},
|
1042
|
-
* * The {@link module:media-embed/mediaembedui~MediaEmbedUI media embed UI feature} and
|
1043
|
-
* * The {@link module:media-embed/automediaembed~AutoMediaEmbed auto-media embed feature}.
|
1044
|
-
*/ class MediaEmbed extends Plugin {
|
992
|
+
class MediaEmbed extends Plugin {
|
1045
993
|
/**
|
1046
|
-
|
1047
|
-
|
994
|
+
* @inheritDoc
|
995
|
+
*/ static get requires() {
|
1048
996
|
return [
|
1049
997
|
MediaEmbedEditing,
|
1050
998
|
MediaEmbedUI,
|
@@ -1053,33 +1001,28 @@ function getFormValidators(t, registry) {
|
|
1053
1001
|
];
|
1054
1002
|
}
|
1055
1003
|
/**
|
1056
|
-
|
1057
|
-
|
1004
|
+
* @inheritDoc
|
1005
|
+
*/ static get pluginName() {
|
1058
1006
|
return 'MediaEmbed';
|
1059
1007
|
}
|
1060
1008
|
}
|
1061
1009
|
|
1062
|
-
|
1063
|
-
* The media embed toolbar plugin. It creates a toolbar for media embed that shows up when the media element is selected.
|
1064
|
-
*
|
1065
|
-
* Instances of toolbar components (e.g. buttons) are created based on the
|
1066
|
-
* {@link module:media-embed/mediaembedconfig~MediaEmbedConfig#toolbar `media.toolbar` configuration option}.
|
1067
|
-
*/ class MediaEmbedToolbar extends Plugin {
|
1010
|
+
class MediaEmbedToolbar extends Plugin {
|
1068
1011
|
/**
|
1069
|
-
|
1070
|
-
|
1012
|
+
* @inheritDoc
|
1013
|
+
*/ static get requires() {
|
1071
1014
|
return [
|
1072
1015
|
WidgetToolbarRepository
|
1073
1016
|
];
|
1074
1017
|
}
|
1075
1018
|
/**
|
1076
|
-
|
1077
|
-
|
1019
|
+
* @inheritDoc
|
1020
|
+
*/ static get pluginName() {
|
1078
1021
|
return 'MediaEmbedToolbar';
|
1079
1022
|
}
|
1080
1023
|
/**
|
1081
|
-
|
1082
|
-
|
1024
|
+
* @inheritDoc
|
1025
|
+
*/ afterInit() {
|
1083
1026
|
const editor = this.editor;
|
1084
1027
|
const t = editor.t;
|
1085
1028
|
const widgetToolbarRepository = editor.plugins.get(WidgetToolbarRepository);
|