@ckeditor/ckeditor5-image 29.0.0 → 31.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 (88) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +1 -1
  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/translations/de.po +6 -6
  61. package/lang/translations/gl.po +3 -3
  62. package/lang/translations/hu.po +3 -3
  63. package/lang/translations/it.po +3 -3
  64. package/lang/translations/nl.po +2 -2
  65. package/lang/translations/ru.po +3 -3
  66. package/lang/translations/sr-latn.po +3 -3
  67. package/lang/translations/sr.po +3 -3
  68. package/package.json +35 -33
  69. package/src/autoimage.js +4 -1
  70. package/src/image/converters.js +174 -8
  71. package/src/image/imageblockediting.js +16 -9
  72. package/src/image/imageinlineediting.js +15 -9
  73. package/src/image/imagetypecommand.js +1 -1
  74. package/src/image/insertimagecommand.js +19 -5
  75. package/src/image/ui/utils.js +2 -1
  76. package/src/image/utils.js +5 -10
  77. package/src/imageblock.js +1 -1
  78. package/src/imagecaption/imagecaptionediting.js +2 -12
  79. package/src/imageinline.js +1 -1
  80. package/src/imageresize/imageresizehandles.js +6 -2
  81. package/src/imagestyle/converters.js +2 -1
  82. package/src/imageupload/imageuploadediting.js +2 -2
  83. package/src/imageupload/imageuploadprogress.js +2 -2
  84. package/src/imageutils.js +8 -16
  85. package/src/pictureediting.js +149 -0
  86. package/theme/image.css +7 -0
  87. package/CHANGELOG.md +0 -423
  88. package/build/image.js.map +0 -1
package/package.json CHANGED
@@ -1,49 +1,50 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-image",
3
- "version": "29.0.0",
3
+ "version": "31.0.0",
4
4
  "description": "Image feature for CKEditor 5.",
5
5
  "keywords": [
6
6
  "ckeditor",
7
7
  "ckeditor5",
8
8
  "ckeditor 5",
9
9
  "ckeditor5-feature",
10
- "ckeditor5-plugin"
10
+ "ckeditor5-plugin",
11
+ "ckeditor5-dll"
11
12
  ],
12
13
  "main": "src/index.js",
13
14
  "dependencies": {
14
- "@ckeditor/ckeditor5-ui": "^29.0.0",
15
- "ckeditor5": "^29.0.0",
15
+ "@ckeditor/ckeditor5-ui": "^31.0.0",
16
+ "ckeditor5": "^31.0.0",
16
17
  "lodash-es": "^4.17.15"
17
18
  },
