@ckeditor/ckeditor5-image 28.0.0 → 30.0.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 +12 -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 +14 -2
  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 +9 -4
  119. package/src/image/converters.js +191 -15
  120. package/src/image/imageblockediting.js +182 -0
  121. package/src/image/imageediting.js +13 -70
  122. package/src/image/imageinlineediting.js +207 -0
  123. package/src/image/imagetypecommand.js +105 -0
  124. package/src/image/insertimagecommand.js +77 -10
  125. package/src/image/ui/utils.js +5 -4
  126. package/src/image/utils.js +65 -121
  127. package/src/image.js +7 -19
  128. package/src/imageblock.js +46 -0
  129. package/src/imagecaption/imagecaptionediting.js +183 -227
  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 +26 -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 +90 -30
  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/pictureediting.js +149 -0
  160. package/theme/image.css +101 -21
  161. package/theme/imagecaption.css +24 -2
  162. package/theme/imageresize.css +11 -0
  163. package/theme/imagestyle.css +76 -0
  164. package/theme/imageuploadicon.css +8 -2
  165. package/theme/imageuploadprogress.css +12 -8
  166. package/CHANGELOG.md +0 -423
  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,10 +37,10 @@ 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
- // Do not convert if image element is absent, is missing src attribute or was already converted.
42
- if ( !viewImage || !viewImage.hasAttribute( 'src' ) || !conversionApi.consumable.test( viewImage, { name: true } ) ) {
42
+ // Do not convert if image element is absent or was already converted.
43
+ if ( !viewImage || !conversionApi.consumable.test( viewImage, { name: true } ) ) {
43
44
  return;
44
45
  }
45
46
 
@@ -54,6 +55,9 @@ export function viewFigureToModel() {
54
55
  return;
55
56
  }
56
57
 
58
+ // Consume the figure to prevent other converters from processing it again.
59
+ conversionApi.consumable.consume( data.viewItem, { name: true, classes: 'image' } );
60
+
57
61
  // Convert rest of the figure element's children as an image children.
58
62
  conversionApi.convertChildren( data.viewItem, modelImage );
59
63
 
@@ -61,14 +65,110 @@ export function viewFigureToModel() {
61
65
  }
62
66
  }
63
67
 
68
+ /**
69
+ * Returns a function that converts the image view representation:
70
+ *
71
+ * <picture><source ... /><source ... />...<img ... /></picture>
72
+ *
73
+ * to the model representation as the `sources` attribute:
74
+ *
75
+ * <image[Block|Inline] ... sources="..."></image[Block|Inline]>
76
+ *
77
+ * @protected
78
+ * @param {module:image/imageutils~ImageUtils} imageUtils
79
+ * @returns {Function}
80
+ */
81
+ export function upcastPicture( imageUtils ) {
82
+ const sourceAttributeNames = [ 'srcset', 'media', 'type' ];
83
+
84
+ return dispatcher => {
85
+ dispatcher.on( 'element:picture', converter );
86
+ };
87
+
88
+ function converter( evt, data, conversionApi ) {
89
+ const pictureViewElement = data.viewItem;
90
+
91
+ // Do not convert <picture> if already consumed.
92
+ if ( !conversionApi.consumable.test( pictureViewElement, { name: true } ) ) {
93
+ return;
94
+ }
95
+
96
+ const sources = new Map();
97
+
98
+ // Collect all <source /> elements attribute values.
99
+ for ( const childSourceElement of pictureViewElement.getChildren() ) {
100
+ if ( childSourceElement.is( 'element', 'source' ) ) {
101
+ const attributes = {};
102
+
103
+ for ( const name of sourceAttributeNames ) {
104
+ if ( childSourceElement.hasAttribute( name ) ) {
105
+ // Don't collect <source /> attribute if already consumed somewhere else.
106
+ if ( conversionApi.consumable.test( childSourceElement, { attributes: name } ) ) {
107
+ attributes[ name ] = childSourceElement.getAttribute( name );
108
+ }
109
+ }
110
+ }
111
+
112
+ if ( Object.keys( attributes ).length ) {
113
+ sources.set( childSourceElement, attributes );
114
+ }
115
+ }
116
+ }
117
+
118
+ const imgViewElement = imageUtils.findViewImgElement( pictureViewElement );
119
+
120
+ // Don't convert when a picture has no <img/> inside (it is broken).
121
+ if ( !imgViewElement ) {
122
+ return;
123
+ }
124
+
125
+ let modelImage = data.modelCursor.parent;
126
+
127
+ // - In case of an inline image (cursor parent in a <paragraph>), the <img/> must be converted right away
128
+ // because no converter handled it yet and otherwise there would be no model element to set the sources attribute on.
129
+ // - In case of a block image, the <figure class="image"> converter (in ImageBlockEditing) converts the
130
+ // <img/> right away on its own and the modelCursor is already inside an imageBlock and there's nothing special
131
+ // to do here.
132
+ if ( !modelImage.is( 'element', 'imageBlock' ) ) {
133
+ const conversionResult = conversionApi.convertItem( imgViewElement, data.modelCursor );
134
+
135
+ // Set image range as conversion result.
136
+ data.modelRange = conversionResult.modelRange;
137
+
138
+ // Continue conversion where image conversion ends.
139
+ data.modelCursor = conversionResult.modelCursor;
140
+
141
+ modelImage = first( conversionResult.modelRange.getItems() );
142
+ }
143
+
144
+ conversionApi.consumable.consume( pictureViewElement, { name: true } );
145
+
146
+ // Consume only these <source/> attributes that were actually collected and will be passed on
147
+ // to the image model element.
148
+ for ( const [ sourceElement, attributes ] of sources ) {
149
+ conversionApi.consumable.consume( sourceElement, { attributes: Object.keys( attributes ) } );
150
+ }
151
+
152
+ if ( sources.size ) {
153
+ conversionApi.writer.setAttribute( 'sources', Array.from( sources.values() ), modelImage );
154
+ }
155
+
156
+ // Convert rest of the <picture> children as an image children. Other converters may want to consume them.
157
+ conversionApi.convertChildren( pictureViewElement, modelImage );
158
+ }
159
+ }
160
+
64
161
  /**
65
162
  * Converter used to convert the `srcset` model image attribute to the `srcset`, `sizes` and `width` attributes in the view.
66
163
  *
164
+ * @protected
165
+ * @param {module:image/imageutils~ImageUtils} imageUtils
166
+ * @param {'imageBlock'|'imageInline'} imageType The type of the image.
67
167
  * @returns {Function}
68
168
  */
