@ckeditor/ckeditor5-image 27.1.0 → 29.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +3 -3
  3. package/build/image.js +1 -1
  4. package/build/translations/ar.js +1 -0
  5. package/build/translations/ast.js +1 -0
  6. package/build/translations/az.js +1 -0
  7. package/build/translations/bg.js +1 -0
  8. package/build/translations/cs.js +1 -0
  9. package/build/translations/da.js +1 -0
  10. package/build/translations/de-ch.js +1 -0
  11. package/build/translations/de.js +1 -0
  12. package/build/translations/el.js +1 -0
  13. package/build/translations/en-au.js +1 -0
  14. package/build/translations/en-gb.js +1 -0
  15. package/build/translations/eo.js +1 -0
  16. package/build/translations/es.js +1 -0
  17. package/build/translations/et.js +1 -0
  18. package/build/translations/eu.js +1 -0
  19. package/build/translations/fa.js +1 -0
  20. package/build/translations/fi.js +1 -0
  21. package/build/translations/fr.js +1 -0
  22. package/build/translations/gl.js +1 -0
  23. package/build/translations/he.js +1 -0
  24. package/build/translations/hi.js +1 -0
  25. package/build/translations/hr.js +1 -0
  26. package/build/translations/hu.js +1 -0
  27. package/build/translations/id.js +1 -0
  28. package/build/translations/it.js +1 -0
  29. package/build/translations/ja.js +1 -0
  30. package/build/translations/km.js +1 -0
  31. package/build/translations/kn.js +1 -0
  32. package/build/translations/ko.js +1 -0
  33. package/build/translations/ku.js +1 -0
  34. package/build/translations/lt.js +1 -0
  35. package/build/translations/lv.js +1 -0
  36. package/build/translations/nb.js +1 -0
  37. package/build/translations/ne.js +1 -0
  38. package/build/translations/nl.js +1 -0
  39. package/build/translations/no.js +1 -0
  40. package/build/translations/pl.js +1 -0
  41. package/build/translations/pt-br.js +1 -0
  42. package/build/translations/pt.js +1 -0
  43. package/build/translations/ro.js +1 -0
  44. package/build/translations/ru.js +1 -0
  45. package/build/translations/si.js +1 -0
  46. package/build/translations/sk.js +1 -0
  47. package/build/translations/sq.js +1 -0
  48. package/build/translations/sr-latn.js +1 -0
  49. package/build/translations/sr.js +1 -0
  50. package/build/translations/sv.js +1 -0
  51. package/build/translations/th.js +1 -0
  52. package/build/translations/tk.js +1 -0
  53. package/build/translations/tr.js +1 -0
  54. package/build/translations/ug.js +1 -0
  55. package/build/translations/uk.js +1 -0
  56. package/build/translations/vi.js +1 -0
  57. package/build/translations/zh-cn.js +1 -0
  58. package/build/translations/zh.js +1 -0
  59. package/ckeditor5-metadata.json +233 -0
  60. package/lang/contexts.json +3 -0
  61. package/lang/translations/ar.po +12 -0
  62. package/lang/translations/ast.po +12 -0
  63. package/lang/translations/az.po +12 -0
  64. package/lang/translations/bg.po +12 -0
  65. package/lang/translations/cs.po +12 -0
  66. package/lang/translations/da.po +12 -0
  67. package/lang/translations/de-ch.po +113 -0
  68. package/lang/translations/de.po +15 -3
  69. package/lang/translations/el.po +12 -0
  70. package/lang/translations/en-au.po +12 -0
  71. package/lang/translations/en-gb.po +12 -0
  72. package/lang/translations/en.po +12 -0
  73. package/lang/translations/eo.po +12 -0
  74. package/lang/translations/es.po +12 -0
  75. package/lang/translations/et.po +12 -0
  76. package/lang/translations/eu.po +12 -0
  77. package/lang/translations/fa.po +12 -0
  78. package/lang/translations/fi.po +12 -0
  79. package/lang/translations/fr.po +12 -0
  80. package/lang/translations/gl.po +12 -0
  81. package/lang/translations/he.po +12 -0
  82. package/lang/translations/hi.po +12 -0
  83. package/lang/translations/hr.po +12 -0
  84. package/lang/translations/hu.po +13 -1
  85. package/lang/translations/id.po +21 -9
  86. package/lang/translations/it.po +12 -0
  87. package/lang/translations/ja.po +12 -0
  88. package/lang/translations/km.po +12 -0
  89. package/lang/translations/kn.po +12 -0
  90. package/lang/translations/ko.po +12 -0
  91. package/lang/translations/ku.po +12 -0
  92. package/lang/translations/lt.po +12 -0
  93. package/lang/translations/lv.po +12 -0
  94. package/lang/translations/nb.po +12 -0
  95. package/lang/translations/ne.po +12 -0
  96. package/lang/translations/nl.po +12 -0
  97. package/lang/translations/no.po +12 -0
  98. package/lang/translations/pl.po +20 -8
  99. package/lang/translations/pt-br.po +12 -0
  100. package/lang/translations/pt.po +12 -0
  101. package/lang/translations/ro.po +21 -9
  102. package/lang/translations/ru.po +12 -0
  103. package/lang/translations/si.po +12 -0
  104. package/lang/translations/sk.po +12 -0
  105. package/lang/translations/sq.po +12 -0
  106. package/lang/translations/sr-latn.po +12 -0
  107. package/lang/translations/sr.po +12 -0
  108. package/lang/translations/sv.po +12 -0
  109. package/lang/translations/th.po +12 -0
  110. package/lang/translations/tk.po +12 -0
  111. package/lang/translations/tr.po +12 -0
  112. package/lang/translations/ug.po +12 -0
  113. package/lang/translations/uk.po +12 -0
  114. package/lang/translations/vi.po +12 -0
  115. package/lang/translations/zh-cn.po +12 -0
  116. package/lang/translations/zh.po +12 -0
  117. package/package.json +36 -29
  118. package/src/autoimage.js +6 -4
  119. package/src/image/converters.js +192 -13
  120. package/src/image/imageblockediting.js +179 -0
  121. package/src/image/imageediting.js +13 -70
  122. package/src/image/imageinlineediting.js +204 -0
  123. package/src/image/imagetypecommand.js +105 -0
  124. package/src/image/insertimagecommand.js +77 -10
  125. package/src/image/ui/utils.js +3 -3
  126. package/src/image/utils.js +70 -121
  127. package/src/image.js +7 -19
  128. package/src/imageblock.js +46 -0
  129. package/src/imagecaption/imagecaptionediting.js +202 -230
  130. package/src/imagecaption/imagecaptionui.js +78 -0
  131. package/src/imagecaption/toggleimagecaptioncommand.js +165 -0
  132. package/src/imagecaption/utils.js +25 -40
  133. package/src/imagecaption.js +3 -2
  134. package/src/imageinline.js +46 -0
  135. package/src/imageinsert/imageinsertui.js +5 -6
  136. package/src/imageinsert.js +16 -4
  137. package/src/imageresize/imageresizebuttons.js +1 -1
  138. package/src/imageresize/imageresizeediting.js +21 -8
  139. package/src/imageresize/imageresizehandles.js +30 -8
  140. package/src/imageresize/resizeimagecommand.js +8 -5
  141. package/src/imagestyle/converters.js +25 -17
  142. package/src/imagestyle/imagestylecommand.js +73 -33
  143. package/src/imagestyle/imagestyleediting.js +113 -52
  144. package/src/imagestyle/imagestyleui.js +197 -31
  145. package/src/imagestyle/utils.js +300 -85
  146. package/src/imagestyle.js +218 -47
  147. package/src/imagetextalternative/imagetextalternativecommand.js +10 -7
  148. package/src/imagetextalternative/imagetextalternativeediting.js +9 -1
  149. package/src/imagetextalternative/imagetextalternativeui.js +2 -2
  150. package/src/imagetextalternative.js +1 -1
  151. package/src/imagetoolbar.js +33 -11
  152. package/src/imageupload/imageuploadediting.js +91 -31
  153. package/src/imageupload/imageuploadprogress.js +17 -9
  154. package/src/imageupload/imageuploadui.js +1 -1
  155. package/src/imageupload/uploadimagecommand.js +50 -24
  156. package/src/imageupload/utils.js +3 -2
  157. package/src/imageupload.js +1 -1
  158. package/src/imageutils.js +342 -0
  159. package/src/index.js +22 -47
  160. package/src/pictureediting.js +149 -0
  161. package/theme/image.css +101 -21
  162. package/theme/imagecaption.css +24 -2
  163. package/theme/imageresize.css +11 -0
  164. package/theme/imagestyle.css +76 -0
  165. package/theme/imageuploadicon.css +8 -2
  166. package/theme/imageuploadprogress.css +12 -8
  167. package/build/image.js.map +0 -1
