@ckeditor/ckeditor5-image 28.0.0 → 30.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +3 -3
  3. package/build/image.js +1 -1
  4. package/build/translations/ar.js +1 -0
  5. package/build/translations/ast.js +1 -0
  6. package/build/translations/az.js +1 -0
  7. package/build/translations/bg.js +1 -0
  8. package/build/translations/cs.js +1 -0
  9. package/build/translations/da.js +1 -0
  10. package/build/translations/de-ch.js +1 -0
  11. package/build/translations/de.js +1 -0
  12. package/build/translations/el.js +1 -0
  13. package/build/translations/en-au.js +1 -0
  14. package/build/translations/en-gb.js +1 -0
  15. package/build/translations/eo.js +1 -0
  16. package/build/translations/es.js +1 -0
  17. package/build/translations/et.js +1 -0
  18. package/build/translations/eu.js +1 -0
  19. package/build/translations/fa.js +1 -0
  20. package/build/translations/fi.js +1 -0
  21. package/build/translations/fr.js +1 -0
  22. package/build/translations/gl.js +1 -0
  23. package/build/translations/he.js +1 -0
  24. package/build/translations/hi.js +1 -0
  25. package/build/translations/hr.js +1 -0
  26. package/build/translations/hu.js +1 -0
  27. package/build/translations/id.js +1 -0
  28. package/build/translations/it.js +1 -0
  29. package/build/translations/ja.js +1 -0
  30. package/build/translations/km.js +1 -0
  31. package/build/translations/kn.js +1 -0
  32. package/build/translations/ko.js +1 -0
  33. package/build/translations/ku.js +1 -0
  34. package/build/translations/lt.js +1 -0
  35. package/build/translations/lv.js +1 -0
  36. package/build/translations/nb.js +1 -0
  37. package/build/translations/ne.js +1 -0
  38. package/build/translations/nl.js +1 -0
  39. package/build/translations/no.js +1 -0
  40. package/build/translations/pl.js +1 -0
  41. package/build/translations/pt-br.js +1 -0
  42. package/build/translations/pt.js +1 -0
  43. package/build/translations/ro.js +1 -0
  44. package/build/translations/ru.js +1 -0
  45. package/build/translations/si.js +1 -0
  46. package/build/translations/sk.js +1 -0
  47. package/build/translations/sq.js +1 -0
  48. package/build/translations/sr-latn.js +1 -0
  49. package/build/translations/sr.js +1 -0
  50. package/build/translations/sv.js +1 -0
  51. package/build/translations/th.js +1 -0
  52. package/build/translations/tk.js +1 -0
  53. package/build/translations/tr.js +1 -0
  54. package/build/translations/ug.js +1 -0
  55. package/build/translations/uk.js +1 -0
  56. package/build/translations/vi.js +1 -0
  57. package/build/translations/zh-cn.js +1 -0
  58. package/build/translations/zh.js +1 -0
  59. package/ckeditor5-metadata.json +233 -0
  60. package/lang/contexts.json +3 -0
  61. package/lang/translations/ar.po +12 -0
  62. package/lang/translations/ast.po +12 -0
  63. package/lang/translations/az.po +12 -0
  64. package/lang/translations/bg.po +12 -0
  65. package/lang/translations/cs.po +12 -0
  66. package/lang/translations/da.po +12 -0
  67. package/lang/translations/de-ch.po +12 -0
  68. package/lang/translations/de.po +15 -3
  69. package/lang/translations/el.po +12 -0
  70. package/lang/translations/en-au.po +12 -0
  71. package/lang/translations/en-gb.po +12 -0
  72. package/lang/translations/en.po +12 -0
  73. package/lang/translations/eo.po +12 -0
  74. package/lang/translations/es.po +12 -0
  75. package/lang/translations/et.po +12 -0
  76. package/lang/translations/eu.po +12 -0
  77. package/lang/translations/fa.po +12 -0
  78. package/lang/translations/fi.po +12 -0
  79. package/lang/translations/fr.po +12 -0
  80. package/lang/translations/gl.po +12 -0
  81. package/lang/translations/he.po +12 -0
  82. package/lang/translations/hi.po +12 -0
  83. package/lang/translations/hr.po +12 -0
  84. package/lang/translations/hu.po +13 -1
  85. package/lang/translations/id.po +21 -9
  86. package/lang/translations/it.po +12 -0
  87. package/lang/translations/ja.po +12 -0
  88. package/lang/translations/km.po +12 -0
  89. package/lang/translations/kn.po +12 -0
  90. package/lang/translations/ko.po +12 -0
  91. package/lang/translations/ku.po +12 -0
  92. package/lang/translations/lt.po +12 -0
  93. package/lang/translations/lv.po +12 -0
  94. package/lang/translations/nb.po +12 -0
  95. package/lang/translations/ne.po +12 -0
  96. package/lang/translations/nl.po +14 -2
  97. package/lang/translations/no.po +12 -0
  98. package/lang/translations/pl.po +20 -8
  99. package/lang/translations/pt-br.po +12 -0
  100. package/lang/translations/pt.po +12 -0
  101. package/lang/translations/ro.po +21 -9
  102. package/lang/translations/ru.po +12 -0
  103. package/lang/translations/si.po +12 -0
  104. package/lang/translations/sk.po +12 -0
  105. package/lang/translations/sq.po +12 -0
  106. package/lang/translations/sr-latn.po +12 -0
  107. package/lang/translations/sr.po +12 -0
  108. package/lang/translations/sv.po +12 -0
  109. package/lang/translations/th.po +12 -0
  110. package/lang/translations/tk.po +12 -0
  111. package/lang/translations/tr.po +12 -0
  112. package/lang/translations/ug.po +12 -0
  113. package/lang/translations/uk.po +12 -0
  114. package/lang/translations/vi.po +12 -0
  115. package/lang/translations/zh-cn.po +12 -0
  116. package/lang/translations/zh.po +12 -0
  117. package/package.json +36 -29
  118. package/src/autoimage.js +9 -4
  119. package/src/image/converters.js +191 -15
  120. package/src/image/imageblockediting.js +182 -0
  121. package/src/image/imageediting.js +13 -70
  122. package/src/image/imageinlineediting.js +207 -0
  123. package/src/image/imagetypecommand.js +105 -0
  124. package/src/image/insertimagecommand.js +77 -10
  125. package/src/image/ui/utils.js +5 -4
  126. package/src/image/utils.js +65 -121
  127. package/src/image.js +7 -19
  128. package/src/imageblock.js +46 -0
  129. package/src/imagecaption/imagecaptionediting.js +183 -227
  130. package/src/imagecaption/imagecaptionui.js +78 -0
  131. package/src/imagecaption/toggleimagecaptioncommand.js +165 -0
  132. package/src/imagecaption/utils.js +25 -40
  133. package/src/imagecaption.js +3 -2
  134. package/src/imageinline.js +46 -0
  135. package/src/imageinsert/imageinsertui.js +5 -6
  136. package/src/imageinsert.js +16 -4
  137. package/src/imageresize/imageresizebuttons.js +1 -1
  138. package/src/imageresize/imageresizeediting.js +21 -8
  139. package/src/imageresize/imageresizehandles.js +30 -8
  140. package/src/imageresize/resizeimagecommand.js +8 -5
  141. package/src/imagestyle/converters.js +26 -17
  142. package/src/imagestyle/imagestylecommand.js +73 -33
  143. package/src/imagestyle/imagestyleediting.js +113 -52
  144. package/src/imagestyle/imagestyleui.js +197 -31
  145. package/src/imagestyle/utils.js +300 -85
  146. package/src/imagestyle.js +218 -47
  147. package/src/imagetextalternative/imagetextalternativecommand.js +10 -7
  148. package/src/imagetextalternative/imagetextalternativeediting.js +9 -1
  149. package/src/imagetextalternative/imagetextalternativeui.js +2 -2
  150. package/src/imagetextalternative.js +1 -1
  151. package/src/imagetoolbar.js +33 -11
  152. package/src/imageupload/imageuploadediting.js +90 -30
  153. package/src/imageupload/imageuploadprogress.js +17 -9
  154. package/src/imageupload/imageuploadui.js +1 -1
  155. package/src/imageupload/uploadimagecommand.js +50 -24
  156. package/src/imageupload/utils.js +3 -2
  157. package/src/imageupload.js +1 -1
  158. package/src/imageutils.js +342 -0
  159. package/src/pictureediting.js +149 -0
  160. package/theme/image.css +101 -21
  161. package/theme/imagecaption.css +24 -2
  162. package/theme/imageresize.css +11 -0
  163. package/theme/imagestyle.css +76 -0
  164. package/theme/imageuploadicon.css +8 -2
  165. package/theme/imageuploadprogress.css +12 -8
  166. package/CHANGELOG.md +0 -423
  167. package/build/image.js.map +0 -1
