@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.
- package/LICENSE.md +3 -3
- package/build/image.js +1 -1
- package/build/translations/pt-br.js +1 -1
- package/ckeditor5-metadata.json +12 -0
- package/lang/translations/pt-br.po +1 -1
- package/package.json +3 -3
- package/src/augmentation.d.ts +2 -1
- package/src/image/converters.d.ts +1 -1
- package/src/image/converters.js +5 -15
- package/src/image/imageblockediting.d.ts +5 -1
- package/src/image/imageblockediting.js +19 -2
- package/src/image/imageediting.js +1 -12
- package/src/image/imageinlineediting.d.ts +5 -1
- package/src/image/imageinlineediting.js +19 -2
- package/src/image/imageplaceholder.d.ts +39 -0
- package/src/image/imageplaceholder.js +113 -0
- package/src/image/imagetypecommand.d.ts +5 -1
- package/src/image/imagetypecommand.js +5 -2
- package/src/image/replaceimagesourcecommand.d.ts +18 -1
- package/src/image/replaceimagesourcecommand.js +33 -2
- package/src/image/utils.d.ts +13 -1
- package/src/image/utils.js +21 -0
- package/src/imageconfig.d.ts +8 -5
- package/src/imageresize/imageresizeediting.js +61 -10
- package/src/imageresize/imageresizehandles.d.ts +2 -1
- package/src/imageresize/imageresizehandles.js +10 -3
- package/src/imageresize/resizeimagecommand.js +5 -3
- package/src/imagesizeattributes.d.ts +34 -0
- package/src/imagesizeattributes.js +142 -0
- package/src/imagestyle/imagestylecommand.d.ts +3 -0
- package/src/imagestyle/imagestylecommand.js +7 -1
- package/src/imagetextalternative/imagetextalternativeui.js +1 -1
- package/src/imageupload/imageuploadediting.js +10 -8
- package/src/imageutils.d.ts +25 -2
- package/src/imageutils.js +64 -6
- package/src/index.d.ts +1 -0
- package/src/index.js +1 -0
- package/theme/image.css +38 -11
- package/theme/imageplaceholder.css +10 -0
- package/theme/imageresize.css +5 -0
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
import { Plugin } from 'ckeditor5/src/core';
|
|
9
9
|
import { ClipboardPipeline } from 'ckeditor5/src/clipboard';
|
|
10
10
|
import ImageEditing from './imageediting';
|
|
11
|
+
import ImageSizeAttributes from '../imagesizeattributes';
|
|
11
12
|
import ImageUtils from '../imageutils';
|
|
13
|
+
import ImagePlaceholder from './imageplaceholder';
|
|
12
14
|
/**
|
|
13
15
|
* The image block plugin.
|
|
14
16
|
*
|
|
@@ -23,7 +25,7 @@ export default class ImageBlockEditing extends Plugin {
|
|
|
23
25
|
/**
|
|
24
26
|
* @inheritDoc
|
|
25
27
|
*/
|
|
26
|
-
static get requires(): readonly [typeof ImageEditing, typeof ImageUtils, typeof ClipboardPipeline];
|
|
28
|
+
static get requires(): readonly [typeof ImageEditing, typeof ImageSizeAttributes, typeof ImageUtils, typeof ImagePlaceholder, typeof ClipboardPipeline];
|
|
27
29
|
/**
|
|
28
30
|
* @inheritDoc
|
|
29
31
|
*/
|
|
@@ -50,6 +52,8 @@ export default class ImageBlockEditing extends Plugin {
|
|
|
50
52
|
* if they decided to put their image there.
|
|
51
53
|
*
|
|
52
54
|
* See the `ImageInlineEditing` for the similar integration that works in the opposite direction.
|
|
55
|
+
*
|
|
56
|
+
* The feature also sets image `width` and `height` attributes on paste.
|
|
53
57
|
*/
|
|
54
58
|
private _setupClipboardIntegration;
|
|
55
59
|
}
|
|
@@ -10,9 +10,11 @@ import { ClipboardPipeline } from 'ckeditor5/src/clipboard';
|
|
|
10
10
|
import { UpcastWriter } from 'ckeditor5/src/engine';
|
|
11
11
|
import { downcastImageAttribute, downcastSrcsetAttribute, upcastImageFigure } from './converters';
|
|
12
12
|
import ImageEditing from './imageediting';
|
|
13
|
+
import ImageSizeAttributes from '../imagesizeattributes';
|
|
13
14
|
import ImageTypeCommand from './imagetypecommand';
|
|
14
15
|
import ImageUtils from '../imageutils';
|
|
15
|
-
import { getImgViewElementMatcher, createBlockImageViewElement, determineImageTypeForInsertionAtSelection } from '
|
|
16
|
+
import { getImgViewElementMatcher, createBlockImageViewElement, determineImageTypeForInsertionAtSelection } from './utils';
|
|
17
|
+
import ImagePlaceholder from './imageplaceholder';
|
|
16
18
|
/**
|
|
17
19
|
* The image block plugin.
|
|
18
20
|
*
|
|
@@ -28,7 +30,7 @@ export default class ImageBlockEditing extends Plugin {
|
|
|
28
30
|
* @inheritDoc
|
|
29
31
|
*/
|
|
30
32
|
static get requires() {
|
|
31
|
-
return [ImageEditing, ImageUtils, ClipboardPipeline];
|
|
33
|
+
return [ImageEditing, ImageSizeAttributes, ImageUtils, ImagePlaceholder, ClipboardPipeline];
|
|
32
34
|
}
|
|
33
35
|
/**
|
|
34
36
|
* @inheritDoc
|
|
@@ -97,6 +99,8 @@ export default class ImageBlockEditing extends Plugin {
|
|
|
97
99
|
* if they decided to put their image there.
|
|
98
100
|
*
|
|
99
101
|
* See the `ImageInlineEditing` for the similar integration that works in the opposite direction.
|
|
102
|
+
*
|
|
103
|
+
* The feature also sets image `width` and `height` attributes on paste.
|
|
100
104
|
*/
|
|
101
105
|
_setupClipboardIntegration() {
|
|
102
106
|
const editor = this.editor;
|
|
@@ -132,5 +136,18 @@ export default class ImageBlockEditing extends Plugin {
|
|
|
132
136
|
data.content = writer.createDocumentFragment(blockViewImages);
|
|
133
137
|
}
|
|
134
138
|
});
|
|
139
|
+
this.listenTo(clipboardPipeline, 'contentInsertion', (evt, data) => {
|
|
140
|
+
if (data.method !== 'paste') {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
model.change(writer => {
|
|
144
|
+
const range = writer.createRangeIn(data.content);
|
|
145
|
+
for (const item of range.getItems()) {
|
|
146
|
+
if (item.is('element', 'imageBlock')) {
|
|
147
|
+
imageUtils.setImageNaturalSizeAttributes(item);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
});
|
|
135
152
|
}
|
|
136
153
|
}
|
|
@@ -51,18 +51,7 @@ export default class ImageEditing extends Plugin {
|
|
|
51
51
|
name: 'img',
|
|
52
52
|
key: 'srcset'
|
|
53
53
|
},
|
|
54
|
-
model:
|
|
55
|
-
key: 'srcset',
|
|
56
|
-
value: (viewImage) => {
|
|
57
|
-
const value = {
|
|
58
|
-
data: viewImage.getAttribute('srcset')
|
|
59
|
-
};
|
|
60
|
-
if (viewImage.hasAttribute('width')) {
|
|
61
|
-
value.width = viewImage.getAttribute('width');
|
|
62
|
-
}
|
|
63
|
-
return value;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
54
|
+
model: 'srcset'
|
|
66
55
|
});
|
|
67
56
|
const insertImageCommand = new InsertImageCommand(editor);
|
|
68
57
|
const replaceImageSourceCommand = new ReplaceImageSourceCommand(editor);
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
import { Plugin } from 'ckeditor5/src/core';
|
|
9
9
|
import { ClipboardPipeline } from 'ckeditor5/src/clipboard';
|
|
10
10
|
import ImageEditing from './imageediting';
|
|
11
|
+
import ImageSizeAttributes from '../imagesizeattributes';
|
|
11
12
|
import ImageUtils from '../imageutils';
|
|
13
|
+
import ImagePlaceholder from './imageplaceholder';
|
|
12
14
|
/**
|
|
13
15
|
* The image inline plugin.
|
|
14
16
|
*
|
|
@@ -23,7 +25,7 @@ export default class ImageInlineEditing extends Plugin {
|
|
|
23
25
|
/**
|
|
24
26
|
* @inheritDoc
|
|
25
27
|
*/
|
|
26
|
-
static get requires(): readonly [typeof ImageEditing, typeof ImageUtils, typeof ClipboardPipeline];
|
|
28
|
+
static get requires(): readonly [typeof ImageEditing, typeof ImageSizeAttributes, typeof ImageUtils, typeof ImagePlaceholder, typeof ClipboardPipeline];
|
|
27
29
|
/**
|
|
28
30
|
* @inheritDoc
|
|
29
31
|
*/
|
|
@@ -51,6 +53,8 @@ export default class ImageInlineEditing extends Plugin {
|
|
|
51
53
|
* in the clipboard pipeline.
|
|
52
54
|
*
|
|
53
55
|
* See the `ImageBlockEditing` for the similar integration that works in the opposite direction.
|
|
56
|
+
*
|
|
57
|
+
* The feature also sets image `width` and `height` attributes when pasting.
|
|
54
58
|
*/
|
|
55
59
|
private _setupClipboardIntegration;
|
|
56
60
|
}
|
|
@@ -10,9 +10,11 @@ import { ClipboardPipeline } from 'ckeditor5/src/clipboard';
|
|
|
10
10
|
import { UpcastWriter } from 'ckeditor5/src/engine';
|
|
11
11
|
import { downcastImageAttribute, downcastSrcsetAttribute } from './converters';
|
|
12
12
|
import ImageEditing from './imageediting';
|
|
13
|
+
import ImageSizeAttributes from '../imagesizeattributes';
|
|
13
14
|
import ImageTypeCommand from './imagetypecommand';
|
|
14
15
|
import ImageUtils from '../imageutils';
|
|
15
|
-
import { getImgViewElementMatcher, createInlineImageViewElement, determineImageTypeForInsertionAtSelection } from '
|
|
16
|
+
import { getImgViewElementMatcher, createInlineImageViewElement, determineImageTypeForInsertionAtSelection } from './utils';
|
|
17
|
+
import ImagePlaceholder from './imageplaceholder';
|
|
16
18
|
/**
|
|
17
19
|
* The image inline plugin.
|
|
18
20
|
*
|
|
@@ -28,7 +30,7 @@ export default class ImageInlineEditing extends Plugin {
|
|
|
28
30
|
* @inheritDoc
|
|
29
31
|
*/
|
|
30
32
|
static get requires() {
|
|
31
|
-
return [ImageEditing, ImageUtils, ClipboardPipeline];
|
|
33
|
+
return [ImageEditing, ImageSizeAttributes, ImageUtils, ImagePlaceholder, ClipboardPipeline];
|
|
32
34
|
}
|
|
33
35
|
/**
|
|
34
36
|
* @inheritDoc
|
|
@@ -105,6 +107,8 @@ export default class ImageInlineEditing extends Plugin {
|
|
|
105
107
|
* in the clipboard pipeline.
|
|
106
108
|
*
|
|
107
109
|
* See the `ImageBlockEditing` for the similar integration that works in the opposite direction.
|
|
110
|
+
*
|
|
111
|
+
* The feature also sets image `width` and `height` attributes when pasting.
|
|
108
112
|
*/
|
|
109
113
|
_setupClipboardIntegration() {
|
|
110
114
|
const editor = this.editor;
|
|
@@ -156,5 +160,18 @@ export default class ImageInlineEditing extends Plugin {
|
|
|
156
160
|
data.content = writer.createDocumentFragment(inlineViewImages);
|
|
157
161
|
}
|
|
158
162
|
});
|
|
163
|
+
this.listenTo(clipboardPipeline, 'contentInsertion', (evt, data) => {
|
|
164
|
+
if (data.method !== 'paste') {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
model.change(writer => {
|
|
168
|
+
const range = writer.createRangeIn(data.content);
|
|
169
|
+
for (const item of range.getItems()) {
|
|
170
|
+
if (item.is('element', 'imageInline')) {
|
|
171
|
+
imageUtils.setImageNaturalSizeAttributes(item);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
});
|
|
159
176
|
}
|
|
160
177
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* @module image/image/imageplaceholder
|
|
7
|
+
*/
|
|
8
|
+
import { Plugin } from 'ckeditor5/src/core';
|
|
9
|
+
import ImageUtils from '../imageutils';
|
|
10
|
+
import '../../theme/imageplaceholder.css';
|
|
11
|
+
/**
|
|
12
|
+
* Adds support for image placeholder that is automatically removed when the image is loaded.
|
|
13
|
+
*/
|
|
14
|
+
export default class ImagePlaceholder extends Plugin {
|
|
15
|
+
/**
|
|
16
|
+
* @inheritDoc
|
|
17
|
+
*/
|
|
18
|
+
static get requires(): readonly [typeof ImageUtils];
|
|
19
|
+
/**
|
|
20
|
+
* @inheritDoc
|
|
21
|
+
*/
|
|
22
|
+
static get pluginName(): "ImagePlaceholder";
|
|
23
|
+
/**
|
|
24
|
+
* @inheritDoc
|
|
25
|
+
*/
|
|
26
|
+
afterInit(): void;
|
|
27
|
+
/**
|
|
28
|
+
* Extends model schema.
|
|
29
|
+
*/
|
|
30
|
+
private _setupSchema;
|
|
31
|
+
/**
|
|
32
|
+
* Registers converters.
|
|
33
|
+
*/
|
|
34
|
+
private _setupConversion;
|
|
35
|
+
/**
|
|
36
|
+
* Prepares listener for image load.
|
|
37
|
+
*/
|
|
38
|
+
private _setupLoadListener;
|
|
39
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* @module image/image/imageplaceholder
|
|
7
|
+
*/
|
|
8
|
+
import { Plugin } from 'ckeditor5/src/core';
|
|
9
|
+
import ImageUtils from '../imageutils';
|
|
10
|
+
import ImageLoadObserver from './imageloadobserver';
|
|
11
|
+
import '../../theme/imageplaceholder.css';
|
|
12
|
+
/**
|
|
13
|
+
* Adds support for image placeholder that is automatically removed when the image is loaded.
|
|
14
|
+
*/
|
|
15
|
+
export default class ImagePlaceholder extends Plugin {
|
|
16
|
+
/**
|
|
17
|
+
* @inheritDoc
|
|
18
|
+
*/
|
|
19
|
+
static get requires() {
|
|
20
|
+
return [ImageUtils];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* @inheritDoc
|
|
24
|
+
*/
|
|
25
|
+
static get pluginName() {
|
|
26
|
+
return 'ImagePlaceholder';
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* @inheritDoc
|
|
30
|
+
*/
|
|
31
|
+
afterInit() {
|
|
32
|
+
this._setupSchema();
|
|
33
|
+
this._setupConversion();
|
|
34
|
+
this._setupLoadListener();
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Extends model schema.
|
|
38
|
+
*/
|
|
39
|
+
_setupSchema() {
|
|
40
|
+
const schema = this.editor.model.schema;
|
|
41
|
+
// Wait for ImageBlockEditing or ImageInlineEditing to register their elements first,
|
|
42
|
+
// that's why doing this in afterInit() instead of init().
|
|
43
|
+
if (schema.isRegistered('imageBlock')) {
|
|
44
|
+
schema.extend('imageBlock', {
|
|
45
|
+
allowAttributes: ['placeholder']
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (schema.isRegistered('imageInline')) {
|
|
49
|
+
schema.extend('imageInline', {
|
|
50
|
+
allowAttributes: ['placeholder']
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Registers converters.
|
|
56
|
+
*/
|
|
57
|
+
_setupConversion() {
|
|
58
|
+
const editor = this.editor;
|
|
59
|
+
const conversion = editor.conversion;
|
|
60
|
+
const imageUtils = editor.plugins.get('ImageUtils');
|
|
61
|
+
conversion.for('editingDowncast').add(dispatcher => {
|
|
62
|
+
dispatcher.on('attribute:placeholder', (evt, data, conversionApi) => {
|
|
63
|
+
if (!conversionApi.consumable.test(data.item, evt.name)) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (!data.item.is('element', 'imageBlock') && !data.item.is('element', 'imageInline')) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
conversionApi.consumable.consume(data.item, evt.name);
|
|
70
|
+
const viewWriter = conversionApi.writer;
|
|
71
|
+
const element = conversionApi.mapper.toViewElement(data.item);
|
|
72
|
+
const img = imageUtils.findViewImgElement(element);
|
|
73
|
+
if (data.attributeNewValue) {
|
|
74
|
+
viewWriter.addClass('image_placeholder', img);
|
|
75
|
+
viewWriter.setStyle('background-image', `url(${data.attributeNewValue})`, img);
|
|
76
|
+
viewWriter.setCustomProperty('editingPipeline:doNotReuseOnce', true, img);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
viewWriter.removeClass('image_placeholder', img);
|
|
80
|
+
viewWriter.removeStyle('background-image', img);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Prepares listener for image load.
|
|
87
|
+
*/
|
|
88
|
+
_setupLoadListener() {
|
|
89
|
+
const editor = this.editor;
|
|
90
|
+
const model = editor.model;
|
|
91
|
+
const editing = editor.editing;
|
|
92
|
+
const editingView = editing.view;
|
|
93
|
+
const imageUtils = editor.plugins.get('ImageUtils');
|
|
94
|
+
editingView.addObserver(ImageLoadObserver);
|
|
95
|
+
this.listenTo(editingView.document, 'imageLoaded', (evt, domEvent) => {
|
|
96
|
+
const imgViewElement = editingView.domConverter.mapDomToView(domEvent.target);
|
|
97
|
+
if (!imgViewElement) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const viewElement = imageUtils.getImageWidgetFromImageView(imgViewElement);
|
|
101
|
+
if (!viewElement) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const modelElement = editing.mapper.toModelElement(viewElement);
|
|
105
|
+
if (!modelElement || !modelElement.hasAttribute('placeholder')) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
model.enqueueChange({ isUndoable: false }, writer => {
|
|
109
|
+
writer.removeAttribute('placeholder', modelElement);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -29,11 +29,15 @@ export default class ImageTypeCommand extends Command {
|
|
|
29
29
|
* Executes the command and changes the type of a selected image.
|
|
30
30
|
*
|
|
31
31
|
* @fires execute
|
|
32
|
+
* @param options.setImageSizes Specifies whether the image `width` and `height` attributes should be set automatically.
|
|
33
|
+
* The default is `true`.
|
|
32
34
|
* @returns An object containing references to old and new model image elements
|
|
33
35
|
* (for before and after the change) so external integrations can hook into the decorated
|
|
34
36
|
* `execute` event and handle this change. `null` if the type change failed.
|
|
35
37
|
*/
|
|
36
|
-
execute(
|
|
38
|
+
execute(options?: {
|
|
39
|
+
setImageSizes?: boolean;
|
|
40
|
+
}): {
|
|
37
41
|
oldElement: Element;
|
|
38
42
|
newElement: Element;
|
|
39
43
|
} | null;
|
|
@@ -34,11 +34,13 @@ export default class ImageTypeCommand extends Command {
|
|
|
34
34
|
* Executes the command and changes the type of a selected image.
|
|
35
35
|
*
|
|
36
36
|
* @fires execute
|
|
37
|
+
* @param options.setImageSizes Specifies whether the image `width` and `height` attributes should be set automatically.
|
|
38
|
+
* The default is `true`.
|
|
37
39
|
* @returns An object containing references to old and new model image elements
|
|
38
40
|
* (for before and after the change) so external integrations can hook into the decorated
|
|
39
41
|
* `execute` event and handle this change. `null` if the type change failed.
|
|
40
42
|
*/
|
|
41
|
-
execute() {
|
|
43
|
+
execute(options = {}) {
|
|
42
44
|
const editor = this.editor;
|
|
43
45
|
const model = this.editor.model;
|
|
44
46
|
const imageUtils = editor.plugins.get('ImageUtils');
|
|
@@ -51,10 +53,11 @@ export default class ImageTypeCommand extends Command {
|
|
|
51
53
|
return null;
|
|
52
54
|
}
|
|
53
55
|
return model.change(writer => {
|
|
56
|
+
const { setImageSizes = true } = options;
|
|
54
57
|
// Get all markers that contain the old image element.
|
|
55
58
|
const markers = Array.from(model.markers)
|
|
56
59
|
.filter(marker => marker.getRange().containsItem(oldElement));
|
|
57
|
-
const newElement = imageUtils.insertImage(attributes, model.createSelection(oldElement, 'on'), this._modelElementName);
|
|
60
|
+
const newElement = imageUtils.insertImage(attributes, model.createSelection(oldElement, 'on'), this._modelElementName, { setImageSizes });
|
|
58
61
|
if (!newElement) {
|
|
59
62
|
return null;
|
|
60
63
|
}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4
4
|
*/
|
|
5
|
-
import { Command } from 'ckeditor5/src/core';
|
|
5
|
+
import { Command, type Editor } from 'ckeditor5/src/core';
|
|
6
|
+
import type { Writer, Element } from 'ckeditor5/src/engine';
|
|
6
7
|
/**
|
|
7
8
|
* @module image/image/replaceimagesourcecommand
|
|
8
9
|
*/
|
|
@@ -17,6 +18,7 @@ import { Command } from 'ckeditor5/src/core';
|
|
|
17
18
|
*/
|
|
18
19
|
export default class ReplaceImageSourceCommand extends Command {
|
|
19
20
|
value: string | null;
|
|
21
|
+
constructor(editor: Editor);
|
|
20
22
|
/**
|
|
21
23
|
* @inheritDoc
|
|
22
24
|
*/
|
|
@@ -31,4 +33,19 @@ export default class ReplaceImageSourceCommand extends Command {
|
|
|
31
33
|
execute(options: {
|
|
32
34
|
source: string;
|
|
33
35
|
}): void;
|
|
36
|
+
/**
|
|
37
|
+
* Cleanup image attributes that are not relevant to the new source.
|
|
38
|
+
*
|
|
39
|
+
* Removed attributes are: 'srcset', 'sizes', 'sources', 'width', 'height', 'alt'.
|
|
40
|
+
*
|
|
41
|
+
* This method is decorated, to allow custom cleanup logic.
|
|
42
|
+
* For example, to remove 'myImageId' attribute after 'src' has changed:
|
|
43
|
+
*
|
|
44
|
+
* ```ts
|
|
45
|
+
* replaceImageSourceCommand.on( 'cleanupImage', ( eventInfo, [ writer, image ] ) => {
|
|
46
|
+
* writer.removeAttribute( 'myImageId', image );
|
|
47
|
+
* } );
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
cleanupImage(writer: Writer, image: Element): void;
|
|
34
51
|
}
|
|
@@ -16,6 +16,10 @@ import { Command } from 'ckeditor5/src/core';
|
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
18
|
export default class ReplaceImageSourceCommand extends Command {
|
|
19
|
+
constructor(editor) {
|
|
20
|
+
super(editor);
|
|
21
|
+
this.decorate('cleanupImage');
|
|
22
|
+
}
|
|
19
23
|
/**
|
|
20
24
|
* @inheritDoc
|
|
21
25
|
*/
|
|
@@ -35,10 +39,37 @@ export default class ReplaceImageSourceCommand extends Command {
|
|
|
35
39
|
*/
|
|
36
40
|
execute(options) {
|
|
37
41
|
const image = this.editor.model.document.selection.getSelectedElement();
|
|
42
|
+
const imageUtils = this.editor.plugins.get('ImageUtils');
|
|
38
43
|
this.editor.model.change(writer => {
|
|
39
44
|
writer.setAttribute('src', options.source, image);
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
this.cleanupImage(writer, image);
|
|
46
|
+
imageUtils.setImageNaturalSizeAttributes(image);
|
|
42
47
|
});
|
|
43
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Cleanup image attributes that are not relevant to the new source.
|
|
51
|
+
*
|
|
52
|
+
* Removed attributes are: 'srcset', 'sizes', 'sources', 'width', 'height', 'alt'.
|
|
53
|
+
*
|
|
54
|
+
* This method is decorated, to allow custom cleanup logic.
|
|
55
|
+
* For example, to remove 'myImageId' attribute after 'src' has changed:
|
|
56
|
+
*
|
|
57
|
+
* ```ts
|
|
58
|
+
* replaceImageSourceCommand.on( 'cleanupImage', ( eventInfo, [ writer, image ] ) => {
|
|
59
|
+
* writer.removeAttribute( 'myImageId', image );
|
|
60
|
+
* } );
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
cleanupImage(writer, image) {
|
|
64
|
+
writer.removeAttribute('srcset', image);
|
|
65
|
+
writer.removeAttribute('sizes', image);
|
|
66
|
+
/**
|
|
67
|
+
* In case responsive images some attributes should be cleaned up.
|
|
68
|
+
* Check: https://github.com/ckeditor/ckeditor5/issues/15093
|
|
69
|
+
*/
|
|
70
|
+
writer.removeAttribute('sources', image);
|
|
71
|
+
writer.removeAttribute('width', image);
|
|
72
|
+
writer.removeAttribute('height', image);
|
|
73
|
+
writer.removeAttribute('alt', image);
|
|
74
|
+
}
|
|
44
75
|
}
|
package/src/image/utils.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* @module image/image/utils
|
|
7
7
|
*/
|
|
8
|
-
import type { DocumentSelection, MatcherPattern, Schema, Selection, ViewContainerElement, DowncastWriter } from 'ckeditor5/src/engine';
|
|
8
|
+
import type { DocumentSelection, MatcherPattern, Schema, Selection, ViewContainerElement, DowncastWriter, ViewElement } from 'ckeditor5/src/engine';
|
|
9
9
|
import type { Editor } from 'ckeditor5/src/core';
|
|
10
10
|
/**
|
|
11
11
|
* Creates a view element representing the inline image.
|
|
@@ -50,3 +50,15 @@ export declare function getImgViewElementMatcher(editor: Editor, matchImageType:
|
|
|
50
50
|
* @internal
|
|
51
51
|
*/
|
|
52
52
|
export declare function determineImageTypeForInsertionAtSelection(schema: Schema, selection: Selection | DocumentSelection): 'imageBlock' | 'imageInline';
|
|
53
|
+
/**
|
|
54
|
+
* Returns parsed value of the size, but only if it contains unit: px.
|
|
55
|
+
*/
|
|
56
|
+
export declare function getSizeValueIfInPx(size: string | undefined): number | null;
|
|
57
|
+
/**
|
|
58
|
+
* Returns true if both styles (width and height) are set.
|
|
59
|
+
*
|
|
60
|
+
* If both image styles: width & height are set, they will override the image width & height attributes in the
|
|
61
|
+
* browser. In this case, the image looks the same as if these styles were applied to attributes instead of styles.
|
|
62
|
+
* That's why we can upcast these styles to width & height attributes instead of resizedWidth and resizedHeight.
|
|
63
|
+
*/
|
|
64
|
+
export declare function widthAndHeightStylesAreBothSet(viewElement: ViewElement): boolean;
|
package/src/image/utils.js
CHANGED
|
@@ -98,3 +98,24 @@ export function determineImageTypeForInsertionAtSelection(schema, selection) {
|
|
|
98
98
|
// Otherwise insert an inline image.
|
|
99
99
|
return 'imageInline';
|
|
100
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Returns parsed value of the size, but only if it contains unit: px.
|
|
103
|
+
*/
|
|
104
|
+
export function getSizeValueIfInPx(size) {
|
|
105
|
+
if (size && size.endsWith('px')) {
|
|
106
|
+
return parseInt(size);
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Returns true if both styles (width and height) are set.
|
|
112
|
+
*
|
|
113
|
+
* If both image styles: width & height are set, they will override the image width & height attributes in the
|
|
114
|
+
* browser. In this case, the image looks the same as if these styles were applied to attributes instead of styles.
|
|
115
|
+
* That's why we can upcast these styles to width & height attributes instead of resizedWidth and resizedHeight.
|
|
116
|
+
*/
|
|
117
|
+
export function widthAndHeightStylesAreBothSet(viewElement) {
|
|
118
|
+
const widthStyle = getSizeValueIfInPx(viewElement.getStyle('width'));
|
|
119
|
+
const heightStyle = getSizeValueIfInPx(viewElement.getStyle('height'));
|
|
120
|
+
return !!(widthStyle && heightStyle);
|
|
121
|
+
}
|
package/src/imageconfig.d.ts
CHANGED
|
@@ -473,16 +473,19 @@ export interface ImageInsertConfig {
|
|
|
473
473
|
*/
|
|
474
474
|
integrations: Array<string>;
|
|
475
475
|
/**
|
|
476
|
-
* This
|
|
477
|
-
* when the user inserts new images into the editor content. By default,
|
|
478
|
-
*
|
|
476
|
+
* This option allows to override the image type used by the {@link module:image/image/insertimagecommand~InsertImageCommand}
|
|
477
|
+
* when the user inserts new images into the editor content. By default, all images inserted into the editor will be block
|
|
478
|
+
* if {@link module:image/imageblock~ImageBlock} is loaded. To let the editor decide the image type, choose `'auto'`.
|
|
479
479
|
*
|
|
480
480
|
* Available options are:
|
|
481
481
|
*
|
|
482
482
|
* * `'block'` – all images inserted into the editor will be block (requires the {@link module:image/imageblock~ImageBlock} plugin),
|
|
483
|
-
* * `'inline'` – all images inserted into the editor will be inline (requires the {@link module:image/imageinline~ImageInline} plugin)
|
|
483
|
+
* * `'inline'` – all images inserted into the editor will be inline (requires the {@link module:image/imageinline~ImageInline} plugin),
|
|
484
|
+
* * `'auto'` – the editor will choose the optimal image type based on the context of the insertion and availability of plugins.
|
|
485
|
+
*
|
|
486
|
+
* @default 'block'
|
|
484
487
|
*/
|
|
485
|
-
type?: 'inline' | 'block';
|
|
488
|
+
type?: 'inline' | 'block' | 'auto';
|
|
486
489
|
}
|
|
487
490
|
/**
|
|
488
491
|
* The image resize option used in the {@link module:image/imageconfig~ImageConfig#resizeOptions image resize configuration}.
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { Plugin } from 'ckeditor5/src/core';
|
|
6
6
|
import ImageUtils from '../imageutils';
|
|
7
7
|
import ResizeImageCommand from './resizeimagecommand';
|
|
8
|
+
import { widthAndHeightStylesAreBothSet } from '../image/utils';
|
|
8
9
|
/**
|
|
9
10
|
* The image resize editing feature.
|
|
10
11
|
*
|
|
@@ -68,10 +69,10 @@ export default class ImageResizeEditing extends Plugin {
|
|
|
68
69
|
}
|
|
69
70
|
_registerSchema() {
|
|
70
71
|
if (this.editor.plugins.has('ImageBlockEditing')) {
|
|
71
|
-
this.editor.model.schema.extend('imageBlock', { allowAttributes: '
|
|
72
|
+
this.editor.model.schema.extend('imageBlock', { allowAttributes: ['resizedWidth', 'resizedHeight'] });
|
|
72
73
|
}
|
|
73
74
|
if (this.editor.plugins.has('ImageInlineEditing')) {
|
|
74
|
-
this.editor.model.schema.extend('imageInline', { allowAttributes: '
|
|
75
|
+
this.editor.model.schema.extend('imageInline', { allowAttributes: ['resizedWidth', 'resizedHeight'] });
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
/**
|
|
@@ -81,20 +82,47 @@ export default class ImageResizeEditing extends Plugin {
|
|
|
81
82
|
*/
|
|
82
83
|
_registerConverters(imageType) {
|
|
83
84
|
const editor = this.editor;
|
|
85
|
+
const imageUtils = editor.plugins.get('ImageUtils');
|
|
84
86
|
// Dedicated converter to propagate image's attribute to the img tag.
|
|
85
|
-
editor.conversion.for('downcast').add(dispatcher => dispatcher.on(`attribute:
|
|
87
|
+
editor.conversion.for('downcast').add(dispatcher => dispatcher.on(`attribute:resizedWidth:${imageType}`, (evt, data, conversionApi) => {
|
|
86
88
|
if (!conversionApi.consumable.consume(data.item, evt.name)) {
|
|
87
89
|
return;
|
|
88
90
|
}
|
|
89
91
|
const viewWriter = conversionApi.writer;
|
|
90
|
-
const
|
|
92
|
+
const viewImg = conversionApi.mapper.toViewElement(data.item);
|
|
91
93
|
if (data.attributeNewValue !== null) {
|
|
92
|
-
viewWriter.setStyle('width', data.attributeNewValue,
|
|
93
|
-
viewWriter.addClass('image_resized',
|
|
94
|
+
viewWriter.setStyle('width', data.attributeNewValue, viewImg);
|
|
95
|
+
viewWriter.addClass('image_resized', viewImg);
|
|
94
96
|
}
|
|
95
97
|
else {
|
|
96
|
-
viewWriter.removeStyle('width',
|
|
97
|
-
viewWriter.removeClass('image_resized',
|
|
98
|
+
viewWriter.removeStyle('width', viewImg);
|
|
99
|
+
viewWriter.removeClass('image_resized', viewImg);
|
|
100
|
+
}
|
|
101
|
+
}));
|
|
102
|
+
editor.conversion.for('dataDowncast').attributeToAttribute({
|
|
103
|
+
model: {
|
|
104
|
+
name: imageType,
|
|
105
|
+
key: 'resizedHeight'
|
|
106
|
+
},
|
|
107
|
+
view: modelAttributeValue => ({
|
|
108
|
+
key: 'style',
|
|
109
|
+
value: {
|
|
110
|
+
'height': modelAttributeValue
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
});
|
|
114
|
+
editor.conversion.for('editingDowncast').add(dispatcher => dispatcher.on(`attribute:resizedHeight:${imageType}`, (evt, data, conversionApi) => {
|
|
115
|
+
if (!conversionApi.consumable.consume(data.item, evt.name)) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const viewWriter = conversionApi.writer;
|
|
119
|
+
const viewImg = conversionApi.mapper.toViewElement(data.item);
|
|
120
|
+
const target = imageType === 'imageInline' ? imageUtils.findViewImgElement(viewImg) : viewImg;
|
|
121
|
+
if (data.attributeNewValue !== null) {
|
|
122
|
+
viewWriter.setStyle('height', data.attributeNewValue, target);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
viewWriter.removeStyle('height', target);
|
|
98
126
|
}
|
|
99
127
|
}));
|
|
100
128
|
editor.conversion.for('upcast')
|
|
@@ -106,8 +134,31 @@ export default class ImageResizeEditing extends Plugin {
|
|
|
106
134
|
}
|
|
107
135
|
},
|
|
108
136
|
model: {
|
|
109
|
-
key: '
|
|
110
|
-
value: (viewElement) =>
|
|
137
|
+
key: 'resizedWidth',
|
|
138
|
+
value: (viewElement) => {
|
|
139
|
+
if (widthAndHeightStylesAreBothSet(viewElement)) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
return viewElement.getStyle('width');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
editor.conversion.for('upcast')
|
|
147
|
+
.attributeToAttribute({
|
|
148
|
+
view: {
|
|
149
|
+
name: imageType === 'imageBlock' ? 'figure' : 'img',
|
|
150
|
+
styles: {
|
|
151
|
+
height: /.+/
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
model: {
|
|
155
|
+
key: 'resizedHeight',
|
|
156
|
+
value: (viewElement) => {
|
|
157
|
+
if (widthAndHeightStylesAreBothSet(viewElement)) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
return viewElement.getStyle('height');
|
|
161
|
+
}
|
|
111
162
|
}
|
|
112
163
|
});
|
|
113
164
|
}
|
|
@@ -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
|
/**
|
|
8
9
|
* The image resize by handles feature.
|
|
9
10
|
*
|
|
@@ -14,7 +15,7 @@ export default class ImageResizeHandles extends Plugin {
|
|
|
14
15
|
/**
|
|
15
16
|
* @inheritDoc
|
|
16
17
|
*/
|
|
17
|
-
static get requires(): readonly [typeof WidgetResize];
|
|
18
|
+
static get requires(): readonly [typeof WidgetResize, typeof ImageUtils];
|
|
18
19
|
/**
|
|
19
20
|
* @inheritDoc
|
|
20
21
|
*/
|