69
- export function srcsetAttributeConverter() {
169
+ export function downcastSrcsetAttribute( imageUtils, imageType ) {
70
170
  return dispatcher => {
71
- dispatcher.on( 'attribute:srcset:image', converter );
171
+ dispatcher.on( `attribute:srcset:${ imageType }`, converter );
72
172
  };
73
173
 
74
174
  function converter( evt, data, conversionApi ) {
@@ -77,8 +177,8 @@ export function srcsetAttributeConverter() {
77
177
  }
78
178
 
79
179
  const writer = conversionApi.writer;
80
- const figure = conversionApi.mapper.toViewElement( data.item );
81
- const img = getViewImgFromWidget( figure );
180
+ const element = conversionApi.mapper.toViewElement( data.item );
181
+ const img = imageUtils.findViewImgElement( element );
82
182
 
83
183
  if ( data.attributeNewValue === null ) {
84
184
  const srcset = data.attributeOldValue;
@@ -107,9 +207,84 @@ export function srcsetAttributeConverter() {
107
207
  }
108
208
  }
109
209
 
110
- export function modelToViewAttributeConverter( attributeKey ) {
210
+ /**
211
+ * Converts the `source` model attribute to the `<picture><source /><source />...<img /></picture>`
212
+ * view structure.
213
+ *
214
+ * @protected
215
+ * @param {module:image/imageutils~ImageUtils} imageUtils
216
+ * @returns {Function}
217
+ */
218
+ export function downcastSourcesAttribute( imageUtils ) {
219
+ return dispatcher => {
220
+ dispatcher.on( 'attribute:sources:imageBlock', converter );
221
+ dispatcher.on( 'attribute:sources:imageInline', converter );
222
+ };
223
+
224
+ function converter( evt, data, conversionApi ) {
225
+ if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
226
+ return;
227
+ }
228
+
229
+ const viewWriter = conversionApi.writer;
230
+ const element = conversionApi.mapper.toViewElement( data.item );
231
+ const imgElement = imageUtils.findViewImgElement( element );
232
+
233
+ if ( data.attributeNewValue && data.attributeNewValue.length ) {
234
+ // Make sure <picture> does not break attribute elements, for instance <a> in linked images.
235
+ const pictureElement = viewWriter.createContainerElement( 'picture', {}, { isAllowedInsideAttributeElement: true } );
236
+
237
+ for ( const sourceAttributes of data.attributeNewValue ) {
238
+ const sourceElement = viewWriter.createEmptyElement( 'source', sourceAttributes );
239
+
240
+ viewWriter.insert( viewWriter.createPositionAt( pictureElement, 'end' ), sourceElement );
241
+ }
242
+
243
+ // Collect all wrapping attribute elements.
244
+ const attributeElements = [];
245
+ let viewElement = imgElement.parent;
246
+
247
+ while ( viewElement && viewElement.is( 'attributeElement' ) ) {
248
+ const parentElement = viewElement.parent;
249
+
250
+ viewWriter.unwrap( viewWriter.createRangeOn( imgElement ), viewElement );
251
+
252
+ attributeElements.unshift( viewElement );
253
+ viewElement = parentElement;
254
+ }
255
+
256
+ // Insert the picture and move img into it.
257
+ viewWriter.insert( viewWriter.createPositionBefore( imgElement ), pictureElement );
258
+ viewWriter.move( viewWriter.createRangeOn( imgElement ), viewWriter.createPositionAt( pictureElement, 'end' ) );
259
+
260
+ // Apply collected attribute elements over the new picture element.
261
+ for ( const attributeElement of attributeElements ) {
262
+ viewWriter.wrap( viewWriter.createRangeOn( pictureElement ), attributeElement );
263
+ }
264
+ }
265
+ // Both setting "sources" to an empty array and removing the attribute should unwrap the <img />.
266
+ // Unwrap once if the latter followed the former, though.
267
+ else if ( imgElement.parent.is( 'element', 'picture' ) ) {
268
+ const pictureElement = imgElement.parent;
269
+
270
+ viewWriter.move( viewWriter.createRangeOn( imgElement ), viewWriter.createPositionBefore( pictureElement ) );
271
+ viewWriter.remove( pictureElement );
272
+ }
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Converter used to convert a given image attribute from the model to the view.
278
+ *
279
+ * @protected
280
+ * @param {module:image/imageutils~ImageUtils} imageUtils
281
+ * @param {'imageBlock'|'imageInline'} imageType The type of the image.
282
+ * @param {String} attributeKey The name of the attribute to convert.
283
+ * @returns {Function}
284
+ */
285
+ export function downcastImageAttribute( imageUtils, imageType, attributeKey ) {
111
286
  return dispatcher => {
112
- dispatcher.on( `attribute:${ attributeKey }:image`, converter );
287
+ dispatcher.on( `attribute:${ attributeKey }:${ imageType }`, converter );
113
288
  };
114
289
 
115
290
  function converter( evt, data, conversionApi ) {
@@ -118,9 +293,10 @@ export function modelToViewAttributeConverter( attributeKey ) {
118
293
  }
119
294
 
120
295
  const viewWriter = conversionApi.writer;
121
- const figure = conversionApi.mapper.toViewElement( data.item );
122
- const img = getViewImgFromWidget( figure );
296
+ const element = conversionApi.mapper.toViewElement( data.item );
297
+ const img = imageUtils.findViewImgElement( element );
123
298
 
124
299
  viewWriter.setAttribute( data.attributeKey, data.attributeNewValue || '', img );
125
300
  }
126
301
  }
302
+
@@ -0,0 +1,182 @@
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(
116
+ 'imageBlock',
117
+ viewImage.hasAttribute( 'src' ) ? { src: viewImage.getAttribute( 'src' ) } : null
118
+ )
119
+ } )
120
+ .add( upcastImageFigure( imageUtils ) );
121
+ }
122
+
123
+ /**
124
+ * Integrates the plugin with the clipboard pipeline.
125
+ *
126
+ * Idea is that the feature should recognize the user's intent when an **inline** image is
127
+ * pasted or dropped. If such an image is pasted/dropped:
128
+ *
129
+ * * into an empty block (e.g. an empty paragraph),
130
+ * * on another object (e.g. some block widget).
131
+ *
132
+ * it gets converted into a block image on the fly. We assume this is the user's intent
133
+ * if they decided to put their image there.
134
+ *
135
+ * See the `ImageInlineEditing` for the similar integration that works in the opposite direction.
136
+ *
137
+ * @private
138
+ */
139
+ _setupClipboardIntegration() {
140
+ const editor = this.editor;
141
+ const model = editor.model;
142
+ const editingView = editor.editing.view;
143
+ const imageUtils = editor.plugins.get( 'ImageUtils' );
144
+
145
+ this.listenTo( editor.plugins.get( 'ClipboardPipeline' ), 'inputTransformation', ( evt, data ) => {
146
+ const docFragmentChildren = Array.from( data.content.getChildren() );
147
+ let modelRange;
148
+
149
+ // Make sure only <img> elements are dropped or pasted. Otherwise, if there some other HTML
150
+ // mixed up, this should be handled as a regular paste.
151
+ if ( !docFragmentChildren.every( imageUtils.isInlineImageView ) ) {
152
+ return;
153
+ }
154
+
155
+ // When drag and dropping, data.targetRanges specifies where to drop because
156
+ // this is usually a different place than the current model selection (the user
157
+ // uses a drop marker to specify the drop location).
158
+ if ( data.targetRanges ) {
159
+ modelRange = editor.editing.mapper.toModelRange( data.targetRanges[ 0 ] );
160
+ }
161
+ // Pasting, however, always occurs at the current model selection.
162
+ else {
163
+ modelRange = model.document.selection.getFirstRange();
164
+ }
165
+
166
+ const selection = model.createSelection( modelRange );
167
+
168
+ // Convert inline images into block images only when the currently selected block is empty
169
+ // (e.g. an empty paragraph) or some object is selected (to replace it).
170
+ if ( determineImageTypeForInsertionAtSelection( model.schema, selection ) === 'imageBlock' ) {
171
+ const writer = new UpcastWriter( editingView.document );
172
+
173
+ // Wrap <img ... /> -> <figure class="image"><img .../></figure>
174
+ const blockViewImages = docFragmentChildren.map(
175
+ inlineViewImage => writer.createElement( 'figure', { class: 'image' }, inlineViewImage )
176
+ );
177
+
178
+ data.content = writer.createDocumentFragment( blockViewImages );
179
+ }
180
+ } );
181
+ }
182
+ }
@@ -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
- }