18
19
  "devDependencies": {
19
- "@ckeditor/ckeditor5-adapter-ckfinder": "^29.0.0",
20
- "@ckeditor/ckeditor5-autoformat": "^29.0.0",
21
- "@ckeditor/ckeditor5-basic-styles": "^29.0.0",
22
- "@ckeditor/ckeditor5-block-quote": "^29.0.0",
23
- "@ckeditor/ckeditor5-ckfinder": "^29.0.0",
24
- "@ckeditor/ckeditor5-clipboard": "^29.0.0",
25
- "@ckeditor/ckeditor5-cloud-services": "^29.0.0",
26
- "@ckeditor/ckeditor5-core": "^29.0.0",
27
- "@ckeditor/ckeditor5-dev-utils": "^25.0.0",
28
- "@ckeditor/ckeditor5-easy-image": "^29.0.0",
29
- "@ckeditor/ckeditor5-editor-classic": "^29.0.0",
30
- "@ckeditor/ckeditor5-engine": "^29.0.0",
31
- "@ckeditor/ckeditor5-enter": "^29.0.0",
32
- "@ckeditor/ckeditor5-essentials": "^29.0.0",
33
- "@ckeditor/ckeditor5-heading": "^29.0.0",
34
- "@ckeditor/ckeditor5-html-embed": "^29.0.0",
35
- "@ckeditor/ckeditor5-indent": "^29.0.0",
36
- "@ckeditor/ckeditor5-link": "^29.0.0",
37
- "@ckeditor/ckeditor5-list": "^29.0.0",
38
- "@ckeditor/ckeditor5-media-embed": "^29.0.0",
39
- "@ckeditor/ckeditor5-paragraph": "^29.0.0",
40
- "@ckeditor/ckeditor5-table": "^29.0.0",
41
- "@ckeditor/ckeditor5-theme-lark": "^29.0.0",
42
- "@ckeditor/ckeditor5-typing": "^29.0.0",
43
- "@ckeditor/ckeditor5-undo": "^29.0.0",
44
- "@ckeditor/ckeditor5-upload": "^29.0.0",
45
- "@ckeditor/ckeditor5-utils": "^29.0.0",
46
- "@ckeditor/ckeditor5-widget": "^29.0.0",
20
+ "@ckeditor/ckeditor5-adapter-ckfinder": "^31.0.0",
21
+ "@ckeditor/ckeditor5-autoformat": "^31.0.0",
22
+ "@ckeditor/ckeditor5-basic-styles": "^31.0.0",
23
+ "@ckeditor/ckeditor5-block-quote": "^31.0.0",
24
+ "@ckeditor/ckeditor5-ckfinder": "^31.0.0",
25
+ "@ckeditor/ckeditor5-clipboard": "^31.0.0",
26
+ "@ckeditor/ckeditor5-cloud-services": "^31.0.0",
27
+ "@ckeditor/ckeditor5-core": "^31.0.0",
28
+ "@ckeditor/ckeditor5-dev-utils": "^25.4.0",
29
+ "@ckeditor/ckeditor5-easy-image": "^31.0.0",
30
+ "@ckeditor/ckeditor5-editor-classic": "^31.0.0",
31
+ "@ckeditor/ckeditor5-engine": "^31.0.0",
32
+ "@ckeditor/ckeditor5-enter": "^31.0.0",
33
+ "@ckeditor/ckeditor5-essentials": "^31.0.0",
34
+ "@ckeditor/ckeditor5-heading": "^31.0.0",
35
+ "@ckeditor/ckeditor5-html-embed": "^31.0.0",
36
+ "@ckeditor/ckeditor5-indent": "^31.0.0",
37
+ "@ckeditor/ckeditor5-link": "^31.0.0",
38
+ "@ckeditor/ckeditor5-list": "^31.0.0",
39
+ "@ckeditor/ckeditor5-media-embed": "^31.0.0",
40
+ "@ckeditor/ckeditor5-paragraph": "^31.0.0",
41
+ "@ckeditor/ckeditor5-table": "^31.0.0",
42
+ "@ckeditor/ckeditor5-theme-lark": "^31.0.0",
43
+ "@ckeditor/ckeditor5-typing": "^31.0.0",
44
+ "@ckeditor/ckeditor5-undo": "^31.0.0",
45
+ "@ckeditor/ckeditor5-upload": "^31.0.0",
46
+ "@ckeditor/ckeditor5-utils": "^31.0.0",
47
+ "@ckeditor/ckeditor5-widget": "^31.0.0",
47
48
  "webpack": "^4.43.0",
48
49
  "webpack-cli": "^3.3.11"
49
50
  },
@@ -64,7 +65,8 @@
64
65
  "lang",
65
66
  "src",
66
67
  "theme",
67
- "build"
68
+ "build",
69
+ "ckeditor5-metadata.json"
68
70
  ],
