@ckeditor/ckeditor5-image 39.0.2 → 40.1.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 (40) hide show
  1. package/LICENSE.md +3 -3
  2. package/build/image.js +1 -1
  3. package/build/translations/pt-br.js +1 -1
  4. package/ckeditor5-metadata.json +12 -0
  5. package/lang/translations/pt-br.po +1 -1
  6. package/package.json +3 -3
  7. package/src/augmentation.d.ts +2 -1
  8. package/src/image/converters.d.ts +1 -1
  9. package/src/image/converters.js +5 -15
  10. package/src/image/imageblockediting.d.ts +5 -1
  11. package/src/image/imageblockediting.js +19 -2
  12. package/src/image/imageediting.js +1 -12
  13. package/src/image/imageinlineediting.d.ts +5 -1
  14. package/src/image/imageinlineediting.js +19 -2
  15. package/src/image/imageplaceholder.d.ts +39 -0
  16. package/src/image/imageplaceholder.js +113 -0
  17. package/src/image/imagetypecommand.d.ts +5 -1
  18. package/src/image/imagetypecommand.js +5 -2
  19. package/src/image/replaceimagesourcecommand.d.ts +18 -1
  20. package/src/image/replaceimagesourcecommand.js +33 -2
  21. package/src/image/utils.d.ts +13 -1
  22. package/src/image/utils.js +21 -0
  23. package/src/imageconfig.d.ts +8 -5
  24. package/src/imageresize/imageresizeediting.js +61 -10
  25. package/src/imageresize/imageresizehandles.d.ts +2 -1
  26. package/src/imageresize/imageresizehandles.js +10 -3
  27. package/src/imageresize/resizeimagecommand.js +5 -3
  28. package/src/imagesizeattributes.d.ts +34 -0
  29. package/src/imagesizeattributes.js +142 -0
  30. package/src/imagestyle/imagestylecommand.d.ts +3 -0
  31. package/src/imagestyle/imagestylecommand.js +7 -1
  32. package/src/imagetextalternative/imagetextalternativeui.js +1 -1
  33. package/src/imageupload/imageuploadediting.js +10 -8
  34. package/src/imageutils.d.ts +25 -2
  35. package/src/imageutils.js +64 -6
  36. package/src/index.d.ts +1 -0
  37. package/src/index.js +1 -0
  38. package/theme/image.css +38 -11
  39. package/theme/imageplaceholder.css +10 -0
  40. package/theme/imageresize.css +5 -0
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { Plugin } from 'ckeditor5/src/core';
6
6
  import { WidgetResize } from 'ckeditor5/src/widget';
7
+ import ImageUtils from '../imageutils';
7
8
  import ImageLoadObserver from '../image/imageloadobserver';
8
9
  const RESIZABLE_IMAGES_CSS_SELECTOR = 'figure.image.ck-widget > img,' +
9
10
  'figure.image.ck-widget > picture > img,' +
@@ -11,7 +12,6 @@ const RESIZABLE_IMAGES_CSS_SELECTOR = 'figure.image.ck-widget > img,' +
11
12
  'figure.image.ck-widget > a > picture > img,' +
12
13
  'span.image-inline.ck-widget > img,' +
13
14
  'span.image-inline.ck-widget > picture > img';
14
- const IMAGE_WIDGETS_CLASSES_MATCH_REGEXP = /(image|image-inline)/;
15
15
  const RESIZED_IMAGE_CLASS = 'image_resized';
16
16
  /**
17
17
  * The image resize by handles feature.
@@ -24,7 +24,7 @@ export default class ImageResizeHandles extends Plugin {
24
24
  * @inheritDoc
25
25
  */
26
26
  static get requires() {
27
- return [WidgetResize];
27
+ return [WidgetResize, ImageUtils];
28
28
  }
