@ckeditor/ckeditor5-image 36.0.1 → 37.0.0-alpha.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 (106) hide show
  1. package/build/image.js +1 -1
  2. package/package.json +40 -35
  3. package/src/autoimage.d.ts +53 -0
  4. package/src/autoimage.js +111 -160
  5. package/src/image/converters.d.ts +66 -0
  6. package/src/image/converters.js +200 -261
  7. package/src/image/imageblockediting.d.ts +57 -0
  8. package/src/image/imageblockediting.js +111 -155
  9. package/src/image/imageediting.d.ts +34 -0
  10. package/src/image/imageediting.js +53 -67
  11. package/src/image/imageinlineediting.d.ts +53 -0
  12. package/src/image/imageinlineediting.js +135 -180
  13. package/src/image/imageloadobserver.d.ts +44 -0
  14. package/src/image/imageloadobserver.js +28 -47
  15. package/src/image/imagetypecommand.d.ts +46 -0
  16. package/src/image/imagetypecommand.js +67 -95
  17. package/src/image/insertimagecommand.d.ts +72 -0
  18. package/src/image/insertimagecommand.js +95 -101
  19. package/src/image/replaceimagesourcecommand.d.ts +39 -0
  20. package/src/image/replaceimagesourcecommand.js +28 -33
  21. package/src/image/ui/utils.d.ts +25 -0
  22. package/src/image/ui/utils.js +25 -35
  23. package/src/image/utils.d.ts +52 -0
  24. package/src/image/utils.js +63 -90
  25. package/src/image.d.ts +38 -0
  26. package/src/image.js +13 -43
  27. package/src/imageblock.d.ts +35 -0
  28. package/src/imageblock.js +12 -21
  29. package/src/imagecaption/imagecaptionediting.d.ts +92 -0
  30. package/src/imagecaption/imagecaptionediting.js +203 -262
  31. package/src/imagecaption/imagecaptionui.d.ts +30 -0
  32. package/src/imagecaption/imagecaptionui.js +46 -63
  33. package/src/imagecaption/imagecaptionutils.d.ts +42 -0
  34. package/src/imagecaption/imagecaptionutils.js +51 -78
  35. package/src/imagecaption/toggleimagecaptioncommand.d.ts +71 -0
  36. package/src/imagecaption/toggleimagecaptioncommand.js +112 -138
  37. package/src/imagecaption.d.ts +29 -0
  38. package/src/imagecaption.js +12 -19
  39. package/src/imageconfig.d.ts +723 -0
  40. package/src/imageconfig.js +5 -0
  41. package/src/imageinline.d.ts +35 -0
  42. package/src/imageinline.js +12 -21
  43. package/src/imageinsert/imageinsertui.d.ts +49 -0
  44. package/src/imageinsert/imageinsertui.js +120 -158
  45. package/src/imageinsert/ui/imageinsertformrowview.d.ts +61 -0
  46. package/src/imageinsert/ui/imageinsertformrowview.js +37 -86
  47. package/src/imageinsert/ui/imageinsertpanelview.d.ts +106 -0
  48. package/src/imageinsert/ui/imageinsertpanelview.js +148 -258
  49. package/src/imageinsert/utils.d.ts +26 -0
  50. package/src/imageinsert/utils.js +42 -58
  51. package/src/imageinsert.d.ts +35 -0
  52. package/src/imageinsert.js +13 -84
  53. package/src/imageinsertviaurl.d.ts +34 -0
  54. package/src/imageinsertviaurl.js +12 -18
  55. package/src/imageresize/imageresizebuttons.d.ts +66 -0
  56. package/src/imageresize/imageresizebuttons.js +190 -255
  57. package/src/imageresize/imageresizeediting.d.ts +42 -0
  58. package/src/imageresize/imageresizeediting.js +100 -125
  59. package/src/imageresize/imageresizehandles.d.ts +30 -0
  60. package/src/imageresize/imageresizehandles.js +91 -123
  61. package/src/imageresize/resizeimagecommand.d.ts +47 -0
  62. package/src/imageresize/resizeimagecommand.js +48 -55
  63. package/src/imageresize.d.ts +29 -0
  64. package/src/imageresize.js +12 -209
  65. package/src/imagestyle/converters.d.ts +24 -0
  66. package/src/imagestyle/converters.js +60 -78
  67. package/src/imagestyle/imagestylecommand.d.ts +70 -0
  68. package/src/imagestyle/imagestylecommand.js +88 -124
  69. package/src/imagestyle/imagestyleediting.d.ts +54 -0
  70. package/src/imagestyle/imagestyleediting.js +90 -137
  71. package/src/imagestyle/imagestyleui.d.ts +60 -0
  72. package/src/imagestyle/imagestyleui.js +169 -277
  73. package/src/imagestyle/utils.d.ts +61 -0
  74. package/src/imagestyle/utils.js +253 -306
  75. package/src/imagestyle.d.ts +35 -0
  76. package/src/imagestyle.js +13 -261
  77. package/src/imagetextalternative/imagetextalternativecommand.d.ts +39 -0
  78. package/src/imagetextalternative/imagetextalternativecommand.js +31 -47
  79. package/src/imagetextalternative/imagetextalternativeediting.d.ts +32 -0
  80. package/src/imagetextalternative/imagetextalternativeediting.js +18 -25
  81. package/src/imagetextalternative/imagetextalternativeui.d.ts +72 -0
  82. package/src/imagetextalternative/imagetextalternativeui.js +156 -219
  83. package/src/imagetextalternative/ui/textalternativeformview.d.ts +72 -0
  84. package/src/imagetextalternative/ui/textalternativeformview.js +103 -192
  85. package/src/imagetextalternative.d.ts +32 -0
  86. package/src/imagetextalternative.js +12 -18
  87. package/src/imagetoolbar.d.ts +38 -0
  88. package/src/imagetoolbar.js +31 -77
  89. package/src/imageupload/imageuploadediting.d.ts +114 -0
  90. package/src/imageupload/imageuploadediting.js +308 -427
  91. package/src/imageupload/imageuploadprogress.d.ts +47 -0
  92. package/src/imageupload/imageuploadprogress.js +180 -261
  93. package/src/imageupload/imageuploadui.d.ts +29 -0
  94. package/src/imageupload/imageuploadui.js +41 -57
  95. package/src/imageupload/uploadimagecommand.d.ts +65 -0
  96. package/src/imageupload/uploadimagecommand.js +73 -87
  97. package/src/imageupload/utils.d.ts +33 -0
  98. package/src/imageupload/utils.js +87 -112
  99. package/src/imageupload.d.ts +34 -0
  100. package/src/imageupload.js +12 -61
  101. package/src/imageutils.d.ts +108 -0
  102. package/src/imageutils.js +233 -329
  103. package/src/index.d.ts +31 -0
  104. package/src/index.js +0 -2
  105. package/src/pictureediting.d.ts +86 -0
  106. package/src/pictureediting.js +101 -120