@@ -0,0 +1,207 @@
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/imageinlineediting
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
+ } from './converters';
18
+
19
+ import ImageEditing from './imageediting';
20
+ import ImageTypeCommand from './imagetypecommand';
21
+ import ImageUtils from '../imageutils';
22
+ import {
23
+ getImgViewElementMatcher,
24
+ createImageViewElement,
25
+ determineImageTypeForInsertionAtSelection
26
+ } from '../image/utils';
27
+
28
+ /**
29
+ * The image inline plugin.
30
+ *
31
+ * It registers:
32
+ *
33
+ * * `<imageInline>` as an inline element in the document schema, and allows `alt`, `src` and `srcset` attributes.
34
+ * * converters for editing and data pipelines.
35
+ * * {@link module:image/image/imagetypecommand~ImageTypeCommand `'imageTypeInline'`} command that converts block images into
36
+ * inline images.
37
+ *
38
+ * @extends module:core/plugin~Plugin
39
+ */
40
+ export default class ImageInlineEditing extends Plugin {
41
+ /**
42
+ * @inheritDoc
43
+ */
44
+ static get requires() {
45
+ return [ ImageEditing, ImageUtils, ClipboardPipeline ];
46
+ }
47
+
48
+ /**
49
+ * @inheritDoc
50
+ */
51
+ static get pluginName() {
52
+ return 'ImageInlineEditing';
53
+ }
54
+
55
+ /**
56
+ * @inheritDoc
57
+ */
58
+ init() {
59
+ const editor = this.editor;
60
+ const schema = editor.model.schema;
61
+
62
+ // Converters 'alt' and 'srcset' are added in 'ImageEditing' plugin.
63
+ schema.register( 'imageInline', {
64
+ isObject: true,
65
+ isInline: true,
66
+ allowWhere: '$text',
67
+ allowAttributes: [ 'alt', 'src', 'srcset' ]
68
+ } );
69
+
70
+ // Disallow inline images in captions (for now). This is the best spot to do that because
71
+ // independent packages can introduce captions (ImageCaption, TableCaption, etc.) so better this
72
+ // be future-proof.
73
+ schema.addChildCheck( ( context, childDefinition ) => {
74
+ if ( context.endsWith( 'caption' ) && childDefinition.name === 'imageInline' ) {
75
+ return false;
76
+ }
77
+ } );
78
+
79
+ this._setupConversion();
80
+
81
+ if ( editor.plugins.has( 'ImageBlockEditing' ) ) {
82
+ editor.commands.add( 'imageTypeInline', new ImageTypeCommand( this.editor, 'imageInline' ) );
83
+
84
+ this._setupClipboardIntegration();
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Configures conversion pipelines to support upcasting and downcasting
90
+ * inline images (inline image widgets) and their attributes.
91
+ *
92
+ * @private
93
+ */
94
+ _setupConversion() {
95
+ const editor = this.editor;
96
+ const t = editor.t;
97
+ const conversion = editor.conversion;
98
+ const imageUtils = editor.plugins.get( 'ImageUtils' );
99
+
100
+ conversion.for( 'dataDowncast' )
101
+ .elementToElement( {
102
+ model: 'imageInline',
103
+ view: ( modelElement, { writer } ) => writer.createEmptyElement( 'img' )
104
+ } );
105
+
106
+ conversion.for( 'editingDowncast' )
107
+ .elementToElement( {
108
+ model: 'imageInline',
109
+ view: ( modelElement, { writer } ) => imageUtils.toImageWidget(
110
+ createImageViewElement( writer, 'imageInline' ), writer, t( 'image widget' )
111
+ )
112
+ } );
113
+
114
+ conversion.for( 'downcast' )
115
+ .add( downcastImageAttribute( imageUtils, 'imageInline', 'src' ) )
116
+ .add( downcastImageAttribute( imageUtils, 'imageInline', 'alt' ) )
117
+ .add( downcastSrcsetAttribute( imageUtils, 'imageInline' ) );
118
+
119
+ // More image related upcasts are in 'ImageEditing' plugin.
120
+ conversion.for( 'upcast' )
121
+ .elementToElement( {
122
+ view: getImgViewElementMatcher( editor, 'imageInline' ),
123
+ model: ( viewImage, { writer } ) => writer.createElement(
124
+ 'imageInline',
125
+ viewImage.hasAttribute( 'src' ) ? { src: viewImage.getAttribute( 'src' ) } : null
126
+ )
127
+ } );
128
+ }
129
+
130
+ /**
131
+ * Integrates the plugin with the clipboard pipeline.
132
+ *
133
+ * Idea is that the feature should recognize the user's intent when an **block** image is
134
+ * pasted or dropped. If such an image is pasted/dropped into a non-empty block
135
+ * (e.g. a paragraph with some text) it gets converted into an inline image on the fly.
136
+ *
137
+ * We assume this is the user's intent if they decided to put their image there.
138
+ *
139
+ * **Note**: If a block image has a caption, it will not be converted to an inline image
140
+ * to avoid the confusion. Captions are added on purpose and they should never be lost
141
+ * in the clipboard pipeline.
142
+ *
143
+ * See the `ImageBlockEditing` for the similar integration that works in the opposite direction.
144
+ *
145
+ * @private
146
+ */
147
+ _setupClipboardIntegration() {
148
+ const editor = this.editor;
149
+ const model = editor.model;
150
+ const editingView = editor.editing.view;
151
+ const imageUtils = editor.plugins.get( 'ImageUtils' );
152
+
153
+ this.listenTo( editor.plugins.get( 'ClipboardPipeline' ), 'inputTransformation', ( evt, data ) => {
154
+ const docFragmentChildren = Array.from( data.content.getChildren() );
155
+ let modelRange;
156
+
157
+ // Make sure only <figure class="image"></figure> elements are dropped or pasted. Otherwise, if there some other HTML
158
+ // mixed up, this should be handled as a regular paste.
159
+ if ( !docFragmentChildren.every( imageUtils.isBlockImageView ) ) {
160
+ return;
161
+ }
162
+
163
+ // When drag and dropping, data.targetRanges specifies where to drop because
164
+ // this is usually a different place than the current model selection (the user
165
+ // uses a drop marker to specify the drop location).
166
+ if ( data.targetRanges ) {
167
+ modelRange = editor.editing.mapper.toModelRange( data.targetRanges[ 0 ] );
168
+ }
169
+ // Pasting, however, always occurs at the current model selection.
170
+ else {
171
+ modelRange = model.document.selection.getFirstRange();
172
+ }
173
+
174
+ const selection = model.createSelection( modelRange );
175
+
176
+ // Convert block images into inline images only when pasting or dropping into non-empty blocks
177
+ // and when the block is not an object (e.g. pasting to replace another widget).
178
+ if ( determineImageTypeForInsertionAtSelection( model.schema, selection ) === 'imageInline' ) {
179
+ const writer = new UpcastWriter( editingView.document );
180
+
181
+ // Unwrap <figure class="image"><img .../></figure> -> <img ... />
182
+ // but <figure class="image"><img .../><figcaption>...</figcaption></figure> -> stays the same
183
+ const inlineViewImages = docFragmentChildren.map( blockViewImage => {
184
+ // If there's just one child, it can be either <img /> or <a><img></a>.
185
+ // If there are other children than <img>, this means that the block image
186
+ // has a caption or some other features and this kind of image should be
187
+ // pasted/dropped without modifications.
188
+ if ( blockViewImage.childCount === 1 ) {
189
+ // Pass the attributes which are present only in the <figure> to the <img>
190
+ // (e.g. the style="width:10%" attribute applied by the ImageResize plugin).
191
+ Array.from( blockViewImage.getAttributes() )
192
+ .forEach( attribute => writer.setAttribute(
193
+ ...attribute,
194
+ imageUtils.findViewImgElement( blockViewImage )
195
+ ) );
196
+
197
+ return blockViewImage.getChild( 0 );
198
+ } else {
199
+ return blockViewImage;
200
+ }
201
+ } );
202
+
203
+ data.content = writer.createDocumentFragment( inlineViewImages );
204
+ }
205
+ } );
206
+ }
207
+ }
@@ -0,0 +1,105 @@
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/imagetypecommand
8
+ */
9
+
10
+ import { Command } from 'ckeditor5/src/core';
11
+
12
+ /**
13
+ * The image type command. It changes the type of a selected image, depending on the configuration.
14
+ *
15
+ * @extends module:core/command~Command
16
+ */
17
+ export default class ImageTypeCommand extends Command {
18
+ /**
19
+ * @inheritDoc
20
+ *
21
+ * @param {module:core/editor/editor~Editor} editor
22
+ * @param {'imageBlock'|'imageInline'} modelElementName Model element name the command converts to.
23
+ */
24
+ constructor( editor, modelElementName ) {
25
+ super( editor );
26
+
27
+ /**
28
+ * Model element name the command converts to.
29
+ *
30
+ * @readonly
31
+ * @private
32
+ * @member {'imageBlock'|'imageInline'}
33
+ */
34
+ this._modelElementName = modelElementName;
35
+ }
36
+
37
+ /**
38
+ * @inheritDoc
39
+ */
40
+ refresh() {
41
+ const editor = this.editor;
42
+ const imageUtils = editor.plugins.get( 'ImageUtils' );
43
+ const element = imageUtils.getClosestSelectedImageElement( this.editor.model.document.selection );
44
+
45
+ if ( this._modelElementName === 'imageBlock' ) {
46
+ this.isEnabled = imageUtils.isInlineImage( element );
47
+ } else {
48
+ this.isEnabled = imageUtils.isBlockImage( element );
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Executes the command and changes the type of a selected image.
54
+ *
55
+ * @fires execute
56
+ * @returns {Object|null} An object containing references to old and new model image elements
57
+ * (for before and after the change) so external integrations can hook into the decorated
58
+ * `execute` event and handle this change. `null` if the type change failed.
59
+ */
60
+ execute() {
61
+ const editor = this.editor;
62
+ const model = this.editor.model;
63
+ const imageUtils = editor.plugins.get( 'ImageUtils' );
64
+ const oldElement = imageUtils.getClosestSelectedImageElement( model.document.selection );
65
+ const attributes = Object.fromEntries( oldElement.getAttributes() );
66
+
67
+ // Don't change image type if "src" is missing (a broken image), unless there's "uploadId" set.
68
+ // This state may happen during image upload (before it finishes) and it should be possible to change type
69
+ // of the image in the meantime.
70
+ if ( !attributes.src && !attributes.uploadId ) {
71
+ return null;
72
+ }
73
+
74
+ return model.change( writer => {
75
+ // Get all markers that contain the old image element.
76
+ const markers = Array.from( model.markers )
77
+ .filter( marker => marker.getRange().containsItem( oldElement ) );
78
+
79
+ const newElement = imageUtils.insertImage( attributes, model.createSelection( oldElement, 'on' ), this._modelElementName );
80
+
81
+ if ( !newElement ) {
82
+ return null;
83
+ }
84
+
85
+ const newElementRange = writer.createRangeOn( newElement );
86
+
87
+ // Expand the previously intersecting markers' ranges to include the new image element.
88
+ for ( const marker of markers ) {
89
+ const markerRange = marker.getRange();
90
+
91
+ // Join the survived part of the old marker range with the new element range
92
+ // (loosely because there could be some new paragraph or the existing one might got split).
93
+ const range = markerRange.root.rootName != '$graveyard' ?
94
+ markerRange.getJoined( newElementRange, true ) : newElementRange;
95
+
96
+ writer.updateMarker( marker, { range } );
97
+ }
98
+
99
+ return {
100
+ oldElement,
101
+ newElement
102
+ };
103
+ } );
104
+ }
105
+ }
@@ -4,9 +4,7 @@
4
4
  */
5
5
 
6
6
  import { Command } from 'ckeditor5/src/core';
7
- import { toArray } from 'ckeditor5/src/utils';
8
-
9
- import { insertImage, isImageAllowed } from './utils';
7
+ import { logWarning, toArray } from 'ckeditor5/src/utils';
10
8
 
11
9
  /**
12
10
  * @module image/image/insertimagecommand
@@ -19,7 +17,7 @@ import { insertImage, isImageAllowed } from './utils';
19
17
  * and it is also available via aliased `imageInsert` name.
20
18
  *
21
19
  * In order to insert an image at the current selection position
22
- * (according to the {@link module:widget/utils~findOptimalInsertionPosition} algorithm),
20
+ * (according to the {@link module:widget/utils~findOptimalInsertionRange} algorithm),
23
21
  * execute the command and specify the image source:
24
22
  *
25
23
  * editor.execute( 'insertImage', { source: 'http://url.to.the/image' } );
@@ -33,14 +31,56 @@ import { insertImage, isImageAllowed } from './utils';
33
31
  * ]
34
32
  * } );
35
33
  *
34
+ * If you want to take the full control over the process, you can specify individual model attributes:
35
+ *
36
+ * editor.execute( 'insertImage', {
37
+ * source: [
38
+ * { src: 'path/to/image.jpg', alt: 'First alt text' },
39
+ * { src: 'path/to/other-image.jpg', alt: 'Second alt text', customAttribute: 'My attribute value' }
40
+ * ]
41
+ * } );
42
+ *
36
43
  * @extends module:core/command~Command
37
44
  */
38
45
  export default class InsertImageCommand extends Command {
46
+ /**
47
+ * @inheritDoc
48
+ */
49
+ constructor( editor ) {
50
+ super( editor );
51
+
52
+ const configImageInsertType = editor.config.get( 'image.insert.type' );
53
+
54
+ if ( !editor.plugins.has( 'ImageBlockEditing' ) ) {
55
+ if ( configImageInsertType === 'block' ) {
56
+ /**
57
+ * The {@link module:image/imageblock~ImageBlock} plugin must be enabled to allow inserting block images. See
58
+ * {@link module:image/imageinsert~ImageInsertConfig#type} to learn more.
59
+ *
60
+ * @error image-block-plugin-required
61
+ */
62
+ logWarning( 'image-block-plugin-required' );
63
+ }
64
+ }
65
+
66
+ if ( !editor.plugins.has( 'ImageInlineEditing' ) ) {
67
+ if ( configImageInsertType === 'inline' ) {
68
+ /**
69
+ * The {@link module:image/imageinline~ImageInline} plugin must be enabled to allow inserting inline images. See
70
+ * {@link module:image/imageinsert~ImageInsertConfig#type} to learn more.
71
+ *
72
+ * @error image-inline-plugin-required
73
+ */
74
+ logWarning( 'image-inline-plugin-required' );
75
+ }
76
+ }
77
+ }
78
+
39
79
  /**
40
80
  * @inheritDoc
41
81
  */
42
82
  refresh() {
43
- this.isEnabled = isImageAllowed( this.editor.model );
83
+ this.isEnabled = this.editor.plugins.get( 'ImageUtils' ).isImageAllowed();
44
84
  }
45
85
 
46
86
  /**
@@ -48,13 +88,40 @@ export default class InsertImageCommand extends Command {
48
88
  *
49
89
  * @fires execute
50
90
  * @param {Object} options Options for the executed command.
51
- * @param {String|Array.<String>} options.source The image source or an array of image sources to insert.
91
+ * @param {String|Array.<String>|Array.<Object>} options.source The image source or an array of image sources to insert.
92
+ * See the documentation of the command to learn more about accepted formats.
52
93
  */
53
94
  execute( options ) {
54
- const model = this.editor.model;
95
+ const sourceDefinitions = toArray( options.source );
96
+ const selection = this.editor.model.document.selection;
97
+ const imageUtils = this.editor.plugins.get( 'ImageUtils' );
55
98
 
56
- for ( const src of toArray( options.source ) ) {
57
- insertImage( model, { src } );
58
- }
99
+ // In case of multiple images, each image (starting from the 2nd) will be inserted at a position that
100
+ // follows the previous one. That will move the selection and, to stay on the safe side and make sure
101
+ // all images inherit the same selection attributes, they are collected beforehand.
102
+ //
103
+ // Applying these attributes ensures, for instance, that inserting an (inline) image into a link does
104
+ // not split that link but preserves its continuity.
105
+ //
106
+ // Note: Selection attributes that do not make sense for images will be filtered out by insertImage() anyway.
107
+ const selectionAttributes = Object.fromEntries( selection.getAttributes() );
108
+
109
+ sourceDefinitions.forEach( ( sourceDefinition, index ) => {
110
+ const selectedElement = selection.getSelectedElement();
111
+
112
+ if ( typeof sourceDefinition === 'string' ) {
113
+ sourceDefinition = { src: sourceDefinition };
114
+ }
115
+
116
+ // Inserting of an inline image replace the selected element and make a selection on the inserted image.
117
+ // Therefore inserting multiple inline images requires creating position after each element.
118
+ if ( index && selectedElement && imageUtils.isImage( selectedElement ) ) {
119
+ const position = this.editor.model.createPositionAfter( selectedElement );
120
+
121
+ imageUtils.insertImage( { ...sourceDefinition, ...selectionAttributes }, position );
122
+ } else {
123
+ imageUtils.insertImage( { ...sourceDefinition, ...selectionAttributes } );
124
+ }
125
+ } );
59
126
  }
60
127
  }
@@ -8,7 +8,6 @@
8
8
  */
9
9
 
10
10
  import { BalloonPanelView } from 'ckeditor5/src/ui';
11
- import { getSelectedImageWidget } from '../utils';
12
11
 
13
12
  /**
14
13
  * A helper utility that positions the
@@ -20,7 +19,7 @@ import { getSelectedImageWidget } from '../utils';
20
19
  export function repositionContextualBalloon( editor ) {
21
20
  const balloon = editor.plugins.get( 'ContextualBalloon' );
22
21
 
23
- if ( getSelectedImageWidget( editor.editing.view.document.selection ) ) {
22
+ if ( editor.plugins.get( 'ImageUtils' ).getClosestSelectedImageWidget( editor.editing.view.document.selection ) ) {
24
23
  const position = getBalloonPositionData( editor );
25
24
 
26
25
  balloon.updatePosition( position );
@@ -38,16 +37,18 @@ export function repositionContextualBalloon( editor ) {
38
37
  export function getBalloonPositionData( editor ) {
39
38
  const editingView = editor.editing.view;
40
39
  const defaultPositions = BalloonPanelView.defaultPositions;
40
+ const imageUtils = editor.plugins.get( 'ImageUtils' );
41
41
 
42
42
  return {
43
- target: editingView.domConverter.viewToDom( editingView.document.selection.getSelectedElement() ),
43
+ target: editingView.domConverter.viewToDom( imageUtils.getClosestSelectedImageWidget( editingView.document.selection ) ),
44
44
  positions: [
45
45
  defaultPositions.northArrowSouth,
46
46
  defaultPositions.northArrowSouthWest,
47
47
  defaultPositions.northArrowSouthEast,
48
48
  defaultPositions.southArrowNorth,
49
49
  defaultPositions.southArrowNorthWest,
50
- defaultPositions.southArrowNorthEast
50
+ defaultPositions.southArrowNorthEast,
51
+ defaultPositions.viewportStickyNorth
51
52
  ]
52
53
  };
53
54
  }