@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.
- package/LICENSE.md +1 -1
- package/README.md +3 -3
- package/build/image.js +1 -1
- package/build/translations/ar.js +1 -0
- package/build/translations/ast.js +1 -0
- package/build/translations/az.js +1 -0
- package/build/translations/bg.js +1 -0
- package/build/translations/cs.js +1 -0
- package/build/translations/da.js +1 -0
- package/build/translations/de-ch.js +1 -0
- package/build/translations/de.js +1 -0
- package/build/translations/el.js +1 -0
- package/build/translations/en-au.js +1 -0
- package/build/translations/en-gb.js +1 -0
- package/build/translations/eo.js +1 -0
- package/build/translations/es.js +1 -0
- package/build/translations/et.js +1 -0
- package/build/translations/eu.js +1 -0
- package/build/translations/fa.js +1 -0
- package/build/translations/fi.js +1 -0
- package/build/translations/fr.js +1 -0
- package/build/translations/gl.js +1 -0
- package/build/translations/he.js +1 -0
- package/build/translations/hi.js +1 -0
- package/build/translations/hr.js +1 -0
- package/build/translations/hu.js +1 -0
- package/build/translations/id.js +1 -0
- package/build/translations/it.js +1 -0
- package/build/translations/ja.js +1 -0
- package/build/translations/km.js +1 -0
- package/build/translations/kn.js +1 -0
- package/build/translations/ko.js +1 -0
- package/build/translations/ku.js +1 -0
- package/build/translations/lt.js +1 -0
- package/build/translations/lv.js +1 -0
- package/build/translations/nb.js +1 -0
- package/build/translations/ne.js +1 -0
- package/build/translations/nl.js +1 -0
- package/build/translations/no.js +1 -0
- package/build/translations/pl.js +1 -0
- package/build/translations/pt-br.js +1 -0
- package/build/translations/pt.js +1 -0
- package/build/translations/ro.js +1 -0
- package/build/translations/ru.js +1 -0
- package/build/translations/si.js +1 -0
- package/build/translations/sk.js +1 -0
- package/build/translations/sq.js +1 -0
- package/build/translations/sr-latn.js +1 -0
- package/build/translations/sr.js +1 -0
- package/build/translations/sv.js +1 -0
- package/build/translations/th.js +1 -0
- package/build/translations/tk.js +1 -0
- package/build/translations/tr.js +1 -0
- package/build/translations/ug.js +1 -0
- package/build/translations/uk.js +1 -0
- package/build/translations/vi.js +1 -0
- package/build/translations/zh-cn.js +1 -0
- package/build/translations/zh.js +1 -0
- package/ckeditor5-metadata.json +233 -0
- package/lang/contexts.json +3 -0
- package/lang/translations/ar.po +12 -0
- package/lang/translations/ast.po +12 -0
- package/lang/translations/az.po +12 -0
- package/lang/translations/bg.po +12 -0
- package/lang/translations/cs.po +12 -0
- package/lang/translations/da.po +12 -0
- package/lang/translations/de-ch.po +113 -0
- package/lang/translations/de.po +15 -3
- package/lang/translations/el.po +12 -0
- package/lang/translations/en-au.po +12 -0
- package/lang/translations/en-gb.po +12 -0
- package/lang/translations/en.po +12 -0
- package/lang/translations/eo.po +12 -0
- package/lang/translations/es.po +12 -0
- package/lang/translations/et.po +12 -0
- package/lang/translations/eu.po +12 -0
- package/lang/translations/fa.po +12 -0
- package/lang/translations/fi.po +12 -0
- package/lang/translations/fr.po +12 -0
- package/lang/translations/gl.po +12 -0
- package/lang/translations/he.po +12 -0
- package/lang/translations/hi.po +12 -0
- package/lang/translations/hr.po +12 -0
- package/lang/translations/hu.po +13 -1
- package/lang/translations/id.po +21 -9
- package/lang/translations/it.po +12 -0
- package/lang/translations/ja.po +12 -0
- package/lang/translations/km.po +12 -0
- package/lang/translations/kn.po +12 -0
- package/lang/translations/ko.po +12 -0
- package/lang/translations/ku.po +12 -0
- package/lang/translations/lt.po +12 -0
- package/lang/translations/lv.po +12 -0
- package/lang/translations/nb.po +12 -0
- package/lang/translations/ne.po +12 -0
- package/lang/translations/nl.po +12 -0
- package/lang/translations/no.po +12 -0
- package/lang/translations/pl.po +20 -8
- package/lang/translations/pt-br.po +12 -0
- package/lang/translations/pt.po +12 -0
- package/lang/translations/ro.po +21 -9
- package/lang/translations/ru.po +12 -0
- package/lang/translations/si.po +12 -0
- package/lang/translations/sk.po +12 -0
- package/lang/translations/sq.po +12 -0
- package/lang/translations/sr-latn.po +12 -0
- package/lang/translations/sr.po +12 -0
- package/lang/translations/sv.po +12 -0
- package/lang/translations/th.po +12 -0
- package/lang/translations/tk.po +12 -0
- package/lang/translations/tr.po +12 -0
- package/lang/translations/ug.po +12 -0
- package/lang/translations/uk.po +12 -0
- package/lang/translations/vi.po +12 -0
- package/lang/translations/zh-cn.po +12 -0
- package/lang/translations/zh.po +12 -0
- package/package.json +36 -29
- package/src/autoimage.js +6 -4
- package/src/image/converters.js +192 -13
- package/src/image/imageblockediting.js +179 -0
- package/src/image/imageediting.js +13 -70
- package/src/image/imageinlineediting.js +204 -0
- package/src/image/imagetypecommand.js +105 -0
- package/src/image/insertimagecommand.js +77 -10
- package/src/image/ui/utils.js +3 -3
- package/src/image/utils.js +70 -121
- package/src/image.js +7 -19
- package/src/imageblock.js +46 -0
- package/src/imagecaption/imagecaptionediting.js +202 -230
- package/src/imagecaption/imagecaptionui.js +78 -0
- package/src/imagecaption/toggleimagecaptioncommand.js +165 -0
- package/src/imagecaption/utils.js +25 -40
- package/src/imagecaption.js +3 -2
- package/src/imageinline.js +46 -0
- package/src/imageinsert/imageinsertui.js +5 -6
- package/src/imageinsert.js +16 -4
- package/src/imageresize/imageresizebuttons.js +1 -1
- package/src/imageresize/imageresizeediting.js +21 -8
- package/src/imageresize/imageresizehandles.js +30 -8
- package/src/imageresize/resizeimagecommand.js +8 -5
- package/src/imagestyle/converters.js +25 -17
- package/src/imagestyle/imagestylecommand.js +73 -33
- package/src/imagestyle/imagestyleediting.js +113 -52
- package/src/imagestyle/imagestyleui.js +197 -31
- package/src/imagestyle/utils.js +300 -85
- package/src/imagestyle.js +218 -47
- package/src/imagetextalternative/imagetextalternativecommand.js +10 -7
- package/src/imagetextalternative/imagetextalternativeediting.js +9 -1
- package/src/imagetextalternative/imagetextalternativeui.js +2 -2
- package/src/imagetextalternative.js +1 -1
- package/src/imagetoolbar.js +33 -11
- package/src/imageupload/imageuploadediting.js +91 -31
- package/src/imageupload/imageuploadprogress.js +17 -9
- package/src/imageupload/imageuploadui.js +1 -1
- package/src/imageupload/uploadimagecommand.js +50 -24
- package/src/imageupload/utils.js +3 -2
- package/src/imageupload.js +1 -1
- package/src/imageutils.js +342 -0
- package/src/index.js +22 -47
- package/src/pictureediting.js +149 -0
- package/theme/image.css +101 -21
- package/theme/imagecaption.css +24 -2
- package/theme/imageresize.css +11 -0
- package/theme/imagestyle.css +76 -0
- package/theme/imageuploadicon.css +8 -2
- package/theme/imageuploadprogress.css +12 -8
- package/build/image.js.map +0 -1
package/src/image/converters.js
CHANGED
|
@@ -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
|
-
* <
|
|
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 `<
|
|
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
|
|
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 =
|
|
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
|
|
172
|
+
export function downcastSrcsetAttribute( imageUtils, imageType ) {
|
|
70
173
|
return dispatcher => {
|
|
71
|
-
dispatcher.on(
|
|
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
|
|
81
|
-
const img =
|
|
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
|
-
|
|
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 }
|
|
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
|
|
122
|
-
const img =
|
|
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
|
-
*
|
|
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
|
-
}
|