@@ -0,0 +1,342 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+
6
+ /**
7
+ * @module image/imageutils
8
+ */
9
+
10
+ import { Plugin } from 'ckeditor5/src/core';
11
+ import { findOptimalInsertionRange, isWidget, toWidget } from 'ckeditor5/src/widget';
12
+ import { determineImageTypeForInsertionAtSelection } from './image/utils';
13
+
14
+ /**
15
+ * A set of helpers related to images.
16
+ *
17
+ * @extends module:core/plugin~Plugin
18
+ */
19
+ export default class ImageUtils extends Plugin {
20
+ /**
21
+ * @inheritDoc
22
+ */
23
+ static get pluginName() {
24
+ return 'ImageUtils';
25
+ }
26
+
27
+ /**
28
+ * Checks if the provided model element is an `image` or `imageInline`.
29
+ *
30
+ * @param {module:engine/model/element~Element} modelElement
31
+ * @returns {Boolean}
32
+ */
33
+ isImage( modelElement ) {
34
+ return this.isInlineImage( modelElement ) || this.isBlockImage( modelElement );
35
+ }
36
+
37
+ /**
38
+ * Checks if the provided view element represents an inline image.
39
+ *
40
+ * Also, see {@link module:image/imageutils~ImageUtils#isImageWidget}.
41
+ *
42
+ * @param {module:engine/view/element~Element} element
43
+ * @returns {Boolean}
44
+ */
45
+ isInlineImageView( element ) {
46
+ return !!element && element.is( 'element', 'img' );
47
+ }
48
+
49
+ /**
50
+ * Checks if the provided view element represents a block image.
51
+ *
52
+ * Also, see {@link module:image/imageutils~ImageUtils#isImageWidget}.
53
+ *
54
+ * @param {module:engine/view/element~Element} element
55
+ * @returns {Boolean}
56
+ */
57
+ isBlockImageView( element ) {
58
+ return !!element && element.is( 'element', 'figure' ) && element.hasClass( 'image' );
59
+ }
60
+
61
+ /**
62
+ * Handles inserting single file. This method unifies image insertion using {@link module:widget/utils~findOptimalInsertionRange}
63
+ * method.
64
+ *
65
+ * const imageUtils = editor.plugins.get( 'ImageUtils' );
66
+ *
67
+ * imageUtils.insertImage( { src: 'path/to/image.jpg' } );
68
+ *
69
+ * @param {Object} [attributes={}] Attributes of the inserted image.
70
+ * This method filters out the attributes which are disallowed by the {@link module:engine/model/schema~Schema}.
71
+ * @param {module:engine/model/selection~Selectable} [selectable] Place to insert the image. If not specified,
72
+ * the {@link module:widget/utils~findOptimalInsertionRange} logic will be applied for the block images
73
+ * and `model.document.selection` for the inline images.
74
+ *
75
+ * **Note**: If `selectable` is passed, this helper will not be able to set selection attributes (such as `linkHref`)
76
+ * and apply them to the new image. In this case, make sure all selection attributes are passed in `attributes`.
77
+ *
78
+ * @param {'imageBlock'|'imageInline'} [imageType] Image type of inserted image. If not specified,
79
+ * it will be determined automatically depending of editor config or place of the insertion.
80
+ * @return {module:engine/view/element~Element|null} The inserted model image element.
81
+ */
82
+ insertImage( attributes = {}, selectable = null, imageType = null ) {
83
+ const editor = this.editor;
84
+ const model = editor.model;
85
+ const selection = model.document.selection;
86
+
87
+ imageType = determineImageTypeForInsertion( editor, selectable || selection, imageType );
88
+
89
+ // Mix declarative attributes with selection attributes because the new image should "inherit"
90
+ // the latter for best UX. For instance, inline images inserted into existing links
91
+ // should not split them. To do that, they need to have "linkHref" inherited from the selection.
92
+ attributes = {
93
+ ...Object.fromEntries( selection.getAttributes() ),
94
+ ...attributes
95
+ };
96
+
97
+ for ( const attributeName in attributes ) {
98
+ if ( !model.schema.checkAttribute( imageType, attributeName ) ) {
99
+ delete attributes[ attributeName ];
100
+ }
101
+ }
102
+
103
+ return model.change( writer => {
104
+ const imageElement = writer.createElement( imageType, attributes );
105
+
106
+ // If we want to insert a block image (for whatever reason) then we don't want to split text blocks.
107
+ // This applies only when we don't have the selectable specified (i.e., we insert multiple block images at once).
108
+ if ( !selectable && imageType != 'imageInline' ) {
109
+ selectable = findOptimalInsertionRange( selection, model );
110
+ }
111
+
112
+ model.insertContent( imageElement, selectable );
113
+
114
+ // Inserting an image might've failed due to schema regulations.
115
+ if ( imageElement.parent ) {
116
+ writer.setSelection( imageElement, 'on' );
117
+
118
+ return imageElement;
119
+ }
120
+
121
+ return null;
122
+ } );
123
+ }
124
+
125
+ /**
126
+ * Returns an image widget editing view element if one is selected or is among the selection's ancestors.
127
+ *
128
+ * @protected
129
+ * @param {module:engine/view/selection~Selection|module:engine/view/documentselection~DocumentSelection} selection
130
+ * @returns {module:engine/view/element~Element|null}
131
+ */
132
+ getClosestSelectedImageWidget( selection ) {
133
+ const viewElement = selection.getSelectedElement();
134
+
135
+ if ( viewElement && this.isImageWidget( viewElement ) ) {
136
+ return viewElement;
137
+ }
138
+
139
+ let parent = selection.getFirstPosition().parent;
140
+
141
+ while ( parent ) {
142
+ if ( parent.is( 'element' ) && this.isImageWidget( parent ) ) {
143
+ return parent;
144
+ }
145
+
146
+ parent = parent.parent;
147
+ }
148
+
149
+ return null;
150
+ }
151
+
152
+ /**
153
+ * Returns a image model element if one is selected or is among the selection's ancestors.
154
+ *
155
+ * @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
156
+ * @returns {module:engine/model/element~Element|null}
157
+ */
158
+ getClosestSelectedImageElement( selection ) {
159
+ const selectedElement = selection.getSelectedElement();
160
+
161
+ return this.isImage( selectedElement ) ? selectedElement : selection.getFirstPosition().findAncestor( 'imageBlock' );
162
+ }
163
+
164
+ /**
165
+ * Checks if image can be inserted at current model selection.
166
+ *
167
+ * @protected
168
+ * @returns {Boolean}
169
+ */
170
+ isImageAllowed() {
171
+ const model = this.editor.model;
172
+ const selection = model.document.selection;
173
+
174
+ return isImageAllowedInParent( this.editor, selection ) && isNotInsideImage( selection );
175
+ }
176
+
177
+ /**
178
+ * Converts a given {@link module:engine/view/element~Element} to an image widget:
179
+ * * Adds a {@link module:engine/view/element~Element#_setCustomProperty custom property} allowing to recognize the image widget
180
+ * element.
181
+ * * Calls the {@link module:widget/utils~toWidget} function with the proper element's label creator.
182
+ *
183
+ * @protected
184
+ * @param {module:engine/view/element~Element} viewElement
185
+ * @param {module:engine/view/downcastwriter~DowncastWriter} writer An instance of the view writer.
186
+ * @param {String} label The element's label. It will be concatenated with the image `alt` attribute if one is present.
187
+ * @returns {module:engine/view/element~Element}
188
+ */
189
+ toImageWidget( viewElement, writer, label ) {
190
+ writer.setCustomProperty( 'image', true, viewElement );
191
+
192
+ const labelCreator = () => {
193
+ const imgElement = this.findViewImgElement( viewElement );
194
+ const altText = imgElement.getAttribute( 'alt' );
195
+
196
+ return altText ? `${ altText } ${ label }` : label;
197
+ };
198
+
199
+ return toWidget( viewElement, writer, { label: labelCreator } );
200
+ }
201
+
202
+ /**
203
+ * Checks if a given view element is an image widget.
204
+ *
205
+ * @protected
206
+ * @param {module:engine/view/element~Element} viewElement
207
+ * @returns {Boolean}
208
+ */
209
+ isImageWidget( viewElement ) {
210
+ return !!viewElement.getCustomProperty( 'image' ) && isWidget( viewElement );
211
+ }
212
+
213
+ /**
214
+ * Checks if the provided model element is an `image`.
215
+ *
216
+ * @param {module:engine/model/element~Element} modelElement
217
+ * @returns {Boolean}
218
+ */
219
+ isBlockImage( modelElement ) {
220
+ return !!modelElement && modelElement.is( 'element', 'imageBlock' );
221
+ }
222
+
223
+ /**
224
+ * Checks if the provided model element is an `imageInline`.
225
+ *
226
+ * @param {module:engine/model/element~Element} modelElement
227
+ * @returns {Boolean}
228
+ */
229
+ isInlineImage( modelElement ) {
230
+ return !!modelElement && modelElement.is( 'element', 'imageInline' );
231
+ }
232
+
233
+ /**
234
+ * Get the view `<img>` from another view element, e.g. a widget (`<figure class="image">`), a link (`<a>`).
235
+ *
236
+ * The `<img>` can be located deep in other elements, so this helper performs a deep tree search.
237
+ *
238
+ * @param {module:engine/view/element~Element} figureView
239
+ * @returns {module:engine/view/element~Element}
240
+ */
241
+ findViewImgElement( figureView ) {
242
+ if ( this.isInlineImageView( figureView ) ) {
243
+ return figureView;
244
+ }
245
+
246
+ const editingView = this.editor.editing.view;
247
+
248
+ for ( const { item } of editingView.createRangeIn( figureView ) ) {
249
+ if ( this.isInlineImageView( item ) ) {
250
+ return item;
251
+ }
252
+ }
253
+ }
254
+ }
255
+
256
+ // Checks if image is allowed by schema in optimal insertion parent.
257
+ //
258
+ // @private
259
+ // @param {module:core/editor/editor~Editor} editor
260
+ // @param {module:engine/model/selection~Selection} selection
261
+ // @returns {Boolean}
262
+ function isImageAllowedInParent( editor, selection ) {
263
+ const imageType = determineImageTypeForInsertion( editor, selection );
264
+
265
+ if ( imageType == 'imageBlock' ) {
266
+ const parent = getInsertImageParent( selection, editor.model );
267
+
268
+ if ( editor.model.schema.checkChild( parent, 'imageBlock' ) ) {
269
+ return true;
270
+ }
271
+ } else if ( editor.model.schema.checkChild( selection.focus, 'imageInline' ) ) {
272
+ return true;
273
+ }
274
+
275
+ return false;
276
+ }
277
+
278
+ // Checks if selection is not placed inside an image (e.g. its caption).
279
+ //
280
+ // @private
281
+ // @param {module:engine/model/selection~Selectable} selection
282
+ // @returns {Boolean}
283
+ function isNotInsideImage( selection ) {
284
+ return [ ...selection.focus.getAncestors() ].every( ancestor => !ancestor.is( 'element', 'imageBlock' ) );
285
+ }
286
+
287
+ // Returns a node that will be used to insert image with `model.insertContent`.
288
+ //
289
+ // @private
290
+ // @param {module:engine/model/selection~Selection} selection
291
+ // @param {module:engine/model/model~Model} model
292
+ // @returns {module:engine/model/element~Element}
293
+ function getInsertImageParent( selection, model ) {
294
+ const insertionRange = findOptimalInsertionRange( selection, model );
295
+ const parent = insertionRange.start.parent;
296
+
297
+ if ( parent.isEmpty && !parent.is( 'element', '$root' ) ) {
298
+ return parent.parent;
299
+ }
300
+
301
+ return parent;
302
+ }
303
+
304
+ // Determine image element type name depending on editor config or place of insertion.
305
+ //
306
+ // @private
307
+ // @param {module:core/editor/editor~Editor} editor
308
+ // @param {module:engine/model/selection~Selectable} selectable
309
+ // @param {'imageBlock'|'imageInline'} [imageType] Image element type name. Used to force return of provided element name,
310
+ // but only if there is proper plugin enabled.
311
+ // @returns {'imageBlock'|'imageInline'} imageType
312
+ function determineImageTypeForInsertion( editor, selectable, imageType ) {
313
+ const schema = editor.model.schema;
314
+ const configImageInsertType = editor.config.get( 'image.insert.type' );
315
+
316
+ if ( !editor.plugins.has( 'ImageBlockEditing' ) ) {
317
+ return 'imageInline';
318
+ }
319
+
320
+ if ( !editor.plugins.has( 'ImageInlineEditing' ) ) {
321
+ return 'imageBlock';
322
+ }
323
+
324
+ if ( imageType ) {
325
+ return imageType;
326
+ }
327
+
328
+ if ( configImageInsertType === 'inline' ) {
329
+ return 'imageInline';
330
+ }
331
+
332
+ if ( configImageInsertType === 'block' ) {
333
+ return 'imageBlock';
334
+ }
335
+
336
+ // Try to replace the selected widget (e.g. another image).
337
+ if ( selectable.is( 'selection' ) ) {
338
+ return determineImageTypeForInsertionAtSelection( schema, selectable );
339
+ }
340
+
341
+ return schema.checkChild( selectable, 'imageInline' ) ? 'imageInline' : 'imageBlock';
342
+ }
package/src/index.js CHANGED
@@ -7,50 +7,25 @@
7
7
  * @module image