69
71
  "scripts": {
70
72
  "dll:build": "webpack"
package/src/autoimage.js CHANGED
@@ -11,6 +11,7 @@ import { Plugin } from 'ckeditor5/src/core';
11
11
  import { Clipboard } from 'ckeditor5/src/clipboard';
12
12
  import { LivePosition, LiveRange } from 'ckeditor5/src/engine';
13
13
  import { Undo } from 'ckeditor5/src/undo';
14
+ import { Delete } from 'ckeditor5/src/typing';
14
15
  import { global } from 'ckeditor5/src/utils';
15
16
 
16
17
  import ImageUtils from './imageutils';
@@ -32,7 +33,7 @@ export default class AutoImage extends Plugin {
32
33
  * @inheritDoc
33
34
  */
34
35
  static get requires() {
35
- return [ Clipboard, ImageUtils, Undo ];
36
+ return [ Clipboard, ImageUtils, Undo, Delete ];
36
37
  }
37
38
 
38
39
  /**
@@ -173,6 +174,8 @@ export default class AutoImage extends Plugin {
173
174
  this._positionToInsert.detach();
174
175
  this._positionToInsert = null;
175
176
  } );
177
+
178
+ editor.plugins.get( 'Delete' ).requestUndoOnBackspace();
176
179
  }, 100 );
177
180
  }
178
181
  }
@@ -21,10 +21,11 @@ import { first } from 'ckeditor5/src/utils';
21
21
  * The entire content of the `<figure>` element except the first `<img>` is being converted as children
22
22
  * of the `<imageBlock>` model element.
23
23
  *
24
+ * @protected
24
25
  * @param {module:image/imageutils~ImageUtils} imageUtils
25
26
  * @returns {Function}
26
27
  */
27
- export function viewFigureToModel( imageUtils ) {
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( imageUtils ) {
36
37
  }
37
38
 
38
39
  // Find an image element inside the figure element.
39
- const viewImage = imageUtils.getViewImageFromWidget( 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( imageUtils ) {
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,108 @@ export function viewFigureToModel( imageUtils ) {
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
67
165
  * @param {module:image/imageutils~ImageUtils} imageUtils
68
166
  * @param {'imageBlock'|'imageInline'} imageType The type of the image.
69
167
  * @returns {Function}
70
168
  */
71
- export function srcsetAttributeConverter( imageUtils, imageType ) {
169
+ export function downcastSrcsetAttribute( imageUtils, imageType ) {
72
170
  return dispatcher => {
73
171
  dispatcher.on( `attribute:srcset:${ imageType }`, converter );
74
172
  };
@@ -80,7 +178,7 @@ export function srcsetAttributeConverter( imageUtils, imageType ) {
80
178
 
81
179
  const writer = conversionApi.writer;
82
180
  const element = conversionApi.mapper.toViewElement( data.item );
83
- const img = imageUtils.getViewImageFromWidget( element );
181
+ const img = imageUtils.findViewImgElement( element );
84
182
 
85
183
  if ( data.attributeNewValue === null ) {
86
184
  const srcset = data.attributeOldValue;
@@ -109,15 +207,82 @@ export function srcsetAttributeConverter( imageUtils, imageType ) {
109
207
  }
110
208
  }
111
209
 
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
+
112
276
  /**
113
277
  * Converter used to convert a given image attribute from the model to the view.
114
278
  *
279
+ * @protected
115
280
  * @param {module:image/imageutils~ImageUtils} imageUtils
116
281
  * @param {'imageBlock'|'imageInline'} imageType The type of the image.
117
282
  * @param {String} attributeKey The name of the attribute to convert.
118
283
  * @returns {Function}
119
284
  */
120
- export function modelToViewAttributeConverter( imageUtils, imageType, attributeKey ) {
285
+ export function downcastImageAttribute( imageUtils, imageType, attributeKey ) {
121
286
  return dispatcher => {
122
287
  dispatcher.on( `attribute:${ attributeKey }:${ imageType }`, converter );
123
288
  };
@@ -129,8 +294,9 @@ export function modelToViewAttributeConverter( imageUtils, imageType, attributeK
129
294
 
130
295
  const viewWriter = conversionApi.writer;
131
296
  const element = conversionApi.mapper.toViewElement( data.item );
132
- const img = imageUtils.getViewImageFromWidget( element );
297
+ const img = imageUtils.findViewImgElement( element );
133
298
 
134
299
  viewWriter.setAttribute( data.attributeKey, data.attributeNewValue || '', img );
135
300
  }
136
301
  }
302
+
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
2
+ * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
5
 
@@ -11,13 +11,17 @@ import { Plugin } from 'ckeditor5/src/core';
11
11
  import { ClipboardPipeline } from 'ckeditor5/src/clipboard';
12
12
  import { UpcastWriter } from 'ckeditor5/src/engine';
13
13
 
14
- import { modelToViewAttributeConverter, srcsetAttributeConverter, viewFigureToModel } from './converters';
14
+ import {
15
+ downcastImageAttribute,
16
+ downcastSrcsetAttribute,
17
+ upcastImageFigure
18
+ } from './converters';
15
19
 
16
20
  import ImageEditing from './imageediting';
17
21
  import ImageTypeCommand from './imagetypecommand';
18
22
  import ImageUtils from '../imageutils';
19
23
  import {
20
- getImageTypeMatcher,
24
+ getImgViewElementMatcher,
21
25
  createImageViewElement,
22
26
  determineImageTypeForInsertionAtSelection
23
27
  } from '../image/utils';
@@ -100,17 +104,20 @@ export default class ImageBlockEditing extends Plugin {
100
104
  } );
101
105
 
102
106
  conversion.for( 'downcast' )
103
- .add( modelToViewAttributeConverter( imageUtils, 'imageBlock', 'src' ) )
104
- .add( modelToViewAttributeConverter( imageUtils, 'imageBlock', 'alt' ) )
105
- .add( srcsetAttributeConverter( imageUtils, 'imageBlock' ) );
107
+ .add( downcastImageAttribute( imageUtils, 'imageBlock', 'src' ) )
108
+ .add( downcastImageAttribute( imageUtils, 'imageBlock', 'alt' ) )
109
+ .add( downcastSrcsetAttribute( imageUtils, 'imageBlock' ) );
106
110
 
107
111
  // More image related upcasts are in 'ImageEditing' plugin.
108
112
  conversion.for( 'upcast' )
109
113
  .elementToElement( {
110
- view: getImageTypeMatcher( editor, 'imageBlock' ),
111
- model: ( viewImage, { writer } ) => writer.createElement( 'imageBlock', { src: viewImage.getAttribute( 'src' ) } )
114
+ view: getImgViewElementMatcher( editor, 'imageBlock' ),
115
+ model: ( viewImage, { writer } ) => writer.createElement(
116
+ 'imageBlock',
117
+ viewImage.hasAttribute( 'src' ) ? { src: viewImage.getAttribute( 'src' ) } : null
118
+ )
112
119
  } )
113
- .add( viewFigureToModel( imageUtils ) );
120
+ .add( upcastImageFigure( imageUtils ) );
114
121
  }
115
122
 
116
123
  /**
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
2
+ * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
5
 
@@ -11,13 +11,16 @@ import { Plugin } from 'ckeditor5/src/core';
11
11
  import { ClipboardPipeline } from 'ckeditor5/src/clipboard';
12
12
  import { UpcastWriter } from 'ckeditor5/src/engine';
13
13
 
14
- import { modelToViewAttributeConverter, srcsetAttributeConverter } from './converters';
14
+ import {
15
+ downcastImageAttribute,
16
+ downcastSrcsetAttribute
17
+ } from './converters';
15
18
 
16
19
  import ImageEditing from './imageediting';
17
20
  import ImageTypeCommand from './imagetypecommand';
18
21
  import ImageUtils from '../imageutils';
19
22
  import {
20
- getImageTypeMatcher,
23
+ getImgViewElementMatcher,
21
24
  createImageViewElement,
22
25
  determineImageTypeForInsertionAtSelection
23
26
  } from '../image/utils';
@@ -109,15 +112,18 @@ export default class ImageInlineEditing extends Plugin {
109
112
  } );
110
113
 
111
114
  conversion.for( 'downcast' )
112
- .add( modelToViewAttributeConverter( imageUtils, 'imageInline', 'src' ) )
113
- .add( modelToViewAttributeConverter( imageUtils, 'imageInline', 'alt' ) )
114
- .add( srcsetAttributeConverter( imageUtils, 'imageInline' ) );
115
+ .add( downcastImageAttribute( imageUtils, 'imageInline', 'src' ) )
116
+ .add( downcastImageAttribute( imageUtils, 'imageInline', 'alt' ) )
117
+ .add( downcastSrcsetAttribute( imageUtils, 'imageInline' ) );
115
118
 
116
119
  // More image related upcasts are in 'ImageEditing' plugin.
117
120
  conversion.for( 'upcast' )
118
121
  .elementToElement( {
119
- view: getImageTypeMatcher( editor, 'imageInline' ),
120
- model: ( viewImage, { writer } ) => writer.createElement( 'imageInline', { src: viewImage.getAttribute( 'src' ) } )
122
+ view: getImgViewElementMatcher( editor, 'imageInline' ),
123
+ model: ( viewImage, { writer } ) => writer.createElement(
124
+ 'imageInline',
125
+ viewImage.hasAttribute( 'src' ) ? { src: viewImage.getAttribute( 'src' ) } : null
126
+ )
121
127
  } );
122
128
  }
123
129
 
@@ -185,7 +191,7 @@ export default class ImageInlineEditing extends Plugin {
185
191
  Array.from( blockViewImage.getAttributes() )
186
192
  .forEach( attribute => writer.setAttribute(
187
193
  ...attribute,
188
- imageUtils.getViewImageFromWidget( blockViewImage )
194
+ imageUtils.findViewImgElement( blockViewImage )
189
195
  ) );
190
196
 
191
197
  return blockViewImage.getChild( 0 );
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
2
+ * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
5
 
@@ -31,6 +31,15 @@ import { logWarning, toArray } from 'ckeditor5/src/utils';
31
31
  * ]
32
32
  * } );
33
33
  *
34
+ * If you want to take the full control over the process, you can specify individual model attributes:
35
+ *
36
+ * editor.execute( 'insertImage', {
37
+ * source: [
38
+ * { src: 'path/to/image.jpg', alt: 'First alt text' },
39
+ * { src: 'path/to/other-image.jpg', alt: 'Second alt text', customAttribute: 'My attribute value' }
40
+ * ]
41
+ * } );
42
+ *
34
43
  * @extends module:core/command~Command
35
44
  */
36
45
  export default class InsertImageCommand extends Command {
@@ -79,10 +88,11 @@ export default class InsertImageCommand extends Command {
79
88
  *
80
89
  * @fires execute
81
90
  * @param {Object} options Options for the executed command.
82
- * @param {String|Array.<String>} options.source The image source or an array of image sources to insert.
91
+ * @param {String|Array.<String>|Array.<Object>} options.source The image source or an array of image sources to insert.
92
+ * See the documentation of the command to learn more about accepted formats.
83
93
  */
84
94
  execute( options ) {
85
- const sources = toArray( options.source );
95
+ const sourceDefinitions = toArray( options.source );
86
96
  const selection = this.editor.model.document.selection;
87
97
  const imageUtils = this.editor.plugins.get( 'ImageUtils' );
88
98
 
@@ -96,17 +106,21 @@ export default class InsertImageCommand extends Command {
96
106
  // Note: Selection attributes that do not make sense for images will be filtered out by insertImage() anyway.
97
107
  const selectionAttributes = Object.fromEntries( selection.getAttributes() );
98
108
 
99
- sources.forEach( ( src, index ) => {
109
+ sourceDefinitions.forEach( ( sourceDefinition, index ) => {
100
110
  const selectedElement = selection.getSelectedElement();
101
111
 
112
+ if ( typeof sourceDefinition === 'string' ) {
113
+ sourceDefinition = { src: sourceDefinition };
114
+ }
115
+
102
116
  // Inserting of an inline image replace the selected element and make a selection on the inserted image.
103
117
  // Therefore inserting multiple inline images requires creating position after each element.
104
118
  if ( index && selectedElement && imageUtils.isImage( selectedElement ) ) {
105
119
  const position = this.editor.model.createPositionAfter( selectedElement );
106
120
 
107
- imageUtils.insertImage( { src, ...selectionAttributes }, position );
121
+ imageUtils.insertImage( { ...sourceDefinition, ...selectionAttributes }, position );
108
122
  } else {
109
- imageUtils.insertImage( { src, ...selectionAttributes } );
123
+ imageUtils.insertImage( { ...sourceDefinition, ...selectionAttributes } );
110
124
  }
111
125
  } );
112
126
  }
@@ -47,7 +47,8 @@ export function getBalloonPositionData( editor ) {
47
47
  defaultPositions.northArrowSouthEast,
48
48
  defaultPositions.southArrowNorth,
49
49
  defaultPositions.southArrowNorthWest,
50
- defaultPositions.southArrowNorthEast
50
+ defaultPositions.southArrowNorthEast,
51
+ defaultPositions.viewportStickyNorth
51
52
  ]
52
53
  };
53
54
  }
@@ -47,21 +47,16 @@ export function createImageViewElement( writer, imageType ) {
47
47
  * @param {'imageBlock'|'imageInline'} matchImageType The type of created image.
48
48
  * @returns {module:engine/view/matcher~MatcherPattern}
49
49
  */
50
- export function getImageTypeMatcher( editor, matchImageType ) {
50
+ export function getImgViewElementMatcher( editor, matchImageType ) {
51
51
  if ( editor.plugins.has( 'ImageInlineEditing' ) !== editor.plugins.has( 'ImageBlockEditing' ) ) {
52
- return {
53
- name: 'img',
54
- attributes: {
55
- src: true
56
- }
57
- };
52
+ return { name: 'img' };
58
53
  }
59
54
 
60
55
  const imageUtils = editor.plugins.get( 'ImageUtils' );
61
56
 
62
57
  return element => {
63
- // Convert only images with src attribute.
64
- if ( !imageUtils.isInlineImageView( element ) || !element.hasAttribute( 'src' ) ) {
58
+ // Check if view element is an `img`.
59
+ if ( !imageUtils.isInlineImageView( element ) ) {
65
60
  return null;
66
61
  }
67
62
 
@@ -73,7 +68,7 @@ export function getImageTypeMatcher( editor, matchImageType ) {
73
68
  return null;
74
69
  }
75
70
 
76
- return { name: true, attributes: [ 'src' ] };
71
+ return { name: true };
77
72
  };
78
73
  }
79
74
 
package/src/imageblock.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
2
+ * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
5
 
@@ -9,8 +9,7 @@
9
9
 
10
10
  import { Plugin } from 'ckeditor5/src/core';
11
11
  import { Element, enablePlaceholder } from 'ckeditor5/src/engine';
12
- import { setHighlightHandling, toWidgetEditable } from 'ckeditor5/src/widget';
13
- import { toArray } from 'ckeditor5/src/utils';
12
+ import { toWidgetEditable } from 'ckeditor5/src/widget';
14
13
 
15
14
  import ToggleImageCaptionCommand from './toggleimagecaptioncommand';
16
15
 
@@ -132,16 +131,7 @@ export default class ImageCaptionEditing extends Plugin {
132
131
  keepOnFocus: true
133
132
  } );
134
133
 
135
- const widgetEditable = toWidgetEditable( figcaptionElement, writer );
136
-
137
- setHighlightHandling(
138
- widgetEditable,
139
- writer,
140
- ( element, descriptor, writer ) => writer.addClass( toArray( descriptor.classes ), element ),
141
- ( element, descriptor, writer ) => writer.removeClass( toArray( descriptor.classes ), element )
142
- );
143
-
144
- return widgetEditable;
134
+ return toWidgetEditable( figcaptionElement, writer );
145
135
  }
146
136
  } );
147
137
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
2
+ * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
5
 
@@ -12,9 +12,13 @@ import { WidgetResize } from 'ckeditor5/src/widget';
12
12
 
13
13
  import ImageLoadObserver from '../image/imageloadobserver';
14
14
 
15
- const RESIZABLE_IMAGES_CSS_SELECTOR = 'figure.image.ck-widget > img,' +
15
+ const RESIZABLE_IMAGES_CSS_SELECTOR =
16
+ 'figure.image.ck-widget > img,' +
17
+ 'figure.image.ck-widget > picture > img,' +
16
18
  'figure.image.ck-widget > a > img,' +
17
- 'span.image-inline.ck-widget > img';
19
+ 'figure.image.ck-widget > a > picture > img,' +
20
+ 'span.image-inline.ck-widget > img,' +
21
+ 'span.image-inline.ck-widget > picture > img';
18
22
 
19
23
  const IMAGE_WIDGETS_CLASSES_MATCH_REGEXP = /(image|image-inline)/;
20
24
 
@@ -61,7 +61,8 @@ export function viewToModelStyleAttribute( styles ) {
61
61
  const viewElement = data.viewItem;
62
62
  const modelImageElement = first( data.modelRange.getItems() );
63
63
 
64
- // Check if `modelImageElement` exists (see: #8270, and #9563)...
64
+ // Run this converter only if an image has been found in the model.
65
+ // In some cases it may not be found (for example if we run this on a figure with different type than image).
65
66
  if ( !modelImageElement ) {
66
67
  return;
67
68
  }