@@ -2,302 +2,241 @@
2
2
  * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
-
6
- /**
7
- * @module image/image/converters
8
- */
9
-
10
5
  import { first } from 'ckeditor5/src/utils';
11
-
12
6
  /**
13
7
  * Returns a function that converts the image view representation:
14
8
  *
15
- * <figure class="image"><img src="..." alt="..."></img></figure>
9
+ * ```html
10
+ * <figure class="image"><img src="..." alt="..."></img></figure>
11
+ * ```
16
12
  *
17
13
  * to the model representation:
18
14
  *
19
- * <imageBlock src="..." alt="..."></imageBlock>
15
+ * ```html
16
+ * <imageBlock src="..." alt="..."></imageBlock>
17
+ * ```
20
18
  *
21
19
  * The entire content of the `<figure>` element except the first `<img>` is being converted as children
22
20
  * of the `<imageBlock>` model element.
23
21
  *
24
- * @protected
25
- * @param {module:image/imageutils~ImageUtils} imageUtils
26
- * @returns {Function}
22
+ * @internal
27
23
  */
28
- export function upcastImageFigure( imageUtils ) {
29
- return dispatcher => {
30
- dispatcher.on( 'element:figure', converter );
31
- };
32
-
33
- function converter( evt, data, conversionApi ) {
34
- // Do not convert if this is not an "image figure".
35
- if ( !conversionApi.consumable.test( data.viewItem, { name: true, classes: 'image' } ) ) {
36
- return;
37
- }
38
-
39
- // Find an image element inside the figure element.
40
- const viewImage = imageUtils.findViewImgElement( data.viewItem );
41
-
42
- // Do not convert if image element is absent or was already converted.
43
- if ( !viewImage || !conversionApi.consumable.test( viewImage, { name: true } ) ) {
44
- return;
45
- }
46
-
47
- // Consume the figure to prevent other converters from processing it again.
48
- conversionApi.consumable.consume( data.viewItem, { name: true, classes: 'image' } );
49
-
50
- // Convert view image to model image.
51
- const conversionResult = conversionApi.convertItem( viewImage, data.modelCursor );
52
-
53
- // Get image element from conversion result.
54
- const modelImage = first( conversionResult.modelRange.getItems() );
55
-
56
- // When image wasn't successfully converted then finish conversion.
57
- if ( !modelImage ) {
58
- // Revert consumed figure so other features can convert it.
59
- conversionApi.consumable.revert( data.viewItem, { name: true, classes: 'image' } );
60
-
61
- return;
62
- }
63
-
64
- // Convert rest of the figure element's children as an image children.
65
- conversionApi.convertChildren( data.viewItem, modelImage );
66
-
67
- conversionApi.updateConversionResult( modelImage, data );
68
- }
24
+ export function upcastImageFigure(imageUtils) {
25
+ const converter = (evt, data, conversionApi) => {
26
+ // Do not convert if this is not an "image figure".
27
+ if (!conversionApi.consumable.test(data.viewItem, { name: true, classes: 'image' })) {
28
+ return;
29
+ }
30
+ // Find an image element inside the figure element.
31
+ const viewImage = imageUtils.findViewImgElement(data.viewItem);
32
+ // Do not convert if image element is absent or was already converted.
33
+ if (!viewImage || !conversionApi.consumable.test(viewImage, { name: true })) {
34
+ return;
35
+ }
36
+ // Consume the figure to prevent other converters from processing it again.
37
+ conversionApi.consumable.consume(data.viewItem, { name: true, classes: 'image' });
38
+ // Convert view image to model image.
39
+ const conversionResult = conversionApi.convertItem(viewImage, data.modelCursor);
40
+ // Get image element from conversion result.
41
+ const modelImage = first(conversionResult.modelRange.getItems());
42
+ // When image wasn't successfully converted then finish conversion.
43
+ if (!modelImage) {
44
+ // Revert consumed figure so other features can convert it.
45
+ conversionApi.consumable.revert(data.viewItem, { name: true, classes: 'image' });
46
+ return;
47
+ }
48
+ // Convert rest of the figure element's children as an image children.
49
+ conversionApi.convertChildren(data.viewItem, modelImage);
50
+ conversionApi.updateConversionResult(modelImage, data);
51
+ };
52
+ return dispatcher => {
53
+ dispatcher.on('element:figure', converter);
54
+ };
69
55
  }
70
-
71
56
  /**
72
57
  * Returns a function that converts the image view representation:
73
58
  *
74
- * <picture><source ... /><source ... />...<img ... /></picture>
59
+ * ```html
60
+ * <picture><source ... /><source ... />...<img ... /></picture>
61
+ * ```
75
62
  *
76
63
  * to the model representation as the `sources` attribute:
77
64
  *
78
- * <image[Block|Inline] ... sources="..."></image[Block|Inline]>
65
+ * ```html
66
+ * <image[Block|Inline] ... sources="..."></image[Block|Inline]>
67
+ * ```
79
68
  *
80
- * @protected
81
- * @param {module:image/imageutils~ImageUtils} imageUtils
82
- * @returns {Function}
69
+ * @internal
83
70
  */
84
- export function upcastPicture( imageUtils ) {
85
- const sourceAttributeNames = [ 'srcset', 'media', 'type', 'sizes' ];
86
-
87
- return dispatcher => {
88
- dispatcher.on( 'element:picture', converter );
89
- };
90
-
91
- function converter( evt, data, conversionApi ) {
92
- const pictureViewElement = data.viewItem;
93
-
94
- // Do not convert <picture> if already consumed.
95
- if ( !conversionApi.consumable.test( pictureViewElement, { name: true } ) ) {
96
- return;
97
- }
98
-
99
- const sources = new Map();
100
-
101
- // Collect all <source /> elements attribute values.
102
- for ( const childSourceElement of pictureViewElement.getChildren() ) {
103
- if ( childSourceElement.is( 'element', 'source' ) ) {
104
- const attributes = {};
105
-
106
- for ( const name of sourceAttributeNames ) {
107
- if ( childSourceElement.hasAttribute( name ) ) {
108
- // Don't collect <source /> attribute if already consumed somewhere else.
109
- if ( conversionApi.consumable.test( childSourceElement, { attributes: name } ) ) {
110
- attributes[ name ] = childSourceElement.getAttribute( name );
111
- }
112
- }
113
- }
114
-
115
- if ( Object.keys( attributes ).length ) {
116
- sources.set( childSourceElement, attributes );
117
- }
118
- }
119
- }
120
-
121
- const imgViewElement = imageUtils.findViewImgElement( pictureViewElement );
122
-
123
- // Don't convert when a picture has no <img/> inside (it is broken).
124
- if ( !imgViewElement ) {
125
- return;
126
- }
127
-
128
- let modelImage = data.modelCursor.parent;
129
-
130
- // - In case of an inline image (cursor parent in a <paragraph>), the <img/> must be converted right away
131
- // because no converter handled it yet and otherwise there would be no model element to set the sources attribute on.
132
- // - In case of a block image, the <figure class="image"> converter (in ImageBlockEditing) converts the
133
- // <img/> right away on its own and the modelCursor is already inside an imageBlock and there's nothing special
134
- // to do here.
135
- if ( !modelImage.is( 'element', 'imageBlock' ) ) {
136
- const conversionResult = conversionApi.convertItem( imgViewElement, data.modelCursor );
137
-
138
- // Set image range as conversion result.
139
- data.modelRange = conversionResult.modelRange;
140
-
141
- // Continue conversion where image conversion ends.
142
- data.modelCursor = conversionResult.modelCursor;
143
-
144
- modelImage = first( conversionResult.modelRange.getItems() );
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
- }
71
+ export function upcastPicture(imageUtils) {
72
+ const sourceAttributeNames = ['srcset', 'media', 'type', 'sizes'];
73
+ const converter = (evt, data, conversionApi) => {
74
+ const pictureViewElement = data.viewItem;
75
+ // Do not convert <picture> if already consumed.
76
+ if (!conversionApi.consumable.test(pictureViewElement, { name: true })) {
77
+ return;
78
+ }
79
+ const sources = new Map();
80
+ // Collect all <source /> elements attribute values.
81
+ for (const childSourceElement of pictureViewElement.getChildren()) {
82
+ if (childSourceElement.is('element', 'source')) {
83
+ const attributes = {};
84
+ for (const name of sourceAttributeNames) {
85
+ if (childSourceElement.hasAttribute(name)) {
86
+ // Don't collect <source /> attribute if already consumed somewhere else.
87
+ if (conversionApi.consumable.test(childSourceElement, { attributes: name })) {
88
+ attributes[name] = childSourceElement.getAttribute(name);
89
+ }
90
+ }
91
+ }
92
+ if (Object.keys(attributes).length) {
93
+ sources.set(childSourceElement, attributes);
94
+ }
95
+ }
96
+ }
97
+ const imgViewElement = imageUtils.findViewImgElement(pictureViewElement);
98
+ // Don't convert when a picture has no <img/> inside (it is broken).
99
+ if (!imgViewElement) {
100
+ return;
101
+ }
102
+ let modelImage = data.modelCursor.parent;
103
+ // - In case of an inline image (cursor parent in a <paragraph>), the <img/> must be converted right away
104
+ // because no converter handled it yet and otherwise there would be no model element to set the sources attribute on.
105
+ // - In case of a block image, the <figure class="image"> converter (in ImageBlockEditing) converts the
106
+ // <img/> right away on its own and the modelCursor is already inside an imageBlock and there's nothing special
107
+ // to do here.
108
+ if (!modelImage.is('element', 'imageBlock')) {
109
+ const conversionResult = conversionApi.convertItem(imgViewElement, data.modelCursor);
110
+ // Set image range as conversion result.
111
+ data.modelRange = conversionResult.modelRange;
112
+ // Continue conversion where image conversion ends.
113
+ data.modelCursor = conversionResult.modelCursor;
114
+ modelImage = first(conversionResult.modelRange.getItems());
115
+ }
116
+ conversionApi.consumable.consume(pictureViewElement, { name: true });
117
+ // Consume only these <source/> attributes that were actually collected and will be passed on
118
+ // to the image model element.
119
+ for (const [sourceElement, attributes] of sources) {
120
+ conversionApi.consumable.consume(sourceElement, { attributes: Object.keys(attributes) });
121
+ }
122
+ if (sources.size) {
123
+ conversionApi.writer.setAttribute('sources', Array.from(sources.values()), modelImage);
124
+ }
125
+ // Convert rest of the <picture> children as an image children. Other converters may want to consume them.
126
+ conversionApi.convertChildren(pictureViewElement, modelImage);
127
+ };
128
+ return dispatcher => {
129
+ dispatcher.on('element:picture', converter);
130
+ };
162
131
  }
163
-
164
132
  /**
165
133
  * Converter used to convert the `srcset` model image attribute to the `srcset`, `sizes` and `width` attributes in the view.
166
134
  *
167
- * @protected
168
- * @param {module:image/imageutils~ImageUtils} imageUtils
169
- * @param {'imageBlock'|'imageInline'} imageType The type of the image.
170
- * @returns {Function}
135
+ * @internal
136
+ * @param imageType The type of the image.
171
137
  */
172
- export function downcastSrcsetAttribute( imageUtils, imageType ) {
173
- return dispatcher => {
174
- dispatcher.on( `attribute:srcset:${ imageType }`, converter );
175
- };
176
-
177
- function converter( evt, data, conversionApi ) {
178
- if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
179
- return;
180
- }
181
-
182
- const writer = conversionApi.writer;
183
- const element = conversionApi.mapper.toViewElement( data.item );
184
- const img = imageUtils.findViewImgElement( element );
185
-
186
- if ( data.attributeNewValue === null ) {
187
- const srcset = data.attributeOldValue;
188
-
189
- if ( srcset.data ) {
190
- writer.removeAttribute( 'srcset', img );
191
- writer.removeAttribute( 'sizes', img );
192
-
193
- if ( srcset.width ) {
194
- writer.removeAttribute( 'width', img );
195
- }
196
- }
197
- } else {
198
- const srcset = data.attributeNewValue;
199
-
200
- if ( srcset.data ) {
201
- writer.setAttribute( 'srcset', srcset.data, img );
202
- // Always outputting `100vw`. See https://github.com/ckeditor/ckeditor5-image/issues/2.
203
- writer.setAttribute( 'sizes', '100vw', img );
204
-
205
- if ( srcset.width ) {
206
- writer.setAttribute( 'width', srcset.width, img );
207
- }
208
- }
209
- }
210
- }
138
+ export function downcastSrcsetAttribute(imageUtils, imageType) {
139
+ const converter = (evt, data, conversionApi) => {
140
+ if (!conversionApi.consumable.consume(data.item, evt.name)) {
141
+ return;
142
+ }
143
+ const writer = conversionApi.writer;
144
+ const element = conversionApi.mapper.toViewElement(data.item);
145
+ const img = imageUtils.findViewImgElement(element);
146
+ if (data.attributeNewValue === null) {
147
+ const srcset = data.attributeOldValue;
148
+ if (srcset && srcset.data) {
149
+ writer.removeAttribute('srcset', img);
150
+ writer.removeAttribute('sizes', img);
151
+ if (srcset.width) {
152
+ writer.removeAttribute('width', img);
153
+ }
154
+ }
155
+ }
156
+ else {
157
+ const srcset = data.attributeNewValue;
158
+ if (srcset && srcset.data) {
159
+ writer.setAttribute('srcset', srcset.data, img);
160
+ // Always outputting `100vw`. See https://github.com/ckeditor/ckeditor5-image/issues/2.
161
+ writer.setAttribute('sizes', '100vw', img);
162
+ if (srcset.width) {
163
+ writer.setAttribute('width', srcset.width, img);
164
+ }
165
+ }
166
+ }
167
+ };
168
+ return dispatcher => {
169
+ dispatcher.on(`attribute:srcset:${imageType}`, converter);
170
+ };
211
171
  }
212
-
213
172
  /**
214
173
  * Converts the `source` model attribute to the `<picture><source /><source />...<img /></picture>`
215
174
  * view structure.
216
175
  *
217
- * @protected
218
- * @param {module:image/imageutils~ImageUtils} imageUtils
219
- * @returns {Function}
176
+ * @internal
220
177
  */
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', null,
239
- data.attributeNewValue.map( sourceAttributes => {
240
- return viewWriter.createEmptyElement( 'source', sourceAttributes );
241
- } )
242
- );
243
-
244
- // Collect all wrapping attribute elements.
245
- const attributeElements = [];
246
- let viewElement = imgElement.parent;
247
-
248
- while ( viewElement && viewElement.is( 'attributeElement' ) ) {
249
- const parentElement = viewElement.parent;
250
-
251
- viewWriter.unwrap( viewWriter.createRangeOn( imgElement ), viewElement );
252
-
253
- attributeElements.unshift( viewElement );
254
- viewElement = parentElement;
255
- }
256
-
257
- // Insert the picture and move img into it.
258
- viewWriter.insert( viewWriter.createPositionBefore( imgElement ), pictureElement );
259
- viewWriter.move( viewWriter.createRangeOn( imgElement ), viewWriter.createPositionAt( pictureElement, 'end' ) );
260
-
261
- // Apply collected attribute elements over the new picture element.
262
- for ( const attributeElement of attributeElements ) {
263
- viewWriter.wrap( viewWriter.createRangeOn( pictureElement ), attributeElement );
264
- }
265
- }
266
- // Both setting "sources" to an empty array and removing the attribute should unwrap the <img />.
267
- // Unwrap once if the latter followed the former, though.
268
- else if ( imgElement.parent.is( 'element', 'picture' ) ) {
269
- const pictureElement = imgElement.parent;
270
-
271
- viewWriter.move( viewWriter.createRangeOn( imgElement ), viewWriter.createPositionBefore( pictureElement ) );
272
- viewWriter.remove( pictureElement );
273
- }
274
- }
178
+ export function downcastSourcesAttribute(imageUtils) {
179
+ const converter = (evt, data, conversionApi) => {
180
+ if (!conversionApi.consumable.consume(data.item, evt.name)) {
181
+ return;
182
+ }
183
+ const viewWriter = conversionApi.writer;
184
+ const element = conversionApi.mapper.toViewElement(data.item);
185
+ const imgElement = imageUtils.findViewImgElement(element);
186
+ const attributeNewValue = data.attributeNewValue;
187
+ if (attributeNewValue && attributeNewValue.length) {
188
+ // Make sure <picture> does not break attribute elements, for instance <a> in linked images.
189
+ const pictureElement = viewWriter.createContainerElement('picture', null, attributeNewValue.map(sourceAttributes => {
190
+ return viewWriter.createEmptyElement('source', sourceAttributes);
191
+ }));
192
+ // Collect all wrapping attribute elements.
193
+ const attributeElements = [];
194
+ let viewElement = imgElement.parent;
195
+ while (viewElement && viewElement.is('attributeElement')) {
196
+ const parentElement = viewElement.parent;
197
+ viewWriter.unwrap(viewWriter.createRangeOn(imgElement), viewElement);
198
+ attributeElements.unshift(viewElement);
199
+ viewElement = parentElement;
200
+ }
201
+ // Insert the picture and move img into it.
202
+ viewWriter.insert(viewWriter.createPositionBefore(imgElement), pictureElement);
203
+ viewWriter.move(viewWriter.createRangeOn(imgElement), viewWriter.createPositionAt(pictureElement, 'end'));
204
+ // Apply collected attribute elements over the new picture element.
205
+ for (const attributeElement of attributeElements) {
206
+ viewWriter.wrap(viewWriter.createRangeOn(pictureElement), attributeElement);
207
+ }
208
+ }
209
+ // Both setting "sources" to an empty array and removing the attribute should unwrap the <img />.
210
+ // Unwrap once if the latter followed the former, though.
211
+ else if (imgElement.parent.is('element', 'picture')) {
212
+ const pictureElement = imgElement.parent;
213
+ viewWriter.move(viewWriter.createRangeOn(imgElement), viewWriter.createPositionBefore(pictureElement));
214
+ viewWriter.remove(pictureElement);
215
+ }
216
+ };
217
+ return dispatcher => {
218
+ dispatcher.on('attribute:sources:imageBlock', converter);
219
+ dispatcher.on('attribute:sources:imageInline', converter);
220
+ };
275
221
  }
