@ckeditor/ckeditor5-image 41.4.2 → 42.0.0-alpha.1

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 (294) hide show
  1. package/README.md +6 -0
  2. package/build/image.js +2 -2
  3. package/build/translations/ar.js +1 -1
  4. package/build/translations/ast.js +1 -1
  5. package/build/translations/az.js +1 -1
  6. package/build/translations/bg.js +1 -1
  7. package/build/translations/bn.js +1 -1
  8. package/build/translations/bs.js +1 -1
  9. package/build/translations/ca.js +1 -1
  10. package/build/translations/cs.js +1 -1
  11. package/build/translations/da.js +1 -1
  12. package/build/translations/de-ch.js +1 -1
  13. package/build/translations/de.js +1 -1
  14. package/build/translations/el.js +1 -1
  15. package/build/translations/en-au.js +1 -1
  16. package/build/translations/en-gb.js +1 -1
  17. package/build/translations/eo.js +1 -1
  18. package/build/translations/es.js +1 -1
  19. package/build/translations/et.js +1 -1
  20. package/build/translations/eu.js +1 -1
  21. package/build/translations/fa.js +1 -1
  22. package/build/translations/fi.js +1 -1
  23. package/build/translations/fr.js +1 -1
  24. package/build/translations/gl.js +1 -1
  25. package/build/translations/he.js +1 -1
  26. package/build/translations/hi.js +1 -1
  27. package/build/translations/hr.js +1 -1
  28. package/build/translations/hu.js +1 -1
  29. package/build/translations/id.js +1 -1
  30. package/build/translations/it.js +1 -1
  31. package/build/translations/ja.js +1 -1
  32. package/build/translations/jv.js +1 -1
  33. package/build/translations/km.js +1 -1
  34. package/build/translations/kn.js +1 -1
  35. package/build/translations/ko.js +1 -1
  36. package/build/translations/ku.js +1 -1
  37. package/build/translations/lt.js +1 -1
  38. package/build/translations/lv.js +1 -1
  39. package/build/translations/ms.js +1 -1
  40. package/build/translations/nb.js +1 -1
  41. package/build/translations/ne.js +1 -1
  42. package/build/translations/nl.js +1 -1
  43. package/build/translations/no.js +1 -1
  44. package/build/translations/pl.js +1 -1
  45. package/build/translations/pt-br.js +1 -1
  46. package/build/translations/pt.js +1 -1
  47. package/build/translations/ro.js +1 -1
  48. package/build/translations/ru.js +1 -1
  49. package/build/translations/si.js +1 -1
  50. package/build/translations/sk.js +1 -1
  51. package/build/translations/sq.js +1 -1
  52. package/build/translations/sr-latn.js +1 -1
  53. package/build/translations/sr.js +1 -1
  54. package/build/translations/sv.js +1 -1
  55. package/build/translations/th.js +1 -1
  56. package/build/translations/ti.js +1 -1
  57. package/build/translations/tk.js +1 -1
  58. package/build/translations/tr.js +1 -1
  59. package/build/translations/ug.js +1 -1
  60. package/build/translations/uk.js +1 -1
  61. package/build/translations/ur.js +1 -1
  62. package/build/translations/uz.js +1 -1
  63. package/build/translations/vi.js +1 -1
  64. package/build/translations/zh-cn.js +1 -1
  65. package/build/translations/zh.js +1 -1
  66. package/ckeditor5-metadata.json +19 -5
  67. package/dist/index-content.css +20 -12
  68. package/dist/index-editor.css +4 -0
  69. package/dist/index.css +50 -33
  70. package/dist/index.css.map +1 -1
  71. package/dist/index.js +1876 -1330
  72. package/dist/index.js.map +1 -1
  73. package/dist/translations/ar.js +1 -1
  74. package/dist/translations/ar.umd.js +1 -1
  75. package/dist/translations/ast.js +1 -1
  76. package/dist/translations/ast.umd.js +1 -1
  77. package/dist/translations/az.js +1 -1
  78. package/dist/translations/az.umd.js +1 -1
  79. package/dist/translations/bg.js +1 -1
  80. package/dist/translations/bg.umd.js +1 -1
  81. package/dist/translations/bn.js +1 -1
  82. package/dist/translations/bn.umd.js +1 -1
  83. package/dist/translations/bs.js +1 -1
  84. package/dist/translations/bs.umd.js +1 -1
  85. package/dist/translations/ca.js +1 -1
  86. package/dist/translations/ca.umd.js +1 -1
  87. package/dist/translations/cs.js +1 -1
  88. package/dist/translations/cs.umd.js +1 -1
  89. package/dist/translations/da.js +1 -1
  90. package/dist/translations/da.umd.js +1 -1
  91. package/dist/translations/de-ch.js +1 -1
  92. package/dist/translations/de-ch.umd.js +1 -1
  93. package/dist/translations/de.js +1 -1
  94. package/dist/translations/de.umd.js +1 -1
  95. package/dist/translations/el.js +1 -1
  96. package/dist/translations/el.umd.js +1 -1
  97. package/dist/translations/en-au.js +1 -1
  98. package/dist/translations/en-au.umd.js +1 -1
  99. package/dist/translations/en-gb.js +1 -1
  100. package/dist/translations/en-gb.umd.js +1 -1
  101. package/dist/translations/en.js +1 -1
  102. package/dist/translations/en.umd.js +1 -1
  103. package/dist/translations/eo.js +1 -1
  104. package/dist/translations/eo.umd.js +1 -1
  105. package/dist/translations/es.js +1 -1
  106. package/dist/translations/es.umd.js +1 -1
  107. package/dist/translations/et.js +1 -1
  108. package/dist/translations/et.umd.js +1 -1
  109. package/dist/translations/eu.js +1 -1
  110. package/dist/translations/eu.umd.js +1 -1
  111. package/dist/translations/fa.js +1 -1
  112. package/dist/translations/fa.umd.js +1 -1
  113. package/dist/translations/fi.js +1 -1
  114. package/dist/translations/fi.umd.js +1 -1
  115. package/dist/translations/fr.js +1 -1
  116. package/dist/translations/fr.umd.js +1 -1
  117. package/dist/translations/gl.js +1 -1
  118. package/dist/translations/gl.umd.js +1 -1
  119. package/dist/translations/he.js +1 -1
  120. package/dist/translations/he.umd.js +1 -1
  121. package/dist/translations/hi.js +1 -1
  122. package/dist/translations/hi.umd.js +1 -1
  123. package/dist/translations/hr.js +1 -1
  124. package/dist/translations/hr.umd.js +1 -1
  125. package/dist/translations/hu.js +1 -1
  126. package/dist/translations/hu.umd.js +1 -1
  127. package/dist/translations/id.js +1 -1
  128. package/dist/translations/id.umd.js +1 -1
  129. package/dist/translations/it.js +1 -1
  130. package/dist/translations/it.umd.js +1 -1
  131. package/dist/translations/ja.js +1 -1
  132. package/dist/translations/ja.umd.js +1 -1
  133. package/dist/translations/jv.js +1 -1
  134. package/dist/translations/jv.umd.js +1 -1
  135. package/dist/translations/km.js +1 -1
  136. package/dist/translations/km.umd.js +1 -1
  137. package/dist/translations/kn.js +1 -1
  138. package/dist/translations/kn.umd.js +1 -1
  139. package/dist/translations/ko.js +1 -1
  140. package/dist/translations/ko.umd.js +1 -1
  141. package/dist/translations/ku.js +1 -1
  142. package/dist/translations/ku.umd.js +1 -1
  143. package/dist/translations/lt.js +1 -1
  144. package/dist/translations/lt.umd.js +1 -1
  145. package/dist/translations/lv.js +1 -1
  146. package/dist/translations/lv.umd.js +1 -1
  147. package/dist/translations/ms.js +1 -1
  148. package/dist/translations/ms.umd.js +1 -1
  149. package/dist/translations/nb.js +1 -1
  150. package/dist/translations/nb.umd.js +1 -1
  151. package/dist/translations/ne.js +1 -1
  152. package/dist/translations/ne.umd.js +1 -1
  153. package/dist/translations/nl.js +1 -1
  154. package/dist/translations/nl.umd.js +1 -1
  155. package/dist/translations/no.js +1 -1
  156. package/dist/translations/no.umd.js +1 -1
  157. package/dist/translations/pl.js +1 -1
  158. package/dist/translations/pl.umd.js +1 -1
  159. package/dist/translations/pt-br.js +1 -1
  160. package/dist/translations/pt-br.umd.js +1 -1
  161. package/dist/translations/pt.js +1 -1
  162. package/dist/translations/pt.umd.js +1 -1
  163. package/dist/translations/ro.js +1 -1
  164. package/dist/translations/ro.umd.js +1 -1
  165. package/dist/translations/ru.js +1 -1
  166. package/dist/translations/ru.umd.js +1 -1
  167. package/dist/translations/si.js +1 -1
  168. package/dist/translations/si.umd.js +1 -1
  169. package/dist/translations/sk.js +1 -1
  170. package/dist/translations/sk.umd.js +1 -1
  171. package/dist/translations/sq.js +1 -1
  172. package/dist/translations/sq.umd.js +1 -1
  173. package/dist/translations/sr-latn.js +1 -1
  174. package/dist/translations/sr-latn.umd.js +1 -1
  175. package/dist/translations/sr.js +1 -1
  176. package/dist/translations/sr.umd.js +1 -1
  177. package/dist/translations/sv.js +1 -1
  178. package/dist/translations/sv.umd.js +1 -1
  179. package/dist/translations/th.js +1 -1
  180. package/dist/translations/th.umd.js +1 -1
  181. package/dist/translations/ti.js +1 -1
  182. package/dist/translations/ti.umd.js +1 -1
  183. package/dist/translations/tk.js +1 -1
  184. package/dist/translations/tk.umd.js +1 -1
  185. package/dist/translations/tr.js +1 -1
  186. package/dist/translations/tr.umd.js +1 -1
  187. package/dist/translations/ug.js +1 -1
  188. package/dist/translations/ug.umd.js +1 -1
  189. package/dist/translations/uk.js +1 -1
  190. package/dist/translations/uk.umd.js +1 -1
  191. package/dist/translations/ur.js +1 -1
  192. package/dist/translations/ur.umd.js +1 -1
  193. package/dist/translations/uz.js +1 -1
  194. package/dist/translations/uz.umd.js +1 -1
  195. package/dist/translations/vi.js +1 -1
  196. package/dist/translations/vi.umd.js +1 -1
  197. package/dist/translations/zh-cn.js +1 -1
  198. package/dist/translations/zh-cn.umd.js +1 -1
  199. package/dist/translations/zh.js +1 -1
  200. package/dist/translations/zh.umd.js +1 -1
  201. package/dist/types/imageinsert/imageinsertui.d.ts +10 -2
  202. package/dist/types/imageinsert/imageinsertviaurlui.d.ts +31 -9
  203. package/dist/types/imageinsert/ui/imageinserturlview.d.ts +6 -49
  204. package/dist/types/imageupload/imageuploadui.d.ts +18 -1
  205. package/lang/contexts.json +7 -4
  206. package/lang/translations/ar.po +21 -9
  207. package/lang/translations/ast.po +19 -7
  208. package/lang/translations/az.po +19 -7
  209. package/lang/translations/bg.po +21 -9
  210. package/lang/translations/bn.po +21 -9
  211. package/lang/translations/bs.po +21 -9
  212. package/lang/translations/ca.po +21 -9
  213. package/lang/translations/cs.po +21 -9
  214. package/lang/translations/da.po +21 -9
  215. package/lang/translations/de-ch.po +21 -9
  216. package/lang/translations/de.po +21 -9
  217. package/lang/translations/el.po +21 -9
  218. package/lang/translations/en-au.po +21 -9
  219. package/lang/translations/en-gb.po +19 -7
  220. package/lang/translations/en.po +21 -9
  221. package/lang/translations/eo.po +19 -7
  222. package/lang/translations/es.po +21 -9
  223. package/lang/translations/et.po +21 -9
  224. package/lang/translations/eu.po +19 -7
  225. package/lang/translations/fa.po +19 -7
  226. package/lang/translations/fi.po +21 -9
  227. package/lang/translations/fr.po +21 -9
  228. package/lang/translations/gl.po +21 -9
  229. package/lang/translations/he.po +21 -9
  230. package/lang/translations/hi.po +21 -9
  231. package/lang/translations/hr.po +21 -9
  232. package/lang/translations/hu.po +21 -9
  233. package/lang/translations/id.po +21 -9
  234. package/lang/translations/it.po +21 -9
  235. package/lang/translations/ja.po +21 -9
  236. package/lang/translations/jv.po +21 -9
  237. package/lang/translations/km.po +19 -7
  238. package/lang/translations/kn.po +19 -7
  239. package/lang/translations/ko.po +21 -9
  240. package/lang/translations/ku.po +19 -7
  241. package/lang/translations/lt.po +21 -9
  242. package/lang/translations/lv.po +21 -9
  243. package/lang/translations/ms.po +21 -9
  244. package/lang/translations/nb.po +19 -7
  245. package/lang/translations/ne.po +19 -7
  246. package/lang/translations/nl.po +21 -9
  247. package/lang/translations/no.po +21 -9
  248. package/lang/translations/pl.po +21 -9
  249. package/lang/translations/pt-br.po +21 -9
  250. package/lang/translations/pt.po +21 -9
  251. package/lang/translations/ro.po +21 -9
  252. package/lang/translations/ru.po +21 -9
  253. package/lang/translations/si.po +19 -7
  254. package/lang/translations/sk.po +21 -9
  255. package/lang/translations/sq.po +19 -7
  256. package/lang/translations/sr-latn.po +21 -9
  257. package/lang/translations/sr.po +21 -9
  258. package/lang/translations/sv.po +21 -9
  259. package/lang/translations/th.po +21 -9
  260. package/lang/translations/ti.po +20 -8
  261. package/lang/translations/tk.po +19 -7
  262. package/lang/translations/tr.po +21 -9
  263. package/lang/translations/ug.po +20 -8
  264. package/lang/translations/uk.po +21 -9
  265. package/lang/translations/ur.po +19 -7
  266. package/lang/translations/uz.po +21 -9
  267. package/lang/translations/vi.po +21 -9
  268. package/lang/translations/zh-cn.po +21 -9
  269. package/lang/translations/zh.po +21 -9
  270. package/package.json +3 -3
  271. package/src/image/imageinlineediting.js +4 -9
  272. package/src/imageinsert/imageinsertui.d.ts +10 -2
  273. package/src/imageinsert/imageinsertui.js +41 -4
  274. package/src/imageinsert/imageinsertviaurlui.d.ts +31 -9
  275. package/src/imageinsert/imageinsertviaurlui.js +111 -64
  276. package/src/imageinsert/ui/imageinsertformview.js +0 -17
  277. package/src/imageinsert/ui/imageinserturlview.d.ts +6 -49
  278. package/src/imageinsert/ui/imageinserturlview.js +6 -74
  279. package/src/imageresize/imageresizebuttons.js +2 -2
  280. package/src/imagestyle/utils.js +17 -18
  281. package/src/imageupload/imageuploadui.d.ts +18 -1
  282. package/src/imageupload/imageuploadui.js +59 -38
  283. package/theme/imageinsert.css +3 -0
  284. package/theme/imagestyle.css +47 -34
  285. package/build/translations/es-co.js +0 -1
  286. package/build/translations/tt.js +0 -1
  287. package/dist/translations/es-co.d.ts +0 -8
  288. package/dist/translations/es-co.js +0 -5
  289. package/dist/translations/es-co.umd.js +0 -11
  290. package/dist/translations/tt.d.ts +0 -8
  291. package/dist/translations/tt.js +0 -5
  292. package/dist/translations/tt.umd.js +0 -11
  293. package/lang/translations/es-co.po +0 -178
  294. package/lang/translations/tt.po +0 -178
package/dist/index.js CHANGED
@@ -7,9 +7,9 @@ import { Clipboard, ClipboardPipeline } from '@ckeditor/ckeditor5-clipboard/dist
7
7
  import { LivePosition, LiveRange, Observer, UpcastWriter, enablePlaceholder, Element } from '@ckeditor/ckeditor5-engine/dist/index.js';
8
8
  import { Undo } from '@ckeditor/ckeditor5-undo/dist/index.js';
9
9
  import { Delete } from '@ckeditor/ckeditor5-typing/dist/index.js';
10
- import { first, global, DomEmitterMixin, FocusTracker, KeystrokeHandler, toArray, logWarning, env, CKEditorError, Collection, Rect } from '@ckeditor/ckeditor5-utils/dist/index.js';
10
+ import { first, DomEmitterMixin, global, FocusTracker, KeystrokeHandler, logWarning, toArray, env, CKEditorError, Collection, Rect } from '@ckeditor/ckeditor5-utils/dist/index.js';
11
11
  import { toWidget, isWidget, findOptimalInsertionRange, Widget, toWidgetEditable, WidgetResize, calculateResizeHostAncestorWidth, WidgetToolbarRepository } from '@ckeditor/ckeditor5-widget/dist/index.js';
12
- import { View, submitHandler, ButtonView, LabeledFieldView, createLabeledInputText, ViewCollection, FocusCycler, BalloonPanelView, ContextualBalloon, CssTransitionDisablerMixin, clickOutsideHandler, CollapsibleView, SplitButtonView, createDropdown, MenuBarMenuListItemFileDialogButtonView, FileDialogButtonView, Notification, DropdownButtonView, ViewModel, addListToDropdown, createLabeledInputNumber, addToolbarToDropdown } from '@ckeditor/ckeditor5-ui/dist/index.js';
12
+ import { View, ViewCollection, FocusCycler, submitHandler, ButtonView, LabeledFieldView, createLabeledInputText, BalloonPanelView, ContextualBalloon, CssTransitionDisablerMixin, clickOutsideHandler, CollapsibleView, SplitButtonView, createDropdown, MenuBarMenuView, MenuBarMenuListView, MenuBarMenuListItemView, FileDialogButtonView, MenuBarMenuListItemFileDialogButtonView, Notification, Dialog, MenuBarMenuListItemButtonView, ViewModel, DropdownButtonView, addListToDropdown, createLabeledInputNumber, addToolbarToDropdown } from '@ckeditor/ckeditor5-ui/dist/index.js';
13
13
  import { FileRepository } from '@ckeditor/ckeditor5-upload/dist/index.js';
14
14
  import { map, isObject, identity } from 'lodash-es';
15
15
 
@@ -129,56 +129,61 @@ import { map, isObject, identity } from 'lodash-es';
129
129
  }
130
130
 
131
131
  const IMAGE_WIDGETS_CLASSES_MATCH_REGEXP = /^(image|image-inline)$/;
