@ckeditor/ckeditor5-image 27.1.0 → 29.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +1 -1
- package/README.md +3 -3
- package/build/image.js +1 -1
- package/build/translations/ar.js +1 -0
- package/build/translations/ast.js +1 -0
- package/build/translations/az.js +1 -0
- package/build/translations/bg.js +1 -0
- package/build/translations/cs.js +1 -0
- package/build/translations/da.js +1 -0
- package/build/translations/de-ch.js +1 -0
- package/build/translations/de.js +1 -0
- package/build/translations/el.js +1 -0
- package/build/translations/en-au.js +1 -0
- package/build/translations/en-gb.js +1 -0
- package/build/translations/eo.js +1 -0
- package/build/translations/es.js +1 -0
- package/build/translations/et.js +1 -0
- package/build/translations/eu.js +1 -0
- package/build/translations/fa.js +1 -0
- package/build/translations/fi.js +1 -0
- package/build/translations/fr.js +1 -0
- package/build/translations/gl.js +1 -0
- package/build/translations/he.js +1 -0
- package/build/translations/hi.js +1 -0
- package/build/translations/hr.js +1 -0
- package/build/translations/hu.js +1 -0
- package/build/translations/id.js +1 -0
- package/build/translations/it.js +1 -0
- package/build/translations/ja.js +1 -0
- package/build/translations/km.js +1 -0
- package/build/translations/kn.js +1 -0
- package/build/translations/ko.js +1 -0
- package/build/translations/ku.js +1 -0
- package/build/translations/lt.js +1 -0
- package/build/translations/lv.js +1 -0
- package/build/translations/nb.js +1 -0
- package/build/translations/ne.js +1 -0
- package/build/translations/nl.js +1 -0
- package/build/translations/no.js +1 -0
- package/build/translations/pl.js +1 -0
- package/build/translations/pt-br.js +1 -0
- package/build/translations/pt.js +1 -0
- package/build/translations/ro.js +1 -0
- package/build/translations/ru.js +1 -0
- package/build/translations/si.js +1 -0
- package/build/translations/sk.js +1 -0
- package/build/translations/sq.js +1 -0
- package/build/translations/sr-latn.js +1 -0
- package/build/translations/sr.js +1 -0
- package/build/translations/sv.js +1 -0
- package/build/translations/th.js +1 -0
- package/build/translations/tk.js +1 -0
- package/build/translations/tr.js +1 -0
- package/build/translations/ug.js +1 -0
- package/build/translations/uk.js +1 -0
- package/build/translations/vi.js +1 -0
- package/build/translations/zh-cn.js +1 -0
- package/build/translations/zh.js +1 -0
- package/ckeditor5-metadata.json +233 -0
- package/lang/contexts.json +3 -0
- package/lang/translations/ar.po +12 -0
- package/lang/translations/ast.po +12 -0
- package/lang/translations/az.po +12 -0
- package/lang/translations/bg.po +12 -0
- package/lang/translations/cs.po +12 -0
- package/lang/translations/da.po +12 -0
- package/lang/translations/de-ch.po +113 -0
- package/lang/translations/de.po +15 -3
- package/lang/translations/el.po +12 -0
- package/lang/translations/en-au.po +12 -0
- package/lang/translations/en-gb.po +12 -0
- package/lang/translations/en.po +12 -0
- package/lang/translations/eo.po +12 -0
- package/lang/translations/es.po +12 -0
- package/lang/translations/et.po +12 -0
- package/lang/translations/eu.po +12 -0
- package/lang/translations/fa.po +12 -0
- package/lang/translations/fi.po +12 -0
- package/lang/translations/fr.po +12 -0
- package/lang/translations/gl.po +12 -0
- package/lang/translations/he.po +12 -0
- package/lang/translations/hi.po +12 -0
- package/lang/translations/hr.po +12 -0
- package/lang/translations/hu.po +13 -1
- package/lang/translations/id.po +21 -9
- package/lang/translations/it.po +12 -0
- package/lang/translations/ja.po +12 -0
- package/lang/translations/km.po +12 -0
- package/lang/translations/kn.po +12 -0
- package/lang/translations/ko.po +12 -0
- package/lang/translations/ku.po +12 -0
- package/lang/translations/lt.po +12 -0
- package/lang/translations/lv.po +12 -0
- package/lang/translations/nb.po +12 -0
- package/lang/translations/ne.po +12 -0
- package/lang/translations/nl.po +12 -0
- package/lang/translations/no.po +12 -0
- package/lang/translations/pl.po +20 -8
- package/lang/translations/pt-br.po +12 -0
- package/lang/translations/pt.po +12 -0
- package/lang/translations/ro.po +21 -9
- package/lang/translations/ru.po +12 -0
- package/lang/translations/si.po +12 -0
- package/lang/translations/sk.po +12 -0
- package/lang/translations/sq.po +12 -0
- package/lang/translations/sr-latn.po +12 -0
- package/lang/translations/sr.po +12 -0
- package/lang/translations/sv.po +12 -0
- package/lang/translations/th.po +12 -0
- package/lang/translations/tk.po +12 -0
- package/lang/translations/tr.po +12 -0
- package/lang/translations/ug.po +12 -0
- package/lang/translations/uk.po +12 -0
- package/lang/translations/vi.po +12 -0
- package/lang/translations/zh-cn.po +12 -0
- package/lang/translations/zh.po +12 -0
- package/package.json +36 -29
- package/src/autoimage.js +6 -4
- package/src/image/converters.js +192 -13
- package/src/image/imageblockediting.js +179 -0
- package/src/image/imageediting.js +13 -70
- package/src/image/imageinlineediting.js +204 -0
- package/src/image/imagetypecommand.js +105 -0
- package/src/image/insertimagecommand.js +77 -10
- package/src/image/ui/utils.js +3 -3
- package/src/image/utils.js +70 -121
- package/src/image.js +7 -19
- package/src/imageblock.js +46 -0
- package/src/imagecaption/imagecaptionediting.js +202 -230
- package/src/imagecaption/imagecaptionui.js +78 -0
- package/src/imagecaption/toggleimagecaptioncommand.js +165 -0
- package/src/imagecaption/utils.js +25 -40
- package/src/imagecaption.js +3 -2
- package/src/imageinline.js +46 -0
- package/src/imageinsert/imageinsertui.js +5 -6
- package/src/imageinsert.js +16 -4
- package/src/imageresize/imageresizebuttons.js +1 -1
- package/src/imageresize/imageresizeediting.js +21 -8
- package/src/imageresize/imageresizehandles.js +30 -8
- package/src/imageresize/resizeimagecommand.js +8 -5
- package/src/imagestyle/converters.js +25 -17
- package/src/imagestyle/imagestylecommand.js +73 -33
- package/src/imagestyle/imagestyleediting.js +113 -52
- package/src/imagestyle/imagestyleui.js +197 -31
- package/src/imagestyle/utils.js +300 -85
- package/src/imagestyle.js +218 -47
- package/src/imagetextalternative/imagetextalternativecommand.js +10 -7
- package/src/imagetextalternative/imagetextalternativeediting.js +9 -1
- package/src/imagetextalternative/imagetextalternativeui.js +2 -2
- package/src/imagetextalternative.js +1 -1
- package/src/imagetoolbar.js +33 -11
- package/src/imageupload/imageuploadediting.js +91 -31
- package/src/imageupload/imageuploadprogress.js +17 -9
- package/src/imageupload/imageuploadui.js +1 -1
- package/src/imageupload/uploadimagecommand.js +50 -24
- package/src/imageupload/utils.js +3 -2
- package/src/imageupload.js +1 -1
- package/src/imageutils.js +342 -0
- package/src/index.js +22 -47
- package/src/pictureediting.js +149 -0
- package/theme/image.css +101 -21
- package/theme/imagecaption.css +24 -2
- package/theme/imageresize.css +11 -0
- package/theme/imagestyle.css +76 -0
- package/theme/imageuploadicon.css +8 -2
- package/theme/imageuploadprogress.css +12 -8
- package/build/image.js.map +0 -1
|
@@ -8,18 +8,32 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { Plugin } from 'ckeditor5/src/core';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { Element, enablePlaceholder } from 'ckeditor5/src/engine';
|
|
12
|
+
import { setHighlightHandling, toWidgetEditable } from 'ckeditor5/src/widget';
|
|
13
|
+
import { toArray } from 'ckeditor5/src/utils';
|
|
14
|
+
|
|
15
|
+
import ToggleImageCaptionCommand from './toggleimagecaptioncommand';
|
|
16
|
+
|
|
17
|
+
import ImageUtils from '../imageutils';
|
|
18
|
+
import { getCaptionFromImageModelElement, matchImageCaptionViewElement } from './utils';
|
|
13
19
|
|
|
14
20
|
/**
|
|
15
|
-
* The image caption engine plugin.
|
|
21
|
+
* The image caption engine plugin. It is responsible for:
|
|
16
22
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
23
|
+
* * registering converters for the caption element,
|
|
24
|
+
* * registering converters for the caption model attribute,
|
|
25
|
+
* * registering the {@link module:image/imagecaption/toggleimagecaptioncommand~ToggleImageCaptionCommand `toggleImageCaption`} command.
|
|
19
26
|
*
|
|
20
27
|
* @extends module:core/plugin~Plugin
|
|
21
28
|
*/
|
|
22
29
|
export default class ImageCaptionEditing extends Plugin {
|
|
30
|
+
/**
|
|
31
|
+
* @inheritDoc
|
|
32
|
+
*/
|
|
33
|
+
static get requires() {
|
|
34
|
+
return [ ImageUtils ];
|
|
35
|
+
}
|
|
36
|
+
|
|
23
37
|
/**
|
|
24
38
|
* @inheritDoc
|
|
25
39
|
*/
|
|
@@ -30,280 +44,238 @@ export default class ImageCaptionEditing extends Plugin {
|
|
|
30
44
|
/**
|
|
31
45
|
* @inheritDoc
|
|
32
46
|
*/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const view = editor.editing.view;
|
|
36
|
-
const schema = editor.model.schema;
|
|
37
|
-
const data = editor.data;
|
|
38
|
-
const editing = editor.editing;
|
|
39
|
-
const t = editor.t;
|
|
47
|
+
constructor( editor ) {
|
|
48
|
+
super( editor );
|
|
40
49
|
|
|
41
50
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
51
|
+
* A map that keeps saved JSONified image captions and image model elements they are
|
|
52
|
+
* associated with.
|
|
53
|
+
*
|
|
54
|
+
* To learn more about this system, see {@link #_saveCaption}.
|
|
44
55
|
*
|
|
45
|
-
* @
|
|
46
|
-
* @member {module:engine/view/editableelement~EditableElement} #_lastSelectedCaption
|
|
56
|
+
* @member {WeakMap.<module:engine/model/element~Element,Object>}
|
|
47
57
|
*/
|
|
58
|
+
this._savedCaptionsMap = new WeakMap();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @inheritDoc
|
|
63
|
+
*/
|
|
64
|
+
init() {
|
|
65
|
+
const editor = this.editor;
|
|
66
|
+
const schema = editor.model.schema;
|
|
48
67
|
|
|
49
68
|
// Schema configuration.
|
|
50
|
-
schema.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
69
|
+
if ( !schema.isRegistered( 'caption' ) ) {
|
|
70
|
+
schema.register( 'caption', {
|
|
71
|
+
allowIn: 'imageBlock',
|
|
72
|
+
allowContentOf: '$block',
|
|
73
|
+
isLimit: true
|
|
74
|
+
} );
|
|
75
|
+
} else {
|
|
76
|
+
schema.extend( 'caption', {
|
|
77
|
+
allowIn: 'imageBlock'
|
|
78
|
+
} );
|
|
79
|
+
}
|
|
55
80
|
|
|
56
|
-
|
|
57
|
-
editor.model.document.registerPostFixer( writer => this._insertMissingModelCaptionElement( writer ) );
|
|
81
|
+
editor.commands.add( 'toggleImageCaption', new ToggleImageCaptionCommand( this.editor ) );
|
|
58
82
|
|
|
59
|
-
|
|
83
|
+
this._setupConversion();
|
|
84
|
+
this._setupImageTypeCommandsIntegration();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Configures conversion pipelines to support upcasting and downcasting
|
|
89
|
+
* image captions.
|
|
90
|
+
*
|
|
91
|
+
* @private
|
|
92
|
+
*/
|
|
93
|
+
_setupConversion() {
|
|
94
|
+
const editor = this.editor;
|
|
95
|
+
const view = editor.editing.view;
|
|
96
|
+
const imageUtils = editor.plugins.get( 'ImageUtils' );
|
|
97
|
+
const t = editor.t;
|
|
98
|
+
|
|
99
|
+
// View -> model converter for the data pipeline.
|
|
60
100
|
editor.conversion.for( 'upcast' ).elementToElement( {
|
|
61
|
-
view:
|
|
101
|
+
view: element => matchImageCaptionViewElement( imageUtils, element ),
|
|
62
102
|
model: 'caption'
|
|
63
103
|
} );
|
|
64
104
|
|
|
65
|
-
// Model
|
|
66
|
-
|
|
67
|
-
|
|
105
|
+
// Model -> view converter for the data pipeline.
|
|
106
|
+
editor.conversion.for( 'dataDowncast' ).elementToElement( {
|
|
107
|
+
model: 'caption',
|
|
108
|
+
view: ( modelElement, { writer } ) => {
|
|
109
|
+
if ( !imageUtils.isBlockImage( modelElement.parent ) ) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return writer.createContainerElement( 'figcaption' );
|
|
114
|
+
}
|
|
115
|
+
} );
|
|
116
|
+
|
|
117
|
+
// Model -> view converter for the editing pipeline.
|
|
118
|
+
editor.conversion.for( 'editingDowncast' ).elementToElement( {
|
|
119
|
+
model: 'caption',
|
|
120
|
+
view: ( modelElement, { writer } ) => {
|
|
121
|
+
if ( !imageUtils.isBlockImage( modelElement.parent ) ) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const figcaptionElement = writer.createEditableElement( 'figcaption' );
|
|
126
|
+
writer.setCustomProperty( 'imageCaption', true, figcaptionElement );
|
|
68
127
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
128
|
+
enablePlaceholder( {
|
|
129
|
+
view,
|
|
130
|
+
element: figcaptionElement,
|
|
131
|
+
text: t( 'Enter image caption' ),
|
|
132
|
+
keepOnFocus: true
|
|
133
|
+
} );
|
|
72
134
|
|
|
73
|
-
|
|
74
|
-
editing.downcastDispatcher.on(
|
|
75
|
-
'insert',
|
|
76
|
-
this._fixCaptionVisibility( data => data.item ),
|
|
77
|
-
{ priority: 'high' }
|
|
78
|
-
);
|
|
135
|
+
const widgetEditable = toWidgetEditable( figcaptionElement, writer );
|
|
79
136
|
|
|
80
|
-
|
|
81
|
-
|
|
137
|
+
setHighlightHandling(
|
|
138
|
+
widgetEditable,
|
|
139
|
+
writer,
|
|
140
|
+
( element, descriptor, writer ) => writer.addClass( toArray( descriptor.classes ), element ),
|
|
141
|
+
( element, descriptor, writer ) => writer.removeClass( toArray( descriptor.classes ), element )
|
|
142
|
+
);
|
|
82
143
|
|
|
83
|
-
|
|
84
|
-
|
|
144
|
+
return widgetEditable;
|
|
145
|
+
}
|
|
146
|
+
} );
|
|
147
|
+
|
|
148
|
+
editor.editing.mapper.on( 'modelToViewPosition', mapModelPositionToView( view ) );
|
|
149
|
+
editor.data.mapper.on( 'modelToViewPosition', mapModelPositionToView( view ) );
|
|
85
150
|
}
|
|
86
151
|
|
|
87
152
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
153
|
+
* Integrates with {@link module:image/image/imagetypecommand~ImageTypeCommand image type commands}
|
|
154
|
+
* to make sure the caption is preserved when the type of an image changes so it can be restored
|
|
155
|
+
* in the future if the user decides they want their caption back.
|
|
90
156
|
*
|
|
91
157
|
* @private
|
|
92
|
-
* @param {module:engine/view/downcastwriter~DowncastWriter} viewWriter
|
|
93
|
-
* @returns {Boolean} Returns `true` when the view is updated.
|
|
94
158
|
*/
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
// If whole image is selected.
|
|
101
|
-
const modelSelection = this.editor.model.document.selection;
|
|
102
|
-
const selectedElement = modelSelection.getSelectedElement();
|
|
103
|
-
|
|
104
|
-
if ( selectedElement && selectedElement.is( 'element', 'image' ) ) {
|
|
105
|
-
const modelCaption = getCaptionFromImage( selectedElement );
|
|
106
|
-
viewCaption = mapper.toViewElement( modelCaption );
|
|
107
|
-
}
|
|
159
|
+
_setupImageTypeCommandsIntegration() {
|
|
160
|
+
const editor = this.editor;
|
|
161
|
+
const imageUtils = editor.plugins.get( 'ImageUtils' );
|
|
162
|
+
const imageTypeInlineCommand = editor.commands.get( 'imageTypeInline' );
|
|
163
|
+
const imageTypeBlockCommand = editor.commands.get( 'imageTypeBlock' );
|
|
108
164
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
165
|
+
const handleImageTypeChange = evt => {
|
|
166
|
+
// The image type command execution can be unsuccessful.
|
|
167
|
+
if ( !evt.return ) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
112
170
|
|
|
113
|
-
|
|
114
|
-
viewCaption = mapper.toViewElement( modelCaption );
|
|
115
|
-
}
|
|
171
|
+
const { oldElement, newElement } = evt.return;
|
|
116
172
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
173
|
+
/* istanbul ignore if: paranoid check */
|
|
174
|
+
if ( !oldElement ) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if ( imageUtils.isBlockImage( oldElement ) ) {
|
|
179
|
+
const oldCaptionElement = getCaptionFromImageModelElement( oldElement );
|
|
180
|
+
|
|
181
|
+
// If the old element was a captioned block image (the caption was visible),
|
|
182
|
+
// simply save it so it can be restored.
|
|
183
|
+
if ( oldCaptionElement ) {
|
|
184
|
+
this._saveCaption( newElement, oldCaptionElement );
|
|
185
|
+
|
|
186
|
+
return;
|
|
129
187
|
}
|
|
130
|
-
} else {
|
|
131
|
-
this._lastSelectedCaption = viewCaption;
|
|
132
|
-
return showCaption( viewCaption, viewWriter );
|
|
133
188
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
189
|
+
|
|
190
|
+
const savedOldElementCaption = this._getSavedCaption( oldElement );
|
|
191
|
+
|
|
192
|
+
// If either:
|
|
193
|
+
//
|
|
194
|
+
// * the block image didn't have a visible caption,
|
|
195
|
+
// * the block image caption was hidden (and already saved),
|
|
196
|
+
// * the inline image was passed
|
|
197
|
+
//
|
|
198
|
+
// just try to "pass" the saved caption from the old image to the new image
|
|
199
|
+
// so it can be retrieved in the future if the user wants it back.
|
|
200
|
+
if ( savedOldElementCaption ) {
|
|
201
|
+
// Note: Since we're writing to a WeakMap, we don't bother with removing the
|
|
202
|
+
// [ oldElement, savedOldElementCaption ] pair from it.
|
|
203
|
+
this._saveCaption( newElement, savedOldElementCaption );
|
|
143
204
|
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// Presence of the commands depends on the Image(Inline|Block)Editing plugins loaded in the editor.
|
|
208
|
+
if ( imageTypeInlineCommand ) {
|
|
209
|
+
this.listenTo( imageTypeInlineCommand, 'execute', handleImageTypeChange, { priority: 'low' } );
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if ( imageTypeBlockCommand ) {
|
|
213
|
+
this.listenTo( imageTypeBlockCommand, 'execute', handleImageTypeChange, { priority: 'low' } );
|
|
144
214
|
}
|
|
145
215
|
}
|
|
146
216
|
|
|
147
217
|
/**
|
|
148
|
-
* Returns
|
|
149
|
-
*
|
|
218
|
+
* Returns the saved {@link module:engine/model/element~Element#toJSON JSONified} caption
|
|
219
|
+
* of an image model element.
|
|
150
220
|
*
|
|
151
|
-
* @
|
|
152
|
-
*
|
|
153
|
-
* @
|
|
221
|
+
* See {@link #_saveCaption}.
|
|
222
|
+
*
|
|
223
|
+
* @protected
|
|
224
|
+
* @param {module:engine/model/element~Element} imageModelElement The model element the
|
|
225
|
+
* caption should be returned for.
|
|
226
|
+
* @returns {module:engine/model/element~Element|null} The model caption element or `null` if there is none.
|
|
154
227
|
*/
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const mapper = this.editor.editing.mapper;
|
|
160
|
-
const viewWriter = conversionApi.writer;
|
|
161
|
-
|
|
162
|
-
if ( modelCaption ) {
|
|
163
|
-
const viewCaption = mapper.toViewElement( modelCaption );
|
|
164
|
-
|
|
165
|
-
if ( viewCaption ) {
|
|
166
|
-
if ( modelCaption.childCount ) {
|
|
167
|
-
viewWriter.removeClass( 'ck-hidden', viewCaption );
|
|
168
|
-
} else {
|
|
169
|
-
viewWriter.addClass( 'ck-hidden', viewCaption );
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
};
|
|
228
|
+
_getSavedCaption( imageModelElement ) {
|
|
229
|
+
const jsonObject = this._savedCaptionsMap.get( imageModelElement );
|
|
230
|
+
|
|
231
|
+
return jsonObject ? Element.fromJSON( jsonObject ) : null;
|
|
174
232
|
}
|
|
175
233
|
|
|
176
234
|
/**
|
|
177
|
-
*
|
|
178
|
-
*
|
|
235
|
+
* Saves a {@link module:engine/model/element~Element#toJSON JSONified} caption for
|
|
236
|
+
* an image element to allow restoring it in the future.
|
|
179
237
|
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
238
|
+
* A caption is saved every time it gets hidden and/or the type of an image changes. The
|
|
239
|
+
* user should be able to restore it on demand.
|
|
240
|
+
*
|
|
241
|
+
* **Note**: The caption cannot be stored in the image model element attribute because,
|
|
242
|
+
* for instance, when the model state propagates to collaborators, the attribute would get
|
|
243
|
+
* lost (mainly because it does not convert to anything when the caption is hidden) and
|
|
244
|
+
* the states of collaborators' models would de-synchronize causing numerous issues.
|
|
245
|
+
*
|
|
246
|
+
* See {@link #_getSavedCaption}.
|
|
247
|
+
*
|
|
248
|
+
* @protected
|
|
249
|
+
* @param {module:engine/model/element~Element} imageModelElement The model element the
|
|
250
|
+
* caption is saved for.
|
|
251
|
+
* @param {module:engine/model/element~Element} caption The caption model element to be saved.
|
|
183
252
|
*/
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const changes = model.document.differ.getChanges();
|
|
187
|
-
|
|
188
|
-
const imagesWithoutCaption = [];
|
|
189
|
-
|
|
190
|
-
for ( const entry of changes ) {
|
|
191
|
-
if ( entry.type == 'insert' && entry.name != '$text' ) {
|
|
192
|
-
const item = entry.position.nodeAfter;
|
|
193
|
-
|
|
194
|
-
if ( item.is( 'element', 'image' ) && !getCaptionFromImage( item ) ) {
|
|
195
|
-
imagesWithoutCaption.push( item );
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Check elements with children for nested images.
|
|
199
|
-
if ( !item.is( 'element', 'image' ) && item.childCount ) {
|
|
200
|
-
for ( const nestedItem of model.createRangeIn( item ).getItems() ) {
|
|
201
|
-
if ( nestedItem.is( 'element', 'image' ) && !getCaptionFromImage( nestedItem ) ) {
|
|
202
|
-
imagesWithoutCaption.push( nestedItem );
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
for ( const image of imagesWithoutCaption ) {
|
|
210
|
-
writer.appendElement( 'caption', image );
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return !!imagesWithoutCaption.length;
|
|
253
|
+
_saveCaption( imageModelElement, caption ) {
|
|
254
|
+
this._savedCaptionsMap.set( imageModelElement, caption.toJSON() );
|
|
214
255
|
}
|
|
215
256
|
}
|
|
216
257
|
|
|
217
|
-
// Creates a
|
|
258
|
+
// Creates a mapper callback that reverses the order of `<img>` and `<figcaption>` in the image.
|
|
259
|
+
// Without it, `<figcaption>` would precede the `<img>` in the conversion.
|
|
260
|
+
//
|
|
261
|
+
// <imageBlock>^</imageBlock> -> <figure><img>^<caption></caption></figure>
|
|
218
262
|
//
|
|
219
263
|
// @private
|
|
220
|
-
// @param {
|
|
221
|
-
// @param {Boolean} [hide=true] When set to `false` view element will not be inserted when it's empty.
|
|
264
|
+
// @param {module:engine/view/view~View} editingView
|
|
222
265
|
// @returns {Function}
|
|
223
|
-
function
|
|
224
|
-
return ( evt, data
|
|
225
|
-
const
|
|
266
|
+
function mapModelPositionToView( editingView ) {
|
|
267
|
+
return ( evt, data ) => {
|
|
268
|
+
const modelPosition = data.modelPosition;
|
|
269
|
+
const parent = modelPosition.parent;
|
|
226
270
|
|
|
227
|
-
|
|
228
|
-
if ( !captionElement.childCount && !hide ) {
|
|
271
|
+
if ( !parent.is( 'element', 'imageBlock' ) ) {
|
|
229
272
|
return;
|
|
230
273
|
}
|
|
231
274
|
|
|
232
|
-
|
|
233
|
-
if ( !conversionApi.consumable.consume( data.item, 'insert' ) ) {
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const viewImage = conversionApi.mapper.toViewElement( data.range.start.parent );
|
|
238
|
-
const viewCaption = elementCreator( conversionApi.writer );
|
|
239
|
-
const viewWriter = conversionApi.writer;
|
|
240
|
-
|
|
241
|
-
// Hide if empty.
|
|
242
|
-
if ( !captionElement.childCount ) {
|
|
243
|
-
viewWriter.addClass( 'ck-hidden', viewCaption );
|
|
244
|
-
}
|
|
275
|
+
const viewElement = data.mapper.toViewElement( parent );
|
|
245
276
|
|
|
246
|
-
|
|
247
|
-
|
|
277
|
+
// The "img" element is inserted by ImageBlockEditing during the downcast conversion via
|
|
278
|
+
// an explicit view position so the "0" position does not need any mapping.
|
|
279
|
+
data.viewPosition = editingView.createPositionAt( viewElement, modelPosition.offset + 1 );
|
|
248
280
|
};
|
|
249
281
|
}
|
|
250
|
-
|
|
251
|
-
// Inserts `viewCaption` at the end of `viewImage` and binds it to `modelCaption`.
|
|
252
|
-
//
|
|
253
|
-
// @private
|
|
254
|
-
// @param {module:engine/view/containerelement~ContainerElement} viewCaption
|
|
255
|
-
// @param {module:engine/model/element~Element} modelCaption
|
|
256
|
-
// @param {module:engine/view/containerelement~ContainerElement} viewImage
|
|
257
|
-
// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi
|
|
258
|
-
function insertViewCaptionAndBind( viewCaption, modelCaption, viewImage, conversionApi ) {
|
|
259
|
-
const viewPosition = conversionApi.writer.createPositionAt( viewImage, 'end' );
|
|
260
|
-
|
|
261
|
-
conversionApi.writer.insert( viewPosition, viewCaption );
|
|
262
|
-
conversionApi.mapper.bindElements( modelCaption, viewCaption );
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Checks if the provided node or one of its ancestors is a caption element, and returns it.
|
|
266
|
-
//
|
|
267
|
-
// @private
|
|
268
|
-
// @param {module:engine/model/node~Node} node
|
|
269
|
-
// @returns {module:engine/model/element~Element|null}
|
|
270
|
-
function getParentCaption( node ) {
|
|
271
|
-
const ancestors = node.getAncestors( { includeSelf: true } );
|
|
272
|
-
const caption = ancestors.find( ancestor => ancestor.name == 'caption' );
|
|
273
|
-
|
|
274
|
-
if ( caption && caption.parent && caption.parent.name == 'image' ) {
|
|
275
|
-
return caption;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
return null;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Hides a given caption in the view if it is empty.
|
|
282
|
-
//
|
|
283
|
-
// @private
|
|
284
|
-
// @param {module:engine/view/containerelement~ContainerElement} caption
|
|
285
|
-
// @param {module:engine/view/downcastwriter~DowncastWriter} viewWriter
|
|
286
|
-
// @returns {Boolean} Returns `true` if the view was modified.
|
|
287
|
-
function hideCaptionIfEmpty( caption, viewWriter ) {
|
|
288
|
-
if ( !caption.childCount && !caption.hasClass( 'ck-hidden' ) ) {
|
|
289
|
-
viewWriter.addClass( 'ck-hidden', caption );
|
|
290
|
-
return true;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
return false;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Shows the caption.
|
|
297
|
-
//
|
|
298
|
-
// @private
|
|
299
|
-
// @param {module:engine/view/containerelement~ContainerElement} caption
|
|
300
|
-
// @param {module:engine/view/downcastwriter~DowncastWriter} viewWriter
|
|
301
|
-
// @returns {Boolean} Returns `true` if the view was modified.
|
|
302
|
-
function showCaption( caption, viewWriter ) {
|
|
303
|
-
if ( caption.hasClass( 'ck-hidden' ) ) {
|
|
304
|
-
viewWriter.removeClass( 'ck-hidden', caption );
|
|
305
|
-
return true;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return false;
|
|
309
|
-
}
|
|
@@ -0,0 +1,78 @@
|
|
|
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/imagecaption/imagecaptionui
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Plugin, icons } from 'ckeditor5/src/core';
|
|
11
|
+
import { ButtonView } from 'ckeditor5/src/ui';
|
|
12
|
+
import ImageUtils from '../imageutils';
|
|
13
|
+
|
|
14
|
+
import { getCaptionFromModelSelection } from './utils';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The image caption UI plugin. It introduces the `'toggleImageCaption'` UI button.
|
|
18
|
+
*
|
|
19
|
+
* @extends module:core/plugin~Plugin
|
|
20
|
+
*/
|
|
21
|
+
export default class ImageCaptionUI extends Plugin {
|
|
22
|
+
/**
|
|
23
|
+
* @inheritDoc
|
|
24
|
+
*/
|
|
25
|
+
static get requires() {
|
|
26
|
+
return [ ImageUtils ];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @inheritDoc
|
|
31
|
+
*/
|
|
32
|
+
static get pluginName() {
|
|
33
|
+
return 'ImageCaptionUI';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @inheritDoc
|
|
38
|
+
*/
|
|
39
|
+
init() {
|
|
40
|
+
const editor = this.editor;
|
|
41
|
+
const editingView = editor.editing.view;
|
|
42
|
+
const imageUtils = editor.plugins.get( 'ImageUtils' );
|
|
43
|
+
const t = editor.t;
|
|
44
|
+
|
|
45
|
+
editor.ui.componentFactory.add( 'toggleImageCaption', locale => {
|
|
46
|
+
const command = editor.commands.get( 'toggleImageCaption' );
|
|
47
|
+
const view = new ButtonView( locale );
|
|
48
|
+
|
|
49
|
+
view.set( {
|
|
50
|
+
icon: icons.caption,
|
|
51
|
+
tooltip: true,
|
|
52
|
+
isToggleable: true
|
|
53
|
+
} );
|
|
54
|
+
|
|
55
|
+
view.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
|
|
56
|
+
view.bind( 'label' ).to( command, 'value', value => value ? t( 'Toggle caption off' ) : t( 'Toggle caption on' ) );
|
|
57
|
+
|
|
58
|
+
this.listenTo( view, 'execute', () => {
|
|
59
|
+
editor.execute( 'toggleImageCaption', { focusCaptionOnShow: true } );
|
|
60
|
+
|
|
61
|
+
// Scroll to the selection and highlight the caption if the caption showed up.
|
|
62
|
+
const modelCaptionElement = getCaptionFromModelSelection( imageUtils, editor.model.document.selection );
|
|
63
|
+
|
|
64
|
+
if ( modelCaptionElement ) {
|
|
65
|
+
const figcaptionElement = editor.editing.mapper.toViewElement( modelCaptionElement );
|
|
66
|
+
|
|
67
|
+
editingView.scrollToTheSelection();
|
|
68
|
+
|
|
69
|
+
editingView.change( writer => {
|
|
70
|
+
writer.addClass( 'image__caption_highlighted', figcaptionElement );
|
|
71
|
+
} );
|
|
72
|
+
}
|
|
73
|
+
} );
|
|
74
|
+
|
|
75
|
+
return view;
|
|
76
|
+
} );
|
|
77
|
+
}
|
|
78
|
+
}
|