276
-
277
222
  /**
278
223
  * Converter used to convert a given image attribute from the model to the view.
279
224
  *
280
- * @protected
281
- * @param {module:image/imageutils~ImageUtils} imageUtils
282
- * @param {'imageBlock'|'imageInline'} imageType The type of the image.
283
- * @param {String} attributeKey The name of the attribute to convert.
284
- * @returns {Function}
225
+ * @internal
226
+ * @param imageType The type of the image.
227
+ * @param attributeKey The name of the attribute to convert.
285
228
  */
286
- export function downcastImageAttribute( imageUtils, imageType, attributeKey ) {
287
- return dispatcher => {
288
- dispatcher.on( `attribute:${ attributeKey }:${ imageType }`, converter );
289
- };
290
-
291
- function converter( evt, data, conversionApi ) {
292
- if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
293
- return;
294
- }
295
-
296
- const viewWriter = conversionApi.writer;
297
- const element = conversionApi.mapper.toViewElement( data.item );
298
- const img = imageUtils.findViewImgElement( element );
299
-
300
- viewWriter.setAttribute( data.attributeKey, data.attributeNewValue || '', img );
301
- }
229
+ export function downcastImageAttribute(imageUtils, imageType, attributeKey) {
230
+ const converter = (evt, data, conversionApi) => {
231
+ if (!conversionApi.consumable.consume(data.item, evt.name)) {
232
+ return;
233
+ }
234
+ const viewWriter = conversionApi.writer;
235
+ const element = conversionApi.mapper.toViewElement(data.item);
236
+ const img = imageUtils.findViewImgElement(element);
237
+ viewWriter.setAttribute(data.attributeKey, data.attributeNewValue || '', img);
238
+ };
239
+ return dispatcher => {
240
+ dispatcher.on(`attribute:${attributeKey}:${imageType}`, converter);
241
+ };
302
242
  }