132
- class ImageUtils extends Plugin {
132
+ /**
133
+ * A set of helpers related to images.
134
+ */ class ImageUtils extends Plugin {
135
+ /**
136
+ * DOM Emitter.
137
+ */ _domEmitter = new (DomEmitterMixin())();
133
138
  /**
134
- * @inheritDoc
135
- */ static get pluginName() {
139
+ * @inheritDoc
140
+ */ static get pluginName() {
136
141
  return 'ImageUtils';
137
142
  }
138
143
  /**
139
- * Checks if the provided model element is an `image` or `imageInline`.
140
- */ isImage(modelElement) {
144
+ * Checks if the provided model element is an `image` or `imageInline`.
145
+ */ isImage(modelElement) {
141
146
  return this.isInlineImage(modelElement) || this.isBlockImage(modelElement);
142
147
  }
143
148
  /**
144
- * Checks if the provided view element represents an inline image.
145
- *
146
- * Also, see {@link module:image/imageutils~ImageUtils#isImageWidget}.
147
- */ isInlineImageView(element) {
149
+ * Checks if the provided view element represents an inline image.
150
+ *
151
+ * Also, see {@link module:image/imageutils~ImageUtils#isImageWidget}.
152
+ */ isInlineImageView(element) {
148
153
  return !!element && element.is('element', 'img');
149
154
  }
150
155
  /**
151
- * Checks if the provided view element represents a block image.
152
- *
153
- * Also, see {@link module:image/imageutils~ImageUtils#isImageWidget}.
154
- */ isBlockImageView(element) {
156
+ * Checks if the provided view element represents a block image.
157
+ *
158
+ * Also, see {@link module:image/imageutils~ImageUtils#isImageWidget}.
159
+ */ isBlockImageView(element) {
155
160
  return !!element && element.is('element', 'figure') && element.hasClass('image');
156
161
  }
157
162
  /**
158
- * Handles inserting single file. This method unifies image insertion using {@link module:widget/utils~findOptimalInsertionRange}
159
- * method.
160
- *
161
- * ```ts
162
- * const imageUtils = editor.plugins.get( 'ImageUtils' );
163
- *
164
- * imageUtils.insertImage( { src: 'path/to/image.jpg' } );
165
- * ```
166
- *
167
- * @param attributes Attributes of the inserted image.
168
- * This method filters out the attributes which are disallowed by the {@link module:engine/model/schema~Schema}.
169
- * @param selectable Place to insert the image. If not specified,
170
- * the {@link module:widget/utils~findOptimalInsertionRange} logic will be applied for the block images
171
- * and `model.document.selection` for the inline images.
172
- *
173
- * **Note**: If `selectable` is passed, this helper will not be able to set selection attributes (such as `linkHref`)
174
- * and apply them to the new image. In this case, make sure all selection attributes are passed in `attributes`.
175
- *
176
- * @param imageType Image type of inserted image. If not specified,
177
- * it will be determined automatically depending of editor config or place of the insertion.
178
- * @param options.setImageSizes Specifies whether the image `width` and `height` attributes should be set automatically.
179
- * The default is `true`.
180
- * @return The inserted model image element.
181
- */ insertImage(attributes = {}, selectable = null, imageType = null, options = {}) {
163
+ * Handles inserting single file. This method unifies image insertion using {@link module:widget/utils~findOptimalInsertionRange}
164
+ * method.
165
+ *
166
+ * ```ts
167
+ * const imageUtils = editor.plugins.get( 'ImageUtils' );
168
+ *
169
+ * imageUtils.insertImage( { src: 'path/to/image.jpg' } );
170
+ * ```
171
+ *
172
+ * @param attributes Attributes of the inserted image.
173
+ * This method filters out the attributes which are disallowed by the {@link module:engine/model/schema~Schema}.
174
+ * @param selectable Place to insert the image. If not specified,
175
+ * the {@link module:widget/utils~findOptimalInsertionRange} logic will be applied for the block images
176
+ * and `model.document.selection` for the inline images.
177
+ *
178
+ * **Note**: If `selectable` is passed, this helper will not be able to set selection attributes (such as `linkHref`)
179
+ * and apply them to the new image. In this case, make sure all selection attributes are passed in `attributes`.
180
+ *
181
+ * @param imageType Image type of inserted image. If not specified,
182
+ * it will be determined automatically depending of editor config or place of the insertion.
183
+ * @param options.setImageSizes Specifies whether the image `width` and `height` attributes should be set automatically.
184
+ * The default is `true`.
185
+ * @return The inserted model image element.
186
+ */ insertImage(attributes = {}, selectable = null, imageType = null, options = {}) {
182
187
  const editor = this.editor;
183
188
  const model = editor.model;
184
189
  const selection = model.document.selection;
@@ -215,11 +220,11 @@ class ImageUtils extends Plugin {
215
220
  });
216
221
  }
217
222
  /**
218
- * Reads original image sizes and sets them as `width` and `height`.
219
- *
220
- * The `src` attribute may not be available if the user is using an upload adapter. In such a case,
221
- * this method is called again after the upload process is complete and the `src` attribute is available.
222
- */ setImageNaturalSizeAttributes(imageElement) {
223
+ * Reads original image sizes and sets them as `width` and `height`.
224
+ *
225
+ * The `src` attribute may not be available if the user is using an upload adapter. In such a case,
226
+ * this method is called again after the upload process is complete and the `src` attribute is available.
227
+ */ setImageNaturalSizeAttributes(imageElement) {
223
228
  const src = imageElement.getAttribute('src');
224
229
  if (!src) {
225
230
  return;
@@ -244,8 +249,8 @@ class ImageUtils extends Plugin {
244
249
  });
245
250
  }
246
251
  /**
247
- * Returns an image widget editing view element if one is selected or is among the selection's ancestors.
248
- */ getClosestSelectedImageWidget(selection) {
252
+ * Returns an image widget editing view element if one is selected or is among the selection's ancestors.
253
+ */ getClosestSelectedImageWidget(selection) {
249
254
  const selectionPosition = selection.getFirstPosition();
250
255
  if (!selectionPosition) {
251
256
  return null;
@@ -264,36 +269,36 @@ class ImageUtils extends Plugin {
264
269
  return null;
265
270
  }
266
271
  /**
267
- * Returns a image model element if one is selected or is among the selection's ancestors.
268
- */ getClosestSelectedImageElement(selection) {
272
+ * Returns a image model element if one is selected or is among the selection's ancestors.
273
+ */ getClosestSelectedImageElement(selection) {
269
274
  const selectedElement = selection.getSelectedElement();
270
275
  return this.isImage(selectedElement) ? selectedElement : selection.getFirstPosition().findAncestor('imageBlock');
271
276
  }
272
277
  /**
273
- * Returns an image widget editing view based on the passed image view.
274
- */ getImageWidgetFromImageView(imageView) {
278
+ * Returns an image widget editing view based on the passed image view.
279
+ */ getImageWidgetFromImageView(imageView) {
275
280
  return imageView.findAncestor({
276
281
  classes: IMAGE_WIDGETS_CLASSES_MATCH_REGEXP
277
282
  });
278
283
  }
279
284
  /**
280
- * Checks if image can be inserted at current model selection.
281
- *
282
- * @internal
283
- */ isImageAllowed() {
285
+ * Checks if image can be inserted at current model selection.
286
+ *
287
+ * @internal
288
+ */ isImageAllowed() {
284
289
  const model = this.editor.model;
285
290
  const selection = model.document.selection;
286
291
  return isImageAllowedInParent(this.editor, selection) && isNotInsideImage(selection);
287
292
  }
288
293
  /**
289
- * Converts a given {@link module:engine/view/element~Element} to an image widget:
290
- * * Adds a {@link module:engine/view/element~Element#_setCustomProperty custom property} allowing to recognize the image widget
291
- * element.
292
- * * Calls the {@link module:widget/utils~toWidget} function with the proper element's label creator.
293
- *
294
- * @param writer An instance of the view writer.
295
- * @param label The element's label. It will be concatenated with the image `alt` attribute if one is present.
296
- */ toImageWidget(viewElement, writer, label) {
294
+ * Converts a given {@link module:engine/view/element~Element} to an image widget:
295
+ * * Adds a {@link module:engine/view/element~Element#_setCustomProperty custom property} allowing to recognize the image widget
296
+ * element.
297
+ * * Calls the {@link module:widget/utils~toWidget} function with the proper element's label creator.
298
+ *
299
+ * @param writer An instance of the view writer.
300
+ * @param label The element's label. It will be concatenated with the image `alt` attribute if one is present.
301
+ */ toImageWidget(viewElement, writer, label) {
297
302
  writer.setCustomProperty('image', true, viewElement);
298
303
  const labelCreator = ()=>{
299
304
  const imgElement = this.findViewImgElement(viewElement);
@@ -305,25 +310,25 @@ class ImageUtils extends Plugin {
305
310
  });
306
311
  }
307
312
  /**
308
- * Checks if a given view element is an image widget.
309
- */ isImageWidget(viewElement) {
313
+ * Checks if a given view element is an image widget.
314
+ */ isImageWidget(viewElement) {
310
315
  return !!viewElement.getCustomProperty('image') && isWidget(viewElement);
311
316
  }
312
317
  /**
313
- * Checks if the provided model element is an `image`.
314
- */ isBlockImage(modelElement) {
318
+ * Checks if the provided model element is an `image`.
319
+ */ isBlockImage(modelElement) {
315
320
  return !!modelElement && modelElement.is('element', 'imageBlock');
316
321
  }
317
322
  /**
318
- * Checks if the provided model element is an `imageInline`.
319
- */ isInlineImage(modelElement) {
323
+ * Checks if the provided model element is an `imageInline`.
324
+ */ isInlineImage(modelElement) {
320
325
  return !!modelElement && modelElement.is('element', 'imageInline');
321
326
  }
322
327
  /**
323
- * Get the view `<img>` from another view element, e.g. a widget (`<figure class="image">`), a link (`<a>`).
324
- *
325
- * The `<img>` can be located deep in other elements, so this helper performs a deep tree search.
326
- */ findViewImgElement(figureView) {
328
+ * Get the view `<img>` from another view element, e.g. a widget (`<figure class="image">`), a link (`<a>`).
329
+ *
330
+ * The `<img>` can be located deep in other elements, so this helper performs a deep tree search.
331
+ */ findViewImgElement(figureView) {
327
332
  if (this.isInlineImageView(figureView)) {
328
333
  return figureView;
329
334
  }
@@ -335,17 +340,11 @@ class ImageUtils extends Plugin {
335
340
  }
336
341
  }
337
342
  /**
338
- * @inheritDoc
339
- */ destroy() {
343
+ * @inheritDoc
344
+ */ destroy() {
340
345
  this._domEmitter.stopListening();
341
346
  return super.destroy();
342
347
  }
343
- constructor(){
344
- super(...arguments);
345
- /**
346
- * DOM Emitter.
347
- */ this._domEmitter = new (DomEmitterMixin())();
348
- }
349
348
  }
350
349
  /**
351
350
  * Checks if image is allowed by schema in optimal insertion parent.
@@ -410,10 +409,13 @@ class ImageUtils extends Plugin {
410
409
 
411
410
  // Implements the pattern: http(s)://(www.)example.com/path/to/resource.ext?query=params&maybe=too.
412
411
  const IMAGE_URL_REGEXP = new RegExp(String(/^(http(s)?:\/\/)?[\w-]+\.[\w.~:/[\]@!$&'()*+,;=%-]+/.source + /\.(jpg|jpeg|png|gif|ico|webp|JPG|JPEG|PNG|GIF|ICO|WEBP)/.source + /(\?[\w.~:/[\]@!$&'()*+,;=%-]*)?/.source + /(#[\w.~:/[\]@!$&'()*+,;=%-]*)?$/.source));
413
- class AutoImage extends Plugin {
412
+ /**
413
+ * The auto-image plugin. It recognizes image links in the pasted content and embeds
414
+ * them shortly after they are injected into the document.
415
+ */ class AutoImage extends Plugin {
414
416
  /**
415
- * @inheritDoc
416
- */ static get requires() {
417
+ * @inheritDoc
418
+ */ static get requires() {
417
419
  return [
418
420
  Clipboard,
419
421
  ImageUtils,
@@ -422,13 +424,28 @@ class AutoImage extends Plugin {
422
424
  ];
423
425
  }
424
426
  /**
425
- * @inheritDoc
426
- */ static get pluginName() {
427
+ * @inheritDoc
428
+ */ static get pluginName() {
427
429
  return 'AutoImage';
428
430
  }
429
431
  /**
430
- * @inheritDoc
431
- */ init() {
432
+ * The paste–to–embed `setTimeout` ID. Stored as a property to allow
433
+ * cleaning of the timeout.
434
+ */ _timeoutId;
435
+ /**
436
+ * The position where the `<imageBlock>` element will be inserted after the timeout,
437
+ * determined each time a new content is pasted into the document.
438
+ */ _positionToInsert;
439
+ /**
440
+ * @inheritDoc
441
+ */ constructor(editor){
442
+ super(editor);
443
+ this._timeoutId = null;
444
+ this._positionToInsert = null;
445
+ }
446
+ /**
447
+ * @inheritDoc
448
+ */ init() {
432
449
  const editor = this.editor;
433
450
  const modelDocument = editor.model.document;
434
451
  const clipboardPipeline = editor.plugins.get('ClipboardPipeline');
@@ -461,12 +478,12 @@ class AutoImage extends Plugin {
461
478
  });
462
479
  }
463
480
  /**
464
- * Analyzes the part of the document between provided positions in search for a URL representing an image.
465
- * When the URL is found, it is automatically converted into an image.
466
- *
467
- * @param leftPosition Left position of the selection.
468
- * @param rightPosition Right position of the selection.
469
- */ _embedImageBetweenPositions(leftPosition, rightPosition) {
481
+ * Analyzes the part of the document between provided positions in search for a URL representing an image.
482
+ * When the URL is found, it is automatically converted into an image.
483
+ *
484
+ * @param leftPosition Left position of the selection.
485
+ * @param rightPosition Right position of the selection.
486
+ */ _embedImageBetweenPositions(leftPosition, rightPosition) {
470
487
  const editor = this.editor;
471
488
  // TODO: Use a marker instead of LiveRange & LivePositions.
472
489
  const urlRange = new LiveRange(leftPosition, rightPosition);
@@ -520,19 +537,14 @@ class AutoImage extends Plugin {
520
537
  deletePlugin.requestUndoOnBackspace();
521
538
  }, 100);
522
539
  }
523
- /**
524
- * @inheritDoc
525
- */ constructor(editor){
526
- super(editor);
527
- this._timeoutId = null;
528
- this._positionToInsert = null;
529
- }
530
540
  }
531
541
 
532
- class ImageTextAlternativeCommand extends Command {
542
+ /**
543
+ * The image text alternative command. It is used to change the `alt` attribute of `<imageBlock>` and `<imageInline>` model elements.
544
+ */ class ImageTextAlternativeCommand extends Command {
533
545
  /**
534
- * @inheritDoc
535
- */ refresh() {
546
+ * @inheritDoc
547
+ */ refresh() {
536
548
  const editor = this.editor;
537
549
  const imageUtils = editor.plugins.get('ImageUtils');
538
550
  const element = imageUtils.getClosestSelectedImageElement(this.editor.model.document.selection);
@@ -544,12 +556,12 @@ class ImageTextAlternativeCommand extends Command {
544
556
  }
545
557
  }
546
558
  /**
547
- * Executes the command.
548
- *
549
- * @fires execute
550
- * @param options
551
- * @param options.newValue The new value of the `alt` attribute to set.
552
- */ execute(options) {
559
+ * Executes the command.
560
+ *
561
+ * @fires execute
562
+ * @param options
563
+ * @param options.newValue The new value of the `alt` attribute to set.
564
+ */ execute(options) {
553
565
  const editor = this.editor;
554
566
  const imageUtils = editor.plugins.get('ImageUtils');
555
567
  const model = editor.model;
@@ -560,30 +572,98 @@ class ImageTextAlternativeCommand extends Command {
560
572
  }
561
573
  }
562
574
 
563
- class ImageTextAlternativeEditing extends Plugin {
575
+ /**
576
+ * The image text alternative editing plugin.
577
+ *
578
+ * Registers the `'imageTextAlternative'` command.
579
+ */ class ImageTextAlternativeEditing extends Plugin {
564
580
  /**
565
- * @inheritDoc
566
- */ static get requires() {
581
+ * @inheritDoc
582
+ */ static get requires() {
567
583
  return [
568
584
  ImageUtils
569
585
  ];
570
586
  }
571
587
  /**
572
- * @inheritDoc
573
- */ static get pluginName() {
588
+ * @inheritDoc
589
+ */ static get pluginName() {
574
590
  return 'ImageTextAlternativeEditing';
575
591
  }
576
592
  /**
577
- * @inheritDoc
578
- */ init() {
593
+ * @inheritDoc
594
+ */ init() {
579
595
  this.editor.commands.add('imageTextAlternative', new ImageTextAlternativeCommand(this.editor));
580
596
  }
581
597
  }
582
598
 
583
- class TextAlternativeFormView extends View {
599
+ /**
600
+ * The TextAlternativeFormView class.
601
+ */ class TextAlternativeFormView extends View {
602
+ /**
603
+ * Tracks information about the DOM focus in the form.
604
+ */ focusTracker;
605
+ /**
606
+ * An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
607
+ */ keystrokes;
608
+ /**
609
+ * An input with a label.
610
+ */ labeledInput;
611
+ /**
612
+ * A button used to submit the form.
613
+ */ saveButtonView;
614
+ /**
615
+ * A button used to cancel the form.
616
+ */ cancelButtonView;
617
+ /**
618
+ * A collection of views which can be focused in the form.
619
+ */ _focusables;
620
+ /**
621
+ * Helps cycling over {@link #_focusables} in the form.
622
+ */ _focusCycler;
623
+ /**
624
+ * @inheritDoc
625
+ */ constructor(locale){
626
+ super(locale);
627
+ const t = this.locale.t;
628
+ this.focusTracker = new FocusTracker();
629
+ this.keystrokes = new KeystrokeHandler();
630
+ this.labeledInput = this._createLabeledInputView();
631
+ this.saveButtonView = this._createButton(t('Save'), icons.check, 'ck-button-save');
632
+ this.saveButtonView.type = 'submit';
633
+ this.cancelButtonView = this._createButton(t('Cancel'), icons.cancel, 'ck-button-cancel', 'cancel');
634
+ this._focusables = new ViewCollection();
635
+ this._focusCycler = new FocusCycler({
636
+ focusables: this._focusables,
637
+ focusTracker: this.focusTracker,
638
+ keystrokeHandler: this.keystrokes,
639
+ actions: {
640
+ // Navigate form fields backwards using the Shift + Tab keystroke.
641
+ focusPrevious: 'shift + tab',
642
+ // Navigate form fields forwards using the Tab key.
643
+ focusNext: 'tab'
644
+ }
645
+ });
646
+ this.setTemplate({
647
+ tag: 'form',
648
+ attributes: {
649
+ class: [
650
+ 'ck',
651
+ 'ck-text-alternative-form',
652
+ 'ck-responsive-form'
653
+ ],
654
+ // https://github.com/ckeditor/ckeditor5-image/issues/40
655
+ tabindex: '-1'
656
+ },
657
+ children: [
658
+ this.labeledInput,
659
+ this.saveButtonView,
660
+ this.cancelButtonView
661
+ ]
662
+ });
663
+ }
584
664
  /**
585
- * @inheritDoc
586
- */ render() {
665
+ * @inheritDoc
666
+ */ render() {
587
667
  super.render();
588
668
  this.keystrokes.listenTo(this.element);
589
669
  submitHandler({
@@ -601,21 +681,21 @@ class TextAlternativeFormView extends View {
601
681
  });
602
682
  }
603
683
  /**
604
- * @inheritDoc
605
- */ destroy() {
684
+ * @inheritDoc
685
+ */ destroy() {
606
686
  super.destroy();
607
687
  this.focusTracker.destroy();
608
688
  this.keystrokes.destroy();
609
689
  }
610
690
  /**
611
- * Creates the button view.
612
- *
613
- * @param label The button label
614
- * @param icon The button's icon.
615
- * @param className The additional button CSS class name.
616
- * @param eventName The event name that the ButtonView#execute event will be delegated to.
617
- * @returns The button view instance.
618
- */ _createButton(label, icon, className, eventName) {
691
+ * Creates the button view.
692
+ *
693
+ * @param label The button label
694
+ * @param icon The button's icon.
695
+ * @param className The additional button CSS class name.
696
+ * @param eventName The event name that the ButtonView#execute event will be delegated to.
697
+ * @returns The button view instance.
698
+ */ _createButton(label, icon, className, eventName) {
619
699
  const button = new ButtonView(this.locale);
620
700
  button.set({
621
701
  label,
@@ -633,56 +713,15 @@ class TextAlternativeFormView extends View {
633
713
  return button;
634
714
  }
635
715
  /**
636
- * Creates an input with a label.
637
- *
638
- * @returns Labeled field view instance.
639
- */ _createLabeledInputView() {
716
+ * Creates an input with a label.
717
+ *
718
+ * @returns Labeled field view instance.
719
+ */ _createLabeledInputView() {
640
720
  const t = this.locale.t;
641
721
  const labeledInput = new LabeledFieldView(this.locale, createLabeledInputText);
642
722
  labeledInput.label = t('Text alternative');
643
723
  return labeledInput;
644
724
  }
645
- /**
646
- * @inheritDoc
647
- */ constructor(locale){
648
- super(locale);
649
- const t = this.locale.t;
650
- this.focusTracker = new FocusTracker();
651
- this.keystrokes = new KeystrokeHandler();
652
- this.labeledInput = this._createLabeledInputView();
653
- this.saveButtonView = this._createButton(t('Save'), icons.check, 'ck-button-save');
654
- this.saveButtonView.type = 'submit';
655
- this.cancelButtonView = this._createButton(t('Cancel'), icons.cancel, 'ck-button-cancel', 'cancel');
656
- this._focusables = new ViewCollection();
657
- this._focusCycler = new FocusCycler({
658
- focusables: this._focusables,
659
- focusTracker: this.focusTracker,
660
- keystrokeHandler: this.keystrokes,
661
- actions: {
662
- // Navigate form fields backwards using the Shift + Tab keystroke.
663
- focusPrevious: 'shift + tab',
664
- // Navigate form fields forwards using the Tab key.
665
- focusNext: 'tab'
666
- }
667
- });
668
- this.setTemplate({
669
- tag: 'form',
670
- attributes: {
671
- class: [
672
- 'ck',
673
- 'ck-text-alternative-form',
674
- 'ck-responsive-form'
675
- ],
676
- // https://github.com/ckeditor/ckeditor5-image/issues/40
677
- tabindex: '-1'
678
- },
679
- children: [
680
- this.labeledInput,
681
- this.saveButtonView,
682
- this.cancelButtonView
683
- ]
684
- });
685
- }
686
725
  }
687
726
 
688
727
  /**
@@ -723,27 +762,37 @@ class TextAlternativeFormView extends View {
723
762
  };
724
763
  }
725
764
 
726
- class ImageTextAlternativeUI extends Plugin {
765
+ /**
766
+ * The image text alternative UI plugin.
767
+ *
768
+ * The plugin uses the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon}.
769
+ */ class ImageTextAlternativeUI extends Plugin {
727
770
  /**
728
- * @inheritDoc
729
- */ static get requires() {
771
+ * The contextual balloon plugin instance.
772
+ */ _balloon;
773
+ /**
774
+ * A form containing a textarea and buttons, used to change the `alt` text value.
775
+ */ _form;
776
+ /**
777
+ * @inheritDoc
778
+ */ static get requires() {
730
779
  return [
731
780
  ContextualBalloon
732
781
  ];
733
782
  }
734
783
  /**
735
- * @inheritDoc
736
- */ static get pluginName() {
784
+ * @inheritDoc
785
+ */ static get pluginName() {
737
786
  return 'ImageTextAlternativeUI';
738
787
  }
739
788
  /**
740
- * @inheritDoc
741
- */ init() {
789
+ * @inheritDoc
790
+ */ init() {
742
791
  this._createButton();
743
792
  }
744
793
  /**
745
- * @inheritDoc
746
- */ destroy() {
794
+ * @inheritDoc
795
+ */ destroy() {
747
796
  super.destroy();
748
797
  // Destroy created UI components as they are not automatically destroyed (see ckeditor5#1341).
749
798
  if (this._form) {
@@ -751,9 +800,9 @@ class ImageTextAlternativeUI extends Plugin {
751
800
  }
752
801
  }
753
802
  /**
754
- * Creates a button showing the balloon panel for changing the image text alternative and
755
- * registers it in the editor {@link module:ui/componentfactory~ComponentFactory ComponentFactory}.
756
- */ _createButton() {
803
+ * Creates a button showing the balloon panel for changing the image text alternative and
804
+ * registers it in the editor {@link module:ui/componentfactory~ComponentFactory ComponentFactory}.
805
+ */ _createButton() {
757
806
  const editor = this.editor;
758
807
  const t = editor.t;
759
808
  editor.ui.componentFactory.add('imageTextAlternative', (locale)=>{
@@ -773,9 +822,9 @@ class ImageTextAlternativeUI extends Plugin {
773
822
  });
774
823
  }
775
824
  /**
776
- * Creates the {@link module:image/imagetextalternative/ui/textalternativeformview~TextAlternativeFormView}
777
- * form.
778
- */ _createForm() {
825
+ * Creates the {@link module:image/imagetextalternative/ui/textalternativeformview~TextAlternativeFormView}
826
+ * form.
827
+ */ _createForm() {
779
828
  const editor = this.editor;
780
829
  const view = editor.editing.view;
781
830
  const viewDocument = view.document;
@@ -817,8 +866,8 @@ class ImageTextAlternativeUI extends Plugin {
817
866
  });
818
867
  }
819
868
  /**
820
- * Shows the {@link #_form} in the {@link #_balloon}.
821
- */ _showForm() {
869
+ * Shows the {@link #_form} in the {@link #_balloon}.
870
+ */ _showForm() {
822
871
  if (this._isVisible) {
823
872
  return;
824
873
  }
@@ -845,10 +894,10 @@ class ImageTextAlternativeUI extends Plugin {
845
894
  this._form.enableCssTransitions();
846
895
  }
847
896
  /**
848
- * Removes the {@link #_form} from the {@link #_balloon}.
849
- *
850
- * @param focusEditable Controls whether the editing view is focused afterwards.
851
- */ _hideForm(focusEditable = false) {
897
+ * Removes the {@link #_form} from the {@link #_balloon}.
898
+ *
899
+ * @param focusEditable Controls whether the editing view is focused afterwards.
900
+ */ _hideForm(focusEditable = false) {
852
901
  if (!this._isInBalloon) {
853
902
  return;
854
903
  }
@@ -863,29 +912,37 @@ class ImageTextAlternativeUI extends Plugin {
863
912
  }
864
913
  }
865
914
  /**
866
- * Returns `true` when the {@link #_form} is the visible view in the {@link #_balloon}.
867
- */ get _isVisible() {
915
+ * Returns `true` when the {@link #_form} is the visible view in the {@link #_balloon}.
916
+ */ get _isVisible() {
868
917
  return !!this._balloon && this._balloon.visibleView === this._form;
869
918
  }
870
919
  /**
871
- * Returns `true` when the {@link #_form} is in the {@link #_balloon}.
872
- */ get _isInBalloon() {
920
+ * Returns `true` when the {@link #_form} is in the {@link #_balloon}.
921
+ */ get _isInBalloon() {
873
922
  return !!this._balloon && this._balloon.hasView(this._form);
874
923
  }
875
924
  }
876
925
 
877
- class ImageTextAlternative extends Plugin {
926
+ /**
927
+ * The image text alternative plugin.
928
+ *
929
+ * For a detailed overview, check the {@glink features/images/images-styles image styles} documentation.
930
+ *
931
+ * This is a "glue" plugin which loads the
932
+ * {@link module:image/imagetextalternative/imagetextalternativeediting~ImageTextAlternativeEditing}
933
+ * and {@link module:image/imagetextalternative/imagetextalternativeui~ImageTextAlternativeUI} plugins.
934
+ */ class ImageTextAlternative extends Plugin {
878
935
  /**
879
- * @inheritDoc
880
- */ static get requires() {
936
+ * @inheritDoc
937
+ */ static get requires() {
881
938
  return [
882
939
  ImageTextAlternativeEditing,
883
940
  ImageTextAlternativeUI
884
941
  ];
885
942
  }
886
943
  /**
887
- * @inheritDoc
888
- */ static get pluginName() {
944
+ * @inheritDoc
945
+ */ static get pluginName() {
889
946
  return 'ImageTextAlternative';
890
947
  }
891
948
  }
@@ -1133,10 +1190,17 @@ class ImageTextAlternative extends Plugin {
1133
1190
  };
1134
1191
  }
1135
1192
 
1136
- class ImageLoadObserver extends Observer {
1193
+ /**
1194
+ * Observes all new images added to the {@link module:engine/view/document~Document},
1195
+ * fires {@link module:engine/view/document~Document#event:imageLoaded} and
1196
+ * {@link module:engine/view/document~Document#event:layoutChanged} event every time when the new image
1197
+ * has been loaded.
1198
+ *
1199
+ * **Note:** This event is not fired for images that has been added to the document and rendered as `complete` (already loaded).
1200
+ */ class ImageLoadObserver extends Observer {
1137
1201
  /**
1138
- * @inheritDoc
1139
- */ observe(domRoot) {
1202
+ * @inheritDoc
1203
+ */ observe(domRoot) {
1140
1204
  this.listenTo(domRoot, 'load', (event, domEvent)=>{
1141
1205
  const domElement = domEvent.target;
1142
1206
  if (this.checkShouldIgnoreEventFromTarget(domElement)) {
@@ -1151,17 +1215,17 @@ class ImageLoadObserver extends Observer {
1151
1215
  });
1152
1216
  }
1153
1217
  /**
1154
- * @inheritDoc
1155
- */ stopObserving(domRoot) {
1218
+ * @inheritDoc
1219
+ */ stopObserving(domRoot) {
1156
1220
  this.stopListening(domRoot);
1157
1221
  }
1158
1222
  /**
1159
- * Fires {@link module:engine/view/document~Document#event:layoutChanged} and
1160
- * {@link module:engine/view/document~Document#event:imageLoaded}
1161
- * if observer {@link #isEnabled is enabled}.
1162
- *
1163
- * @param domEvent The DOM event.
1164
- */ _fireEvents(domEvent) {
1223
+ * Fires {@link module:engine/view/document~Document#event:layoutChanged} and
1224
+ * {@link module:engine/view/document~Document#event:imageLoaded}
1225
+ * if observer {@link #isEnabled is enabled}.
1226
+ *
1227
+ * @param domEvent The DOM event.
1228
+ */ _fireEvents(domEvent) {
1165
1229
  if (this.isEnabled) {
1166
1230
  this.document.fire('layoutChanged');
1167
1231
  this.document.fire('imageLoaded', domEvent);
@@ -1169,21 +1233,82 @@ class ImageLoadObserver extends Observer {
1169
1233
  }
1170
1234
  }
1171
1235
 
1172
- class InsertImageCommand extends Command {
1236
+ /**
1237
+ * Insert image command.
1238
+ *
1239
+ * The command is registered by the {@link module:image/image/imageediting~ImageEditing} plugin as `insertImage`
1240
+ * and it is also available via aliased `imageInsert` name.
1241
+ *
1242
+ * In order to insert an image at the current selection position
1243
+ * (according to the {@link module:widget/utils~findOptimalInsertionRange} algorithm),
1244
+ * execute the command and specify the image source:
1245
+ *
1246
+ * ```ts
1247
+ * editor.execute( 'insertImage', { source: 'http://url.to.the/image' } );
1248
+ * ```
1249
+ *
1250
+ * It is also possible to insert multiple images at once:
1251
+ *
1252
+ * ```ts
1253
+ * editor.execute( 'insertImage', {
1254
+ * source: [
1255
+ * 'path/to/image.jpg',
1256
+ * 'path/to/other-image.jpg'
1257
+ * ]
1258
+ * } );
1259
+ * ```
1260
+ *
1261
+ * If you want to take the full control over the process, you can specify individual model attributes:
1262
+ *
1263
+ * ```ts
1264
+ * editor.execute( 'insertImage', {
1265
+ * source: [
1266
+ * { src: 'path/to/image.jpg', alt: 'First alt text' },
1267
+ * { src: 'path/to/other-image.jpg', alt: 'Second alt text', customAttribute: 'My attribute value' }
1268
+ * ]
1269
+ * } );
1270
+ * ```
1271
+ */ class InsertImageCommand extends Command {
1272
+ /**
1273
+ * @inheritDoc
1274
+ */ constructor(editor){
1275
+ super(editor);
1276
+ const configImageInsertType = editor.config.get('image.insert.type');
1277
+ if (!editor.plugins.has('ImageBlockEditing')) {
1278
+ if (configImageInsertType === 'block') {
1279
+ /**
1280
+ * The {@link module:image/imageblock~ImageBlock} plugin must be enabled to allow inserting block images. See
1281
+ * {@link module:image/imageconfig~ImageInsertConfig#type} to learn more.
1282
+ *
1283
+ * @error image-block-plugin-required
1284
+ */ logWarning('image-block-plugin-required');
1285
+ }
1286
+ }
1287
+ if (!editor.plugins.has('ImageInlineEditing')) {
1288
+ if (configImageInsertType === 'inline') {
1289
+ /**
1290
+ * The {@link module:image/imageinline~ImageInline} plugin must be enabled to allow inserting inline images. See
1291
+ * {@link module:image/imageconfig~ImageInsertConfig#type} to learn more.
1292
+ *
1293
+ * @error image-inline-plugin-required
1294
+ */ logWarning('image-inline-plugin-required');
1295
+ }
1296
+ }
1297
+ }
1173
1298
  /**
1174
- * @inheritDoc
1175
- */ refresh() {
1299
+ * @inheritDoc
1300
+ */ refresh() {
1176
1301
  const imageUtils = this.editor.plugins.get('ImageUtils');
1177
1302
  this.isEnabled = imageUtils.isImageAllowed();
1178
1303
  }
1179
1304
  /**
1180
- * Executes the command.
1181
- *
1182
- * @fires execute
1183
- * @param options Options for the executed command.
1184
- * @param options.source The image source or an array of image sources to insert.
1185
- * See the documentation of the command to learn more about accepted formats.
1186
- */ execute(options) {
1305
+ * Executes the command.
1306
+ *
1307
+ * @fires execute
1308
+ * @param options Options for the executed command.
1309
+ * @param options.source The image source or an array of image sources to insert.
1310
+ * See the documentation of the command to learn more about accepted formats.
1311
+ */ execute(options) {
1187
1312
  const sourceDefinitions = toArray(options.source);
1188
1313
  const selection = this.editor.model.document.selection;
1189
1314
  const imageUtils = this.editor.plugins.get('ImageUtils');
@@ -1219,38 +1344,26 @@ class InsertImageCommand extends Command {
1219
1344
  }
1220
1345
  });
1221
1346
  }
1222
- /**
1223
- * @inheritDoc
1224
- */ constructor(editor){
1225
- super(editor);
1226
- const configImageInsertType = editor.config.get('image.insert.type');
1227
- if (!editor.plugins.has('ImageBlockEditing')) {
1228
- if (configImageInsertType === 'block') {
1229
- /**
1230
- * The {@link module:image/imageblock~ImageBlock} plugin must be enabled to allow inserting block images. See
1231
- * {@link module:image/imageconfig~ImageInsertConfig#type} to learn more.
1232
- *
1233
- * @error image-block-plugin-required
1234
- */ logWarning('image-block-plugin-required');
1235
- }
1236
- }
1237
- if (!editor.plugins.has('ImageInlineEditing')) {
1238
- if (configImageInsertType === 'inline') {
1239
- /**
1240
- * The {@link module:image/imageinline~ImageInline} plugin must be enabled to allow inserting inline images. See
1241
- * {@link module:image/imageconfig~ImageInsertConfig#type} to learn more.
1242
- *
1243
- * @error image-inline-plugin-required
1244
- */ logWarning('image-inline-plugin-required');
1245
- }
1246
- }
1247
- }
1248
1347
  }
1249
1348
 
1250
- class ReplaceImageSourceCommand extends Command {
1349
+ /**
1350
+ * @module image/image/replaceimagesourcecommand
1351
+ */ /**
1352
+ * Replace image source command.
1353
+ *
1354
+ * Changes image source to the one provided. Can be executed as follows:
1355
+ *
1356
+ * ```ts
1357
+ * editor.execute( 'replaceImageSource', { source: 'http://url.to.the/image' } );
1358
+ * ```
1359
+ */ class ReplaceImageSourceCommand extends Command {
1360
+ constructor(editor){
1361
+ super(editor);
1362
+ this.decorate('cleanupImage');
1363
+ }
1251
1364
  /**
1252
- * @inheritDoc
1253
- */ refresh() {
1365
+ * @inheritDoc
1366
+ */ refresh() {
1254
1367
  const editor = this.editor;
1255
1368
  const imageUtils = editor.plugins.get('ImageUtils');
1256
1369
  const element = this.editor.model.document.selection.getSelectedElement();
@@ -1258,12 +1371,12 @@ class ReplaceImageSourceCommand extends Command {
1258
1371
  this.value = this.isEnabled ? element.getAttribute('src') : null;
1259
1372
  }
1260
1373
  /**
1261
- * Executes the command.
1262
- *
1263
- * @fires execute
1264
- * @param options Options for the executed command.
1265
- * @param options.source The image source to replace.
1266
- */ execute(options) {
1374
+ * Executes the command.
1375
+ *
1376
+ * @fires execute
1377
+ * @param options Options for the executed command.
1378
+ * @param options.source The image source to replace.
1379
+ */ execute(options) {
1267
1380
  const image = this.editor.model.document.selection.getSelectedElement();
1268
1381
  const imageUtils = this.editor.plugins.get('ImageUtils');
1269
1382
  this.editor.model.change((writer)=>{
@@ -1273,51 +1386,53 @@ class ReplaceImageSourceCommand extends Command {
1273
1386
  });
1274
1387
  }
1275
1388
  /**
1276
- * Cleanup image attributes that are not relevant to the new source.
1277
- *
1278
- * Removed attributes are: 'srcset', 'sizes', 'sources', 'width', 'height', 'alt'.
1279
- *
1280
- * This method is decorated, to allow custom cleanup logic.
1281
- * For example, to remove 'myImageId' attribute after 'src' has changed:
1282
- *
1283
- * ```ts
1284
- * replaceImageSourceCommand.on( 'cleanupImage', ( eventInfo, [ writer, image ] ) => {
1285
- * writer.removeAttribute( 'myImageId', image );
1286
- * } );
1287
- * ```
1288
- */ cleanupImage(writer, image) {
1389
+ * Cleanup image attributes that are not relevant to the new source.
1390
+ *
1391
+ * Removed attributes are: 'srcset', 'sizes', 'sources', 'width', 'height', 'alt'.
1392
+ *
1393
+ * This method is decorated, to allow custom cleanup logic.
1394
+ * For example, to remove 'myImageId' attribute after 'src' has changed:
1395
+ *
1396
+ * ```ts
1397
+ * replaceImageSourceCommand.on( 'cleanupImage', ( eventInfo, [ writer, image ] ) => {
1398
+ * writer.removeAttribute( 'myImageId', image );
1399
+ * } );
1400
+ * ```
1401
+ */ cleanupImage(writer, image) {
1289
1402
  writer.removeAttribute('srcset', image);
1290
1403
  writer.removeAttribute('sizes', image);
1291
1404
  /**
1292
- * In case responsive images some attributes should be cleaned up.
1293
- * Check: https://github.com/ckeditor/ckeditor5/issues/15093
1294
- */ writer.removeAttribute('sources', image);
1405
+ * In case responsive images some attributes should be cleaned up.
1406
+ * Check: https://github.com/ckeditor/ckeditor5/issues/15093
1407
+ */ writer.removeAttribute('sources', image);
1295
1408
  writer.removeAttribute('width', image);
1296
1409
  writer.removeAttribute('height', image);
1297
1410
  writer.removeAttribute('alt', image);
1298
1411
  }
1299
- constructor(editor){
1300
- super(editor);
1301
- this.decorate('cleanupImage');
1302
- }
1303
1412
  }
1304
1413
 
1305
- class ImageEditing extends Plugin {
1414
+ /**
1415
+ * The image engine plugin. This module loads common code shared between
1416
+ * {@link module:image/image/imageinlineediting~ImageInlineEditing} and
1417
+ * {@link module:image/image/imageblockediting~ImageBlockEditing} plugins.
1418
+ *
1419
+ * This plugin registers the {@link module:image/image/insertimagecommand~InsertImageCommand 'insertImage'} command.
1420
+ */ class ImageEditing extends Plugin {
1306
1421
  /**
1307
- * @inheritDoc
1308
- */ static get requires() {
1422
+ * @inheritDoc
1423
+ */ static get requires() {
1309
1424
  return [
1310
1425
  ImageUtils
1311
1426
  ];
1312
1427
  }
1313
1428
  /**
1314
- * @inheritDoc
1315
- */ static get pluginName() {
1429
+ * @inheritDoc
1430
+ */ static get pluginName() {
1316
1431
  return 'ImageEditing';
1317
1432
  }
1318
1433
  /**
1319
- * @inheritDoc
1320
- */ init() {
1434
+ * @inheritDoc
1435
+ */ init() {
1321
1436
  const editor = this.editor;
1322
1437
  const conversion = editor.conversion;
1323
1438
  // See https://github.com/ckeditor/ckeditor5-image/issues/142.
@@ -1344,29 +1459,31 @@ class ImageEditing extends Plugin {
1344
1459
  }
1345
1460
  }
1346
1461
 
1347
- class ImageSizeAttributes extends Plugin {
1462
+ /**
1463
+ * This plugin enables `width` and `height` attributes in inline and block image elements.
1464
+ */ class ImageSizeAttributes extends Plugin {
1348
1465
  /**
1349
- * @inheritDoc
1350
- */ static get requires() {
1466
+ * @inheritDoc
1467
+ */ static get requires() {
1351
1468
  return [
1352
1469
  ImageUtils
1353
1470
  ];
1354
1471
  }
1355
1472
  /**
1356
- * @inheritDoc
1357
- */ static get pluginName() {
1473
+ * @inheritDoc
1474
+ */ static get pluginName() {
1358
1475
  return 'ImageSizeAttributes';
1359
1476
  }
1360
1477
  /**
1361
- * @inheritDoc
1362
- */ afterInit() {
1478
+ * @inheritDoc
1479
+ */ afterInit() {
1363
1480
  this._registerSchema();
1364
1481
  this._registerConverters('imageBlock');
1365
1482
  this._registerConverters('imageInline');
1366
1483
  }
1367
1484
  /**
1368
- * Registers the `width` and `height` attributes for inline and block images.
1369
- */ _registerSchema() {
1485
+ * Registers the `width` and `height` attributes for inline and block images.
1486
+ */ _registerSchema() {
1370
1487
  if (this.editor.plugins.has('ImageBlockEditing')) {
1371
1488
  this.editor.model.schema.extend('imageBlock', {
1372
1489
  allowAttributes: [
@@ -1385,8 +1502,8 @@ class ImageSizeAttributes extends Plugin {
1385
1502
  }
1386
1503
  }
1387
1504
  /**
1388
- * Registers converters for `width` and `height` attributes.
1389
- */ _registerConverters(imageType) {
1505
+ * Registers converters for `width` and `height` attributes.
1506
+ */ _registerConverters(imageType) {
1390
1507
  const editor = this.editor;
1391
1508
  const imageUtils = editor.plugins.get('ImageUtils');
1392
1509
  const viewElementName = imageType === 'imageBlock' ? 'figure' : 'img';
@@ -1476,10 +1593,23 @@ class ImageSizeAttributes extends Plugin {
1476
1593
  }
1477
1594
  }
1478
1595
 
1479
- class ImageTypeCommand extends Command {
1596
+ /**
1597
+ * The image type command. It changes the type of a selected image, depending on the configuration.
1598
+ */ class ImageTypeCommand extends Command {
1599
+ /**
1600
+ * Model element name the command converts to.
1601
+ */ _modelElementName;
1480
1602
  /**
1481
- * @inheritDoc
1482
- */ refresh() {
1603
+ * @inheritDoc
1604
+ *
1605
+ * @param modelElementName Model element name the command converts to.
1606
+ */ constructor(editor, modelElementName){
1607
+ super(editor);
1608
+ this._modelElementName = modelElementName;
1609
+ }
1610
+ /**
1611
+ * @inheritDoc
1612
+ */ refresh() {
1483
1613
  const editor = this.editor;
1484
1614
  const imageUtils = editor.plugins.get('ImageUtils');
1485
1615
  const element = imageUtils.getClosestSelectedImageElement(this.editor.model.document.selection);
@@ -1490,15 +1620,15 @@ class ImageTypeCommand extends Command {
1490
1620
  }
1491
1621
  }
1492
1622
  /**
1493
- * Executes the command and changes the type of a selected image.
1494
- *
1495
- * @fires execute
1496
- * @param options.setImageSizes Specifies whether the image `width` and `height` attributes should be set automatically.
1497
- * The default is `true`.
1498
- * @returns An object containing references to old and new model image elements
1499
- * (for before and after the change) so external integrations can hook into the decorated
1500
- * `execute` event and handle this change. `null` if the type change failed.
1501
- */ execute(options = {}) {
1623
+ * Executes the command and changes the type of a selected image.
1624
+ *
1625
+ * @fires execute
1626
+ * @param options.setImageSizes Specifies whether the image `width` and `height` attributes should be set automatically.
1627
+ * The default is `true`.
1628
+ * @returns An object containing references to old and new model image elements
1629
+ * (for before and after the change) so external integrations can hook into the decorated
1630
+ * `execute` event and handle this change. `null` if the type change failed.
1631
+ */ execute(options = {}) {
1502
1632
  const editor = this.editor;
1503
1633
  const model = this.editor.model;
1504
1634
  const imageUtils = editor.plugins.get('ImageUtils');
@@ -1537,39 +1667,33 @@ class ImageTypeCommand extends Command {
1537
1667
  };
1538
1668
  });
1539
1669
  }
1540
- /**
1541
- * @inheritDoc
1542
- *
1543
- * @param modelElementName Model element name the command converts to.
1544
- */ constructor(editor, modelElementName){
1545
- super(editor);
1546
- this._modelElementName = modelElementName;
1547
- }
1548
1670
  }
1549
1671
 
1550
- class ImagePlaceholder extends Plugin {
1672
+ /**
1673
+ * Adds support for image placeholder that is automatically removed when the image is loaded.
1674
+ */ class ImagePlaceholder extends Plugin {
1551
1675
  /**
1552
- * @inheritDoc
1553
- */ static get requires() {
1676
+ * @inheritDoc
1677
+ */ static get requires() {
1554
1678
  return [
1555
1679
  ImageUtils
1556
1680
  ];
1557
1681
  }
1558
1682
  /**
1559
- * @inheritDoc
1560
- */ static get pluginName() {
1683
+ * @inheritDoc
1684
+ */ static get pluginName() {
1561
1685
  return 'ImagePlaceholder';
1562
1686
  }
1563
1687
  /**
1564
- * @inheritDoc
1565
- */ afterInit() {
1688
+ * @inheritDoc
1689
+ */ afterInit() {
1566
1690
  this._setupSchema();
1567
1691
  this._setupConversion();
1568
1692
  this._setupLoadListener();
1569
1693
  }
1570
1694
  /**
1571
- * Extends model schema.
1572
- */ _setupSchema() {
1695
+ * Extends model schema.
1696
+ */ _setupSchema() {
1573
1697
  const schema = this.editor.model.schema;
1574
1698
  // Wait for ImageBlockEditing or ImageInlineEditing to register their elements first,
1575
1699
  // that's why doing this in afterInit() instead of init().
@@ -1589,8 +1713,8 @@ class ImagePlaceholder extends Plugin {
1589
1713
  }
1590
1714
  }
1591
1715
  /**
1592
- * Registers converters.
1593
- */ _setupConversion() {
1716
+ * Registers converters.
1717
+ */ _setupConversion() {
1594
1718
  const editor = this.editor;
1595
1719
  const conversion = editor.conversion;
1596
1720
  const imageUtils = editor.plugins.get('ImageUtils');
@@ -1618,8 +1742,8 @@ class ImagePlaceholder extends Plugin {
1618
1742
  });
1619
1743
  }
1620
1744
  /**
1621
- * Prepares listener for image load.
1622
- */ _setupLoadListener() {
1745
+ * Prepares listener for image load.
1746
+ */ _setupLoadListener() {
1623
1747
  const editor = this.editor;
1624
1748
  const model = editor.model;
1625
1749
  const editing = editor.editing;
@@ -1648,10 +1772,19 @@ class ImagePlaceholder extends Plugin {
1648
1772
  }
1649
1773
  }
1650
1774
 
1651
- class ImageBlockEditing extends Plugin {
1652
- /**
1653
- * @inheritDoc
1654
- */ static get requires() {
1775
+ /**
1776
+ * The image block plugin.
1777
+ *
1778
+ * It registers:
1779
+ *
1780
+ * * `<imageBlock>` as a block element in the document schema, and allows `alt`, `src` and `srcset` attributes.
1781
+ * * converters for editing and data pipelines.,
1782
+ * * {@link module:image/image/imagetypecommand~ImageTypeCommand `'imageTypeBlock'`} command that converts inline images into
1783
+ * block images.
1784
+ */ class ImageBlockEditing extends Plugin {
1785
+ /**
1786
+ * @inheritDoc
1787
+ */ static get requires() {
1655
1788
  return [
1656
1789
  ImageEditing,
1657
1790
  ImageSizeAttributes,
@@ -1661,13 +1794,13 @@ class ImageBlockEditing extends Plugin {
1661
1794
  ];
1662
1795
  }
1663
1796
  /**
1664
- * @inheritDoc
1665
- */ static get pluginName() {
1797
+ * @inheritDoc
1798
+ */ static get pluginName() {
1666
1799
  return 'ImageBlockEditing';
1667
1800
  }
1668
1801
  /**
1669
- * @inheritDoc
1670
- */ init() {
1802
+ * @inheritDoc
1803
+ */ init() {
1671
1804
  const editor = this.editor;
1672
1805
  const schema = editor.model.schema;
1673
1806
  // Converters 'alt' and 'srcset' are added in 'ImageEditing' plugin.
@@ -1686,9 +1819,9 @@ class ImageBlockEditing extends Plugin {
1686
1819
  }
1687
1820
  }
1688
1821
  /**
1689
- * Configures conversion pipelines to support upcasting and downcasting
1690
- * block images (block image widgets) and their attributes.
1691
- */ _setupConversion() {
1822
+ * Configures conversion pipelines to support upcasting and downcasting
1823
+ * block images (block image widgets) and their attributes.
1824
+ */ _setupConversion() {
1692
1825
  const editor = this.editor;
1693
1826
  const t = editor.t;
1694
1827
  const conversion = editor.conversion;
@@ -1711,21 +1844,21 @@ class ImageBlockEditing extends Plugin {
1711
1844
  }).add(upcastImageFigure(imageUtils));
1712
1845
  }
1713
1846
  /**
1714
- * Integrates the plugin with the clipboard pipeline.
1715
- *
1716
- * Idea is that the feature should recognize the user's intent when an **inline** image is
1717
- * pasted or dropped. If such an image is pasted/dropped:
1718
- *
1719
- * * into an empty block (e.g. an empty paragraph),
1720
- * * on another object (e.g. some block widget).
1721
- *
1722
- * it gets converted into a block image on the fly. We assume this is the user's intent
1723
- * if they decided to put their image there.
1724
- *
1725
- * See the `ImageInlineEditing` for the similar integration that works in the opposite direction.
1726
- *
1727
- * The feature also sets image `width` and `height` attributes on paste.
1728
- */ _setupClipboardIntegration() {
1847
+ * Integrates the plugin with the clipboard pipeline.
1848
+ *
1849
+ * Idea is that the feature should recognize the user's intent when an **inline** image is
1850
+ * pasted or dropped. If such an image is pasted/dropped:
1851
+ *
1852
+ * * into an empty block (e.g. an empty paragraph),
1853
+ * * on another object (e.g. some block widget).
1854
+ *
1855
+ * it gets converted into a block image on the fly. We assume this is the user's intent
1856
+ * if they decided to put their image there.
1857
+ *
1858
+ * See the `ImageInlineEditing` for the similar integration that works in the opposite direction.
1859
+ *
1860
+ * The feature also sets image `width` and `height` attributes on paste.
1861
+ */ _setupClipboardIntegration() {
1729
1862
  const editor = this.editor;
1730
1863
  const model = editor.model;
1731
1864
  const editingView = editor.editing.view;
@@ -1775,46 +1908,32 @@ class ImageBlockEditing extends Plugin {
1775
1908
  }
1776
1909
  }
1777
1910
 
1778
- class ImageInsertFormView extends View {
1911
+ /**
1912
+ * The view displayed in the insert image dropdown.
1913
+ *
1914
+ * See {@link module:image/imageinsert/imageinsertui~ImageInsertUI}.
1915
+ */ class ImageInsertFormView extends View {
1779
1916
  /**
1780
- * @inheritDoc
1781
- */ render() {
1782
- super.render();
1783
- submitHandler({
1784
- view: this
1785
- });
1786
- for (const view of this._focusables){
1787
- this.focusTracker.add(view.element);
1788
- }
1789
- // Start listening for the keystrokes coming from #element.
1790
- this.keystrokes.listenTo(this.element);
1791
- const stopPropagation = (data)=>data.stopPropagation();
1792
- // Since the form is in the dropdown panel which is a child of the toolbar, the toolbar's
1793
- // keystroke handler would take over the key management in the URL input. We need to prevent
1794
- // this ASAP. Otherwise, the basic caret movement using the arrow keys will be impossible.
1795
- this.keystrokes.set('arrowright', stopPropagation);
1796
- this.keystrokes.set('arrowleft', stopPropagation);
1797
- this.keystrokes.set('arrowup', stopPropagation);
1798
- this.keystrokes.set('arrowdown', stopPropagation);
1799
- }
1917
+ * Tracks information about DOM focus in the form.
1918
+ */ focusTracker;
1800
1919
  /**
1801
- * @inheritDoc
1802
- */ destroy() {
1803
- super.destroy();
1804
- this.focusTracker.destroy();
1805
- this.keystrokes.destroy();
1806
- }
1920
+ * An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
1921
+ */ keystrokes;
1807
1922
  /**
1808
- * Focuses the first {@link #_focusables focusable} in the form.
1809
- */ focus() {
1810
- this._focusCycler.focusFirst();
1811
- }
1923
+ * A collection of views that can be focused in the form.
1924
+ */ _focusables;
1925
+ /**
1926
+ * Helps cycling over {@link #_focusables} in the form.
1927
+ */ _focusCycler;
1812
1928
  /**
1813
- * Creates a view for the dropdown panel of {@link module:image/imageinsert/imageinsertui~ImageInsertUI}.
1814
- *
1815
- * @param locale The localization services instance.
1816
- * @param integrations An integrations object that contains components (or tokens for components) to be shown in the panel view.
1817
- */ constructor(locale, integrations = []){
1929
+ * A collection of the defined integrations for inserting the images.
1930
+ */ children;
1931
+ /**
1932
+ * Creates a view for the dropdown panel of {@link module:image/imageinsert/imageinsertui~ImageInsertUI}.
1933
+ *
1934
+ * @param locale The localization services instance.
1935
+ * @param integrations An integrations object that contains components (or tokens for components) to be shown in the panel view.
1936
+ */ constructor(locale, integrations = []){
1818
1937
  super(locale);
1819
1938
  this.focusTracker = new FocusTracker();
1820
1939
  this.keystrokes = new KeystrokeHandler();
@@ -1838,20 +1957,6 @@ class ImageInsertFormView extends View {
1838
1957
  this._focusables.addMany(view.children);
1839
1958
  }
1840
1959
  }
1841
- if (this._focusables.length > 1) {
1842
- for (const view of this._focusables){
1843
- if (isViewWithFocusCycler(view)) {
1844
- view.focusCycler.on('forwardCycle', (evt)=>{
1845
- this._focusCycler.focusNext();
1846
- evt.stop();
1847
- });
1848
- view.focusCycler.on('backwardCycle', (evt)=>{
1849
- this._focusCycler.focusPrevious();
1850
- evt.stop();
1851
- });
1852
- }
1853
- }
1854
- }
1855
1960
  this.setTemplate({
1856
1961
  tag: 'form',
1857
1962
  attributes: {
@@ -1864,63 +1969,123 @@ class ImageInsertFormView extends View {
1864
1969
  children: this.children
1865
1970
  });
1866
1971
  }
1867
- }
1868
- function isViewWithFocusCycler(view) {
1869
- return 'focusCycler' in view;
1972
+ /**
1973
+ * @inheritDoc
1974
+ */ render() {
1975
+ super.render();
1976
+ submitHandler({
1977
+ view: this
1978
+ });
1979
+ for (const view of this._focusables){
1980
+ this.focusTracker.add(view.element);
1981
+ }
1982
+ // Start listening for the keystrokes coming from #element.
1983
+ this.keystrokes.listenTo(this.element);
1984
+ const stopPropagation = (data)=>data.stopPropagation();
1985
+ // Since the form is in the dropdown panel which is a child of the toolbar, the toolbar's
1986
+ // keystroke handler would take over the key management in the URL input. We need to prevent
1987
+ // this ASAP. Otherwise, the basic caret movement using the arrow keys will be impossible.
1988
+ this.keystrokes.set('arrowright', stopPropagation);
1989
+ this.keystrokes.set('arrowleft', stopPropagation);
1990
+ this.keystrokes.set('arrowup', stopPropagation);
1991
+ this.keystrokes.set('arrowdown', stopPropagation);
1992
+ }
1993
+ /**
1994
+ * @inheritDoc
1995
+ */ destroy() {
1996
+ super.destroy();
1997
+ this.focusTracker.destroy();
1998
+ this.keystrokes.destroy();
1999
+ }
2000
+ /**
2001
+ * Focuses the first {@link #_focusables focusable} in the form.
2002
+ */ focus() {
2003
+ this._focusCycler.focusFirst();
2004
+ }
1870
2005
  }
1871
2006
 
1872
- class ImageInsertUI extends Plugin {
2007
+ /**
2008
+ * The image insert dropdown plugin.
2009
+ *
2010
+ * For a detailed overview, check the {@glink features/images/image-upload/image-upload Image upload feature}
2011
+ * and {@glink features/images/images-inserting Insert images via source URL} documentation.
2012
+ *
2013
+ * Adds the `'insertImage'` dropdown to the {@link module:ui/componentfactory~ComponentFactory UI component factory}
2014
+ * and also the `imageInsert` dropdown as an alias for backward compatibility.
2015
+ *
2016
+ * Adds the `'menuBar:insertImage'` sub-menu to the {@link module:ui/componentfactory~ComponentFactory UI component factory}, which is
2017
+ * by default added to the `'Insert'` menu.
2018
+ */ class ImageInsertUI extends Plugin {
1873
2019
  /**
1874
- * @inheritDoc
1875
- */ static get pluginName() {
2020
+ * @inheritDoc
2021
+ */ static get pluginName() {
1876
2022
  return 'ImageInsertUI';
1877
2023
  }
1878
2024
  /**
1879
- * @inheritDoc
1880
- */ static get requires() {
2025
+ * @inheritDoc
2026
+ */ static get requires() {
1881
2027
  return [
1882
2028
  ImageUtils
1883
2029
  ];
1884
2030
  }
1885
2031
  /**
1886
- * @inheritDoc
1887
- */ init() {
1888
- const editor = this.editor;
1889
- const selection = editor.model.document.selection;
2032
+ * The dropdown view responsible for displaying the image insert UI.
2033
+ */ dropdownView;
2034
+ /**
2035
+ * Registered integrations map.
2036
+ */ _integrations = new Map();
2037
+ /**
2038
+ * @inheritDoc
2039
+ */ constructor(editor){
2040
+ super(editor);
2041
+ editor.config.define('image.insert.integrations', [
2042
+ 'upload',
2043
+ 'assetManager',
2044
+ 'url'
2045
+ ]);
2046
+ }
2047
+ /**
2048
+ * @inheritDoc
2049
+ */ init() {
2050
+ const editor = this.editor;
2051
+ const selection = editor.model.document.selection;
1890
2052
  const imageUtils = editor.plugins.get('ImageUtils');
1891
2053
  this.set('isImageSelected', false);
1892
2054
  this.listenTo(editor.model.document, 'change', ()=>{
1893
2055
  this.isImageSelected = imageUtils.isImage(selection.getSelectedElement());
1894
2056
  });
1895
2057
  const componentCreator = (locale)=>this._createToolbarComponent(locale);
2058
+ const menuBarComponentCreator = (locale)=>this._createMenuBarComponent(locale);
1896
2059
  // Register `insertImage` dropdown and add `imageInsert` dropdown as an alias for backward compatibility.
1897
2060
  editor.ui.componentFactory.add('insertImage', componentCreator);
1898
2061
  editor.ui.componentFactory.add('imageInsert', componentCreator);
2062
+ editor.ui.componentFactory.add('menuBar:insertImage', menuBarComponentCreator);
1899
2063
  }
1900
2064
  /**
1901
- * Registers the insert image dropdown integration.
1902
- */ registerIntegration({ name, observable, buttonViewCreator, formViewCreator, requiresForm }) {
2065
+ * Registers the insert image dropdown integration.
2066
+ */ registerIntegration({ name, observable, buttonViewCreator, formViewCreator, menuBarButtonViewCreator, requiresForm = false }) {
1903
2067
  if (this._integrations.has(name)) {
1904
2068
  /**
1905
- * There are two insert-image integrations registered with the same name.
1906
- *
1907
- * Make sure that you do not load multiple asset manager plugins.
1908
- *
1909
- * @error image-insert-integration-exists
1910
- */ logWarning('image-insert-integration-exists', {
2069
+ * There are two insert-image integrations registered with the same name.
2070
+ *
2071
+ * Make sure that you do not load multiple asset manager plugins.
2072
+ *
2073
+ * @error image-insert-integration-exists
2074
+ */ logWarning('image-insert-integration-exists', {
1911
2075
  name
1912
2076
  });
1913
2077
  }
1914
2078
  this._integrations.set(name, {
1915
2079
  observable,
1916
2080
  buttonViewCreator,
2081
+ menuBarButtonViewCreator,
1917
2082
  formViewCreator,
1918
- requiresForm: !!requiresForm
2083
+ requiresForm
1919
2084
  });
1920
2085
  }
1921
2086
  /**
1922
- * Creates the toolbar component.
1923
- */ _createToolbarComponent(locale) {
2087
+ * Creates the toolbar component.
2088
+ */ _createToolbarComponent(locale) {
1924
2089
  const editor = this.editor;
1925
2090
  const t = locale.t;
1926
2091
  const integrations = this._prepareIntegrations();
@@ -1952,21 +2117,50 @@ class ImageInsertUI extends Plugin {
1952
2117
  return dropdownView;
1953
2118
  }
1954
2119
  /**
1955
- * Validates the integrations list.
1956
- */ _prepareIntegrations() {
2120
+ * Creates the menu bar component.
2121
+ */ _createMenuBarComponent(locale) {
2122
+ const t = locale.t;
2123
+ const integrations = this._prepareIntegrations();
2124
+ if (!integrations.length) {
2125
+ return null;
2126
+ }
2127
+ let resultView;
2128
+ const firstIntegration = integrations[0];
2129
+ if (integrations.length == 1) {
2130
+ resultView = firstIntegration.menuBarButtonViewCreator(true);
2131
+ } else {
2132
+ resultView = new MenuBarMenuView(locale);
2133
+ const listView = new MenuBarMenuListView(locale);
2134
+ resultView.panelView.children.add(listView);
2135
+ resultView.buttonView.set({
2136
+ icon: icons.image,
2137
+ label: t('Image')
2138
+ });
2139
+ for (const integration of integrations){
2140
+ const listItemView = new MenuBarMenuListItemView(locale, resultView);
2141
+ const buttonView = integration.menuBarButtonViewCreator(false);
2142
+ listItemView.children.add(buttonView);
2143
+ listView.items.add(listItemView);
2144
+ }
2145
+ }
2146
+ return resultView;
2147
+ }
2148
+ /**
2149
+ * Validates the integrations list.
2150
+ */ _prepareIntegrations() {
1957
2151
  const editor = this.editor;
1958
2152
  const items = editor.config.get('image.insert.integrations');
1959
2153
  const result = [];
1960
2154
  if (!items.length) {
1961
2155
  /**
1962
- * The insert image feature requires a list of integrations to be provided in the editor configuration.
1963
- *
1964
- * The default list of integrations is `upload`, `assetManager`, `url`. Those integrations are included
1965
- * in the insert image dropdown if the given feature plugin is loaded. You should omit the `integrations`
1966
- * configuration key to use the default set or provide a selected list of integrations that should be used.
1967
- *
1968
- * @error image-insert-integrations-not-specified
1969
- */ logWarning('image-insert-integrations-not-specified');
2156
+ * The insert image feature requires a list of integrations to be provided in the editor configuration.
2157
+ *
2158
+ * The default list of integrations is `upload`, `assetManager`, `url`. Those integrations are included
2159
+ * in the insert image dropdown if the given feature plugin is loaded. You should omit the `integrations`
2160
+ * configuration key to use the default set or provide a selected list of integrations that should be used.
2161
+ *
2162
+ * @error image-insert-integrations-not-specified
2163
+ */ logWarning('image-insert-integrations-not-specified');
1970
2164
  return result;
1971
2165
  }
1972
2166
  for (const item of items){
@@ -1977,10 +2171,10 @@ class ImageInsertUI extends Plugin {
1977
2171
  'url'
1978
2172
  ].includes(item)) {
1979
2173
  /**
1980
- * The specified insert image integration name is unknown or the providing plugin is not loaded in the editor.
1981
- *
1982
- * @error image-insert-unknown-integration
1983
- */ logWarning('image-insert-unknown-integration', {
2174
+ * The specified insert image integration name is unknown or the providing plugin is not loaded in the editor.
2175
+ *
2176
+ * @error image-insert-unknown-integration
2177
+ */ logWarning('image-insert-unknown-integration', {
1984
2178
  item
1985
2179
  });
1986
2180
  }
@@ -1990,38 +2184,35 @@ class ImageInsertUI extends Plugin {
1990
2184
  }
1991
2185
  if (!result.length) {
1992
2186
  /**
1993
- * The image insert feature requires integrations to be registered by separate features.
1994
- *
1995
- * The `insertImage` toolbar button requires integrations to be registered by other features.
1996
- * For example {@link module:image/imageupload~ImageUpload ImageUpload},
1997
- * {@link module:image/imageinsert~ImageInsert ImageInsert},
1998
- * {@link module:image/imageinsertviaurl~ImageInsertViaUrl ImageInsertViaUrl},
1999
- * {@link module:ckbox/ckbox~CKBox CKBox}
2000
- *
2001
- * @error image-insert-integrations-not-registered
2002
- */ logWarning('image-insert-integrations-not-registered');
2187
+ * The image insert feature requires integrations to be registered by separate features.
2188
+ *
2189
+ * The `insertImage` toolbar button requires integrations to be registered by other features.
2190
+ * For example {@link module:image/imageupload~ImageUpload ImageUpload},
2191
+ * {@link module:image/imageinsert~ImageInsert ImageInsert},
2192
+ * {@link module:image/imageinsertviaurl~ImageInsertViaUrl ImageInsertViaUrl},
2193
+ * {@link module:ckbox/ckbox~CKBox CKBox}
2194
+ *
2195
+ * @error image-insert-integrations-not-registered
2196
+ */ logWarning('image-insert-integrations-not-registered');
2003
2197
  }
2004
2198
  return result;
2005
2199
  }
2006
- /**
2007
- * @inheritDoc
2008
- */ constructor(editor){
2009
- super(editor);
2010
- /**
2011
- * Registered integrations map.
2012
- */ this._integrations = new Map();
2013
- editor.config.define('image.insert.integrations', [
2014
- 'upload',
2015
- 'assetManager',
2016
- 'url'
2017
- ]);
2018
- }
2019
2200
  }
2020
2201
 
2021
- class ImageBlock extends Plugin {
2202
+ /**
2203
+ * The image block plugin.
2204
+ *
2205
+ * This is a "glue" plugin which loads the following plugins:
2206
+ *
2207
+ * * {@link module:image/image/imageblockediting~ImageBlockEditing},
2208
+ * * {@link module:image/imagetextalternative~ImageTextAlternative}.
2209
+ *
2210
+ * Usually, it is used in conjunction with other plugins from this package. See the {@glink api/image package page}
2211
+ * for more information.
2212
+ */ class ImageBlock extends Plugin {
2022
2213
  /**
2023
- * @inheritDoc
2024
- */ static get requires() {
2214
+ * @inheritDoc
2215
+ */ static get requires() {
2025
2216
  return [
2026
2217
  ImageBlockEditing,
2027
2218
  Widget,
@@ -2030,16 +2221,25 @@ class ImageBlock extends Plugin {
2030
2221
  ];
2031
2222
  }
2032
2223
  /**
2033
- * @inheritDoc
2034
- */ static get pluginName() {
2224
+ * @inheritDoc
2225
+ */ static get pluginName() {
2035
2226
  return 'ImageBlock';
2036
2227
  }
2037
2228
  }
2038
2229
 
2039
- class ImageInlineEditing extends Plugin {
2040
- /**
2041
- * @inheritDoc
2042
- */ static get requires() {
2230
+ /**
2231
+ * The image inline plugin.
2232
+ *
2233
+ * It registers:
2234
+ *
2235
+ * * `<imageInline>` as an inline element in the document schema, and allows `alt`, `src` and `srcset` attributes.
2236
+ * * converters for editing and data pipelines.
2237
+ * * {@link module:image/image/imagetypecommand~ImageTypeCommand `'imageTypeInline'`} command that converts block images into
2238
+ * inline images.
2239
+ */ class ImageInlineEditing extends Plugin {
2240
+ /**
2241
+ * @inheritDoc
2242
+ */ static get requires() {
2043
2243
  return [
2044
2244
  ImageEditing,
2045
2245
  ImageSizeAttributes,
@@ -2049,13 +2249,13 @@ class ImageInlineEditing extends Plugin {
2049
2249
  ];
2050
2250
  }
2051
2251
  /**
2052
- * @inheritDoc
2053
- */ static get pluginName() {
2252
+ * @inheritDoc
2253
+ */ static get pluginName() {
2054
2254
  return 'ImageInlineEditing';
2055
2255
  }
2056
2256
  /**
2057
- * @inheritDoc
2058
- */ init() {
2257
+ * @inheritDoc
2258
+ */ init() {
2059
2259
  const editor = this.editor;
2060
2260
  const schema = editor.model.schema;
2061
2261
  // Converters 'alt' and 'srcset' are added in 'ImageEditing' plugin.
@@ -2065,16 +2265,13 @@ class ImageInlineEditing extends Plugin {
2065
2265
  'alt',
2066
2266
  'src',
2067
2267
  'srcset'
2268
+ ],
2269
+ // Disallow inline images in captions (at least for now).
2270
+ // This is the best spot to do that because independent packages can introduce captions (ImageCaption, TableCaption, etc.).
2271
+ disallowIn: [
2272
+ 'caption'
2068
2273
  ]
2069
2274
  });
2070
- // Disallow inline images in captions (for now). This is the best spot to do that because
2071
- // independent packages can introduce captions (ImageCaption, TableCaption, etc.) so better this
2072
- // be future-proof.
2073
- schema.addChildCheck((context, childDefinition)=>{
2074
- if (context.endsWith('caption') && childDefinition.name === 'imageInline') {
2075
- return false;
2076
- }
2077
- });
2078
2275
  this._setupConversion();
2079
2276
  if (editor.plugins.has('ImageBlockEditing')) {
2080
2277
  editor.commands.add('imageTypeInline', new ImageTypeCommand(this.editor, 'imageInline'));
@@ -2082,9 +2279,9 @@ class ImageInlineEditing extends Plugin {
2082
2279
  }
2083
2280
  }
2084
2281
  /**
2085
- * Configures conversion pipelines to support upcasting and downcasting
2086
- * inline images (inline image widgets) and their attributes.
2087
- */ _setupConversion() {
2282
+ * Configures conversion pipelines to support upcasting and downcasting
2283
+ * inline images (inline image widgets) and their attributes.
2284
+ */ _setupConversion() {
2088
2285
  const editor = this.editor;
2089
2286
  const t = editor.t;
2090
2287
  const conversion = editor.conversion;
@@ -2107,22 +2304,22 @@ class ImageInlineEditing extends Plugin {
2107
2304
  });
2108
2305
  }
2109
2306
  /**
2110
- * Integrates the plugin with the clipboard pipeline.
2111
- *
2112
- * Idea is that the feature should recognize the user's intent when an **block** image is
2113
- * pasted or dropped. If such an image is pasted/dropped into a non-empty block
2114
- * (e.g. a paragraph with some text) it gets converted into an inline image on the fly.
2115
- *
2116
- * We assume this is the user's intent if they decided to put their image there.
2117
- *
2118
- * **Note**: If a block image has a caption, it will not be converted to an inline image
2119
- * to avoid the confusion. Captions are added on purpose and they should never be lost
2120
- * in the clipboard pipeline.
2121
- *
2122
- * See the `ImageBlockEditing` for the similar integration that works in the opposite direction.
2123
- *
2124
- * The feature also sets image `width` and `height` attributes when pasting.
2125
- */ _setupClipboardIntegration() {
2307
+ * Integrates the plugin with the clipboard pipeline.
2308
+ *
2309
+ * Idea is that the feature should recognize the user's intent when an **block** image is
2310
+ * pasted or dropped. If such an image is pasted/dropped into a non-empty block
2311
+ * (e.g. a paragraph with some text) it gets converted into an inline image on the fly.
2312
+ *
2313
+ * We assume this is the user's intent if they decided to put their image there.
2314
+ *
2315
+ * **Note**: If a block image has a caption, it will not be converted to an inline image
2316
+ * to avoid the confusion. Captions are added on purpose and they should never be lost
2317
+ * in the clipboard pipeline.
2318
+ *
2319
+ * See the `ImageBlockEditing` for the similar integration that works in the opposite direction.
2320
+ *
2321
+ * The feature also sets image `width` and `height` attributes when pasting.
2322
+ */ _setupClipboardIntegration() {
2126
2323
  const editor = this.editor;
2127
2324
  const model = editor.model;
2128
2325
  const editingView = editor.editing.view;
@@ -2184,10 +2381,20 @@ class ImageInlineEditing extends Plugin {
2184
2381
  }
2185
2382
  }
2186
2383
 
2187
- class ImageInline extends Plugin {
2384
+ /**
2385
+ * The image inline plugin.
2386
+ *
2387
+ * This is a "glue" plugin which loads the following plugins:
2388
+ *
2389
+ * * {@link module:image/image/imageinlineediting~ImageInlineEditing},
2390
+ * * {@link module:image/imagetextalternative~ImageTextAlternative}.
2391
+ *
2392
+ * Usually, it is used in conjunction with other plugins from this package. See the {@glink api/image package page}
2393
+ * for more information.
2394
+ */ class ImageInline extends Plugin {
2188
2395
  /**
2189
- * @inheritDoc
2190
- */ static get requires() {
2396
+ * @inheritDoc
2397
+ */ static get requires() {
2191
2398
  return [
2192
2399
  ImageInlineEditing,
2193
2400
  Widget,
@@ -2196,44 +2403,58 @@ class ImageInline extends Plugin {
2196
2403
  ];
2197
2404
  }
2198
2405
  /**
2199
- * @inheritDoc
2200
- */ static get pluginName() {
2406
+ * @inheritDoc
2407
+ */ static get pluginName() {
2201
2408
  return 'ImageInline';
2202
2409
  }
2203
2410
  }
2204
2411
 
2205
- class Image extends Plugin {
2412
+ /**
2413
+ * The image plugin.
2414
+ *
2415
+ * For a detailed overview, check the {@glink features/images/images-overview image feature} documentation.
2416
+ *
2417
+ * This is a "glue" plugin which loads the following plugins:
2418
+ *
2419
+ * * {@link module:image/imageblock~ImageBlock},
2420
+ * * {@link module:image/imageinline~ImageInline},
2421
+ *
2422
+ * Usually, it is used in conjunction with other plugins from this package. See the {@glink api/image package page}
2423
+ * for more information.
2424
+ */ class Image extends Plugin {
2206
2425
  /**
2207
- * @inheritDoc
2208
- */ static get requires() {
2426
+ * @inheritDoc
2427
+ */ static get requires() {
2209
2428
  return [
2210
2429
  ImageBlock,
2211
2430
  ImageInline
2212
2431
  ];
2213
2432
  }
2214
2433
  /**
2215
- * @inheritDoc
2216
- */ static get pluginName() {
2434
+ * @inheritDoc
2435
+ */ static get pluginName() {
2217
2436
  return 'Image';
2218
2437
  }
2219
2438
  }
2220
2439
 
2221
- class ImageCaptionUtils extends Plugin {
2440
+ /**
2441
+ * The image caption utilities plugin.
2442
+ */ class ImageCaptionUtils extends Plugin {
2222
2443
  /**
2223
- * @inheritDoc
2224
- */ static get pluginName() {
2444
+ * @inheritDoc
2445
+ */ static get pluginName() {
2225
2446
  return 'ImageCaptionUtils';
2226
2447
  }
2227
2448
  /**
2228
- * @inheritDoc
2229
- */ static get requires() {
2449
+ * @inheritDoc
2450
+ */ static get requires() {
2230
2451
  return [
2231
2452
  ImageUtils
2232
2453
  ];
2233
2454
  }
2234
2455
  /**
2235
- * Returns the caption model element from a given image element. Returns `null` if no caption is found.
2236
- */ getCaptionFromImageModelElement(imageModelElement) {
2456
+ * Returns the caption model element from a given image element. Returns `null` if no caption is found.
2457
+ */ getCaptionFromImageModelElement(imageModelElement) {
2237
2458
  for (const node of imageModelElement.getChildren()){
2238
2459
  if (!!node && node.is('element', 'caption')) {
2239
2460
  return node;
@@ -2242,8 +2463,8 @@ class ImageCaptionUtils extends Plugin {
2242
2463
  return null;
2243
2464
  }
2244
2465
  /**
2245
- * Returns the caption model element for a model selection. Returns `null` if the selection has no caption element ancestor.
2246
- */ getCaptionFromModelSelection(selection) {
2466
+ * Returns the caption model element for a model selection. Returns `null` if the selection has no caption element ancestor.
2467
+ */ getCaptionFromModelSelection(selection) {
2247
2468
  const imageUtils = this.editor.plugins.get('ImageUtils');
2248
2469
  const captionElement = selection.getFirstPosition().findAncestor('caption');
2249
2470
  if (!captionElement) {
@@ -2255,11 +2476,11 @@ class ImageCaptionUtils extends Plugin {
2255
2476
  return null;
2256
2477
  }
2257
2478
  /**
2258
- * {@link module:engine/view/matcher~Matcher} pattern. Checks if a given element is a `<figcaption>` element that is placed
2259
- * inside the image `<figure>` element.
2260
- * @returns Returns the object accepted by {@link module:engine/view/matcher~Matcher} or `null` if the element
2261
- * cannot be matched.
2262
- */ matchImageCaptionViewElement(element) {
2479
+ * {@link module:engine/view/matcher~Matcher} pattern. Checks if a given element is a `<figcaption>` element that is placed
2480
+ * inside the image `<figure>` element.
2481
+ * @returns Returns the object accepted by {@link module:engine/view/matcher~Matcher} or `null` if the element
2482
+ * cannot be matched.
2483
+ */ matchImageCaptionViewElement(element) {
2263
2484
  const imageUtils = this.editor.plugins.get('ImageUtils');
2264
2485
  // Convert only captions for images.
2265
2486
  if (element.name == 'figcaption' && imageUtils.isBlockImageView(element.parent)) {
@@ -2271,10 +2492,34 @@ class ImageCaptionUtils extends Plugin {
2271
2492
  }
2272
2493
  }
2273
2494
 
2274
- class ToggleImageCaptionCommand extends Command {
2495
+ /**
2496
+ * The toggle image caption command.
2497
+ *
2498
+ * This command is registered by {@link module:image/imagecaption/imagecaptionediting~ImageCaptionEditing} as the
2499
+ * `'toggleImageCaption'` editor command.
2500
+ *
2501
+ * Executing this command:
2502
+ *
2503
+ * * either adds or removes the image caption of a selected image (depending on whether the caption is present or not),
2504
+ * * removes the image caption if the selection is anchored in one.
2505
+ *
2506
+ * ```ts
2507
+ * // Toggle the presence of the caption.
2508
+ * editor.execute( 'toggleImageCaption' );
2509
+ * ```
2510
+ *
2511
+ * **Note**: Upon executing this command, the selection will be set on the image if previously anchored in the caption element.
2512
+ *
2513
+ * **Note**: You can move the selection to the caption right away as it shows up upon executing this command by using
2514
+ * the `focusCaptionOnShow` option:
2515
+ *
2516
+ * ```ts
2517
+ * editor.execute( 'toggleImageCaption', { focusCaptionOnShow: true } );
2518
+ * ```
2519
+ */ class ToggleImageCaptionCommand extends Command {
2275
2520
  /**
2276
- * @inheritDoc
2277
- */ refresh() {
2521
+ * @inheritDoc
2522
+ */ refresh() {
2278
2523
  const editor = this.editor;
2279
2524
  const imageCaptionUtils = editor.plugins.get('ImageCaptionUtils');
2280
2525
  const imageUtils = editor.plugins.get('ImageUtils');
@@ -2302,16 +2547,16 @@ class ToggleImageCaptionCommand extends Command {
2302
2547
  }
2303
2548
  }
2304
2549
  /**
2305
- * Executes the command.
2306
- *
2307
- * ```ts
2308
- * editor.execute( 'toggleImageCaption' );
2309
- * ```
2310
- *
2311
- * @param options Options for the executed command.
2312
- * @param options.focusCaptionOnShow When true and the caption shows up, the selection will be moved into it straight away.
2313
- * @fires execute
2314
- */ execute(options = {}) {
2550
+ * Executes the command.
2551
+ *
2552
+ * ```ts
2553
+ * editor.execute( 'toggleImageCaption' );
2554
+ * ```
2555
+ *
2556
+ * @param options Options for the executed command.
2557
+ * @param options.focusCaptionOnShow When true and the caption shows up, the selection will be moved into it straight away.
2558
+ * @fires execute
2559
+ */ execute(options = {}) {
2315
2560
  const { focusCaptionOnShow } = options;
2316
2561
  this.editor.model.change((writer)=>{
2317
2562
  if (this.value) {
@@ -2322,12 +2567,12 @@ class ToggleImageCaptionCommand extends Command {
2322
2567
  });
2323
2568
  }
2324
2569
  /**
2325
- * Shows the caption of the `<imageBlock>` or `<imageInline>`. Also:
2326
- *
2327
- * * it converts `<imageInline>` to `<imageBlock>` to show the caption,
2328
- * * it attempts to restore the caption content from the `ImageCaptionEditing` caption registry,
2329
- * * it moves the selection to the caption right away, it the `focusCaptionOnShow` option was set.
2330
- */ _showImageCaption(writer, focusCaptionOnShow) {
2570
+ * Shows the caption of the `<imageBlock>` or `<imageInline>`. Also:
2571
+ *
2572
+ * * it converts `<imageInline>` to `<imageBlock>` to show the caption,
2573
+ * * it attempts to restore the caption content from the `ImageCaptionEditing` caption registry,
2574
+ * * it moves the selection to the caption right away, it the `focusCaptionOnShow` option was set.
2575
+ */ _showImageCaption(writer, focusCaptionOnShow) {
2331
2576
  const model = this.editor.model;
2332
2577
  const selection = model.document.selection;
2333
2578
  const imageCaptionEditing = this.editor.plugins.get('ImageCaptionEditing');
@@ -2348,11 +2593,11 @@ class ToggleImageCaptionCommand extends Command {
2348
2593
  }
2349
2594
  }
2350
2595
  /**
2351
- * Hides the caption of a selected image (or an image caption the selection is anchored to).
2352
- *
2353
- * The content of the caption is stored in the `ImageCaptionEditing` caption registry to make this
2354
- * a reversible action.
2355
- */ _hideImageCaption(writer) {
2596
+ * Hides the caption of a selected image (or an image caption the selection is anchored to).
2597
+ *
2598
+ * The content of the caption is stored in the `ImageCaptionEditing` caption registry to make this
2599
+ * a reversible action.
2600
+ */ _hideImageCaption(writer) {
2356
2601
  const editor = this.editor;
2357
2602
  const selection = editor.model.document.selection;
2358
2603
  const imageCaptionEditing = editor.plugins.get('ImageCaptionEditing');
@@ -2372,23 +2617,41 @@ class ToggleImageCaptionCommand extends Command {
2372
2617
  }
2373
2618
  }
2374
2619
 
2375
- class ImageCaptionEditing extends Plugin {
2620
+ /**
2621
+ * The image caption engine plugin. It is responsible for:
2622
+ *
2623
+ * * registering converters for the caption element,
2624
+ * * registering converters for the caption model attribute,
2625
+ * * registering the {@link module:image/imagecaption/toggleimagecaptioncommand~ToggleImageCaptionCommand `toggleImageCaption`} command.
2626
+ */ class ImageCaptionEditing extends Plugin {
2376
2627
  /**
2377
- * @inheritDoc
2378
- */ static get requires() {
2628
+ * @inheritDoc
2629
+ */ static get requires() {
2379
2630
  return [
2380
2631
  ImageUtils,
2381
2632
  ImageCaptionUtils
2382
2633
  ];
2383
2634
  }
2384
2635
  /**
2385
- * @inheritDoc
2386
- */ static get pluginName() {
2636
+ * @inheritDoc
2637
+ */ static get pluginName() {
2387
2638
  return 'ImageCaptionEditing';
2388
2639
  }
2389
2640
  /**
2390
- * @inheritDoc
2391
- */ init() {
2641
+ * A map that keeps saved JSONified image captions and image model elements they are
2642
+ * associated with.
2643
+ *
2644
+ * To learn more about this system, see {@link #_saveCaption}.
2645
+ */ _savedCaptionsMap;
2646
+ /**
2647
+ * @inheritDoc
2648
+ */ constructor(editor){
2649
+ super(editor);
2650
+ this._savedCaptionsMap = new WeakMap();
2651
+ }
2652
+ /**
2653
+ * @inheritDoc
2654
+ */ init() {
2392
2655
  const editor = this.editor;
2393
2656
  const schema = editor.model.schema;
2394
2657
  // Schema configuration.
@@ -2409,9 +2672,9 @@ class ImageCaptionEditing extends Plugin {
2409
2672
  this._registerCaptionReconversion();
2410
2673
  }
2411
2674
  /**
2412
- * Configures conversion pipelines to support upcasting and downcasting
2413
- * image captions.
2414
- */ _setupConversion() {
2675
+ * Configures conversion pipelines to support upcasting and downcasting
2676
+ * image captions.
2677
+ */ _setupConversion() {
2415
2678
  const editor = this.editor;
2416
2679
  const view = editor.editing.view;
2417
2680
  const imageUtils = editor.plugins.get('ImageUtils');
@@ -2458,10 +2721,10 @@ class ImageCaptionEditing extends Plugin {
2458
2721
  });
2459
2722
  }
2460
2723
  /**
2461
- * Integrates with {@link module:image/image/imagetypecommand~ImageTypeCommand image type commands}
2462
- * to make sure the caption is preserved when the type of an image changes so it can be restored
2463
- * in the future if the user decides they want their caption back.
2464
- */ _setupImageTypeCommandsIntegration() {
2724
+ * Integrates with {@link module:image/image/imagetypecommand~ImageTypeCommand image type commands}
2725
+ * to make sure the caption is preserved when the type of an image changes so it can be restored
2726
+ * in the future if the user decides they want their caption back.
2727
+ */ _setupImageTypeCommandsIntegration() {
2465
2728
  const editor = this.editor;
2466
2729
  const imageUtils = editor.plugins.get('ImageUtils');
2467
2730
  const imageCaptionUtils = editor.plugins.get('ImageCaptionUtils');
@@ -2513,42 +2776,42 @@ class ImageCaptionEditing extends Plugin {
2513
2776
  }
2514
2777
  }
2515
2778
  /**
2516
- * Returns the saved {@link module:engine/model/element~Element#toJSON JSONified} caption
2517
- * of an image model element.
2518
- *
2519
- * See {@link #_saveCaption}.
2520
- *
2521
- * @internal
2522
- * @param imageModelElement The model element the caption should be returned for.
2523
- * @returns The model caption element or `null` if there is none.
2524
- */ _getSavedCaption(imageModelElement) {
2779
+ * Returns the saved {@link module:engine/model/element~Element#toJSON JSONified} caption
2780
+ * of an image model element.
2781
+ *
2782
+ * See {@link #_saveCaption}.
2783
+ *
2784
+ * @internal
2785
+ * @param imageModelElement The model element the caption should be returned for.
2786
+ * @returns The model caption element or `null` if there is none.
2787
+ */ _getSavedCaption(imageModelElement) {
2525
2788
  const jsonObject = this._savedCaptionsMap.get(imageModelElement);
2526
2789
  return jsonObject ? Element.fromJSON(jsonObject) : null;
2527
2790
  }
2528
2791
  /**
2529
- * Saves a {@link module:engine/model/element~Element#toJSON JSONified} caption for
2530
- * an image element to allow restoring it in the future.
2531
- *
2532
- * A caption is saved every time it gets hidden and/or the type of an image changes. The
2533
- * user should be able to restore it on demand.
2534
- *
2535
- * **Note**: The caption cannot be stored in the image model element attribute because,
2536
- * for instance, when the model state propagates to collaborators, the attribute would get
2537
- * lost (mainly because it does not convert to anything when the caption is hidden) and
2538
- * the states of collaborators' models would de-synchronize causing numerous issues.
2539
- *
2540
- * See {@link #_getSavedCaption}.
2541
- *
2542
- * @internal
2543
- * @param imageModelElement The model element the caption is saved for.
2544
- * @param caption The caption model element to be saved.
2545
- */ _saveCaption(imageModelElement, caption) {
2792
+ * Saves a {@link module:engine/model/element~Element#toJSON JSONified} caption for
2793
+ * an image element to allow restoring it in the future.
2794
+ *
2795
+ * A caption is saved every time it gets hidden and/or the type of an image changes. The
2796
+ * user should be able to restore it on demand.
2797
+ *
2798
+ * **Note**: The caption cannot be stored in the image model element attribute because,
2799
+ * for instance, when the model state propagates to collaborators, the attribute would get
2800
+ * lost (mainly because it does not convert to anything when the caption is hidden) and
2801
+ * the states of collaborators' models would de-synchronize causing numerous issues.
2802
+ *
2803
+ * See {@link #_getSavedCaption}.
2804
+ *
2805
+ * @internal
2806
+ * @param imageModelElement The model element the caption is saved for.
2807
+ * @param caption The caption model element to be saved.
2808
+ */ _saveCaption(imageModelElement, caption) {
2546
2809
  this._savedCaptionsMap.set(imageModelElement, caption.toJSON());
2547
2810
  }
2548
2811
  /**
2549
- * Reconverts image caption when image alt attribute changes.
2550
- * The change of alt attribute is reflected in caption's aria-label attribute.
2551
- */ _registerCaptionReconversion() {
2812
+ * Reconverts image caption when image alt attribute changes.
2813
+ * The change of alt attribute is reflected in caption's aria-label attribute.
2814
+ */ _registerCaptionReconversion() {
2552
2815
  const editor = this.editor;
2553
2816
  const model = editor.model;
2554
2817
  const imageUtils = editor.plugins.get('ImageUtils');
@@ -2570,30 +2833,26 @@ class ImageCaptionEditing extends Plugin {
2570
2833
  }
2571
2834
  });
2572
2835
  }
2573
- /**
2574
- * @inheritDoc
2575
- */ constructor(editor){
2576
- super(editor);
2577
- this._savedCaptionsMap = new WeakMap();
2578
- }
2579
2836
  }
2580
2837
 
2581
- class ImageCaptionUI extends Plugin {
2838
+ /**
2839
+ * The image caption UI plugin. It introduces the `'toggleImageCaption'` UI button.
2840
+ */ class ImageCaptionUI extends Plugin {
2582
2841
  /**
2583
- * @inheritDoc
2584
- */ static get requires() {
2842
+ * @inheritDoc
2843
+ */ static get requires() {
2585
2844
  return [
2586
2845
  ImageCaptionUtils
2587
2846
  ];
2588
2847
  }
2589
2848
  /**
2590
- * @inheritDoc
2591
- */ static get pluginName() {
2849
+ * @inheritDoc
2850
+ */ static get pluginName() {
2592
2851
  return 'ImageCaptionUI';
2593
2852
  }
2594
2853
  /**
2595
- * @inheritDoc
2596
- */ init() {
2854
+ * @inheritDoc
2855
+ */ init() {
2597
2856
  const editor = this.editor;
2598
2857
  const editingView = editor.editing.view;
2599
2858
  const imageCaptionUtils = editor.plugins.get('ImageCaptionUtils');
@@ -2628,18 +2887,22 @@ class ImageCaptionUI extends Plugin {
2628
2887
  }
2629
2888
  }
2630
2889
 
2631
- class ImageCaption extends Plugin {
2890
+ /**
2891
+ * The image caption plugin.
2892
+ *
2893
+ * For a detailed overview, check the {@glink features/images/images-captions image caption} documentation.
2894
+ */ class ImageCaption extends Plugin {
2632
2895
  /**
2633
- * @inheritDoc
2634
- */ static get requires() {
2896
+ * @inheritDoc
2897
+ */ static get requires() {
2635
2898
  return [
2636
2899
  ImageCaptionEditing,
2637
2900
  ImageCaptionUI
2638
2901
  ];
2639
2902
  }
2640
2903
  /**
2641
- * @inheritDoc
2642
- */ static get pluginName() {
2904
+ * @inheritDoc
2905
+ */ static get pluginName() {
2643
2906
  return 'ImageCaption';
2644
2907
  }
2645
2908
  }
@@ -2746,58 +3009,45 @@ class ImageCaption extends Plugin {
2746
3009
  });
2747
3010
  }
2748
3011
 
2749
- class ImageUploadUI extends Plugin {
3012
+ /**
3013
+ * The image upload button plugin.
3014
+ *
3015
+ * For a detailed overview, check the {@glink features/images/image-upload/image-upload Image upload feature} documentation.
3016
+ *
3017
+ * Adds the `'uploadImage'` button to the {@link module:ui/componentfactory~ComponentFactory UI component factory}
3018
+ * and also the `imageUpload` button as an alias for backward compatibility.
3019
+ *
3020
+ * Adds the `'menuBar:uploadImage'` menu button to the {@link module:ui/componentfactory~ComponentFactory UI component factory}.
3021
+ *
3022
+ * It also integrates with the `insertImage` toolbar component and `menuBar:insertImage` menu component, which are the default components
3023
+ * through which image upload is available.
3024
+ */ class ImageUploadUI extends Plugin {
2750
3025
  /**
2751
- * @inheritDoc
2752
- */ static get pluginName() {
3026
+ * @inheritDoc
3027
+ */ static get pluginName() {
2753
3028
  return 'ImageUploadUI';
2754
3029
  }
2755
3030
  /**
2756
- * @inheritDoc
2757
- */ init() {
3031
+ * @inheritDoc
3032
+ */ init() {
2758
3033
  const editor = this.editor;
2759
- const t = editor.t;
2760
- const toolbarComponentCreator = ()=>{
2761
- const button = this._createButton(FileDialogButtonView);
2762
- button.set({
2763
- label: t('Upload image from computer'),
2764
- tooltip: true
2765
- });
2766
- return button;
2767
- };
2768
3034
  // Setup `uploadImage` button and add `imageUpload` button as an alias for backward compatibility.
2769
- editor.ui.componentFactory.add('uploadImage', toolbarComponentCreator);
2770
- editor.ui.componentFactory.add('imageUpload', toolbarComponentCreator);
2771
- editor.ui.componentFactory.add('menuBar:uploadImage', ()=>{
2772
- const button = this._createButton(MenuBarMenuListItemFileDialogButtonView);
2773
- button.label = t('Image from computer');
2774
- return button;
2775
- });
3035
+ editor.ui.componentFactory.add('uploadImage', ()=>this._createToolbarButton());
3036
+ editor.ui.componentFactory.add('imageUpload', ()=>this._createToolbarButton());
3037
+ editor.ui.componentFactory.add('menuBar:uploadImage', ()=>this._createMenuBarButton('standalone'));
2776
3038
  if (editor.plugins.has('ImageInsertUI')) {
2777
- const imageInsertUI = editor.plugins.get('ImageInsertUI');
2778
- imageInsertUI.registerIntegration({
3039
+ editor.plugins.get('ImageInsertUI').registerIntegration({
2779
3040
  name: 'upload',
2780
3041
  observable: ()=>editor.commands.get('uploadImage'),
2781
- buttonViewCreator: ()=>{
2782
- const uploadImageButton = editor.ui.componentFactory.create('uploadImage');
2783
- uploadImageButton.bind('label').to(imageInsertUI, 'isImageSelected', (isImageSelected)=>isImageSelected ? t('Replace image from computer') : t('Upload image from computer'));
2784
- return uploadImageButton;
2785
- },
2786
- formViewCreator: ()=>{
2787
- const uploadImageButton = editor.ui.componentFactory.create('uploadImage');
2788
- uploadImageButton.withText = true;
2789
- uploadImageButton.bind('label').to(imageInsertUI, 'isImageSelected', (isImageSelected)=>isImageSelected ? t('Replace from computer') : t('Upload from computer'));
2790
- uploadImageButton.on('execute', ()=>{
2791
- imageInsertUI.dropdownView.isOpen = false;
2792
- });
2793
- return uploadImageButton;
2794
- }
3042
+ buttonViewCreator: ()=>this._createToolbarButton(),
3043
+ formViewCreator: ()=>this._createDropdownButton(),
3044
+ menuBarButtonViewCreator: (isOnly)=>this._createMenuBarButton(isOnly ? 'insertOnly' : 'insertNested')
2795
3045
  });
2796
3046
  }
2797
3047
  }
2798
3048
  /**
2799
- * Creates a button for image upload command to use either in toolbar or in menu bar.
2800
- */ _createButton(ButtonClass) {
3049
+ * Creates the base for various kinds of the button component provided by this feature.
3050
+ */ _createButton(ButtonClass) {
2801
3051
  const editor = this.editor;
2802
3052
  const locale = editor.locale;
2803
3053
  const command = editor.commands.get('uploadImage');
@@ -2808,7 +3058,7 @@ class ImageUploadUI extends Plugin {
2808
3058
  view.set({
2809
3059
  acceptedType: imageTypes.map((type)=>`image/${type}`).join(','),
2810
3060
  allowMultipleFiles: true,
2811
- label: t('Upload image from computer'),
3061
+ label: t('Upload from computer'),
2812
3062
  icon: icons.imageUpload
2813
3063
  });
2814
3064
  view.bind('isEnabled').to(command);
@@ -2823,17 +3073,73 @@ class ImageUploadUI extends Plugin {
2823
3073
  });
2824
3074
  return view;
2825
3075
  }
3076
+ /**
3077
+ * Creates a simple toolbar button, with an icon and a tooltip.
3078
+ */ _createToolbarButton() {
3079
+ const t = this.editor.locale.t;
3080
+ const imageInsertUI = this.editor.plugins.get('ImageInsertUI');
3081
+ const button = this._createButton(FileDialogButtonView);
3082
+ button.tooltip = true;
3083
+ button.bind('label').to(imageInsertUI, 'isImageSelected', (isImageSelected)=>isImageSelected ? t('Replace image from computer') : t('Upload image from computer'));
3084
+ return button;
3085
+ }
3086
+ /**
3087
+ * Creates a button for the dropdown view, with an icon, text and no tooltip.
3088
+ */ _createDropdownButton() {
3089
+ const t = this.editor.locale.t;
3090
+ const imageInsertUI = this.editor.plugins.get('ImageInsertUI');
3091
+ const button = this._createButton(FileDialogButtonView);
3092
+ button.withText = true;
3093
+ button.bind('label').to(imageInsertUI, 'isImageSelected', (isImageSelected)=>isImageSelected ? t('Replace from computer') : t('Upload from computer'));
3094
+ button.on('execute', ()=>{
3095
+ imageInsertUI.dropdownView.isOpen = false;
3096
+ });
3097
+ return button;
3098
+ }
3099
+ /**
3100
+ * Creates a button for the menu bar.
3101
+ */ _createMenuBarButton(type) {
3102
+ const t = this.editor.locale.t;
3103
+ const button = this._createButton(MenuBarMenuListItemFileDialogButtonView);
3104
+ button.withText = true;
3105
+ switch(type){
3106
+ case 'standalone':
3107
+ button.label = t('Image from computer');
3108
+ break;
3109
+ case 'insertOnly':
3110
+ button.label = t('Image');
3111
+ break;
3112
+ case 'insertNested':
3113
+ button.label = t('From computer');
3114
+ break;
3115
+ }
3116
+ return button;
3117
+ }
2826
3118
  }
2827
3119
 
2828
- class ImageUploadProgress extends Plugin {
3120
+ /**
3121
+ * The image upload progress plugin.
3122
+ * It shows a placeholder when the image is read from the disk and a progress bar while the image is uploading.
3123
+ */ class ImageUploadProgress extends Plugin {
2829
3124
  /**
2830
- * @inheritDoc
2831
- */ static get pluginName() {
3125
+ * @inheritDoc
3126
+ */ static get pluginName() {
2832
3127
  return 'ImageUploadProgress';
2833
3128
  }
2834
3129
  /**
2835
- * @inheritDoc
2836
- */ init() {
3130
+ * The image placeholder that is displayed before real image data can be accessed.
3131
+ *
3132
+ * For the record, this image is a 1x1 px GIF with an aspect ratio set by CSS.
3133
+ */ placeholder;
3134
+ /**
3135
+ * @inheritDoc
3136
+ */ constructor(editor){
3137
+ super(editor);
3138
+ this.placeholder = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
3139
+ }
3140
+ /**
3141
+ * @inheritDoc
3142
+ */ init() {
2837
3143
  const editor = this.editor;
2838
3144
  // Upload status change - update image's view according to that status.
2839
3145
  if (editor.plugins.has('ImageBlockEditing')) {
@@ -2844,62 +3150,56 @@ class ImageUploadProgress extends Plugin {
2844
3150
  }
2845
3151
  }
2846
3152
  /**
2847
- * @inheritDoc
2848
- */ constructor(editor){
2849
- super(editor);
2850
- /**
2851
- * This method is called each time the image `uploadStatus` attribute is changed.
2852
- *
2853
- * @param evt An object containing information about the fired event.
2854
- * @param data Additional information about the change.
2855
- */ this.uploadStatusChange = (evt, data, conversionApi)=>{
2856
- const editor = this.editor;
2857
- const modelImage = data.item;
2858
- const uploadId = modelImage.getAttribute('uploadId');
2859
- if (!conversionApi.consumable.consume(data.item, evt.name)) {
2860
- return;
2861
- }
2862
- const imageUtils = editor.plugins.get('ImageUtils');
2863
- const fileRepository = editor.plugins.get(FileRepository);
2864
- const status = uploadId ? data.attributeNewValue : null;
2865
- const placeholder = this.placeholder;
2866
- const viewFigure = editor.editing.mapper.toViewElement(modelImage);
2867
- const viewWriter = conversionApi.writer;
2868
- if (status == 'reading') {
2869
- // Start "appearing" effect and show placeholder with infinite progress bar on the top
2870
- // while image is read from disk.
2871
- _startAppearEffect(viewFigure, viewWriter);
3153
+ * This method is called each time the image `uploadStatus` attribute is changed.
3154
+ *
3155
+ * @param evt An object containing information about the fired event.
3156
+ * @param data Additional information about the change.
3157
+ */ uploadStatusChange = (evt, data, conversionApi)=>{
3158
+ const editor = this.editor;
3159
+ const modelImage = data.item;
3160
+ const uploadId = modelImage.getAttribute('uploadId');
3161
+ if (!conversionApi.consumable.consume(data.item, evt.name)) {
3162
+ return;
3163
+ }
3164
+ const imageUtils = editor.plugins.get('ImageUtils');
3165
+ const fileRepository = editor.plugins.get(FileRepository);
3166
+ const status = uploadId ? data.attributeNewValue : null;
3167
+ const placeholder = this.placeholder;
3168
+ const viewFigure = editor.editing.mapper.toViewElement(modelImage);
3169
+ const viewWriter = conversionApi.writer;
3170
+ if (status == 'reading') {
3171
+ // Start "appearing" effect and show placeholder with infinite progress bar on the top
3172
+ // while image is read from disk.
3173
+ _startAppearEffect(viewFigure, viewWriter);
3174
+ _showPlaceholder(imageUtils, placeholder, viewFigure, viewWriter);
3175
+ return;
3176
+ }
3177
+ // Show progress bar on the top of the image when image is uploading.
3178
+ if (status == 'uploading') {
3179
+ const loader = fileRepository.loaders.get(uploadId);
3180
+ // Start appear effect if needed - see https://github.com/ckeditor/ckeditor5-image/issues/191.
3181
+ _startAppearEffect(viewFigure, viewWriter);
3182
+ if (!loader) {
3183
+ // There is no loader associated with uploadId - this means that image came from external changes.
3184
+ // In such cases we still want to show the placeholder until image is fully uploaded.
3185
+ // Show placeholder if needed - see https://github.com/ckeditor/ckeditor5-image/issues/191.
2872
3186
  _showPlaceholder(imageUtils, placeholder, viewFigure, viewWriter);
2873
- return;
2874
- }
2875
- // Show progress bar on the top of the image when image is uploading.
2876
- if (status == 'uploading') {
2877
- const loader = fileRepository.loaders.get(uploadId);
2878
- // Start appear effect if needed - see https://github.com/ckeditor/ckeditor5-image/issues/191.
2879
- _startAppearEffect(viewFigure, viewWriter);
2880
- if (!loader) {
2881
- // There is no loader associated with uploadId - this means that image came from external changes.
2882
- // In such cases we still want to show the placeholder until image is fully uploaded.
2883
- // Show placeholder if needed - see https://github.com/ckeditor/ckeditor5-image/issues/191.
2884
- _showPlaceholder(imageUtils, placeholder, viewFigure, viewWriter);
2885
- } else {
2886
- // Hide placeholder and initialize progress bar showing upload progress.
2887
- _hidePlaceholder(viewFigure, viewWriter);
2888
- _showProgressBar(viewFigure, viewWriter, loader, editor.editing.view);
2889
- _displayLocalImage(imageUtils, viewFigure, viewWriter, loader);
2890
- }
2891
- return;
2892
- }
2893
- if (status == 'complete' && fileRepository.loaders.get(uploadId)) {
2894
- _showCompleteIcon(viewFigure, viewWriter, editor.editing.view);
3187
+ } else {
3188
+ // Hide placeholder and initialize progress bar showing upload progress.
3189
+ _hidePlaceholder(viewFigure, viewWriter);
3190
+ _showProgressBar(viewFigure, viewWriter, loader, editor.editing.view);
3191
+ _displayLocalImage(imageUtils, viewFigure, viewWriter, loader);
2895
3192
  }
2896
- // Clean up.
2897
- _hideProgressBar(viewFigure, viewWriter);
2898
- _hidePlaceholder(viewFigure, viewWriter);
2899
- _stopAppearEffect(viewFigure, viewWriter);
2900
- };
2901
- this.placeholder = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
2902
- }
3193
+ return;
3194
+ }
3195
+ if (status == 'complete' && fileRepository.loaders.get(uploadId)) {
3196
+ _showCompleteIcon(viewFigure, viewWriter, editor.editing.view);
3197
+ }
3198
+ // Clean up.
3199
+ _hideProgressBar(viewFigure, viewWriter);
3200
+ _hidePlaceholder(viewFigure, viewWriter);
3201
+ _stopAppearEffect(viewFigure, viewWriter);
3202
+ };
2903
3203
  }
2904
3204
  /**
2905
3205
  * Adds ck-appear class to the image figure if one is not already applied.
@@ -3009,10 +3309,42 @@ class ImageUploadProgress extends Plugin {
3009
3309
  }
3010
3310
  }
3011
3311
 
3012
- class UploadImageCommand extends Command {
3312
+ /**
3313
+ * @module image/imageupload/uploadimagecommand
3314
+ */ /**
3315
+ * The upload image command.
3316
+ *
3317
+ * The command is registered by the {@link module:image/imageupload/imageuploadediting~ImageUploadEditing} plugin as `uploadImage`
3318
+ * and it is also available via aliased `imageUpload` name.
3319
+ *
3320
+ * In order to upload an image at the current selection position
3321
+ * (according to the {@link module:widget/utils~findOptimalInsertionRange} algorithm),
3322
+ * execute the command and pass the native image file instance:
3323
+ *
3324
+ * ```ts
3325
+ * this.listenTo( editor.editing.view.document, 'clipboardInput', ( evt, data ) => {
3326
+ * // Assuming that only images were pasted:
3327
+ * const images = Array.from( data.dataTransfer.files );
3328
+ *
3329
+ * // Upload the first image:
3330
+ * editor.execute( 'uploadImage', { file: images[ 0 ] } );
3331
+ * } );
3332
+ * ```
3333
+ *
3334
+ * It is also possible to insert multiple images at once:
3335
+ *
3336
+ * ```ts
3337
+ * editor.execute( 'uploadImage', {
3338
+ * file: [
3339
+ * file1,
3340
+ * file2
3341
+ * ]
3342
+ * } );
3343
+ * ```
3344
+ */ class UploadImageCommand extends Command {
3013
3345
  /**
3014
- * @inheritDoc
3015
- */ refresh() {
3346
+ * @inheritDoc
3347
+ */ refresh() {
3016
3348
  const editor = this.editor;
3017
3349
  const imageUtils = editor.plugins.get('ImageUtils');
3018
3350
  const selectedElement = editor.model.document.selection.getSelectedElement();
@@ -3020,12 +3352,12 @@ class UploadImageCommand extends Command {
3020
3352
  this.isEnabled = imageUtils.isImageAllowed() || imageUtils.isImage(selectedElement);
3021
3353
  }
3022
3354
  /**
3023
- * Executes the command.
3024
- *
3025
- * @fires execute
3026
- * @param options Options for the executed command.
3027
- * @param options.file The image file or an array of image files to upload.
3028
- */ execute(options) {
3355
+ * Executes the command.
3356
+ *
3357
+ * @fires execute
3358
+ * @param options Options for the executed command.
3359
+ * @param options.file The image file or an array of image files to upload.
3360
+ */ execute(options) {
3029
3361
  const files = toArray(options.file);
3030
3362
  const selection = this.editor.model.document.selection;
3031
3363
  const imageUtils = this.editor.plugins.get('ImageUtils');
@@ -3051,8 +3383,8 @@ class UploadImageCommand extends Command {
3051
3383
  });
3052
3384
  }
3053
3385
  /**
3054
- * Handles uploading single file.
3055
- */ _uploadImage(file, attributes, position) {
3386
+ * Handles uploading single file.
3387
+ */ _uploadImage(file, attributes, position) {
3056
3388
  const editor = this.editor;
3057
3389
  const fileRepository = editor.plugins.get(FileRepository);
3058
3390
  const loader = fileRepository.createLoader(file);
@@ -3068,10 +3400,16 @@ class UploadImageCommand extends Command {
3068
3400
  }
3069
3401
  }
3070
3402
 
3071
- class ImageUploadEditing extends Plugin {
3403
+ /**
3404
+ * The editing part of the image upload feature. It registers the `'uploadImage'` command
3405
+ * and the `imageUpload` command as an aliased name.
3406
+ *
3407
+ * When an image is uploaded, it fires the {@link ~ImageUploadEditing#event:uploadComplete `uploadComplete`} event
3408
+ * that allows adding custom attributes to the {@link module:engine/model/element~Element image element}.
3409
+ */ class ImageUploadEditing extends Plugin {
3072
3410
  /**
3073
- * @inheritDoc
3074
- */ static get requires() {
3411
+ * @inheritDoc
3412
+ */ static get requires() {
3075
3413
  return [
3076
3414
  FileRepository,
3077
3415
  Notification,
@@ -3083,8 +3421,35 @@ class ImageUploadEditing extends Plugin {
3083
3421
  return 'ImageUploadEditing';
3084
3422
  }
3085
3423
  /**
3086
- * @inheritDoc
3087
- */ init() {
3424
+ * An internal mapping of {@link module:upload/filerepository~FileLoader#id file loader UIDs} and
3425
+ * model elements during the upload.
3426
+ *
3427
+ * Model element of the uploaded image can change, for instance, when {@link module:image/image/imagetypecommand~ImageTypeCommand}
3428
+ * is executed as a result of adding caption or changing image style. As a result, the upload logic must keep track of the model
3429
+ * element (reference) and resolve the upload for the correct model element (instead of the one that landed in the `$graveyard`
3430
+ * after image type changed).
3431
+ */ _uploadImageElements;
3432
+ /**
3433
+ * @inheritDoc
3434
+ */ constructor(editor){
3435
+ super(editor);
3436
+ editor.config.define('image', {
3437
+ upload: {
3438
+ types: [
3439
+ 'jpeg',
3440
+ 'png',
3441
+ 'gif',
3442
+ 'bmp',
3443
+ 'webp',
3444
+ 'tiff'
3445
+ ]
3446
+ }
3447
+ });
3448
+ this._uploadImageElements = new Map();
3449
+ }
3450
+ /**
3451
+ * @inheritDoc
3452
+ */ init() {
3088
3453
  const editor = this.editor;
3089
3454
  const doc = editor.model.document;
3090
3455
  const conversion = editor.conversion;
@@ -3227,8 +3592,8 @@ class ImageUploadEditing extends Plugin {
3227
3592
  });
3228
3593
  }
3229
3594
  /**
3230
- * @inheritDoc
3231
- */ afterInit() {
3595
+ * @inheritDoc
3596
+ */ afterInit() {
3232
3597
  const schema = this.editor.model.schema;
3233
3598
  // Setup schema to allow uploadId and uploadStatus for images.
3234
3599
  // Wait for ImageBlockEditing or ImageInlineEditing to register their elements first,
@@ -3251,12 +3616,12 @@ class ImageUploadEditing extends Plugin {
3251
3616
  }
3252
3617
  }
3253
3618
  /**
3254
- * Reads and uploads an image.
3255
- *
3256
- * The image is read from the disk and as a Base64-encoded string it is set temporarily to
3257
- * `image[src]`. When the image is successfully uploaded, the temporary data is replaced with the target
3258
- * image's URL (the URL to the uploaded image on the server).
3259
- */ _readAndUpload(loader) {
3619
+ * Reads and uploads an image.
3620
+ *
3621
+ * The image is read from the disk and as a Base64-encoded string it is set temporarily to
3622
+ * `image[src]`. When the image is successfully uploaded, the temporary data is replaced with the target
3623
+ * image's URL (the URL to the uploaded image on the server).
3624
+ */ _readAndUpload(loader) {
3260
3625
  const editor = this.editor;
3261
3626
  const model = editor.model;
3262
3627
  const t = editor.locale.t;
@@ -3355,11 +3720,11 @@ class ImageUploadEditing extends Plugin {
3355
3720
  }
3356
3721
  }
3357
3722
  /**
3358
- * Creates the `srcset` attribute based on a given file upload response and sets it as an attribute to a specific image element.
3359
- *
3360
- * @param data Data object from which `srcset` will be created.
3361
- * @param image The image element on which the `srcset` attribute will be set.
3362
- */ _parseAndSetSrcsetAttributeOnImage(data, image, writer) {
3723
+ * Creates the `srcset` attribute based on a given file upload response and sets it as an attribute to a specific image element.
3724
+ *
3725
+ * @param data Data object from which `srcset` will be created.
3726
+ * @param image The image element on which the `srcset` attribute will be set.
3727
+ */ _parseAndSetSrcsetAttributeOnImage(data, image, writer) {
3363
3728
  // Srcset attribute for responsive images support.
3364
3729
  let maxWidth = 0;
3365
3730
  const srcsetAttribute = Object.keys(data)// Filter out keys that are not integers.
@@ -3382,24 +3747,6 @@ class ImageUploadEditing extends Plugin {
3382
3747
  writer.setAttributes(attributes, image);
3383
3748
  }
3384
3749
  }
3385
- /**
3386
- * @inheritDoc
3387
- */ constructor(editor){
3388
- super(editor);
3389
- editor.config.define('image', {
3390
- upload: {
3391
- types: [
3392
- 'jpeg',
3393
- 'png',
3394
- 'gif',
3395
- 'bmp',
3396
- 'webp',
3397
- 'tiff'
3398
- ]
3399
- }
3400
- });
3401
- this._uploadImageElements = new Map();
3402
- }
3403
3750
  }
3404
3751
  /**
3405
3752
  * Returns `true` if non-empty `text/html` is included in the data transfer.
@@ -3411,15 +3758,25 @@ function getImagesFromChangeItem(editor, item) {
3411
3758
  return Array.from(editor.model.createRangeOn(item)).filter((value)=>imageUtils.isImage(value.item)).map((value)=>value.item);
3412
3759
  }
3413
3760
 
3414
- class ImageUpload extends Plugin {
3761
+ /**
3762
+ * The image upload plugin.
3763
+ *
3764
+ * For a detailed overview, check the {@glink features/images/image-upload/image-upload image upload feature} documentation.
3765
+ *
3766
+ * This plugin does not do anything directly, but it loads a set of specific plugins to enable image uploading:
3767
+ *
3768
+ * * {@link module:image/imageupload/imageuploadediting~ImageUploadEditing},
3769
+ * * {@link module:image/imageupload/imageuploadui~ImageUploadUI},
3770
+ * * {@link module:image/imageupload/imageuploadprogress~ImageUploadProgress}.
3771
+ */ class ImageUpload extends Plugin {
3415
3772
  /**
3416
- * @inheritDoc
3417
- */ static get pluginName() {
3773
+ * @inheritDoc
3774
+ */ static get pluginName() {
3418
3775
  return 'ImageUpload';
3419
3776
  }
3420
3777
  /**
3421
- * @inheritDoc
3422
- */ static get requires() {
3778
+ * @inheritDoc
3779
+ */ static get requires() {
3423
3780
  return [
3424
3781
  ImageUploadEditing,
3425
3782
  ImageUploadUI,
@@ -3428,112 +3785,28 @@ class ImageUpload extends Plugin {
3428
3785
  }
3429
3786
  }
3430
3787
 
3431
- class ImageInsertUrlView extends View {
3432
- /**
3433
- * @inheritDoc
3434
- */ render() {
3435
- super.render();
3436
- for (const view of this._focusables){
3437
- this.focusTracker.add(view.element);
3438
- }
3439
- // Start listening for the keystrokes coming from #element.
3440
- this.keystrokes.listenTo(this.element);
3441
- }
3442
- /**
3443
- * @inheritDoc
3444
- */ destroy() {
3445
- super.destroy();
3446
- this.focusTracker.destroy();
3447
- this.keystrokes.destroy();
3448
- }
3449
- /**
3450
- * Creates the {@link #urlInputView}.
3451
- */ _createUrlInputView() {
3452
- const locale = this.locale;
3453
- const t = locale.t;
3454
- const urlInputView = new LabeledFieldView(locale, createLabeledInputText);
3455
- urlInputView.bind('label').to(this, 'isImageSelected', (value)=>value ? t('Update image URL') : t('Insert image via URL'));
3456
- urlInputView.bind('isEnabled').to(this);
3457
- urlInputView.fieldView.placeholder = 'https://example.com/image.png';
3458
- urlInputView.fieldView.bind('value').to(this, 'imageURLInputValue', (value)=>value || '');
3459
- urlInputView.fieldView.on('input', ()=>{
3460
- this.imageURLInputValue = urlInputView.fieldView.element.value.trim();
3461
- });
3462
- return urlInputView;
3463
- }
3464
- /**
3465
- * Creates the {@link #insertButtonView}.
3466
- */ _createInsertButton() {
3467
- const locale = this.locale;
3468
- const t = locale.t;
3469
- const insertButtonView = new ButtonView(locale);
3470
- insertButtonView.set({
3471
- icon: icons.check,
3472
- class: 'ck-button-save',
3473
- type: 'submit',
3474
- withText: true
3475
- });
3476
- insertButtonView.bind('label').to(this, 'isImageSelected', (value)=>value ? t('Update') : t('Insert'));
3477
- insertButtonView.bind('isEnabled').to(this, 'imageURLInputValue', this, 'isEnabled', (...values)=>values.every((value)=>value));
3478
- insertButtonView.delegate('execute').to(this, 'submit');
3479
- return insertButtonView;
3480
- }
3788
+ /**
3789
+ * The insert an image via URL view.
3790
+ *
3791
+ * See {@link module:image/imageinsert/imageinsertviaurlui~ImageInsertViaUrlUI}.
3792
+ */ class ImageInsertUrlView extends View {
3481
3793
  /**
3482
- * Creates the {@link #cancelButtonView}.
3483
- */ _createCancelButton() {
3484
- const locale = this.locale;
3485
- const t = locale.t;
3486
- const cancelButtonView = new ButtonView(locale);
3487
- cancelButtonView.set({
3488
- label: t('Cancel'),
3489
- icon: icons.cancel,
3490
- class: 'ck-button-cancel',
3491
- withText: true
3492
- });
3493
- cancelButtonView.bind('isEnabled').to(this);
3494
- cancelButtonView.delegate('execute').to(this, 'cancel');
3495
- return cancelButtonView;
3496
- }
3794
+ * The URL input field view.
3795
+ */ urlInputView;
3497
3796
  /**
3498
- * Focuses the view.
3499
- */ focus(direction) {
3500
- if (direction === -1) {
3501
- this.focusCycler.focusLast();
3502
- } else {
3503
- this.focusCycler.focusFirst();
3504
- }
3505
- }
3797
+ * An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
3798
+ */ keystrokes;
3506
3799
  /**
3507
- * Creates a view for the dropdown panel of {@link module:image/imageinsert/imageinsertui~ImageInsertUI}.
3508
- *
3509
- * @param locale The localization services instance.
3510
- */ constructor(locale){
3800
+ * Creates a view for the dropdown panel of {@link module:image/imageinsert/imageinsertui~ImageInsertUI}.
3801
+ *
3802
+ * @param locale The localization services instance.
3803
+ */ constructor(locale){
3511
3804
  super(locale);
3512
3805
  this.set('imageURLInputValue', '');
3513
3806
  this.set('isImageSelected', false);
3514
3807
  this.set('isEnabled', true);
3515
- this.focusTracker = new FocusTracker();
3516
3808
  this.keystrokes = new KeystrokeHandler();
3517
- this._focusables = new ViewCollection();
3518
- this.focusCycler = new FocusCycler({
3519
- focusables: this._focusables,
3520
- focusTracker: this.focusTracker,
3521
- keystrokeHandler: this.keystrokes,
3522
- actions: {
3523
- // Navigate form fields backwards using the Shift + Tab keystroke.
3524
- focusPrevious: 'shift + tab',
3525
- // Navigate form fields forwards using the Tab key.
3526
- focusNext: 'tab'
3527
- }
3528
- });
3529
3809
  this.urlInputView = this._createUrlInputView();
3530
- this.insertButtonView = this._createInsertButton();
3531
- this.cancelButtonView = this._createCancelButton();
3532
- this._focusables.addMany([
3533
- this.urlInputView,
3534
- this.insertButtonView,
3535
- this.cancelButtonView
3536
- ]);
3537
3810
  this.setTemplate({
3538
3811
  tag: 'div',
3539
3812
  attributes: {
@@ -3551,131 +3824,222 @@ class ImageInsertUrlView extends View {
3551
3824
  'ck',
3552
3825
  'ck-image-insert-url__action-row'
3553
3826
  ]
3554
- },
3555
- children: [
3556
- this.insertButtonView,
3557
- this.cancelButtonView
3558
- ]
3827
+ }
3559
3828
  }
3560
3829
  ]
3561
3830
  });
3562
3831
  }
3832
+ /**
3833
+ * @inheritDoc
3834
+ */ render() {
3835
+ super.render();
3836
+ // Start listening for the keystrokes coming from #element.
3837
+ this.keystrokes.listenTo(this.element);
3838
+ }
3839
+ /**
3840
+ * @inheritDoc
3841
+ */ destroy() {
3842
+ super.destroy();
3843
+ this.keystrokes.destroy();
3844
+ }
3845
+ /**
3846
+ * Creates the {@link #urlInputView}.
3847
+ */ _createUrlInputView() {
3848
+ const locale = this.locale;
3849
+ const t = locale.t;
3850
+ const urlInputView = new LabeledFieldView(locale, createLabeledInputText);
3851
+ urlInputView.bind('label').to(this, 'isImageSelected', (value)=>value ? t('Update image URL') : t('Insert image via URL'));
3852
+ urlInputView.bind('isEnabled').to(this);
3853
+ urlInputView.fieldView.inputMode = 'url';
3854
+ urlInputView.fieldView.placeholder = 'https://example.com/image.png';
3855
+ urlInputView.fieldView.bind('value').to(this, 'imageURLInputValue', (value)=>value || '');
3856
+ urlInputView.fieldView.on('input', ()=>{
3857
+ this.imageURLInputValue = urlInputView.fieldView.element.value.trim();
3858
+ });
3859
+ return urlInputView;
3860
+ }
3861
+ /**
3862
+ * Focuses the view.
3863
+ */ focus() {
3864
+ this.urlInputView.focus();
3865
+ }
3563
3866
  }
3564
3867
 
3565
- class ImageInsertViaUrlUI extends Plugin {
3566
- /**
3567
- * @inheritDoc
3568
- */ static get pluginName() {
3868
+ /**
3869
+ * The image insert via URL plugin (UI part).
3870
+ *
3871
+ * The plugin introduces two UI components to the {@link module:ui/componentfactory~ComponentFactory UI component factory}:
3872
+ *
3873
+ * * the `'insertImageViaUrl'` toolbar button,
3874
+ * * the `'menuBar:insertImageViaUrl'` menu bar component.
3875
+ *
3876
+ * It also integrates with the `insertImage` toolbar component and `menuBar:insertImage` menu component, which are default components
3877
+ * through which inserting image via URL is available.
3878
+ */ class ImageInsertViaUrlUI extends Plugin {
3879
+ _imageInsertUI;
3880
+ _formView;
3881
+ /**
3882
+ * @inheritDoc
3883
+ */ static get pluginName() {
3569
3884
  return 'ImageInsertViaUrlUI';
3570
3885
  }
3571
3886
  /**
3572
- * @inheritDoc
3573
- */ static get requires() {
3887
+ * @inheritDoc
3888
+ */ static get requires() {
3574
3889
  return [
3575
- ImageInsertUI
3890
+ ImageInsertUI,
3891
+ Dialog
3576
3892
  ];
3577
3893
  }
3894
+ init() {
3895
+ this.editor.ui.componentFactory.add('insertImageViaUrl', ()=>this._createToolbarButton());
3896
+ this.editor.ui.componentFactory.add('menuBar:insertImageViaUrl', ()=>this._createMenuBarButton('standalone'));
3897
+ }
3578
3898
  /**
3579
- * @inheritDoc
3580
- */ afterInit() {
3899
+ * @inheritDoc
3900
+ */ afterInit() {
3581
3901
  this._imageInsertUI = this.editor.plugins.get('ImageInsertUI');
3582
3902
  this._imageInsertUI.registerIntegration({
3583
3903
  name: 'url',
3584
3904
  observable: ()=>this.editor.commands.get('insertImage'),
3585
- requiresForm: true,
3586
- buttonViewCreator: (isOnlyOne)=>this._createInsertUrlButton(isOnlyOne),
3587
- formViewCreator: (isOnlyOne)=>this._createInsertUrlView(isOnlyOne)
3905
+ buttonViewCreator: ()=>this._createToolbarButton(),
3906
+ formViewCreator: ()=>this._createDropdownButton(),
3907
+ menuBarButtonViewCreator: (isOnly)=>this._createMenuBarButton(isOnly ? 'insertOnly' : 'insertNested')
3908
+ });
3909
+ }
3910
+ /**
3911
+ * Creates the base for various kinds of the button component provided by this feature.
3912
+ */ _createInsertUrlButton(ButtonClass) {
3913
+ const button = new ButtonClass(this.editor.locale);
3914
+ button.icon = icons.imageUrl;
3915
+ button.on('execute', ()=>{
3916
+ this._showModal();
3588
3917
  });
3918
+ return button;
3919
+ }
3920
+ /**
3921
+ * Creates a simple toolbar button, with an icon and a tooltip.
3922
+ */ _createToolbarButton() {
3923
+ const t = this.editor.locale.t;
3924
+ const button = this._createInsertUrlButton(ButtonView);
3925
+ button.tooltip = true;
3926
+ button.bind('label').to(this._imageInsertUI, 'isImageSelected', (isImageSelected)=>isImageSelected ? t('Update image URL') : t('Insert image via URL'));
3927
+ return button;
3928
+ }
3929
+ /**
3930
+ * Creates a button for the dropdown view, with an icon, text and no tooltip.
3931
+ */ _createDropdownButton() {
3932
+ const t = this.editor.locale.t;
3933
+ const button = this._createInsertUrlButton(ButtonView);
3934
+ button.withText = true;
3935
+ button.bind('label').to(this._imageInsertUI, 'isImageSelected', (isImageSelected)=>isImageSelected ? t('Update image URL') : t('Insert via URL'));
3936
+ return button;
3937
+ }
3938
+ /**
3939
+ * Creates a button for the menu bar.
3940
+ */ _createMenuBarButton(type) {
3941
+ const t = this.editor.locale.t;
3942
+ const button = this._createInsertUrlButton(MenuBarMenuListItemButtonView);
3943
+ button.withText = true;
3944
+ switch(type){
3945
+ case 'standalone':
3946
+ button.label = t('Image via URL');
3947
+ break;
3948
+ case 'insertOnly':
3949
+ button.label = t('Image');
3950
+ break;
3951
+ case 'insertNested':
3952
+ button.label = t('Via URL');
3953
+ break;
3954
+ }
3955
+ return button;
3589
3956
  }
3590
3957
  /**
3591
- * Creates the view displayed in the dropdown.
3592
- */ _createInsertUrlView(isOnlyOne) {
3958
+ * Creates the form view used to submit the image URL.
3959
+ */ _createInsertUrlView() {
3593
3960
  const editor = this.editor;
3594
3961
  const locale = editor.locale;
3595
- const t = locale.t;
3596
3962
  const replaceImageSourceCommand = editor.commands.get('replaceImageSource');
3597
3963
  const insertImageCommand = editor.commands.get('insertImage');
3598
3964
  const imageInsertUrlView = new ImageInsertUrlView(locale);
3599
- const collapsibleView = isOnlyOne ? null : new CollapsibleView(locale, [
3600
- imageInsertUrlView
3601
- ]);
3602
3965
  imageInsertUrlView.bind('isImageSelected').to(this._imageInsertUI);
3603
3966
  imageInsertUrlView.bind('isEnabled').toMany([
3604
3967
  insertImageCommand,
3605
3968
  replaceImageSourceCommand
3606
3969
  ], 'isEnabled', (...isEnabled)=>isEnabled.some((isCommandEnabled)=>isCommandEnabled));
3607
- // Set initial value because integrations are created on first dropdown open.
3608
- imageInsertUrlView.imageURLInputValue = replaceImageSourceCommand.value || '';
3609
- this._imageInsertUI.dropdownView.on('change:isOpen', ()=>{
3610
- if (this._imageInsertUI.dropdownView.isOpen) {
3611
- // Make sure that each time the panel shows up, the URL field remains in sync with the value of
3612
- // the command. If the user typed in the input, then canceled and re-opened it without changing
3613
- // the value of the media command (e.g. because they didn't change the selection), they would see
3614
- // the old value instead of the actual value of the command.
3615
- imageInsertUrlView.imageURLInputValue = replaceImageSourceCommand.value || '';
3616
- if (collapsibleView) {
3617
- collapsibleView.isCollapsed = true;
3618
- }
3619
- }
3620
- // Note: Use the low priority to make sure the following listener starts working after the
3621
- // default action of the drop-down is executed (i.e. the panel showed up). Otherwise, the
3622
- // invisible form/input cannot be focused/selected.
3623
- }, {
3624
- priority: 'low'
3625
- });
3626
- imageInsertUrlView.on('submit', ()=>{
3627
- if (replaceImageSourceCommand.isEnabled) {
3628
- editor.execute('replaceImageSource', {
3629
- source: imageInsertUrlView.imageURLInputValue
3630
- });
3631
- } else {
3632
- editor.execute('insertImage', {
3633
- source: imageInsertUrlView.imageURLInputValue
3634
- });
3635
- }
3636
- this._closePanel();
3637
- });
3638
- imageInsertUrlView.on('cancel', ()=>this._closePanel());
3639
- if (collapsibleView) {
3640
- collapsibleView.set({
3641
- isCollapsed: true
3642
- });
3643
- collapsibleView.bind('label').to(this._imageInsertUI, 'isImageSelected', (isImageSelected)=>isImageSelected ? t('Update image URL') : t('Insert image via URL'));
3644
- return collapsibleView;
3645
- }
3646
3970
  return imageInsertUrlView;
3647
3971
  }
3648
3972
  /**
3649
- * Creates the toolbar button.
3650
- */ _createInsertUrlButton(isOnlyOne) {
3651
- const ButtonClass = isOnlyOne ? DropdownButtonView : ButtonView;
3973
+ * Shows the insert image via URL form view in a modal.
3974
+ */ _showModal() {
3652
3975
  const editor = this.editor;
3653
- const button = new ButtonClass(editor.locale);
3654
- const t = editor.locale.t;
3655
- button.set({
3656
- icon: icons.imageUrl,
3657
- tooltip: true
3976
+ const locale = editor.locale;
3977
+ const t = locale.t;
3978
+ const dialog = editor.plugins.get('Dialog');
3979
+ if (!this._formView) {
3980
+ this._formView = this._createInsertUrlView();
3981
+ this._formView.on('submit', ()=>this._handleSave());
3982
+ }
3983
+ const replaceImageSourceCommand = editor.commands.get('replaceImageSource');
3984
+ this._formView.imageURLInputValue = replaceImageSourceCommand.value || '';
3985
+ dialog.show({
3986
+ id: 'insertImageViaUrl',
3987
+ title: this._imageInsertUI.isImageSelected ? t('Update image URL') : t('Insert image via URL'),
3988
+ isModal: true,
3989
+ content: this._formView,
3990
+ actionButtons: [
3991
+ {
3992
+ label: t('Cancel'),
3993
+ withText: true,
3994
+ onExecute: ()=>dialog.hide()
3995
+ },
3996
+ {
3997
+ label: t('Accept'),
3998
+ class: 'ck-button-action',
3999
+ withText: true,
4000
+ onExecute: ()=>this._handleSave()
4001
+ }
4002
+ ]
3658
4003
  });
3659
- button.bind('label').to(this._imageInsertUI, 'isImageSelected', (isImageSelected)=>isImageSelected ? t('Update image URL') : t('Insert image via URL'));
3660
- return button;
3661
4004
  }
3662
4005
  /**
3663
- * Closes the dropdown.
3664
- */ _closePanel() {
3665
- this.editor.editing.view.focus();
3666
- this._imageInsertUI.dropdownView.isOpen = false;
4006
+ * Executes appropriate command depending on selection and form value.
4007
+ */ _handleSave() {
4008
+ const replaceImageSourceCommand = this.editor.commands.get('replaceImageSource');
4009
+ // If an image element is currently selected, we want to replace its source attribute (instead of inserting a new image).
4010
+ // We detect if an image is selected by checking `replaceImageSource` command state.
4011
+ if (replaceImageSourceCommand.isEnabled) {
4012
+ this.editor.execute('replaceImageSource', {
4013
+ source: this._formView.imageURLInputValue
4014
+ });
4015
+ } else {
4016
+ this.editor.execute('insertImage', {
4017
+ source: this._formView.imageURLInputValue
4018
+ });
4019
+ }
4020
+ this.editor.plugins.get('Dialog').hide();
3667
4021
  }
3668
4022
  }
3669
4023
 
3670
- class ImageInsertViaUrl extends Plugin {
4024
+ /**
4025
+ * The image insert via URL plugin.
4026
+ *
4027
+ * For a detailed overview, check the {@glink features/images/images-inserting
4028
+ * Insert images via source URL} documentation.
4029
+ *
4030
+ * This plugin does not do anything directly, but it loads a set of specific plugins
4031
+ * to enable image inserting via implemented integrations:
4032
+ *
4033
+ * * {@link module:image/imageinsert/imageinsertui~ImageInsertUI},
4034
+ */ class ImageInsertViaUrl extends Plugin {
3671
4035
  /**
3672
- * @inheritDoc
3673
- */ static get pluginName() {
4036
+ * @inheritDoc
4037
+ */ static get pluginName() {
3674
4038
  return 'ImageInsertViaUrl';
3675
4039
  }
3676
4040
  /**
3677
- * @inheritDoc
3678
- */ static get requires() {
4041
+ * @inheritDoc
4042
+ */ static get requires() {
3679
4043
  return [
3680
4044
  ImageInsertViaUrlUI,
3681
4045
  ImageInsertUI
@@ -3683,15 +4047,26 @@ class ImageInsertViaUrl extends Plugin {
3683
4047
  }
3684
4048
  }
3685
4049
 
3686
- class ImageInsert extends Plugin {
4050
+ /**
4051
+ * The image insert plugin.
4052
+ *
4053
+ * For a detailed overview, check the {@glink features/images/image-upload/image-upload Image upload feature}
4054
+ * and {@glink features/images/images-inserting Insert images via source URL} documentation.
4055
+ *
4056
+ * This plugin does not do anything directly, but it loads a set of specific plugins
4057
+ * to enable image uploading or inserting via implemented integrations:
4058
+ *
4059
+ * * {@link module:image/imageupload~ImageUpload}
4060
+ * * {@link module:image/imageinsert/imageinsertui~ImageInsertUI}
4061
+ */ class ImageInsert extends Plugin {
3687
4062
  /**
3688
- * @inheritDoc
3689
- */ static get pluginName() {
4063
+ * @inheritDoc
4064
+ */ static get pluginName() {
3690
4065
  return 'ImageInsert';
3691
4066
  }
3692
4067
  /**
3693
- * @inheritDoc
3694
- */ static get requires() {
4068
+ * @inheritDoc
4069
+ */ static get requires() {
3695
4070
  return [
3696
4071
  ImageUpload,
3697
4072
  ImageInsertViaUrl,
@@ -3700,10 +4075,12 @@ class ImageInsert extends Plugin {
3700
4075
  }
3701
4076
  }
3702
4077
 
3703
- class ResizeImageCommand extends Command {
4078
+ /**
4079
+ * The resize image command. Currently, it only supports the width attribute.
4080
+ */ class ResizeImageCommand extends Command {
3704
4081
  /**
3705
- * @inheritDoc
3706
- */ refresh() {
4082
+ * @inheritDoc
4083
+ */ refresh() {
3707
4084
  const editor = this.editor;
3708
4085
  const imageUtils = editor.plugins.get('ImageUtils');
3709
4086
  const element = imageUtils.getClosestSelectedImageElement(editor.model.document.selection);
@@ -3718,20 +4095,20 @@ class ResizeImageCommand extends Command {
3718
4095
  }
3719
4096
  }
3720
4097
  /**
3721
- * Executes the command.
3722
- *
3723
- * ```ts
3724
- * // Sets the width to 50%:
3725
- * editor.execute( 'resizeImage', { width: '50%' } );
3726
- *
3727
- * // Removes the width attribute:
3728
- * editor.execute( 'resizeImage', { width: null } );
3729
- * ```
3730
- *
3731
- * @param options
3732
- * @param options.width The new width of the image.
3733
- * @fires execute
3734
- */ execute(options) {
4098
+ * Executes the command.
4099
+ *
4100
+ * ```ts
4101
+ * // Sets the width to 50%:
4102
+ * editor.execute( 'resizeImage', { width: '50%' } );
4103
+ *
4104
+ * // Removes the width attribute:
4105
+ * editor.execute( 'resizeImage', { width: null } );
4106
+ * ```
4107
+ *
4108
+ * @param options
4109
+ * @param options.width The new width of the image.
4110
+ * @fires execute
4111
+ */ execute(options) {
3735
4112
  const editor = this.editor;
3736
4113
  const model = editor.model;
3737
4114
  const imageUtils = editor.plugins.get('ImageUtils');
@@ -3750,22 +4127,62 @@ class ResizeImageCommand extends Command {
3750
4127
  }
3751
4128
  }
3752
4129
 
3753
- class ImageResizeEditing extends Plugin {
4130
+ /**
4131
+ * The image resize editing feature.
4132
+ *
4133
+ * It adds the ability to resize each image using handles or manually by
4134
+ * {@link module:image/imageresize/imageresizebuttons~ImageResizeButtons} buttons.
4135
+ */ class ImageResizeEditing extends Plugin {
3754
4136
  /**
3755
- * @inheritDoc
3756
- */ static get requires() {
4137
+ * @inheritDoc
4138
+ */ static get requires() {
3757
4139
  return [
3758
4140
  ImageUtils
3759
4141
  ];
3760
4142
  }
3761
4143
  /**
3762
- * @inheritDoc
3763
- */ static get pluginName() {
4144
+ * @inheritDoc
4145
+ */ static get pluginName() {
3764
4146
  return 'ImageResizeEditing';
3765
4147
  }
3766
4148
  /**
3767
- * @inheritDoc
3768
- */ init() {
4149
+ * @inheritDoc
4150
+ */ constructor(editor){
4151
+ super(editor);
4152
+ editor.config.define('image', {
4153
+ resizeUnit: '%',
4154
+ resizeOptions: [
4155
+ {
4156
+ name: 'resizeImage:original',
4157
+ value: null,
4158
+ icon: 'original'
4159
+ },
4160
+ {
4161
+ name: 'resizeImage:custom',
4162
+ value: 'custom',
4163
+ icon: 'custom'
4164
+ },
4165
+ {
4166
+ name: 'resizeImage:25',
4167
+ value: '25',
4168
+ icon: 'small'
4169
+ },
4170
+ {
4171
+ name: 'resizeImage:50',
4172
+ value: '50',
4173
+ icon: 'medium'
4174
+ },
4175
+ {
4176
+ name: 'resizeImage:75',
4177
+ value: '75',
4178
+ icon: 'large'
4179
+ }
4180
+ ]
4181
+ });
4182
+ }
4183
+ /**
4184
+ * @inheritDoc
4185
+ */ init() {
3769
4186
  const editor = this.editor;
3770
4187
  const resizeImageCommand = new ResizeImageCommand(editor);
3771
4188
  this._registerConverters('imageBlock');
@@ -3775,8 +4192,8 @@ class ImageResizeEditing extends Plugin {
3775
4192
  editor.commands.add('imageResize', resizeImageCommand);
3776
4193
  }
3777
4194
  /**
3778
- * @inheritDoc
3779
- */ afterInit() {
4195
+ * @inheritDoc
4196
+ */ afterInit() {
3780
4197
  this._registerSchema();
3781
4198
  }
3782
4199
  _registerSchema() {
@@ -3798,10 +4215,10 @@ class ImageResizeEditing extends Plugin {
3798
4215
  }
3799
4216
  }
3800
4217
  /**
3801
- * Registers image resize converters.
3802
- *
3803
- * @param imageType The type of the image.
3804
- */ _registerConverters(imageType) {
4218
+ * Registers image resize converters.
4219
+ *
4220
+ * @param imageType The type of the image.
4221
+ */ _registerConverters(imageType) {
3805
4222
  const editor = this.editor;
3806
4223
  const imageUtils = editor.plugins.get('ImageUtils');
3807
4224
  // Dedicated converter to propagate image's attribute to the img tag.
@@ -3879,66 +4296,45 @@ class ImageResizeEditing extends Plugin {
3879
4296
  }
3880
4297
  });
3881
4298
  }
3882
- /**
3883
- * @inheritDoc
3884
- */ constructor(editor){
3885
- super(editor);
3886
- editor.config.define('image', {
3887
- resizeUnit: '%',
3888
- resizeOptions: [
3889
- {
3890
- name: 'resizeImage:original',
3891
- value: null,
3892
- icon: 'original'
3893
- },
3894
- {
3895
- name: 'resizeImage:custom',
3896
- value: 'custom',
3897
- icon: 'custom'
3898
- },
3899
- {
3900
- name: 'resizeImage:25',
3901
- value: '25',
3902
- icon: 'small'
3903
- },
3904
- {
3905
- name: 'resizeImage:50',
3906
- value: '50',
3907
- icon: 'medium'
3908
- },
3909
- {
3910
- name: 'resizeImage:75',
3911
- value: '75',
3912
- icon: 'large'
3913
- }
3914
- ]
3915
- });
3916
- }
3917
4299
  }
3918
4300
 
3919
- const RESIZE_ICONS = {
3920
- small: icons.objectSizeSmall,
3921
- medium: icons.objectSizeMedium,
3922
- large: icons.objectSizeLarge,
3923
- custom: icons.objectSizeCustom,
3924
- original: icons.objectSizeFull
3925
- };
3926
- class ImageResizeButtons extends Plugin {
4301
+ const RESIZE_ICONS = /* #__PURE__ */ (()=>({
4302
+ small: icons.objectSizeSmall,
4303
+ medium: icons.objectSizeMedium,
4304
+ large: icons.objectSizeLarge,
4305
+ custom: icons.objectSizeCustom,
4306
+ original: icons.objectSizeFull
4307
+ }))();
4308
+ /**
4309
+ * The image resize buttons plugin.
4310
+ *
4311
+ * It adds a possibility to resize images using the toolbar dropdown or individual buttons, depending on the plugin configuration.
4312
+ */ class ImageResizeButtons extends Plugin {
3927
4313
  /**
3928
- * @inheritDoc
3929
- */ static get requires() {
4314
+ * @inheritDoc
4315
+ */ static get requires() {
3930
4316
  return [
3931
4317
  ImageResizeEditing
3932
4318
  ];
3933
4319
  }
3934
4320
  /**
3935
- * @inheritDoc
3936
- */ static get pluginName() {
4321
+ * @inheritDoc
4322
+ */ static get pluginName() {
3937
4323
  return 'ImageResizeButtons';
3938
4324
  }
3939
4325
  /**
3940
- * @inheritDoc
3941
- */ init() {
4326
+ * The resize unit.
4327
+ * @default '%'
4328
+ */ _resizeUnit;
4329
+ /**
4330
+ * @inheritDoc
4331
+ */ constructor(editor){
4332
+ super(editor);
4333
+ this._resizeUnit = editor.config.get('image.resizeUnit');
4334
+ }
4335
+ /**
4336
+ * @inheritDoc
4337
+ */ init() {
3942
4338
  const editor = this.editor;
3943
4339
  const options = editor.config.get('image.resizeOptions');
3944
4340
  const command = editor.commands.get('resizeImage');
@@ -3949,10 +4345,10 @@ class ImageResizeButtons extends Plugin {
3949
4345
  this._registerImageResizeDropdown(options);
3950
4346
  }
3951
4347
  /**
3952
- * A helper function that creates a standalone button component for the plugin.
3953
- *
3954
- * @param resizeOption A model of the resize option.
3955
- */ _registerImageResizeButton(option) {
4348
+ * A helper function that creates a standalone button component for the plugin.
4349
+ *
4350
+ * @param resizeOption A model of the resize option.
4351
+ */ _registerImageResizeButton(option) {
3956
4352
  const editor = this.editor;
3957
4353
  const { name, value, icon } = option;
3958
4354
  editor.ui.componentFactory.add(name, (locale)=>{
@@ -3961,15 +4357,15 @@ class ImageResizeButtons extends Plugin {
3961
4357
  const labelText = this._getOptionLabelValue(option, true);
3962
4358
  if (!RESIZE_ICONS[icon]) {
3963
4359
  /**
3964
- * When configuring {@link module:image/imageconfig~ImageConfig#resizeOptions `config.image.resizeOptions`} for standalone
3965
- * buttons, a valid `icon` token must be set for each option.
3966
- *
3967
- * See all valid options described in the
3968
- * {@link module:image/imageconfig~ImageResizeOption plugin configuration}.
3969
- *
3970
- * @error imageresizebuttons-missing-icon
3971
- * @param option Invalid image resize option.
3972
- */ throw new CKEditorError('imageresizebuttons-missing-icon', editor, option);
4360
+ * When configuring {@link module:image/imageconfig~ImageConfig#resizeOptions `config.image.resizeOptions`} for standalone
4361
+ * buttons, a valid `icon` token must be set for each option.
4362
+ *
4363
+ * See all valid options described in the
4364
+ * {@link module:image/imageconfig~ImageResizeOption plugin configuration}.
4365
+ *
4366
+ * @error imageresizebuttons-missing-icon
4367
+ * @param option Invalid image resize option.
4368
+ */ throw new CKEditorError('imageresizebuttons-missing-icon', editor, option);
3973
4369
  }
3974
4370
  button.set({
3975
4371
  // Use the `label` property for a verbose description (because of ARIA).
@@ -3998,11 +4394,11 @@ class ImageResizeButtons extends Plugin {
3998
4394
  });
3999
4395
  }
4000
4396
  /**
4001
- * A helper function that creates a dropdown component for the plugin containing all the resize options defined in
4002
- * the editor configuration.
4003
- *
4004
- * @param options An array of configured options.
4005
- */ _registerImageResizeDropdown(options) {
4397
+ * A helper function that creates a dropdown component for the plugin containing all the resize options defined in
4398
+ * the editor configuration.
4399
+ *
4400
+ * @param options An array of configured options.
4401
+ */ _registerImageResizeDropdown(options) {
4006
4402
  const editor = this.editor;
4007
4403
  const t = editor.t;
4008
4404
  const originalSizeOption = options.find((option)=>!option.value);
@@ -4052,13 +4448,13 @@ class ImageResizeButtons extends Plugin {
4052
4448
  editor.ui.componentFactory.add('imageResize', componentCreator);
4053
4449
  }
4054
4450
  /**
4055
- * A helper function for creating an option label value string.
4056
- *
4057
- * @param option A resize option object.
4058
- * @param forTooltip An optional flag for creating a tooltip label.
4059
- * @returns A user-defined label combined from the numeric value and the resize unit or the default label
4060
- * for reset options (`Original`).
4061
- */ _getOptionLabelValue(option, forTooltip = false) {
4451
+ * A helper function for creating an option label value string.
4452
+ *
4453
+ * @param option A resize option object.
4454
+ * @param forTooltip An optional flag for creating a tooltip label.
4455
+ * @returns A user-defined label combined from the numeric value and the resize unit or the default label
4456
+ * for reset options (`Original`).
4457
+ */ _getOptionLabelValue(option, forTooltip = false) {
4062
4458
  const t = this.editor.t;
4063
4459
  if (option.label) {
4064
4460
  return option.label;
@@ -4081,12 +4477,12 @@ class ImageResizeButtons extends Plugin {
4081
4477
  }
4082
4478
  }
4083
4479
  /**
4084
- * A helper function that parses the resize options and returns list item definitions ready for use in the dropdown.
4085
- *
4086
- * @param options The resize options.
4087
- * @param command The resize image command.
4088
- * @returns Dropdown item definitions.
4089
- */ _getResizeDropdownListItemDefinitions(options, command) {
4480
+ * A helper function that parses the resize options and returns list item definitions ready for use in the dropdown.
4481
+ *
4482
+ * @param options The resize options.
4483
+ * @param command The resize image command.
4484
+ * @returns Dropdown item definitions.
4485
+ */ _getResizeDropdownListItemDefinitions(options, command) {
4090
4486
  const { editor } = this;
4091
4487
  const itemDefinitions = new Collection();
4092
4488
  const optionsWithSerializedValues = options.map((option)=>{
@@ -4144,12 +4540,6 @@ class ImageResizeButtons extends Plugin {
4144
4540
  }
4145
4541
  return itemDefinitions;
4146
4542
  }
4147
- /**
4148
- * @inheritDoc
4149
- */ constructor(editor){
4150
- super(editor);
4151
- this._resizeUnit = editor.config.get('image.resizeUnit');
4152
- }
4153
4543
  }
4154
4544
  /**
4155
4545
  * A helper that checks if provided option triggers custom resize balloon.
@@ -4175,30 +4565,35 @@ class ImageResizeButtons extends Plugin {
4175
4565
 
4176
4566
  const RESIZABLE_IMAGES_CSS_SELECTOR = 'figure.image.ck-widget > img,' + 'figure.image.ck-widget > picture > img,' + 'figure.image.ck-widget > a > img,' + 'figure.image.ck-widget > a > picture > img,' + 'span.image-inline.ck-widget > img,' + 'span.image-inline.ck-widget > picture > img';
4177
4567
  const RESIZED_IMAGE_CLASS = 'image_resized';
4178
- class ImageResizeHandles extends Plugin {
4568
+ /**
4569
+ * The image resize by handles feature.
4570
+ *
4571
+ * It adds the ability to resize each image using handles or manually by
4572
+ * {@link module:image/imageresize/imageresizebuttons~ImageResizeButtons} buttons.
4573
+ */ class ImageResizeHandles extends Plugin {
4179
4574
  /**
4180
- * @inheritDoc
4181
- */ static get requires() {
4575
+ * @inheritDoc
4576
+ */ static get requires() {
4182
4577
  return [
4183
4578
  WidgetResize,
4184
4579
  ImageUtils
4185
4580
  ];
4186
4581
  }
4187
4582
  /**
4188
- * @inheritDoc
4189
- */ static get pluginName() {
4583
+ * @inheritDoc
4584
+ */ static get pluginName() {
4190
4585
  return 'ImageResizeHandles';
4191
4586
  }
4192
4587
  /**
4193
- * @inheritDoc
4194
- */ init() {
4588
+ * @inheritDoc
4589
+ */ init() {
4195
4590
  const command = this.editor.commands.get('resizeImage');
4196
4591
  this.bind('isEnabled').to(command);
4197
4592
  this._setupResizerCreator();
4198
4593
  }
4199
4594
  /**
4200
- * Attaches the listeners responsible for creating a resizer for each image, except for images inside the HTML embed preview.
4201
- */ _setupResizerCreator() {
4595
+ * Attaches the listeners responsible for creating a resizer for each image, except for images inside the HTML embed preview.
4596
+ */ _setupResizerCreator() {
4202
4597
  const editor = this.editor;
4203
4598
  const editingView = editor.editing.view;
4204
4599
  const imageUtils = editor.plugins.get('ImageUtils');
@@ -4323,6 +4718,8 @@ class ImageResizeHandles extends Plugin {
4323
4718
  * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
4324
4719
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4325
4720
  */ /**
4721
+ * @module image/imageresize/utils/getselectedimageeditornodes
4722
+ */ /**
4326
4723
  * Finds model, view and DOM element for selected image element. Returns `null` if there is no image selected.
4327
4724
  *
4328
4725
  * @param editor Editor instance.
@@ -4372,10 +4769,82 @@ class ImageResizeHandles extends Plugin {
4372
4769
  return tryCastDimensionsToUnit(imageParentWidthPx, imageHolderDimension, targetUnit);
4373
4770
  }
4374
4771
 
4375
- class ImageCustomResizeFormView extends View {
4772
+ /**
4773
+ * The ImageCustomResizeFormView class.
4774
+ */ class ImageCustomResizeFormView extends View {
4775
+ /**
4776
+ * Tracks information about the DOM focus in the form.
4777
+ */ focusTracker;
4778
+ /**
4779
+ * An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
4780
+ */ keystrokes;
4781
+ /**
4782
+ * Resize unit shortcut.
4783
+ */ unit;
4784
+ /**
4785
+ * An input with a label.
4786
+ */ labeledInput;
4787
+ /**
4788
+ * A button used to submit the form.
4789
+ */ saveButtonView;
4790
+ /**
4791
+ * A button used to cancel the form.
4792
+ */ cancelButtonView;
4793
+ /**
4794
+ * A collection of views which can be focused in the form.
4795
+ */ _focusables;
4796
+ /**
4797
+ * Helps cycling over {@link #_focusables} in the form.
4798
+ */ _focusCycler;
4799
+ /**
4800
+ * An array of form validators used by {@link #isValid}.
4801
+ */ _validators;
4802
+ /**
4803
+ * @inheritDoc
4804
+ */ constructor(locale, unit, validators){
4805
+ super(locale);
4806
+ const t = this.locale.t;
4807
+ this.focusTracker = new FocusTracker();
4808
+ this.keystrokes = new KeystrokeHandler();
4809
+ this.unit = unit;
4810
+ this.labeledInput = this._createLabeledInputView();
4811
+ this.saveButtonView = this._createButton(t('Save'), icons.check, 'ck-button-save');
4812
+ this.saveButtonView.type = 'submit';
4813
+ this.cancelButtonView = this._createButton(t('Cancel'), icons.cancel, 'ck-button-cancel', 'cancel');
4814
+ this._focusables = new ViewCollection();
4815
+ this._validators = validators;
4816
+ this._focusCycler = new FocusCycler({
4817
+ focusables: this._focusables,
4818
+ focusTracker: this.focusTracker,
4819
+ keystrokeHandler: this.keystrokes,
4820
+ actions: {
4821
+ // Navigate form fields backwards using the Shift + Tab keystroke.
4822
+ focusPrevious: 'shift + tab',
4823
+ // Navigate form fields forwards using the Tab key.
4824
+ focusNext: 'tab'
4825
+ }
4826
+ });
4827
+ this.setTemplate({
4828
+ tag: 'form',
4829
+ attributes: {
4830
+ class: [
4831
+ 'ck',
4832
+ 'ck-image-custom-resize-form',
4833
+ 'ck-responsive-form'
4834
+ ],
4835
+ // https://github.com/ckeditor/ckeditor5-image/issues/40
4836
+ tabindex: '-1'
4837
+ },
4838
+ children: [
4839
+ this.labeledInput,
4840
+ this.saveButtonView,
4841
+ this.cancelButtonView
4842
+ ]
4843
+ });
4844
+ }
4376
4845
  /**
4377
- * @inheritDoc
4378
- */ render() {
4846
+ * @inheritDoc
4847
+ */ render() {
4379
4848
  super.render();
4380
4849
  this.keystrokes.listenTo(this.element);
4381
4850
  submitHandler({
@@ -4393,21 +4862,21 @@ class ImageCustomResizeFormView extends View {
4393
4862
  });
4394
4863
  }
4395
4864
  /**
4396
- * @inheritDoc
4397
- */ destroy() {
4865
+ * @inheritDoc
4866
+ */ destroy() {
4398
4867
  super.destroy();
4399
4868
  this.focusTracker.destroy();
4400
4869
  this.keystrokes.destroy();
4401
4870
  }
4402
4871
  /**
4403
- * Creates the button view.
4404
- *
4405
- * @param label The button label
4406
- * @param icon The button's icon.
4407
- * @param className The additional button CSS class name.
4408
- * @param eventName The event name that the ButtonView#execute event will be delegated to.
4409
- * @returns The button view instance.
4410
- */ _createButton(label, icon, className, eventName) {
4872
+ * Creates the button view.
4873
+ *
4874
+ * @param label The button label
4875
+ * @param icon The button's icon.
4876
+ * @param className The additional button CSS class name.
4877
+ * @param eventName The event name that the ButtonView#execute event will be delegated to.
4878
+ * @returns The button view instance.
4879
+ */ _createButton(label, icon, className, eventName) {
4411
4880
  const button = new ButtonView(this.locale);
4412
4881
  button.set({
4413
4882
  label,
@@ -4425,10 +4894,10 @@ class ImageCustomResizeFormView extends View {
4425
4894
  return button;
4426
4895
  }
4427
4896
  /**
4428
- * Creates an input with a label.
4429
- *
4430
- * @returns Labeled field view instance.
4431
- */ _createLabeledInputView() {
4897
+ * Creates an input with a label.
4898
+ *
4899
+ * @returns Labeled field view instance.
4900
+ */ _createLabeledInputView() {
4432
4901
  const t = this.locale.t;
4433
4902
  const labeledInput = new LabeledFieldView(this.locale, createLabeledInputNumber);
4434
4903
  labeledInput.label = t('Resize image (in %0)', this.unit);
@@ -4438,8 +4907,8 @@ class ImageCustomResizeFormView extends View {
4438
4907
  return labeledInput;
4439
4908
  }
4440
4909
  /**
4441
- * Validates the form and returns `false` when some fields are invalid.
4442
- */ isValid() {
4910
+ * Validates the form and returns `false` when some fields are invalid.
4911
+ */ isValid() {
4443
4912
  this.resetFormStatus();
4444
4913
  for (const validator of this._validators){
4445
4914
  const errorText = validator(this);
@@ -4453,16 +4922,16 @@ class ImageCustomResizeFormView extends View {
4453
4922
  return true;
4454
4923
  }
4455
4924
  /**
4456
- * Cleans up the supplementary error and information text of the {@link #labeledInput}
4457
- * bringing them back to the state when the form has been displayed for the first time.
4458
- *
4459
- * See {@link #isValid}.
4460
- */ resetFormStatus() {
4925
+ * Cleans up the supplementary error and information text of the {@link #labeledInput}
4926
+ * bringing them back to the state when the form has been displayed for the first time.
4927
+ *
4928
+ * See {@link #isValid}.
4929
+ */ resetFormStatus() {
4461
4930
  this.labeledInput.errorText = null;
4462
4931
  }
4463
4932
  /**
4464
- * The native DOM `value` of the input element of {@link #labeledInput}.
4465
- */ get rawSize() {
4933
+ * The native DOM `value` of the input element of {@link #labeledInput}.
4934
+ */ get rawSize() {
4466
4935
  const { element } = this.labeledInput.fieldView;
4467
4936
  if (!element) {
4468
4937
  return null;
@@ -4470,8 +4939,8 @@ class ImageCustomResizeFormView extends View {
4470
4939
  return element.value;
4471
4940
  }
4472
4941
  /**
4473
- * Get numeric value of size. Returns `null` if value of size input element in {@link #labeledInput}.is not a number.
4474
- */ get parsedSize() {
4942
+ * Get numeric value of size. Returns `null` if value of size input element in {@link #labeledInput}.is not a number.
4943
+ */ get parsedSize() {
4475
4944
  const { rawSize } = this;
4476
4945
  if (rawSize === null) {
4477
4946
  return null;
@@ -4483,58 +4952,15 @@ class ImageCustomResizeFormView extends View {
4483
4952
  return parsed;
4484
4953
  }
4485
4954
  /**
4486
- * Returns serialized image input size with unit.
4487
- * Returns `null` if value of size input element in {@link #labeledInput}.is not a number.
4488
- */ get sizeWithUnits() {
4955
+ * Returns serialized image input size with unit.
4956
+ * Returns `null` if value of size input element in {@link #labeledInput}.is not a number.
4957
+ */ get sizeWithUnits() {
4489
4958
  const { parsedSize, unit } = this;
4490
4959
  if (parsedSize === null) {
4491
4960
  return null;
4492
4961
  }
4493
4962
  return `${parsedSize}${unit}`;
4494
4963
  }
4495
- /**
4496
- * @inheritDoc
4497
- */ constructor(locale, unit, validators){
4498
- super(locale);
4499
- const t = this.locale.t;
4500
- this.focusTracker = new FocusTracker();
4501
- this.keystrokes = new KeystrokeHandler();
4502
- this.unit = unit;
4503
- this.labeledInput = this._createLabeledInputView();
4504
- this.saveButtonView = this._createButton(t('Save'), icons.check, 'ck-button-save');
4505
- this.saveButtonView.type = 'submit';
4506
- this.cancelButtonView = this._createButton(t('Cancel'), icons.cancel, 'ck-button-cancel', 'cancel');
4507
- this._focusables = new ViewCollection();
4508
- this._validators = validators;
4509
- this._focusCycler = new FocusCycler({
4510
- focusables: this._focusables,
4511
- focusTracker: this.focusTracker,
4512
- keystrokeHandler: this.keystrokes,
4513
- actions: {
4514
- // Navigate form fields backwards using the Shift + Tab keystroke.
4515
- focusPrevious: 'shift + tab',
4516
- // Navigate form fields forwards using the Tab key.
4517
- focusNext: 'tab'
4518
- }
4519
- });
4520
- this.setTemplate({
4521
- tag: 'form',
4522
- attributes: {
4523
- class: [
4524
- 'ck',
4525
- 'ck-image-custom-resize-form',
4526
- 'ck-responsive-form'
4527
- ],
4528
- // https://github.com/ckeditor/ckeditor5-image/issues/40
4529
- tabindex: '-1'
4530
- },
4531
- children: [
4532
- this.labeledInput,
4533
- this.saveButtonView,
4534
- this.cancelButtonView
4535
- ]
4536
- });
4537
- }
4538
4964
  }
4539
4965
 
4540
4966
  /**
@@ -4562,22 +4988,32 @@ class ImageCustomResizeFormView extends View {
4562
4988
  };
4563
4989
  }
4564
4990
 
4565
- class ImageCustomResizeUI extends Plugin {
4991
+ /**
4992
+ * The custom resize image UI plugin.
4993
+ *
4994
+ * The plugin uses the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon}.
4995
+ */ class ImageCustomResizeUI extends Plugin {
4996
+ /**
4997
+ * The contextual balloon plugin instance.
4998
+ */ _balloon;
4566
4999
  /**
4567
- * @inheritDoc
4568
- */ static get requires() {
5000
+ * A form containing a textarea and buttons, used to change the `alt` text value.
5001
+ */ _form;
5002
+ /**
5003
+ * @inheritDoc
5004
+ */ static get requires() {
4569
5005
  return [
4570
5006
  ContextualBalloon
4571
5007
  ];
4572
5008
  }
4573
5009
  /**
4574
- * @inheritDoc
4575
- */ static get pluginName() {
5010
+ * @inheritDoc
5011
+ */ static get pluginName() {
4576
5012
  return 'ImageCustomResizeUI';
4577
5013
  }
4578
5014
  /**
4579
- * @inheritDoc
4580
- */ destroy() {
5015
+ * @inheritDoc
5016
+ */ destroy() {
4581
5017
  super.destroy();
4582
5018
  // Destroy created UI components as they are not automatically destroyed (see ckeditor5#1341).
4583
5019
  if (this._form) {
@@ -4585,9 +5021,9 @@ class ImageCustomResizeUI extends Plugin {
4585
5021
  }
4586
5022
  }
4587
5023
  /**
4588
- * Creates the {@link module:image/imageresize/ui/imagecustomresizeformview~ImageCustomResizeFormView}
4589
- * form.
4590
- */ _createForm(unit) {
5024
+ * Creates the {@link module:image/imageresize/ui/imagecustomresizeformview~ImageCustomResizeFormView}
5025
+ * form.
5026
+ */ _createForm(unit) {
4591
5027
  const editor = this.editor;
4592
5028
  this._balloon = this.editor.plugins.get('ContextualBalloon');
4593
5029
  this._form = new (CssTransitionDisablerMixin(ImageCustomResizeFormView))(editor.locale, unit, getFormValidators(editor));
@@ -4624,10 +5060,10 @@ class ImageCustomResizeUI extends Plugin {
4624
5060
  });
4625
5061
  }
4626
5062
  /**
4627
- * Shows the {@link #_form} in the {@link #_balloon}.
4628
- *
4629
- * @internal
4630
- */ _showForm(unit) {
5063
+ * Shows the {@link #_form} in the {@link #_balloon}.
5064
+ *
5065
+ * @internal
5066
+ */ _showForm(unit) {
4631
5067
  if (this._isVisible) {
4632
5068
  return;
4633
5069
  }
@@ -4662,10 +5098,10 @@ class ImageCustomResizeUI extends Plugin {
4662
5098
  this._form.enableCssTransitions();
4663
5099
  }
4664
5100
  /**
4665
- * Removes the {@link #_form} from the {@link #_balloon}.
4666
- *
4667
- * @param focusEditable Controls whether the editing view is focused afterwards.
4668
- */ _hideForm(focusEditable = false) {
5101
+ * Removes the {@link #_form} from the {@link #_balloon}.
5102
+ *
5103
+ * @param focusEditable Controls whether the editing view is focused afterwards.
5104
+ */ _hideForm(focusEditable = false) {
4669
5105
  if (!this._isInBalloon) {
4670
5106
  return;
4671
5107
  }
@@ -4680,13 +5116,13 @@ class ImageCustomResizeUI extends Plugin {
4680
5116
  }
4681
5117
  }
4682
5118
  /**
4683
- * Returns `true` when the {@link #_form} is the visible view in the {@link #_balloon}.
4684
- */ get _isVisible() {
5119
+ * Returns `true` when the {@link #_form} is the visible view in the {@link #_balloon}.
5120
+ */ get _isVisible() {
4685
5121
  return !!this._balloon && this._balloon.visibleView === this._form;
4686
5122
  }
4687
5123
  /**
4688
- * Returns `true` when the {@link #_form} is in the {@link #_balloon}.
4689
- */ get _isInBalloon() {
5124
+ * Returns `true` when the {@link #_form} is in the {@link #_balloon}.
5125
+ */ get _isInBalloon() {
4690
5126
  return !!this._balloon && this._balloon.hasView(this._form);
4691
5127
  }
4692
5128
  }
@@ -4708,10 +5144,14 @@ class ImageCustomResizeUI extends Plugin {
4708
5144
  ];
4709
5145
  }
4710
5146
 
4711
- class ImageResize extends Plugin {
5147
+ /**
5148
+ * The image resize plugin.
5149
+ *
5150
+ * It adds a possibility to resize each image using handles.
5151
+ */ class ImageResize extends Plugin {
4712
5152
  /**
4713
- * @inheritDoc
4714
- */ static get requires() {
5153
+ * @inheritDoc
5154
+ */ static get requires() {
4715
5155
  return [
4716
5156
  ImageResizeEditing,
4717
5157
  ImageResizeHandles,
@@ -4720,16 +5160,54 @@ class ImageResize extends Plugin {
4720
5160
  ];
4721
5161
  }
4722
5162
  /**
4723
- * @inheritDoc
4724
- */ static get pluginName() {
5163
+ * @inheritDoc
5164
+ */ static get pluginName() {
4725
5165
  return 'ImageResize';
4726
5166
  }
4727
5167
  }
4728
5168
 
4729
- class ImageStyleCommand extends Command {
5169
+ /**
5170
+ * The image style command. It is used to apply {@link module:image/imageconfig~ImageStyleConfig#options image style option}
5171
+ * to a selected image.
5172
+ *
5173
+ * **Note**: Executing this command may change the image model element if the desired style requires an image of a different
5174
+ * type. See {@link module:image/imagestyle/imagestylecommand~ImageStyleCommand#execute} to learn more.
5175
+ */ class ImageStyleCommand extends Command {
5176
+ /**
5177
+ * An object containing names of default style options for the inline and block images.
5178
+ * If there is no default style option for the given image type in the configuration,
5179
+ * the name will be `false`.
5180
+ */ _defaultStyles;
5181
+ /**
5182
+ * The styles handled by this command.
5183
+ */ _styles;
5184
+ /**
5185
+ * Creates an instance of the image style command. When executed, the command applies one of
5186
+ * {@link module:image/imageconfig~ImageStyleConfig#options style options} to the currently selected image.
5187
+ *
5188
+ * @param editor The editor instance.
5189
+ * @param styles The style options that this command supports.
5190
+ */ constructor(editor, styles){
5191
+ super(editor);
5192
+ this._defaultStyles = {
5193
+ imageBlock: false,
5194
+ imageInline: false
5195
+ };
5196
+ this._styles = new Map(styles.map((style)=>{
5197
+ if (style.isDefault) {
5198
+ for (const modelElementName of style.modelElements){
5199
+ this._defaultStyles[modelElementName] = style.name;
5200
+ }
5201
+ }
5202
+ return [
5203
+ style.name,
5204
+ style
5205
+ ];
5206
+ }));
5207
+ }
4730
5208
  /**
4731
- * @inheritDoc
4732
- */ refresh() {
5209
+ * @inheritDoc
5210
+ */ refresh() {
4733
5211
  const editor = this.editor;
4734
5212
  const imageUtils = editor.plugins.get('ImageUtils');
4735
5213
  const element = imageUtils.getClosestSelectedImageElement(this.editor.model.document.selection);
@@ -4743,21 +5221,21 @@ class ImageStyleCommand extends Command {
4743
5221
  }
4744
5222
  }
4745
5223
  /**
4746
- * Executes the command and applies the style to the currently selected image:
4747
- *
4748
- * ```ts
4749
- * editor.execute( 'imageStyle', { value: 'side' } );
4750
- * ```
4751
- *
4752
- * **Note**: Executing this command may change the image model element if the desired style requires an image
4753
- * of a different type. Learn more about {@link module:image/imageconfig~ImageStyleOptionDefinition#modelElements model element}
4754
- * configuration for the style option.
4755
- *
4756
- * @param options.value The name of the style (as configured in {@link module:image/imageconfig~ImageStyleConfig#options}).
4757
- * @param options.setImageSizes Specifies whether the image `width` and `height` attributes should be set automatically.
4758
- * The default is `true`.
4759
- * @fires execute
4760
- */ execute(options = {}) {
5224
+ * Executes the command and applies the style to the currently selected image:
5225
+ *
5226
+ * ```ts
5227
+ * editor.execute( 'imageStyle', { value: 'side' } );
5228
+ * ```
5229
+ *
5230
+ * **Note**: Executing this command may change the image model element if the desired style requires an image
5231
+ * of a different type. Learn more about {@link module:image/imageconfig~ImageStyleOptionDefinition#modelElements model element}
5232
+ * configuration for the style option.
5233
+ *
5234
+ * @param options.value The name of the style (as configured in {@link module:image/imageconfig~ImageStyleConfig#options}).
5235
+ * @param options.setImageSizes Specifies whether the image `width` and `height` attributes should be set automatically.
5236
+ * The default is `true`.
5237
+ * @fires execute
5238
+ */ execute(options = {}) {
4761
5239
  const editor = this.editor;
4762
5240
  const model = editor.model;
4763
5241
  const imageUtils = editor.plugins.get('ImageUtils');
@@ -4786,41 +5264,16 @@ class ImageStyleCommand extends Command {
4786
5264
  });
4787
5265
  }
4788
5266
  /**
4789
- * Returns `true` if requested style change would trigger the image type change.
4790
- *
4791
- * @param requestedStyle The name of the style (as configured in {@link module:image/imageconfig~ImageStyleConfig#options}).
4792
- * @param imageElement The image model element.
4793
- */ shouldConvertImageType(requestedStyle, imageElement) {
5267
+ * Returns `true` if requested style change would trigger the image type change.
5268
+ *
5269
+ * @param requestedStyle The name of the style (as configured in {@link module:image/imageconfig~ImageStyleConfig#options}).
5270
+ * @param imageElement The image model element.
5271
+ */ shouldConvertImageType(requestedStyle, imageElement) {
4794
5272
  const supportedTypes = this._styles.get(requestedStyle).modelElements;
4795
5273
  return !supportedTypes.includes(imageElement.name);
4796
5274
  }
4797
- /**
4798
- * Creates an instance of the image style command. When executed, the command applies one of
4799
- * {@link module:image/imageconfig~ImageStyleConfig#options style options} to the currently selected image.
4800
- *
4801
- * @param editor The editor instance.
4802
- * @param styles The style options that this command supports.
4803
- */ constructor(editor, styles){
4804
- super(editor);
4805
- this._defaultStyles = {
4806
- imageBlock: false,
4807
- imageInline: false
4808
- };
4809
- this._styles = new Map(styles.map((style)=>{
4810
- if (style.isDefault) {
4811
- for (const modelElementName of style.modelElements){
4812
- this._defaultStyles[modelElementName] = style.name;
4813
- }
4814
- }
4815
- return [
4816
- style.name,
4817
- style
4818
- ];
4819
- }));
4820
- }
4821
5275
  }
4822
5276
 
4823
- const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, objectBlockLeft, objectBlockRight } = icons;
4824
5277
  /**
4825
5278
  * Default image style options provided by the plugin that can be referred in the {@link module:image/imageconfig~ImageConfig#styles}
4826
5279
  * configuration.
@@ -4844,7 +5297,7 @@ const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, ob
4844
5297
  return {
4845
5298
  name: 'inline',
4846
5299
  title: 'In line',
4847
- icon: objectInline,
5300
+ icon: icons.objectInline,
4848
5301
  modelElements: [
4849
5302
  'imageInline'
4850
5303
  ],
@@ -4856,7 +5309,7 @@ const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, ob
4856
5309
  return {
4857
5310
  name: 'alignLeft',
4858
5311
  title: 'Left aligned image',
4859
- icon: objectLeft,
5312
+ icon: icons.objectLeft,
4860
5313
  modelElements: [
4861
5314
  'imageBlock',
4862
5315
  'imageInline'
@@ -4869,7 +5322,7 @@ const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, ob
4869
5322
  return {
4870
5323
  name: 'alignBlockLeft',
4871
5324
  title: 'Left aligned image',
4872
- icon: objectBlockLeft,
5325
+ icon: icons.objectBlockLeft,
4873
5326
  modelElements: [
4874
5327
  'imageBlock'
4875
5328
  ],
@@ -4881,7 +5334,7 @@ const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, ob
4881
5334
  return {
4882
5335
  name: 'alignCenter',
4883
5336
  title: 'Centered image',
4884
- icon: objectCenter,
5337
+ icon: icons.objectCenter,
4885
5338
  modelElements: [
4886
5339
  'imageBlock'
4887
5340
  ],
@@ -4893,7 +5346,7 @@ const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, ob
4893
5346
  return {
4894
5347
  name: 'alignRight',
4895
5348
  title: 'Right aligned image',
4896
- icon: objectRight,
5349
+ icon: icons.objectRight,
4897
5350
  modelElements: [
4898
5351
  'imageBlock',
4899
5352
  'imageInline'
@@ -4906,7 +5359,7 @@ const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, ob
4906
5359
  return {
4907
5360
  name: 'alignBlockRight',
4908
5361
  title: 'Right aligned image',
4909
- icon: objectBlockRight,
5362
+ icon: icons.objectBlockRight,
4910
5363
  modelElements: [
4911
5364
  'imageBlock'
4912
5365
  ],
@@ -4918,7 +5371,7 @@ const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, ob
4918
5371
  return {
4919
5372
  name: 'block',
4920
5373
  title: 'Centered image',
4921
- icon: objectCenter,
5374
+ icon: icons.objectCenter,
4922
5375
  modelElements: [
4923
5376
  'imageBlock'
4924
5377
  ],
@@ -4930,7 +5383,7 @@ const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, ob
4930
5383
  return {
4931
5384
  name: 'side',
4932
5385
  title: 'Side image',
4933
- icon: objectRight,
5386
+ icon: icons.objectRight,
4934
5387
  modelElements: [
4935
5388
  'imageBlock'
4936
5389
  ],
@@ -4945,15 +5398,15 @@ const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, ob
4945
5398
  * See {@link module:image/imageconfig~ImageStyleOptionDefinition#icon} to learn more.
4946
5399
  *
4947
5400
  * There are 7 default icons available: `'full'`, `'left'`, `'inlineLeft'`, `'center'`, `'right'`, `'inlineRight'`, and `'inline'`.
4948
- */ const DEFAULT_ICONS = {
4949
- full: objectFullWidth,
4950
- left: objectBlockLeft,
4951
- right: objectBlockRight,
4952
- center: objectCenter,
4953
- inlineLeft: objectLeft,
4954
- inlineRight: objectRight,
4955
- inline: objectInline
4956
- };
5401
+ */ const DEFAULT_ICONS = /* #__PURE__ */ (()=>({
5402
+ full: icons.objectFullWidth,
5403
+ left: icons.objectBlockLeft,
5404
+ right: icons.objectBlockRight,
5405
+ center: icons.objectCenter,
5406
+ inlineLeft: icons.objectLeft,
5407
+ inlineRight: icons.objectRight,
5408
+ inline: icons.objectInline
5409
+ }))();
4957
5410
  /**
4958
5411
  * Default drop-downs provided by the plugin that can be referred in the {@link module:image/imageconfig~ImageConfig#toolbar}
4959
5412
  * configuration. The drop-downs are containers for the {@link module:image/imageconfig~ImageStyleConfig#options image style options}.
@@ -5110,20 +5563,20 @@ const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, ob
5110
5563
  // Check if the option is supported by any of the loaded plugins.
5111
5564
  if (!modelElements.some((elementName)=>supportedElements.includes(elementName))) {
5112
5565
  /**
5113
- * In order to work correctly, each image style {@link module:image/imageconfig~ImageStyleOptionDefinition option}
5114
- * requires specific model elements (also: types of images) to be supported by the editor.
5115
- *
5116
- * Model element names to which the image style option can be applied are defined in the
5117
- * {@link module:image/imageconfig~ImageStyleOptionDefinition#modelElements} property of the style option
5118
- * definition.
5119
- *
5120
- * Explore the warning in the console to find out precisely which option is not supported and which editor plugins
5121
- * are missing. Make sure these plugins are loaded in your editor to get this image style option working.
5122
- *
5123
- * @error image-style-missing-dependency
5124
- * @param {String} [option] The name of the unsupported option.
5125
- * @param {String} [missingPlugins] The names of the plugins one of which has to be loaded for the particular option.
5126
- */ logWarning('image-style-missing-dependency', {
5566
+ * In order to work correctly, each image style {@link module:image/imageconfig~ImageStyleOptionDefinition option}
5567
+ * requires specific model elements (also: types of images) to be supported by the editor.
5568
+ *
5569
+ * Model element names to which the image style option can be applied are defined in the
5570
+ * {@link module:image/imageconfig~ImageStyleOptionDefinition#modelElements} property of the style option
5571
+ * definition.
5572
+ *
5573
+ * Explore the warning in the console to find out precisely which option is not supported and which editor plugins
5574
+ * are missing. Make sure these plugins are loaded in your editor to get this image style option working.
5575
+ *
5576
+ * @error image-style-missing-dependency
5577
+ * @param {String} [option] The name of the unsupported option.
5578
+ * @param {String} [missingPlugins] The names of the plugins one of which has to be loaded for the particular option.
5579
+ */ logWarning('image-style-missing-dependency', {
5127
5580
  style: option,
5128
5581
  missingPlugins: modelElements.map((name)=>name === 'imageBlock' ? 'ImageBlockEditing' : 'ImageInlineEditing')
5129
5582
  });
@@ -5150,17 +5603,17 @@ const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, ob
5150
5603
  * Displays a console warning with the 'image-style-configuration-definition-invalid' error.
5151
5604
  */ function warnInvalidStyle(info) {
5152
5605
  /**
5153
- * The image style definition provided in the configuration is invalid.
5154
- *
5155
- * Please make sure the definition implements properly one of the following:
5156
- *
5157
- * * {@link module:image/imageconfig~ImageStyleOptionDefinition image style option definition},
5158
- * * {@link module:image/imageconfig~ImageStyleDropdownDefinition image style dropdown definition}
5159
- *
5160
- * @error image-style-configuration-definition-invalid
5161
- * @param {String} [dropdown] The name of the invalid drop-down
5162
- * @param {String} [style] The name of the invalid image style option
5163
- */ logWarning('image-style-configuration-definition-invalid', info);
5606
+ * The image style definition provided in the configuration is invalid.
5607
+ *
5608
+ * Please make sure the definition implements properly one of the following:
5609
+ *
5610
+ * * {@link module:image/imageconfig~ImageStyleOptionDefinition image style option definition},
5611
+ * * {@link module:image/imageconfig~ImageStyleDropdownDefinition image style dropdown definition}
5612
+ *
5613
+ * @error image-style-configuration-definition-invalid
5614
+ * @param {String} [dropdown] The name of the invalid drop-down
5615
+ * @param {String} [style] The name of the invalid image style option
5616
+ */ logWarning('image-style-configuration-definition-invalid', info);
5164
5617
  }
5165
5618
  var utils = {
5166
5619
  normalizeStyles,
@@ -5245,22 +5698,36 @@ var utils = {
5245
5698
  }
5246
5699
  }
5247
5700
 
5248
- class ImageStyleEditing extends Plugin {
5701
+ /**
5702
+ * The image style engine plugin. It sets the default configuration, creates converters and registers
5703
+ * {@link module:image/imagestyle/imagestylecommand~ImageStyleCommand ImageStyleCommand}.
5704
+ */ class ImageStyleEditing extends Plugin {
5249
5705
  /**
5250
- * @inheritDoc
5251
- */ static get pluginName() {
5706
+ * @inheritDoc
5707
+ */ static get pluginName() {
5252
5708
  return 'ImageStyleEditing';
5253
5709
  }
5254
5710
  /**
5255
- * @inheritDoc
5256
- */ static get requires() {
5711
+ * @inheritDoc
5712
+ */ static get requires() {
5257
5713
  return [
5258
5714
  ImageUtils
5259
5715
  ];
5260
5716
  }
5261
5717
  /**
5262
- * @inheritDoc
5263
- */ init() {
5718
+ * It contains a list of the normalized and validated style options.
5719
+ *
5720
+ * * Each option contains a complete icon markup.
5721
+ * * The style options not supported by any of the loaded image editing plugins (
5722
+ * {@link module:image/image/imageinlineediting~ImageInlineEditing `ImageInlineEditing`} or
5723
+ * {@link module:image/image/imageblockediting~ImageBlockEditing `ImageBlockEditing`}) are filtered out.
5724
+ *
5725
+ * @internal
5726
+ * @readonly
5727
+ */ normalizedStyles;
5728
+ /**
5729
+ * @inheritDoc
5730
+ */ init() {
5264
5731
  const { normalizeStyles, getDefaultStylesConfiguration } = utils;
5265
5732
  const editor = this.editor;
5266
5733
  const isBlockPluginLoaded = editor.plugins.has('ImageBlockEditing');
@@ -5277,10 +5744,10 @@ class ImageStyleEditing extends Plugin {
5277
5744
  editor.commands.add('imageStyle', new ImageStyleCommand(editor, this.normalizedStyles));
5278
5745
  }
5279
5746
  /**
5280
- * Sets the editor conversion taking the presence of
5281
- * {@link module:image/image/imageinlineediting~ImageInlineEditing `ImageInlineEditing`}
5282
- * and {@link module:image/image/imageblockediting~ImageBlockEditing `ImageBlockEditing`} plugins into consideration.
5283
- */ _setupConversion(isBlockPluginLoaded, isInlinePluginLoaded) {
5747
+ * Sets the editor conversion taking the presence of
5748
+ * {@link module:image/image/imageinlineediting~ImageInlineEditing `ImageInlineEditing`}
5749
+ * and {@link module:image/image/imageblockediting~ImageBlockEditing `ImageBlockEditing`} plugins into consideration.
5750
+ */ _setupConversion(isBlockPluginLoaded, isInlinePluginLoaded) {
5284
5751
  const editor = this.editor;
5285
5752
  const schema = editor.model.schema;
5286
5753
  const modelToViewConverter = modelToViewStyleAttribute(this.normalizedStyles);
@@ -5309,8 +5776,8 @@ class ImageStyleEditing extends Plugin {
5309
5776
  }
5310
5777
  }
5311
5778
  /**
5312
- * Registers a post-fixer that will make sure that the style attribute value is correct for a specific image type (block vs inline).
5313
- */ _setupPostFixer() {
5779
+ * Registers a post-fixer that will make sure that the style attribute value is correct for a specific image type (block vs inline).
5780
+ */ _setupPostFixer() {
5314
5781
  const editor = this.editor;
5315
5782
  const document = editor.model.document;
5316
5783
  const imageUtils = editor.plugins.get(ImageUtils);
@@ -5346,34 +5813,40 @@ class ImageStyleEditing extends Plugin {
5346
5813
  }
5347
5814
  }
5348
5815
 
5349
- class ImageStyleUI extends Plugin {
5816
+ /**
5817
+ * The image style UI plugin.
5818
+ *
5819
+ * It registers buttons corresponding to the {@link module:image/imageconfig~ImageConfig#styles} configuration.
5820
+ * It also registers the {@link module:image/imagestyle/utils#DEFAULT_DROPDOWN_DEFINITIONS default drop-downs} and the
5821
+ * custom drop-downs defined by the developer in the {@link module:image/imageconfig~ImageConfig#toolbar} configuration.
5822
+ */ class ImageStyleUI extends Plugin {
5350
5823
  /**
5351
- * @inheritDoc
5352
- */ static get requires() {
5824
+ * @inheritDoc
5825
+ */ static get requires() {
5353
5826
  return [
5354
5827
  ImageStyleEditing
5355
5828
  ];
5356
5829
  }
5357
5830
  /**
5358
- * @inheritDoc
5359
- */ static get pluginName() {
5831
+ * @inheritDoc
5832
+ */ static get pluginName() {
5360
5833
  return 'ImageStyleUI';
5361
5834
  }
5362
5835
  /**
5363
- * Returns the default localized style titles provided by the plugin.
5364
- *
5365
- * The following localized titles corresponding with
5366
- * {@link module:image/imagestyle/utils#DEFAULT_OPTIONS} are available:
5367
- *
5368
- * * `'Wrap text'`,
5369
- * * `'Break text'`,
5370
- * * `'In line'`,
5371
- * * `'Full size image'`,
5372
- * * `'Side image'`,
5373
- * * `'Left aligned image'`,
5374
- * * `'Centered image'`,
5375
- * * `'Right aligned image'`
5376
- */ get localizedDefaultStylesTitles() {
5836
+ * Returns the default localized style titles provided by the plugin.
5837
+ *
5838
+ * The following localized titles corresponding with
5839
+ * {@link module:image/imagestyle/utils#DEFAULT_OPTIONS} are available:
5840
+ *
5841
+ * * `'Wrap text'`,
5842
+ * * `'Break text'`,
5843
+ * * `'In line'`,
5844
+ * * `'Full size image'`,
5845
+ * * `'Side image'`,
5846
+ * * `'Left aligned image'`,
5847
+ * * `'Centered image'`,
5848
+ * * `'Right aligned image'`
5849
+ */ get localizedDefaultStylesTitles() {
5377
5850
  const t = this.editor.t;
5378
5851
  return {
5379
5852
  'Wrap text': t('Wrap text'),
@@ -5387,8 +5860,8 @@ class ImageStyleUI extends Plugin {
5387
5860
  };
5388
5861
  }
5389
5862
  /**
5390
- * @inheritDoc
5391
- */ init() {
5863
+ * @inheritDoc
5864
+ */ init() {
5392
5865
  const plugins = this.editor.plugins;
5393
5866
  const toolbarConfig = this.editor.config.get('image.toolbar') || [];
5394
5867
  const imageStyleEditing = plugins.get('ImageStyleEditing');
@@ -5405,8 +5878,8 @@ class ImageStyleUI extends Plugin {
5405
5878
  }
5406
5879
  }
5407
5880
  /**
5408
- * Creates a dropdown and stores it in the editor {@link module:ui/componentfactory~ComponentFactory}.
5409
- */ _createDropdown(dropdownConfig, definedStyles) {
5881
+ * Creates a dropdown and stores it in the editor {@link module:ui/componentfactory~ComponentFactory}.
5882
+ */ _createDropdown(dropdownConfig, definedStyles) {
5410
5883
  const factory = this.editor.ui.componentFactory;
5411
5884
  factory.add(dropdownConfig.name, (locale)=>{
5412
5885
  let defaultButton;
@@ -5465,8 +5938,8 @@ class ImageStyleUI extends Plugin {
5465
5938
  });
5466
5939
  }
5467
5940
  /**
5468
- * Creates a button and stores it in the editor {@link module:ui/componentfactory~ComponentFactory}.
5469
- */ _createButton(buttonConfig) {
5941
+ * Creates a button and stores it in the editor {@link module:ui/componentfactory~ComponentFactory}.
5942
+ */ _createButton(buttonConfig) {
5470
5943
  const buttonName = buttonConfig.name;
5471
5944
  this.editor.ui.componentFactory.add(getUIComponentName(buttonName), (locale)=>{
5472
5945
  const command = this.editor.commands.get('imageStyle');
@@ -5513,39 +5986,60 @@ class ImageStyleUI extends Plugin {
5513
5986
  return (dropdownTitle ? dropdownTitle + ': ' : '') + buttonTitle;
5514
5987
  }
5515
5988
 
5516
- class ImageStyle extends Plugin {
5989
+ /**
5990
+ * The image style plugin.
5991
+ *
5992
+ * For a detailed overview of the image styles feature, check the {@glink features/images/images-styles documentation}.
5993
+ *
5994
+ * This is a "glue" plugin which loads the following plugins:
5995
+ * * {@link module:image/imagestyle/imagestyleediting~ImageStyleEditing},
5996
+ * * {@link module:image/imagestyle/imagestyleui~ImageStyleUI}
5997
+ *
5998
+ * It provides a default configuration, which can be extended or overwritten.
5999
+ * Read more about the {@link module:image/imageconfig~ImageConfig#styles image styles configuration}.
6000
+ */ class ImageStyle extends Plugin {
5517
6001
  /**
5518
- * @inheritDoc
5519
- */ static get requires() {
6002
+ * @inheritDoc
6003
+ */ static get requires() {
5520
6004
  return [
5521
6005
  ImageStyleEditing,
5522
6006
  ImageStyleUI
5523
6007
  ];
5524
6008
  }
5525
6009
  /**
5526
- * @inheritDoc
5527
- */ static get pluginName() {
6010
+ * @inheritDoc
6011
+ */ static get pluginName() {
5528
6012
  return 'ImageStyle';
5529
6013
  }
5530
6014
  }
5531
6015
 
5532
- class ImageToolbar extends Plugin {
6016
+ /**
6017
+ * The image toolbar plugin. It creates and manages the image toolbar (the toolbar displayed when an image is selected).
6018
+ *
6019
+ * For an overview, check the {@glink features/images/images-overview#image-contextual-toolbar image contextual toolbar} documentation.
6020
+ *
6021
+ * Instances of toolbar components (e.g. buttons) are created using the editor's
6022
+ * {@link module:ui/componentfactory~ComponentFactory component factory}
6023
+ * based on the {@link module:image/imageconfig~ImageConfig#toolbar `image.toolbar` configuration option}.
6024
+ *
6025
+ * The toolbar uses the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon}.
6026
+ */ class ImageToolbar extends Plugin {
5533
6027
  /**
5534
- * @inheritDoc
5535
- */ static get requires() {
6028
+ * @inheritDoc
6029
+ */ static get requires() {
5536
6030
  return [
5537
6031
  WidgetToolbarRepository,
5538
6032
  ImageUtils
5539
6033
  ];
5540
6034
  }
5541
6035
  /**
5542
- * @inheritDoc
5543
- */ static get pluginName() {
6036
+ * @inheritDoc
6037
+ */ static get pluginName() {
5544
6038
  return 'ImageToolbar';
5545
6039
  }
5546
6040
  /**
5547
- * @inheritDoc
5548
- */ afterInit() {
6041
+ * @inheritDoc
6042
+ */ afterInit() {
5549
6043
  const editor = this.editor;
5550
6044
  const t = editor.t;
5551
6045
  const widgetToolbarRepository = editor.plugins.get(WidgetToolbarRepository);
@@ -5564,23 +6058,75 @@ class ImageToolbar extends Plugin {
5564
6058
  return config.map((item)=>isObject(item) ? item.name : item);
5565
6059
  }
5566
6060
 
5567
- class PictureEditing extends Plugin {
6061
+ /**
6062
+ * This plugin enables the [`<picture>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture) element support in the editor.
6063
+ *
6064
+ * * It enables the `sources` model attribute on `imageBlock` and `imageInline` model elements
6065
+ * (brought by {@link module:image/imageblock~ImageBlock} and {@link module:image/imageinline~ImageInline}, respectively).
6066
+ * * It translates the `sources` model element to the view (also: data) structure that may look as follows:
6067
+ *
6068
+ * ```html
6069
+ * <p>Inline image using picture:
6070
+ * <picture>
6071
+ * <source media="(min-width: 800px)" srcset="image-large.webp" type="image/webp">
6072
+ * <source media="(max-width: 800px)" srcset="image-small.webp" type="image/webp">
6073
+ * <!-- Other sources as specified in the "sources" model attribute... -->
6074
+ * <img src="image.png" alt="An image using picture" />
6075
+ * </picture>
6076
+ * </p>
6077
+ *
6078
+ * <p>Block image using picture:</p>
6079
+ * <figure class="image">
6080
+ * <picture>
6081
+ * <source media="(min-width: 800px)" srcset="image-large.webp" type="image/webp">
6082
+ * <source media="(max-width: 800px)" srcset="image-small.webp" type="image/webp">
6083
+ * <!-- Other sources as specified in the "sources" model attribute... -->
6084
+ * <img src="image.png" alt="An image using picture" />
6085
+ * </picture>
6086
+ * <figcaption>Caption of the image</figcaption>
6087
+ * </figure>
6088
+ * ```
6089
+ *
6090
+ * **Note:** The value of the `sources` {@glink framework/architecture/editing-engine#changing-the-model model attribute}
6091
+ * in both examples equals:
6092
+ *
6093
+ * ```css
6094
+ * [
6095
+ * {
6096
+ * media: '(min-width: 800px)',
6097
+ * srcset: 'image-large.webp',
6098
+ * type: 'image/webp'
6099
+ * },
6100
+ * {
6101
+ * media: '(max-width: 800px)',
6102
+ * srcset: 'image-small.webp',
6103
+ * type: 'image/webp'
6104
+ * }
6105
+ * ]
6106
+ * ```
6107
+ *
6108
+ * * It integrates with the {@link module:image/imageupload~ImageUpload} plugin so images uploaded in the editor
6109
+ * automatically render using `<picture>` if the {@glink features/images/image-upload/image-upload upload adapter}
6110
+ * supports image sources and provides neccessary data.
6111
+ *
6112
+ * @private
6113
+ */ class PictureEditing extends Plugin {
5568
6114
  /**
5569
- * @inheritDoc
5570
- */ static get requires() {
6115
+ * @inheritDoc
6116
+ */ static get requires() {
5571
6117
  return [
5572
6118
  ImageEditing,
5573
6119
  ImageUtils
5574
6120
  ];
5575
6121
  }
5576
6122
  /**
5577
- * @inheritDoc
5578
- */ static get pluginName() {
6123
+ * @inheritDoc
6124
+ */ static get pluginName() {
5579
6125
  return 'PictureEditing';
5580
6126
  }
5581
6127
  /**
5582
- * @inheritDoc
5583
- */ afterInit() {
6128
+ * @inheritDoc
6129
+ */ afterInit() {
5584
6130
  const editor = this.editor;
5585
6131
  if (editor.plugins.has('ImageBlockEditing')) {
5586
6132
  editor.model.schema.extend('imageBlock', {
@@ -5600,9 +6146,9 @@ class PictureEditing extends Plugin {
5600
6146
  this._setupImageUploadEditingIntegration();
5601
6147
  }
5602
6148
  /**
5603
- * Configures conversion pipelines to support upcasting and downcasting images using the `<picture>` view element
5604
- * and the model `sources` attribute.
5605
- */ _setupConversion() {
6149
+ * Configures conversion pipelines to support upcasting and downcasting images using the `<picture>` view element
6150
+ * and the model `sources` attribute.
6151
+ */ _setupConversion() {
5606
6152
  const editor = this.editor;
5607
6153
  const conversion = editor.conversion;
5608
6154
  const imageUtils = editor.plugins.get('ImageUtils');
@@ -5610,10 +6156,10 @@ class PictureEditing extends Plugin {
5610
6156
  conversion.for('downcast').add(downcastSourcesAttribute(imageUtils));
5611
6157
  }
5612
6158
  /**
5613
- * Makes it possible for uploaded images to get the `sources` model attribute and the `<picture>...</picture>`
5614
- * view structure out-of-the-box if relevant data is provided along the
5615
- * {@link module:image/imageupload/imageuploadediting~ImageUploadEditing#event:uploadComplete} event.
5616
- */ _setupImageUploadEditingIntegration() {
6159
+ * Makes it possible for uploaded images to get the `sources` model attribute and the `<picture>...</picture>`
6160
+ * view structure out-of-the-box if relevant data is provided along the
6161
+ * {@link module:image/imageupload/imageuploadediting~ImageUploadEditing#event:uploadComplete} event.
6162
+ */ _setupImageUploadEditingIntegration() {
5617
6163
  const editor = this.editor;
5618
6164
  if (!editor.plugins.has('ImageUploadEditing')) {
5619
6165
  return;