@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
@@ -8,7 +8,6 @@
8
8
  */
9
9
 
10
10
  import { first } from 'ckeditor5/src/utils';
11
- import { getViewImgFromWidget } from './utils';
12
11
 
13
12
  /**
14
13
  * Returns a function that converts the image view representation:
@@ -17,14 +16,16 @@ import { getViewImgFromWidget } from './utils';
17
16
  *
18
17
  * to the model representation:
19
18
  *
20
- * <image src="..." alt="..."></image>
19
+ * <imageBlock src="..." alt="..."></imageBlock>
21
20
  *
22
21
  * The entire content of the `<figure>` element except the first `<img>` is being converted as children
23
- * of the `<image>` model element.
22
+ * of the `<imageBlock>` model element.
24
23
  *
24
+ * @protected
25
+ * @param {module:image/imageutils~ImageUtils} imageUtils
25
26
  * @returns {Function}
26
27
  */
27
- export function viewFigureToModel() {
28
+ export function upcastImageFigure( imageUtils ) {
28
29
  return dispatcher => {
29
30
  dispatcher.on( 'element:figure', converter );
30
31
  };
@@ -36,7 +37,7 @@ export function viewFigureToModel() {
36
37
  }
37
38
 
38
39
  // Find an image element inside the figure element.
39
- const viewImage = getViewImgFromWidget( data.viewItem );
40
+ const viewImage = imageUtils.findViewImgElement( data.viewItem );
40
41
 
41
42
  // Do not convert if image element is absent, is missing src attribute or was already converted.
42
43
  if ( !viewImage || !viewImage.hasAttribute( 'src' ) || !conversionApi.consumable.test( viewImage, { name: true } ) ) {
@@ -61,14 +62,116 @@ export function viewFigureToModel() {
61
62
  }
62
63
  }
63
64
 
65
+ /**
66
+ * Returns a function that converts the image view representation:
67
+ *
68
+ * <picture><source ... /><source ... />...<img ... /></picture>
69
+ *
70
+ * to the model representation as the `sources` attribute:
71
+ *
72
+ * <image[Block|Inline] ... sources="..."></image[Block|Inline]>
73
+ *
74
+ * @protected
75
+ * @param {module:image/imageutils~ImageUtils} imageUtils
76
+ * @returns {Function}
77
+ */
78
+ export function upcastPicture( imageUtils ) {
79
+ const sourceAttributeNames = [ 'srcset', 'media', 'type' ];
80
+
81
+ return dispatcher => {
82
+ dispatcher.on( 'element:picture', converter );
83
+ };
84
+
85
+ function converter( evt, data, conversionApi ) {
86
+ const pictureViewElement = data.viewItem;
87
+
88
+ // Do not convert <picture> if already consumed.
89
+ if ( !conversionApi.consumable.test( pictureViewElement, { name: true } ) ) {
90
+ return;
91
+ }
92
+
93
+ const sources = new Map();
94
+
95
+ // Collect all <source /> elements attribute values.
96
+ for ( const childSourceElement of pictureViewElement.getChildren() ) {
97
+ if ( childSourceElement.is( 'element', 'source' ) ) {
98
+ const attributes = {};
99
+
100
+ for ( const name of sourceAttributeNames ) {
101
+ if ( childSourceElement.hasAttribute( name ) ) {
102
+ // Don't collect <source /> attribute if already consumed somewhere else.
103
+ if ( conversionApi.consumable.test( childSourceElement, { attributes: name } ) ) {
104
+ attributes[ name ] = childSourceElement.getAttribute( name );
105
+ }
106
+ }
107
+ }
108
+
109
+ if ( Object.keys( attributes ).length ) {
110
+ sources.set( childSourceElement, attributes );
111
+ }
112
+ }
113
+ }
114
+
115
+ const imgViewElement = imageUtils.findViewImgElement( pictureViewElement );
116
+
117
+ // Don't convert when a picture has no <img/> inside (it is broken).
118
+ if ( !imgViewElement ) {
119
+ return;
120
+ }
121
+
122
+ let modelImage = data.modelCursor.parent;
123
+
124
+ // - In case of an inline image (cursor parent in a <paragraph>), the <img/> must be converted right away
125
+ // because no converter handled it yet and otherwise there would be no model element to set the sources attribute on.
126
+ // - In case of a block image, the <figure class="image"> converter (in ImageBlockEditing) converts the
127
+ // <img/> right away on its own and the modelCursor is already inside an imageBlock and there's nothing special
128
+ // to do here.
129
+ if ( !modelImage.is( 'element', 'imageBlock' ) ) {
130
+ const conversionResult = conversionApi.convertItem( imgViewElement, data.modelCursor );
131
+
132
+ // Set image range as conversion result.
133
+ data.modelRange = conversionResult.modelRange;
134
+
135
+ // Continue conversion where image conversion ends.
136
+ data.modelCursor = conversionResult.modelCursor;
137
+
138
+ modelImage = first( conversionResult.modelRange.getItems() );
139
+
140
+ // It could be that the <img/> was broken (e.g. missing "src"). There's no point in converting
141
+ // <picture> any further around a broken <img/>.
142
+ if ( !modelImage ) {
143
+ return;
144
+ }
145
+ }
146
+
147
+ conversionApi.consumable.consume( pictureViewElement, { name: true } );
148
+
149
+ // Consume only these <source/> attributes that were actually collected and will be passed on
150
+ // to the image model element.
151
+ for ( const [ sourceElement, attributes ] of sources ) {
152
+ conversionApi.consumable.consume( sourceElement, { attributes: Object.keys( attributes ) } );
153
+ }
154
+
155
+ if ( sources.size ) {
156
+ conversionApi.writer.setAttribute( 'sources', Array.from( sources.values() ), modelImage );
157
+ }
158
+
159
+ // Convert rest of the <picture> children as an image children. Other converters may want to consume them.
160
+ conversionApi.convertChildren( pictureViewElement, modelImage );
161
+ }
162
+ }
163
+
64
164
  /**
65
165
  * Converter used to convert the `srcset` model image attribute to the `srcset`, `sizes` and `width` attributes in the view.
66
166
  *
167
+ * @protected
168
+ * @param {module:image/imageutils~ImageUtils} imageUtils
169
+ * @param {'imageBlock'|'imageInline'} imageType The type of the image.
67
170
  * @returns {Function}
68
171
  */
69
- export function srcsetAttributeConverter() {
172
+ export function downcastSrcsetAttribute( imageUtils, imageType ) {
70
173
  return dispatcher => {
71
- dispatcher.on( 'attribute:srcset:image', converter );
174
+ dispatcher.on( `attribute:srcset:${ imageType }`, converter );
72
175
  };
73
176
 
74
177
  function converter( evt, data, conversionApi ) {
@@ -77,8 +180,8 @@ export function srcsetAttributeConverter() {
77
180
  }
78
181
 
79
182
  const writer = conversionApi.writer;
80
- const figure = conversionApi.mapper.toViewElement( data.item );
81
- const img = getViewImgFromWidget( figure );
183
+ const element = conversionApi.mapper.toViewElement( data.item );
184
+ const img = imageUtils.findViewImgElement( element );
82
185
 
83
186
  if ( data.attributeNewValue === null ) {
84
187
  const srcset = data.attributeOldValue;
@@ -107,9 +210,84 @@ export function srcsetAttributeConverter() {
107
210
  }
108
211
  }
109
212
 
110
- export function modelToViewAttributeConverter( attributeKey ) {
213
+ /**
214
+ * Converts the `source` model attribute to the `<picture><source /><source />...<img /></picture>`
215
+ * view structure.
216
+ *
217
+ * @protected
218
+ * @param {module:image/imageutils~ImageUtils} imageUtils
219
+ * @returns {Function}
220
+ */
221
+ export function downcastSourcesAttribute( imageUtils ) {
222
+ return dispatcher => {
223
+ dispatcher.on( 'attribute:sources:imageBlock', converter );
224
+ dispatcher.on( 'attribute:sources:imageInline', converter );
225
+ };
226
+
227
+ function converter( evt, data, conversionApi ) {
228
+ if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
229
+ return;
230
+ }
231
+
232
+ const viewWriter = conversionApi.writer;
233
+ const element = conversionApi.mapper.toViewElement( data.item );
234
+ const imgElement = imageUtils.findViewImgElement( element );
235
+
236
+ if ( data.attributeNewValue && data.attributeNewValue.length ) {
237
+ // Make sure <picture> does not break attribute elements, for instance <a> in linked images.
238
+ const pictureElement = viewWriter.createContainerElement( 'picture', {}, { isAllowedInsideAttributeElement: true } );
239
+
240
+ for ( const sourceAttributes of data.attributeNewValue ) {
241
+ const sourceElement = viewWriter.createEmptyElement( 'source', sourceAttributes );
242
+
243
+ viewWriter.insert( viewWriter.createPositionAt( pictureElement, 'end' ), sourceElement );
244
+ }
245
+
246
+ // Collect all wrapping attribute elements.
247
+ const attributeElements = [];
248
+ let viewElement = imgElement.parent;
249
+
250
+ while ( viewElement && viewElement.is( 'attributeElement' ) ) {
251
+ const parentElement = viewElement.parent;
252
+
253
+ viewWriter.unwrap( viewWriter.createRangeOn( imgElement ), viewElement );
254
+
255
+ attributeElements.unshift( viewElement );
256
+ viewElement = parentElement;
257
+ }
258
+
259
+ // Insert the picture and move img into it.
260
+ viewWriter.insert( viewWriter.createPositionBefore( imgElement ), pictureElement );
261
+ viewWriter.move( viewWriter.createRangeOn( imgElement ), viewWriter.createPositionAt( pictureElement, 'end' ) );
262
+
263
+ // Apply collected attribute elements over the new picture element.
264
+ for ( const attributeElement of attributeElements ) {
265
+ viewWriter.wrap( viewWriter.createRangeOn( pictureElement ), attributeElement );
266
+ }
267
+ }
268
+ // Both setting "sources" to an empty array and removing the attribute should unwrap the <img />.
269
+ // Unwrap once if the latter followed the former, though.
270
+ else if ( imgElement.parent.is( 'element', 'picture' ) ) {
271
+ const pictureElement = imgElement.parent;
272
+
273
+ viewWriter.move( viewWriter.createRangeOn( imgElement ), viewWriter.createPositionBefore( pictureElement ) );
274
+ viewWriter.remove( pictureElement );
275
+ }
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Converter used to convert a given image attribute from the model to the view.
281
+ *
282
+ * @protected
283
+ * @param {module:image/imageutils~ImageUtils} imageUtils
284
+ * @param {'imageBlock'|'imageInline'} imageType The type of the image.
285
+ * @param {String} attributeKey The name of the attribute to convert.
286
+ * @returns {Function}
287
+ */
288
+ export function downcastImageAttribute( imageUtils, imageType, attributeKey ) {
111
289
  return dispatcher => {
112
- dispatcher.on( `attribute:${ attributeKey }:image`, converter );
290
+ dispatcher.on( `attribute:${ attributeKey }:${ imageType }`, converter );
113
291
  };
114
292
 
115
293
  function converter( evt, data, conversionApi ) {
@@ -118,9 +296,10 @@ export function modelToViewAttributeConverter( attributeKey ) {
118
296
  }
119
297
 
120
298
  const viewWriter = conversionApi.writer;
121
- const figure = conversionApi.mapper.toViewElement( data.item );
122
- const img = getViewImgFromWidget( figure );
299
+ const element = conversionApi.mapper.toViewElement( data.item );
300
+ const img = imageUtils.findViewImgElement( element );
123
301
 
124
302
  viewWriter.setAttribute( data.attributeKey, data.attributeNewValue || '', img );
125
303
  }
126
304
  }
305
+
@@ -0,0 +1,179 @@
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/image/imageblockediting
8
+ */
9
+
10
+ import { Plugin } from 'ckeditor5/src/core';
11
+ import { ClipboardPipeline } from 'ckeditor5/src/clipboard';
12
+ import { UpcastWriter } from 'ckeditor5/src/engine';
13
+
14
+ import {
15
+ downcastImageAttribute,
16
+ downcastSrcsetAttribute,
17
+ upcastImageFigure
18
+ } from './converters';
19
+
20
+ import ImageEditing from './imageediting';
21
+ import ImageTypeCommand from './imagetypecommand';
22
+ import ImageUtils from '../imageutils';
23
+ import {
24
+ getImgViewElementMatcher,
25
+ createImageViewElement,
26
+ determineImageTypeForInsertionAtSelection
27
+ } from '../image/utils';
28
+
29
+ /**
30
+ * The image block plugin.
31
+ *
32
+ * It registers:
33
+ *
34
+ * * `<imageBlock>` as a block element in the document schema, and allows `alt`, `src` and `srcset` attributes.
35
+ * * converters for editing and data pipelines.,
36
+ * * {@link module:image/image/imagetypecommand~ImageTypeCommand `'imageTypeBlock'`} command that converts inline images into
37
+ * block images.
38
+ *
39
+ * @extends module:core/plugin~Plugin
40
+ */
41
+ export default class ImageBlockEditing extends Plugin {
42
+ /**
43
+ * @inheritDoc
44
+ */
45
+ static get requires() {
46
+ return [ ImageEditing, ImageUtils, ClipboardPipeline ];
47
+ }
48
+
49
+ /**
50
+ * @inheritDoc
51
+ */
52
+ static get pluginName() {
53
+ return 'ImageBlockEditing';
54
+ }
55
+
56
+ /**
57
+ * @inheritDoc
58
+ */
59
+ init() {
60
+ const editor = this.editor;
61
+ const schema = editor.model.schema;
62
+
63
+ // Converters 'alt' and 'srcset' are added in 'ImageEditing' plugin.
64
+ schema.register( 'imageBlock', {
65
+ isObject: true,
66
+ isBlock: true,
67
+ allowWhere: '$block',
68
+ allowAttributes: [ 'alt', 'src', 'srcset' ]
69
+ } );
70
+
71
+ this._setupConversion();
72
+
73
+ if ( editor.plugins.has( 'ImageInlineEditing' ) ) {
74
+ editor.commands.add( 'imageTypeBlock', new ImageTypeCommand( this.editor, 'imageBlock' ) );
75
+
76
+ this._setupClipboardIntegration();
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Configures conversion pipelines to support upcasting and downcasting
82
+ * block images (block image widgets) and their attributes.
83
+ *
84
+ * @private
85
+ */
86
+ _setupConversion() {
87
+ const editor = this.editor;
88
+ const t = editor.t;
89
+ const conversion = editor.conversion;
90
+ const imageUtils = editor.plugins.get( 'ImageUtils' );
91
+
92
+ conversion.for( 'dataDowncast' )
93
+ .elementToElement( {
94
+ model: 'imageBlock',
95
+ view: ( modelElement, { writer } ) => createImageViewElement( writer, 'imageBlock' )
96
+ } );
97
+
98
+ conversion.for( 'editingDowncast' )
99
+ .elementToElement( {
100
+ model: 'imageBlock',
101
+ view: ( modelElement, { writer } ) => imageUtils.toImageWidget(
102
+ createImageViewElement( writer, 'imageBlock' ), writer, t( 'image widget' )
103
+ )
104
+ } );
105
+
106
+ conversion.for( 'downcast' )
107
+ .add( downcastImageAttribute( imageUtils, 'imageBlock', 'src' ) )
108
+ .add( downcastImageAttribute( imageUtils, 'imageBlock', 'alt' ) )
109
+ .add( downcastSrcsetAttribute( imageUtils, 'imageBlock' ) );
110
+
111
+ // More image related upcasts are in 'ImageEditing' plugin.
112
+ conversion.for( 'upcast' )
113
+ .elementToElement( {
114
+ view: getImgViewElementMatcher( editor, 'imageBlock' ),
115
+ model: ( viewImage, { writer } ) => writer.createElement( 'imageBlock', { src: viewImage.getAttribute( 'src' ) } )
116
+ } )
117
+ .add( upcastImageFigure( imageUtils ) );
118
+ }
119
+
120
+ /**
121
+ * Integrates the plugin with the clipboard pipeline.
122
+ *
123
+ * Idea is that the feature should recognize the user's intent when an **inline** image is
124
+ * pasted or dropped. If such an image is pasted/dropped:
125
+ *
126
+ * * into an empty block (e.g. an empty paragraph),
127
+ * * on another object (e.g. some block widget).
128
+ *
129
+ * it gets converted into a block image on the fly. We assume this is the user's intent
130
+ * if they decided to put their image there.
131
+ *
132
+ * See the `ImageInlineEditing` for the similar integration that works in the opposite direction.
133
+ *
134
+ * @private
135
+ */
136
+ _setupClipboardIntegration() {
137
+ const editor = this.editor;
138
+ const model = editor.model;
139
+ const editingView = editor.editing.view;
140
+ const imageUtils = editor.plugins.get( 'ImageUtils' );
141
+
142
+ this.listenTo( editor.plugins.get( 'ClipboardPipeline' ), 'inputTransformation', ( evt, data ) => {
143
+ const docFragmentChildren = Array.from( data.content.getChildren() );
144
+ let modelRange;
145
+
146
+ // Make sure only <img> elements are dropped or pasted. Otherwise, if there some other HTML
147
+ // mixed up, this should be handled as a regular paste.
148
+ if ( !docFragmentChildren.every( imageUtils.isInlineImageView ) ) {
149
+ return;
150
+ }
151
+
152
+ // When drag and dropping, data.targetRanges specifies where to drop because
153
+ // this is usually a different place than the current model selection (the user
154
+ // uses a drop marker to specify the drop location).
155
+ if ( data.targetRanges ) {
156
+ modelRange = editor.editing.mapper.toModelRange( data.targetRanges[ 0 ] );
157
+ }
158
+ // Pasting, however, always occurs at the current model selection.
159
+ else {
160
+ modelRange = model.document.selection.getFirstRange();
161
+ }
162
+
163
+ const selection = model.createSelection( modelRange );
164
+
165
+ // Convert inline images into block images only when the currently selected block is empty
166
+ // (e.g. an empty paragraph) or some object is selected (to replace it).
167
+ if ( determineImageTypeForInsertionAtSelection( model.schema, selection ) === 'imageBlock' ) {
168
+ const writer = new UpcastWriter( editingView.document );
169
+
170
+ // Wrap <img ... /> -> <figure class="image"><img .../></figure>
171
+ const blockViewImages = docFragmentChildren.map(
172
+ inlineViewImage => writer.createElement( 'figure', { class: 'image' }, inlineViewImage )
173
+ );
174
+
175
+ data.content = writer.createDocumentFragment( blockViewImages );
176
+ }
177
+ } );
178
+ }
179
+ }
@@ -9,30 +9,26 @@
9
9
 
10
10
  import { Plugin } from 'ckeditor5/src/core';
11
11
  import ImageLoadObserver from './imageloadobserver';
12
-
13
- import {
14
- viewFigureToModel,
15
- modelToViewAttributeConverter,
16
- srcsetAttributeConverter
17
- } from './converters';
18
-
19
- import { toImageWidget } from './utils';
20
-
21
12
  import InsertImageCommand from './insertimagecommand';
13
+ import ImageUtils from '../imageutils';
22
14
 
23
15
  /**
24
- * The image engine plugin.
16
+ * The image engine plugin. This module loads common code shared between
17
+ * {@link module:image/image/imageinlineediting~ImageInlineEditing} and
18
+ * {@link module:image/image/imageblockediting~ImageBlockEditing} plugins.
25
19
  *
26
- * It registers:
27
- *
28
- * * `<image>` as a block element in the document schema, and allows `alt`, `src` and `srcset` attributes.
29
- * * converters for editing and data pipelines.
30
- * * `'insertImage'` command.
31
- * * `'imageInsert'` command as an alias for `insertImage` command.
20
+ * This plugin registers the {@link module:image/image/insertimagecommand~InsertImageCommand 'insertImage'} command.
32
21
  *
33
22
  * @extends module:core/plugin~Plugin
34
23
  */
35
24
  export default class ImageEditing extends Plugin {
25
+ /**
26
+ * @inheritDoc
27
+ */
28
+ static get requires() {
29
+ return [ ImageUtils ];
30
+ }
31
+
36
32
  /**
37
33
  * @inheritDoc
38
34
  */
@@ -45,46 +41,12 @@ export default class ImageEditing extends Plugin {
45
41
  */
46
42
  init() {
47
43
  const editor = this.editor;
48
- const schema = editor.model.schema;
49
- const t = editor.t;
50
44
  const conversion = editor.conversion;
51
45
 
52
46
  // See https://github.com/ckeditor/ckeditor5-image/issues/142.
53
47
  editor.editing.view.addObserver( ImageLoadObserver );
54
48
 
55
- // Configure schema.
56
- schema.register( 'image', {
57
- isObject: true,
58
- isBlock: true,
59
- allowWhere: '$block',
60
- allowAttributes: [ 'alt', 'src', 'srcset' ]
61
- } );
62
-
63
- conversion.for( 'dataDowncast' ).elementToElement( {
64
- model: 'image',
65
- view: ( modelElement, { writer } ) => createImageViewElement( writer )
66
- } );
67
-
68
- conversion.for( 'editingDowncast' ).elementToElement( {
69
- model: 'image',
70
- view: ( modelElement, { writer } ) => toImageWidget( createImageViewElement( writer ), writer, t( 'image widget' ) )
71
- } );
72
-
73
- conversion.for( 'downcast' )
74
- .add( modelToViewAttributeConverter( 'src' ) )
75
- .add( modelToViewAttributeConverter( 'alt' ) )
76
- .add( srcsetAttributeConverter() );
77
-
78
49
  conversion.for( 'upcast' )
79
- .elementToElement( {
80
- view: {
81
- name: 'img',
82
- attributes: {
83
- src: true
84
- }
85
- },
86
- model: ( viewImage, { writer } ) => writer.createElement( 'image', { src: viewImage.getAttribute( 'src' ) } )
87
- } )
88
50
  .attributeToAttribute( {
89
51
  view: {
90
52
  name: 'img',
@@ -111,8 +73,7 @@ export default class ImageEditing extends Plugin {
111
73
  return value;
112
74
  }
113
75
  }
114
- } )
115
- .add( viewFigureToModel() );
76
+ } );
116
77
 
117
78
  const insertImageCommand = new InsertImageCommand( editor );
118
79
 
@@ -121,21 +82,3 @@ export default class ImageEditing extends Plugin {
121
82
  editor.commands.add( 'imageInsert', insertImageCommand );
122
83
  }
123
84
  }
124
-
125
- // Creates a view element representing the image.
126
- //
127
- // <figure class="image"><img></img></figure>
128
- //
129
- // Note that `alt` and `src` attributes are converted separately, so they are not included.
130
- //
131
- // @private
132
- // @param {module:engine/view/downcastwriter~DowncastWriter} writer
133
- // @returns {module:engine/view/containerelement~ContainerElement}
134
- export function createImageViewElement( writer ) {
135
- const emptyElement = writer.createEmptyElement( 'img' );
136
- const figure = writer.createContainerElement( 'figure', { class: 'image' } );
137
-
138
- writer.insert( writer.createPositionAt( figure, 0 ), emptyElement );
139
-
140
- return figure;
141
- }