8
8
  */
9
9
 
10
- import AutoImage from './autoimage';
11
- import Image from './image';
12
- import ImageEditing from './image/imageediting';
13
- import ImageCaption from './imagecaption';
14
- import ImageCaptionEditing from './imagecaption/imagecaptionediting';
15
- import ImageInsert from './imageinsert';
16
- import ImageInsertUI from './imageinsert/imageinsertui';
17
- import ImageResize from './imageresize';
18
- import ImageResizeButtons from './imageresize/imageresizebuttons';
19
- import ImageResizeEditing from './imageresize/imageresizeediting';
20
- import ImageResizeHandles from './imageresize/imageresizehandles';
21
- import ImageStyle from './imagestyle';
22
- import ImageStyleEditing from './imagestyle/imagestyleediting';
23
- import ImageStyleUI from './imagestyle/imagestyleui';
24
- import ImageTextAlternative from './imagetextalternative';
25
- import ImageTextAlternativeEditing from './imagetextalternative/imagetextalternativeediting';
26
- import ImageTextAlternativeUI from './imagetextalternative/imagetextalternativeui';
27
- import ImageToolbar from './imagetoolbar';
28
- import ImageUpload from './imageupload';
29
- import ImageUploadEditing from './imageupload/imageuploadediting';
30
- import ImageUploadProgress from './imageupload/imageuploadprogress';
31
- import ImageUploadUI from './imageupload/imageuploadui';
32
-
33
- export default {
34
- AutoImage,
35
- Image,
36
- ImageEditing,
37
- ImageCaption,
38
- ImageCaptionEditing,
39
- ImageInsert,
40
- ImageInsertUI,
41
- ImageResize,
42
- ImageResizeButtons,
43
- ImageResizeEditing,
44
- ImageResizeHandles,
45
- ImageStyle,
46
- ImageStyleEditing,
47
- ImageStyleUI,
48
- ImageTextAlternative,
49
- ImageTextAlternativeEditing,
50
- ImageTextAlternativeUI,
51
- ImageToolbar,
52
- ImageUpload,
53
- ImageUploadEditing,
54
- ImageUploadProgress,
55
- ImageUploadUI
56
- };
10
+ export { default as AutoImage } from './autoimage';
11
+ export { default as Image } from './image';
12
+ export { default as ImageEditing } from './image/imageediting';
13
+ export { default as ImageCaption } from './imagecaption';
14
+ export { default as ImageCaptionEditing } from './imagecaption/imagecaptionediting';
15
+ export { default as ImageInsert } from './imageinsert';
16
+ export { default as ImageInsertUI } from './imageinsert/imageinsertui';
17
+ export { default as ImageResize } from './imageresize';
18
+ export { default as ImageResizeButtons } from './imageresize/imageresizebuttons';
19
+ export { default as ImageResizeEditing } from './imageresize/imageresizeediting';
20
+ export { default as ImageResizeHandles } from './imageresize/imageresizehandles';
21
+ export { default as ImageStyle } from './imagestyle';
22
+ export { default as ImageStyleEditing } from './imagestyle/imagestyleediting';
23
+ export { default as ImageStyleUI } from './imagestyle/imagestyleui';
24
+ export { default as ImageTextAlternative } from './imagetextalternative';
25
+ export { default as ImageTextAlternativeEditing } from './imagetextalternative/imagetextalternativeediting';
26
+ export { default as ImageTextAlternativeUI } from './imagetextalternative/imagetextalternativeui';
27
+ export { default as ImageToolbar } from './imagetoolbar';
28
+ export { default as ImageUpload } from './imageupload';
29
+ export { default as ImageUploadEditing } from './imageupload/imageuploadediting';
30
+ export { default as ImageUploadProgress } from './imageupload/imageuploadprogress';
31
+ export { default as ImageUploadUI } from './imageupload/imageuploadui';
@@ -0,0 +1,149 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+
6
+ /**
7
+ * @module image/pictureediting
8
+ */
9
+
10
+ import { Plugin } from 'ckeditor5/src/core';
11
+
12
+ import ImageEditing from './image/imageediting';
13
+ import ImageUtils from './imageutils';
14
+ import {
15
+ downcastSourcesAttribute,
16
+ upcastPicture
17
+ } from './image/converters';
18
+
19
+ /**
20
+ * This plugin enables the [`<picture>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture) element support in the editor.
21
+ *
22
+ * * It enables the `sources` model attribute on `imageBlock` and `imageInline` model elements
23
+ * (brought by {@link module:image/imageblock~ImageBlock} and {@link module:image/imageinline~ImageInline}, respectively).
24
+ * * It translates the `sources` model element to the view (also: data) structure that may look as follows:
25
+ *
26
+ * <p>Inline image using picture:
27
+ * <picture>
28
+ * <source media="(min-width: 800px)" srcset="image-large.webp" type="image/webp">
29
+ * <source media="(max-width: 800px)" srcset="image-small.webp" type="image/webp">
30
+ * <!-- Other sources as specified in the "sources" model attribute... -->
31
+ * <img src="image.png" alt="An image using picture" />
32
+ * </picture>
33
+ * </p>
34
+ *
35
+ * <p>Block image using picture:</p>
36
+ * <figure class="image">
37
+ * <picture>
38
+ * <source media="(min-width: 800px)" srcset="image-large.webp" type="image/webp">
39
+ * <source media="(max-width: 800px)" srcset="image-small.webp" type="image/webp">
40
+ * <!-- Other sources as specified in the "sources" model attribute... -->
41
+ * <img src="image.png" alt="An image using picture" />
42
+ * </picture>
43
+ * <figcaption>Caption of the image</figcaption>
44
+ * </figure>
45
+ *
46
+ * **Note:** The value of the `sources` {@glink framework/guides/architecture/editing-engine#changing-the-model model attribute}
47
+ * in both examples equals:
48
+ *
49
+ * [
50
+ * {
51
+ * media: '(min-width: 800px)',
52
+ * srcset: 'image-large.webp',
53
+ * type: 'image/webp'
54
+ * },
55
+ * {
56
+ * media: '(max-width: 800px)',
57
+ * srcset: 'image-small.webp',
58
+ * type: 'image/webp'
59
+ * }
60
+ * ]
61
+ *
62
+ * * It integrates with the {@link module:image/imageupload~ImageUpload} plugin so images uploaded in the editor
63
+ * automatically render using `<picture>` if the {@glink features/images/image-upload/image-upload upload adapter}
64
+ * supports image sources and provides neccessary data.
65
+ *
66
+ * @private
67
+ * @extends module:core/plugin~Plugin
68
+ */
69
+ export default class PictureEditing extends Plugin {
70
+ /**
71
+ * @inheritDoc
72
+ */
73
+ static get requires() {
74
+ return [ ImageEditing, ImageUtils ];
75
+ }
76
+
77
+ /**
78
+ * @inheritDoc
79
+ */
80
+ static get pluginName() {
81
+ return 'PictureEditing';
82
+ }
83
+
84
+ /**
85
+ * @inheritDoc
86
+ */
87
+ afterInit() {
88
+ const editor = this.editor;
89
+
90
+ if ( editor.plugins.has( 'ImageBlockEditing' ) ) {
91
+ editor.model.schema.extend( 'imageBlock', {
92
+ allowAttributes: [ 'sources' ]
93
+ } );
94
+ }
95
+
96
+ if ( editor.plugins.has( 'ImageInlineEditing' ) ) {
97
+ editor.model.schema.extend( 'imageInline', {
98
+ allowAttributes: [ 'sources' ]
99
+ } );
100
+ }
101
+
102
+ this._setupConversion();
103
+ this._setupImageUploadEditingIntegration();
104
+ }
105
+
106
+ /**
107
+ * Configures conversion pipelines to support upcasting and downcasting images using the `<picture>` view element
108
+ * and the model `sources` attribute.
109
+ *
110
+ * @private
111
+ */
112
+ _setupConversion() {
113
+ const editor = this.editor;
114
+ const conversion = editor.conversion;
115
+ const imageUtils = editor.plugins.get( 'ImageUtils' );
116
+
117
+ conversion.for( 'upcast' ).add( upcastPicture( imageUtils ) );
118
+ conversion.for( 'downcast' ).add( downcastSourcesAttribute( imageUtils ) );
119
+ }
120
+
121
+ /**
122
+ * Makes it possible for uploaded images to get the `sources` model attribute and the `<picture>...</picture>`
123
+ * view structure out-of-the-box if relevant data is provided along the
124
+ * {@link module:image/imageupload/imageuploadediting~ImageUploadEditing#event:uploadComplete} event.
125
+ *
126
+ * @private
127
+ */
128
+ _setupImageUploadEditingIntegration() {
129
+ const editor = this.editor;
130
+
131
+ if ( !editor.plugins.has( 'ImageUploadEditing' ) ) {
132
+ return;
133
+ }
134
+
135
+ this.listenTo( editor.plugins.get( 'ImageUploadEditing' ), 'uploadComplete', ( evt, { imageElement, data } ) => {
136
+ const sources = data.sources;
137
+
138
+ if ( !sources ) {
139
+ return;
140
+ }
141
+
142
+ editor.model.change( writer => {
143
+ writer.setAttributes( {
144
+ sources
145
+ }, imageElement );
146
+ } );
147
+ } );
148
+ }
149
+ }