29
29
  /**
30
30
  * @inheritDoc
@@ -46,6 +46,7 @@ export default class ImageResizeHandles extends Plugin {
46
46
  _setupResizerCreator() {
47
47
  const editor = this.editor;
48
48
  const editingView = editor.editing.view;
49
+ const imageUtils = editor.plugins.get('ImageUtils');
49
50
  editingView.addObserver(ImageLoadObserver);
50
51
  this.listenTo(editingView.document, 'imageLoaded', (evt, domEvent) => {
51
52
  // The resizer must be attached only to images loaded by the `ImageInsert`, `ImageUpload` or `LinkImage` plugins.
@@ -54,7 +55,7 @@ export default class ImageResizeHandles extends Plugin {
54
55
  }
55
56
  const domConverter = editor.editing.view.domConverter;
56
57
  const imageView = domConverter.domToView(domEvent.target);
57
- const widgetView = imageView.findAncestor({ classes: IMAGE_WIDGETS_CLASSES_MATCH_REGEXP });
58
+ const widgetView = imageUtils.getImageWidgetFromImageView(imageView);
58
59
  let resizer = this.editor.plugins.get(WidgetResize).getResizerByViewElement(widgetView);
59
60
  if (resizer) {
60
61
  // There are rare cases when the image will be triggered multiple times for the same widget, e.g. when
@@ -100,6 +101,12 @@ export default class ImageResizeHandles extends Plugin {
100
101
  writer.addClass(RESIZED_IMAGE_CLASS, widgetView);
101
102
  });
102
103
  }
104
+ const target = imageModel.name === 'imageInline' ? imageView : widgetView;
105
+ if (target.getStyle('height')) {
106
+ editingView.change(writer => {
107
+ writer.removeStyle('height', target);
108
+ });
109
+ }
103
110
  });
104
111
  resizer.bind('isEnabled').to(this);
105
112
  });
@@ -18,12 +18,12 @@ export default class ResizeImageCommand extends Command {
18
18
  const imageUtils = editor.plugins.get('ImageUtils');
19
19
  const element = imageUtils.getClosestSelectedImageElement(editor.model.document.selection);
20
20
  this.isEnabled = !!element;
21
- if (!element || !element.hasAttribute('width')) {
21
+ if (!element || !element.hasAttribute('resizedWidth')) {
22
22
  this.value = null;
23
23
  }
24
24
  else {
25
25
  this.value = {
26
- width: element.getAttribute('width'),
26
+ width: element.getAttribute('resizedWidth'),
27
27
  height: null
28
28
  };
29
29
  }
@@ -54,7 +54,9 @@ export default class ResizeImageCommand extends Command {
54
54
  };
55
55
  if (imageElement) {
56
56
  model.change(writer => {
57
- writer.setAttribute('width', options.width, imageElement);
57
+ writer.setAttribute('resizedWidth', options.width, imageElement);
58
+ writer.removeAttribute('resizedHeight', imageElement);
59
+ imageUtils.setImageNaturalSizeAttributes(imageElement);
58
60
  });
59
61
  }
60
62
  }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * @module image/imagesizeattributes
7
+ */
8
+ import { Plugin } from 'ckeditor5/src/core';
9
+ import ImageUtils from './imageutils';
10
+ /**
11
+ * This plugin enables `width` and `height` attributes in inline and block image elements.
12
+ */
13
+ export default class ImageSizeAttributes extends Plugin {
14
+ /**
15
+ * @inheritDoc
16
+ */
17
+ static get requires(): readonly [typeof ImageUtils];
18
+ /**
19
+ * @inheritDoc
20
+ */
21
+ static get pluginName(): "ImageSizeAttributes";
22
+ /**
23
+ * @inheritDoc
24
+ */
25
+ afterInit(): void;
26
+ /**
27
+ * Registers the `width` and `height` attributes for inline and block images.
28
+ */
29
+ private _registerSchema;
30
+ /**
31
+ * Registers converters for `width` and `height` attributes.
32
+ */
33
+ private _registerConverters;
34
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * @module image/imagesizeattributes
7
+ */
8
+ import { Plugin } from 'ckeditor5/src/core';
9
+ import ImageUtils from './imageutils';
10
+ import { widthAndHeightStylesAreBothSet, getSizeValueIfInPx } from './image/utils';
11
+ /**
12
+ * This plugin enables `width` and `height` attributes in inline and block image elements.
13
+ */
14
+ export default class ImageSizeAttributes extends Plugin {
15
+ /**
16
+ * @inheritDoc
17
+ */
18
+ static get requires() {
19
+ return [ImageUtils];
20
+ }
21
+ /**
22
+ * @inheritDoc
23
+ */
24
+ static get pluginName() {
25
+ return 'ImageSizeAttributes';
26
+ }
27
+ /**
28
+ * @inheritDoc
29
+ */
30
+ afterInit() {
31
+ this._registerSchema();
32
+ this._registerConverters('imageBlock');
33
+ this._registerConverters('imageInline');
34
+ }
35
+ /**
36
+ * Registers the `width` and `height` attributes for inline and block images.
37
+ */
38
+ _registerSchema() {
39
+ if (this.editor.plugins.has('ImageBlockEditing')) {
40
+ this.editor.model.schema.extend('imageBlock', { allowAttributes: ['width', 'height'] });
41
+ }
42
+ if (this.editor.plugins.has('ImageInlineEditing')) {
43
+ this.editor.model.schema.extend('imageInline', { allowAttributes: ['width', 'height'] });
44
+ }
45
+ }
46
+ /**
47
+ * Registers converters for `width` and `height` attributes.
48
+ */
49
+ _registerConverters(imageType) {
50
+ const editor = this.editor;
51
+ const imageUtils = editor.plugins.get('ImageUtils');
52
+ const viewElementName = imageType === 'imageBlock' ? 'figure' : 'img';
53
+ editor.conversion.for('upcast')
54
+ .attributeToAttribute({
55
+ view: {
56
+ name: viewElementName,
57
+ styles: {
58
+ width: /.+/
59
+ }
60
+ },
61
+ model: {
62
+ key: 'width',
63
+ value: (viewElement) => {
64
+ if (widthAndHeightStylesAreBothSet(viewElement)) {
65
+ return getSizeValueIfInPx(viewElement.getStyle('width'));
66
+ }
67
+ return null;
68
+ }
69
+ }
70
+ })
71
+ .attributeToAttribute({
72
+ view: {
73
+ name: viewElementName,
74
+ key: 'width'
75
+ },
76
+ model: 'width'
77
+ })
78
+ .attributeToAttribute({
79
+ view: {
80
+ name: viewElementName,
81
+ styles: {
82
+ height: /.+/
83
+ }
84
+ },
85
+ model: {
86
+ key: 'height',
87
+ value: (viewElement) => {
88
+ if (widthAndHeightStylesAreBothSet(viewElement)) {
89
+ return getSizeValueIfInPx(viewElement.getStyle('height'));
90
+ }
91
+ return null;
92
+ }
93
+ }
94
+ })
95
+ .attributeToAttribute({
96
+ view: {
97
+ name: viewElementName,
98
+ key: 'height'
99
+ },
100
+ model: 'height'
101
+ });
102
+ // Dedicated converters to propagate attributes to the <img> element.
103
+ editor.conversion.for('editingDowncast').add(dispatcher => {
104
+ attachDowncastConverter(dispatcher, 'width', 'width', true);
105
+ attachDowncastConverter(dispatcher, 'height', 'height', true);
106
+ });
107
+ editor.conversion.for('dataDowncast').add(dispatcher => {
108
+ attachDowncastConverter(dispatcher, 'width', 'width', false);
109
+ attachDowncastConverter(dispatcher, 'height', 'height', false);
110
+ });
111
+ function attachDowncastConverter(dispatcher, modelAttributeName, viewAttributeName, setRatioForInlineImage) {
112
+ dispatcher.on(`attribute:${modelAttributeName}:${imageType}`, (evt, data, conversionApi) => {
113
+ if (!conversionApi.consumable.consume(data.item, evt.name)) {
114
+ return;
115
+ }
116
+ const viewWriter = conversionApi.writer;
117
+ const viewElement = conversionApi.mapper.toViewElement(data.item);
118
+ const img = imageUtils.findViewImgElement(viewElement);
119
+ if (data.attributeNewValue !== null) {
120
+ viewWriter.setAttribute(viewAttributeName, data.attributeNewValue, img);
121
+ }
122
+ else {
123
+ viewWriter.removeAttribute(viewAttributeName, img);
124
+ }
125
+ // Do not set aspect-ratio for pictures. See https://github.com/ckeditor/ckeditor5/issues/14579.
126
+ if (data.item.hasAttribute('sources')) {
127
+ return;
128
+ }
129
+ const isResized = data.item.hasAttribute('resizedWidth');
130
+ // Do not set aspect ratio for inline images which are not resized (data pipeline).
131
+ if (imageType === 'imageInline' && !isResized && !setRatioForInlineImage) {
132
+ return;
133
+ }
134
+ const width = data.item.getAttribute('width');
135
+ const height = data.item.getAttribute('height');
136
+ if (width && height) {
137
+ viewWriter.setStyle('aspect-ratio', `${width}/${height}`, img);
138
+ }
139
+ });
140
+ }
141
+ }
142
+ }
@@ -50,10 +50,13 @@ export default class ImageStyleCommand extends Command {
50
50
  * configuration for the style option.
51
51
  *
52
52
  * @param options.value The name of the style (as configured in {@link module:image/imageconfig~ImageStyleConfig#options}).
53
+ * @param options.setImageSizes Specifies whether the image `width` and `height` attributes should be set automatically.
54
+ * The default is `true`.
53
55
  * @fires execute
54
56
  */
55
57
  execute(options?: {
56
58
  value?: string;
59
+ setImageSizes?: boolean;
57
60
  }): void;
58
61
  /**
59
62
  * Returns `true` if requested style change would trigger the image type change.
@@ -63,6 +63,8 @@ export default class ImageStyleCommand extends Command {
63
63
  * configuration for the style option.
64
64
  *
65
65
  * @param options.value The name of the style (as configured in {@link module:image/imageconfig~ImageStyleConfig#options}).
66
+ * @param options.setImageSizes Specifies whether the image `width` and `height` attributes should be set automatically.
67
+ * The default is `true`.
66
68
  * @fires execute
67
69
  */
68
70
  execute(options = {}) {
@@ -71,10 +73,11 @@ export default class ImageStyleCommand extends Command {
71
73
  const imageUtils = editor.plugins.get('ImageUtils');
72
74
  model.change(writer => {
73
75
  const requestedStyle = options.value;
76
+ const { setImageSizes = true } = options;
74
77
  let imageElement = imageUtils.getClosestSelectedImageElement(model.document.selection);
75
78
  // Change the image type if a style requires it.
76
79
  if (requestedStyle && this.shouldConvertImageType(requestedStyle, imageElement)) {
77
- this.editor.execute(imageUtils.isBlockImage(imageElement) ? 'imageTypeInline' : 'imageTypeBlock');
80
+ this.editor.execute(imageUtils.isBlockImage(imageElement) ? 'imageTypeInline' : 'imageTypeBlock', { setImageSizes });
78
81
  // Update the imageElement to the newly created image.
79
82
  imageElement = imageUtils.getClosestSelectedImageElement(model.document.selection);
80
83
  }
@@ -86,6 +89,9 @@ export default class ImageStyleCommand extends Command {
86
89
  else {
87
90
  writer.setAttribute('imageStyle', requestedStyle, imageElement);
88
91
  }
92
+ if (setImageSizes) {
93
+ imageUtils.setImageNaturalSizeAttributes(imageElement);
94
+ }
89
95
  });
90
96
  }
91
97
  /**
@@ -55,7 +55,7 @@ export default class ImageTextAlternativeUI extends Plugin {
55
55
  const view = new ButtonView(locale);
56
56
  view.set({
57
57
  label: t('Change image text alternative'),
58
- icon: icons.lowVision,
58
+ icon: icons.textAlternative,
59
59
  tooltip: true
60
60
  });
61
61
  view.bind('isEnabled').to(command, 'isEnabled');
@@ -94,10 +94,7 @@ export default class ImageUploadEditing extends Plugin {
94
94
  if (data.targetRanges) {
95
95
  writer.setSelection(data.targetRanges.map(viewRange => editor.editing.mapper.toModelRange(viewRange)));
96
96
  }
97
- // Upload images after the selection has changed in order to ensure the command's state is refreshed.
98
- editor.model.enqueueChange(() => {
99
- editor.execute('uploadImage', { file: images });
100
- });
97
+ editor.execute('uploadImage', { file: images });
101
98
  });
102
99
  });
103
100
  // Handle HTML pasted with images with base64 or blob sources.
@@ -177,11 +174,13 @@ export default class ImageUploadEditing extends Plugin {
177
174
  }
178
175
  });
179
176
  // Set the default handler for feeding the image element with `src` and `srcset` attributes.
177
+ // Also set the natural `width` and `height` attributes (if not already set).
180
178
  this.on('uploadComplete', (evt, { imageElement, data }) => {
181
179
  const urls = data.urls ? data.urls : data;
182
180
  this.editor.model.change(writer => {
183
181
  writer.setAttribute('src', urls.default, imageElement);
184
182
  this._parseAndSetSrcsetAttributeOnImage(urls, imageElement, writer);
183
+ imageUtils.setImageNaturalSizeAttributes(imageElement);
185
184
  });
186
185
  }, { priority: 'low' });
187
186
  }
@@ -314,10 +313,13 @@ export default class ImageUploadEditing extends Plugin {
314
313
  // Join all entries.
315
314
  .join(', ');
316
315
  if (srcsetAttribute != '') {
317
- writer.setAttribute('srcset', {
318
- data: srcsetAttribute,
319
- width: maxWidth
320
- }, image);
316
+ const attributes = {
317
+ srcset: srcsetAttribute
318
+ };
319
+ if (!image.hasAttribute('width') && !image.hasAttribute('height')) {
320
+ attributes.width = maxWidth;
321
+ }
322
+ writer.setAttributes(attributes, image);
321
323
  }
322
324
  }
323
325
  }
@@ -5,12 +5,16 @@
5
5
  /**
6
6
  * @module image/imageutils
7
7
  */
8
- import type { Element, ViewElement, DocumentSelection, ViewDocumentSelection, Selection, ViewSelection, DowncastWriter, Position } from 'ckeditor5/src/engine';
8
+ import type { Element, ViewElement, DocumentSelection, ViewDocumentSelection, Selection, ViewSelection, DowncastWriter, Position, ViewContainerElement } from 'ckeditor5/src/engine';
9
9
  import { Plugin } from 'ckeditor5/src/core';
10
10
  /**
11
11
  * A set of helpers related to images.
12
12
  */
13
13
  export default class ImageUtils extends Plugin {
14
+ /**
15
+ * DOM Emitter.
16
+ */
17
+ private _domEmitter;
14
18
  /**
15
19
  * @inheritDoc
16
20
  */
@@ -54,9 +58,20 @@ export default class ImageUtils extends Plugin {
54
58
  *
55
59
  * @param imageType Image type of inserted image. If not specified,
56
60
  * it will be determined automatically depending of editor config or place of the insertion.
61
+ * @param options.setImageSizes Specifies whether the image `width` and `height` attributes should be set automatically.
62
+ * The default is `true`.
57
63
  * @return The inserted model image element.
58
64
  */
59
- insertImage(attributes?: Record<string, unknown>, selectable?: Selection | Position | null, imageType?: ('imageBlock' | 'imageInline' | null)): Element | null;
65
+ insertImage(attributes?: Record<string, unknown>, selectable?: Selection | Position | null, imageType?: ('imageBlock' | 'imageInline' | null), options?: {
66
+ setImageSizes?: boolean;
67
+ }): Element | null;
68
+ /**
69
+ * Reads original image sizes and sets them as `width` and `height`.
70
+ *
71
+ * The `src` attribute may not be available if the user is using an upload adapter. In such a case,
72
+ * this method is called again after the upload process is complete and the `src` attribute is available.
73
+ */
74
+ setImageNaturalSizeAttributes(imageElement: Element): void;
60
75
  /**
61
76
  * Returns an image widget editing view element if one is selected or is among the selection's ancestors.
62
77
  */
@@ -65,6 +80,10 @@ export default class ImageUtils extends Plugin {
65
80
  * Returns a image model element if one is selected or is among the selection's ancestors.
66
81
  */
67
82
  getClosestSelectedImageElement(selection: Selection | DocumentSelection): Element | null;
83
+ /**
84
+ * Returns an image widget editing view based on the passed image view.
85
+ */
86
+ getImageWidgetFromImageView(imageView: ViewElement): ViewContainerElement | null;
68
87
  /**
69
88
  * Checks if image can be inserted at current model selection.
70
89
  *
@@ -99,4 +118,8 @@ export default class ImageUtils extends Plugin {
99
118
  * The `<img>` can be located deep in other elements, so this helper performs a deep tree search.
100
119
  */
101
120
  findViewImgElement(figureView: ViewElement): ViewElement | undefined;
121
+ /**
122
+ * @inheritDoc
123
+ */
124
+ destroy(): void;
102
125
  }
package/src/imageutils.js CHANGED
@@ -5,10 +5,19 @@
5
5
  import { Plugin } from 'ckeditor5/src/core';
6
6
  import { findOptimalInsertionRange, isWidget, toWidget } from 'ckeditor5/src/widget';
7
7
  import { determineImageTypeForInsertionAtSelection } from './image/utils';
8
+ import { DomEmitterMixin, global } from 'ckeditor5/src/utils';
9
+ const IMAGE_WIDGETS_CLASSES_MATCH_REGEXP = /^(image|image-inline)$/;
8
10
  /**
9
11
  * A set of helpers related to images.
10
12
  */
11
13
  export default class ImageUtils extends Plugin {
14
+ constructor() {
15
+ super(...arguments);
16
+ /**
17
+ * DOM Emitter.
18
+ */
19
+ this._domEmitter = new (DomEmitterMixin())();
20
+ }
12
21
  /**
13
22
  * @inheritDoc
14
23
  */
@@ -58,13 +67,15 @@ export default class ImageUtils extends Plugin {
58
67
  *
59
68
  * @param imageType Image type of inserted image. If not specified,
60
69
  * it will be determined automatically depending of editor config or place of the insertion.
70
+ * @param options.setImageSizes Specifies whether the image `width` and `height` attributes should be set automatically.
71
+ * The default is `true`.
61
72
  * @return The inserted model image element.
62
73
  */
63
- insertImage(attributes = {}, selectable = null, imageType = null) {
74
+ insertImage(attributes = {}, selectable = null, imageType = null, options = {}) {
64
75
  const editor = this.editor;
65
76
  const model = editor.model;
66
77
  const selection = model.document.selection;
67
- imageType = determineImageTypeForInsertion(editor, selectable || selection, imageType);
78
+ const determinedImageType = determineImageTypeForInsertion(editor, selectable || selection, imageType);
68
79
  // Mix declarative attributes with selection attributes because the new image should "inherit"
69
80
  // the latter for best UX. For instance, inline images inserted into existing links
70
81
  // should not split them. To do that, they need to have "linkHref" inherited from the selection.
@@ -73,25 +84,59 @@ export default class ImageUtils extends Plugin {
73
84
  ...attributes
74
85
  };
75
86
  for (const attributeName in attributes) {
76
- if (!model.schema.checkAttribute(imageType, attributeName)) {
87
+ if (!model.schema.checkAttribute(determinedImageType, attributeName)) {
77
88
  delete attributes[attributeName];
78
89
  }
79
90
  }
80
91
  return model.change(writer => {
81
- const imageElement = writer.createElement(imageType, attributes);
92
+ const { setImageSizes = true } = options;
93
+ const imageElement = writer.createElement(determinedImageType, attributes);
82
94
  model.insertObject(imageElement, selectable, null, {
83
95
  setSelection: 'on',
84
96
  // If we want to insert a block image (for whatever reason) then we don't want to split text blocks.
85
97
  // This applies only when we don't have the selectable specified (i.e., we insert multiple block images at once).
86
- findOptimalPosition: !selectable && imageType != 'imageInline' ? 'auto' : undefined
98
+ findOptimalPosition: !selectable && determinedImageType != 'imageInline' ? 'auto' : undefined
87
99
  });
88
100
  // Inserting an image might've failed due to schema regulations.
89
101
  if (imageElement.parent) {
102
+ if (setImageSizes) {
103
+ this.setImageNaturalSizeAttributes(imageElement);
104
+ }
90
105
  return imageElement;
91
106
  }
92
107
  return null;
93
108
  });
94
109
  }
110
+ /**
111
+ * Reads original image sizes and sets them as `width` and `height`.
112
+ *
113
+ * The `src` attribute may not be available if the user is using an upload adapter. In such a case,
114
+ * this method is called again after the upload process is complete and the `src` attribute is available.
115
+ */
116
+ setImageNaturalSizeAttributes(imageElement) {
117
+ const src = imageElement.getAttribute('src');
118
+ if (!src) {
119
+ return;
120
+ }
121
+ if (imageElement.getAttribute('width') || imageElement.getAttribute('height')) {
122
+ return;
123
+ }
124
+ this.editor.model.change(writer => {
125
+ const img = new global.window.Image();
126
+ this._domEmitter.listenTo(img, 'load', () => {
127
+ if (!imageElement.getAttribute('width') && !imageElement.getAttribute('height')) {
128
+ // We use writer.batch to be able to undo (in a single step) width and height setting
129
+ // along with any change that triggered this action (e.g. image resize or image style change).
130
+ this.editor.model.enqueueChange(writer.batch, writer => {
131
+ writer.setAttribute('width', img.naturalWidth, imageElement);
132
+ writer.setAttribute('height', img.naturalHeight, imageElement);
133
+ });
134
+ }
135
+ this._domEmitter.stopListening(img, 'load');
136
+ });
137
+ img.src = src;
138
+ });
139
+ }
95
140
  /**
96
141
  * Returns an image widget editing view element if one is selected or is among the selection's ancestors.
97
142
  */
@@ -120,6 +165,12 @@ export default class ImageUtils extends Plugin {
120
165
  const selectedElement = selection.getSelectedElement();
121
166
  return this.isImage(selectedElement) ? selectedElement : selection.getFirstPosition().findAncestor('imageBlock');
122
167
  }
168
+ /**
169
+ * Returns an image widget editing view based on the passed image view.
170
+ */
171
+ getImageWidgetFromImageView(imageView) {
172
+ return imageView.findAncestor({ classes: IMAGE_WIDGETS_CLASSES_MATCH_REGEXP });
173
+ }
123
174
  /**
124
175
  * Checks if image can be inserted at current model selection.
125
176
  *
@@ -182,6 +233,13 @@ export default class ImageUtils extends Plugin {
182
233
  }
183
234
  }
184
235
  }
236
+ /**
237
+ * @inheritDoc
238
+ */
239
+ destroy() {
240
+ this._domEmitter.stopListening();
241
+ return super.destroy();
242
+ }
185
243
  }
186
244
  /**
187
245
  * Checks if image is allowed by schema in optimal insertion parent.
@@ -237,7 +295,7 @@ function determineImageTypeForInsertion(editor, selectable, imageType) {
237
295
  if (configImageInsertType === 'inline') {
238
296
  return 'imageInline';
239
297
  }
240
- if (configImageInsertType === 'block') {
298
+ if (configImageInsertType !== 'auto') {
241
299
  return 'imageBlock';
242
300
  }
243
301
  // Try to replace the selected widget (e.g. another image).
package/src/index.d.ts CHANGED
@@ -17,6 +17,7 @@ export { default as ImageResize } from './imageresize';
17
17
  export { default as ImageResizeButtons } from './imageresize/imageresizebuttons';
18
18
  export { default as ImageResizeEditing } from './imageresize/imageresizeediting';
19
19
  export { default as ImageResizeHandles } from './imageresize/imageresizehandles';
20
+ export { default as ImageSizeAttributes } from './imagesizeattributes';
20
21
  export { default as ImageStyle } from './imagestyle';
21
22
  export { default as ImageStyleEditing } from './imagestyle/imagestyleediting';
22
23
  export { default as ImageStyleUI } from './imagestyle/imagestyleui';
package/src/index.js CHANGED
@@ -17,6 +17,7 @@ export { default as ImageResize } from './imageresize';
17
17
  export { default as ImageResizeButtons } from './imageresize/imageresizebuttons';
18
18
  export { default as ImageResizeEditing } from './imageresize/imageresizeediting';
19
19
  export { default as ImageResizeHandles } from './imageresize/imageresizehandles';
20
+ export { default as ImageSizeAttributes } from './imagesizeattributes';
20
21
  export { default as ImageStyle } from './imagestyle';
21
22
  export { default as ImageStyleEditing } from './imagestyle/imagestyleediting';
22
23
  export { default as ImageStyleUI } from './imagestyle/imagestyleui';
package/theme/image.css CHANGED
@@ -28,7 +28,11 @@
28
28
  max-width: 100%;
29
29
 
30
30
  /* Make sure the image is never smaller than the parent container (See: https://github.com/ckeditor/ckeditor5/issues/9300). */
31
- min-width: 100%
31
+ min-width: 100%;
32
+
33
+ /* Keep proportions of the block image if the height is set and the image is wider than the editor width.
34
+ See https://github.com/ckeditor/ckeditor5/issues/14542. */
35
+ height: auto;
32
36
  }
33
37
  }
34
38
 
@@ -83,28 +87,51 @@
83
87
  text-overflow: ellipsis;
84
88
  }
85
89
 
86
-
87
90
  /*
88
- * Make sure the selected inline image always stays on top of its siblings.
89
- * See https://github.com/ckeditor/ckeditor5/issues/9108.
91
+ * See https://github.com/ckeditor/ckeditor5/issues/15115.
90
92
  */
91
- & .image.ck-widget_selected {
93
+ & .image {
92
94
  z-index: 1;
95
+
96
+ /*
97
+ * Make sure the selected image always stays on top of its siblings.
98
+ * See https://github.com/ckeditor/ckeditor5/issues/9108.
99
+ */
100
+ &.ck-widget_selected {
101
+ z-index: 2;
102
+ }
93
103
  }
94
104
 
95
- & .image-inline.ck-widget_selected {
105
+ /*
106
+ * See https://github.com/ckeditor/ckeditor5/issues/15115.
107
+ */
108
+ & .image-inline {
96
109
  z-index: 1;
97
110
 
98
111
  /*
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.
112
+ * Make sure the selected inline image always stays on top of its siblings.
113
+ * See https://github.com/ckeditor/ckeditor5/issues/9108.
102
114
  */
103
- & ::selection {
104
- display: none;
115
+ &.ck-widget_selected {
116
+ z-index: 2;
117
+
118
+ /*
119
+ * Make sure the native browser selection style is not displayed.
120
+ * Inline image widgets have their own styles for the selected state and
121
+ * leaving this up to the browser is asking for a visual collision.
122
+ */
123
+ & ::selection {
124
+ display: none;
125
+ }
105
126
  }
106
127
  }
107
128
 
129
+ /* Keep proportions of the inline image if the height is set and the image is wider than the editor width.
130
+ See https://github.com/ckeditor/ckeditor5/issues/14542. */
131
+ & .image-inline img {
132
+ height: auto;
133
+ }
134
+
108
135
  /* The inline image nested in the table should have its original size if not resized.
109
136
  See https://github.com/ckeditor/ckeditor5/issues/9117. */
110
137
  & td,
@@ -0,0 +1,10 @@
1
+ /*
2
+ * Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+
6
+ .ck.ck-editor__editable {
7
+ & img.image_placeholder {
8
+ background-size: 100% 100%;
9
+ }
10
+ }