@ckeditor/ckeditor5-media-embed 41.4.1 → 42.0.0-alpha.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.
- package/README.md +6 -0
- package/build/media-embed.js +2 -2
- package/build/translations/ar.js +1 -1
- package/build/translations/az.js +1 -1
- package/build/translations/bg.js +1 -1
- package/build/translations/bn.js +1 -1
- package/build/translations/ca.js +1 -1
- package/build/translations/cs.js +1 -1
- package/build/translations/da.js +1 -1
- package/build/translations/de-ch.js +1 -1
- package/build/translations/de.js +1 -1
- package/build/translations/el.js +1 -1
- package/build/translations/en-au.js +1 -1
- package/build/translations/en-gb.js +1 -1
- package/build/translations/es.js +1 -1
- package/build/translations/et.js +1 -1
- package/build/translations/fa.js +1 -1
- package/build/translations/fi.js +1 -1
- package/build/translations/fr.js +1 -1
- package/build/translations/gl.js +1 -1
- package/build/translations/he.js +1 -1
- package/build/translations/hi.js +1 -1
- package/build/translations/hr.js +1 -1
- package/build/translations/hu.js +1 -1
- package/build/translations/id.js +1 -1
- package/build/translations/it.js +1 -1
- package/build/translations/ja.js +1 -1
- package/build/translations/ko.js +1 -1
- package/build/translations/ku.js +1 -1
- package/build/translations/lt.js +1 -1
- package/build/translations/lv.js +1 -1
- package/build/translations/ms.js +1 -1
- package/build/translations/ne.js +1 -1
- package/build/translations/nl.js +1 -1
- package/build/translations/no.js +1 -1
- package/build/translations/pl.js +1 -1
- package/build/translations/pt-br.js +1 -1
- package/build/translations/pt.js +1 -1
- package/build/translations/ro.js +1 -1
- package/build/translations/ru.js +1 -1
- package/build/translations/sk.js +1 -1
- package/build/translations/sq.js +1 -1
- package/build/translations/sr-latn.js +1 -1
- package/build/translations/sr.js +1 -1
- package/build/translations/sv.js +1 -1
- package/build/translations/th.js +1 -1
- package/build/translations/tk.js +1 -1
- package/build/translations/tr.js +1 -1
- package/build/translations/uk.js +1 -1
- package/build/translations/ur.js +1 -1
- package/build/translations/uz.js +1 -1
- package/build/translations/vi.js +1 -1
- package/build/translations/zh-cn.js +1 -1
- package/build/translations/zh.js +1 -1
- package/dist/index-editor.css +5 -0
- package/dist/index.css +6 -0
- package/dist/index.css.map +1 -1
- package/dist/index.js +436 -379
- package/dist/index.js.map +1 -1
- package/dist/translations/ar.js +1 -1
- package/dist/translations/ar.umd.js +1 -1
- package/dist/translations/az.js +1 -1
- package/dist/translations/az.umd.js +1 -1
- package/dist/translations/bg.js +1 -1
- package/dist/translations/bg.umd.js +1 -1
- package/dist/translations/bn.js +1 -1
- package/dist/translations/bn.umd.js +1 -1
- package/dist/translations/ca.js +1 -1
- package/dist/translations/ca.umd.js +1 -1
- package/dist/translations/cs.js +1 -1
- package/dist/translations/cs.umd.js +1 -1
- package/dist/translations/da.js +1 -1
- package/dist/translations/da.umd.js +1 -1
- package/dist/translations/de-ch.js +1 -1
- package/dist/translations/de-ch.umd.js +1 -1
- package/dist/translations/de.js +1 -1
- package/dist/translations/de.umd.js +1 -1
- package/dist/translations/el.js +1 -1
- package/dist/translations/el.umd.js +1 -1
- package/dist/translations/en-au.js +1 -1
- package/dist/translations/en-au.umd.js +1 -1
- package/dist/translations/en-gb.js +1 -1
- package/dist/translations/en-gb.umd.js +1 -1
- package/dist/translations/en.js +1 -1
- package/dist/translations/en.umd.js +1 -1
- package/dist/translations/es.js +1 -1
- package/dist/translations/es.umd.js +1 -1
- package/dist/translations/et.js +1 -1
- package/dist/translations/et.umd.js +1 -1
- package/dist/translations/fa.js +1 -1
- package/dist/translations/fa.umd.js +1 -1
- package/dist/translations/fi.js +1 -1
- package/dist/translations/fi.umd.js +1 -1
- package/dist/translations/fr.js +1 -1
- package/dist/translations/fr.umd.js +1 -1
- package/dist/translations/gl.js +1 -1
- package/dist/translations/gl.umd.js +1 -1
- package/dist/translations/he.js +1 -1
- package/dist/translations/he.umd.js +1 -1
- package/dist/translations/hi.js +1 -1
- package/dist/translations/hi.umd.js +1 -1
- package/dist/translations/hr.js +1 -1
- package/dist/translations/hr.umd.js +1 -1
- package/dist/translations/hu.js +1 -1
- package/dist/translations/hu.umd.js +1 -1
- package/dist/translations/id.js +1 -1
- package/dist/translations/id.umd.js +1 -1
- package/dist/translations/it.js +1 -1
- package/dist/translations/it.umd.js +1 -1
- package/dist/translations/ja.js +1 -1
- package/dist/translations/ja.umd.js +1 -1
- package/dist/translations/ko.js +1 -1
- package/dist/translations/ko.umd.js +1 -1
- package/dist/translations/ku.js +1 -1
- package/dist/translations/ku.umd.js +1 -1
- package/dist/translations/lt.js +1 -1
- package/dist/translations/lt.umd.js +1 -1
- package/dist/translations/lv.js +1 -1
- package/dist/translations/lv.umd.js +1 -1
- package/dist/translations/ms.js +1 -1
- package/dist/translations/ms.umd.js +1 -1
- package/dist/translations/ne.js +1 -1
- package/dist/translations/ne.umd.js +1 -1
- package/dist/translations/nl.js +1 -1
- package/dist/translations/nl.umd.js +1 -1
- package/dist/translations/no.js +1 -1
- package/dist/translations/no.umd.js +1 -1
- package/dist/translations/pl.js +1 -1
- package/dist/translations/pl.umd.js +1 -1
- package/dist/translations/pt-br.js +1 -1
- package/dist/translations/pt-br.umd.js +1 -1
- package/dist/translations/pt.js +1 -1
- package/dist/translations/pt.umd.js +1 -1
- package/dist/translations/ro.js +1 -1
- package/dist/translations/ro.umd.js +1 -1
- package/dist/translations/ru.js +1 -1
- package/dist/translations/ru.umd.js +1 -1
- package/dist/translations/sk.js +1 -1
- package/dist/translations/sk.umd.js +1 -1
- package/dist/translations/sq.js +1 -1
- package/dist/translations/sq.umd.js +1 -1
- package/dist/translations/sr-latn.js +1 -1
- package/dist/translations/sr-latn.umd.js +1 -1
- package/dist/translations/sr.js +1 -1
- package/dist/translations/sr.umd.js +1 -1
- package/dist/translations/sv.js +1 -1
- package/dist/translations/sv.umd.js +1 -1
- package/dist/translations/th.js +1 -1
- package/dist/translations/th.umd.js +1 -1
- package/dist/translations/tk.js +1 -1
- package/dist/translations/tk.umd.js +1 -1
- package/dist/translations/tr.js +1 -1
- package/dist/translations/tr.umd.js +1 -1
- package/dist/translations/uk.js +1 -1
- package/dist/translations/uk.umd.js +1 -1
- package/dist/translations/ur.js +1 -1
- package/dist/translations/ur.umd.js +1 -1
- package/dist/translations/uz.js +1 -1
- package/dist/translations/uz.umd.js +1 -1
- package/dist/translations/vi.js +1 -1
- package/dist/translations/vi.umd.js +1 -1
- package/dist/translations/zh-cn.js +1 -1
- package/dist/translations/zh-cn.umd.js +1 -1
- package/dist/translations/zh.js +1 -1
- package/dist/translations/zh.umd.js +1 -1
- package/dist/types/mediaembedui.d.ts +9 -2
- package/dist/types/ui/mediaformview.d.ts +2 -28
- package/lang/contexts.json +1 -0
- package/lang/translations/ar.po +4 -0
- package/lang/translations/az.po +4 -0
- package/lang/translations/bg.po +4 -0
- package/lang/translations/bn.po +4 -0
- package/lang/translations/ca.po +4 -0
- package/lang/translations/cs.po +4 -0
- package/lang/translations/da.po +4 -0
- package/lang/translations/de-ch.po +4 -0
- package/lang/translations/de.po +4 -0
- package/lang/translations/el.po +4 -0
- package/lang/translations/en-au.po +4 -0
- package/lang/translations/en-gb.po +4 -0
- package/lang/translations/en.po +4 -0
- package/lang/translations/es.po +4 -0
- package/lang/translations/et.po +4 -0
- package/lang/translations/fa.po +4 -0
- package/lang/translations/fi.po +4 -0
- package/lang/translations/fr.po +4 -0
- package/lang/translations/gl.po +4 -0
- package/lang/translations/he.po +4 -0
- package/lang/translations/hi.po +4 -0
- package/lang/translations/hr.po +4 -0
- package/lang/translations/hu.po +4 -0
- package/lang/translations/id.po +4 -0
- package/lang/translations/it.po +4 -0
- package/lang/translations/ja.po +4 -0
- package/lang/translations/ko.po +4 -0
- package/lang/translations/ku.po +4 -0
- package/lang/translations/lt.po +4 -0
- package/lang/translations/lv.po +4 -0
- package/lang/translations/ms.po +4 -0
- package/lang/translations/ne.po +4 -0
- package/lang/translations/nl.po +4 -0
- package/lang/translations/no.po +4 -0
- package/lang/translations/pl.po +4 -0
- package/lang/translations/pt-br.po +4 -0
- package/lang/translations/pt.po +4 -0
- package/lang/translations/ro.po +4 -0
- package/lang/translations/ru.po +4 -0
- package/lang/translations/sk.po +4 -0
- package/lang/translations/sq.po +4 -0
- package/lang/translations/sr-latn.po +4 -0
- package/lang/translations/sr.po +4 -0
- package/lang/translations/sv.po +4 -0
- package/lang/translations/th.po +4 -0
- package/lang/translations/tk.po +4 -0
- package/lang/translations/tr.po +4 -0
- package/lang/translations/uk.po +4 -0
- package/lang/translations/ur.po +4 -0
- package/lang/translations/uz.po +4 -0
- package/lang/translations/vi.po +4 -0
- package/lang/translations/zh-cn.po +4 -0
- package/lang/translations/zh.po +4 -0
- package/package.json +3 -3
- package/src/mediaembedui.d.ts +9 -2
- package/src/mediaembedui.js +73 -50
- package/src/ui/mediaformview.d.ts +2 -28
- package/src/ui/mediaformview.js +8 -69
- package/theme/mediaform.css +6 -0
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
|
5
|
+
import { Command, Plugin } 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 { logWarning, toArray, first, global, FocusTracker, KeystrokeHandler } from '@ckeditor/ckeditor5-utils/dist/index.js';
|
8
|
+
import { IconView, Template, View, submitHandler, LabeledFieldView, createLabeledInputText, Dialog, ButtonView, MenuBarMenuListItemButtonView, 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
|
-
|
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
|
-
|
151
|
-
|
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
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
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
|
-
|
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;
|
201
226
|
/**
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
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
|
+
}
|
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) {
|
206
259
|
return !!this._getMedia(url);
|
207
260
|
}
|
208
261
|
/**
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
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
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
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
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
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
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
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
|
-
|
329
|
-
|
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
|
-
|
343
|
-
|
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
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
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
|
-
|
448
|
+
/**
|
449
|
+
* The media embed editing feature.
|
450
|
+
*/ class MediaEmbedEditing extends Plugin {
|
410
451
|
/**
|
411
|
-
|
412
|
-
|
452
|
+
* @inheritDoc
|
453
|
+
*/ static get pluginName() {
|
413
454
|
return 'MediaEmbedEditing';
|
414
455
|
}
|
415
456
|
/**
|
416
|
-
|
417
|
-
|
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
|
-
|
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
|
-
|
620
|
-
|
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
|
-
|
629
|
-
|
675
|
+
* @inheritDoc
|
676
|
+
*/ static get pluginName() {
|
630
677
|
return 'AutoMediaEmbed';
|
631
678
|
}
|
632
679
|
/**
|
633
|
-
|
634
|
-
|
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
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
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,71 +784,95 @@ 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
|
-
|
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
|
+
* An array of form validators used by {@link #isValid}.
|
805
|
+
*/ _validators;
|
806
|
+
/**
|
807
|
+
* The default info text for the {@link #urlInputView}.
|
808
|
+
*/ _urlInputViewInfoDefault;
|
735
809
|
/**
|
736
|
-
|
737
|
-
|
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
|
+
});
|
837
|
+
}
|
838
|
+
/**
|
839
|
+
* @inheritDoc
|
840
|
+
*/ render() {
|
738
841
|
super.render();
|
739
842
|
submitHandler({
|
740
843
|
view: this
|
741
844
|
});
|
742
|
-
|
743
|
-
|
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
|
-
});
|
845
|
+
// Register the view in the focus tracker.
|
846
|
+
this.focusTracker.add(this.urlInputView.element);
|
753
847
|
// Start listening for the keystrokes coming from #element.
|
754
848
|
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);
|
763
849
|
}
|
764
850
|
/**
|
765
|
-
|
766
|
-
|
851
|
+
* @inheritDoc
|
852
|
+
*/ destroy() {
|
767
853
|
super.destroy();
|
768
854
|
this.focusTracker.destroy();
|
769
855
|
this.keystrokes.destroy();
|
770
856
|
}
|
771
857
|
/**
|
772
|
-
|
773
|
-
|
774
|
-
this.
|
858
|
+
* Focuses the {@link #urlInputView}.
|
859
|
+
*/ focus() {
|
860
|
+
this.urlInputView.focus();
|
775
861
|
}
|
776
862
|
/**
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
863
|
+
* The native DOM `value` of the {@link #urlInputView} element.
|
864
|
+
*
|
865
|
+
* **Note**: Do not confuse it with the {@link module:ui/inputtext/inputtextview~InputTextView#value}
|
866
|
+
* which works one way only and may not represent the actual state of the component in the DOM.
|
867
|
+
*/ get url() {
|
782
868
|
return this.urlInputView.fieldView.element.value.trim();
|
783
869
|
}
|
784
870
|
set url(url) {
|
785
|
-
this.urlInputView.fieldView.
|
871
|
+
this.urlInputView.fieldView.value = url.trim();
|
786
872
|
}
|
787
873
|
/**
|
788
|
-
|
789
|
-
|
874
|
+
* Validates the form and returns `false` when some fields are invalid.
|
875
|
+
*/ isValid() {
|
790
876
|
this.resetFormStatus();
|
791
877
|
for (const validator of this._validators){
|
792
878
|
const errorText = validator(this);
|
@@ -800,19 +886,19 @@ class MediaFormView extends View {
|
|
800
886
|
return true;
|
801
887
|
}
|
802
888
|
/**
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
889
|
+
* Cleans up the supplementary error and information text of the {@link #urlInputView}
|
890
|
+
* bringing them back to the state when the form has been displayed for the first time.
|
891
|
+
*
|
892
|
+
* See {@link #isValid}.
|
893
|
+
*/ resetFormStatus() {
|
808
894
|
this.urlInputView.errorText = null;
|
809
895
|
this.urlInputView.infoText = this._urlInputViewInfoDefault;
|
810
896
|
}
|
811
897
|
/**
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
898
|
+
* Creates a labeled input view.
|
899
|
+
*
|
900
|
+
* @returns Labeled input view instance.
|
901
|
+
*/ _createUrlInput() {
|
816
902
|
const t = this.locale.t;
|
817
903
|
const labeledInput = new LabeledFieldView(this.locale, createLabeledInputText);
|
818
904
|
const inputField = labeledInput.fieldView;
|
@@ -820,6 +906,7 @@ class MediaFormView extends View {
|
|
820
906
|
this._urlInputViewInfoTip = t('Tip: Paste the URL into the content to embed faster.');
|
821
907
|
labeledInput.label = t('Media URL');
|
822
908
|
labeledInput.infoText = this._urlInputViewInfoDefault;
|
909
|
+
inputField.inputMode = 'url';
|
823
910
|
inputField.on('input', ()=>{
|
824
911
|
// Display the tip text only when there is some value. Otherwise fall back to the default info text.
|
825
912
|
labeledInput.infoText = inputField.element.value ? this._urlInputViewInfoTip : this._urlInputViewInfoDefault;
|
@@ -827,152 +914,107 @@ class MediaFormView extends View {
|
|
827
914
|
});
|
828
915
|
return labeledInput;
|
829
916
|
}
|
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
|
-
}
|
898
917
|
}
|
899
918
|
|
900
919
|
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
920
|
|
902
|
-
|
921
|
+
/**
|
922
|
+
* The media embed UI plugin.
|
923
|
+
*/ class MediaEmbedUI extends Plugin {
|
903
924
|
/**
|
904
|
-
|
905
|
-
|
925
|
+
* @inheritDoc
|
926
|
+
*/ static get requires() {
|
906
927
|
return [
|
907
|
-
MediaEmbedEditing
|
928
|
+
MediaEmbedEditing,
|
929
|
+
Dialog
|
908
930
|
];
|
909
931
|
}
|
910
932
|
/**
|
911
|
-
|
912
|
-
|
933
|
+
* @inheritDoc
|
934
|
+
*/ static get pluginName() {
|
913
935
|
return 'MediaEmbedUI';
|
914
936
|
}
|
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
|
+
}
|
915
956
|
/**
|
916
|
-
|
917
|
-
|
957
|
+
* Creates a button for menu bar that will show media embed dialog.
|
958
|
+
*/ _createDialogButton(ButtonClass) {
|
918
959
|
const editor = this.editor;
|
960
|
+
const buttonView = new ButtonClass(editor.locale);
|
919
961
|
const command = editor.commands.get('mediaEmbed');
|
920
|
-
editor.
|
921
|
-
|
922
|
-
|
923
|
-
|
962
|
+
const dialogPlugin = this.editor.plugins.get('Dialog');
|
963
|
+
buttonView.icon = mediaIcon;
|
964
|
+
buttonView.bind('isEnabled').to(command, 'isEnabled');
|
965
|
+
buttonView.on('execute', ()=>{
|
966
|
+
if (dialogPlugin.id === 'mediaEmbed') {
|
967
|
+
dialogPlugin.hide();
|
968
|
+
} else {
|
969
|
+
this._showDialog();
|
970
|
+
}
|
924
971
|
});
|
972
|
+
return buttonView;
|
925
973
|
}
|
926
|
-
|
974
|
+
_showDialog() {
|
927
975
|
const editor = this.editor;
|
928
|
-
const
|
929
|
-
const
|
930
|
-
const
|
931
|
-
|
932
|
-
const
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
976
|
+
const dialog = editor.plugins.get('Dialog');
|
977
|
+
const command = editor.commands.get('mediaEmbed');
|
978
|
+
const t = editor.locale.t;
|
979
|
+
if (!this._formView) {
|
980
|
+
const registry = editor.plugins.get(MediaEmbedEditing).registry;
|
981
|
+
this._formView = new (CssTransitionDisablerMixin(MediaFormView))(getFormValidators(editor.t, registry), editor.locale);
|
982
|
+
this._formView.on('submit', ()=>this._handleSubmitForm());
|
983
|
+
}
|
984
|
+
dialog.show({
|
985
|
+
id: 'mediaEmbed',
|
986
|
+
title: t('Insert media'),
|
987
|
+
content: this._formView,
|
988
|
+
isModal: true,
|
989
|
+
onShow: ()=>{
|
990
|
+
this._formView.url = command.value || '';
|
991
|
+
this._formView.resetFormStatus();
|
992
|
+
this._formView.urlInputView.fieldView.select();
|
993
|
+
},
|
994
|
+
actionButtons: [
|
995
|
+
{
|
996
|
+
label: t('Cancel'),
|
997
|
+
withText: true,
|
998
|
+
onExecute: ()=>dialog.hide()
|
999
|
+
},
|
1000
|
+
{
|
1001
|
+
label: t('Accept'),
|
1002
|
+
class: 'ck-button-action',
|
1003
|
+
withText: true,
|
1004
|
+
onExecute: ()=>this._handleSubmitForm()
|
954
1005
|
}
|
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
|
1006
|
+
]
|
974
1007
|
});
|
975
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
|
+
}
|
976
1018
|
}
|
977
1019
|
function getFormValidators(t, registry) {
|
978
1020
|
return [
|
@@ -989,10 +1031,20 @@ function getFormValidators(t, registry) {
|
|
989
1031
|
];
|
990
1032
|
}
|
991
1033
|
|
992
|
-
|
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 {
|
993
1045
|
/**
|
994
|
-
|
995
|
-
|
1046
|
+
* @inheritDoc
|
1047
|
+
*/ static get requires() {
|
996
1048
|
return [
|
997
1049
|
MediaEmbedEditing,
|
998
1050
|
MediaEmbedUI,
|
@@ -1001,28 +1053,33 @@ class MediaEmbed extends Plugin {
|
|
1001
1053
|
];
|
1002
1054
|
}
|
1003
1055
|
/**
|
1004
|
-
|
1005
|
-
|
1056
|
+
* @inheritDoc
|
1057
|
+
*/ static get pluginName() {
|
1006
1058
|
return 'MediaEmbed';
|
1007
1059
|
}
|
1008
1060
|
}
|
1009
1061
|
|
1010
|
-
|
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 {
|
1011
1068
|
/**
|
1012
|
-
|
1013
|
-
|
1069
|
+
* @inheritDoc
|
1070
|
+
*/ static get requires() {
|
1014
1071
|
return [
|
1015
1072
|
WidgetToolbarRepository
|
1016
1073
|
];
|
1017
1074
|
}
|
1018
1075
|
/**
|
1019
|
-
|
1020
|
-
|
1076
|
+
* @inheritDoc
|
1077
|
+
*/ static get pluginName() {
|
1021
1078
|
return 'MediaEmbedToolbar';
|
1022
1079
|
}
|
1023
1080
|
/**
|
1024
|
-
|
1025
|
-
|
1081
|
+
* @inheritDoc
|
1082
|
+
*/ afterInit() {
|
1026
1083
|
const editor = this.editor;
|
1027
1084
|
const t = editor.t;
|
1028
1085
|
const widgetToolbarRepository = editor.plugins.get(WidgetToolbarRepository);
|