@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.
Files changed (227) hide show
  1. package/README.md +6 -0
  2. package/build/media-embed.js +2 -2
  3. package/build/translations/ar.js +1 -1
  4. package/build/translations/az.js +1 -1
  5. package/build/translations/bg.js +1 -1
  6. package/build/translations/bn.js +1 -1
  7. package/build/translations/ca.js +1 -1
  8. package/build/translations/cs.js +1 -1
  9. package/build/translations/da.js +1 -1
  10. package/build/translations/de-ch.js +1 -1
  11. package/build/translations/de.js +1 -1
  12. package/build/translations/el.js +1 -1
  13. package/build/translations/en-au.js +1 -1
  14. package/build/translations/en-gb.js +1 -1
  15. package/build/translations/es.js +1 -1
  16. package/build/translations/et.js +1 -1
  17. package/build/translations/fa.js +1 -1
  18. package/build/translations/fi.js +1 -1
  19. package/build/translations/fr.js +1 -1
  20. package/build/translations/gl.js +1 -1
  21. package/build/translations/he.js +1 -1
  22. package/build/translations/hi.js +1 -1
  23. package/build/translations/hr.js +1 -1
  24. package/build/translations/hu.js +1 -1
  25. package/build/translations/id.js +1 -1
  26. package/build/translations/it.js +1 -1
  27. package/build/translations/ja.js +1 -1
  28. package/build/translations/ko.js +1 -1
  29. package/build/translations/ku.js +1 -1
  30. package/build/translations/lt.js +1 -1
  31. package/build/translations/lv.js +1 -1
  32. package/build/translations/ms.js +1 -1
  33. package/build/translations/ne.js +1 -1
  34. package/build/translations/nl.js +1 -1
  35. package/build/translations/no.js +1 -1
  36. package/build/translations/pl.js +1 -1
  37. package/build/translations/pt-br.js +1 -1
  38. package/build/translations/pt.js +1 -1
  39. package/build/translations/ro.js +1 -1
  40. package/build/translations/ru.js +1 -1
  41. package/build/translations/sk.js +1 -1
  42. package/build/translations/sq.js +1 -1
  43. package/build/translations/sr-latn.js +1 -1
  44. package/build/translations/sr.js +1 -1
  45. package/build/translations/sv.js +1 -1
  46. package/build/translations/th.js +1 -1
  47. package/build/translations/tk.js +1 -1
  48. package/build/translations/tr.js +1 -1
  49. package/build/translations/uk.js +1 -1
  50. package/build/translations/ur.js +1 -1
  51. package/build/translations/uz.js +1 -1
  52. package/build/translations/vi.js +1 -1
  53. package/build/translations/zh-cn.js +1 -1
  54. package/build/translations/zh.js +1 -1
  55. package/dist/index-editor.css +5 -0
  56. package/dist/index.css +6 -0
  57. package/dist/index.css.map +1 -1
  58. package/dist/index.js +436 -379
  59. package/dist/index.js.map +1 -1
  60. package/dist/translations/ar.js +1 -1
  61. package/dist/translations/ar.umd.js +1 -1
  62. package/dist/translations/az.js +1 -1
  63. package/dist/translations/az.umd.js +1 -1
  64. package/dist/translations/bg.js +1 -1
  65. package/dist/translations/bg.umd.js +1 -1
  66. package/dist/translations/bn.js +1 -1
  67. package/dist/translations/bn.umd.js +1 -1
  68. package/dist/translations/ca.js +1 -1
  69. package/dist/translations/ca.umd.js +1 -1
  70. package/dist/translations/cs.js +1 -1
  71. package/dist/translations/cs.umd.js +1 -1
  72. package/dist/translations/da.js +1 -1
  73. package/dist/translations/da.umd.js +1 -1
  74. package/dist/translations/de-ch.js +1 -1
  75. package/dist/translations/de-ch.umd.js +1 -1
  76. package/dist/translations/de.js +1 -1
  77. package/dist/translations/de.umd.js +1 -1
  78. package/dist/translations/el.js +1 -1
  79. package/dist/translations/el.umd.js +1 -1
  80. package/dist/translations/en-au.js +1 -1
  81. package/dist/translations/en-au.umd.js +1 -1
  82. package/dist/translations/en-gb.js +1 -1
  83. package/dist/translations/en-gb.umd.js +1 -1
  84. package/dist/translations/en.js +1 -1
  85. package/dist/translations/en.umd.js +1 -1
  86. package/dist/translations/es.js +1 -1
  87. package/dist/translations/es.umd.js +1 -1
  88. package/dist/translations/et.js +1 -1
  89. package/dist/translations/et.umd.js +1 -1
  90. package/dist/translations/fa.js +1 -1
  91. package/dist/translations/fa.umd.js +1 -1
  92. package/dist/translations/fi.js +1 -1
  93. package/dist/translations/fi.umd.js +1 -1
  94. package/dist/translations/fr.js +1 -1
  95. package/dist/translations/fr.umd.js +1 -1
  96. package/dist/translations/gl.js +1 -1
  97. package/dist/translations/gl.umd.js +1 -1
  98. package/dist/translations/he.js +1 -1
  99. package/dist/translations/he.umd.js +1 -1
  100. package/dist/translations/hi.js +1 -1
  101. package/dist/translations/hi.umd.js +1 -1
  102. package/dist/translations/hr.js +1 -1
  103. package/dist/translations/hr.umd.js +1 -1
  104. package/dist/translations/hu.js +1 -1
  105. package/dist/translations/hu.umd.js +1 -1
  106. package/dist/translations/id.js +1 -1
  107. package/dist/translations/id.umd.js +1 -1
  108. package/dist/translations/it.js +1 -1
  109. package/dist/translations/it.umd.js +1 -1
  110. package/dist/translations/ja.js +1 -1
  111. package/dist/translations/ja.umd.js +1 -1
  112. package/dist/translations/ko.js +1 -1
  113. package/dist/translations/ko.umd.js +1 -1
  114. package/dist/translations/ku.js +1 -1
  115. package/dist/translations/ku.umd.js +1 -1
  116. package/dist/translations/lt.js +1 -1
  117. package/dist/translations/lt.umd.js +1 -1
  118. package/dist/translations/lv.js +1 -1
  119. package/dist/translations/lv.umd.js +1 -1
  120. package/dist/translations/ms.js +1 -1
  121. package/dist/translations/ms.umd.js +1 -1
  122. package/dist/translations/ne.js +1 -1
  123. package/dist/translations/ne.umd.js +1 -1
  124. package/dist/translations/nl.js +1 -1
  125. package/dist/translations/nl.umd.js +1 -1
  126. package/dist/translations/no.js +1 -1
  127. package/dist/translations/no.umd.js +1 -1
  128. package/dist/translations/pl.js +1 -1
  129. package/dist/translations/pl.umd.js +1 -1
  130. package/dist/translations/pt-br.js +1 -1
  131. package/dist/translations/pt-br.umd.js +1 -1
  132. package/dist/translations/pt.js +1 -1
  133. package/dist/translations/pt.umd.js +1 -1
  134. package/dist/translations/ro.js +1 -1
  135. package/dist/translations/ro.umd.js +1 -1
  136. package/dist/translations/ru.js +1 -1
  137. package/dist/translations/ru.umd.js +1 -1
  138. package/dist/translations/sk.js +1 -1
  139. package/dist/translations/sk.umd.js +1 -1
  140. package/dist/translations/sq.js +1 -1
  141. package/dist/translations/sq.umd.js +1 -1
  142. package/dist/translations/sr-latn.js +1 -1
  143. package/dist/translations/sr-latn.umd.js +1 -1
  144. package/dist/translations/sr.js +1 -1
  145. package/dist/translations/sr.umd.js +1 -1
  146. package/dist/translations/sv.js +1 -1
  147. package/dist/translations/sv.umd.js +1 -1
  148. package/dist/translations/th.js +1 -1
  149. package/dist/translations/th.umd.js +1 -1
  150. package/dist/translations/tk.js +1 -1
  151. package/dist/translations/tk.umd.js +1 -1
  152. package/dist/translations/tr.js +1 -1
  153. package/dist/translations/tr.umd.js +1 -1
  154. package/dist/translations/uk.js +1 -1
  155. package/dist/translations/uk.umd.js +1 -1
  156. package/dist/translations/ur.js +1 -1
  157. package/dist/translations/ur.umd.js +1 -1
  158. package/dist/translations/uz.js +1 -1
  159. package/dist/translations/uz.umd.js +1 -1
  160. package/dist/translations/vi.js +1 -1
  161. package/dist/translations/vi.umd.js +1 -1
  162. package/dist/translations/zh-cn.js +1 -1
  163. package/dist/translations/zh-cn.umd.js +1 -1
  164. package/dist/translations/zh.js +1 -1
  165. package/dist/translations/zh.umd.js +1 -1
  166. package/dist/types/mediaembedui.d.ts +9 -2
  167. package/dist/types/ui/mediaformview.d.ts +2 -28
  168. package/lang/contexts.json +1 -0
  169. package/lang/translations/ar.po +4 -0
  170. package/lang/translations/az.po +4 -0
  171. package/lang/translations/bg.po +4 -0
  172. package/lang/translations/bn.po +4 -0
  173. package/lang/translations/ca.po +4 -0
  174. package/lang/translations/cs.po +4 -0
  175. package/lang/translations/da.po +4 -0
  176. package/lang/translations/de-ch.po +4 -0
  177. package/lang/translations/de.po +4 -0
  178. package/lang/translations/el.po +4 -0
  179. package/lang/translations/en-au.po +4 -0
  180. package/lang/translations/en-gb.po +4 -0
  181. package/lang/translations/en.po +4 -0
  182. package/lang/translations/es.po +4 -0
  183. package/lang/translations/et.po +4 -0
  184. package/lang/translations/fa.po +4 -0
  185. package/lang/translations/fi.po +4 -0
  186. package/lang/translations/fr.po +4 -0
  187. package/lang/translations/gl.po +4 -0
  188. package/lang/translations/he.po +4 -0
  189. package/lang/translations/hi.po +4 -0
  190. package/lang/translations/hr.po +4 -0
  191. package/lang/translations/hu.po +4 -0
  192. package/lang/translations/id.po +4 -0
  193. package/lang/translations/it.po +4 -0
  194. package/lang/translations/ja.po +4 -0
  195. package/lang/translations/ko.po +4 -0
  196. package/lang/translations/ku.po +4 -0
  197. package/lang/translations/lt.po +4 -0
  198. package/lang/translations/lv.po +4 -0
  199. package/lang/translations/ms.po +4 -0
  200. package/lang/translations/ne.po +4 -0
  201. package/lang/translations/nl.po +4 -0
  202. package/lang/translations/no.po +4 -0
  203. package/lang/translations/pl.po +4 -0
  204. package/lang/translations/pt-br.po +4 -0
  205. package/lang/translations/pt.po +4 -0
  206. package/lang/translations/ro.po +4 -0
  207. package/lang/translations/ru.po +4 -0
  208. package/lang/translations/sk.po +4 -0
  209. package/lang/translations/sq.po +4 -0
  210. package/lang/translations/sr-latn.po +4 -0
  211. package/lang/translations/sr.po +4 -0
  212. package/lang/translations/sv.po +4 -0
  213. package/lang/translations/th.po +4 -0
  214. package/lang/translations/tk.po +4 -0
  215. package/lang/translations/tr.po +4 -0
  216. package/lang/translations/uk.po +4 -0
  217. package/lang/translations/ur.po +4 -0
  218. package/lang/translations/uz.po +4 -0
  219. package/lang/translations/vi.po +4 -0
  220. package/lang/translations/zh-cn.po +4 -0
  221. package/lang/translations/zh.po +4 -0
  222. package/package.json +3 -3
  223. package/src/mediaembedui.d.ts +9 -2
  224. package/src/mediaembedui.js +73 -50
  225. package/src/ui/mediaformview.d.ts +2 -28
  226. package/src/ui/mediaformview.js +8 -69
  227. 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, icons } from '@ckeditor/ckeditor5-core/dist/index.js';
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 { toArray, logWarning, first, global, FocusTracker, KeystrokeHandler } from '@ckeditor/ckeditor5-utils/dist/index.js';
8
- import { IconView, Template, View, submitHandler, LabeledFieldView, createLabeledInputText, ButtonView, ViewCollection, FocusCycler, createDropdown, CssTransitionDisablerMixin } from '@ckeditor/ckeditor5-ui/dist/index.js';
7
+ import { logWarning, toArray, first, global, FocusTracker, KeystrokeHandler } from '@ckeditor/ckeditor5-utils/dist/index.js';
8
+ import { IconView, Template, View, 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
- class MediaEmbedCommand extends Command {
150
+ /**
151
+ * The insert media command.
152
+ *
153
+ * The command is registered by the {@link module:media-embed/mediaembedediting~MediaEmbedEditing} as `'mediaEmbed'`.
154
+ *
155
+ * To insert media at the current selection, execute the command and specify the URL:
156
+ *
157
+ * ```ts
158
+ * editor.execute( 'mediaEmbed', 'http://url.to.the/media' );
159
+ * ```
160
+ */ class MediaEmbedCommand extends Command {
149
161
  /**
150
- * @inheritDoc
151
- */ refresh() {
162
+ * @inheritDoc
163
+ */ refresh() {
152
164
  const model = this.editor.model;
153
165
  const selection = model.document.selection;
154
166
  const selectedMedia = getSelectedMediaModelWidget(selection);
@@ -156,14 +168,14 @@ class MediaEmbedCommand extends Command {
156
168
  this.isEnabled = isMediaSelected(selection) || isAllowedInParent(selection, model);
157
169
  }
158
170
  /**
159
- * Executes the command, which either:
160
- *
161
- * * updates the URL of the selected media,
162
- * * inserts the new media into the editor and puts the selection around it.
163
- *
164
- * @fires execute
165
- * @param url The URL of the media.
166
- */ execute(url) {
171
+ * Executes the command, which either:
172
+ *
173
+ * * updates the URL of the selected media,
174
+ * * inserts the new media into the editor and puts the selection around it.
175
+ *
176
+ * @fires execute
177
+ * @param url The URL of the media.
178
+ */ execute(url) {
167
179
  const model = this.editor.model;
168
180
  const selection = model.document.selection;
169
181
  const selectedMedia = getSelectedMediaModelWidget(selection);
@@ -197,31 +209,72 @@ class MediaEmbedCommand extends Command {
197
209
  var mediaPlaceholderIcon = "<svg viewBox=\"0 0 64 42\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M47.426 17V3.713L63.102 0v19.389h-.001l.001.272c0 1.595-2.032 3.43-4.538 4.098-2.506.668-4.538-.083-4.538-1.678 0-1.594 2.032-3.43 4.538-4.098.914-.244 2.032-.565 2.888-.603V4.516L49.076 7.447v9.556A1.014 1.014 0 0 0 49 17h-1.574zM29.5 17h-8.343a7.073 7.073 0 1 0-4.657 4.06v3.781H3.3a2.803 2.803 0 0 1-2.8-2.804V8.63a2.803 2.803 0 0 1 2.8-2.805h4.082L8.58 2.768A1.994 1.994 0 0 1 10.435 1.5h8.985c.773 0 1.477.448 1.805 1.149l1.488 3.177H26.7c1.546 0 2.8 1.256 2.8 2.805V17zm-11.637 0H17.5a1 1 0 0 0-1 1v.05A4.244 4.244 0 1 1 17.863 17zm29.684 2c.97 0 .953-.048.953.889v20.743c0 .953.016.905-.953.905H19.453c-.97 0-.953.048-.953-.905V19.89c0-.937-.016-.889.97-.889h28.077zm-4.701 19.338V22.183H24.154v16.155h18.692zM20.6 21.375v1.616h1.616v-1.616H20.6zm0 3.231v1.616h1.616v-1.616H20.6zm0 3.231v1.616h1.616v-1.616H20.6zm0 3.231v1.616h1.616v-1.616H20.6zm0 3.231v1.616h1.616v-1.616H20.6zm0 3.231v1.616h1.616V37.53H20.6zm24.233-16.155v1.616h1.615v-1.616h-1.615zm0 3.231v1.616h1.615v-1.616h-1.615zm0 3.231v1.616h1.615v-1.616h-1.615zm0 3.231v1.616h1.615v-1.616h-1.615zm0 3.231v1.616h1.615v-1.616h-1.615zm0 3.231v1.616h1.615V37.53h-1.615zM29.485 25.283a.4.4 0 0 1 .593-.35l9.05 4.977a.4.4 0 0 1 0 .701l-9.05 4.978a.4.4 0 0 1-.593-.35v-9.956z\"/></svg>";
198
210
 
199
211
  const mediaPlaceholderIconViewBox = '0 0 64 42';
200
- class MediaRegistry {
212
+ /**
213
+ * A bridge between the raw media content provider definitions and the editor view content.
214
+ *
215
+ * It helps translating media URLs to corresponding {@link module:engine/view/element~Element view elements}.
216
+ *
217
+ * Mostly used by the {@link module:media-embed/mediaembedediting~MediaEmbedEditing} plugin.
218
+ */ class MediaRegistry {
219
+ /**
220
+ * The {@link module:utils/locale~Locale} instance.
221
+ */ locale;
222
+ /**
223
+ * The media provider definitions available for the registry. Usually corresponding with the
224
+ * {@link module:media-embed/mediaembedconfig~MediaEmbedConfig media configuration}.
225
+ */ providerDefinitions;
201
226
  /**
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) {
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
- * For the given media URL string and options, it returns the {@link module:engine/view/element~Element view element}
210
- * representing that media.
211
- *
212
- * **Note:** If no URL is specified, an empty view element is returned.
213
- *
214
- * @param writer The view writer used to produce a view element.
215
- * @param url The URL to be translated into a view element.
216
- */ getMediaViewElement(writer, url, options) {
262
+ * For the given media URL string and options, it returns the {@link module:engine/view/element~Element view element}
263
+ * representing that media.
264
+ *
265
+ * **Note:** If no URL is specified, an empty view element is returned.
266
+ *
267
+ * @param writer The view writer used to produce a view element.
268
+ * @param url The URL to be translated into a view element.
269
+ */ getMediaViewElement(writer, url, options) {
217
270
  return this._getMedia(url).getViewElement(writer, options);
218
271
  }
219
272
  /**
220
- * Returns a `Media` instance for the given URL.
221
- *
222
- * @param url The URL of the media.
223
- * @returns The `Media` instance or `null` when there is none.
224
- */ _getMedia(url) {
273
+ * Returns a `Media` instance for the given URL.
274
+ *
275
+ * @param url The URL of the media.
276
+ * @returns The `Media` instance or `null` when there is none.
277
+ */ _getMedia(url) {
225
278
  if (!url) {
226
279
  return new Media(this.locale);
227
280
  }
@@ -239,11 +292,11 @@ class MediaRegistry {
239
292
  return null;
240
293
  }
241
294
  /**
242
- * Tries to match `url` to `pattern`.
243
- *
244
- * @param url The URL of the media.
245
- * @param pattern The pattern that should accept the media URL.
246
- */ _getUrlMatches(url, pattern) {
295
+ * Tries to match `url` to `pattern`.
296
+ *
297
+ * @param url The URL of the media.
298
+ * @param pattern The pattern that should accept the media URL.
299
+ */ _getUrlMatches(url, pattern) {
247
300
  // 1. Try to match without stripping the protocol and "www" subdomain.
248
301
  let match = url.match(pattern);
249
302
  if (match) {
@@ -263,34 +316,6 @@ class MediaRegistry {
263
316
  }
264
317
  return null;
265
318
  }
266
- /**
267
- * Creates an instance of the {@link module:media-embed/mediaregistry~MediaRegistry} class.
268
- *
269
- * @param locale The localization services instance.
270
- * @param config The configuration of the media embed feature.
271
- */ constructor(locale, config){
272
- const providers = config.providers;
273
- const extraProviders = config.extraProviders || [];
274
- const removedProviders = new Set(config.removeProviders);
275
- const providerDefinitions = providers.concat(extraProviders).filter((provider)=>{
276
- const name = provider.name;
277
- if (!name) {
278
- /**
279
- * One of the providers (or extra providers) specified in the media embed configuration
280
- * has no name and will not be used by the editor. In order to get this media
281
- * provider working, double check your editor configuration.
282
- *
283
- * @error media-embed-no-provider-name
284
- */ logWarning('media-embed-no-provider-name', {
285
- provider
286
- });
287
- return false;
288
- }
289
- return !removedProviders.has(name);
290
- });
291
- this.locale = locale;
292
- this.providerDefinitions = providerDefinitions;
293
- }
294
319
  }
295
320
  /**
296
321
  * Represents media defined by the provider configuration.
@@ -298,10 +323,30 @@ class MediaRegistry {
298
323
  * It can be rendered to the {@link module:engine/view/element~Element view element} and used in the editing or data pipeline.
299
324
  */ class Media {
300
325
  /**
301
- * Returns the view element representation of the media.
302
- *
303
- * @param writer The view writer used to produce a view element.
304
- */ getViewElement(writer, options) {
326
+ * The URL this Media instance represents.
327
+ */ url;
328
+ /**
329
+ * Shorthand for {@link module:utils/locale~Locale#t}.
330
+ *
331
+ * @see module:utils/locale~Locale#t
332
+ */ _locale;
333
+ /**
334
+ * The output of the `RegExp.match` which validated the {@link #url} of this media.
335
+ */ _match;
336
+ /**
337
+ * The function returning the HTML string preview of this media.
338
+ */ _previewRenderer;
339
+ constructor(locale, url, match, previewRenderer){
340
+ this.url = this._getValidUrl(url);
341
+ this._locale = locale;
342
+ this._match = match;
343
+ this._previewRenderer = previewRenderer;
344
+ }
345
+ /**
346
+ * Returns the view element representation of the media.
347
+ *
348
+ * @param writer The view writer used to produce a view element.
349
+ */ getViewElement(writer, options) {
305
350
  const attributes = {};
306
351
  let viewElement;
307
352
  if (options.renderForEditingView || options.renderMediaPreview && this.url && this._previewRenderer) {
@@ -325,8 +370,8 @@ class MediaRegistry {
325
370
  return viewElement;
326
371
  }
327
372
  /**
328
- * Returns the HTML string of the media content preview.
329
- */ _getPreviewHtml(options) {
373
+ * Returns the HTML string of the media content preview.
374
+ */ _getPreviewHtml(options) {
330
375
  if (this._previewRenderer) {
331
376
  return this._previewRenderer(this._match);
332
377
  } else {
@@ -339,8 +384,8 @@ class MediaRegistry {
339
384
  }
340
385
  }
341
386
  /**
342
- * Returns the placeholder HTML when the media has no content preview.
343
- */ _getPlaceholderHtml() {
387
+ * Returns the placeholder HTML when the media has no content preview.
388
+ */ _getPlaceholderHtml() {
344
389
  const icon = new IconView();
345
390
  const t = this._locale.t;
346
391
  icon.content = mediaPlaceholderIcon;
@@ -386,10 +431,10 @@ class MediaRegistry {
386
431
  return placeholder.outerHTML;
387
432
  }
388
433
  /**
389
- * Returns the full URL to the specified media.
390
- *
391
- * @param url The URL of the media.
392
- */ _getValidUrl(url) {
434
+ * Returns the full URL to the specified media.
435
+ *
436
+ * @param url The URL of the media.
437
+ */ _getValidUrl(url) {
393
438
  if (!url) {
394
439
  return null;
395
440
  }
@@ -398,23 +443,111 @@ class MediaRegistry {
398
443
  }
399
444
  return 'https://' + url;
400
445
  }
401
- constructor(locale, url, match, previewRenderer){
402
- this.url = this._getValidUrl(url);
403
- this._locale = locale;
404
- this._match = match;
405
- this._previewRenderer = previewRenderer;
406
- }
407
446
  }
408
447
 
409
- class MediaEmbedEditing extends Plugin {
448
+ /**
449
+ * The media embed editing feature.
450
+ */ class MediaEmbedEditing extends Plugin {
410
451
  /**
411
- * @inheritDoc
412
- */ static get pluginName() {
452
+ * @inheritDoc
453
+ */ static get pluginName() {
413
454
  return 'MediaEmbedEditing';
414
455
  }
415
456
  /**
416
- * @inheritDoc
417
- */ init() {
457
+ * The media registry managing the media providers in the editor.
458
+ */ registry;
459
+ /**
460
+ * @inheritDoc
461
+ */ constructor(editor){
462
+ super(editor);
463
+ editor.config.define('mediaEmbed', {
464
+ elementName: 'oembed',
465
+ providers: [
466
+ {
467
+ name: 'dailymotion',
468
+ url: [
469
+ /^dailymotion\.com\/video\/(\w+)/,
470
+ /^dai.ly\/(\w+)/
471
+ ],
472
+ html: (match)=>{
473
+ const id = match[1];
474
+ return '<div style="position: relative; padding-bottom: 100%; height: 0; ">' + `<iframe src="https://www.dailymotion.com/embed/video/${id}" ` + 'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' + 'frameborder="0" width="480" height="270" allowfullscreen allow="autoplay">' + '</iframe>' + '</div>';
475
+ }
476
+ },
477
+ {
478
+ name: 'spotify',
479
+ url: [
480
+ /^open\.spotify\.com\/(artist\/\w+)/,
481
+ /^open\.spotify\.com\/(album\/\w+)/,
482
+ /^open\.spotify\.com\/(track\/\w+)/
483
+ ],
484
+ html: (match)=>{
485
+ const id = match[1];
486
+ return '<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 126%;">' + `<iframe src="https://open.spotify.com/embed/${id}" ` + 'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' + 'frameborder="0" allowtransparency="true" allow="encrypted-media">' + '</iframe>' + '</div>';
487
+ }
488
+ },
489
+ {
490
+ name: 'youtube',
491
+ url: [
492
+ /^(?:m\.)?youtube\.com\/watch\?v=([\w-]+)(?:&t=(\d+))?/,
493
+ /^(?:m\.)?youtube\.com\/v\/([\w-]+)(?:\?t=(\d+))?/,
494
+ /^youtube\.com\/embed\/([\w-]+)(?:\?start=(\d+))?/,
495
+ /^youtu\.be\/([\w-]+)(?:\?t=(\d+))?/
496
+ ],
497
+ html: (match)=>{
498
+ const id = match[1];
499
+ const time = match[2];
500
+ return '<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 56.2493%;">' + `<iframe src="https://www.youtube.com/embed/${id}${time ? `?start=${time}` : ''}" ` + 'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' + 'frameborder="0" allow="autoplay; encrypted-media" allowfullscreen>' + '</iframe>' + '</div>';
501
+ }
502
+ },
503
+ {
504
+ name: 'vimeo',
505
+ url: [
506
+ /^vimeo\.com\/(\d+)/,
507
+ /^vimeo\.com\/[^/]+\/[^/]+\/video\/(\d+)/,
508
+ /^vimeo\.com\/album\/[^/]+\/video\/(\d+)/,
509
+ /^vimeo\.com\/channels\/[^/]+\/(\d+)/,
510
+ /^vimeo\.com\/groups\/[^/]+\/videos\/(\d+)/,
511
+ /^vimeo\.com\/ondemand\/[^/]+\/(\d+)/,
512
+ /^player\.vimeo\.com\/video\/(\d+)/
513
+ ],
514
+ html: (match)=>{
515
+ const id = match[1];
516
+ return '<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 56.2493%;">' + `<iframe src="https://player.vimeo.com/video/${id}" ` + 'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' + 'frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen>' + '</iframe>' + '</div>';
517
+ }
518
+ },
519
+ {
520
+ name: 'instagram',
521
+ url: /^instagram\.com\/p\/(\w+)/
522
+ },
523
+ {
524
+ name: 'twitter',
525
+ url: /^twitter\.com/
526
+ },
527
+ {
528
+ name: 'googleMaps',
529
+ url: [
530
+ /^google\.com\/maps/,
531
+ /^goo\.gl\/maps/,
532
+ /^maps\.google\.com/,
533
+ /^maps\.app\.goo\.gl/
534
+ ]
535
+ },
536
+ {
537
+ name: 'flickr',
538
+ url: /^flickr\.com/
539
+ },
540
+ {
541
+ name: 'facebook',
542
+ url: /^facebook\.com/
543
+ }
544
+ ]
545
+ });
546
+ this.registry = new MediaRegistry(editor.locale, editor.config.get('mediaEmbed'));
547
+ }
548
+ /**
549
+ * @inheritDoc
550
+ */ init() {
418
551
  const editor = this.editor;
419
552
  const schema = editor.model.schema;
420
553
  const t = editor.t;
@@ -522,102 +655,16 @@ class MediaEmbedEditing extends Plugin {
522
655
  dispatcher.on('element:figure', converter);
523
656
  });
524
657
  }
525
- /**
526
- * @inheritDoc
527
- */ constructor(editor){
528
- super(editor);
529
- editor.config.define('mediaEmbed', {
530
- elementName: 'oembed',
531
- providers: [
532
- {
533
- name: 'dailymotion',
534
- url: [
535
- /^dailymotion\.com\/video\/(\w+)/,
536
- /^dai.ly\/(\w+)/
537
- ],
538
- html: (match)=>{
539
- const id = match[1];
540
- return '<div style="position: relative; padding-bottom: 100%; height: 0; ">' + `<iframe src="https://www.dailymotion.com/embed/video/${id}" ` + 'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' + 'frameborder="0" width="480" height="270" allowfullscreen allow="autoplay">' + '</iframe>' + '</div>';
541
- }
542
- },
543
- {
544
- name: 'spotify',
545
- url: [
546
- /^open\.spotify\.com\/(artist\/\w+)/,
547
- /^open\.spotify\.com\/(album\/\w+)/,
548
- /^open\.spotify\.com\/(track\/\w+)/
549
- ],
550
- html: (match)=>{
551
- const id = match[1];
552
- return '<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 126%;">' + `<iframe src="https://open.spotify.com/embed/${id}" ` + 'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' + 'frameborder="0" allowtransparency="true" allow="encrypted-media">' + '</iframe>' + '</div>';
553
- }
554
- },
555
- {
556
- name: 'youtube',
557
- url: [
558
- /^(?:m\.)?youtube\.com\/watch\?v=([\w-]+)(?:&t=(\d+))?/,
559
- /^(?:m\.)?youtube\.com\/v\/([\w-]+)(?:\?t=(\d+))?/,
560
- /^youtube\.com\/embed\/([\w-]+)(?:\?start=(\d+))?/,
561
- /^youtu\.be\/([\w-]+)(?:\?t=(\d+))?/
562
- ],
563
- html: (match)=>{
564
- const id = match[1];
565
- const time = match[2];
566
- return '<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 56.2493%;">' + `<iframe src="https://www.youtube.com/embed/${id}${time ? `?start=${time}` : ''}" ` + 'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' + 'frameborder="0" allow="autoplay; encrypted-media" allowfullscreen>' + '</iframe>' + '</div>';
567
- }
568
- },
569
- {
570
- name: 'vimeo',
571
- url: [
572
- /^vimeo\.com\/(\d+)/,
573
- /^vimeo\.com\/[^/]+\/[^/]+\/video\/(\d+)/,
574
- /^vimeo\.com\/album\/[^/]+\/video\/(\d+)/,
575
- /^vimeo\.com\/channels\/[^/]+\/(\d+)/,
576
- /^vimeo\.com\/groups\/[^/]+\/videos\/(\d+)/,
577
- /^vimeo\.com\/ondemand\/[^/]+\/(\d+)/,
578
- /^player\.vimeo\.com\/video\/(\d+)/
579
- ],
580
- html: (match)=>{
581
- const id = match[1];
582
- return '<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 56.2493%;">' + `<iframe src="https://player.vimeo.com/video/${id}" ` + 'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' + 'frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen>' + '</iframe>' + '</div>';
583
- }
584
- },
585
- {
586
- name: 'instagram',
587
- url: /^instagram\.com\/p\/(\w+)/
588
- },
589
- {
590
- name: 'twitter',
591
- url: /^twitter\.com/
592
- },
593
- {
594
- name: 'googleMaps',
595
- url: [
596
- /^google\.com\/maps/,
597
- /^goo\.gl\/maps/,
598
- /^maps\.google\.com/,
599
- /^maps\.app\.goo\.gl/
600
- ]
601
- },
602
- {
603
- name: 'flickr',
604
- url: /^flickr\.com/
605
- },
606
- {
607
- name: 'facebook',
608
- url: /^facebook\.com/
609
- }
610
- ]
611
- });
612
- this.registry = new MediaRegistry(editor.locale, editor.config.get('mediaEmbed'));
613
- }
614
658
  }
615
659
 
616
660
  const URL_REGEXP = /^(?:http(s)?:\/\/)?[\w-]+\.[\w-.~:/?#[\]@!$&'()*+,;=%]+$/;
617
- class AutoMediaEmbed extends Plugin {
661
+ /**
662
+ * The auto-media embed plugin. It recognizes media links in the pasted content and embeds
663
+ * them shortly after they are injected into the document.
664
+ */ class AutoMediaEmbed extends Plugin {
618
665
  /**
619
- * @inheritDoc
620
- */ static get requires() {
666
+ * @inheritDoc
667
+ */ static get requires() {
621
668
  return [
622
669
  Clipboard,
623
670
  Delete,
@@ -625,13 +672,28 @@ class AutoMediaEmbed extends Plugin {
625
672
  ];
626
673
  }
627
674
  /**
628
- * @inheritDoc
629
- */ static get pluginName() {
675
+ * @inheritDoc
676
+ */ static get pluginName() {
630
677
  return 'AutoMediaEmbed';
631
678
  }
632
679
  /**
633
- * @inheritDoc
634
- */ init() {
680
+ * The paste–to–embed `setTimeout` ID. Stored as a property to allow
681
+ * cleaning of the timeout.
682
+ */ _timeoutId;
683
+ /**
684
+ * The position where the `<media>` element will be inserted after the timeout,
685
+ * determined each time the new content is pasted into the document.
686
+ */ _positionToInsert;
687
+ /**
688
+ * @inheritDoc
689
+ */ constructor(editor){
690
+ super(editor);
691
+ this._timeoutId = null;
692
+ this._positionToInsert = null;
693
+ }
694
+ /**
695
+ * @inheritDoc
696
+ */ init() {
635
697
  const editor = this.editor;
636
698
  const modelDocument = editor.model.document;
637
699
  // We need to listen on `Clipboard#inputTransformation` because we need to save positions of selection.
@@ -665,12 +727,12 @@ class AutoMediaEmbed extends Plugin {
665
727
  });
666
728
  }
667
729
  /**
668
- * Analyzes the part of the document between provided positions in search for a URL representing media.
669
- * When the URL is found, it is automatically converted into media.
670
- *
671
- * @param leftPosition Left position of the selection.
672
- * @param rightPosition Right position of the selection.
673
- */ _embedMediaBetweenPositions(leftPosition, rightPosition) {
730
+ * Analyzes the part of the document between provided positions in search for a URL representing media.
731
+ * When the URL is found, it is automatically converted into media.
732
+ *
733
+ * @param leftPosition Left position of the selection.
734
+ * @param rightPosition Right position of the selection.
735
+ */ _embedMediaBetweenPositions(leftPosition, rightPosition) {
674
736
  const editor = this.editor;
675
737
  const mediaRegistry = editor.plugins.get(MediaEmbedEditing).registry;
676
738
  // TODO: Use marker instead of LiveRange & LivePositions.
@@ -722,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
- class MediaFormView extends View {
789
+ /**
790
+ * The media form view controller class.
791
+ *
792
+ * See {@link module:media-embed/ui/mediaformview~MediaFormView}.
793
+ */ class MediaFormView extends View {
794
+ /**
795
+ * Tracks information about the DOM focus in the form.
796
+ */ focusTracker;
797
+ /**
798
+ * An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
799
+ */ keystrokes;
800
+ /**
801
+ * The URL input view.
802
+ */ urlInputView;
803
+ /**
804
+ * 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
- * @inheritDoc
737
- */ render() {
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
- 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
- });
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
- * @inheritDoc
766
- */ destroy() {
851
+ * @inheritDoc
852
+ */ destroy() {
767
853
  super.destroy();
768
854
  this.focusTracker.destroy();
769
855
  this.keystrokes.destroy();
770
856
  }
771
857
  /**
772
- * Focuses the fist {@link #_focusables} in the form.
773
- */ focus() {
774
- this._focusCycler.focusFirst();
858
+ * Focuses the {@link #urlInputView}.
859
+ */ focus() {
860
+ this.urlInputView.focus();
775
861
  }
776
862
  /**
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() {
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.element.value = url.trim();
871
+ this.urlInputView.fieldView.value = url.trim();
786
872
  }
787
873
  /**
788
- * Validates the form and returns `false` when some fields are invalid.
789
- */ isValid() {
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
- * 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() {
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
- * Creates a labeled input view.
813
- *
814
- * @returns Labeled input view instance.
815
- */ _createUrlInput() {
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
- class MediaEmbedUI extends Plugin {
921
+ /**
922
+ * The media embed UI plugin.
923
+ */ class MediaEmbedUI extends Plugin {
903
924
  /**
904
- * @inheritDoc
905
- */ static get requires() {
925
+ * @inheritDoc
926
+ */ static get requires() {
906
927
  return [
907
- MediaEmbedEditing
928
+ MediaEmbedEditing,
929
+ Dialog
908
930
  ];
909
931
  }
910
932
  /**
911
- * @inheritDoc
912
- */ static get pluginName() {
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
- * @inheritDoc
917
- */ init() {
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.ui.componentFactory.add('mediaEmbed', (locale)=>{
921
- const dropdown = createDropdown(locale);
922
- this._setUpDropdown(dropdown, command);
923
- return dropdown;
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
- _setUpDropdown(dropdown, command) {
974
+ _showDialog() {
927
975
  const editor = this.editor;
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();
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
- class MediaEmbed extends Plugin {
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
- * @inheritDoc
995
- */ static get requires() {
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
- * @inheritDoc
1005
- */ static get pluginName() {
1056
+ * @inheritDoc
1057
+ */ static get pluginName() {
1006
1058
  return 'MediaEmbed';
1007
1059
  }
1008
1060
  }
1009
1061
 
1010
- class MediaEmbedToolbar extends Plugin {
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
- * @inheritDoc
1013
- */ static get requires() {
1069
+ * @inheritDoc
1070
+ */ static get requires() {
1014
1071
  return [
1015
1072
  WidgetToolbarRepository
1016
1073
  ];
1017
1074
  }
1018
1075
  /**
1019
- * @inheritDoc
1020
- */ static get pluginName() {
1076
+ * @inheritDoc
1077
+ */ static get pluginName() {
1021
1078
  return 'MediaEmbedToolbar';
1022
1079
  }
1023
1080
  /**
1024
- * @inheritDoc
1025
- */ afterInit() {
1081
+ * @inheritDoc
1082
+ */ afterInit() {
1026
1083
  const editor = this.editor;
1027
1084
  const t = editor.t;
1028
1085
  const widgetToolbarRepository = editor.plugins.get(WidgetToolbarRepository);