303
-
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * @module image/image/imageblockediting
7
+ */
8
+ import { Plugin, type PluginDependencies } from 'ckeditor5/src/core';
9
+ /**
10
+ * The image block plugin.
11
+ *
12
+ * It registers:
13
+ *
14
+ * * `<imageBlock>` as a block element in the document schema, and allows `alt`, `src` and `srcset` attributes.
15
+ * * converters for editing and data pipelines.,
16
+ * * {@link module:image/image/imagetypecommand~ImageTypeCommand `'imageTypeBlock'`} command that converts inline images into
17
+ * block images.
18
+ */
19
+ export default class ImageBlockEditing extends Plugin {
20
+ /**
21
+ * @inheritDoc
22
+ */
23
+ static get requires(): PluginDependencies;
24
+ /**
25
+ * @inheritDoc
26
+ */
27
+ static get pluginName(): 'ImageBlockEditing';
28
+ /**
29
+ * @inheritDoc
30
+ */
31
+ init(): void;
32
+ /**
33
+ * Configures conversion pipelines to support upcasting and downcasting
34
+ * block images (block image widgets) and their attributes.
35
+ */
36
+ private _setupConversion;
37
+ /**
38
+ * Integrates the plugin with the clipboard pipeline.
39
+ *
40
+ * Idea is that the feature should recognize the user's intent when an **inline** image is
41
+ * pasted or dropped. If such an image is pasted/dropped:
42
+ *
43
+ * * into an empty block (e.g. an empty paragraph),
44
+ * * on another object (e.g. some block widget).
45
+ *
46
+ * it gets converted into a block image on the fly. We assume this is the user's intent
47
+ * if they decided to put their image there.
48
+ *
49
+ * See the `ImageInlineEditing` for the similar integration that works in the opposite direction.
50
+ */
51
+ private _setupClipboardIntegration;
52
+ }
53
+ declare module '@ckeditor/ckeditor5-core' {
54
+ interface PluginsMap {
55
+ [ImageBlockEditing.pluginName]: ImageBlockEditing;
56
+ }
57
+ }