@ckeditor/ckeditor5-image 28.0.0 → 30.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +12 -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 +14 -2
- 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 +9 -4
- package/src/image/converters.js +191 -15
- package/src/image/imageblockediting.js +182 -0
- package/src/image/imageediting.js +13 -70
- package/src/image/imageinlineediting.js +207 -0
- package/src/image/imagetypecommand.js +105 -0
- package/src/image/insertimagecommand.js +77 -10
- package/src/image/ui/utils.js +5 -4
- package/src/image/utils.js +65 -121
- package/src/image.js +7 -19
- package/src/imageblock.js +46 -0
- package/src/imagecaption/imagecaptionediting.js +183 -227
- 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 +26 -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 +90 -30
- 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/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/CHANGELOG.md +0 -423
- package/build/image.js.map +0 -1
|
@@ -0,0 +1,342 @@
|
|
|
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/imageutils
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Plugin } from 'ckeditor5/src/core';
|
|
11
|
+
import { findOptimalInsertionRange, isWidget, toWidget } from 'ckeditor5/src/widget';
|
|
12
|
+
import { determineImageTypeForInsertionAtSelection } from './image/utils';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A set of helpers related to images.
|
|
16
|
+
*
|
|
17
|
+
* @extends module:core/plugin~Plugin
|
|
18
|
+
*/
|
|
19
|
+
export default class ImageUtils extends Plugin {
|
|
20
|
+
/**
|
|
21
|
+
* @inheritDoc
|
|
22
|
+
*/
|
|
23
|
+
static get pluginName() {
|
|
24
|
+
return 'ImageUtils';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Checks if the provided model element is an `image` or `imageInline`.
|
|
29
|
+
*
|
|
30
|
+
* @param {module:engine/model/element~Element} modelElement
|
|
31
|
+
* @returns {Boolean}
|
|
32
|
+
*/
|
|
33
|
+
isImage( modelElement ) {
|
|
34
|
+
return this.isInlineImage( modelElement ) || this.isBlockImage( modelElement );
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Checks if the provided view element represents an inline image.
|
|
39
|
+
*
|
|
40
|
+
* Also, see {@link module:image/imageutils~ImageUtils#isImageWidget}.
|
|
41
|
+
*
|
|
42
|
+
* @param {module:engine/view/element~Element} element
|
|
43
|
+
* @returns {Boolean}
|
|
44
|
+
*/
|
|
45
|
+
isInlineImageView( element ) {
|
|
46
|
+
return !!element && element.is( 'element', 'img' );
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Checks if the provided view element represents a block image.
|
|
51
|
+
*
|
|
52
|
+
* Also, see {@link module:image/imageutils~ImageUtils#isImageWidget}.
|
|
53
|
+
*
|
|
54
|
+
* @param {module:engine/view/element~Element} element
|
|
55
|
+
* @returns {Boolean}
|
|
56
|
+
*/
|
|
57
|
+
isBlockImageView( element ) {
|
|
58
|
+
return !!element && element.is( 'element', 'figure' ) && element.hasClass( 'image' );
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Handles inserting single file. This method unifies image insertion using {@link module:widget/utils~findOptimalInsertionRange}
|
|
63
|
+
* method.
|
|
64
|
+
*
|
|
65
|
+
* const imageUtils = editor.plugins.get( 'ImageUtils' );
|
|
66
|
+
*
|
|
67
|
+
* imageUtils.insertImage( { src: 'path/to/image.jpg' } );
|
|
68
|
+
*
|
|
69
|
+
* @param {Object} [attributes={}] Attributes of the inserted image.
|
|
70
|
+
* This method filters out the attributes which are disallowed by the {@link module:engine/model/schema~Schema}.
|
|
71
|
+
* @param {module:engine/model/selection~Selectable} [selectable] Place to insert the image. If not specified,
|
|
72
|
+
* the {@link module:widget/utils~findOptimalInsertionRange} logic will be applied for the block images
|
|
73
|
+
* and `model.document.selection` for the inline images.
|
|
74
|
+
*
|
|
75
|
+
* **Note**: If `selectable` is passed, this helper will not be able to set selection attributes (such as `linkHref`)
|
|
76
|
+
* and apply them to the new image. In this case, make sure all selection attributes are passed in `attributes`.
|
|
77
|
+
*
|
|
78
|
+
* @param {'imageBlock'|'imageInline'} [imageType] Image type of inserted image. If not specified,
|
|
79
|
+
* it will be determined automatically depending of editor config or place of the insertion.
|
|
80
|
+
* @return {module:engine/view/element~Element|null} The inserted model image element.
|
|
81
|
+
*/
|
|
82
|
+
insertImage( attributes = {}, selectable = null, imageType = null ) {
|
|
83
|
+
const editor = this.editor;
|
|
84
|
+
const model = editor.model;
|
|
85
|
+
const selection = model.document.selection;
|
|
86
|
+
|
|
87
|
+
imageType = determineImageTypeForInsertion( editor, selectable || selection, imageType );
|
|
88
|
+
|
|
89
|
+
// Mix declarative attributes with selection attributes because the new image should "inherit"
|
|
90
|
+
// the latter for best UX. For instance, inline images inserted into existing links
|
|
91
|
+
// should not split them. To do that, they need to have "linkHref" inherited from the selection.
|
|
92
|
+
attributes = {
|
|
93
|
+
...Object.fromEntries( selection.getAttributes() ),
|
|
94
|
+
...attributes
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
for ( const attributeName in attributes ) {
|
|
98
|
+
if ( !model.schema.checkAttribute( imageType, attributeName ) ) {
|
|
99
|
+
delete attributes[ attributeName ];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return model.change( writer => {
|
|
104
|
+
const imageElement = writer.createElement( imageType, attributes );
|
|
105
|
+
|
|
106
|
+
// If we want to insert a block image (for whatever reason) then we don't want to split text blocks.
|
|
107
|
+
// This applies only when we don't have the selectable specified (i.e., we insert multiple block images at once).
|
|
108
|
+
if ( !selectable && imageType != 'imageInline' ) {
|
|
109
|
+
selectable = findOptimalInsertionRange( selection, model );
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
model.insertContent( imageElement, selectable );
|
|
113
|
+
|
|
114
|
+
// Inserting an image might've failed due to schema regulations.
|
|
115
|
+
if ( imageElement.parent ) {
|
|
116
|
+
writer.setSelection( imageElement, 'on' );
|
|
117
|
+
|
|
118
|
+
return imageElement;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return null;
|
|
122
|
+
} );
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Returns an image widget editing view element if one is selected or is among the selection's ancestors.
|
|
127
|
+
*
|
|
128
|
+
* @protected
|
|
129
|
+
* @param {module:engine/view/selection~Selection|module:engine/view/documentselection~DocumentSelection} selection
|
|
130
|
+
* @returns {module:engine/view/element~Element|null}
|
|
131
|
+
*/
|
|
132
|
+
getClosestSelectedImageWidget( selection ) {
|
|
133
|
+
const viewElement = selection.getSelectedElement();
|
|
134
|
+
|
|
135
|
+
if ( viewElement && this.isImageWidget( viewElement ) ) {
|
|
136
|
+
return viewElement;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let parent = selection.getFirstPosition().parent;
|
|
140
|
+
|
|
141
|
+
while ( parent ) {
|
|
142
|
+
if ( parent.is( 'element' ) && this.isImageWidget( parent ) ) {
|
|
143
|
+
return parent;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
parent = parent.parent;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Returns a image model element if one is selected or is among the selection's ancestors.
|
|
154
|
+
*
|
|
155
|
+
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
|
|
156
|
+
* @returns {module:engine/model/element~Element|null}
|
|
157
|
+
*/
|
|
158
|
+
getClosestSelectedImageElement( selection ) {
|
|
159
|
+
const selectedElement = selection.getSelectedElement();
|
|
160
|
+
|
|
161
|
+
return this.isImage( selectedElement ) ? selectedElement : selection.getFirstPosition().findAncestor( 'imageBlock' );
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Checks if image can be inserted at current model selection.
|
|
166
|
+
*
|
|
167
|
+
* @protected
|
|
168
|
+
* @returns {Boolean}
|
|
169
|
+
*/
|
|
170
|
+
isImageAllowed() {
|
|
171
|
+
const model = this.editor.model;
|
|
172
|
+
const selection = model.document.selection;
|
|
173
|
+
|
|
174
|
+
return isImageAllowedInParent( this.editor, selection ) && isNotInsideImage( selection );
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Converts a given {@link module:engine/view/element~Element} to an image widget:
|
|
179
|
+
* * Adds a {@link module:engine/view/element~Element#_setCustomProperty custom property} allowing to recognize the image widget
|
|
180
|
+
* element.
|
|
181
|
+
* * Calls the {@link module:widget/utils~toWidget} function with the proper element's label creator.
|
|
182
|
+
*
|
|
183
|
+
* @protected
|
|
184
|
+
* @param {module:engine/view/element~Element} viewElement
|
|
185
|
+
* @param {module:engine/view/downcastwriter~DowncastWriter} writer An instance of the view writer.
|
|
186
|
+
* @param {String} label The element's label. It will be concatenated with the image `alt` attribute if one is present.
|
|
187
|
+
* @returns {module:engine/view/element~Element}
|
|
188
|
+
*/
|
|
189
|
+
toImageWidget( viewElement, writer, label ) {
|
|
190
|
+
writer.setCustomProperty( 'image', true, viewElement );
|
|
191
|
+
|
|
192
|
+
const labelCreator = () => {
|
|
193
|
+
const imgElement = this.findViewImgElement( viewElement );
|
|
194
|
+
const altText = imgElement.getAttribute( 'alt' );
|
|
195
|
+
|
|
196
|
+
return altText ? `${ altText } ${ label }` : label;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
return toWidget( viewElement, writer, { label: labelCreator } );
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Checks if a given view element is an image widget.
|
|
204
|
+
*
|
|
205
|
+
* @protected
|
|
206
|
+
* @param {module:engine/view/element~Element} viewElement
|
|
207
|
+
* @returns {Boolean}
|
|
208
|
+
*/
|
|
209
|
+
isImageWidget( viewElement ) {
|
|
210
|
+
return !!viewElement.getCustomProperty( 'image' ) && isWidget( viewElement );
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Checks if the provided model element is an `image`.
|
|
215
|
+
*
|
|
216
|
+
* @param {module:engine/model/element~Element} modelElement
|
|
217
|
+
* @returns {Boolean}
|
|
218
|
+
*/
|
|
219
|
+
isBlockImage( modelElement ) {
|
|
220
|
+
return !!modelElement && modelElement.is( 'element', 'imageBlock' );
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Checks if the provided model element is an `imageInline`.
|
|
225
|
+
*
|
|
226
|
+
* @param {module:engine/model/element~Element} modelElement
|
|
227
|
+
* @returns {Boolean}
|
|
228
|
+
*/
|
|
229
|
+
isInlineImage( modelElement ) {
|
|
230
|
+
return !!modelElement && modelElement.is( 'element', 'imageInline' );
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Get the view `<img>` from another view element, e.g. a widget (`<figure class="image">`), a link (`<a>`).
|
|
235
|
+
*
|
|
236
|
+
* The `<img>` can be located deep in other elements, so this helper performs a deep tree search.
|
|
237
|
+
*
|
|
238
|
+
* @param {module:engine/view/element~Element} figureView
|
|
239
|
+
* @returns {module:engine/view/element~Element}
|
|
240
|
+
*/
|
|
241
|
+
findViewImgElement( figureView ) {
|
|
242
|
+
if ( this.isInlineImageView( figureView ) ) {
|
|
243
|
+
return figureView;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const editingView = this.editor.editing.view;
|
|
247
|
+
|
|
248
|
+
for ( const { item } of editingView.createRangeIn( figureView ) ) {
|
|
249
|
+
if ( this.isInlineImageView( item ) ) {
|
|
250
|
+
return item;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Checks if image is allowed by schema in optimal insertion parent.
|
|
257
|
+
//
|
|
258
|
+
// @private
|
|
259
|
+
// @param {module:core/editor/editor~Editor} editor
|
|
260
|
+
// @param {module:engine/model/selection~Selection} selection
|
|
261
|
+
// @returns {Boolean}
|
|
262
|
+
function isImageAllowedInParent( editor, selection ) {
|
|
263
|
+
const imageType = determineImageTypeForInsertion( editor, selection );
|
|
264
|
+
|
|
265
|
+
if ( imageType == 'imageBlock' ) {
|
|
266
|
+
const parent = getInsertImageParent( selection, editor.model );
|
|
267
|
+
|
|
268
|
+
if ( editor.model.schema.checkChild( parent, 'imageBlock' ) ) {
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
} else if ( editor.model.schema.checkChild( selection.focus, 'imageInline' ) ) {
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Checks if selection is not placed inside an image (e.g. its caption).
|
|
279
|
+
//
|
|
280
|
+
// @private
|
|
281
|
+
// @param {module:engine/model/selection~Selectable} selection
|
|
282
|
+
// @returns {Boolean}
|
|
283
|
+
function isNotInsideImage( selection ) {
|
|
284
|
+
return [ ...selection.focus.getAncestors() ].every( ancestor => !ancestor.is( 'element', 'imageBlock' ) );
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Returns a node that will be used to insert image with `model.insertContent`.
|
|
288
|
+
//
|
|
289
|
+
// @private
|
|
290
|
+
// @param {module:engine/model/selection~Selection} selection
|
|
291
|
+
// @param {module:engine/model/model~Model} model
|
|
292
|
+
// @returns {module:engine/model/element~Element}
|
|
293
|
+
function getInsertImageParent( selection, model ) {
|
|
294
|
+
const insertionRange = findOptimalInsertionRange( selection, model );
|
|
295
|
+
const parent = insertionRange.start.parent;
|
|
296
|
+
|
|
297
|
+
if ( parent.isEmpty && !parent.is( 'element', '$root' ) ) {
|
|
298
|
+
return parent.parent;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return parent;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Determine image element type name depending on editor config or place of insertion.
|
|
305
|
+
//
|
|
306
|
+
// @private
|
|
307
|
+
// @param {module:core/editor/editor~Editor} editor
|
|
308
|
+
// @param {module:engine/model/selection~Selectable} selectable
|
|
309
|
+
// @param {'imageBlock'|'imageInline'} [imageType] Image element type name. Used to force return of provided element name,
|
|
310
|
+
// but only if there is proper plugin enabled.
|
|
311
|
+
// @returns {'imageBlock'|'imageInline'} imageType
|
|
312
|
+
function determineImageTypeForInsertion( editor, selectable, imageType ) {
|
|
313
|
+
const schema = editor.model.schema;
|
|
314
|
+
const configImageInsertType = editor.config.get( 'image.insert.type' );
|
|
315
|
+
|
|
316
|
+
if ( !editor.plugins.has( 'ImageBlockEditing' ) ) {
|
|
317
|
+
return 'imageInline';
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if ( !editor.plugins.has( 'ImageInlineEditing' ) ) {
|
|
321
|
+
return 'imageBlock';
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if ( imageType ) {
|
|
325
|
+
return imageType;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if ( configImageInsertType === 'inline' ) {
|
|
329
|
+
return 'imageInline';
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if ( configImageInsertType === 'block' ) {
|
|
333
|
+
return 'imageBlock';
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Try to replace the selected widget (e.g. another image).
|
|
337
|
+
if ( selectable.is( 'selection' ) ) {
|
|
338
|
+
return determineImageTypeForInsertionAtSelection( schema, selectable );
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return schema.checkChild( selectable, 'imageInline' ) ? 'imageInline' : 'imageBlock';
|
|
342
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
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/pictureediting
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Plugin } from 'ckeditor5/src/core';
|
|
11
|
+
|
|
12
|
+
import ImageEditing from './image/imageediting';
|
|
13
|
+
import ImageUtils from './imageutils';
|
|
14
|
+
import {
|
|
15
|
+
downcastSourcesAttribute,
|
|
16
|
+
upcastPicture
|
|
17
|
+
} from './image/converters';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* This plugin enables the [`<picture>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture) element support in the editor.
|
|
21
|
+
*
|
|
22
|
+
* * It enables the `sources` model attribute on `imageBlock` and `imageInline` model elements
|
|
23
|
+
* (brought by {@link module:image/imageblock~ImageBlock} and {@link module:image/imageinline~ImageInline}, respectively).
|
|
24
|
+
* * It translates the `sources` model element to the view (also: data) structure that may look as follows:
|
|
25
|
+
*
|
|
26
|
+
* <p>Inline image using picture:
|
|
27
|
+
* <picture>
|
|
28
|
+
* <source media="(min-width: 800px)" srcset="image-large.webp" type="image/webp">
|
|
29
|
+
* <source media="(max-width: 800px)" srcset="image-small.webp" type="image/webp">
|
|
30
|
+
* <!-- Other sources as specified in the "sources" model attribute... -->
|
|
31
|
+
* <img src="image.png" alt="An image using picture" />
|
|
32
|
+
* </picture>
|
|
33
|
+
* </p>
|
|
34
|
+
*
|
|
35
|
+
* <p>Block image using picture:</p>
|
|
36
|
+
* <figure class="image">
|
|
37
|
+
* <picture>
|
|
38
|
+
* <source media="(min-width: 800px)" srcset="image-large.webp" type="image/webp">
|
|
39
|
+
* <source media="(max-width: 800px)" srcset="image-small.webp" type="image/webp">
|
|
40
|
+
* <!-- Other sources as specified in the "sources" model attribute... -->
|
|
41
|
+
* <img src="image.png" alt="An image using picture" />
|
|
42
|
+
* </picture>
|
|
43
|
+
* <figcaption>Caption of the image</figcaption>
|
|
44
|
+
* </figure>
|
|
45
|
+
*
|
|
46
|
+
* **Note:** The value of the `sources` {@glink framework/guides/architecture/editing-engine#changing-the-model model attribute}
|
|
47
|
+
* in both examples equals:
|
|
48
|
+
*
|
|
49
|
+
* [
|
|
50
|
+
* {
|
|
51
|
+
* media: '(min-width: 800px)',
|
|
52
|
+
* srcset: 'image-large.webp',
|
|
53
|
+
* type: 'image/webp'
|
|
54
|
+
* },
|
|
55
|
+
* {
|
|
56
|
+
* media: '(max-width: 800px)',
|
|
57
|
+
* srcset: 'image-small.webp',
|
|
58
|
+
* type: 'image/webp'
|
|
59
|
+
* }
|
|
60
|
+
* ]
|
|
61
|
+
*
|
|
62
|
+
* * It integrates with the {@link module:image/imageupload~ImageUpload} plugin so images uploaded in the editor
|
|
63
|
+
* automatically render using `<picture>` if the {@glink features/images/image-upload/image-upload upload adapter}
|
|
64
|
+
* supports image sources and provides neccessary data.
|
|
65
|
+
*
|
|
66
|
+
* @private
|
|
67
|
+
* @extends module:core/plugin~Plugin
|
|
68
|
+
*/
|
|
69
|
+
export default class PictureEditing extends Plugin {
|
|
70
|
+
/**
|
|
71
|
+
* @inheritDoc
|
|
72
|
+
*/
|
|
73
|
+
static get requires() {
|
|
74
|
+
return [ ImageEditing, ImageUtils ];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @inheritDoc
|
|
79
|
+
*/
|
|
80
|
+
static get pluginName() {
|
|
81
|
+
return 'PictureEditing';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @inheritDoc
|
|
86
|
+
*/
|
|
87
|
+
afterInit() {
|
|
88
|
+
const editor = this.editor;
|
|
89
|
+
|
|
90
|
+
if ( editor.plugins.has( 'ImageBlockEditing' ) ) {
|
|
91
|
+
editor.model.schema.extend( 'imageBlock', {
|
|
92
|
+
allowAttributes: [ 'sources' ]
|
|
93
|
+
} );
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if ( editor.plugins.has( 'ImageInlineEditing' ) ) {
|
|
97
|
+
editor.model.schema.extend( 'imageInline', {
|
|
98
|
+
allowAttributes: [ 'sources' ]
|
|
99
|
+
} );
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
this._setupConversion();
|
|
103
|
+
this._setupImageUploadEditingIntegration();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Configures conversion pipelines to support upcasting and downcasting images using the `<picture>` view element
|
|
108
|
+
* and the model `sources` attribute.
|
|
109
|
+
*
|
|
110
|
+
* @private
|
|
111
|
+
*/
|
|
112
|
+
_setupConversion() {
|
|
113
|
+
const editor = this.editor;
|
|
114
|
+
const conversion = editor.conversion;
|
|
115
|
+
const imageUtils = editor.plugins.get( 'ImageUtils' );
|
|
116
|
+
|
|
117
|
+
conversion.for( 'upcast' ).add( upcastPicture( imageUtils ) );
|
|
118
|
+
conversion.for( 'downcast' ).add( downcastSourcesAttribute( imageUtils ) );
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Makes it possible for uploaded images to get the `sources` model attribute and the `<picture>...</picture>`
|
|
123
|
+
* view structure out-of-the-box if relevant data is provided along the
|
|
124
|
+
* {@link module:image/imageupload/imageuploadediting~ImageUploadEditing#event:uploadComplete} event.
|
|
125
|
+
*
|
|
126
|
+
* @private
|
|
127
|
+
*/
|
|
128
|
+
_setupImageUploadEditingIntegration() {
|
|
129
|
+
const editor = this.editor;
|
|
130
|
+
|
|
131
|
+
if ( !editor.plugins.has( 'ImageUploadEditing' ) ) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
this.listenTo( editor.plugins.get( 'ImageUploadEditing' ), 'uploadComplete', ( evt, { imageElement, data } ) => {
|
|
136
|
+
const sources = data.sources;
|
|
137
|
+
|
|
138
|
+
if ( !sources ) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
editor.model.change( writer => {
|
|
143
|
+
writer.setAttributes( {
|
|
144
|
+
sources
|
|
145
|
+
}, imageElement );
|
|
146
|
+
} );
|
|
147
|
+
} );
|
|
148
|
+
}
|
|
149
|
+
}
|
package/theme/image.css
CHANGED
|
@@ -3,34 +3,114 @@
|
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
.ck-content
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
.ck-content {
|
|
7
|
+
& .image {
|
|
8
|
+
display: table;
|
|
9
|
+
clear: both;
|
|
10
|
+
text-align: center;
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
/* Make sure there is some space between the content and the image. Center image by default. */
|
|
13
|
+
/* The first value should be equal to --ck-spacing-large variable if used in the editor context
|
|
14
|
+
to avoid the content jumping (See https://github.com/ckeditor/ckeditor5/issues/9825). */
|
|
15
|
+
margin: 0.9em auto;
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
/* Make sure the caption will be displayed properly (See: https://github.com/ckeditor/ckeditor5/issues/1870). */
|
|
18
|
+
min-width: 50px;
|
|
19
|
+
|
|
20
|
+
& img {
|
|
21
|
+
/* Prevent unnecessary margins caused by line-height (see #44). */
|
|
22
|
+
display: block;
|
|
23
|
+
|
|
24
|
+
/* Center the image if its width is smaller than the content's width. */
|
|
25
|
+
margin: 0 auto;
|
|
26
|
+
|
|
27
|
+
/* Make sure the image never exceeds the size of the parent container (ckeditor/ckeditor5-ui#67). */
|
|
28
|
+
max-width: 100%;
|
|
29
|
+
|
|
30
|
+
/* Make sure the image is never smaller than the parent container (See: https://github.com/ckeditor/ckeditor5/issues/9300). */
|
|
31
|
+
min-width: 100%
|
|
32
|
+
}
|
|
33
|
+
}
|
|
17
34
|
|
|
18
|
-
|
|
19
|
-
|
|
35
|
+
& .image-inline {
|
|
36
|
+
/*
|
|
37
|
+
* Normally, the .image-inline would have "display: inline-block" and "img { width: 100% }" (to follow the wrapper while resizing).
|
|
38
|
+
* Unfortunately, together with "srcset", it gets automatically stretched up to the width of the editing root.
|
|
39
|
+
* This strange behavior does not happen with inline-flex.
|
|
40
|
+
*/
|
|
41
|
+
display: inline-flex;
|
|
20
42
|
|
|
21
|
-
/*
|
|
43
|
+
/* While being resized, don't allow the image to exceed the width of the editing root. */
|
|
22
44
|
max-width: 100%;
|
|
23
45
|
|
|
24
|
-
/*
|
|
25
|
-
|
|
46
|
+
/* This is required by Safari to resize images in a sensible way. Without this, the browser breaks the ratio. */
|
|
47
|
+
align-items: flex-start;
|
|
48
|
+
|
|
49
|
+
/* When the picture is present it must act as a flex container to let the img resize properly */
|
|
50
|
+
& picture {
|
|
51
|
+
display: flex;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* When the picture is present, it must act like a resizable img. */
|
|
55
|
+
& picture,
|
|
56
|
+
& img {
|
|
57
|
+
/* This is necessary for the img to span the entire .image-inline wrapper and to resize properly. */
|
|
58
|
+
flex-grow: 1;
|
|
59
|
+
flex-shrink: 1;
|
|
60
|
+
|
|
61
|
+
/* Prevents overflowing the editing root boundaries when an inline image is very wide. */
|
|
62
|
+
max-width: 100%;
|
|
63
|
+
}
|
|
26
64
|
}
|
|
27
65
|
}
|
|
28
66
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
67
|
+
.ck.ck-editor__editable {
|
|
68
|
+
/*
|
|
69
|
+
* Inhertit the content styles padding of the <figcaption> in case the integration overrides `text-align: center`
|
|
70
|
+
* of `.image` (e.g. to the left/right). This ensures the placeholder stays at the padding just like the native
|
|
71
|
+
* caret does, and not at the edge of <figcaption>.
|
|
72
|
+
*/
|
|
73
|
+
& .image > figcaption.ck-placeholder::before {
|
|
74
|
+
padding-left: inherit;
|
|
75
|
+
padding-right: inherit;
|
|
76
|
+
|
|
77
|
+
/*
|
|
78
|
+
* Make sure the image caption placeholder doesn't overflow the placeholder area.
|
|
79
|
+
* See https://github.com/ckeditor/ckeditor5/issues/9162.
|
|
80
|
+
*/
|
|
81
|
+
white-space: nowrap;
|
|
82
|
+
overflow: hidden;
|
|
83
|
+
text-overflow: ellipsis;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
/*
|
|
88
|
+
* Make sure the selected inline image always stays on top of its siblings.
|
|
89
|
+
* See https://github.com/ckeditor/ckeditor5/issues/9108.
|
|
90
|
+
*/
|
|
91
|
+
& .image.ck-widget_selected {
|
|
92
|
+
z-index: 1;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
& .image-inline.ck-widget_selected {
|
|
96
|
+
z-index: 1;
|
|
97
|
+
|
|
98
|
+
/*
|
|
99
|
+
* Make sure the native browser selection style is not displayed.
|
|
100
|
+
* Inline image widgets have their own styles for the selected state and
|
|
101
|
+
* leaving this up to the browser is asking for a visual collision.
|
|
102
|
+
*/
|
|
103
|
+
& ::selection {
|
|
104
|
+
display: none;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* The inline image nested in the table should have its original size if not resized.
|
|
109
|
+
See https://github.com/ckeditor/ckeditor5/issues/9117. */
|
|
110
|
+
& td,
|
|
111
|
+
& th {
|
|
112
|
+
& .image-inline img {
|
|
113
|
+
max-width: none;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
36
116
|
}
|
package/theme/imagecaption.css
CHANGED
|
@@ -3,13 +3,35 @@
|
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
:root {
|
|
7
|
+
--ck-color-image-caption-background: hsl(0, 0%, 97%);
|
|
8
|
+
--ck-color-image-caption-text: hsl(0, 0%, 20%);
|
|
9
|
+
--ck-color-image-caption-highligted-background: hsl(52deg 100% 50%);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* Content styles */
|
|
6
13
|
.ck-content .image > figcaption {
|
|
7
14
|
display: table-caption;
|
|
8
15
|
caption-side: bottom;
|
|
9
16
|
word-break: break-word;
|
|
10
|
-
color:
|
|
11
|
-
background-color:
|
|
17
|
+
color: var(--ck-color-image-caption-text);
|
|
18
|
+
background-color: var(--ck-color-image-caption-background);
|
|
12
19
|
padding: .6em;
|
|
13
20
|
font-size: .75em;
|
|
14
21
|
outline-offset: -1px;
|
|
15
22
|
}
|
|
23
|
+
|
|
24
|
+
/* Editing styles */
|
|
25
|
+
.ck.ck-editor__editable .image > figcaption.image__caption_highlighted {
|
|
26
|
+
animation: ck-image-caption-highlight .6s ease-out;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@keyframes ck-image-caption-highlight {
|
|
30
|
+
0% {
|
|
31
|
+
background-color: var(--ck-color-image-caption-highligted-background);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
100% {
|
|
35
|
+
background-color: var(--ck-color-image-caption-background);
|
|
36
|
+
}
|
|
37
|
+
}
|
package/theme/imageresize.css
CHANGED
|
@@ -24,6 +24,17 @@
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
.ck.ck-editor__editable {
|
|
28
|
+
/* The resized inline image nested in the table should respect its parent size.
|
|
29
|
+
See https://github.com/ckeditor/ckeditor5/issues/9117. */
|
|
30
|
+
& td,
|
|
31
|
+
& th {
|
|
32
|
+
& .image-inline.image_resized img {
|
|
33
|
+
max-width: 100%;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
27
38
|
[dir="ltr"] .ck.ck-button.ck-button_with-text.ck-resize-image-button .ck-button__icon {
|
|
28
39
|
margin-right: var(--ck-spacing-standard);
|
|
29
40
|
}
|