@ckeditor/ckeditor5-image 41.4.2 → 42.0.0-alpha.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/README.md +6 -0
- package/build/image.js +2 -2
- package/build/translations/ar.js +1 -1
- package/build/translations/ast.js +1 -1
- package/build/translations/az.js +1 -1
- package/build/translations/bg.js +1 -1
- package/build/translations/bn.js +1 -1
- package/build/translations/bs.js +1 -1
- package/build/translations/ca.js +1 -1
- package/build/translations/cs.js +1 -1
- package/build/translations/da.js +1 -1
- package/build/translations/de-ch.js +1 -1
- package/build/translations/de.js +1 -1
- package/build/translations/el.js +1 -1
- package/build/translations/en-au.js +1 -1
- package/build/translations/en-gb.js +1 -1
- package/build/translations/eo.js +1 -1
- package/build/translations/es.js +1 -1
- package/build/translations/et.js +1 -1
- package/build/translations/eu.js +1 -1
- package/build/translations/fa.js +1 -1
- package/build/translations/fi.js +1 -1
- package/build/translations/fr.js +1 -1
- package/build/translations/gl.js +1 -1
- package/build/translations/he.js +1 -1
- package/build/translations/hi.js +1 -1
- package/build/translations/hr.js +1 -1
- package/build/translations/hu.js +1 -1
- package/build/translations/id.js +1 -1
- package/build/translations/it.js +1 -1
- package/build/translations/ja.js +1 -1
- package/build/translations/jv.js +1 -1
- package/build/translations/km.js +1 -1
- package/build/translations/kn.js +1 -1
- package/build/translations/ko.js +1 -1
- package/build/translations/ku.js +1 -1
- package/build/translations/lt.js +1 -1
- package/build/translations/lv.js +1 -1
- package/build/translations/ms.js +1 -1
- package/build/translations/nb.js +1 -1
- package/build/translations/ne.js +1 -1
- package/build/translations/nl.js +1 -1
- package/build/translations/no.js +1 -1
- package/build/translations/pl.js +1 -1
- package/build/translations/pt-br.js +1 -1
- package/build/translations/pt.js +1 -1
- package/build/translations/ro.js +1 -1
- package/build/translations/ru.js +1 -1
- package/build/translations/si.js +1 -1
- package/build/translations/sk.js +1 -1
- package/build/translations/sq.js +1 -1
- package/build/translations/sr-latn.js +1 -1
- package/build/translations/sr.js +1 -1
- package/build/translations/sv.js +1 -1
- package/build/translations/th.js +1 -1
- package/build/translations/ti.js +1 -1
- package/build/translations/tk.js +1 -1
- package/build/translations/tr.js +1 -1
- package/build/translations/ug.js +1 -1
- package/build/translations/uk.js +1 -1
- package/build/translations/ur.js +1 -1
- package/build/translations/uz.js +1 -1
- package/build/translations/vi.js +1 -1
- package/build/translations/zh-cn.js +1 -1
- package/build/translations/zh.js +1 -1
- package/ckeditor5-metadata.json +19 -5
- package/dist/index-content.css +20 -12
- package/dist/index-editor.css +4 -0
- package/dist/index.css +50 -33
- package/dist/index.css.map +1 -1
- package/dist/index.js +1876 -1330
- package/dist/index.js.map +1 -1
- package/dist/translations/ar.js +1 -1
- package/dist/translations/ar.umd.js +1 -1
- package/dist/translations/ast.js +1 -1
- package/dist/translations/ast.umd.js +1 -1
- package/dist/translations/az.js +1 -1
- package/dist/translations/az.umd.js +1 -1
- package/dist/translations/bg.js +1 -1
- package/dist/translations/bg.umd.js +1 -1
- package/dist/translations/bn.js +1 -1
- package/dist/translations/bn.umd.js +1 -1
- package/dist/translations/bs.js +1 -1
- package/dist/translations/bs.umd.js +1 -1
- package/dist/translations/ca.js +1 -1
- package/dist/translations/ca.umd.js +1 -1
- package/dist/translations/cs.js +1 -1
- package/dist/translations/cs.umd.js +1 -1
- package/dist/translations/da.js +1 -1
- package/dist/translations/da.umd.js +1 -1
- package/dist/translations/de-ch.js +1 -1
- package/dist/translations/de-ch.umd.js +1 -1
- package/dist/translations/de.js +1 -1
- package/dist/translations/de.umd.js +1 -1
- package/dist/translations/el.js +1 -1
- package/dist/translations/el.umd.js +1 -1
- package/dist/translations/en-au.js +1 -1
- package/dist/translations/en-au.umd.js +1 -1
- package/dist/translations/en-gb.js +1 -1
- package/dist/translations/en-gb.umd.js +1 -1
- package/dist/translations/en.js +1 -1
- package/dist/translations/en.umd.js +1 -1
- package/dist/translations/eo.js +1 -1
- package/dist/translations/eo.umd.js +1 -1
- package/dist/translations/es.js +1 -1
- package/dist/translations/es.umd.js +1 -1
- package/dist/translations/et.js +1 -1
- package/dist/translations/et.umd.js +1 -1
- package/dist/translations/eu.js +1 -1
- package/dist/translations/eu.umd.js +1 -1
- package/dist/translations/fa.js +1 -1
- package/dist/translations/fa.umd.js +1 -1
- package/dist/translations/fi.js +1 -1
- package/dist/translations/fi.umd.js +1 -1
- package/dist/translations/fr.js +1 -1
- package/dist/translations/fr.umd.js +1 -1
- package/dist/translations/gl.js +1 -1
- package/dist/translations/gl.umd.js +1 -1
- package/dist/translations/he.js +1 -1
- package/dist/translations/he.umd.js +1 -1
- package/dist/translations/hi.js +1 -1
- package/dist/translations/hi.umd.js +1 -1
- package/dist/translations/hr.js +1 -1
- package/dist/translations/hr.umd.js +1 -1
- package/dist/translations/hu.js +1 -1
- package/dist/translations/hu.umd.js +1 -1
- package/dist/translations/id.js +1 -1
- package/dist/translations/id.umd.js +1 -1
- package/dist/translations/it.js +1 -1
- package/dist/translations/it.umd.js +1 -1
- package/dist/translations/ja.js +1 -1
- package/dist/translations/ja.umd.js +1 -1
- package/dist/translations/jv.js +1 -1
- package/dist/translations/jv.umd.js +1 -1
- package/dist/translations/km.js +1 -1
- package/dist/translations/km.umd.js +1 -1
- package/dist/translations/kn.js +1 -1
- package/dist/translations/kn.umd.js +1 -1
- package/dist/translations/ko.js +1 -1
- package/dist/translations/ko.umd.js +1 -1
- package/dist/translations/ku.js +1 -1
- package/dist/translations/ku.umd.js +1 -1
- package/dist/translations/lt.js +1 -1
- package/dist/translations/lt.umd.js +1 -1
- package/dist/translations/lv.js +1 -1
- package/dist/translations/lv.umd.js +1 -1
- package/dist/translations/ms.js +1 -1
- package/dist/translations/ms.umd.js +1 -1
- package/dist/translations/nb.js +1 -1
- package/dist/translations/nb.umd.js +1 -1
- package/dist/translations/ne.js +1 -1
- package/dist/translations/ne.umd.js +1 -1
- package/dist/translations/nl.js +1 -1
- package/dist/translations/nl.umd.js +1 -1
- package/dist/translations/no.js +1 -1
- package/dist/translations/no.umd.js +1 -1
- package/dist/translations/pl.js +1 -1
- package/dist/translations/pl.umd.js +1 -1
- package/dist/translations/pt-br.js +1 -1
- package/dist/translations/pt-br.umd.js +1 -1
- package/dist/translations/pt.js +1 -1
- package/dist/translations/pt.umd.js +1 -1
- package/dist/translations/ro.js +1 -1
- package/dist/translations/ro.umd.js +1 -1
- package/dist/translations/ru.js +1 -1
- package/dist/translations/ru.umd.js +1 -1
- package/dist/translations/si.js +1 -1
- package/dist/translations/si.umd.js +1 -1
- package/dist/translations/sk.js +1 -1
- package/dist/translations/sk.umd.js +1 -1
- package/dist/translations/sq.js +1 -1
- package/dist/translations/sq.umd.js +1 -1
- package/dist/translations/sr-latn.js +1 -1
- package/dist/translations/sr-latn.umd.js +1 -1
- package/dist/translations/sr.js +1 -1
- package/dist/translations/sr.umd.js +1 -1
- package/dist/translations/sv.js +1 -1
- package/dist/translations/sv.umd.js +1 -1
- package/dist/translations/th.js +1 -1
- package/dist/translations/th.umd.js +1 -1
- package/dist/translations/ti.js +1 -1
- package/dist/translations/ti.umd.js +1 -1
- package/dist/translations/tk.js +1 -1
- package/dist/translations/tk.umd.js +1 -1
- package/dist/translations/tr.js +1 -1
- package/dist/translations/tr.umd.js +1 -1
- package/dist/translations/ug.js +1 -1
- package/dist/translations/ug.umd.js +1 -1
- package/dist/translations/uk.js +1 -1
- package/dist/translations/uk.umd.js +1 -1
- package/dist/translations/ur.js +1 -1
- package/dist/translations/ur.umd.js +1 -1
- package/dist/translations/uz.js +1 -1
- package/dist/translations/uz.umd.js +1 -1
- package/dist/translations/vi.js +1 -1
- package/dist/translations/vi.umd.js +1 -1
- package/dist/translations/zh-cn.js +1 -1
- package/dist/translations/zh-cn.umd.js +1 -1
- package/dist/translations/zh.js +1 -1
- package/dist/translations/zh.umd.js +1 -1
- package/dist/types/imageinsert/imageinsertui.d.ts +10 -2
- package/dist/types/imageinsert/imageinsertviaurlui.d.ts +31 -9
- package/dist/types/imageinsert/ui/imageinserturlview.d.ts +6 -49
- package/dist/types/imageupload/imageuploadui.d.ts +18 -1
- package/lang/contexts.json +7 -4
- package/lang/translations/ar.po +21 -9
- package/lang/translations/ast.po +19 -7
- package/lang/translations/az.po +19 -7
- package/lang/translations/bg.po +21 -9
- package/lang/translations/bn.po +21 -9
- package/lang/translations/bs.po +21 -9
- package/lang/translations/ca.po +21 -9
- package/lang/translations/cs.po +21 -9
- package/lang/translations/da.po +21 -9
- package/lang/translations/de-ch.po +21 -9
- package/lang/translations/de.po +21 -9
- package/lang/translations/el.po +21 -9
- package/lang/translations/en-au.po +21 -9
- package/lang/translations/en-gb.po +19 -7
- package/lang/translations/en.po +21 -9
- package/lang/translations/eo.po +19 -7
- package/lang/translations/es.po +21 -9
- package/lang/translations/et.po +21 -9
- package/lang/translations/eu.po +19 -7
- package/lang/translations/fa.po +19 -7
- package/lang/translations/fi.po +21 -9
- package/lang/translations/fr.po +21 -9
- package/lang/translations/gl.po +21 -9
- package/lang/translations/he.po +21 -9
- package/lang/translations/hi.po +21 -9
- package/lang/translations/hr.po +21 -9
- package/lang/translations/hu.po +21 -9
- package/lang/translations/id.po +21 -9
- package/lang/translations/it.po +21 -9
- package/lang/translations/ja.po +21 -9
- package/lang/translations/jv.po +21 -9
- package/lang/translations/km.po +19 -7
- package/lang/translations/kn.po +19 -7
- package/lang/translations/ko.po +21 -9
- package/lang/translations/ku.po +19 -7
- package/lang/translations/lt.po +21 -9
- package/lang/translations/lv.po +21 -9
- package/lang/translations/ms.po +21 -9
- package/lang/translations/nb.po +19 -7
- package/lang/translations/ne.po +19 -7
- package/lang/translations/nl.po +21 -9
- package/lang/translations/no.po +21 -9
- package/lang/translations/pl.po +21 -9
- package/lang/translations/pt-br.po +21 -9
- package/lang/translations/pt.po +21 -9
- package/lang/translations/ro.po +21 -9
- package/lang/translations/ru.po +21 -9
- package/lang/translations/si.po +19 -7
- package/lang/translations/sk.po +21 -9
- package/lang/translations/sq.po +19 -7
- package/lang/translations/sr-latn.po +21 -9
- package/lang/translations/sr.po +21 -9
- package/lang/translations/sv.po +21 -9
- package/lang/translations/th.po +21 -9
- package/lang/translations/ti.po +20 -8
- package/lang/translations/tk.po +19 -7
- package/lang/translations/tr.po +21 -9
- package/lang/translations/ug.po +20 -8
- package/lang/translations/uk.po +21 -9
- package/lang/translations/ur.po +19 -7
- package/lang/translations/uz.po +21 -9
- package/lang/translations/vi.po +21 -9
- package/lang/translations/zh-cn.po +21 -9
- package/lang/translations/zh.po +21 -9
- package/package.json +3 -3
- package/src/image/imageinlineediting.js +4 -9
- package/src/imageinsert/imageinsertui.d.ts +10 -2
- package/src/imageinsert/imageinsertui.js +41 -4
- package/src/imageinsert/imageinsertviaurlui.d.ts +31 -9
- package/src/imageinsert/imageinsertviaurlui.js +111 -64
- package/src/imageinsert/ui/imageinsertformview.js +0 -17
- package/src/imageinsert/ui/imageinserturlview.d.ts +6 -49
- package/src/imageinsert/ui/imageinserturlview.js +6 -74
- package/src/imageresize/imageresizebuttons.js +2 -2
- package/src/imagestyle/utils.js +17 -18
- package/src/imageupload/imageuploadui.d.ts +18 -1
- package/src/imageupload/imageuploadui.js +59 -38
- package/theme/imageinsert.css +3 -0
- package/theme/imagestyle.css +47 -34
- package/build/translations/es-co.js +0 -1
- package/build/translations/tt.js +0 -1
- package/dist/translations/es-co.d.ts +0 -8
- package/dist/translations/es-co.js +0 -5
- package/dist/translations/es-co.umd.js +0 -11
- package/dist/translations/tt.d.ts +0 -8
- package/dist/translations/tt.js +0 -5
- package/dist/translations/tt.umd.js +0 -11
- package/lang/translations/es-co.po +0 -178
- package/lang/translations/tt.po +0 -178
package/dist/index.js
CHANGED
|
@@ -7,9 +7,9 @@ import { Clipboard, ClipboardPipeline } from '@ckeditor/ckeditor5-clipboard/dist
|
|
|
7
7
|
import { LivePosition, LiveRange, Observer, UpcastWriter, enablePlaceholder, Element } from '@ckeditor/ckeditor5-engine/dist/index.js';
|
|
8
8
|
import { Undo } from '@ckeditor/ckeditor5-undo/dist/index.js';
|
|
9
9
|
import { Delete } from '@ckeditor/ckeditor5-typing/dist/index.js';
|
|
10
|
-
import { first,
|
|
10
|
+
import { first, DomEmitterMixin, global, FocusTracker, KeystrokeHandler, logWarning, toArray, env, CKEditorError, Collection, Rect } from '@ckeditor/ckeditor5-utils/dist/index.js';
|
|
11
11
|
import { toWidget, isWidget, findOptimalInsertionRange, Widget, toWidgetEditable, WidgetResize, calculateResizeHostAncestorWidth, WidgetToolbarRepository } from '@ckeditor/ckeditor5-widget/dist/index.js';
|
|
12
|
-
import { View, submitHandler, ButtonView, LabeledFieldView, createLabeledInputText,
|
|
12
|
+
import { View, ViewCollection, FocusCycler, submitHandler, ButtonView, LabeledFieldView, createLabeledInputText, BalloonPanelView, ContextualBalloon, CssTransitionDisablerMixin, clickOutsideHandler, CollapsibleView, SplitButtonView, createDropdown, MenuBarMenuView, MenuBarMenuListView, MenuBarMenuListItemView, FileDialogButtonView, MenuBarMenuListItemFileDialogButtonView, Notification, Dialog, MenuBarMenuListItemButtonView, ViewModel, DropdownButtonView, addListToDropdown, createLabeledInputNumber, addToolbarToDropdown } from '@ckeditor/ckeditor5-ui/dist/index.js';
|
|
13
13
|
import { FileRepository } from '@ckeditor/ckeditor5-upload/dist/index.js';
|
|
14
14
|
import { map, isObject, identity } from 'lodash-es';
|
|
15
15
|
|
|
@@ -129,56 +129,61 @@ import { map, isObject, identity } from 'lodash-es';
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
const IMAGE_WIDGETS_CLASSES_MATCH_REGEXP = /^(image|image-inline)$/;
|
|
132
|
-
|
|
132
|
+
/**
|
|
133
|
+
* A set of helpers related to images.
|
|
134
|
+
*/ class ImageUtils extends Plugin {
|
|
135
|
+
/**
|
|
136
|
+
* DOM Emitter.
|
|
137
|
+
*/ _domEmitter = new (DomEmitterMixin())();
|
|
133
138
|
/**
|
|
134
|
-
|
|
135
|
-
|
|
139
|
+
* @inheritDoc
|
|
140
|
+
*/ static get pluginName() {
|
|
136
141
|
return 'ImageUtils';
|
|
137
142
|
}
|
|
138
143
|
/**
|
|
139
|
-
|
|
140
|
-
|
|
144
|
+
* Checks if the provided model element is an `image` or `imageInline`.
|
|
145
|
+
*/ isImage(modelElement) {
|
|
141
146
|
return this.isInlineImage(modelElement) || this.isBlockImage(modelElement);
|
|
142
147
|
}
|
|
143
148
|
/**
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
149
|
+
* Checks if the provided view element represents an inline image.
|
|
150
|
+
*
|
|
151
|
+
* Also, see {@link module:image/imageutils~ImageUtils#isImageWidget}.
|
|
152
|
+
*/ isInlineImageView(element) {
|
|
148
153
|
return !!element && element.is('element', 'img');
|
|
149
154
|
}
|
|
150
155
|
/**
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
156
|
+
* Checks if the provided view element represents a block image.
|
|
157
|
+
*
|
|
158
|
+
* Also, see {@link module:image/imageutils~ImageUtils#isImageWidget}.
|
|
159
|
+
*/ isBlockImageView(element) {
|
|
155
160
|
return !!element && element.is('element', 'figure') && element.hasClass('image');
|
|
156
161
|
}
|
|
157
162
|
/**
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
163
|
+
* Handles inserting single file. This method unifies image insertion using {@link module:widget/utils~findOptimalInsertionRange}
|
|
164
|
+
* method.
|
|
165
|
+
*
|
|
166
|
+
* ```ts
|
|
167
|
+
* const imageUtils = editor.plugins.get( 'ImageUtils' );
|
|
168
|
+
*
|
|
169
|
+
* imageUtils.insertImage( { src: 'path/to/image.jpg' } );
|
|
170
|
+
* ```
|
|
171
|
+
*
|
|
172
|
+
* @param attributes Attributes of the inserted image.
|
|
173
|
+
* This method filters out the attributes which are disallowed by the {@link module:engine/model/schema~Schema}.
|
|
174
|
+
* @param selectable Place to insert the image. If not specified,
|
|
175
|
+
* the {@link module:widget/utils~findOptimalInsertionRange} logic will be applied for the block images
|
|
176
|
+
* and `model.document.selection` for the inline images.
|
|
177
|
+
*
|
|
178
|
+
* **Note**: If `selectable` is passed, this helper will not be able to set selection attributes (such as `linkHref`)
|
|
179
|
+
* and apply them to the new image. In this case, make sure all selection attributes are passed in `attributes`.
|
|
180
|
+
*
|
|
181
|
+
* @param imageType Image type of inserted image. If not specified,
|
|
182
|
+
* it will be determined automatically depending of editor config or place of the insertion.
|
|
183
|
+
* @param options.setImageSizes Specifies whether the image `width` and `height` attributes should be set automatically.
|
|
184
|
+
* The default is `true`.
|
|
185
|
+
* @return The inserted model image element.
|
|
186
|
+
*/ insertImage(attributes = {}, selectable = null, imageType = null, options = {}) {
|
|
182
187
|
const editor = this.editor;
|
|
183
188
|
const model = editor.model;
|
|
184
189
|
const selection = model.document.selection;
|
|
@@ -215,11 +220,11 @@ class ImageUtils extends Plugin {
|
|
|
215
220
|
});
|
|
216
221
|
}
|
|
217
222
|
/**
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
+
* Reads original image sizes and sets them as `width` and `height`.
|
|
224
|
+
*
|
|
225
|
+
* The `src` attribute may not be available if the user is using an upload adapter. In such a case,
|
|
226
|
+
* this method is called again after the upload process is complete and the `src` attribute is available.
|
|
227
|
+
*/ setImageNaturalSizeAttributes(imageElement) {
|
|
223
228
|
const src = imageElement.getAttribute('src');
|
|
224
229
|
if (!src) {
|
|
225
230
|
return;
|
|
@@ -244,8 +249,8 @@ class ImageUtils extends Plugin {
|
|
|
244
249
|
});
|
|
245
250
|
}
|
|
246
251
|
/**
|
|
247
|
-
|
|
248
|
-
|
|
252
|
+
* Returns an image widget editing view element if one is selected or is among the selection's ancestors.
|
|
253
|
+
*/ getClosestSelectedImageWidget(selection) {
|
|
249
254
|
const selectionPosition = selection.getFirstPosition();
|
|
250
255
|
if (!selectionPosition) {
|
|
251
256
|
return null;
|
|
@@ -264,36 +269,36 @@ class ImageUtils extends Plugin {
|
|
|
264
269
|
return null;
|
|
265
270
|
}
|
|
266
271
|
/**
|
|
267
|
-
|
|
268
|
-
|
|
272
|
+
* Returns a image model element if one is selected or is among the selection's ancestors.
|
|
273
|
+
*/ getClosestSelectedImageElement(selection) {
|
|
269
274
|
const selectedElement = selection.getSelectedElement();
|
|
270
275
|
return this.isImage(selectedElement) ? selectedElement : selection.getFirstPosition().findAncestor('imageBlock');
|
|
271
276
|
}
|
|
272
277
|
/**
|
|
273
|
-
|
|
274
|
-
|
|
278
|
+
* Returns an image widget editing view based on the passed image view.
|
|
279
|
+
*/ getImageWidgetFromImageView(imageView) {
|
|
275
280
|
return imageView.findAncestor({
|
|
276
281
|
classes: IMAGE_WIDGETS_CLASSES_MATCH_REGEXP
|
|
277
282
|
});
|
|
278
283
|
}
|
|
279
284
|
/**
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
285
|
+
* Checks if image can be inserted at current model selection.
|
|
286
|
+
*
|
|
287
|
+
* @internal
|
|
288
|
+
*/ isImageAllowed() {
|
|
284
289
|
const model = this.editor.model;
|
|
285
290
|
const selection = model.document.selection;
|
|
286
291
|
return isImageAllowedInParent(this.editor, selection) && isNotInsideImage(selection);
|
|
287
292
|
}
|
|
288
293
|
/**
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
294
|
+
* Converts a given {@link module:engine/view/element~Element} to an image widget:
|
|
295
|
+
* * Adds a {@link module:engine/view/element~Element#_setCustomProperty custom property} allowing to recognize the image widget
|
|
296
|
+
* element.
|
|
297
|
+
* * Calls the {@link module:widget/utils~toWidget} function with the proper element's label creator.
|
|
298
|
+
*
|
|
299
|
+
* @param writer An instance of the view writer.
|
|
300
|
+
* @param label The element's label. It will be concatenated with the image `alt` attribute if one is present.
|
|
301
|
+
*/ toImageWidget(viewElement, writer, label) {
|
|
297
302
|
writer.setCustomProperty('image', true, viewElement);
|
|
298
303
|
const labelCreator = ()=>{
|
|
299
304
|
const imgElement = this.findViewImgElement(viewElement);
|
|
@@ -305,25 +310,25 @@ class ImageUtils extends Plugin {
|
|
|
305
310
|
});
|
|
306
311
|
}
|
|
307
312
|
/**
|
|
308
|
-
|
|
309
|
-
|
|
313
|
+
* Checks if a given view element is an image widget.
|
|
314
|
+
*/ isImageWidget(viewElement) {
|
|
310
315
|
return !!viewElement.getCustomProperty('image') && isWidget(viewElement);
|
|
311
316
|
}
|
|
312
317
|
/**
|
|
313
|
-
|
|
314
|
-
|
|
318
|
+
* Checks if the provided model element is an `image`.
|
|
319
|
+
*/ isBlockImage(modelElement) {
|
|
315
320
|
return !!modelElement && modelElement.is('element', 'imageBlock');
|
|
316
321
|
}
|
|
317
322
|
/**
|
|
318
|
-
|
|
319
|
-
|
|
323
|
+
* Checks if the provided model element is an `imageInline`.
|
|
324
|
+
*/ isInlineImage(modelElement) {
|
|
320
325
|
return !!modelElement && modelElement.is('element', 'imageInline');
|
|
321
326
|
}
|
|
322
327
|
/**
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
328
|
+
* Get the view `<img>` from another view element, e.g. a widget (`<figure class="image">`), a link (`<a>`).
|
|
329
|
+
*
|
|
330
|
+
* The `<img>` can be located deep in other elements, so this helper performs a deep tree search.
|
|
331
|
+
*/ findViewImgElement(figureView) {
|
|
327
332
|
if (this.isInlineImageView(figureView)) {
|
|
328
333
|
return figureView;
|
|
329
334
|
}
|
|
@@ -335,17 +340,11 @@ class ImageUtils extends Plugin {
|
|
|
335
340
|
}
|
|
336
341
|
}
|
|
337
342
|
/**
|
|
338
|
-
|
|
339
|
-
|
|
343
|
+
* @inheritDoc
|
|
344
|
+
*/ destroy() {
|
|
340
345
|
this._domEmitter.stopListening();
|
|
341
346
|
return super.destroy();
|
|
342
347
|
}
|
|
343
|
-
constructor(){
|
|
344
|
-
super(...arguments);
|
|
345
|
-
/**
|
|
346
|
-
* DOM Emitter.
|
|
347
|
-
*/ this._domEmitter = new (DomEmitterMixin())();
|
|
348
|
-
}
|
|
349
348
|
}
|
|
350
349
|
/**
|
|
351
350
|
* Checks if image is allowed by schema in optimal insertion parent.
|
|
@@ -410,10 +409,13 @@ class ImageUtils extends Plugin {
|
|
|
410
409
|
|
|
411
410
|
// Implements the pattern: http(s)://(www.)example.com/path/to/resource.ext?query=params&maybe=too.
|
|
412
411
|
const IMAGE_URL_REGEXP = new RegExp(String(/^(http(s)?:\/\/)?[\w-]+\.[\w.~:/[\]@!$&'()*+,;=%-]+/.source + /\.(jpg|jpeg|png|gif|ico|webp|JPG|JPEG|PNG|GIF|ICO|WEBP)/.source + /(\?[\w.~:/[\]@!$&'()*+,;=%-]*)?/.source + /(#[\w.~:/[\]@!$&'()*+,;=%-]*)?$/.source));
|
|
413
|
-
|
|
412
|
+
/**
|
|
413
|
+
* The auto-image plugin. It recognizes image links in the pasted content and embeds
|
|
414
|
+
* them shortly after they are injected into the document.
|
|
415
|
+
*/ class AutoImage extends Plugin {
|
|
414
416
|
/**
|
|
415
|
-
|
|
416
|
-
|
|
417
|
+
* @inheritDoc
|
|
418
|
+
*/ static get requires() {
|
|
417
419
|
return [
|
|
418
420
|
Clipboard,
|
|
419
421
|
ImageUtils,
|
|
@@ -422,13 +424,28 @@ class AutoImage extends Plugin {
|
|
|
422
424
|
];
|
|
423
425
|
}
|
|
424
426
|
/**
|
|
425
|
-
|
|
426
|
-
|
|
427
|
+
* @inheritDoc
|
|
428
|
+
*/ static get pluginName() {
|
|
427
429
|
return 'AutoImage';
|
|
428
430
|
}
|
|
429
431
|
/**
|
|
430
|
-
|
|
431
|
-
|
|
432
|
+
* The paste–to–embed `setTimeout` ID. Stored as a property to allow
|
|
433
|
+
* cleaning of the timeout.
|
|
434
|
+
*/ _timeoutId;
|
|
435
|
+
/**
|
|
436
|
+
* The position where the `<imageBlock>` element will be inserted after the timeout,
|
|
437
|
+
* determined each time a new content is pasted into the document.
|
|
438
|
+
*/ _positionToInsert;
|
|
439
|
+
/**
|
|
440
|
+
* @inheritDoc
|
|
441
|
+
*/ constructor(editor){
|
|
442
|
+
super(editor);
|
|
443
|
+
this._timeoutId = null;
|
|
444
|
+
this._positionToInsert = null;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* @inheritDoc
|
|
448
|
+
*/ init() {
|
|
432
449
|
const editor = this.editor;
|
|
433
450
|
const modelDocument = editor.model.document;
|
|
434
451
|
const clipboardPipeline = editor.plugins.get('ClipboardPipeline');
|
|
@@ -461,12 +478,12 @@ class AutoImage extends Plugin {
|
|
|
461
478
|
});
|
|
462
479
|
}
|
|
463
480
|
/**
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
481
|
+
* Analyzes the part of the document between provided positions in search for a URL representing an image.
|
|
482
|
+
* When the URL is found, it is automatically converted into an image.
|
|
483
|
+
*
|
|
484
|
+
* @param leftPosition Left position of the selection.
|
|
485
|
+
* @param rightPosition Right position of the selection.
|
|
486
|
+
*/ _embedImageBetweenPositions(leftPosition, rightPosition) {
|
|
470
487
|
const editor = this.editor;
|
|
471
488
|
// TODO: Use a marker instead of LiveRange & LivePositions.
|
|
472
489
|
const urlRange = new LiveRange(leftPosition, rightPosition);
|
|
@@ -520,19 +537,14 @@ class AutoImage extends Plugin {
|
|
|
520
537
|
deletePlugin.requestUndoOnBackspace();
|
|
521
538
|
}, 100);
|
|
522
539
|
}
|
|
523
|
-
/**
|
|
524
|
-
* @inheritDoc
|
|
525
|
-
*/ constructor(editor){
|
|
526
|
-
super(editor);
|
|
527
|
-
this._timeoutId = null;
|
|
528
|
-
this._positionToInsert = null;
|
|
529
|
-
}
|
|
530
540
|
}
|
|
531
541
|
|
|
532
|
-
|
|
542
|
+
/**
|
|
543
|
+
* The image text alternative command. It is used to change the `alt` attribute of `<imageBlock>` and `<imageInline>` model elements.
|
|
544
|
+
*/ class ImageTextAlternativeCommand extends Command {
|
|
533
545
|
/**
|
|
534
|
-
|
|
535
|
-
|
|
546
|
+
* @inheritDoc
|
|
547
|
+
*/ refresh() {
|
|
536
548
|
const editor = this.editor;
|
|
537
549
|
const imageUtils = editor.plugins.get('ImageUtils');
|
|
538
550
|
const element = imageUtils.getClosestSelectedImageElement(this.editor.model.document.selection);
|
|
@@ -544,12 +556,12 @@ class ImageTextAlternativeCommand extends Command {
|
|
|
544
556
|
}
|
|
545
557
|
}
|
|
546
558
|
/**
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
559
|
+
* Executes the command.
|
|
560
|
+
*
|
|
561
|
+
* @fires execute
|
|
562
|
+
* @param options
|
|
563
|
+
* @param options.newValue The new value of the `alt` attribute to set.
|
|
564
|
+
*/ execute(options) {
|
|
553
565
|
const editor = this.editor;
|
|
554
566
|
const imageUtils = editor.plugins.get('ImageUtils');
|
|
555
567
|
const model = editor.model;
|
|
@@ -560,30 +572,98 @@ class ImageTextAlternativeCommand extends Command {
|
|
|
560
572
|
}
|
|
561
573
|
}
|
|
562
574
|
|
|
563
|
-
|
|
575
|
+
/**
|
|
576
|
+
* The image text alternative editing plugin.
|
|
577
|
+
*
|
|
578
|
+
* Registers the `'imageTextAlternative'` command.
|
|
579
|
+
*/ class ImageTextAlternativeEditing extends Plugin {
|
|
564
580
|
/**
|
|
565
|
-
|
|
566
|
-
|
|
581
|
+
* @inheritDoc
|
|
582
|
+
*/ static get requires() {
|
|
567
583
|
return [
|
|
568
584
|
ImageUtils
|
|
569
585
|
];
|
|
570
586
|
}
|
|
571
587
|
/**
|
|
572
|
-
|
|
573
|
-
|
|
588
|
+
* @inheritDoc
|
|
589
|
+
*/ static get pluginName() {
|
|
574
590
|
return 'ImageTextAlternativeEditing';
|
|
575
591
|
}
|
|
576
592
|
/**
|
|
577
|
-
|
|
578
|
-
|
|
593
|
+
* @inheritDoc
|
|
594
|
+
*/ init() {
|
|
579
595
|
this.editor.commands.add('imageTextAlternative', new ImageTextAlternativeCommand(this.editor));
|
|
580
596
|
}
|
|
581
597
|
}
|
|
582
598
|
|
|
583
|
-
|
|
599
|
+
/**
|
|
600
|
+
* The TextAlternativeFormView class.
|
|
601
|
+
*/ class TextAlternativeFormView extends View {
|
|
602
|
+
/**
|
|
603
|
+
* Tracks information about the DOM focus in the form.
|
|
604
|
+
*/ focusTracker;
|
|
605
|
+
/**
|
|
606
|
+
* An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
|
|
607
|
+
*/ keystrokes;
|
|
608
|
+
/**
|
|
609
|
+
* An input with a label.
|
|
610
|
+
*/ labeledInput;
|
|
611
|
+
/**
|
|
612
|
+
* A button used to submit the form.
|
|
613
|
+
*/ saveButtonView;
|
|
614
|
+
/**
|
|
615
|
+
* A button used to cancel the form.
|
|
616
|
+
*/ cancelButtonView;
|
|
617
|
+
/**
|
|
618
|
+
* A collection of views which can be focused in the form.
|
|
619
|
+
*/ _focusables;
|
|
620
|
+
/**
|
|
621
|
+
* Helps cycling over {@link #_focusables} in the form.
|
|
622
|
+
*/ _focusCycler;
|
|
623
|
+
/**
|
|
624
|
+
* @inheritDoc
|
|
625
|
+
*/ constructor(locale){
|
|
626
|
+
super(locale);
|
|
627
|
+
const t = this.locale.t;
|
|
628
|
+
this.focusTracker = new FocusTracker();
|
|
629
|
+
this.keystrokes = new KeystrokeHandler();
|
|
630
|
+
this.labeledInput = this._createLabeledInputView();
|
|
631
|
+
this.saveButtonView = this._createButton(t('Save'), icons.check, 'ck-button-save');
|
|
632
|
+
this.saveButtonView.type = 'submit';
|
|
633
|
+
this.cancelButtonView = this._createButton(t('Cancel'), icons.cancel, 'ck-button-cancel', 'cancel');
|
|
634
|
+
this._focusables = new ViewCollection();
|
|
635
|
+
this._focusCycler = new FocusCycler({
|
|
636
|
+
focusables: this._focusables,
|
|
637
|
+
focusTracker: this.focusTracker,
|
|
638
|
+
keystrokeHandler: this.keystrokes,
|
|
639
|
+
actions: {
|
|
640
|
+
// Navigate form fields backwards using the Shift + Tab keystroke.
|
|
641
|
+
focusPrevious: 'shift + tab',
|
|
642
|
+
// Navigate form fields forwards using the Tab key.
|
|
643
|
+
focusNext: 'tab'
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
this.setTemplate({
|
|
647
|
+
tag: 'form',
|
|
648
|
+
attributes: {
|
|
649
|
+
class: [
|
|
650
|
+
'ck',
|
|
651
|
+
'ck-text-alternative-form',
|
|
652
|
+
'ck-responsive-form'
|
|
653
|
+
],
|
|
654
|
+
// https://github.com/ckeditor/ckeditor5-image/issues/40
|
|
655
|
+
tabindex: '-1'
|
|
656
|
+
},
|
|
657
|
+
children: [
|
|
658
|
+
this.labeledInput,
|
|
659
|
+
this.saveButtonView,
|
|
660
|
+
this.cancelButtonView
|
|
661
|
+
]
|
|
662
|
+
});
|
|
663
|
+
}
|
|
584
664
|
/**
|
|
585
|
-
|
|
586
|
-
|
|
665
|
+
* @inheritDoc
|
|
666
|
+
*/ render() {
|
|
587
667
|
super.render();
|
|
588
668
|
this.keystrokes.listenTo(this.element);
|
|
589
669
|
submitHandler({
|
|
@@ -601,21 +681,21 @@ class TextAlternativeFormView extends View {
|
|
|
601
681
|
});
|
|
602
682
|
}
|
|
603
683
|
/**
|
|
604
|
-
|
|
605
|
-
|
|
684
|
+
* @inheritDoc
|
|
685
|
+
*/ destroy() {
|
|
606
686
|
super.destroy();
|
|
607
687
|
this.focusTracker.destroy();
|
|
608
688
|
this.keystrokes.destroy();
|
|
609
689
|
}
|
|
610
690
|
/**
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
691
|
+
* Creates the button view.
|
|
692
|
+
*
|
|
693
|
+
* @param label The button label
|
|
694
|
+
* @param icon The button's icon.
|
|
695
|
+
* @param className The additional button CSS class name.
|
|
696
|
+
* @param eventName The event name that the ButtonView#execute event will be delegated to.
|
|
697
|
+
* @returns The button view instance.
|
|
698
|
+
*/ _createButton(label, icon, className, eventName) {
|
|
619
699
|
const button = new ButtonView(this.locale);
|
|
620
700
|
button.set({
|
|
621
701
|
label,
|
|
@@ -633,56 +713,15 @@ class TextAlternativeFormView extends View {
|
|
|
633
713
|
return button;
|
|
634
714
|
}
|
|
635
715
|
/**
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
716
|
+
* Creates an input with a label.
|
|
717
|
+
*
|
|
718
|
+
* @returns Labeled field view instance.
|
|
719
|
+
*/ _createLabeledInputView() {
|
|
640
720
|
const t = this.locale.t;
|
|
641
721
|
const labeledInput = new LabeledFieldView(this.locale, createLabeledInputText);
|
|
642
722
|
labeledInput.label = t('Text alternative');
|
|
643
723
|
return labeledInput;
|
|
644
724
|
}
|
|
645
|
-
/**
|
|
646
|
-
* @inheritDoc
|
|
647
|
-
*/ constructor(locale){
|
|
648
|
-
super(locale);
|
|
649
|
-
const t = this.locale.t;
|
|
650
|
-
this.focusTracker = new FocusTracker();
|
|
651
|
-
this.keystrokes = new KeystrokeHandler();
|
|
652
|
-
this.labeledInput = this._createLabeledInputView();
|
|
653
|
-
this.saveButtonView = this._createButton(t('Save'), icons.check, 'ck-button-save');
|
|
654
|
-
this.saveButtonView.type = 'submit';
|
|
655
|
-
this.cancelButtonView = this._createButton(t('Cancel'), icons.cancel, 'ck-button-cancel', 'cancel');
|
|
656
|
-
this._focusables = new ViewCollection();
|
|
657
|
-
this._focusCycler = new FocusCycler({
|
|
658
|
-
focusables: this._focusables,
|
|
659
|
-
focusTracker: this.focusTracker,
|
|
660
|
-
keystrokeHandler: this.keystrokes,
|
|
661
|
-
actions: {
|
|
662
|
-
// Navigate form fields backwards using the Shift + Tab keystroke.
|
|
663
|
-
focusPrevious: 'shift + tab',
|
|
664
|
-
// Navigate form fields forwards using the Tab key.
|
|
665
|
-
focusNext: 'tab'
|
|
666
|
-
}
|
|
667
|
-
});
|
|
668
|
-
this.setTemplate({
|
|
669
|
-
tag: 'form',
|
|
670
|
-
attributes: {
|
|
671
|
-
class: [
|
|
672
|
-
'ck',
|
|
673
|
-
'ck-text-alternative-form',
|
|
674
|
-
'ck-responsive-form'
|
|
675
|
-
],
|
|
676
|
-
// https://github.com/ckeditor/ckeditor5-image/issues/40
|
|
677
|
-
tabindex: '-1'
|
|
678
|
-
},
|
|
679
|
-
children: [
|
|
680
|
-
this.labeledInput,
|
|
681
|
-
this.saveButtonView,
|
|
682
|
-
this.cancelButtonView
|
|
683
|
-
]
|
|
684
|
-
});
|
|
685
|
-
}
|
|
686
725
|
}
|
|
687
726
|
|
|
688
727
|
/**
|
|
@@ -723,27 +762,37 @@ class TextAlternativeFormView extends View {
|
|
|
723
762
|
};
|
|
724
763
|
}
|
|
725
764
|
|
|
726
|
-
|
|
765
|
+
/**
|
|
766
|
+
* The image text alternative UI plugin.
|
|
767
|
+
*
|
|
768
|
+
* The plugin uses the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon}.
|
|
769
|
+
*/ class ImageTextAlternativeUI extends Plugin {
|
|
727
770
|
/**
|
|
728
|
-
|
|
729
|
-
|
|
771
|
+
* The contextual balloon plugin instance.
|
|
772
|
+
*/ _balloon;
|
|
773
|
+
/**
|
|
774
|
+
* A form containing a textarea and buttons, used to change the `alt` text value.
|
|
775
|
+
*/ _form;
|
|
776
|
+
/**
|
|
777
|
+
* @inheritDoc
|
|
778
|
+
*/ static get requires() {
|
|
730
779
|
return [
|
|
731
780
|
ContextualBalloon
|
|
732
781
|
];
|
|
733
782
|
}
|
|
734
783
|
/**
|
|
735
|
-
|
|
736
|
-
|
|
784
|
+
* @inheritDoc
|
|
785
|
+
*/ static get pluginName() {
|
|
737
786
|
return 'ImageTextAlternativeUI';
|
|
738
787
|
}
|
|
739
788
|
/**
|
|
740
|
-
|
|
741
|
-
|
|
789
|
+
* @inheritDoc
|
|
790
|
+
*/ init() {
|
|
742
791
|
this._createButton();
|
|
743
792
|
}
|
|
744
793
|
/**
|
|
745
|
-
|
|
746
|
-
|
|
794
|
+
* @inheritDoc
|
|
795
|
+
*/ destroy() {
|
|
747
796
|
super.destroy();
|
|
748
797
|
// Destroy created UI components as they are not automatically destroyed (see ckeditor5#1341).
|
|
749
798
|
if (this._form) {
|
|
@@ -751,9 +800,9 @@ class ImageTextAlternativeUI extends Plugin {
|
|
|
751
800
|
}
|
|
752
801
|
}
|
|
753
802
|
/**
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
803
|
+
* Creates a button showing the balloon panel for changing the image text alternative and
|
|
804
|
+
* registers it in the editor {@link module:ui/componentfactory~ComponentFactory ComponentFactory}.
|
|
805
|
+
*/ _createButton() {
|
|
757
806
|
const editor = this.editor;
|
|
758
807
|
const t = editor.t;
|
|
759
808
|
editor.ui.componentFactory.add('imageTextAlternative', (locale)=>{
|
|
@@ -773,9 +822,9 @@ class ImageTextAlternativeUI extends Plugin {
|
|
|
773
822
|
});
|
|
774
823
|
}
|
|
775
824
|
/**
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
825
|
+
* Creates the {@link module:image/imagetextalternative/ui/textalternativeformview~TextAlternativeFormView}
|
|
826
|
+
* form.
|
|
827
|
+
*/ _createForm() {
|
|
779
828
|
const editor = this.editor;
|
|
780
829
|
const view = editor.editing.view;
|
|
781
830
|
const viewDocument = view.document;
|
|
@@ -817,8 +866,8 @@ class ImageTextAlternativeUI extends Plugin {
|
|
|
817
866
|
});
|
|
818
867
|
}
|
|
819
868
|
/**
|
|
820
|
-
|
|
821
|
-
|
|
869
|
+
* Shows the {@link #_form} in the {@link #_balloon}.
|
|
870
|
+
*/ _showForm() {
|
|
822
871
|
if (this._isVisible) {
|
|
823
872
|
return;
|
|
824
873
|
}
|
|
@@ -845,10 +894,10 @@ class ImageTextAlternativeUI extends Plugin {
|
|
|
845
894
|
this._form.enableCssTransitions();
|
|
846
895
|
}
|
|
847
896
|
/**
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
897
|
+
* Removes the {@link #_form} from the {@link #_balloon}.
|
|
898
|
+
*
|
|
899
|
+
* @param focusEditable Controls whether the editing view is focused afterwards.
|
|
900
|
+
*/ _hideForm(focusEditable = false) {
|
|
852
901
|
if (!this._isInBalloon) {
|
|
853
902
|
return;
|
|
854
903
|
}
|
|
@@ -863,29 +912,37 @@ class ImageTextAlternativeUI extends Plugin {
|
|
|
863
912
|
}
|
|
864
913
|
}
|
|
865
914
|
/**
|
|
866
|
-
|
|
867
|
-
|
|
915
|
+
* Returns `true` when the {@link #_form} is the visible view in the {@link #_balloon}.
|
|
916
|
+
*/ get _isVisible() {
|
|
868
917
|
return !!this._balloon && this._balloon.visibleView === this._form;
|
|
869
918
|
}
|
|
870
919
|
/**
|
|
871
|
-
|
|
872
|
-
|
|
920
|
+
* Returns `true` when the {@link #_form} is in the {@link #_balloon}.
|
|
921
|
+
*/ get _isInBalloon() {
|
|
873
922
|
return !!this._balloon && this._balloon.hasView(this._form);
|
|
874
923
|
}
|
|
875
924
|
}
|
|
876
925
|
|
|
877
|
-
|
|
926
|
+
/**
|
|
927
|
+
* The image text alternative plugin.
|
|
928
|
+
*
|
|
929
|
+
* For a detailed overview, check the {@glink features/images/images-styles image styles} documentation.
|
|
930
|
+
*
|
|
931
|
+
* This is a "glue" plugin which loads the
|
|
932
|
+
* {@link module:image/imagetextalternative/imagetextalternativeediting~ImageTextAlternativeEditing}
|
|
933
|
+
* and {@link module:image/imagetextalternative/imagetextalternativeui~ImageTextAlternativeUI} plugins.
|
|
934
|
+
*/ class ImageTextAlternative extends Plugin {
|
|
878
935
|
/**
|
|
879
|
-
|
|
880
|
-
|
|
936
|
+
* @inheritDoc
|
|
937
|
+
*/ static get requires() {
|
|
881
938
|
return [
|
|
882
939
|
ImageTextAlternativeEditing,
|
|
883
940
|
ImageTextAlternativeUI
|
|
884
941
|
];
|
|
885
942
|
}
|
|
886
943
|
/**
|
|
887
|
-
|
|
888
|
-
|
|
944
|
+
* @inheritDoc
|
|
945
|
+
*/ static get pluginName() {
|
|
889
946
|
return 'ImageTextAlternative';
|
|
890
947
|
}
|
|
891
948
|
}
|
|
@@ -1133,10 +1190,17 @@ class ImageTextAlternative extends Plugin {
|
|
|
1133
1190
|
};
|
|
1134
1191
|
}
|
|
1135
1192
|
|
|
1136
|
-
|
|
1193
|
+
/**
|
|
1194
|
+
* Observes all new images added to the {@link module:engine/view/document~Document},
|
|
1195
|
+
* fires {@link module:engine/view/document~Document#event:imageLoaded} and
|
|
1196
|
+
* {@link module:engine/view/document~Document#event:layoutChanged} event every time when the new image
|
|
1197
|
+
* has been loaded.
|
|
1198
|
+
*
|
|
1199
|
+
* **Note:** This event is not fired for images that has been added to the document and rendered as `complete` (already loaded).
|
|
1200
|
+
*/ class ImageLoadObserver extends Observer {
|
|
1137
1201
|
/**
|
|
1138
|
-
|
|
1139
|
-
|
|
1202
|
+
* @inheritDoc
|
|
1203
|
+
*/ observe(domRoot) {
|
|
1140
1204
|
this.listenTo(domRoot, 'load', (event, domEvent)=>{
|
|
1141
1205
|
const domElement = domEvent.target;
|
|
1142
1206
|
if (this.checkShouldIgnoreEventFromTarget(domElement)) {
|
|
@@ -1151,17 +1215,17 @@ class ImageLoadObserver extends Observer {
|
|
|
1151
1215
|
});
|
|
1152
1216
|
}
|
|
1153
1217
|
/**
|
|
1154
|
-
|
|
1155
|
-
|
|
1218
|
+
* @inheritDoc
|
|
1219
|
+
*/ stopObserving(domRoot) {
|
|
1156
1220
|
this.stopListening(domRoot);
|
|
1157
1221
|
}
|
|
1158
1222
|
/**
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1223
|
+
* Fires {@link module:engine/view/document~Document#event:layoutChanged} and
|
|
1224
|
+
* {@link module:engine/view/document~Document#event:imageLoaded}
|
|
1225
|
+
* if observer {@link #isEnabled is enabled}.
|
|
1226
|
+
*
|
|
1227
|
+
* @param domEvent The DOM event.
|
|
1228
|
+
*/ _fireEvents(domEvent) {
|
|
1165
1229
|
if (this.isEnabled) {
|
|
1166
1230
|
this.document.fire('layoutChanged');
|
|
1167
1231
|
this.document.fire('imageLoaded', domEvent);
|
|
@@ -1169,21 +1233,82 @@ class ImageLoadObserver extends Observer {
|
|
|
1169
1233
|
}
|
|
1170
1234
|
}
|
|
1171
1235
|
|
|
1172
|
-
|
|
1236
|
+
/**
|
|
1237
|
+
* Insert image command.
|
|
1238
|
+
*
|
|
1239
|
+
* The command is registered by the {@link module:image/image/imageediting~ImageEditing} plugin as `insertImage`
|
|
1240
|
+
* and it is also available via aliased `imageInsert` name.
|
|
1241
|
+
*
|
|
1242
|
+
* In order to insert an image at the current selection position
|
|
1243
|
+
* (according to the {@link module:widget/utils~findOptimalInsertionRange} algorithm),
|
|
1244
|
+
* execute the command and specify the image source:
|
|
1245
|
+
*
|
|
1246
|
+
* ```ts
|
|
1247
|
+
* editor.execute( 'insertImage', { source: 'http://url.to.the/image' } );
|
|
1248
|
+
* ```
|
|
1249
|
+
*
|
|
1250
|
+
* It is also possible to insert multiple images at once:
|
|
1251
|
+
*
|
|
1252
|
+
* ```ts
|
|
1253
|
+
* editor.execute( 'insertImage', {
|
|
1254
|
+
* source: [
|
|
1255
|
+
* 'path/to/image.jpg',
|
|
1256
|
+
* 'path/to/other-image.jpg'
|
|
1257
|
+
* ]
|
|
1258
|
+
* } );
|
|
1259
|
+
* ```
|
|
1260
|
+
*
|
|
1261
|
+
* If you want to take the full control over the process, you can specify individual model attributes:
|
|
1262
|
+
*
|
|
1263
|
+
* ```ts
|
|
1264
|
+
* editor.execute( 'insertImage', {
|
|
1265
|
+
* source: [
|
|
1266
|
+
* { src: 'path/to/image.jpg', alt: 'First alt text' },
|
|
1267
|
+
* { src: 'path/to/other-image.jpg', alt: 'Second alt text', customAttribute: 'My attribute value' }
|
|
1268
|
+
* ]
|
|
1269
|
+
* } );
|
|
1270
|
+
* ```
|
|
1271
|
+
*/ class InsertImageCommand extends Command {
|
|
1272
|
+
/**
|
|
1273
|
+
* @inheritDoc
|
|
1274
|
+
*/ constructor(editor){
|
|
1275
|
+
super(editor);
|
|
1276
|
+
const configImageInsertType = editor.config.get('image.insert.type');
|
|
1277
|
+
if (!editor.plugins.has('ImageBlockEditing')) {
|
|
1278
|
+
if (configImageInsertType === 'block') {
|
|
1279
|
+
/**
|
|
1280
|
+
* The {@link module:image/imageblock~ImageBlock} plugin must be enabled to allow inserting block images. See
|
|
1281
|
+
* {@link module:image/imageconfig~ImageInsertConfig#type} to learn more.
|
|
1282
|
+
*
|
|
1283
|
+
* @error image-block-plugin-required
|
|
1284
|
+
*/ logWarning('image-block-plugin-required');
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
if (!editor.plugins.has('ImageInlineEditing')) {
|
|
1288
|
+
if (configImageInsertType === 'inline') {
|
|
1289
|
+
/**
|
|
1290
|
+
* The {@link module:image/imageinline~ImageInline} plugin must be enabled to allow inserting inline images. See
|
|
1291
|
+
* {@link module:image/imageconfig~ImageInsertConfig#type} to learn more.
|
|
1292
|
+
*
|
|
1293
|
+
* @error image-inline-plugin-required
|
|
1294
|
+
*/ logWarning('image-inline-plugin-required');
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1173
1298
|
/**
|
|
1174
|
-
|
|
1175
|
-
|
|
1299
|
+
* @inheritDoc
|
|
1300
|
+
*/ refresh() {
|
|
1176
1301
|
const imageUtils = this.editor.plugins.get('ImageUtils');
|
|
1177
1302
|
this.isEnabled = imageUtils.isImageAllowed();
|
|
1178
1303
|
}
|
|
1179
1304
|
/**
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1305
|
+
* Executes the command.
|
|
1306
|
+
*
|
|
1307
|
+
* @fires execute
|
|
1308
|
+
* @param options Options for the executed command.
|
|
1309
|
+
* @param options.source The image source or an array of image sources to insert.
|
|
1310
|
+
* See the documentation of the command to learn more about accepted formats.
|
|
1311
|
+
*/ execute(options) {
|
|
1187
1312
|
const sourceDefinitions = toArray(options.source);
|
|
1188
1313
|
const selection = this.editor.model.document.selection;
|
|
1189
1314
|
const imageUtils = this.editor.plugins.get('ImageUtils');
|
|
@@ -1219,38 +1344,26 @@ class InsertImageCommand extends Command {
|
|
|
1219
1344
|
}
|
|
1220
1345
|
});
|
|
1221
1346
|
}
|
|
1222
|
-
/**
|
|
1223
|
-
* @inheritDoc
|
|
1224
|
-
*/ constructor(editor){
|
|
1225
|
-
super(editor);
|
|
1226
|
-
const configImageInsertType = editor.config.get('image.insert.type');
|
|
1227
|
-
if (!editor.plugins.has('ImageBlockEditing')) {
|
|
1228
|
-
if (configImageInsertType === 'block') {
|
|
1229
|
-
/**
|
|
1230
|
-
* The {@link module:image/imageblock~ImageBlock} plugin must be enabled to allow inserting block images. See
|
|
1231
|
-
* {@link module:image/imageconfig~ImageInsertConfig#type} to learn more.
|
|
1232
|
-
*
|
|
1233
|
-
* @error image-block-plugin-required
|
|
1234
|
-
*/ logWarning('image-block-plugin-required');
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
if (!editor.plugins.has('ImageInlineEditing')) {
|
|
1238
|
-
if (configImageInsertType === 'inline') {
|
|
1239
|
-
/**
|
|
1240
|
-
* The {@link module:image/imageinline~ImageInline} plugin must be enabled to allow inserting inline images. See
|
|
1241
|
-
* {@link module:image/imageconfig~ImageInsertConfig#type} to learn more.
|
|
1242
|
-
*
|
|
1243
|
-
* @error image-inline-plugin-required
|
|
1244
|
-
*/ logWarning('image-inline-plugin-required');
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
1347
|
}
|
|
1249
1348
|
|
|
1250
|
-
|
|
1349
|
+
/**
|
|
1350
|
+
* @module image/image/replaceimagesourcecommand
|
|
1351
|
+
*/ /**
|
|
1352
|
+
* Replace image source command.
|
|
1353
|
+
*
|
|
1354
|
+
* Changes image source to the one provided. Can be executed as follows:
|
|
1355
|
+
*
|
|
1356
|
+
* ```ts
|
|
1357
|
+
* editor.execute( 'replaceImageSource', { source: 'http://url.to.the/image' } );
|
|
1358
|
+
* ```
|
|
1359
|
+
*/ class ReplaceImageSourceCommand extends Command {
|
|
1360
|
+
constructor(editor){
|
|
1361
|
+
super(editor);
|
|
1362
|
+
this.decorate('cleanupImage');
|
|
1363
|
+
}
|
|
1251
1364
|
/**
|
|
1252
|
-
|
|
1253
|
-
|
|
1365
|
+
* @inheritDoc
|
|
1366
|
+
*/ refresh() {
|
|
1254
1367
|
const editor = this.editor;
|
|
1255
1368
|
const imageUtils = editor.plugins.get('ImageUtils');
|
|
1256
1369
|
const element = this.editor.model.document.selection.getSelectedElement();
|
|
@@ -1258,12 +1371,12 @@ class ReplaceImageSourceCommand extends Command {
|
|
|
1258
1371
|
this.value = this.isEnabled ? element.getAttribute('src') : null;
|
|
1259
1372
|
}
|
|
1260
1373
|
/**
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1374
|
+
* Executes the command.
|
|
1375
|
+
*
|
|
1376
|
+
* @fires execute
|
|
1377
|
+
* @param options Options for the executed command.
|
|
1378
|
+
* @param options.source The image source to replace.
|
|
1379
|
+
*/ execute(options) {
|
|
1267
1380
|
const image = this.editor.model.document.selection.getSelectedElement();
|
|
1268
1381
|
const imageUtils = this.editor.plugins.get('ImageUtils');
|
|
1269
1382
|
this.editor.model.change((writer)=>{
|
|
@@ -1273,51 +1386,53 @@ class ReplaceImageSourceCommand extends Command {
|
|
|
1273
1386
|
});
|
|
1274
1387
|
}
|
|
1275
1388
|
/**
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1389
|
+
* Cleanup image attributes that are not relevant to the new source.
|
|
1390
|
+
*
|
|
1391
|
+
* Removed attributes are: 'srcset', 'sizes', 'sources', 'width', 'height', 'alt'.
|
|
1392
|
+
*
|
|
1393
|
+
* This method is decorated, to allow custom cleanup logic.
|
|
1394
|
+
* For example, to remove 'myImageId' attribute after 'src' has changed:
|
|
1395
|
+
*
|
|
1396
|
+
* ```ts
|
|
1397
|
+
* replaceImageSourceCommand.on( 'cleanupImage', ( eventInfo, [ writer, image ] ) => {
|
|
1398
|
+
* writer.removeAttribute( 'myImageId', image );
|
|
1399
|
+
* } );
|
|
1400
|
+
* ```
|
|
1401
|
+
*/ cleanupImage(writer, image) {
|
|
1289
1402
|
writer.removeAttribute('srcset', image);
|
|
1290
1403
|
writer.removeAttribute('sizes', image);
|
|
1291
1404
|
/**
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1405
|
+
* In case responsive images some attributes should be cleaned up.
|
|
1406
|
+
* Check: https://github.com/ckeditor/ckeditor5/issues/15093
|
|
1407
|
+
*/ writer.removeAttribute('sources', image);
|
|
1295
1408
|
writer.removeAttribute('width', image);
|
|
1296
1409
|
writer.removeAttribute('height', image);
|
|
1297
1410
|
writer.removeAttribute('alt', image);
|
|
1298
1411
|
}
|
|
1299
|
-
constructor(editor){
|
|
1300
|
-
super(editor);
|
|
1301
|
-
this.decorate('cleanupImage');
|
|
1302
|
-
}
|
|
1303
1412
|
}
|
|
1304
1413
|
|
|
1305
|
-
|
|
1414
|
+
/**
|
|
1415
|
+
* The image engine plugin. This module loads common code shared between
|
|
1416
|
+
* {@link module:image/image/imageinlineediting~ImageInlineEditing} and
|
|
1417
|
+
* {@link module:image/image/imageblockediting~ImageBlockEditing} plugins.
|
|
1418
|
+
*
|
|
1419
|
+
* This plugin registers the {@link module:image/image/insertimagecommand~InsertImageCommand 'insertImage'} command.
|
|
1420
|
+
*/ class ImageEditing extends Plugin {
|
|
1306
1421
|
/**
|
|
1307
|
-
|
|
1308
|
-
|
|
1422
|
+
* @inheritDoc
|
|
1423
|
+
*/ static get requires() {
|
|
1309
1424
|
return [
|
|
1310
1425
|
ImageUtils
|
|
1311
1426
|
];
|
|
1312
1427
|
}
|
|
1313
1428
|
/**
|
|
1314
|
-
|
|
1315
|
-
|
|
1429
|
+
* @inheritDoc
|
|
1430
|
+
*/ static get pluginName() {
|
|
1316
1431
|
return 'ImageEditing';
|
|
1317
1432
|
}
|
|
1318
1433
|
/**
|
|
1319
|
-
|
|
1320
|
-
|
|
1434
|
+
* @inheritDoc
|
|
1435
|
+
*/ init() {
|
|
1321
1436
|
const editor = this.editor;
|
|
1322
1437
|
const conversion = editor.conversion;
|
|
1323
1438
|
// See https://github.com/ckeditor/ckeditor5-image/issues/142.
|
|
@@ -1344,29 +1459,31 @@ class ImageEditing extends Plugin {
|
|
|
1344
1459
|
}
|
|
1345
1460
|
}
|
|
1346
1461
|
|
|
1347
|
-
|
|
1462
|
+
/**
|
|
1463
|
+
* This plugin enables `width` and `height` attributes in inline and block image elements.
|
|
1464
|
+
*/ class ImageSizeAttributes extends Plugin {
|
|
1348
1465
|
/**
|
|
1349
|
-
|
|
1350
|
-
|
|
1466
|
+
* @inheritDoc
|
|
1467
|
+
*/ static get requires() {
|
|
1351
1468
|
return [
|
|
1352
1469
|
ImageUtils
|
|
1353
1470
|
];
|
|
1354
1471
|
}
|
|
1355
1472
|
/**
|
|
1356
|
-
|
|
1357
|
-
|
|
1473
|
+
* @inheritDoc
|
|
1474
|
+
*/ static get pluginName() {
|
|
1358
1475
|
return 'ImageSizeAttributes';
|
|
1359
1476
|
}
|
|
1360
1477
|
/**
|
|
1361
|
-
|
|
1362
|
-
|
|
1478
|
+
* @inheritDoc
|
|
1479
|
+
*/ afterInit() {
|
|
1363
1480
|
this._registerSchema();
|
|
1364
1481
|
this._registerConverters('imageBlock');
|
|
1365
1482
|
this._registerConverters('imageInline');
|
|
1366
1483
|
}
|
|
1367
1484
|
/**
|
|
1368
|
-
|
|
1369
|
-
|
|
1485
|
+
* Registers the `width` and `height` attributes for inline and block images.
|
|
1486
|
+
*/ _registerSchema() {
|
|
1370
1487
|
if (this.editor.plugins.has('ImageBlockEditing')) {
|
|
1371
1488
|
this.editor.model.schema.extend('imageBlock', {
|
|
1372
1489
|
allowAttributes: [
|
|
@@ -1385,8 +1502,8 @@ class ImageSizeAttributes extends Plugin {
|
|
|
1385
1502
|
}
|
|
1386
1503
|
}
|
|
1387
1504
|
/**
|
|
1388
|
-
|
|
1389
|
-
|
|
1505
|
+
* Registers converters for `width` and `height` attributes.
|
|
1506
|
+
*/ _registerConverters(imageType) {
|
|
1390
1507
|
const editor = this.editor;
|
|
1391
1508
|
const imageUtils = editor.plugins.get('ImageUtils');
|
|
1392
1509
|
const viewElementName = imageType === 'imageBlock' ? 'figure' : 'img';
|
|
@@ -1476,10 +1593,23 @@ class ImageSizeAttributes extends Plugin {
|
|
|
1476
1593
|
}
|
|
1477
1594
|
}
|
|
1478
1595
|
|
|
1479
|
-
|
|
1596
|
+
/**
|
|
1597
|
+
* The image type command. It changes the type of a selected image, depending on the configuration.
|
|
1598
|
+
*/ class ImageTypeCommand extends Command {
|
|
1599
|
+
/**
|
|
1600
|
+
* Model element name the command converts to.
|
|
1601
|
+
*/ _modelElementName;
|
|
1480
1602
|
/**
|
|
1481
|
-
|
|
1482
|
-
|
|
1603
|
+
* @inheritDoc
|
|
1604
|
+
*
|
|
1605
|
+
* @param modelElementName Model element name the command converts to.
|
|
1606
|
+
*/ constructor(editor, modelElementName){
|
|
1607
|
+
super(editor);
|
|
1608
|
+
this._modelElementName = modelElementName;
|
|
1609
|
+
}
|
|
1610
|
+
/**
|
|
1611
|
+
* @inheritDoc
|
|
1612
|
+
*/ refresh() {
|
|
1483
1613
|
const editor = this.editor;
|
|
1484
1614
|
const imageUtils = editor.plugins.get('ImageUtils');
|
|
1485
1615
|
const element = imageUtils.getClosestSelectedImageElement(this.editor.model.document.selection);
|
|
@@ -1490,15 +1620,15 @@ class ImageTypeCommand extends Command {
|
|
|
1490
1620
|
}
|
|
1491
1621
|
}
|
|
1492
1622
|
/**
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1623
|
+
* Executes the command and changes the type of a selected image.
|
|
1624
|
+
*
|
|
1625
|
+
* @fires execute
|
|
1626
|
+
* @param options.setImageSizes Specifies whether the image `width` and `height` attributes should be set automatically.
|
|
1627
|
+
* The default is `true`.
|
|
1628
|
+
* @returns An object containing references to old and new model image elements
|
|
1629
|
+
* (for before and after the change) so external integrations can hook into the decorated
|
|
1630
|
+
* `execute` event and handle this change. `null` if the type change failed.
|
|
1631
|
+
*/ execute(options = {}) {
|
|
1502
1632
|
const editor = this.editor;
|
|
1503
1633
|
const model = this.editor.model;
|
|
1504
1634
|
const imageUtils = editor.plugins.get('ImageUtils');
|
|
@@ -1537,39 +1667,33 @@ class ImageTypeCommand extends Command {
|
|
|
1537
1667
|
};
|
|
1538
1668
|
});
|
|
1539
1669
|
}
|
|
1540
|
-
/**
|
|
1541
|
-
* @inheritDoc
|
|
1542
|
-
*
|
|
1543
|
-
* @param modelElementName Model element name the command converts to.
|
|
1544
|
-
*/ constructor(editor, modelElementName){
|
|
1545
|
-
super(editor);
|
|
1546
|
-
this._modelElementName = modelElementName;
|
|
1547
|
-
}
|
|
1548
1670
|
}
|
|
1549
1671
|
|
|
1550
|
-
|
|
1672
|
+
/**
|
|
1673
|
+
* Adds support for image placeholder that is automatically removed when the image is loaded.
|
|
1674
|
+
*/ class ImagePlaceholder extends Plugin {
|
|
1551
1675
|
/**
|
|
1552
|
-
|
|
1553
|
-
|
|
1676
|
+
* @inheritDoc
|
|
1677
|
+
*/ static get requires() {
|
|
1554
1678
|
return [
|
|
1555
1679
|
ImageUtils
|
|
1556
1680
|
];
|
|
1557
1681
|
}
|
|
1558
1682
|
/**
|
|
1559
|
-
|
|
1560
|
-
|
|
1683
|
+
* @inheritDoc
|
|
1684
|
+
*/ static get pluginName() {
|
|
1561
1685
|
return 'ImagePlaceholder';
|
|
1562
1686
|
}
|
|
1563
1687
|
/**
|
|
1564
|
-
|
|
1565
|
-
|
|
1688
|
+
* @inheritDoc
|
|
1689
|
+
*/ afterInit() {
|
|
1566
1690
|
this._setupSchema();
|
|
1567
1691
|
this._setupConversion();
|
|
1568
1692
|
this._setupLoadListener();
|
|
1569
1693
|
}
|
|
1570
1694
|
/**
|
|
1571
|
-
|
|
1572
|
-
|
|
1695
|
+
* Extends model schema.
|
|
1696
|
+
*/ _setupSchema() {
|
|
1573
1697
|
const schema = this.editor.model.schema;
|
|
1574
1698
|
// Wait for ImageBlockEditing or ImageInlineEditing to register their elements first,
|
|
1575
1699
|
// that's why doing this in afterInit() instead of init().
|
|
@@ -1589,8 +1713,8 @@ class ImagePlaceholder extends Plugin {
|
|
|
1589
1713
|
}
|
|
1590
1714
|
}
|
|
1591
1715
|
/**
|
|
1592
|
-
|
|
1593
|
-
|
|
1716
|
+
* Registers converters.
|
|
1717
|
+
*/ _setupConversion() {
|
|
1594
1718
|
const editor = this.editor;
|
|
1595
1719
|
const conversion = editor.conversion;
|
|
1596
1720
|
const imageUtils = editor.plugins.get('ImageUtils');
|
|
@@ -1618,8 +1742,8 @@ class ImagePlaceholder extends Plugin {
|
|
|
1618
1742
|
});
|
|
1619
1743
|
}
|
|
1620
1744
|
/**
|
|
1621
|
-
|
|
1622
|
-
|
|
1745
|
+
* Prepares listener for image load.
|
|
1746
|
+
*/ _setupLoadListener() {
|
|
1623
1747
|
const editor = this.editor;
|
|
1624
1748
|
const model = editor.model;
|
|
1625
1749
|
const editing = editor.editing;
|
|
@@ -1648,10 +1772,19 @@ class ImagePlaceholder extends Plugin {
|
|
|
1648
1772
|
}
|
|
1649
1773
|
}
|
|
1650
1774
|
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1775
|
+
/**
|
|
1776
|
+
* The image block plugin.
|
|
1777
|
+
*
|
|
1778
|
+
* It registers:
|
|
1779
|
+
*
|
|
1780
|
+
* * `<imageBlock>` as a block element in the document schema, and allows `alt`, `src` and `srcset` attributes.
|
|
1781
|
+
* * converters for editing and data pipelines.,
|
|
1782
|
+
* * {@link module:image/image/imagetypecommand~ImageTypeCommand `'imageTypeBlock'`} command that converts inline images into
|
|
1783
|
+
* block images.
|
|
1784
|
+
*/ class ImageBlockEditing extends Plugin {
|
|
1785
|
+
/**
|
|
1786
|
+
* @inheritDoc
|
|
1787
|
+
*/ static get requires() {
|
|
1655
1788
|
return [
|
|
1656
1789
|
ImageEditing,
|
|
1657
1790
|
ImageSizeAttributes,
|
|
@@ -1661,13 +1794,13 @@ class ImageBlockEditing extends Plugin {
|
|
|
1661
1794
|
];
|
|
1662
1795
|
}
|
|
1663
1796
|
/**
|
|
1664
|
-
|
|
1665
|
-
|
|
1797
|
+
* @inheritDoc
|
|
1798
|
+
*/ static get pluginName() {
|
|
1666
1799
|
return 'ImageBlockEditing';
|
|
1667
1800
|
}
|
|
1668
1801
|
/**
|
|
1669
|
-
|
|
1670
|
-
|
|
1802
|
+
* @inheritDoc
|
|
1803
|
+
*/ init() {
|
|
1671
1804
|
const editor = this.editor;
|
|
1672
1805
|
const schema = editor.model.schema;
|
|
1673
1806
|
// Converters 'alt' and 'srcset' are added in 'ImageEditing' plugin.
|
|
@@ -1686,9 +1819,9 @@ class ImageBlockEditing extends Plugin {
|
|
|
1686
1819
|
}
|
|
1687
1820
|
}
|
|
1688
1821
|
/**
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1822
|
+
* Configures conversion pipelines to support upcasting and downcasting
|
|
1823
|
+
* block images (block image widgets) and their attributes.
|
|
1824
|
+
*/ _setupConversion() {
|
|
1692
1825
|
const editor = this.editor;
|
|
1693
1826
|
const t = editor.t;
|
|
1694
1827
|
const conversion = editor.conversion;
|
|
@@ -1711,21 +1844,21 @@ class ImageBlockEditing extends Plugin {
|
|
|
1711
1844
|
}).add(upcastImageFigure(imageUtils));
|
|
1712
1845
|
}
|
|
1713
1846
|
/**
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1847
|
+
* Integrates the plugin with the clipboard pipeline.
|
|
1848
|
+
*
|
|
1849
|
+
* Idea is that the feature should recognize the user's intent when an **inline** image is
|
|
1850
|
+
* pasted or dropped. If such an image is pasted/dropped:
|
|
1851
|
+
*
|
|
1852
|
+
* * into an empty block (e.g. an empty paragraph),
|
|
1853
|
+
* * on another object (e.g. some block widget).
|
|
1854
|
+
*
|
|
1855
|
+
* it gets converted into a block image on the fly. We assume this is the user's intent
|
|
1856
|
+
* if they decided to put their image there.
|
|
1857
|
+
*
|
|
1858
|
+
* See the `ImageInlineEditing` for the similar integration that works in the opposite direction.
|
|
1859
|
+
*
|
|
1860
|
+
* The feature also sets image `width` and `height` attributes on paste.
|
|
1861
|
+
*/ _setupClipboardIntegration() {
|
|
1729
1862
|
const editor = this.editor;
|
|
1730
1863
|
const model = editor.model;
|
|
1731
1864
|
const editingView = editor.editing.view;
|
|
@@ -1775,46 +1908,32 @@ class ImageBlockEditing extends Plugin {
|
|
|
1775
1908
|
}
|
|
1776
1909
|
}
|
|
1777
1910
|
|
|
1778
|
-
|
|
1911
|
+
/**
|
|
1912
|
+
* The view displayed in the insert image dropdown.
|
|
1913
|
+
*
|
|
1914
|
+
* See {@link module:image/imageinsert/imageinsertui~ImageInsertUI}.
|
|
1915
|
+
*/ class ImageInsertFormView extends View {
|
|
1779
1916
|
/**
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
super.render();
|
|
1783
|
-
submitHandler({
|
|
1784
|
-
view: this
|
|
1785
|
-
});
|
|
1786
|
-
for (const view of this._focusables){
|
|
1787
|
-
this.focusTracker.add(view.element);
|
|
1788
|
-
}
|
|
1789
|
-
// Start listening for the keystrokes coming from #element.
|
|
1790
|
-
this.keystrokes.listenTo(this.element);
|
|
1791
|
-
const stopPropagation = (data)=>data.stopPropagation();
|
|
1792
|
-
// Since the form is in the dropdown panel which is a child of the toolbar, the toolbar's
|
|
1793
|
-
// keystroke handler would take over the key management in the URL input. We need to prevent
|
|
1794
|
-
// this ASAP. Otherwise, the basic caret movement using the arrow keys will be impossible.
|
|
1795
|
-
this.keystrokes.set('arrowright', stopPropagation);
|
|
1796
|
-
this.keystrokes.set('arrowleft', stopPropagation);
|
|
1797
|
-
this.keystrokes.set('arrowup', stopPropagation);
|
|
1798
|
-
this.keystrokes.set('arrowdown', stopPropagation);
|
|
1799
|
-
}
|
|
1917
|
+
* Tracks information about DOM focus in the form.
|
|
1918
|
+
*/ focusTracker;
|
|
1800
1919
|
/**
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
super.destroy();
|
|
1804
|
-
this.focusTracker.destroy();
|
|
1805
|
-
this.keystrokes.destroy();
|
|
1806
|
-
}
|
|
1920
|
+
* An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
|
|
1921
|
+
*/ keystrokes;
|
|
1807
1922
|
/**
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1923
|
+
* A collection of views that can be focused in the form.
|
|
1924
|
+
*/ _focusables;
|
|
1925
|
+
/**
|
|
1926
|
+
* Helps cycling over {@link #_focusables} in the form.
|
|
1927
|
+
*/ _focusCycler;
|
|
1812
1928
|
/**
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1929
|
+
* A collection of the defined integrations for inserting the images.
|
|
1930
|
+
*/ children;
|
|
1931
|
+
/**
|
|
1932
|
+
* Creates a view for the dropdown panel of {@link module:image/imageinsert/imageinsertui~ImageInsertUI}.
|
|
1933
|
+
*
|
|
1934
|
+
* @param locale The localization services instance.
|
|
1935
|
+
* @param integrations An integrations object that contains components (or tokens for components) to be shown in the panel view.
|
|
1936
|
+
*/ constructor(locale, integrations = []){
|
|
1818
1937
|
super(locale);
|
|
1819
1938
|
this.focusTracker = new FocusTracker();
|
|
1820
1939
|
this.keystrokes = new KeystrokeHandler();
|
|
@@ -1838,20 +1957,6 @@ class ImageInsertFormView extends View {
|
|
|
1838
1957
|
this._focusables.addMany(view.children);
|
|
1839
1958
|
}
|
|
1840
1959
|
}
|
|
1841
|
-
if (this._focusables.length > 1) {
|
|
1842
|
-
for (const view of this._focusables){
|
|
1843
|
-
if (isViewWithFocusCycler(view)) {
|
|
1844
|
-
view.focusCycler.on('forwardCycle', (evt)=>{
|
|
1845
|
-
this._focusCycler.focusNext();
|
|
1846
|
-
evt.stop();
|
|
1847
|
-
});
|
|
1848
|
-
view.focusCycler.on('backwardCycle', (evt)=>{
|
|
1849
|
-
this._focusCycler.focusPrevious();
|
|
1850
|
-
evt.stop();
|
|
1851
|
-
});
|
|
1852
|
-
}
|
|
1853
|
-
}
|
|
1854
|
-
}
|
|
1855
1960
|
this.setTemplate({
|
|
1856
1961
|
tag: 'form',
|
|
1857
1962
|
attributes: {
|
|
@@ -1864,63 +1969,123 @@ class ImageInsertFormView extends View {
|
|
|
1864
1969
|
children: this.children
|
|
1865
1970
|
});
|
|
1866
1971
|
}
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1972
|
+
/**
|
|
1973
|
+
* @inheritDoc
|
|
1974
|
+
*/ render() {
|
|
1975
|
+
super.render();
|
|
1976
|
+
submitHandler({
|
|
1977
|
+
view: this
|
|
1978
|
+
});
|
|
1979
|
+
for (const view of this._focusables){
|
|
1980
|
+
this.focusTracker.add(view.element);
|
|
1981
|
+
}
|
|
1982
|
+
// Start listening for the keystrokes coming from #element.
|
|
1983
|
+
this.keystrokes.listenTo(this.element);
|
|
1984
|
+
const stopPropagation = (data)=>data.stopPropagation();
|
|
1985
|
+
// Since the form is in the dropdown panel which is a child of the toolbar, the toolbar's
|
|
1986
|
+
// keystroke handler would take over the key management in the URL input. We need to prevent
|
|
1987
|
+
// this ASAP. Otherwise, the basic caret movement using the arrow keys will be impossible.
|
|
1988
|
+
this.keystrokes.set('arrowright', stopPropagation);
|
|
1989
|
+
this.keystrokes.set('arrowleft', stopPropagation);
|
|
1990
|
+
this.keystrokes.set('arrowup', stopPropagation);
|
|
1991
|
+
this.keystrokes.set('arrowdown', stopPropagation);
|
|
1992
|
+
}
|
|
1993
|
+
/**
|
|
1994
|
+
* @inheritDoc
|
|
1995
|
+
*/ destroy() {
|
|
1996
|
+
super.destroy();
|
|
1997
|
+
this.focusTracker.destroy();
|
|
1998
|
+
this.keystrokes.destroy();
|
|
1999
|
+
}
|
|
2000
|
+
/**
|
|
2001
|
+
* Focuses the first {@link #_focusables focusable} in the form.
|
|
2002
|
+
*/ focus() {
|
|
2003
|
+
this._focusCycler.focusFirst();
|
|
2004
|
+
}
|
|
1870
2005
|
}
|
|
1871
2006
|
|
|
1872
|
-
|
|
2007
|
+
/**
|
|
2008
|
+
* The image insert dropdown plugin.
|
|
2009
|
+
*
|
|
2010
|
+
* For a detailed overview, check the {@glink features/images/image-upload/image-upload Image upload feature}
|
|
2011
|
+
* and {@glink features/images/images-inserting Insert images via source URL} documentation.
|
|
2012
|
+
*
|
|
2013
|
+
* Adds the `'insertImage'` dropdown to the {@link module:ui/componentfactory~ComponentFactory UI component factory}
|
|
2014
|
+
* and also the `imageInsert` dropdown as an alias for backward compatibility.
|
|
2015
|
+
*
|
|
2016
|
+
* Adds the `'menuBar:insertImage'` sub-menu to the {@link module:ui/componentfactory~ComponentFactory UI component factory}, which is
|
|
2017
|
+
* by default added to the `'Insert'` menu.
|
|
2018
|
+
*/ class ImageInsertUI extends Plugin {
|
|
1873
2019
|
/**
|
|
1874
|
-
|
|
1875
|
-
|
|
2020
|
+
* @inheritDoc
|
|
2021
|
+
*/ static get pluginName() {
|
|
1876
2022
|
return 'ImageInsertUI';
|
|
1877
2023
|
}
|
|
1878
2024
|
/**
|
|
1879
|
-
|
|
1880
|
-
|
|
2025
|
+
* @inheritDoc
|
|
2026
|
+
*/ static get requires() {
|
|
1881
2027
|
return [
|
|
1882
2028
|
ImageUtils
|
|
1883
2029
|
];
|
|
1884
2030
|
}
|
|
1885
2031
|
/**
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
2032
|
+
* The dropdown view responsible for displaying the image insert UI.
|
|
2033
|
+
*/ dropdownView;
|
|
2034
|
+
/**
|
|
2035
|
+
* Registered integrations map.
|
|
2036
|
+
*/ _integrations = new Map();
|
|
2037
|
+
/**
|
|
2038
|
+
* @inheritDoc
|
|
2039
|
+
*/ constructor(editor){
|
|
2040
|
+
super(editor);
|
|
2041
|
+
editor.config.define('image.insert.integrations', [
|
|
2042
|
+
'upload',
|
|
2043
|
+
'assetManager',
|
|
2044
|
+
'url'
|
|
2045
|
+
]);
|
|
2046
|
+
}
|
|
2047
|
+
/**
|
|
2048
|
+
* @inheritDoc
|
|
2049
|
+
*/ init() {
|
|
2050
|
+
const editor = this.editor;
|
|
2051
|
+
const selection = editor.model.document.selection;
|
|
1890
2052
|
const imageUtils = editor.plugins.get('ImageUtils');
|
|
1891
2053
|
this.set('isImageSelected', false);
|
|
1892
2054
|
this.listenTo(editor.model.document, 'change', ()=>{
|
|
1893
2055
|
this.isImageSelected = imageUtils.isImage(selection.getSelectedElement());
|
|
1894
2056
|
});
|
|
1895
2057
|
const componentCreator = (locale)=>this._createToolbarComponent(locale);
|
|
2058
|
+
const menuBarComponentCreator = (locale)=>this._createMenuBarComponent(locale);
|
|
1896
2059
|
// Register `insertImage` dropdown and add `imageInsert` dropdown as an alias for backward compatibility.
|
|
1897
2060
|
editor.ui.componentFactory.add('insertImage', componentCreator);
|
|
1898
2061
|
editor.ui.componentFactory.add('imageInsert', componentCreator);
|
|
2062
|
+
editor.ui.componentFactory.add('menuBar:insertImage', menuBarComponentCreator);
|
|
1899
2063
|
}
|
|
1900
2064
|
/**
|
|
1901
|
-
|
|
1902
|
-
|
|
2065
|
+
* Registers the insert image dropdown integration.
|
|
2066
|
+
*/ registerIntegration({ name, observable, buttonViewCreator, formViewCreator, menuBarButtonViewCreator, requiresForm = false }) {
|
|
1903
2067
|
if (this._integrations.has(name)) {
|
|
1904
2068
|
/**
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
2069
|
+
* There are two insert-image integrations registered with the same name.
|
|
2070
|
+
*
|
|
2071
|
+
* Make sure that you do not load multiple asset manager plugins.
|
|
2072
|
+
*
|
|
2073
|
+
* @error image-insert-integration-exists
|
|
2074
|
+
*/ logWarning('image-insert-integration-exists', {
|
|
1911
2075
|
name
|
|
1912
2076
|
});
|
|
1913
2077
|
}
|
|
1914
2078
|
this._integrations.set(name, {
|
|
1915
2079
|
observable,
|
|
1916
2080
|
buttonViewCreator,
|
|
2081
|
+
menuBarButtonViewCreator,
|
|
1917
2082
|
formViewCreator,
|
|
1918
|
-
requiresForm
|
|
2083
|
+
requiresForm
|
|
1919
2084
|
});
|
|
1920
2085
|
}
|
|
1921
2086
|
/**
|
|
1922
|
-
|
|
1923
|
-
|
|
2087
|
+
* Creates the toolbar component.
|
|
2088
|
+
*/ _createToolbarComponent(locale) {
|
|
1924
2089
|
const editor = this.editor;
|
|
1925
2090
|
const t = locale.t;
|
|
1926
2091
|
const integrations = this._prepareIntegrations();
|
|
@@ -1952,21 +2117,50 @@ class ImageInsertUI extends Plugin {
|
|
|
1952
2117
|
return dropdownView;
|
|
1953
2118
|
}
|
|
1954
2119
|
/**
|
|
1955
|
-
|
|
1956
|
-
|
|
2120
|
+
* Creates the menu bar component.
|
|
2121
|
+
*/ _createMenuBarComponent(locale) {
|
|
2122
|
+
const t = locale.t;
|
|
2123
|
+
const integrations = this._prepareIntegrations();
|
|
2124
|
+
if (!integrations.length) {
|
|
2125
|
+
return null;
|
|
2126
|
+
}
|
|
2127
|
+
let resultView;
|
|
2128
|
+
const firstIntegration = integrations[0];
|
|
2129
|
+
if (integrations.length == 1) {
|
|
2130
|
+
resultView = firstIntegration.menuBarButtonViewCreator(true);
|
|
2131
|
+
} else {
|
|
2132
|
+
resultView = new MenuBarMenuView(locale);
|
|
2133
|
+
const listView = new MenuBarMenuListView(locale);
|
|
2134
|
+
resultView.panelView.children.add(listView);
|
|
2135
|
+
resultView.buttonView.set({
|
|
2136
|
+
icon: icons.image,
|
|
2137
|
+
label: t('Image')
|
|
2138
|
+
});
|
|
2139
|
+
for (const integration of integrations){
|
|
2140
|
+
const listItemView = new MenuBarMenuListItemView(locale, resultView);
|
|
2141
|
+
const buttonView = integration.menuBarButtonViewCreator(false);
|
|
2142
|
+
listItemView.children.add(buttonView);
|
|
2143
|
+
listView.items.add(listItemView);
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
return resultView;
|
|
2147
|
+
}
|
|
2148
|
+
/**
|
|
2149
|
+
* Validates the integrations list.
|
|
2150
|
+
*/ _prepareIntegrations() {
|
|
1957
2151
|
const editor = this.editor;
|
|
1958
2152
|
const items = editor.config.get('image.insert.integrations');
|
|
1959
2153
|
const result = [];
|
|
1960
2154
|
if (!items.length) {
|
|
1961
2155
|
/**
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
2156
|
+
* The insert image feature requires a list of integrations to be provided in the editor configuration.
|
|
2157
|
+
*
|
|
2158
|
+
* The default list of integrations is `upload`, `assetManager`, `url`. Those integrations are included
|
|
2159
|
+
* in the insert image dropdown if the given feature plugin is loaded. You should omit the `integrations`
|
|
2160
|
+
* configuration key to use the default set or provide a selected list of integrations that should be used.
|
|
2161
|
+
*
|
|
2162
|
+
* @error image-insert-integrations-not-specified
|
|
2163
|
+
*/ logWarning('image-insert-integrations-not-specified');
|
|
1970
2164
|
return result;
|
|
1971
2165
|
}
|
|
1972
2166
|
for (const item of items){
|
|
@@ -1977,10 +2171,10 @@ class ImageInsertUI extends Plugin {
|
|
|
1977
2171
|
'url'
|
|
1978
2172
|
].includes(item)) {
|
|
1979
2173
|
/**
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
2174
|
+
* The specified insert image integration name is unknown or the providing plugin is not loaded in the editor.
|
|
2175
|
+
*
|
|
2176
|
+
* @error image-insert-unknown-integration
|
|
2177
|
+
*/ logWarning('image-insert-unknown-integration', {
|
|
1984
2178
|
item
|
|
1985
2179
|
});
|
|
1986
2180
|
}
|
|
@@ -1990,38 +2184,35 @@ class ImageInsertUI extends Plugin {
|
|
|
1990
2184
|
}
|
|
1991
2185
|
if (!result.length) {
|
|
1992
2186
|
/**
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2187
|
+
* The image insert feature requires integrations to be registered by separate features.
|
|
2188
|
+
*
|
|
2189
|
+
* The `insertImage` toolbar button requires integrations to be registered by other features.
|
|
2190
|
+
* For example {@link module:image/imageupload~ImageUpload ImageUpload},
|
|
2191
|
+
* {@link module:image/imageinsert~ImageInsert ImageInsert},
|
|
2192
|
+
* {@link module:image/imageinsertviaurl~ImageInsertViaUrl ImageInsertViaUrl},
|
|
2193
|
+
* {@link module:ckbox/ckbox~CKBox CKBox}
|
|
2194
|
+
*
|
|
2195
|
+
* @error image-insert-integrations-not-registered
|
|
2196
|
+
*/ logWarning('image-insert-integrations-not-registered');
|
|
2003
2197
|
}
|
|
2004
2198
|
return result;
|
|
2005
2199
|
}
|
|
2006
|
-
/**
|
|
2007
|
-
* @inheritDoc
|
|
2008
|
-
*/ constructor(editor){
|
|
2009
|
-
super(editor);
|
|
2010
|
-
/**
|
|
2011
|
-
* Registered integrations map.
|
|
2012
|
-
*/ this._integrations = new Map();
|
|
2013
|
-
editor.config.define('image.insert.integrations', [
|
|
2014
|
-
'upload',
|
|
2015
|
-
'assetManager',
|
|
2016
|
-
'url'
|
|
2017
|
-
]);
|
|
2018
|
-
}
|
|
2019
2200
|
}
|
|
2020
2201
|
|
|
2021
|
-
|
|
2202
|
+
/**
|
|
2203
|
+
* The image block plugin.
|
|
2204
|
+
*
|
|
2205
|
+
* This is a "glue" plugin which loads the following plugins:
|
|
2206
|
+
*
|
|
2207
|
+
* * {@link module:image/image/imageblockediting~ImageBlockEditing},
|
|
2208
|
+
* * {@link module:image/imagetextalternative~ImageTextAlternative}.
|
|
2209
|
+
*
|
|
2210
|
+
* Usually, it is used in conjunction with other plugins from this package. See the {@glink api/image package page}
|
|
2211
|
+
* for more information.
|
|
2212
|
+
*/ class ImageBlock extends Plugin {
|
|
2022
2213
|
/**
|
|
2023
|
-
|
|
2024
|
-
|
|
2214
|
+
* @inheritDoc
|
|
2215
|
+
*/ static get requires() {
|
|
2025
2216
|
return [
|
|
2026
2217
|
ImageBlockEditing,
|
|
2027
2218
|
Widget,
|
|
@@ -2030,16 +2221,25 @@ class ImageBlock extends Plugin {
|
|
|
2030
2221
|
];
|
|
2031
2222
|
}
|
|
2032
2223
|
/**
|
|
2033
|
-
|
|
2034
|
-
|
|
2224
|
+
* @inheritDoc
|
|
2225
|
+
*/ static get pluginName() {
|
|
2035
2226
|
return 'ImageBlock';
|
|
2036
2227
|
}
|
|
2037
2228
|
}
|
|
2038
2229
|
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2230
|
+
/**
|
|
2231
|
+
* The image inline plugin.
|
|
2232
|
+
*
|
|
2233
|
+
* It registers:
|
|
2234
|
+
*
|
|
2235
|
+
* * `<imageInline>` as an inline element in the document schema, and allows `alt`, `src` and `srcset` attributes.
|
|
2236
|
+
* * converters for editing and data pipelines.
|
|
2237
|
+
* * {@link module:image/image/imagetypecommand~ImageTypeCommand `'imageTypeInline'`} command that converts block images into
|
|
2238
|
+
* inline images.
|
|
2239
|
+
*/ class ImageInlineEditing extends Plugin {
|
|
2240
|
+
/**
|
|
2241
|
+
* @inheritDoc
|
|
2242
|
+
*/ static get requires() {
|
|
2043
2243
|
return [
|
|
2044
2244
|
ImageEditing,
|
|
2045
2245
|
ImageSizeAttributes,
|
|
@@ -2049,13 +2249,13 @@ class ImageInlineEditing extends Plugin {
|
|
|
2049
2249
|
];
|
|
2050
2250
|
}
|
|
2051
2251
|
/**
|
|
2052
|
-
|
|
2053
|
-
|
|
2252
|
+
* @inheritDoc
|
|
2253
|
+
*/ static get pluginName() {
|
|
2054
2254
|
return 'ImageInlineEditing';
|
|
2055
2255
|
}
|
|
2056
2256
|
/**
|
|
2057
|
-
|
|
2058
|
-
|
|
2257
|
+
* @inheritDoc
|
|
2258
|
+
*/ init() {
|
|
2059
2259
|
const editor = this.editor;
|
|
2060
2260
|
const schema = editor.model.schema;
|
|
2061
2261
|
// Converters 'alt' and 'srcset' are added in 'ImageEditing' plugin.
|
|
@@ -2065,16 +2265,13 @@ class ImageInlineEditing extends Plugin {
|
|
|
2065
2265
|
'alt',
|
|
2066
2266
|
'src',
|
|
2067
2267
|
'srcset'
|
|
2268
|
+
],
|
|
2269
|
+
// Disallow inline images in captions (at least for now).
|
|
2270
|
+
// This is the best spot to do that because independent packages can introduce captions (ImageCaption, TableCaption, etc.).
|
|
2271
|
+
disallowIn: [
|
|
2272
|
+
'caption'
|
|
2068
2273
|
]
|
|
2069
2274
|
});
|
|
2070
|
-
// Disallow inline images in captions (for now). This is the best spot to do that because
|
|
2071
|
-
// independent packages can introduce captions (ImageCaption, TableCaption, etc.) so better this
|
|
2072
|
-
// be future-proof.
|
|
2073
|
-
schema.addChildCheck((context, childDefinition)=>{
|
|
2074
|
-
if (context.endsWith('caption') && childDefinition.name === 'imageInline') {
|
|
2075
|
-
return false;
|
|
2076
|
-
}
|
|
2077
|
-
});
|
|
2078
2275
|
this._setupConversion();
|
|
2079
2276
|
if (editor.plugins.has('ImageBlockEditing')) {
|
|
2080
2277
|
editor.commands.add('imageTypeInline', new ImageTypeCommand(this.editor, 'imageInline'));
|
|
@@ -2082,9 +2279,9 @@ class ImageInlineEditing extends Plugin {
|
|
|
2082
2279
|
}
|
|
2083
2280
|
}
|
|
2084
2281
|
/**
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2282
|
+
* Configures conversion pipelines to support upcasting and downcasting
|
|
2283
|
+
* inline images (inline image widgets) and their attributes.
|
|
2284
|
+
*/ _setupConversion() {
|
|
2088
2285
|
const editor = this.editor;
|
|
2089
2286
|
const t = editor.t;
|
|
2090
2287
|
const conversion = editor.conversion;
|
|
@@ -2107,22 +2304,22 @@ class ImageInlineEditing extends Plugin {
|
|
|
2107
2304
|
});
|
|
2108
2305
|
}
|
|
2109
2306
|
/**
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2307
|
+
* Integrates the plugin with the clipboard pipeline.
|
|
2308
|
+
*
|
|
2309
|
+
* Idea is that the feature should recognize the user's intent when an **block** image is
|
|
2310
|
+
* pasted or dropped. If such an image is pasted/dropped into a non-empty block
|
|
2311
|
+
* (e.g. a paragraph with some text) it gets converted into an inline image on the fly.
|
|
2312
|
+
*
|
|
2313
|
+
* We assume this is the user's intent if they decided to put their image there.
|
|
2314
|
+
*
|
|
2315
|
+
* **Note**: If a block image has a caption, it will not be converted to an inline image
|
|
2316
|
+
* to avoid the confusion. Captions are added on purpose and they should never be lost
|
|
2317
|
+
* in the clipboard pipeline.
|
|
2318
|
+
*
|
|
2319
|
+
* See the `ImageBlockEditing` for the similar integration that works in the opposite direction.
|
|
2320
|
+
*
|
|
2321
|
+
* The feature also sets image `width` and `height` attributes when pasting.
|
|
2322
|
+
*/ _setupClipboardIntegration() {
|
|
2126
2323
|
const editor = this.editor;
|
|
2127
2324
|
const model = editor.model;
|
|
2128
2325
|
const editingView = editor.editing.view;
|
|
@@ -2184,10 +2381,20 @@ class ImageInlineEditing extends Plugin {
|
|
|
2184
2381
|
}
|
|
2185
2382
|
}
|
|
2186
2383
|
|
|
2187
|
-
|
|
2384
|
+
/**
|
|
2385
|
+
* The image inline plugin.
|
|
2386
|
+
*
|
|
2387
|
+
* This is a "glue" plugin which loads the following plugins:
|
|
2388
|
+
*
|
|
2389
|
+
* * {@link module:image/image/imageinlineediting~ImageInlineEditing},
|
|
2390
|
+
* * {@link module:image/imagetextalternative~ImageTextAlternative}.
|
|
2391
|
+
*
|
|
2392
|
+
* Usually, it is used in conjunction with other plugins from this package. See the {@glink api/image package page}
|
|
2393
|
+
* for more information.
|
|
2394
|
+
*/ class ImageInline extends Plugin {
|
|
2188
2395
|
/**
|
|
2189
|
-
|
|
2190
|
-
|
|
2396
|
+
* @inheritDoc
|
|
2397
|
+
*/ static get requires() {
|
|
2191
2398
|
return [
|
|
2192
2399
|
ImageInlineEditing,
|
|
2193
2400
|
Widget,
|
|
@@ -2196,44 +2403,58 @@ class ImageInline extends Plugin {
|
|
|
2196
2403
|
];
|
|
2197
2404
|
}
|
|
2198
2405
|
/**
|
|
2199
|
-
|
|
2200
|
-
|
|
2406
|
+
* @inheritDoc
|
|
2407
|
+
*/ static get pluginName() {
|
|
2201
2408
|
return 'ImageInline';
|
|
2202
2409
|
}
|
|
2203
2410
|
}
|
|
2204
2411
|
|
|
2205
|
-
|
|
2412
|
+
/**
|
|
2413
|
+
* The image plugin.
|
|
2414
|
+
*
|
|
2415
|
+
* For a detailed overview, check the {@glink features/images/images-overview image feature} documentation.
|
|
2416
|
+
*
|
|
2417
|
+
* This is a "glue" plugin which loads the following plugins:
|
|
2418
|
+
*
|
|
2419
|
+
* * {@link module:image/imageblock~ImageBlock},
|
|
2420
|
+
* * {@link module:image/imageinline~ImageInline},
|
|
2421
|
+
*
|
|
2422
|
+
* Usually, it is used in conjunction with other plugins from this package. See the {@glink api/image package page}
|
|
2423
|
+
* for more information.
|
|
2424
|
+
*/ class Image extends Plugin {
|
|
2206
2425
|
/**
|
|
2207
|
-
|
|
2208
|
-
|
|
2426
|
+
* @inheritDoc
|
|
2427
|
+
*/ static get requires() {
|
|
2209
2428
|
return [
|
|
2210
2429
|
ImageBlock,
|
|
2211
2430
|
ImageInline
|
|
2212
2431
|
];
|
|
2213
2432
|
}
|
|
2214
2433
|
/**
|
|
2215
|
-
|
|
2216
|
-
|
|
2434
|
+
* @inheritDoc
|
|
2435
|
+
*/ static get pluginName() {
|
|
2217
2436
|
return 'Image';
|
|
2218
2437
|
}
|
|
2219
2438
|
}
|
|
2220
2439
|
|
|
2221
|
-
|
|
2440
|
+
/**
|
|
2441
|
+
* The image caption utilities plugin.
|
|
2442
|
+
*/ class ImageCaptionUtils extends Plugin {
|
|
2222
2443
|
/**
|
|
2223
|
-
|
|
2224
|
-
|
|
2444
|
+
* @inheritDoc
|
|
2445
|
+
*/ static get pluginName() {
|
|
2225
2446
|
return 'ImageCaptionUtils';
|
|
2226
2447
|
}
|
|
2227
2448
|
/**
|
|
2228
|
-
|
|
2229
|
-
|
|
2449
|
+
* @inheritDoc
|
|
2450
|
+
*/ static get requires() {
|
|
2230
2451
|
return [
|
|
2231
2452
|
ImageUtils
|
|
2232
2453
|
];
|
|
2233
2454
|
}
|
|
2234
2455
|
/**
|
|
2235
|
-
|
|
2236
|
-
|
|
2456
|
+
* Returns the caption model element from a given image element. Returns `null` if no caption is found.
|
|
2457
|
+
*/ getCaptionFromImageModelElement(imageModelElement) {
|
|
2237
2458
|
for (const node of imageModelElement.getChildren()){
|
|
2238
2459
|
if (!!node && node.is('element', 'caption')) {
|
|
2239
2460
|
return node;
|
|
@@ -2242,8 +2463,8 @@ class ImageCaptionUtils extends Plugin {
|
|
|
2242
2463
|
return null;
|
|
2243
2464
|
}
|
|
2244
2465
|
/**
|
|
2245
|
-
|
|
2246
|
-
|
|
2466
|
+
* Returns the caption model element for a model selection. Returns `null` if the selection has no caption element ancestor.
|
|
2467
|
+
*/ getCaptionFromModelSelection(selection) {
|
|
2247
2468
|
const imageUtils = this.editor.plugins.get('ImageUtils');
|
|
2248
2469
|
const captionElement = selection.getFirstPosition().findAncestor('caption');
|
|
2249
2470
|
if (!captionElement) {
|
|
@@ -2255,11 +2476,11 @@ class ImageCaptionUtils extends Plugin {
|
|
|
2255
2476
|
return null;
|
|
2256
2477
|
}
|
|
2257
2478
|
/**
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2479
|
+
* {@link module:engine/view/matcher~Matcher} pattern. Checks if a given element is a `<figcaption>` element that is placed
|
|
2480
|
+
* inside the image `<figure>` element.
|
|
2481
|
+
* @returns Returns the object accepted by {@link module:engine/view/matcher~Matcher} or `null` if the element
|
|
2482
|
+
* cannot be matched.
|
|
2483
|
+
*/ matchImageCaptionViewElement(element) {
|
|
2263
2484
|
const imageUtils = this.editor.plugins.get('ImageUtils');
|
|
2264
2485
|
// Convert only captions for images.
|
|
2265
2486
|
if (element.name == 'figcaption' && imageUtils.isBlockImageView(element.parent)) {
|
|
@@ -2271,10 +2492,34 @@ class ImageCaptionUtils extends Plugin {
|
|
|
2271
2492
|
}
|
|
2272
2493
|
}
|
|
2273
2494
|
|
|
2274
|
-
|
|
2495
|
+
/**
|
|
2496
|
+
* The toggle image caption command.
|
|
2497
|
+
*
|
|
2498
|
+
* This command is registered by {@link module:image/imagecaption/imagecaptionediting~ImageCaptionEditing} as the
|
|
2499
|
+
* `'toggleImageCaption'` editor command.
|
|
2500
|
+
*
|
|
2501
|
+
* Executing this command:
|
|
2502
|
+
*
|
|
2503
|
+
* * either adds or removes the image caption of a selected image (depending on whether the caption is present or not),
|
|
2504
|
+
* * removes the image caption if the selection is anchored in one.
|
|
2505
|
+
*
|
|
2506
|
+
* ```ts
|
|
2507
|
+
* // Toggle the presence of the caption.
|
|
2508
|
+
* editor.execute( 'toggleImageCaption' );
|
|
2509
|
+
* ```
|
|
2510
|
+
*
|
|
2511
|
+
* **Note**: Upon executing this command, the selection will be set on the image if previously anchored in the caption element.
|
|
2512
|
+
*
|
|
2513
|
+
* **Note**: You can move the selection to the caption right away as it shows up upon executing this command by using
|
|
2514
|
+
* the `focusCaptionOnShow` option:
|
|
2515
|
+
*
|
|
2516
|
+
* ```ts
|
|
2517
|
+
* editor.execute( 'toggleImageCaption', { focusCaptionOnShow: true } );
|
|
2518
|
+
* ```
|
|
2519
|
+
*/ class ToggleImageCaptionCommand extends Command {
|
|
2275
2520
|
/**
|
|
2276
|
-
|
|
2277
|
-
|
|
2521
|
+
* @inheritDoc
|
|
2522
|
+
*/ refresh() {
|
|
2278
2523
|
const editor = this.editor;
|
|
2279
2524
|
const imageCaptionUtils = editor.plugins.get('ImageCaptionUtils');
|
|
2280
2525
|
const imageUtils = editor.plugins.get('ImageUtils');
|
|
@@ -2302,16 +2547,16 @@ class ToggleImageCaptionCommand extends Command {
|
|
|
2302
2547
|
}
|
|
2303
2548
|
}
|
|
2304
2549
|
/**
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2550
|
+
* Executes the command.
|
|
2551
|
+
*
|
|
2552
|
+
* ```ts
|
|
2553
|
+
* editor.execute( 'toggleImageCaption' );
|
|
2554
|
+
* ```
|
|
2555
|
+
*
|
|
2556
|
+
* @param options Options for the executed command.
|
|
2557
|
+
* @param options.focusCaptionOnShow When true and the caption shows up, the selection will be moved into it straight away.
|
|
2558
|
+
* @fires execute
|
|
2559
|
+
*/ execute(options = {}) {
|
|
2315
2560
|
const { focusCaptionOnShow } = options;
|
|
2316
2561
|
this.editor.model.change((writer)=>{
|
|
2317
2562
|
if (this.value) {
|
|
@@ -2322,12 +2567,12 @@ class ToggleImageCaptionCommand extends Command {
|
|
|
2322
2567
|
});
|
|
2323
2568
|
}
|
|
2324
2569
|
/**
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2570
|
+
* Shows the caption of the `<imageBlock>` or `<imageInline>`. Also:
|
|
2571
|
+
*
|
|
2572
|
+
* * it converts `<imageInline>` to `<imageBlock>` to show the caption,
|
|
2573
|
+
* * it attempts to restore the caption content from the `ImageCaptionEditing` caption registry,
|
|
2574
|
+
* * it moves the selection to the caption right away, it the `focusCaptionOnShow` option was set.
|
|
2575
|
+
*/ _showImageCaption(writer, focusCaptionOnShow) {
|
|
2331
2576
|
const model = this.editor.model;
|
|
2332
2577
|
const selection = model.document.selection;
|
|
2333
2578
|
const imageCaptionEditing = this.editor.plugins.get('ImageCaptionEditing');
|
|
@@ -2348,11 +2593,11 @@ class ToggleImageCaptionCommand extends Command {
|
|
|
2348
2593
|
}
|
|
2349
2594
|
}
|
|
2350
2595
|
/**
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2596
|
+
* Hides the caption of a selected image (or an image caption the selection is anchored to).
|
|
2597
|
+
*
|
|
2598
|
+
* The content of the caption is stored in the `ImageCaptionEditing` caption registry to make this
|
|
2599
|
+
* a reversible action.
|
|
2600
|
+
*/ _hideImageCaption(writer) {
|
|
2356
2601
|
const editor = this.editor;
|
|
2357
2602
|
const selection = editor.model.document.selection;
|
|
2358
2603
|
const imageCaptionEditing = editor.plugins.get('ImageCaptionEditing');
|
|
@@ -2372,23 +2617,41 @@ class ToggleImageCaptionCommand extends Command {
|
|
|
2372
2617
|
}
|
|
2373
2618
|
}
|
|
2374
2619
|
|
|
2375
|
-
|
|
2620
|
+
/**
|
|
2621
|
+
* The image caption engine plugin. It is responsible for:
|
|
2622
|
+
*
|
|
2623
|
+
* * registering converters for the caption element,
|
|
2624
|
+
* * registering converters for the caption model attribute,
|
|
2625
|
+
* * registering the {@link module:image/imagecaption/toggleimagecaptioncommand~ToggleImageCaptionCommand `toggleImageCaption`} command.
|
|
2626
|
+
*/ class ImageCaptionEditing extends Plugin {
|
|
2376
2627
|
/**
|
|
2377
|
-
|
|
2378
|
-
|
|
2628
|
+
* @inheritDoc
|
|
2629
|
+
*/ static get requires() {
|
|
2379
2630
|
return [
|
|
2380
2631
|
ImageUtils,
|
|
2381
2632
|
ImageCaptionUtils
|
|
2382
2633
|
];
|
|
2383
2634
|
}
|
|
2384
2635
|
/**
|
|
2385
|
-
|
|
2386
|
-
|
|
2636
|
+
* @inheritDoc
|
|
2637
|
+
*/ static get pluginName() {
|
|
2387
2638
|
return 'ImageCaptionEditing';
|
|
2388
2639
|
}
|
|
2389
2640
|
/**
|
|
2390
|
-
|
|
2391
|
-
|
|
2641
|
+
* A map that keeps saved JSONified image captions and image model elements they are
|
|
2642
|
+
* associated with.
|
|
2643
|
+
*
|
|
2644
|
+
* To learn more about this system, see {@link #_saveCaption}.
|
|
2645
|
+
*/ _savedCaptionsMap;
|
|
2646
|
+
/**
|
|
2647
|
+
* @inheritDoc
|
|
2648
|
+
*/ constructor(editor){
|
|
2649
|
+
super(editor);
|
|
2650
|
+
this._savedCaptionsMap = new WeakMap();
|
|
2651
|
+
}
|
|
2652
|
+
/**
|
|
2653
|
+
* @inheritDoc
|
|
2654
|
+
*/ init() {
|
|
2392
2655
|
const editor = this.editor;
|
|
2393
2656
|
const schema = editor.model.schema;
|
|
2394
2657
|
// Schema configuration.
|
|
@@ -2409,9 +2672,9 @@ class ImageCaptionEditing extends Plugin {
|
|
|
2409
2672
|
this._registerCaptionReconversion();
|
|
2410
2673
|
}
|
|
2411
2674
|
/**
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2675
|
+
* Configures conversion pipelines to support upcasting and downcasting
|
|
2676
|
+
* image captions.
|
|
2677
|
+
*/ _setupConversion() {
|
|
2415
2678
|
const editor = this.editor;
|
|
2416
2679
|
const view = editor.editing.view;
|
|
2417
2680
|
const imageUtils = editor.plugins.get('ImageUtils');
|
|
@@ -2458,10 +2721,10 @@ class ImageCaptionEditing extends Plugin {
|
|
|
2458
2721
|
});
|
|
2459
2722
|
}
|
|
2460
2723
|
/**
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2724
|
+
* Integrates with {@link module:image/image/imagetypecommand~ImageTypeCommand image type commands}
|
|
2725
|
+
* to make sure the caption is preserved when the type of an image changes so it can be restored
|
|
2726
|
+
* in the future if the user decides they want their caption back.
|
|
2727
|
+
*/ _setupImageTypeCommandsIntegration() {
|
|
2465
2728
|
const editor = this.editor;
|
|
2466
2729
|
const imageUtils = editor.plugins.get('ImageUtils');
|
|
2467
2730
|
const imageCaptionUtils = editor.plugins.get('ImageCaptionUtils');
|
|
@@ -2513,42 +2776,42 @@ class ImageCaptionEditing extends Plugin {
|
|
|
2513
2776
|
}
|
|
2514
2777
|
}
|
|
2515
2778
|
/**
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2779
|
+
* Returns the saved {@link module:engine/model/element~Element#toJSON JSONified} caption
|
|
2780
|
+
* of an image model element.
|
|
2781
|
+
*
|
|
2782
|
+
* See {@link #_saveCaption}.
|
|
2783
|
+
*
|
|
2784
|
+
* @internal
|
|
2785
|
+
* @param imageModelElement The model element the caption should be returned for.
|
|
2786
|
+
* @returns The model caption element or `null` if there is none.
|
|
2787
|
+
*/ _getSavedCaption(imageModelElement) {
|
|
2525
2788
|
const jsonObject = this._savedCaptionsMap.get(imageModelElement);
|
|
2526
2789
|
return jsonObject ? Element.fromJSON(jsonObject) : null;
|
|
2527
2790
|
}
|
|
2528
2791
|
/**
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2792
|
+
* Saves a {@link module:engine/model/element~Element#toJSON JSONified} caption for
|
|
2793
|
+
* an image element to allow restoring it in the future.
|
|
2794
|
+
*
|
|
2795
|
+
* A caption is saved every time it gets hidden and/or the type of an image changes. The
|
|
2796
|
+
* user should be able to restore it on demand.
|
|
2797
|
+
*
|
|
2798
|
+
* **Note**: The caption cannot be stored in the image model element attribute because,
|
|
2799
|
+
* for instance, when the model state propagates to collaborators, the attribute would get
|
|
2800
|
+
* lost (mainly because it does not convert to anything when the caption is hidden) and
|
|
2801
|
+
* the states of collaborators' models would de-synchronize causing numerous issues.
|
|
2802
|
+
*
|
|
2803
|
+
* See {@link #_getSavedCaption}.
|
|
2804
|
+
*
|
|
2805
|
+
* @internal
|
|
2806
|
+
* @param imageModelElement The model element the caption is saved for.
|
|
2807
|
+
* @param caption The caption model element to be saved.
|
|
2808
|
+
*/ _saveCaption(imageModelElement, caption) {
|
|
2546
2809
|
this._savedCaptionsMap.set(imageModelElement, caption.toJSON());
|
|
2547
2810
|
}
|
|
2548
2811
|
/**
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2812
|
+
* Reconverts image caption when image alt attribute changes.
|
|
2813
|
+
* The change of alt attribute is reflected in caption's aria-label attribute.
|
|
2814
|
+
*/ _registerCaptionReconversion() {
|
|
2552
2815
|
const editor = this.editor;
|
|
2553
2816
|
const model = editor.model;
|
|
2554
2817
|
const imageUtils = editor.plugins.get('ImageUtils');
|
|
@@ -2570,30 +2833,26 @@ class ImageCaptionEditing extends Plugin {
|
|
|
2570
2833
|
}
|
|
2571
2834
|
});
|
|
2572
2835
|
}
|
|
2573
|
-
/**
|
|
2574
|
-
* @inheritDoc
|
|
2575
|
-
*/ constructor(editor){
|
|
2576
|
-
super(editor);
|
|
2577
|
-
this._savedCaptionsMap = new WeakMap();
|
|
2578
|
-
}
|
|
2579
2836
|
}
|
|
2580
2837
|
|
|
2581
|
-
|
|
2838
|
+
/**
|
|
2839
|
+
* The image caption UI plugin. It introduces the `'toggleImageCaption'` UI button.
|
|
2840
|
+
*/ class ImageCaptionUI extends Plugin {
|
|
2582
2841
|
/**
|
|
2583
|
-
|
|
2584
|
-
|
|
2842
|
+
* @inheritDoc
|
|
2843
|
+
*/ static get requires() {
|
|
2585
2844
|
return [
|
|
2586
2845
|
ImageCaptionUtils
|
|
2587
2846
|
];
|
|
2588
2847
|
}
|
|
2589
2848
|
/**
|
|
2590
|
-
|
|
2591
|
-
|
|
2849
|
+
* @inheritDoc
|
|
2850
|
+
*/ static get pluginName() {
|
|
2592
2851
|
return 'ImageCaptionUI';
|
|
2593
2852
|
}
|
|
2594
2853
|
/**
|
|
2595
|
-
|
|
2596
|
-
|
|
2854
|
+
* @inheritDoc
|
|
2855
|
+
*/ init() {
|
|
2597
2856
|
const editor = this.editor;
|
|
2598
2857
|
const editingView = editor.editing.view;
|
|
2599
2858
|
const imageCaptionUtils = editor.plugins.get('ImageCaptionUtils');
|
|
@@ -2628,18 +2887,22 @@ class ImageCaptionUI extends Plugin {
|
|
|
2628
2887
|
}
|
|
2629
2888
|
}
|
|
2630
2889
|
|
|
2631
|
-
|
|
2890
|
+
/**
|
|
2891
|
+
* The image caption plugin.
|
|
2892
|
+
*
|
|
2893
|
+
* For a detailed overview, check the {@glink features/images/images-captions image caption} documentation.
|
|
2894
|
+
*/ class ImageCaption extends Plugin {
|
|
2632
2895
|
/**
|
|
2633
|
-
|
|
2634
|
-
|
|
2896
|
+
* @inheritDoc
|
|
2897
|
+
*/ static get requires() {
|
|
2635
2898
|
return [
|
|
2636
2899
|
ImageCaptionEditing,
|
|
2637
2900
|
ImageCaptionUI
|
|
2638
2901
|
];
|
|
2639
2902
|
}
|
|
2640
2903
|
/**
|
|
2641
|
-
|
|
2642
|
-
|
|
2904
|
+
* @inheritDoc
|
|
2905
|
+
*/ static get pluginName() {
|
|
2643
2906
|
return 'ImageCaption';
|
|
2644
2907
|
}
|
|
2645
2908
|
}
|
|
@@ -2746,58 +3009,45 @@ class ImageCaption extends Plugin {
|
|
|
2746
3009
|
});
|
|
2747
3010
|
}
|
|
2748
3011
|
|
|
2749
|
-
|
|
3012
|
+
/**
|
|
3013
|
+
* The image upload button plugin.
|
|
3014
|
+
*
|
|
3015
|
+
* For a detailed overview, check the {@glink features/images/image-upload/image-upload Image upload feature} documentation.
|
|
3016
|
+
*
|
|
3017
|
+
* Adds the `'uploadImage'` button to the {@link module:ui/componentfactory~ComponentFactory UI component factory}
|
|
3018
|
+
* and also the `imageUpload` button as an alias for backward compatibility.
|
|
3019
|
+
*
|
|
3020
|
+
* Adds the `'menuBar:uploadImage'` menu button to the {@link module:ui/componentfactory~ComponentFactory UI component factory}.
|
|
3021
|
+
*
|
|
3022
|
+
* It also integrates with the `insertImage` toolbar component and `menuBar:insertImage` menu component, which are the default components
|
|
3023
|
+
* through which image upload is available.
|
|
3024
|
+
*/ class ImageUploadUI extends Plugin {
|
|
2750
3025
|
/**
|
|
2751
|
-
|
|
2752
|
-
|
|
3026
|
+
* @inheritDoc
|
|
3027
|
+
*/ static get pluginName() {
|
|
2753
3028
|
return 'ImageUploadUI';
|
|
2754
3029
|
}
|
|
2755
3030
|
/**
|
|
2756
|
-
|
|
2757
|
-
|
|
3031
|
+
* @inheritDoc
|
|
3032
|
+
*/ init() {
|
|
2758
3033
|
const editor = this.editor;
|
|
2759
|
-
const t = editor.t;
|
|
2760
|
-
const toolbarComponentCreator = ()=>{
|
|
2761
|
-
const button = this._createButton(FileDialogButtonView);
|
|
2762
|
-
button.set({
|
|
2763
|
-
label: t('Upload image from computer'),
|
|
2764
|
-
tooltip: true
|
|
2765
|
-
});
|
|
2766
|
-
return button;
|
|
2767
|
-
};
|
|
2768
3034
|
// Setup `uploadImage` button and add `imageUpload` button as an alias for backward compatibility.
|
|
2769
|
-
editor.ui.componentFactory.add('uploadImage',
|
|
2770
|
-
editor.ui.componentFactory.add('imageUpload',
|
|
2771
|
-
editor.ui.componentFactory.add('menuBar:uploadImage', ()=>
|
|
2772
|
-
const button = this._createButton(MenuBarMenuListItemFileDialogButtonView);
|
|
2773
|
-
button.label = t('Image from computer');
|
|
2774
|
-
return button;
|
|
2775
|
-
});
|
|
3035
|
+
editor.ui.componentFactory.add('uploadImage', ()=>this._createToolbarButton());
|
|
3036
|
+
editor.ui.componentFactory.add('imageUpload', ()=>this._createToolbarButton());
|
|
3037
|
+
editor.ui.componentFactory.add('menuBar:uploadImage', ()=>this._createMenuBarButton('standalone'));
|
|
2776
3038
|
if (editor.plugins.has('ImageInsertUI')) {
|
|
2777
|
-
|
|
2778
|
-
imageInsertUI.registerIntegration({
|
|
3039
|
+
editor.plugins.get('ImageInsertUI').registerIntegration({
|
|
2779
3040
|
name: 'upload',
|
|
2780
3041
|
observable: ()=>editor.commands.get('uploadImage'),
|
|
2781
|
-
buttonViewCreator: ()=>
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
return uploadImageButton;
|
|
2785
|
-
},
|
|
2786
|
-
formViewCreator: ()=>{
|
|
2787
|
-
const uploadImageButton = editor.ui.componentFactory.create('uploadImage');
|
|
2788
|
-
uploadImageButton.withText = true;
|
|
2789
|
-
uploadImageButton.bind('label').to(imageInsertUI, 'isImageSelected', (isImageSelected)=>isImageSelected ? t('Replace from computer') : t('Upload from computer'));
|
|
2790
|
-
uploadImageButton.on('execute', ()=>{
|
|
2791
|
-
imageInsertUI.dropdownView.isOpen = false;
|
|
2792
|
-
});
|
|
2793
|
-
return uploadImageButton;
|
|
2794
|
-
}
|
|
3042
|
+
buttonViewCreator: ()=>this._createToolbarButton(),
|
|
3043
|
+
formViewCreator: ()=>this._createDropdownButton(),
|
|
3044
|
+
menuBarButtonViewCreator: (isOnly)=>this._createMenuBarButton(isOnly ? 'insertOnly' : 'insertNested')
|
|
2795
3045
|
});
|
|
2796
3046
|
}
|
|
2797
3047
|
}
|
|
2798
3048
|
/**
|
|
2799
|
-
|
|
2800
|
-
|
|
3049
|
+
* Creates the base for various kinds of the button component provided by this feature.
|
|
3050
|
+
*/ _createButton(ButtonClass) {
|
|
2801
3051
|
const editor = this.editor;
|
|
2802
3052
|
const locale = editor.locale;
|
|
2803
3053
|
const command = editor.commands.get('uploadImage');
|
|
@@ -2808,7 +3058,7 @@ class ImageUploadUI extends Plugin {
|
|
|
2808
3058
|
view.set({
|
|
2809
3059
|
acceptedType: imageTypes.map((type)=>`image/${type}`).join(','),
|
|
2810
3060
|
allowMultipleFiles: true,
|
|
2811
|
-
label: t('Upload
|
|
3061
|
+
label: t('Upload from computer'),
|
|
2812
3062
|
icon: icons.imageUpload
|
|
2813
3063
|
});
|
|
2814
3064
|
view.bind('isEnabled').to(command);
|
|
@@ -2823,17 +3073,73 @@ class ImageUploadUI extends Plugin {
|
|
|
2823
3073
|
});
|
|
2824
3074
|
return view;
|
|
2825
3075
|
}
|
|
3076
|
+
/**
|
|
3077
|
+
* Creates a simple toolbar button, with an icon and a tooltip.
|
|
3078
|
+
*/ _createToolbarButton() {
|
|
3079
|
+
const t = this.editor.locale.t;
|
|
3080
|
+
const imageInsertUI = this.editor.plugins.get('ImageInsertUI');
|
|
3081
|
+
const button = this._createButton(FileDialogButtonView);
|
|
3082
|
+
button.tooltip = true;
|
|
3083
|
+
button.bind('label').to(imageInsertUI, 'isImageSelected', (isImageSelected)=>isImageSelected ? t('Replace image from computer') : t('Upload image from computer'));
|
|
3084
|
+
return button;
|
|
3085
|
+
}
|
|
3086
|
+
/**
|
|
3087
|
+
* Creates a button for the dropdown view, with an icon, text and no tooltip.
|
|
3088
|
+
*/ _createDropdownButton() {
|
|
3089
|
+
const t = this.editor.locale.t;
|
|
3090
|
+
const imageInsertUI = this.editor.plugins.get('ImageInsertUI');
|
|
3091
|
+
const button = this._createButton(FileDialogButtonView);
|
|
3092
|
+
button.withText = true;
|
|
3093
|
+
button.bind('label').to(imageInsertUI, 'isImageSelected', (isImageSelected)=>isImageSelected ? t('Replace from computer') : t('Upload from computer'));
|
|
3094
|
+
button.on('execute', ()=>{
|
|
3095
|
+
imageInsertUI.dropdownView.isOpen = false;
|
|
3096
|
+
});
|
|
3097
|
+
return button;
|
|
3098
|
+
}
|
|
3099
|
+
/**
|
|
3100
|
+
* Creates a button for the menu bar.
|
|
3101
|
+
*/ _createMenuBarButton(type) {
|
|
3102
|
+
const t = this.editor.locale.t;
|
|
3103
|
+
const button = this._createButton(MenuBarMenuListItemFileDialogButtonView);
|
|
3104
|
+
button.withText = true;
|
|
3105
|
+
switch(type){
|
|
3106
|
+
case 'standalone':
|
|
3107
|
+
button.label = t('Image from computer');
|
|
3108
|
+
break;
|
|
3109
|
+
case 'insertOnly':
|
|
3110
|
+
button.label = t('Image');
|
|
3111
|
+
break;
|
|
3112
|
+
case 'insertNested':
|
|
3113
|
+
button.label = t('From computer');
|
|
3114
|
+
break;
|
|
3115
|
+
}
|
|
3116
|
+
return button;
|
|
3117
|
+
}
|
|
2826
3118
|
}
|
|
2827
3119
|
|
|
2828
|
-
|
|
3120
|
+
/**
|
|
3121
|
+
* The image upload progress plugin.
|
|
3122
|
+
* It shows a placeholder when the image is read from the disk and a progress bar while the image is uploading.
|
|
3123
|
+
*/ class ImageUploadProgress extends Plugin {
|
|
2829
3124
|
/**
|
|
2830
|
-
|
|
2831
|
-
|
|
3125
|
+
* @inheritDoc
|
|
3126
|
+
*/ static get pluginName() {
|
|
2832
3127
|
return 'ImageUploadProgress';
|
|
2833
3128
|
}
|
|
2834
3129
|
/**
|
|
2835
|
-
|
|
2836
|
-
|
|
3130
|
+
* The image placeholder that is displayed before real image data can be accessed.
|
|
3131
|
+
*
|
|
3132
|
+
* For the record, this image is a 1x1 px GIF with an aspect ratio set by CSS.
|
|
3133
|
+
*/ placeholder;
|
|
3134
|
+
/**
|
|
3135
|
+
* @inheritDoc
|
|
3136
|
+
*/ constructor(editor){
|
|
3137
|
+
super(editor);
|
|
3138
|
+
this.placeholder = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
|
|
3139
|
+
}
|
|
3140
|
+
/**
|
|
3141
|
+
* @inheritDoc
|
|
3142
|
+
*/ init() {
|
|
2837
3143
|
const editor = this.editor;
|
|
2838
3144
|
// Upload status change - update image's view according to that status.
|
|
2839
3145
|
if (editor.plugins.has('ImageBlockEditing')) {
|
|
@@ -2844,62 +3150,56 @@ class ImageUploadProgress extends Plugin {
|
|
|
2844
3150
|
}
|
|
2845
3151
|
}
|
|
2846
3152
|
/**
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
3153
|
+
* This method is called each time the image `uploadStatus` attribute is changed.
|
|
3154
|
+
*
|
|
3155
|
+
* @param evt An object containing information about the fired event.
|
|
3156
|
+
* @param data Additional information about the change.
|
|
3157
|
+
*/ uploadStatusChange = (evt, data, conversionApi)=>{
|
|
3158
|
+
const editor = this.editor;
|
|
3159
|
+
const modelImage = data.item;
|
|
3160
|
+
const uploadId = modelImage.getAttribute('uploadId');
|
|
3161
|
+
if (!conversionApi.consumable.consume(data.item, evt.name)) {
|
|
3162
|
+
return;
|
|
3163
|
+
}
|
|
3164
|
+
const imageUtils = editor.plugins.get('ImageUtils');
|
|
3165
|
+
const fileRepository = editor.plugins.get(FileRepository);
|
|
3166
|
+
const status = uploadId ? data.attributeNewValue : null;
|
|
3167
|
+
const placeholder = this.placeholder;
|
|
3168
|
+
const viewFigure = editor.editing.mapper.toViewElement(modelImage);
|
|
3169
|
+
const viewWriter = conversionApi.writer;
|
|
3170
|
+
if (status == 'reading') {
|
|
3171
|
+
// Start "appearing" effect and show placeholder with infinite progress bar on the top
|
|
3172
|
+
// while image is read from disk.
|
|
3173
|
+
_startAppearEffect(viewFigure, viewWriter);
|
|
3174
|
+
_showPlaceholder(imageUtils, placeholder, viewFigure, viewWriter);
|
|
3175
|
+
return;
|
|
3176
|
+
}
|
|
3177
|
+
// Show progress bar on the top of the image when image is uploading.
|
|
3178
|
+
if (status == 'uploading') {
|
|
3179
|
+
const loader = fileRepository.loaders.get(uploadId);
|
|
3180
|
+
// Start appear effect if needed - see https://github.com/ckeditor/ckeditor5-image/issues/191.
|
|
3181
|
+
_startAppearEffect(viewFigure, viewWriter);
|
|
3182
|
+
if (!loader) {
|
|
3183
|
+
// There is no loader associated with uploadId - this means that image came from external changes.
|
|
3184
|
+
// In such cases we still want to show the placeholder until image is fully uploaded.
|
|
3185
|
+
// Show placeholder if needed - see https://github.com/ckeditor/ckeditor5-image/issues/191.
|
|
2872
3186
|
_showPlaceholder(imageUtils, placeholder, viewFigure, viewWriter);
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
// Start appear effect if needed - see https://github.com/ckeditor/ckeditor5-image/issues/191.
|
|
2879
|
-
_startAppearEffect(viewFigure, viewWriter);
|
|
2880
|
-
if (!loader) {
|
|
2881
|
-
// There is no loader associated with uploadId - this means that image came from external changes.
|
|
2882
|
-
// In such cases we still want to show the placeholder until image is fully uploaded.
|
|
2883
|
-
// Show placeholder if needed - see https://github.com/ckeditor/ckeditor5-image/issues/191.
|
|
2884
|
-
_showPlaceholder(imageUtils, placeholder, viewFigure, viewWriter);
|
|
2885
|
-
} else {
|
|
2886
|
-
// Hide placeholder and initialize progress bar showing upload progress.
|
|
2887
|
-
_hidePlaceholder(viewFigure, viewWriter);
|
|
2888
|
-
_showProgressBar(viewFigure, viewWriter, loader, editor.editing.view);
|
|
2889
|
-
_displayLocalImage(imageUtils, viewFigure, viewWriter, loader);
|
|
2890
|
-
}
|
|
2891
|
-
return;
|
|
2892
|
-
}
|
|
2893
|
-
if (status == 'complete' && fileRepository.loaders.get(uploadId)) {
|
|
2894
|
-
_showCompleteIcon(viewFigure, viewWriter, editor.editing.view);
|
|
3187
|
+
} else {
|
|
3188
|
+
// Hide placeholder and initialize progress bar showing upload progress.
|
|
3189
|
+
_hidePlaceholder(viewFigure, viewWriter);
|
|
3190
|
+
_showProgressBar(viewFigure, viewWriter, loader, editor.editing.view);
|
|
3191
|
+
_displayLocalImage(imageUtils, viewFigure, viewWriter, loader);
|
|
2895
3192
|
}
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
}
|
|
2901
|
-
|
|
2902
|
-
|
|
3193
|
+
return;
|
|
3194
|
+
}
|
|
3195
|
+
if (status == 'complete' && fileRepository.loaders.get(uploadId)) {
|
|
3196
|
+
_showCompleteIcon(viewFigure, viewWriter, editor.editing.view);
|
|
3197
|
+
}
|
|
3198
|
+
// Clean up.
|
|
3199
|
+
_hideProgressBar(viewFigure, viewWriter);
|
|
3200
|
+
_hidePlaceholder(viewFigure, viewWriter);
|
|
3201
|
+
_stopAppearEffect(viewFigure, viewWriter);
|
|
3202
|
+
};
|
|
2903
3203
|
}
|
|
2904
3204
|
/**
|
|
2905
3205
|
* Adds ck-appear class to the image figure if one is not already applied.
|
|
@@ -3009,10 +3309,42 @@ class ImageUploadProgress extends Plugin {
|
|
|
3009
3309
|
}
|
|
3010
3310
|
}
|
|
3011
3311
|
|
|
3012
|
-
|
|
3312
|
+
/**
|
|
3313
|
+
* @module image/imageupload/uploadimagecommand
|
|
3314
|
+
*/ /**
|
|
3315
|
+
* The upload image command.
|
|
3316
|
+
*
|
|
3317
|
+
* The command is registered by the {@link module:image/imageupload/imageuploadediting~ImageUploadEditing} plugin as `uploadImage`
|
|
3318
|
+
* and it is also available via aliased `imageUpload` name.
|
|
3319
|
+
*
|
|
3320
|
+
* In order to upload an image at the current selection position
|
|
3321
|
+
* (according to the {@link module:widget/utils~findOptimalInsertionRange} algorithm),
|
|
3322
|
+
* execute the command and pass the native image file instance:
|
|
3323
|
+
*
|
|
3324
|
+
* ```ts
|
|
3325
|
+
* this.listenTo( editor.editing.view.document, 'clipboardInput', ( evt, data ) => {
|
|
3326
|
+
* // Assuming that only images were pasted:
|
|
3327
|
+
* const images = Array.from( data.dataTransfer.files );
|
|
3328
|
+
*
|
|
3329
|
+
* // Upload the first image:
|
|
3330
|
+
* editor.execute( 'uploadImage', { file: images[ 0 ] } );
|
|
3331
|
+
* } );
|
|
3332
|
+
* ```
|
|
3333
|
+
*
|
|
3334
|
+
* It is also possible to insert multiple images at once:
|
|
3335
|
+
*
|
|
3336
|
+
* ```ts
|
|
3337
|
+
* editor.execute( 'uploadImage', {
|
|
3338
|
+
* file: [
|
|
3339
|
+
* file1,
|
|
3340
|
+
* file2
|
|
3341
|
+
* ]
|
|
3342
|
+
* } );
|
|
3343
|
+
* ```
|
|
3344
|
+
*/ class UploadImageCommand extends Command {
|
|
3013
3345
|
/**
|
|
3014
|
-
|
|
3015
|
-
|
|
3346
|
+
* @inheritDoc
|
|
3347
|
+
*/ refresh() {
|
|
3016
3348
|
const editor = this.editor;
|
|
3017
3349
|
const imageUtils = editor.plugins.get('ImageUtils');
|
|
3018
3350
|
const selectedElement = editor.model.document.selection.getSelectedElement();
|
|
@@ -3020,12 +3352,12 @@ class UploadImageCommand extends Command {
|
|
|
3020
3352
|
this.isEnabled = imageUtils.isImageAllowed() || imageUtils.isImage(selectedElement);
|
|
3021
3353
|
}
|
|
3022
3354
|
/**
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3355
|
+
* Executes the command.
|
|
3356
|
+
*
|
|
3357
|
+
* @fires execute
|
|
3358
|
+
* @param options Options for the executed command.
|
|
3359
|
+
* @param options.file The image file or an array of image files to upload.
|
|
3360
|
+
*/ execute(options) {
|
|
3029
3361
|
const files = toArray(options.file);
|
|
3030
3362
|
const selection = this.editor.model.document.selection;
|
|
3031
3363
|
const imageUtils = this.editor.plugins.get('ImageUtils');
|
|
@@ -3051,8 +3383,8 @@ class UploadImageCommand extends Command {
|
|
|
3051
3383
|
});
|
|
3052
3384
|
}
|
|
3053
3385
|
/**
|
|
3054
|
-
|
|
3055
|
-
|
|
3386
|
+
* Handles uploading single file.
|
|
3387
|
+
*/ _uploadImage(file, attributes, position) {
|
|
3056
3388
|
const editor = this.editor;
|
|
3057
3389
|
const fileRepository = editor.plugins.get(FileRepository);
|
|
3058
3390
|
const loader = fileRepository.createLoader(file);
|
|
@@ -3068,10 +3400,16 @@ class UploadImageCommand extends Command {
|
|
|
3068
3400
|
}
|
|
3069
3401
|
}
|
|
3070
3402
|
|
|
3071
|
-
|
|
3403
|
+
/**
|
|
3404
|
+
* The editing part of the image upload feature. It registers the `'uploadImage'` command
|
|
3405
|
+
* and the `imageUpload` command as an aliased name.
|
|
3406
|
+
*
|
|
3407
|
+
* When an image is uploaded, it fires the {@link ~ImageUploadEditing#event:uploadComplete `uploadComplete`} event
|
|
3408
|
+
* that allows adding custom attributes to the {@link module:engine/model/element~Element image element}.
|
|
3409
|
+
*/ class ImageUploadEditing extends Plugin {
|
|
3072
3410
|
/**
|
|
3073
|
-
|
|
3074
|
-
|
|
3411
|
+
* @inheritDoc
|
|
3412
|
+
*/ static get requires() {
|
|
3075
3413
|
return [
|
|
3076
3414
|
FileRepository,
|
|
3077
3415
|
Notification,
|
|
@@ -3083,8 +3421,35 @@ class ImageUploadEditing extends Plugin {
|
|
|
3083
3421
|
return 'ImageUploadEditing';
|
|
3084
3422
|
}
|
|
3085
3423
|
/**
|
|
3086
|
-
|
|
3087
|
-
|
|
3424
|
+
* An internal mapping of {@link module:upload/filerepository~FileLoader#id file loader UIDs} and
|
|
3425
|
+
* model elements during the upload.
|
|
3426
|
+
*
|
|
3427
|
+
* Model element of the uploaded image can change, for instance, when {@link module:image/image/imagetypecommand~ImageTypeCommand}
|
|
3428
|
+
* is executed as a result of adding caption or changing image style. As a result, the upload logic must keep track of the model
|
|
3429
|
+
* element (reference) and resolve the upload for the correct model element (instead of the one that landed in the `$graveyard`
|
|
3430
|
+
* after image type changed).
|
|
3431
|
+
*/ _uploadImageElements;
|
|
3432
|
+
/**
|
|
3433
|
+
* @inheritDoc
|
|
3434
|
+
*/ constructor(editor){
|
|
3435
|
+
super(editor);
|
|
3436
|
+
editor.config.define('image', {
|
|
3437
|
+
upload: {
|
|
3438
|
+
types: [
|
|
3439
|
+
'jpeg',
|
|
3440
|
+
'png',
|
|
3441
|
+
'gif',
|
|
3442
|
+
'bmp',
|
|
3443
|
+
'webp',
|
|
3444
|
+
'tiff'
|
|
3445
|
+
]
|
|
3446
|
+
}
|
|
3447
|
+
});
|
|
3448
|
+
this._uploadImageElements = new Map();
|
|
3449
|
+
}
|
|
3450
|
+
/**
|
|
3451
|
+
* @inheritDoc
|
|
3452
|
+
*/ init() {
|
|
3088
3453
|
const editor = this.editor;
|
|
3089
3454
|
const doc = editor.model.document;
|
|
3090
3455
|
const conversion = editor.conversion;
|
|
@@ -3227,8 +3592,8 @@ class ImageUploadEditing extends Plugin {
|
|
|
3227
3592
|
});
|
|
3228
3593
|
}
|
|
3229
3594
|
/**
|
|
3230
|
-
|
|
3231
|
-
|
|
3595
|
+
* @inheritDoc
|
|
3596
|
+
*/ afterInit() {
|
|
3232
3597
|
const schema = this.editor.model.schema;
|
|
3233
3598
|
// Setup schema to allow uploadId and uploadStatus for images.
|
|
3234
3599
|
// Wait for ImageBlockEditing or ImageInlineEditing to register their elements first,
|
|
@@ -3251,12 +3616,12 @@ class ImageUploadEditing extends Plugin {
|
|
|
3251
3616
|
}
|
|
3252
3617
|
}
|
|
3253
3618
|
/**
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3619
|
+
* Reads and uploads an image.
|
|
3620
|
+
*
|
|
3621
|
+
* The image is read from the disk and as a Base64-encoded string it is set temporarily to
|
|
3622
|
+
* `image[src]`. When the image is successfully uploaded, the temporary data is replaced with the target
|
|
3623
|
+
* image's URL (the URL to the uploaded image on the server).
|
|
3624
|
+
*/ _readAndUpload(loader) {
|
|
3260
3625
|
const editor = this.editor;
|
|
3261
3626
|
const model = editor.model;
|
|
3262
3627
|
const t = editor.locale.t;
|
|
@@ -3355,11 +3720,11 @@ class ImageUploadEditing extends Plugin {
|
|
|
3355
3720
|
}
|
|
3356
3721
|
}
|
|
3357
3722
|
/**
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3723
|
+
* Creates the `srcset` attribute based on a given file upload response and sets it as an attribute to a specific image element.
|
|
3724
|
+
*
|
|
3725
|
+
* @param data Data object from which `srcset` will be created.
|
|
3726
|
+
* @param image The image element on which the `srcset` attribute will be set.
|
|
3727
|
+
*/ _parseAndSetSrcsetAttributeOnImage(data, image, writer) {
|
|
3363
3728
|
// Srcset attribute for responsive images support.
|
|
3364
3729
|
let maxWidth = 0;
|
|
3365
3730
|
const srcsetAttribute = Object.keys(data)// Filter out keys that are not integers.
|
|
@@ -3382,24 +3747,6 @@ class ImageUploadEditing extends Plugin {
|
|
|
3382
3747
|
writer.setAttributes(attributes, image);
|
|
3383
3748
|
}
|
|
3384
3749
|
}
|
|
3385
|
-
/**
|
|
3386
|
-
* @inheritDoc
|
|
3387
|
-
*/ constructor(editor){
|
|
3388
|
-
super(editor);
|
|
3389
|
-
editor.config.define('image', {
|
|
3390
|
-
upload: {
|
|
3391
|
-
types: [
|
|
3392
|
-
'jpeg',
|
|
3393
|
-
'png',
|
|
3394
|
-
'gif',
|
|
3395
|
-
'bmp',
|
|
3396
|
-
'webp',
|
|
3397
|
-
'tiff'
|
|
3398
|
-
]
|
|
3399
|
-
}
|
|
3400
|
-
});
|
|
3401
|
-
this._uploadImageElements = new Map();
|
|
3402
|
-
}
|
|
3403
3750
|
}
|
|
3404
3751
|
/**
|
|
3405
3752
|
* Returns `true` if non-empty `text/html` is included in the data transfer.
|
|
@@ -3411,15 +3758,25 @@ function getImagesFromChangeItem(editor, item) {
|
|
|
3411
3758
|
return Array.from(editor.model.createRangeOn(item)).filter((value)=>imageUtils.isImage(value.item)).map((value)=>value.item);
|
|
3412
3759
|
}
|
|
3413
3760
|
|
|
3414
|
-
|
|
3761
|
+
/**
|
|
3762
|
+
* The image upload plugin.
|
|
3763
|
+
*
|
|
3764
|
+
* For a detailed overview, check the {@glink features/images/image-upload/image-upload image upload feature} documentation.
|
|
3765
|
+
*
|
|
3766
|
+
* This plugin does not do anything directly, but it loads a set of specific plugins to enable image uploading:
|
|
3767
|
+
*
|
|
3768
|
+
* * {@link module:image/imageupload/imageuploadediting~ImageUploadEditing},
|
|
3769
|
+
* * {@link module:image/imageupload/imageuploadui~ImageUploadUI},
|
|
3770
|
+
* * {@link module:image/imageupload/imageuploadprogress~ImageUploadProgress}.
|
|
3771
|
+
*/ class ImageUpload extends Plugin {
|
|
3415
3772
|
/**
|
|
3416
|
-
|
|
3417
|
-
|
|
3773
|
+
* @inheritDoc
|
|
3774
|
+
*/ static get pluginName() {
|
|
3418
3775
|
return 'ImageUpload';
|
|
3419
3776
|
}
|
|
3420
3777
|
/**
|
|
3421
|
-
|
|
3422
|
-
|
|
3778
|
+
* @inheritDoc
|
|
3779
|
+
*/ static get requires() {
|
|
3423
3780
|
return [
|
|
3424
3781
|
ImageUploadEditing,
|
|
3425
3782
|
ImageUploadUI,
|
|
@@ -3428,112 +3785,28 @@ class ImageUpload extends Plugin {
|
|
|
3428
3785
|
}
|
|
3429
3786
|
}
|
|
3430
3787
|
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
for (const view of this._focusables){
|
|
3437
|
-
this.focusTracker.add(view.element);
|
|
3438
|
-
}
|
|
3439
|
-
// Start listening for the keystrokes coming from #element.
|
|
3440
|
-
this.keystrokes.listenTo(this.element);
|
|
3441
|
-
}
|
|
3442
|
-
/**
|
|
3443
|
-
* @inheritDoc
|
|
3444
|
-
*/ destroy() {
|
|
3445
|
-
super.destroy();
|
|
3446
|
-
this.focusTracker.destroy();
|
|
3447
|
-
this.keystrokes.destroy();
|
|
3448
|
-
}
|
|
3449
|
-
/**
|
|
3450
|
-
* Creates the {@link #urlInputView}.
|
|
3451
|
-
*/ _createUrlInputView() {
|
|
3452
|
-
const locale = this.locale;
|
|
3453
|
-
const t = locale.t;
|
|
3454
|
-
const urlInputView = new LabeledFieldView(locale, createLabeledInputText);
|
|
3455
|
-
urlInputView.bind('label').to(this, 'isImageSelected', (value)=>value ? t('Update image URL') : t('Insert image via URL'));
|
|
3456
|
-
urlInputView.bind('isEnabled').to(this);
|
|
3457
|
-
urlInputView.fieldView.placeholder = 'https://example.com/image.png';
|
|
3458
|
-
urlInputView.fieldView.bind('value').to(this, 'imageURLInputValue', (value)=>value || '');
|
|
3459
|
-
urlInputView.fieldView.on('input', ()=>{
|
|
3460
|
-
this.imageURLInputValue = urlInputView.fieldView.element.value.trim();
|
|
3461
|
-
});
|
|
3462
|
-
return urlInputView;
|
|
3463
|
-
}
|
|
3464
|
-
/**
|
|
3465
|
-
* Creates the {@link #insertButtonView}.
|
|
3466
|
-
*/ _createInsertButton() {
|
|
3467
|
-
const locale = this.locale;
|
|
3468
|
-
const t = locale.t;
|
|
3469
|
-
const insertButtonView = new ButtonView(locale);
|
|
3470
|
-
insertButtonView.set({
|
|
3471
|
-
icon: icons.check,
|
|
3472
|
-
class: 'ck-button-save',
|
|
3473
|
-
type: 'submit',
|
|
3474
|
-
withText: true
|
|
3475
|
-
});
|
|
3476
|
-
insertButtonView.bind('label').to(this, 'isImageSelected', (value)=>value ? t('Update') : t('Insert'));
|
|
3477
|
-
insertButtonView.bind('isEnabled').to(this, 'imageURLInputValue', this, 'isEnabled', (...values)=>values.every((value)=>value));
|
|
3478
|
-
insertButtonView.delegate('execute').to(this, 'submit');
|
|
3479
|
-
return insertButtonView;
|
|
3480
|
-
}
|
|
3788
|
+
/**
|
|
3789
|
+
* The insert an image via URL view.
|
|
3790
|
+
*
|
|
3791
|
+
* See {@link module:image/imageinsert/imageinsertviaurlui~ImageInsertViaUrlUI}.
|
|
3792
|
+
*/ class ImageInsertUrlView extends View {
|
|
3481
3793
|
/**
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
const locale = this.locale;
|
|
3485
|
-
const t = locale.t;
|
|
3486
|
-
const cancelButtonView = new ButtonView(locale);
|
|
3487
|
-
cancelButtonView.set({
|
|
3488
|
-
label: t('Cancel'),
|
|
3489
|
-
icon: icons.cancel,
|
|
3490
|
-
class: 'ck-button-cancel',
|
|
3491
|
-
withText: true
|
|
3492
|
-
});
|
|
3493
|
-
cancelButtonView.bind('isEnabled').to(this);
|
|
3494
|
-
cancelButtonView.delegate('execute').to(this, 'cancel');
|
|
3495
|
-
return cancelButtonView;
|
|
3496
|
-
}
|
|
3794
|
+
* The URL input field view.
|
|
3795
|
+
*/ urlInputView;
|
|
3497
3796
|
/**
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
if (direction === -1) {
|
|
3501
|
-
this.focusCycler.focusLast();
|
|
3502
|
-
} else {
|
|
3503
|
-
this.focusCycler.focusFirst();
|
|
3504
|
-
}
|
|
3505
|
-
}
|
|
3797
|
+
* An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
|
|
3798
|
+
*/ keystrokes;
|
|
3506
3799
|
/**
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3800
|
+
* Creates a view for the dropdown panel of {@link module:image/imageinsert/imageinsertui~ImageInsertUI}.
|
|
3801
|
+
*
|
|
3802
|
+
* @param locale The localization services instance.
|
|
3803
|
+
*/ constructor(locale){
|
|
3511
3804
|
super(locale);
|
|
3512
3805
|
this.set('imageURLInputValue', '');
|
|
3513
3806
|
this.set('isImageSelected', false);
|
|
3514
3807
|
this.set('isEnabled', true);
|
|
3515
|
-
this.focusTracker = new FocusTracker();
|
|
3516
3808
|
this.keystrokes = new KeystrokeHandler();
|
|
3517
|
-
this._focusables = new ViewCollection();
|
|
3518
|
-
this.focusCycler = new FocusCycler({
|
|
3519
|
-
focusables: this._focusables,
|
|
3520
|
-
focusTracker: this.focusTracker,
|
|
3521
|
-
keystrokeHandler: this.keystrokes,
|
|
3522
|
-
actions: {
|
|
3523
|
-
// Navigate form fields backwards using the Shift + Tab keystroke.
|
|
3524
|
-
focusPrevious: 'shift + tab',
|
|
3525
|
-
// Navigate form fields forwards using the Tab key.
|
|
3526
|
-
focusNext: 'tab'
|
|
3527
|
-
}
|
|
3528
|
-
});
|
|
3529
3809
|
this.urlInputView = this._createUrlInputView();
|
|
3530
|
-
this.insertButtonView = this._createInsertButton();
|
|
3531
|
-
this.cancelButtonView = this._createCancelButton();
|
|
3532
|
-
this._focusables.addMany([
|
|
3533
|
-
this.urlInputView,
|
|
3534
|
-
this.insertButtonView,
|
|
3535
|
-
this.cancelButtonView
|
|
3536
|
-
]);
|
|
3537
3810
|
this.setTemplate({
|
|
3538
3811
|
tag: 'div',
|
|
3539
3812
|
attributes: {
|
|
@@ -3551,131 +3824,222 @@ class ImageInsertUrlView extends View {
|
|
|
3551
3824
|
'ck',
|
|
3552
3825
|
'ck-image-insert-url__action-row'
|
|
3553
3826
|
]
|
|
3554
|
-
}
|
|
3555
|
-
children: [
|
|
3556
|
-
this.insertButtonView,
|
|
3557
|
-
this.cancelButtonView
|
|
3558
|
-
]
|
|
3827
|
+
}
|
|
3559
3828
|
}
|
|
3560
3829
|
]
|
|
3561
3830
|
});
|
|
3562
3831
|
}
|
|
3832
|
+
/**
|
|
3833
|
+
* @inheritDoc
|
|
3834
|
+
*/ render() {
|
|
3835
|
+
super.render();
|
|
3836
|
+
// Start listening for the keystrokes coming from #element.
|
|
3837
|
+
this.keystrokes.listenTo(this.element);
|
|
3838
|
+
}
|
|
3839
|
+
/**
|
|
3840
|
+
* @inheritDoc
|
|
3841
|
+
*/ destroy() {
|
|
3842
|
+
super.destroy();
|
|
3843
|
+
this.keystrokes.destroy();
|
|
3844
|
+
}
|
|
3845
|
+
/**
|
|
3846
|
+
* Creates the {@link #urlInputView}.
|
|
3847
|
+
*/ _createUrlInputView() {
|
|
3848
|
+
const locale = this.locale;
|
|
3849
|
+
const t = locale.t;
|
|
3850
|
+
const urlInputView = new LabeledFieldView(locale, createLabeledInputText);
|
|
3851
|
+
urlInputView.bind('label').to(this, 'isImageSelected', (value)=>value ? t('Update image URL') : t('Insert image via URL'));
|
|
3852
|
+
urlInputView.bind('isEnabled').to(this);
|
|
3853
|
+
urlInputView.fieldView.inputMode = 'url';
|
|
3854
|
+
urlInputView.fieldView.placeholder = 'https://example.com/image.png';
|
|
3855
|
+
urlInputView.fieldView.bind('value').to(this, 'imageURLInputValue', (value)=>value || '');
|
|
3856
|
+
urlInputView.fieldView.on('input', ()=>{
|
|
3857
|
+
this.imageURLInputValue = urlInputView.fieldView.element.value.trim();
|
|
3858
|
+
});
|
|
3859
|
+
return urlInputView;
|
|
3860
|
+
}
|
|
3861
|
+
/**
|
|
3862
|
+
* Focuses the view.
|
|
3863
|
+
*/ focus() {
|
|
3864
|
+
this.urlInputView.focus();
|
|
3865
|
+
}
|
|
3563
3866
|
}
|
|
3564
3867
|
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3868
|
+
/**
|
|
3869
|
+
* The image insert via URL plugin (UI part).
|
|
3870
|
+
*
|
|
3871
|
+
* The plugin introduces two UI components to the {@link module:ui/componentfactory~ComponentFactory UI component factory}:
|
|
3872
|
+
*
|
|
3873
|
+
* * the `'insertImageViaUrl'` toolbar button,
|
|
3874
|
+
* * the `'menuBar:insertImageViaUrl'` menu bar component.
|
|
3875
|
+
*
|
|
3876
|
+
* It also integrates with the `insertImage` toolbar component and `menuBar:insertImage` menu component, which are default components
|
|
3877
|
+
* through which inserting image via URL is available.
|
|
3878
|
+
*/ class ImageInsertViaUrlUI extends Plugin {
|
|
3879
|
+
_imageInsertUI;
|
|
3880
|
+
_formView;
|
|
3881
|
+
/**
|
|
3882
|
+
* @inheritDoc
|
|
3883
|
+
*/ static get pluginName() {
|
|
3569
3884
|
return 'ImageInsertViaUrlUI';
|
|
3570
3885
|
}
|
|
3571
3886
|
/**
|
|
3572
|
-
|
|
3573
|
-
|
|
3887
|
+
* @inheritDoc
|
|
3888
|
+
*/ static get requires() {
|
|
3574
3889
|
return [
|
|
3575
|
-
ImageInsertUI
|
|
3890
|
+
ImageInsertUI,
|
|
3891
|
+
Dialog
|
|
3576
3892
|
];
|
|
3577
3893
|
}
|
|
3894
|
+
init() {
|
|
3895
|
+
this.editor.ui.componentFactory.add('insertImageViaUrl', ()=>this._createToolbarButton());
|
|
3896
|
+
this.editor.ui.componentFactory.add('menuBar:insertImageViaUrl', ()=>this._createMenuBarButton('standalone'));
|
|
3897
|
+
}
|
|
3578
3898
|
/**
|
|
3579
|
-
|
|
3580
|
-
|
|
3899
|
+
* @inheritDoc
|
|
3900
|
+
*/ afterInit() {
|
|
3581
3901
|
this._imageInsertUI = this.editor.plugins.get('ImageInsertUI');
|
|
3582
3902
|
this._imageInsertUI.registerIntegration({
|
|
3583
3903
|
name: 'url',
|
|
3584
3904
|
observable: ()=>this.editor.commands.get('insertImage'),
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3905
|
+
buttonViewCreator: ()=>this._createToolbarButton(),
|
|
3906
|
+
formViewCreator: ()=>this._createDropdownButton(),
|
|
3907
|
+
menuBarButtonViewCreator: (isOnly)=>this._createMenuBarButton(isOnly ? 'insertOnly' : 'insertNested')
|
|
3908
|
+
});
|
|
3909
|
+
}
|
|
3910
|
+
/**
|
|
3911
|
+
* Creates the base for various kinds of the button component provided by this feature.
|
|
3912
|
+
*/ _createInsertUrlButton(ButtonClass) {
|
|
3913
|
+
const button = new ButtonClass(this.editor.locale);
|
|
3914
|
+
button.icon = icons.imageUrl;
|
|
3915
|
+
button.on('execute', ()=>{
|
|
3916
|
+
this._showModal();
|
|
3588
3917
|
});
|
|
3918
|
+
return button;
|
|
3919
|
+
}
|
|
3920
|
+
/**
|
|
3921
|
+
* Creates a simple toolbar button, with an icon and a tooltip.
|
|
3922
|
+
*/ _createToolbarButton() {
|
|
3923
|
+
const t = this.editor.locale.t;
|
|
3924
|
+
const button = this._createInsertUrlButton(ButtonView);
|
|
3925
|
+
button.tooltip = true;
|
|
3926
|
+
button.bind('label').to(this._imageInsertUI, 'isImageSelected', (isImageSelected)=>isImageSelected ? t('Update image URL') : t('Insert image via URL'));
|
|
3927
|
+
return button;
|
|
3928
|
+
}
|
|
3929
|
+
/**
|
|
3930
|
+
* Creates a button for the dropdown view, with an icon, text and no tooltip.
|
|
3931
|
+
*/ _createDropdownButton() {
|
|
3932
|
+
const t = this.editor.locale.t;
|
|
3933
|
+
const button = this._createInsertUrlButton(ButtonView);
|
|
3934
|
+
button.withText = true;
|
|
3935
|
+
button.bind('label').to(this._imageInsertUI, 'isImageSelected', (isImageSelected)=>isImageSelected ? t('Update image URL') : t('Insert via URL'));
|
|
3936
|
+
return button;
|
|
3937
|
+
}
|
|
3938
|
+
/**
|
|
3939
|
+
* Creates a button for the menu bar.
|
|
3940
|
+
*/ _createMenuBarButton(type) {
|
|
3941
|
+
const t = this.editor.locale.t;
|
|
3942
|
+
const button = this._createInsertUrlButton(MenuBarMenuListItemButtonView);
|
|
3943
|
+
button.withText = true;
|
|
3944
|
+
switch(type){
|
|
3945
|
+
case 'standalone':
|
|
3946
|
+
button.label = t('Image via URL');
|
|
3947
|
+
break;
|
|
3948
|
+
case 'insertOnly':
|
|
3949
|
+
button.label = t('Image');
|
|
3950
|
+
break;
|
|
3951
|
+
case 'insertNested':
|
|
3952
|
+
button.label = t('Via URL');
|
|
3953
|
+
break;
|
|
3954
|
+
}
|
|
3955
|
+
return button;
|
|
3589
3956
|
}
|
|
3590
3957
|
/**
|
|
3591
|
-
|
|
3592
|
-
|
|
3958
|
+
* Creates the form view used to submit the image URL.
|
|
3959
|
+
*/ _createInsertUrlView() {
|
|
3593
3960
|
const editor = this.editor;
|
|
3594
3961
|
const locale = editor.locale;
|
|
3595
|
-
const t = locale.t;
|
|
3596
3962
|
const replaceImageSourceCommand = editor.commands.get('replaceImageSource');
|
|
3597
3963
|
const insertImageCommand = editor.commands.get('insertImage');
|
|
3598
3964
|
const imageInsertUrlView = new ImageInsertUrlView(locale);
|
|
3599
|
-
const collapsibleView = isOnlyOne ? null : new CollapsibleView(locale, [
|
|
3600
|
-
imageInsertUrlView
|
|
3601
|
-
]);
|
|
3602
3965
|
imageInsertUrlView.bind('isImageSelected').to(this._imageInsertUI);
|
|
3603
3966
|
imageInsertUrlView.bind('isEnabled').toMany([
|
|
3604
3967
|
insertImageCommand,
|
|
3605
3968
|
replaceImageSourceCommand
|
|
3606
3969
|
], 'isEnabled', (...isEnabled)=>isEnabled.some((isCommandEnabled)=>isCommandEnabled));
|
|
3607
|
-
// Set initial value because integrations are created on first dropdown open.
|
|
3608
|
-
imageInsertUrlView.imageURLInputValue = replaceImageSourceCommand.value || '';
|
|
3609
|
-
this._imageInsertUI.dropdownView.on('change:isOpen', ()=>{
|
|
3610
|
-
if (this._imageInsertUI.dropdownView.isOpen) {
|
|
3611
|
-
// Make sure that each time the panel shows up, the URL field remains in sync with the value of
|
|
3612
|
-
// the command. If the user typed in the input, then canceled and re-opened it without changing
|
|
3613
|
-
// the value of the media command (e.g. because they didn't change the selection), they would see
|
|
3614
|
-
// the old value instead of the actual value of the command.
|
|
3615
|
-
imageInsertUrlView.imageURLInputValue = replaceImageSourceCommand.value || '';
|
|
3616
|
-
if (collapsibleView) {
|
|
3617
|
-
collapsibleView.isCollapsed = true;
|
|
3618
|
-
}
|
|
3619
|
-
}
|
|
3620
|
-
// Note: Use the low priority to make sure the following listener starts working after the
|
|
3621
|
-
// default action of the drop-down is executed (i.e. the panel showed up). Otherwise, the
|
|
3622
|
-
// invisible form/input cannot be focused/selected.
|
|
3623
|
-
}, {
|
|
3624
|
-
priority: 'low'
|
|
3625
|
-
});
|
|
3626
|
-
imageInsertUrlView.on('submit', ()=>{
|
|
3627
|
-
if (replaceImageSourceCommand.isEnabled) {
|
|
3628
|
-
editor.execute('replaceImageSource', {
|
|
3629
|
-
source: imageInsertUrlView.imageURLInputValue
|
|
3630
|
-
});
|
|
3631
|
-
} else {
|
|
3632
|
-
editor.execute('insertImage', {
|
|
3633
|
-
source: imageInsertUrlView.imageURLInputValue
|
|
3634
|
-
});
|
|
3635
|
-
}
|
|
3636
|
-
this._closePanel();
|
|
3637
|
-
});
|
|
3638
|
-
imageInsertUrlView.on('cancel', ()=>this._closePanel());
|
|
3639
|
-
if (collapsibleView) {
|
|
3640
|
-
collapsibleView.set({
|
|
3641
|
-
isCollapsed: true
|
|
3642
|
-
});
|
|
3643
|
-
collapsibleView.bind('label').to(this._imageInsertUI, 'isImageSelected', (isImageSelected)=>isImageSelected ? t('Update image URL') : t('Insert image via URL'));
|
|
3644
|
-
return collapsibleView;
|
|
3645
|
-
}
|
|
3646
3970
|
return imageInsertUrlView;
|
|
3647
3971
|
}
|
|
3648
3972
|
/**
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
const ButtonClass = isOnlyOne ? DropdownButtonView : ButtonView;
|
|
3973
|
+
* Shows the insert image via URL form view in a modal.
|
|
3974
|
+
*/ _showModal() {
|
|
3652
3975
|
const editor = this.editor;
|
|
3653
|
-
const
|
|
3654
|
-
const t =
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3976
|
+
const locale = editor.locale;
|
|
3977
|
+
const t = locale.t;
|
|
3978
|
+
const dialog = editor.plugins.get('Dialog');
|
|
3979
|
+
if (!this._formView) {
|
|
3980
|
+
this._formView = this._createInsertUrlView();
|
|
3981
|
+
this._formView.on('submit', ()=>this._handleSave());
|
|
3982
|
+
}
|
|
3983
|
+
const replaceImageSourceCommand = editor.commands.get('replaceImageSource');
|
|
3984
|
+
this._formView.imageURLInputValue = replaceImageSourceCommand.value || '';
|
|
3985
|
+
dialog.show({
|
|
3986
|
+
id: 'insertImageViaUrl',
|
|
3987
|
+
title: this._imageInsertUI.isImageSelected ? t('Update image URL') : t('Insert image via URL'),
|
|
3988
|
+
isModal: true,
|
|
3989
|
+
content: this._formView,
|
|
3990
|
+
actionButtons: [
|
|
3991
|
+
{
|
|
3992
|
+
label: t('Cancel'),
|
|
3993
|
+
withText: true,
|
|
3994
|
+
onExecute: ()=>dialog.hide()
|
|
3995
|
+
},
|
|
3996
|
+
{
|
|
3997
|
+
label: t('Accept'),
|
|
3998
|
+
class: 'ck-button-action',
|
|
3999
|
+
withText: true,
|
|
4000
|
+
onExecute: ()=>this._handleSave()
|
|
4001
|
+
}
|
|
4002
|
+
]
|
|
3658
4003
|
});
|
|
3659
|
-
button.bind('label').to(this._imageInsertUI, 'isImageSelected', (isImageSelected)=>isImageSelected ? t('Update image URL') : t('Insert image via URL'));
|
|
3660
|
-
return button;
|
|
3661
4004
|
}
|
|
3662
4005
|
/**
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
this.editor.
|
|
3666
|
-
|
|
4006
|
+
* Executes appropriate command depending on selection and form value.
|
|
4007
|
+
*/ _handleSave() {
|
|
4008
|
+
const replaceImageSourceCommand = this.editor.commands.get('replaceImageSource');
|
|
4009
|
+
// If an image element is currently selected, we want to replace its source attribute (instead of inserting a new image).
|
|
4010
|
+
// We detect if an image is selected by checking `replaceImageSource` command state.
|
|
4011
|
+
if (replaceImageSourceCommand.isEnabled) {
|
|
4012
|
+
this.editor.execute('replaceImageSource', {
|
|
4013
|
+
source: this._formView.imageURLInputValue
|
|
4014
|
+
});
|
|
4015
|
+
} else {
|
|
4016
|
+
this.editor.execute('insertImage', {
|
|
4017
|
+
source: this._formView.imageURLInputValue
|
|
4018
|
+
});
|
|
4019
|
+
}
|
|
4020
|
+
this.editor.plugins.get('Dialog').hide();
|
|
3667
4021
|
}
|
|
3668
4022
|
}
|
|
3669
4023
|
|
|
3670
|
-
|
|
4024
|
+
/**
|
|
4025
|
+
* The image insert via URL plugin.
|
|
4026
|
+
*
|
|
4027
|
+
* For a detailed overview, check the {@glink features/images/images-inserting
|
|
4028
|
+
* Insert images via source URL} documentation.
|
|
4029
|
+
*
|
|
4030
|
+
* This plugin does not do anything directly, but it loads a set of specific plugins
|
|
4031
|
+
* to enable image inserting via implemented integrations:
|
|
4032
|
+
*
|
|
4033
|
+
* * {@link module:image/imageinsert/imageinsertui~ImageInsertUI},
|
|
4034
|
+
*/ class ImageInsertViaUrl extends Plugin {
|
|
3671
4035
|
/**
|
|
3672
|
-
|
|
3673
|
-
|
|
4036
|
+
* @inheritDoc
|
|
4037
|
+
*/ static get pluginName() {
|
|
3674
4038
|
return 'ImageInsertViaUrl';
|
|
3675
4039
|
}
|
|
3676
4040
|
/**
|
|
3677
|
-
|
|
3678
|
-
|
|
4041
|
+
* @inheritDoc
|
|
4042
|
+
*/ static get requires() {
|
|
3679
4043
|
return [
|
|
3680
4044
|
ImageInsertViaUrlUI,
|
|
3681
4045
|
ImageInsertUI
|
|
@@ -3683,15 +4047,26 @@ class ImageInsertViaUrl extends Plugin {
|
|
|
3683
4047
|
}
|
|
3684
4048
|
}
|
|
3685
4049
|
|
|
3686
|
-
|
|
4050
|
+
/**
|
|
4051
|
+
* The image insert plugin.
|
|
4052
|
+
*
|
|
4053
|
+
* For a detailed overview, check the {@glink features/images/image-upload/image-upload Image upload feature}
|
|
4054
|
+
* and {@glink features/images/images-inserting Insert images via source URL} documentation.
|
|
4055
|
+
*
|
|
4056
|
+
* This plugin does not do anything directly, but it loads a set of specific plugins
|
|
4057
|
+
* to enable image uploading or inserting via implemented integrations:
|
|
4058
|
+
*
|
|
4059
|
+
* * {@link module:image/imageupload~ImageUpload}
|
|
4060
|
+
* * {@link module:image/imageinsert/imageinsertui~ImageInsertUI}
|
|
4061
|
+
*/ class ImageInsert extends Plugin {
|
|
3687
4062
|
/**
|
|
3688
|
-
|
|
3689
|
-
|
|
4063
|
+
* @inheritDoc
|
|
4064
|
+
*/ static get pluginName() {
|
|
3690
4065
|
return 'ImageInsert';
|
|
3691
4066
|
}
|
|
3692
4067
|
/**
|
|
3693
|
-
|
|
3694
|
-
|
|
4068
|
+
* @inheritDoc
|
|
4069
|
+
*/ static get requires() {
|
|
3695
4070
|
return [
|
|
3696
4071
|
ImageUpload,
|
|
3697
4072
|
ImageInsertViaUrl,
|
|
@@ -3700,10 +4075,12 @@ class ImageInsert extends Plugin {
|
|
|
3700
4075
|
}
|
|
3701
4076
|
}
|
|
3702
4077
|
|
|
3703
|
-
|
|
4078
|
+
/**
|
|
4079
|
+
* The resize image command. Currently, it only supports the width attribute.
|
|
4080
|
+
*/ class ResizeImageCommand extends Command {
|
|
3704
4081
|
/**
|
|
3705
|
-
|
|
3706
|
-
|
|
4082
|
+
* @inheritDoc
|
|
4083
|
+
*/ refresh() {
|
|
3707
4084
|
const editor = this.editor;
|
|
3708
4085
|
const imageUtils = editor.plugins.get('ImageUtils');
|
|
3709
4086
|
const element = imageUtils.getClosestSelectedImageElement(editor.model.document.selection);
|
|
@@ -3718,20 +4095,20 @@ class ResizeImageCommand extends Command {
|
|
|
3718
4095
|
}
|
|
3719
4096
|
}
|
|
3720
4097
|
/**
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
4098
|
+
* Executes the command.
|
|
4099
|
+
*
|
|
4100
|
+
* ```ts
|
|
4101
|
+
* // Sets the width to 50%:
|
|
4102
|
+
* editor.execute( 'resizeImage', { width: '50%' } );
|
|
4103
|
+
*
|
|
4104
|
+
* // Removes the width attribute:
|
|
4105
|
+
* editor.execute( 'resizeImage', { width: null } );
|
|
4106
|
+
* ```
|
|
4107
|
+
*
|
|
4108
|
+
* @param options
|
|
4109
|
+
* @param options.width The new width of the image.
|
|
4110
|
+
* @fires execute
|
|
4111
|
+
*/ execute(options) {
|
|
3735
4112
|
const editor = this.editor;
|
|
3736
4113
|
const model = editor.model;
|
|
3737
4114
|
const imageUtils = editor.plugins.get('ImageUtils');
|
|
@@ -3750,22 +4127,62 @@ class ResizeImageCommand extends Command {
|
|
|
3750
4127
|
}
|
|
3751
4128
|
}
|
|
3752
4129
|
|
|
3753
|
-
|
|
4130
|
+
/**
|
|
4131
|
+
* The image resize editing feature.
|
|
4132
|
+
*
|
|
4133
|
+
* It adds the ability to resize each image using handles or manually by
|
|
4134
|
+
* {@link module:image/imageresize/imageresizebuttons~ImageResizeButtons} buttons.
|
|
4135
|
+
*/ class ImageResizeEditing extends Plugin {
|
|
3754
4136
|
/**
|
|
3755
|
-
|
|
3756
|
-
|
|
4137
|
+
* @inheritDoc
|
|
4138
|
+
*/ static get requires() {
|
|
3757
4139
|
return [
|
|
3758
4140
|
ImageUtils
|
|
3759
4141
|
];
|
|
3760
4142
|
}
|
|
3761
4143
|
/**
|
|
3762
|
-
|
|
3763
|
-
|
|
4144
|
+
* @inheritDoc
|
|
4145
|
+
*/ static get pluginName() {
|
|
3764
4146
|
return 'ImageResizeEditing';
|
|
3765
4147
|
}
|
|
3766
4148
|
/**
|
|
3767
|
-
|
|
3768
|
-
|
|
4149
|
+
* @inheritDoc
|
|
4150
|
+
*/ constructor(editor){
|
|
4151
|
+
super(editor);
|
|
4152
|
+
editor.config.define('image', {
|
|
4153
|
+
resizeUnit: '%',
|
|
4154
|
+
resizeOptions: [
|
|
4155
|
+
{
|
|
4156
|
+
name: 'resizeImage:original',
|
|
4157
|
+
value: null,
|
|
4158
|
+
icon: 'original'
|
|
4159
|
+
},
|
|
4160
|
+
{
|
|
4161
|
+
name: 'resizeImage:custom',
|
|
4162
|
+
value: 'custom',
|
|
4163
|
+
icon: 'custom'
|
|
4164
|
+
},
|
|
4165
|
+
{
|
|
4166
|
+
name: 'resizeImage:25',
|
|
4167
|
+
value: '25',
|
|
4168
|
+
icon: 'small'
|
|
4169
|
+
},
|
|
4170
|
+
{
|
|
4171
|
+
name: 'resizeImage:50',
|
|
4172
|
+
value: '50',
|
|
4173
|
+
icon: 'medium'
|
|
4174
|
+
},
|
|
4175
|
+
{
|
|
4176
|
+
name: 'resizeImage:75',
|
|
4177
|
+
value: '75',
|
|
4178
|
+
icon: 'large'
|
|
4179
|
+
}
|
|
4180
|
+
]
|
|
4181
|
+
});
|
|
4182
|
+
}
|
|
4183
|
+
/**
|
|
4184
|
+
* @inheritDoc
|
|
4185
|
+
*/ init() {
|
|
3769
4186
|
const editor = this.editor;
|
|
3770
4187
|
const resizeImageCommand = new ResizeImageCommand(editor);
|
|
3771
4188
|
this._registerConverters('imageBlock');
|
|
@@ -3775,8 +4192,8 @@ class ImageResizeEditing extends Plugin {
|
|
|
3775
4192
|
editor.commands.add('imageResize', resizeImageCommand);
|
|
3776
4193
|
}
|
|
3777
4194
|
/**
|
|
3778
|
-
|
|
3779
|
-
|
|
4195
|
+
* @inheritDoc
|
|
4196
|
+
*/ afterInit() {
|
|
3780
4197
|
this._registerSchema();
|
|
3781
4198
|
}
|
|
3782
4199
|
_registerSchema() {
|
|
@@ -3798,10 +4215,10 @@ class ImageResizeEditing extends Plugin {
|
|
|
3798
4215
|
}
|
|
3799
4216
|
}
|
|
3800
4217
|
/**
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
4218
|
+
* Registers image resize converters.
|
|
4219
|
+
*
|
|
4220
|
+
* @param imageType The type of the image.
|
|
4221
|
+
*/ _registerConverters(imageType) {
|
|
3805
4222
|
const editor = this.editor;
|
|
3806
4223
|
const imageUtils = editor.plugins.get('ImageUtils');
|
|
3807
4224
|
// Dedicated converter to propagate image's attribute to the img tag.
|
|
@@ -3879,66 +4296,45 @@ class ImageResizeEditing extends Plugin {
|
|
|
3879
4296
|
}
|
|
3880
4297
|
});
|
|
3881
4298
|
}
|
|
3882
|
-
/**
|
|
3883
|
-
* @inheritDoc
|
|
3884
|
-
*/ constructor(editor){
|
|
3885
|
-
super(editor);
|
|
3886
|
-
editor.config.define('image', {
|
|
3887
|
-
resizeUnit: '%',
|
|
3888
|
-
resizeOptions: [
|
|
3889
|
-
{
|
|
3890
|
-
name: 'resizeImage:original',
|
|
3891
|
-
value: null,
|
|
3892
|
-
icon: 'original'
|
|
3893
|
-
},
|
|
3894
|
-
{
|
|
3895
|
-
name: 'resizeImage:custom',
|
|
3896
|
-
value: 'custom',
|
|
3897
|
-
icon: 'custom'
|
|
3898
|
-
},
|
|
3899
|
-
{
|
|
3900
|
-
name: 'resizeImage:25',
|
|
3901
|
-
value: '25',
|
|
3902
|
-
icon: 'small'
|
|
3903
|
-
},
|
|
3904
|
-
{
|
|
3905
|
-
name: 'resizeImage:50',
|
|
3906
|
-
value: '50',
|
|
3907
|
-
icon: 'medium'
|
|
3908
|
-
},
|
|
3909
|
-
{
|
|
3910
|
-
name: 'resizeImage:75',
|
|
3911
|
-
value: '75',
|
|
3912
|
-
icon: 'large'
|
|
3913
|
-
}
|
|
3914
|
-
]
|
|
3915
|
-
});
|
|
3916
|
-
}
|
|
3917
4299
|
}
|
|
3918
4300
|
|
|
3919
|
-
const RESIZE_ICONS = {
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
};
|
|
3926
|
-
|
|
4301
|
+
const RESIZE_ICONS = /* #__PURE__ */ (()=>({
|
|
4302
|
+
small: icons.objectSizeSmall,
|
|
4303
|
+
medium: icons.objectSizeMedium,
|
|
4304
|
+
large: icons.objectSizeLarge,
|
|
4305
|
+
custom: icons.objectSizeCustom,
|
|
4306
|
+
original: icons.objectSizeFull
|
|
4307
|
+
}))();
|
|
4308
|
+
/**
|
|
4309
|
+
* The image resize buttons plugin.
|
|
4310
|
+
*
|
|
4311
|
+
* It adds a possibility to resize images using the toolbar dropdown or individual buttons, depending on the plugin configuration.
|
|
4312
|
+
*/ class ImageResizeButtons extends Plugin {
|
|
3927
4313
|
/**
|
|
3928
|
-
|
|
3929
|
-
|
|
4314
|
+
* @inheritDoc
|
|
4315
|
+
*/ static get requires() {
|
|
3930
4316
|
return [
|
|
3931
4317
|
ImageResizeEditing
|
|
3932
4318
|
];
|
|
3933
4319
|
}
|
|
3934
4320
|
/**
|
|
3935
|
-
|
|
3936
|
-
|
|
4321
|
+
* @inheritDoc
|
|
4322
|
+
*/ static get pluginName() {
|
|
3937
4323
|
return 'ImageResizeButtons';
|
|
3938
4324
|
}
|
|
3939
4325
|
/**
|
|
3940
|
-
|
|
3941
|
-
|
|
4326
|
+
* The resize unit.
|
|
4327
|
+
* @default '%'
|
|
4328
|
+
*/ _resizeUnit;
|
|
4329
|
+
/**
|
|
4330
|
+
* @inheritDoc
|
|
4331
|
+
*/ constructor(editor){
|
|
4332
|
+
super(editor);
|
|
4333
|
+
this._resizeUnit = editor.config.get('image.resizeUnit');
|
|
4334
|
+
}
|
|
4335
|
+
/**
|
|
4336
|
+
* @inheritDoc
|
|
4337
|
+
*/ init() {
|
|
3942
4338
|
const editor = this.editor;
|
|
3943
4339
|
const options = editor.config.get('image.resizeOptions');
|
|
3944
4340
|
const command = editor.commands.get('resizeImage');
|
|
@@ -3949,10 +4345,10 @@ class ImageResizeButtons extends Plugin {
|
|
|
3949
4345
|
this._registerImageResizeDropdown(options);
|
|
3950
4346
|
}
|
|
3951
4347
|
/**
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
4348
|
+
* A helper function that creates a standalone button component for the plugin.
|
|
4349
|
+
*
|
|
4350
|
+
* @param resizeOption A model of the resize option.
|
|
4351
|
+
*/ _registerImageResizeButton(option) {
|
|
3956
4352
|
const editor = this.editor;
|
|
3957
4353
|
const { name, value, icon } = option;
|
|
3958
4354
|
editor.ui.componentFactory.add(name, (locale)=>{
|
|
@@ -3961,15 +4357,15 @@ class ImageResizeButtons extends Plugin {
|
|
|
3961
4357
|
const labelText = this._getOptionLabelValue(option, true);
|
|
3962
4358
|
if (!RESIZE_ICONS[icon]) {
|
|
3963
4359
|
/**
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
4360
|
+
* When configuring {@link module:image/imageconfig~ImageConfig#resizeOptions `config.image.resizeOptions`} for standalone
|
|
4361
|
+
* buttons, a valid `icon` token must be set for each option.
|
|
4362
|
+
*
|
|
4363
|
+
* See all valid options described in the
|
|
4364
|
+
* {@link module:image/imageconfig~ImageResizeOption plugin configuration}.
|
|
4365
|
+
*
|
|
4366
|
+
* @error imageresizebuttons-missing-icon
|
|
4367
|
+
* @param option Invalid image resize option.
|
|
4368
|
+
*/ throw new CKEditorError('imageresizebuttons-missing-icon', editor, option);
|
|
3973
4369
|
}
|
|
3974
4370
|
button.set({
|
|
3975
4371
|
// Use the `label` property for a verbose description (because of ARIA).
|
|
@@ -3998,11 +4394,11 @@ class ImageResizeButtons extends Plugin {
|
|
|
3998
4394
|
});
|
|
3999
4395
|
}
|
|
4000
4396
|
/**
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4397
|
+
* A helper function that creates a dropdown component for the plugin containing all the resize options defined in
|
|
4398
|
+
* the editor configuration.
|
|
4399
|
+
*
|
|
4400
|
+
* @param options An array of configured options.
|
|
4401
|
+
*/ _registerImageResizeDropdown(options) {
|
|
4006
4402
|
const editor = this.editor;
|
|
4007
4403
|
const t = editor.t;
|
|
4008
4404
|
const originalSizeOption = options.find((option)=>!option.value);
|
|
@@ -4052,13 +4448,13 @@ class ImageResizeButtons extends Plugin {
|
|
|
4052
4448
|
editor.ui.componentFactory.add('imageResize', componentCreator);
|
|
4053
4449
|
}
|
|
4054
4450
|
/**
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4451
|
+
* A helper function for creating an option label value string.
|
|
4452
|
+
*
|
|
4453
|
+
* @param option A resize option object.
|
|
4454
|
+
* @param forTooltip An optional flag for creating a tooltip label.
|
|
4455
|
+
* @returns A user-defined label combined from the numeric value and the resize unit or the default label
|
|
4456
|
+
* for reset options (`Original`).
|
|
4457
|
+
*/ _getOptionLabelValue(option, forTooltip = false) {
|
|
4062
4458
|
const t = this.editor.t;
|
|
4063
4459
|
if (option.label) {
|
|
4064
4460
|
return option.label;
|
|
@@ -4081,12 +4477,12 @@ class ImageResizeButtons extends Plugin {
|
|
|
4081
4477
|
}
|
|
4082
4478
|
}
|
|
4083
4479
|
/**
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4480
|
+
* A helper function that parses the resize options and returns list item definitions ready for use in the dropdown.
|
|
4481
|
+
*
|
|
4482
|
+
* @param options The resize options.
|
|
4483
|
+
* @param command The resize image command.
|
|
4484
|
+
* @returns Dropdown item definitions.
|
|
4485
|
+
*/ _getResizeDropdownListItemDefinitions(options, command) {
|
|
4090
4486
|
const { editor } = this;
|
|
4091
4487
|
const itemDefinitions = new Collection();
|
|
4092
4488
|
const optionsWithSerializedValues = options.map((option)=>{
|
|
@@ -4144,12 +4540,6 @@ class ImageResizeButtons extends Plugin {
|
|
|
4144
4540
|
}
|
|
4145
4541
|
return itemDefinitions;
|
|
4146
4542
|
}
|
|
4147
|
-
/**
|
|
4148
|
-
* @inheritDoc
|
|
4149
|
-
*/ constructor(editor){
|
|
4150
|
-
super(editor);
|
|
4151
|
-
this._resizeUnit = editor.config.get('image.resizeUnit');
|
|
4152
|
-
}
|
|
4153
4543
|
}
|
|
4154
4544
|
/**
|
|
4155
4545
|
* A helper that checks if provided option triggers custom resize balloon.
|
|
@@ -4175,30 +4565,35 @@ class ImageResizeButtons extends Plugin {
|
|
|
4175
4565
|
|
|
4176
4566
|
const RESIZABLE_IMAGES_CSS_SELECTOR = 'figure.image.ck-widget > img,' + 'figure.image.ck-widget > picture > img,' + 'figure.image.ck-widget > a > img,' + 'figure.image.ck-widget > a > picture > img,' + 'span.image-inline.ck-widget > img,' + 'span.image-inline.ck-widget > picture > img';
|
|
4177
4567
|
const RESIZED_IMAGE_CLASS = 'image_resized';
|
|
4178
|
-
|
|
4568
|
+
/**
|
|
4569
|
+
* The image resize by handles feature.
|
|
4570
|
+
*
|
|
4571
|
+
* It adds the ability to resize each image using handles or manually by
|
|
4572
|
+
* {@link module:image/imageresize/imageresizebuttons~ImageResizeButtons} buttons.
|
|
4573
|
+
*/ class ImageResizeHandles extends Plugin {
|
|
4179
4574
|
/**
|
|
4180
|
-
|
|
4181
|
-
|
|
4575
|
+
* @inheritDoc
|
|
4576
|
+
*/ static get requires() {
|
|
4182
4577
|
return [
|
|
4183
4578
|
WidgetResize,
|
|
4184
4579
|
ImageUtils
|
|
4185
4580
|
];
|
|
4186
4581
|
}
|
|
4187
4582
|
/**
|
|
4188
|
-
|
|
4189
|
-
|
|
4583
|
+
* @inheritDoc
|
|
4584
|
+
*/ static get pluginName() {
|
|
4190
4585
|
return 'ImageResizeHandles';
|
|
4191
4586
|
}
|
|
4192
4587
|
/**
|
|
4193
|
-
|
|
4194
|
-
|
|
4588
|
+
* @inheritDoc
|
|
4589
|
+
*/ init() {
|
|
4195
4590
|
const command = this.editor.commands.get('resizeImage');
|
|
4196
4591
|
this.bind('isEnabled').to(command);
|
|
4197
4592
|
this._setupResizerCreator();
|
|
4198
4593
|
}
|
|
4199
4594
|
/**
|
|
4200
|
-
|
|
4201
|
-
|
|
4595
|
+
* Attaches the listeners responsible for creating a resizer for each image, except for images inside the HTML embed preview.
|
|
4596
|
+
*/ _setupResizerCreator() {
|
|
4202
4597
|
const editor = this.editor;
|
|
4203
4598
|
const editingView = editor.editing.view;
|
|
4204
4599
|
const imageUtils = editor.plugins.get('ImageUtils');
|
|
@@ -4323,6 +4718,8 @@ class ImageResizeHandles extends Plugin {
|
|
|
4323
4718
|
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
4324
4719
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4325
4720
|
*/ /**
|
|
4721
|
+
* @module image/imageresize/utils/getselectedimageeditornodes
|
|
4722
|
+
*/ /**
|
|
4326
4723
|
* Finds model, view and DOM element for selected image element. Returns `null` if there is no image selected.
|
|
4327
4724
|
*
|
|
4328
4725
|
* @param editor Editor instance.
|
|
@@ -4372,10 +4769,82 @@ class ImageResizeHandles extends Plugin {
|
|
|
4372
4769
|
return tryCastDimensionsToUnit(imageParentWidthPx, imageHolderDimension, targetUnit);
|
|
4373
4770
|
}
|
|
4374
4771
|
|
|
4375
|
-
|
|
4772
|
+
/**
|
|
4773
|
+
* The ImageCustomResizeFormView class.
|
|
4774
|
+
*/ class ImageCustomResizeFormView extends View {
|
|
4775
|
+
/**
|
|
4776
|
+
* Tracks information about the DOM focus in the form.
|
|
4777
|
+
*/ focusTracker;
|
|
4778
|
+
/**
|
|
4779
|
+
* An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
|
|
4780
|
+
*/ keystrokes;
|
|
4781
|
+
/**
|
|
4782
|
+
* Resize unit shortcut.
|
|
4783
|
+
*/ unit;
|
|
4784
|
+
/**
|
|
4785
|
+
* An input with a label.
|
|
4786
|
+
*/ labeledInput;
|
|
4787
|
+
/**
|
|
4788
|
+
* A button used to submit the form.
|
|
4789
|
+
*/ saveButtonView;
|
|
4790
|
+
/**
|
|
4791
|
+
* A button used to cancel the form.
|
|
4792
|
+
*/ cancelButtonView;
|
|
4793
|
+
/**
|
|
4794
|
+
* A collection of views which can be focused in the form.
|
|
4795
|
+
*/ _focusables;
|
|
4796
|
+
/**
|
|
4797
|
+
* Helps cycling over {@link #_focusables} in the form.
|
|
4798
|
+
*/ _focusCycler;
|
|
4799
|
+
/**
|
|
4800
|
+
* An array of form validators used by {@link #isValid}.
|
|
4801
|
+
*/ _validators;
|
|
4802
|
+
/**
|
|
4803
|
+
* @inheritDoc
|
|
4804
|
+
*/ constructor(locale, unit, validators){
|
|
4805
|
+
super(locale);
|
|
4806
|
+
const t = this.locale.t;
|
|
4807
|
+
this.focusTracker = new FocusTracker();
|
|
4808
|
+
this.keystrokes = new KeystrokeHandler();
|
|
4809
|
+
this.unit = unit;
|
|
4810
|
+
this.labeledInput = this._createLabeledInputView();
|
|
4811
|
+
this.saveButtonView = this._createButton(t('Save'), icons.check, 'ck-button-save');
|
|
4812
|
+
this.saveButtonView.type = 'submit';
|
|
4813
|
+
this.cancelButtonView = this._createButton(t('Cancel'), icons.cancel, 'ck-button-cancel', 'cancel');
|
|
4814
|
+
this._focusables = new ViewCollection();
|
|
4815
|
+
this._validators = validators;
|
|
4816
|
+
this._focusCycler = new FocusCycler({
|
|
4817
|
+
focusables: this._focusables,
|
|
4818
|
+
focusTracker: this.focusTracker,
|
|
4819
|
+
keystrokeHandler: this.keystrokes,
|
|
4820
|
+
actions: {
|
|
4821
|
+
// Navigate form fields backwards using the Shift + Tab keystroke.
|
|
4822
|
+
focusPrevious: 'shift + tab',
|
|
4823
|
+
// Navigate form fields forwards using the Tab key.
|
|
4824
|
+
focusNext: 'tab'
|
|
4825
|
+
}
|
|
4826
|
+
});
|
|
4827
|
+
this.setTemplate({
|
|
4828
|
+
tag: 'form',
|
|
4829
|
+
attributes: {
|
|
4830
|
+
class: [
|
|
4831
|
+
'ck',
|
|
4832
|
+
'ck-image-custom-resize-form',
|
|
4833
|
+
'ck-responsive-form'
|
|
4834
|
+
],
|
|
4835
|
+
// https://github.com/ckeditor/ckeditor5-image/issues/40
|
|
4836
|
+
tabindex: '-1'
|
|
4837
|
+
},
|
|
4838
|
+
children: [
|
|
4839
|
+
this.labeledInput,
|
|
4840
|
+
this.saveButtonView,
|
|
4841
|
+
this.cancelButtonView
|
|
4842
|
+
]
|
|
4843
|
+
});
|
|
4844
|
+
}
|
|
4376
4845
|
/**
|
|
4377
|
-
|
|
4378
|
-
|
|
4846
|
+
* @inheritDoc
|
|
4847
|
+
*/ render() {
|
|
4379
4848
|
super.render();
|
|
4380
4849
|
this.keystrokes.listenTo(this.element);
|
|
4381
4850
|
submitHandler({
|
|
@@ -4393,21 +4862,21 @@ class ImageCustomResizeFormView extends View {
|
|
|
4393
4862
|
});
|
|
4394
4863
|
}
|
|
4395
4864
|
/**
|
|
4396
|
-
|
|
4397
|
-
|
|
4865
|
+
* @inheritDoc
|
|
4866
|
+
*/ destroy() {
|
|
4398
4867
|
super.destroy();
|
|
4399
4868
|
this.focusTracker.destroy();
|
|
4400
4869
|
this.keystrokes.destroy();
|
|
4401
4870
|
}
|
|
4402
4871
|
/**
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4872
|
+
* Creates the button view.
|
|
4873
|
+
*
|
|
4874
|
+
* @param label The button label
|
|
4875
|
+
* @param icon The button's icon.
|
|
4876
|
+
* @param className The additional button CSS class name.
|
|
4877
|
+
* @param eventName The event name that the ButtonView#execute event will be delegated to.
|
|
4878
|
+
* @returns The button view instance.
|
|
4879
|
+
*/ _createButton(label, icon, className, eventName) {
|
|
4411
4880
|
const button = new ButtonView(this.locale);
|
|
4412
4881
|
button.set({
|
|
4413
4882
|
label,
|
|
@@ -4425,10 +4894,10 @@ class ImageCustomResizeFormView extends View {
|
|
|
4425
4894
|
return button;
|
|
4426
4895
|
}
|
|
4427
4896
|
/**
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4897
|
+
* Creates an input with a label.
|
|
4898
|
+
*
|
|
4899
|
+
* @returns Labeled field view instance.
|
|
4900
|
+
*/ _createLabeledInputView() {
|
|
4432
4901
|
const t = this.locale.t;
|
|
4433
4902
|
const labeledInput = new LabeledFieldView(this.locale, createLabeledInputNumber);
|
|
4434
4903
|
labeledInput.label = t('Resize image (in %0)', this.unit);
|
|
@@ -4438,8 +4907,8 @@ class ImageCustomResizeFormView extends View {
|
|
|
4438
4907
|
return labeledInput;
|
|
4439
4908
|
}
|
|
4440
4909
|
/**
|
|
4441
|
-
|
|
4442
|
-
|
|
4910
|
+
* Validates the form and returns `false` when some fields are invalid.
|
|
4911
|
+
*/ isValid() {
|
|
4443
4912
|
this.resetFormStatus();
|
|
4444
4913
|
for (const validator of this._validators){
|
|
4445
4914
|
const errorText = validator(this);
|
|
@@ -4453,16 +4922,16 @@ class ImageCustomResizeFormView extends View {
|
|
|
4453
4922
|
return true;
|
|
4454
4923
|
}
|
|
4455
4924
|
/**
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4925
|
+
* Cleans up the supplementary error and information text of the {@link #labeledInput}
|
|
4926
|
+
* bringing them back to the state when the form has been displayed for the first time.
|
|
4927
|
+
*
|
|
4928
|
+
* See {@link #isValid}.
|
|
4929
|
+
*/ resetFormStatus() {
|
|
4461
4930
|
this.labeledInput.errorText = null;
|
|
4462
4931
|
}
|
|
4463
4932
|
/**
|
|
4464
|
-
|
|
4465
|
-
|
|
4933
|
+
* The native DOM `value` of the input element of {@link #labeledInput}.
|
|
4934
|
+
*/ get rawSize() {
|
|
4466
4935
|
const { element } = this.labeledInput.fieldView;
|
|
4467
4936
|
if (!element) {
|
|
4468
4937
|
return null;
|
|
@@ -4470,8 +4939,8 @@ class ImageCustomResizeFormView extends View {
|
|
|
4470
4939
|
return element.value;
|
|
4471
4940
|
}
|
|
4472
4941
|
/**
|
|
4473
|
-
|
|
4474
|
-
|
|
4942
|
+
* Get numeric value of size. Returns `null` if value of size input element in {@link #labeledInput}.is not a number.
|
|
4943
|
+
*/ get parsedSize() {
|
|
4475
4944
|
const { rawSize } = this;
|
|
4476
4945
|
if (rawSize === null) {
|
|
4477
4946
|
return null;
|
|
@@ -4483,58 +4952,15 @@ class ImageCustomResizeFormView extends View {
|
|
|
4483
4952
|
return parsed;
|
|
4484
4953
|
}
|
|
4485
4954
|
/**
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4955
|
+
* Returns serialized image input size with unit.
|
|
4956
|
+
* Returns `null` if value of size input element in {@link #labeledInput}.is not a number.
|
|
4957
|
+
*/ get sizeWithUnits() {
|
|
4489
4958
|
const { parsedSize, unit } = this;
|
|
4490
4959
|
if (parsedSize === null) {
|
|
4491
4960
|
return null;
|
|
4492
4961
|
}
|
|
4493
4962
|
return `${parsedSize}${unit}`;
|
|
4494
4963
|
}
|
|
4495
|
-
/**
|
|
4496
|
-
* @inheritDoc
|
|
4497
|
-
*/ constructor(locale, unit, validators){
|
|
4498
|
-
super(locale);
|
|
4499
|
-
const t = this.locale.t;
|
|
4500
|
-
this.focusTracker = new FocusTracker();
|
|
4501
|
-
this.keystrokes = new KeystrokeHandler();
|
|
4502
|
-
this.unit = unit;
|
|
4503
|
-
this.labeledInput = this._createLabeledInputView();
|
|
4504
|
-
this.saveButtonView = this._createButton(t('Save'), icons.check, 'ck-button-save');
|
|
4505
|
-
this.saveButtonView.type = 'submit';
|
|
4506
|
-
this.cancelButtonView = this._createButton(t('Cancel'), icons.cancel, 'ck-button-cancel', 'cancel');
|
|
4507
|
-
this._focusables = new ViewCollection();
|
|
4508
|
-
this._validators = validators;
|
|
4509
|
-
this._focusCycler = new FocusCycler({
|
|
4510
|
-
focusables: this._focusables,
|
|
4511
|
-
focusTracker: this.focusTracker,
|
|
4512
|
-
keystrokeHandler: this.keystrokes,
|
|
4513
|
-
actions: {
|
|
4514
|
-
// Navigate form fields backwards using the Shift + Tab keystroke.
|
|
4515
|
-
focusPrevious: 'shift + tab',
|
|
4516
|
-
// Navigate form fields forwards using the Tab key.
|
|
4517
|
-
focusNext: 'tab'
|
|
4518
|
-
}
|
|
4519
|
-
});
|
|
4520
|
-
this.setTemplate({
|
|
4521
|
-
tag: 'form',
|
|
4522
|
-
attributes: {
|
|
4523
|
-
class: [
|
|
4524
|
-
'ck',
|
|
4525
|
-
'ck-image-custom-resize-form',
|
|
4526
|
-
'ck-responsive-form'
|
|
4527
|
-
],
|
|
4528
|
-
// https://github.com/ckeditor/ckeditor5-image/issues/40
|
|
4529
|
-
tabindex: '-1'
|
|
4530
|
-
},
|
|
4531
|
-
children: [
|
|
4532
|
-
this.labeledInput,
|
|
4533
|
-
this.saveButtonView,
|
|
4534
|
-
this.cancelButtonView
|
|
4535
|
-
]
|
|
4536
|
-
});
|
|
4537
|
-
}
|
|
4538
4964
|
}
|
|
4539
4965
|
|
|
4540
4966
|
/**
|
|
@@ -4562,22 +4988,32 @@ class ImageCustomResizeFormView extends View {
|
|
|
4562
4988
|
};
|
|
4563
4989
|
}
|
|
4564
4990
|
|
|
4565
|
-
|
|
4991
|
+
/**
|
|
4992
|
+
* The custom resize image UI plugin.
|
|
4993
|
+
*
|
|
4994
|
+
* The plugin uses the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon}.
|
|
4995
|
+
*/ class ImageCustomResizeUI extends Plugin {
|
|
4996
|
+
/**
|
|
4997
|
+
* The contextual balloon plugin instance.
|
|
4998
|
+
*/ _balloon;
|
|
4566
4999
|
/**
|
|
4567
|
-
|
|
4568
|
-
|
|
5000
|
+
* A form containing a textarea and buttons, used to change the `alt` text value.
|
|
5001
|
+
*/ _form;
|
|
5002
|
+
/**
|
|
5003
|
+
* @inheritDoc
|
|
5004
|
+
*/ static get requires() {
|
|
4569
5005
|
return [
|
|
4570
5006
|
ContextualBalloon
|
|
4571
5007
|
];
|
|
4572
5008
|
}
|
|
4573
5009
|
/**
|
|
4574
|
-
|
|
4575
|
-
|
|
5010
|
+
* @inheritDoc
|
|
5011
|
+
*/ static get pluginName() {
|
|
4576
5012
|
return 'ImageCustomResizeUI';
|
|
4577
5013
|
}
|
|
4578
5014
|
/**
|
|
4579
|
-
|
|
4580
|
-
|
|
5015
|
+
* @inheritDoc
|
|
5016
|
+
*/ destroy() {
|
|
4581
5017
|
super.destroy();
|
|
4582
5018
|
// Destroy created UI components as they are not automatically destroyed (see ckeditor5#1341).
|
|
4583
5019
|
if (this._form) {
|
|
@@ -4585,9 +5021,9 @@ class ImageCustomResizeUI extends Plugin {
|
|
|
4585
5021
|
}
|
|
4586
5022
|
}
|
|
4587
5023
|
/**
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
5024
|
+
* Creates the {@link module:image/imageresize/ui/imagecustomresizeformview~ImageCustomResizeFormView}
|
|
5025
|
+
* form.
|
|
5026
|
+
*/ _createForm(unit) {
|
|
4591
5027
|
const editor = this.editor;
|
|
4592
5028
|
this._balloon = this.editor.plugins.get('ContextualBalloon');
|
|
4593
5029
|
this._form = new (CssTransitionDisablerMixin(ImageCustomResizeFormView))(editor.locale, unit, getFormValidators(editor));
|
|
@@ -4624,10 +5060,10 @@ class ImageCustomResizeUI extends Plugin {
|
|
|
4624
5060
|
});
|
|
4625
5061
|
}
|
|
4626
5062
|
/**
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
5063
|
+
* Shows the {@link #_form} in the {@link #_balloon}.
|
|
5064
|
+
*
|
|
5065
|
+
* @internal
|
|
5066
|
+
*/ _showForm(unit) {
|
|
4631
5067
|
if (this._isVisible) {
|
|
4632
5068
|
return;
|
|
4633
5069
|
}
|
|
@@ -4662,10 +5098,10 @@ class ImageCustomResizeUI extends Plugin {
|
|
|
4662
5098
|
this._form.enableCssTransitions();
|
|
4663
5099
|
}
|
|
4664
5100
|
/**
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
5101
|
+
* Removes the {@link #_form} from the {@link #_balloon}.
|
|
5102
|
+
*
|
|
5103
|
+
* @param focusEditable Controls whether the editing view is focused afterwards.
|
|
5104
|
+
*/ _hideForm(focusEditable = false) {
|
|
4669
5105
|
if (!this._isInBalloon) {
|
|
4670
5106
|
return;
|
|
4671
5107
|
}
|
|
@@ -4680,13 +5116,13 @@ class ImageCustomResizeUI extends Plugin {
|
|
|
4680
5116
|
}
|
|
4681
5117
|
}
|
|
4682
5118
|
/**
|
|
4683
|
-
|
|
4684
|
-
|
|
5119
|
+
* Returns `true` when the {@link #_form} is the visible view in the {@link #_balloon}.
|
|
5120
|
+
*/ get _isVisible() {
|
|
4685
5121
|
return !!this._balloon && this._balloon.visibleView === this._form;
|
|
4686
5122
|
}
|
|
4687
5123
|
/**
|
|
4688
|
-
|
|
4689
|
-
|
|
5124
|
+
* Returns `true` when the {@link #_form} is in the {@link #_balloon}.
|
|
5125
|
+
*/ get _isInBalloon() {
|
|
4690
5126
|
return !!this._balloon && this._balloon.hasView(this._form);
|
|
4691
5127
|
}
|
|
4692
5128
|
}
|
|
@@ -4708,10 +5144,14 @@ class ImageCustomResizeUI extends Plugin {
|
|
|
4708
5144
|
];
|
|
4709
5145
|
}
|
|
4710
5146
|
|
|
4711
|
-
|
|
5147
|
+
/**
|
|
5148
|
+
* The image resize plugin.
|
|
5149
|
+
*
|
|
5150
|
+
* It adds a possibility to resize each image using handles.
|
|
5151
|
+
*/ class ImageResize extends Plugin {
|
|
4712
5152
|
/**
|
|
4713
|
-
|
|
4714
|
-
|
|
5153
|
+
* @inheritDoc
|
|
5154
|
+
*/ static get requires() {
|
|
4715
5155
|
return [
|
|
4716
5156
|
ImageResizeEditing,
|
|
4717
5157
|
ImageResizeHandles,
|
|
@@ -4720,16 +5160,54 @@ class ImageResize extends Plugin {
|
|
|
4720
5160
|
];
|
|
4721
5161
|
}
|
|
4722
5162
|
/**
|
|
4723
|
-
|
|
4724
|
-
|
|
5163
|
+
* @inheritDoc
|
|
5164
|
+
*/ static get pluginName() {
|
|
4725
5165
|
return 'ImageResize';
|
|
4726
5166
|
}
|
|
4727
5167
|
}
|
|
4728
5168
|
|
|
4729
|
-
|
|
5169
|
+
/**
|
|
5170
|
+
* The image style command. It is used to apply {@link module:image/imageconfig~ImageStyleConfig#options image style option}
|
|
5171
|
+
* to a selected image.
|
|
5172
|
+
*
|
|
5173
|
+
* **Note**: Executing this command may change the image model element if the desired style requires an image of a different
|
|
5174
|
+
* type. See {@link module:image/imagestyle/imagestylecommand~ImageStyleCommand#execute} to learn more.
|
|
5175
|
+
*/ class ImageStyleCommand extends Command {
|
|
5176
|
+
/**
|
|
5177
|
+
* An object containing names of default style options for the inline and block images.
|
|
5178
|
+
* If there is no default style option for the given image type in the configuration,
|
|
5179
|
+
* the name will be `false`.
|
|
5180
|
+
*/ _defaultStyles;
|
|
5181
|
+
/**
|
|
5182
|
+
* The styles handled by this command.
|
|
5183
|
+
*/ _styles;
|
|
5184
|
+
/**
|
|
5185
|
+
* Creates an instance of the image style command. When executed, the command applies one of
|
|
5186
|
+
* {@link module:image/imageconfig~ImageStyleConfig#options style options} to the currently selected image.
|
|
5187
|
+
*
|
|
5188
|
+
* @param editor The editor instance.
|
|
5189
|
+
* @param styles The style options that this command supports.
|
|
5190
|
+
*/ constructor(editor, styles){
|
|
5191
|
+
super(editor);
|
|
5192
|
+
this._defaultStyles = {
|
|
5193
|
+
imageBlock: false,
|
|
5194
|
+
imageInline: false
|
|
5195
|
+
};
|
|
5196
|
+
this._styles = new Map(styles.map((style)=>{
|
|
5197
|
+
if (style.isDefault) {
|
|
5198
|
+
for (const modelElementName of style.modelElements){
|
|
5199
|
+
this._defaultStyles[modelElementName] = style.name;
|
|
5200
|
+
}
|
|
5201
|
+
}
|
|
5202
|
+
return [
|
|
5203
|
+
style.name,
|
|
5204
|
+
style
|
|
5205
|
+
];
|
|
5206
|
+
}));
|
|
5207
|
+
}
|
|
4730
5208
|
/**
|
|
4731
|
-
|
|
4732
|
-
|
|
5209
|
+
* @inheritDoc
|
|
5210
|
+
*/ refresh() {
|
|
4733
5211
|
const editor = this.editor;
|
|
4734
5212
|
const imageUtils = editor.plugins.get('ImageUtils');
|
|
4735
5213
|
const element = imageUtils.getClosestSelectedImageElement(this.editor.model.document.selection);
|
|
@@ -4743,21 +5221,21 @@ class ImageStyleCommand extends Command {
|
|
|
4743
5221
|
}
|
|
4744
5222
|
}
|
|
4745
5223
|
/**
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
5224
|
+
* Executes the command and applies the style to the currently selected image:
|
|
5225
|
+
*
|
|
5226
|
+
* ```ts
|
|
5227
|
+
* editor.execute( 'imageStyle', { value: 'side' } );
|
|
5228
|
+
* ```
|
|
5229
|
+
*
|
|
5230
|
+
* **Note**: Executing this command may change the image model element if the desired style requires an image
|
|
5231
|
+
* of a different type. Learn more about {@link module:image/imageconfig~ImageStyleOptionDefinition#modelElements model element}
|
|
5232
|
+
* configuration for the style option.
|
|
5233
|
+
*
|
|
5234
|
+
* @param options.value The name of the style (as configured in {@link module:image/imageconfig~ImageStyleConfig#options}).
|
|
5235
|
+
* @param options.setImageSizes Specifies whether the image `width` and `height` attributes should be set automatically.
|
|
5236
|
+
* The default is `true`.
|
|
5237
|
+
* @fires execute
|
|
5238
|
+
*/ execute(options = {}) {
|
|
4761
5239
|
const editor = this.editor;
|
|
4762
5240
|
const model = editor.model;
|
|
4763
5241
|
const imageUtils = editor.plugins.get('ImageUtils');
|
|
@@ -4786,41 +5264,16 @@ class ImageStyleCommand extends Command {
|
|
|
4786
5264
|
});
|
|
4787
5265
|
}
|
|
4788
5266
|
/**
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
5267
|
+
* Returns `true` if requested style change would trigger the image type change.
|
|
5268
|
+
*
|
|
5269
|
+
* @param requestedStyle The name of the style (as configured in {@link module:image/imageconfig~ImageStyleConfig#options}).
|
|
5270
|
+
* @param imageElement The image model element.
|
|
5271
|
+
*/ shouldConvertImageType(requestedStyle, imageElement) {
|
|
4794
5272
|
const supportedTypes = this._styles.get(requestedStyle).modelElements;
|
|
4795
5273
|
return !supportedTypes.includes(imageElement.name);
|
|
4796
5274
|
}
|
|
4797
|
-
/**
|
|
4798
|
-
* Creates an instance of the image style command. When executed, the command applies one of
|
|
4799
|
-
* {@link module:image/imageconfig~ImageStyleConfig#options style options} to the currently selected image.
|
|
4800
|
-
*
|
|
4801
|
-
* @param editor The editor instance.
|
|
4802
|
-
* @param styles The style options that this command supports.
|
|
4803
|
-
*/ constructor(editor, styles){
|
|
4804
|
-
super(editor);
|
|
4805
|
-
this._defaultStyles = {
|
|
4806
|
-
imageBlock: false,
|
|
4807
|
-
imageInline: false
|
|
4808
|
-
};
|
|
4809
|
-
this._styles = new Map(styles.map((style)=>{
|
|
4810
|
-
if (style.isDefault) {
|
|
4811
|
-
for (const modelElementName of style.modelElements){
|
|
4812
|
-
this._defaultStyles[modelElementName] = style.name;
|
|
4813
|
-
}
|
|
4814
|
-
}
|
|
4815
|
-
return [
|
|
4816
|
-
style.name,
|
|
4817
|
-
style
|
|
4818
|
-
];
|
|
4819
|
-
}));
|
|
4820
|
-
}
|
|
4821
5275
|
}
|
|
4822
5276
|
|
|
4823
|
-
const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, objectBlockLeft, objectBlockRight } = icons;
|
|
4824
5277
|
/**
|
|
4825
5278
|
* Default image style options provided by the plugin that can be referred in the {@link module:image/imageconfig~ImageConfig#styles}
|
|
4826
5279
|
* configuration.
|
|
@@ -4844,7 +5297,7 @@ const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, ob
|
|
|
4844
5297
|
return {
|
|
4845
5298
|
name: 'inline',
|
|
4846
5299
|
title: 'In line',
|
|
4847
|
-
icon: objectInline,
|
|
5300
|
+
icon: icons.objectInline,
|
|
4848
5301
|
modelElements: [
|
|
4849
5302
|
'imageInline'
|
|
4850
5303
|
],
|
|
@@ -4856,7 +5309,7 @@ const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, ob
|
|
|
4856
5309
|
return {
|
|
4857
5310
|
name: 'alignLeft',
|
|
4858
5311
|
title: 'Left aligned image',
|
|
4859
|
-
icon: objectLeft,
|
|
5312
|
+
icon: icons.objectLeft,
|
|
4860
5313
|
modelElements: [
|
|
4861
5314
|
'imageBlock',
|
|
4862
5315
|
'imageInline'
|
|
@@ -4869,7 +5322,7 @@ const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, ob
|
|
|
4869
5322
|
return {
|
|
4870
5323
|
name: 'alignBlockLeft',
|
|
4871
5324
|
title: 'Left aligned image',
|
|
4872
|
-
icon: objectBlockLeft,
|
|
5325
|
+
icon: icons.objectBlockLeft,
|
|
4873
5326
|
modelElements: [
|
|
4874
5327
|
'imageBlock'
|
|
4875
5328
|
],
|
|
@@ -4881,7 +5334,7 @@ const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, ob
|
|
|
4881
5334
|
return {
|
|
4882
5335
|
name: 'alignCenter',
|
|
4883
5336
|
title: 'Centered image',
|
|
4884
|
-
icon: objectCenter,
|
|
5337
|
+
icon: icons.objectCenter,
|
|
4885
5338
|
modelElements: [
|
|
4886
5339
|
'imageBlock'
|
|
4887
5340
|
],
|
|
@@ -4893,7 +5346,7 @@ const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, ob
|
|
|
4893
5346
|
return {
|
|
4894
5347
|
name: 'alignRight',
|
|
4895
5348
|
title: 'Right aligned image',
|
|
4896
|
-
icon: objectRight,
|
|
5349
|
+
icon: icons.objectRight,
|
|
4897
5350
|
modelElements: [
|
|
4898
5351
|
'imageBlock',
|
|
4899
5352
|
'imageInline'
|
|
@@ -4906,7 +5359,7 @@ const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, ob
|
|
|
4906
5359
|
return {
|
|
4907
5360
|
name: 'alignBlockRight',
|
|
4908
5361
|
title: 'Right aligned image',
|
|
4909
|
-
icon: objectBlockRight,
|
|
5362
|
+
icon: icons.objectBlockRight,
|
|
4910
5363
|
modelElements: [
|
|
4911
5364
|
'imageBlock'
|
|
4912
5365
|
],
|
|
@@ -4918,7 +5371,7 @@ const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, ob
|
|
|
4918
5371
|
return {
|
|
4919
5372
|
name: 'block',
|
|
4920
5373
|
title: 'Centered image',
|
|
4921
|
-
icon: objectCenter,
|
|
5374
|
+
icon: icons.objectCenter,
|
|
4922
5375
|
modelElements: [
|
|
4923
5376
|
'imageBlock'
|
|
4924
5377
|
],
|
|
@@ -4930,7 +5383,7 @@ const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, ob
|
|
|
4930
5383
|
return {
|
|
4931
5384
|
name: 'side',
|
|
4932
5385
|
title: 'Side image',
|
|
4933
|
-
icon: objectRight,
|
|
5386
|
+
icon: icons.objectRight,
|
|
4934
5387
|
modelElements: [
|
|
4935
5388
|
'imageBlock'
|
|
4936
5389
|
],
|
|
@@ -4945,15 +5398,15 @@ const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, ob
|
|
|
4945
5398
|
* See {@link module:image/imageconfig~ImageStyleOptionDefinition#icon} to learn more.
|
|
4946
5399
|
*
|
|
4947
5400
|
* There are 7 default icons available: `'full'`, `'left'`, `'inlineLeft'`, `'center'`, `'right'`, `'inlineRight'`, and `'inline'`.
|
|
4948
|
-
*/ const DEFAULT_ICONS = {
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
};
|
|
5401
|
+
*/ const DEFAULT_ICONS = /* #__PURE__ */ (()=>({
|
|
5402
|
+
full: icons.objectFullWidth,
|
|
5403
|
+
left: icons.objectBlockLeft,
|
|
5404
|
+
right: icons.objectBlockRight,
|
|
5405
|
+
center: icons.objectCenter,
|
|
5406
|
+
inlineLeft: icons.objectLeft,
|
|
5407
|
+
inlineRight: icons.objectRight,
|
|
5408
|
+
inline: icons.objectInline
|
|
5409
|
+
}))();
|
|
4957
5410
|
/**
|
|
4958
5411
|
* Default drop-downs provided by the plugin that can be referred in the {@link module:image/imageconfig~ImageConfig#toolbar}
|
|
4959
5412
|
* configuration. The drop-downs are containers for the {@link module:image/imageconfig~ImageStyleConfig#options image style options}.
|
|
@@ -5110,20 +5563,20 @@ const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, ob
|
|
|
5110
5563
|
// Check if the option is supported by any of the loaded plugins.
|
|
5111
5564
|
if (!modelElements.some((elementName)=>supportedElements.includes(elementName))) {
|
|
5112
5565
|
/**
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5566
|
+
* In order to work correctly, each image style {@link module:image/imageconfig~ImageStyleOptionDefinition option}
|
|
5567
|
+
* requires specific model elements (also: types of images) to be supported by the editor.
|
|
5568
|
+
*
|
|
5569
|
+
* Model element names to which the image style option can be applied are defined in the
|
|
5570
|
+
* {@link module:image/imageconfig~ImageStyleOptionDefinition#modelElements} property of the style option
|
|
5571
|
+
* definition.
|
|
5572
|
+
*
|
|
5573
|
+
* Explore the warning in the console to find out precisely which option is not supported and which editor plugins
|
|
5574
|
+
* are missing. Make sure these plugins are loaded in your editor to get this image style option working.
|
|
5575
|
+
*
|
|
5576
|
+
* @error image-style-missing-dependency
|
|
5577
|
+
* @param {String} [option] The name of the unsupported option.
|
|
5578
|
+
* @param {String} [missingPlugins] The names of the plugins one of which has to be loaded for the particular option.
|
|
5579
|
+
*/ logWarning('image-style-missing-dependency', {
|
|
5127
5580
|
style: option,
|
|
5128
5581
|
missingPlugins: modelElements.map((name)=>name === 'imageBlock' ? 'ImageBlockEditing' : 'ImageInlineEditing')
|
|
5129
5582
|
});
|
|
@@ -5150,17 +5603,17 @@ const { objectFullWidth, objectInline, objectLeft, objectRight, objectCenter, ob
|
|
|
5150
5603
|
* Displays a console warning with the 'image-style-configuration-definition-invalid' error.
|
|
5151
5604
|
*/ function warnInvalidStyle(info) {
|
|
5152
5605
|
/**
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5606
|
+
* The image style definition provided in the configuration is invalid.
|
|
5607
|
+
*
|
|
5608
|
+
* Please make sure the definition implements properly one of the following:
|
|
5609
|
+
*
|
|
5610
|
+
* * {@link module:image/imageconfig~ImageStyleOptionDefinition image style option definition},
|
|
5611
|
+
* * {@link module:image/imageconfig~ImageStyleDropdownDefinition image style dropdown definition}
|
|
5612
|
+
*
|
|
5613
|
+
* @error image-style-configuration-definition-invalid
|
|
5614
|
+
* @param {String} [dropdown] The name of the invalid drop-down
|
|
5615
|
+
* @param {String} [style] The name of the invalid image style option
|
|
5616
|
+
*/ logWarning('image-style-configuration-definition-invalid', info);
|
|
5164
5617
|
}
|
|
5165
5618
|
var utils = {
|
|
5166
5619
|
normalizeStyles,
|
|
@@ -5245,22 +5698,36 @@ var utils = {
|
|
|
5245
5698
|
}
|
|
5246
5699
|
}
|
|
5247
5700
|
|
|
5248
|
-
|
|
5701
|
+
/**
|
|
5702
|
+
* The image style engine plugin. It sets the default configuration, creates converters and registers
|
|
5703
|
+
* {@link module:image/imagestyle/imagestylecommand~ImageStyleCommand ImageStyleCommand}.
|
|
5704
|
+
*/ class ImageStyleEditing extends Plugin {
|
|
5249
5705
|
/**
|
|
5250
|
-
|
|
5251
|
-
|
|
5706
|
+
* @inheritDoc
|
|
5707
|
+
*/ static get pluginName() {
|
|
5252
5708
|
return 'ImageStyleEditing';
|
|
5253
5709
|
}
|
|
5254
5710
|
/**
|
|
5255
|
-
|
|
5256
|
-
|
|
5711
|
+
* @inheritDoc
|
|
5712
|
+
*/ static get requires() {
|
|
5257
5713
|
return [
|
|
5258
5714
|
ImageUtils
|
|
5259
5715
|
];
|
|
5260
5716
|
}
|
|
5261
5717
|
/**
|
|
5262
|
-
|
|
5263
|
-
|
|
5718
|
+
* It contains a list of the normalized and validated style options.
|
|
5719
|
+
*
|
|
5720
|
+
* * Each option contains a complete icon markup.
|
|
5721
|
+
* * The style options not supported by any of the loaded image editing plugins (
|
|
5722
|
+
* {@link module:image/image/imageinlineediting~ImageInlineEditing `ImageInlineEditing`} or
|
|
5723
|
+
* {@link module:image/image/imageblockediting~ImageBlockEditing `ImageBlockEditing`}) are filtered out.
|
|
5724
|
+
*
|
|
5725
|
+
* @internal
|
|
5726
|
+
* @readonly
|
|
5727
|
+
*/ normalizedStyles;
|
|
5728
|
+
/**
|
|
5729
|
+
* @inheritDoc
|
|
5730
|
+
*/ init() {
|
|
5264
5731
|
const { normalizeStyles, getDefaultStylesConfiguration } = utils;
|
|
5265
5732
|
const editor = this.editor;
|
|
5266
5733
|
const isBlockPluginLoaded = editor.plugins.has('ImageBlockEditing');
|
|
@@ -5277,10 +5744,10 @@ class ImageStyleEditing extends Plugin {
|
|
|
5277
5744
|
editor.commands.add('imageStyle', new ImageStyleCommand(editor, this.normalizedStyles));
|
|
5278
5745
|
}
|
|
5279
5746
|
/**
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5747
|
+
* Sets the editor conversion taking the presence of
|
|
5748
|
+
* {@link module:image/image/imageinlineediting~ImageInlineEditing `ImageInlineEditing`}
|
|
5749
|
+
* and {@link module:image/image/imageblockediting~ImageBlockEditing `ImageBlockEditing`} plugins into consideration.
|
|
5750
|
+
*/ _setupConversion(isBlockPluginLoaded, isInlinePluginLoaded) {
|
|
5284
5751
|
const editor = this.editor;
|
|
5285
5752
|
const schema = editor.model.schema;
|
|
5286
5753
|
const modelToViewConverter = modelToViewStyleAttribute(this.normalizedStyles);
|
|
@@ -5309,8 +5776,8 @@ class ImageStyleEditing extends Plugin {
|
|
|
5309
5776
|
}
|
|
5310
5777
|
}
|
|
5311
5778
|
/**
|
|
5312
|
-
|
|
5313
|
-
|
|
5779
|
+
* Registers a post-fixer that will make sure that the style attribute value is correct for a specific image type (block vs inline).
|
|
5780
|
+
*/ _setupPostFixer() {
|
|
5314
5781
|
const editor = this.editor;
|
|
5315
5782
|
const document = editor.model.document;
|
|
5316
5783
|
const imageUtils = editor.plugins.get(ImageUtils);
|
|
@@ -5346,34 +5813,40 @@ class ImageStyleEditing extends Plugin {
|
|
|
5346
5813
|
}
|
|
5347
5814
|
}
|
|
5348
5815
|
|
|
5349
|
-
|
|
5816
|
+
/**
|
|
5817
|
+
* The image style UI plugin.
|
|
5818
|
+
*
|
|
5819
|
+
* It registers buttons corresponding to the {@link module:image/imageconfig~ImageConfig#styles} configuration.
|
|
5820
|
+
* It also registers the {@link module:image/imagestyle/utils#DEFAULT_DROPDOWN_DEFINITIONS default drop-downs} and the
|
|
5821
|
+
* custom drop-downs defined by the developer in the {@link module:image/imageconfig~ImageConfig#toolbar} configuration.
|
|
5822
|
+
*/ class ImageStyleUI extends Plugin {
|
|
5350
5823
|
/**
|
|
5351
|
-
|
|
5352
|
-
|
|
5824
|
+
* @inheritDoc
|
|
5825
|
+
*/ static get requires() {
|
|
5353
5826
|
return [
|
|
5354
5827
|
ImageStyleEditing
|
|
5355
5828
|
];
|
|
5356
5829
|
}
|
|
5357
5830
|
/**
|
|
5358
|
-
|
|
5359
|
-
|
|
5831
|
+
* @inheritDoc
|
|
5832
|
+
*/ static get pluginName() {
|
|
5360
5833
|
return 'ImageStyleUI';
|
|
5361
5834
|
}
|
|
5362
5835
|
/**
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5836
|
+
* Returns the default localized style titles provided by the plugin.
|
|
5837
|
+
*
|
|
5838
|
+
* The following localized titles corresponding with
|
|
5839
|
+
* {@link module:image/imagestyle/utils#DEFAULT_OPTIONS} are available:
|
|
5840
|
+
*
|
|
5841
|
+
* * `'Wrap text'`,
|
|
5842
|
+
* * `'Break text'`,
|
|
5843
|
+
* * `'In line'`,
|
|
5844
|
+
* * `'Full size image'`,
|
|
5845
|
+
* * `'Side image'`,
|
|
5846
|
+
* * `'Left aligned image'`,
|
|
5847
|
+
* * `'Centered image'`,
|
|
5848
|
+
* * `'Right aligned image'`
|
|
5849
|
+
*/ get localizedDefaultStylesTitles() {
|
|
5377
5850
|
const t = this.editor.t;
|
|
5378
5851
|
return {
|
|
5379
5852
|
'Wrap text': t('Wrap text'),
|
|
@@ -5387,8 +5860,8 @@ class ImageStyleUI extends Plugin {
|
|
|
5387
5860
|
};
|
|
5388
5861
|
}
|
|
5389
5862
|
/**
|
|
5390
|
-
|
|
5391
|
-
|
|
5863
|
+
* @inheritDoc
|
|
5864
|
+
*/ init() {
|
|
5392
5865
|
const plugins = this.editor.plugins;
|
|
5393
5866
|
const toolbarConfig = this.editor.config.get('image.toolbar') || [];
|
|
5394
5867
|
const imageStyleEditing = plugins.get('ImageStyleEditing');
|
|
@@ -5405,8 +5878,8 @@ class ImageStyleUI extends Plugin {
|
|
|
5405
5878
|
}
|
|
5406
5879
|
}
|
|
5407
5880
|
/**
|
|
5408
|
-
|
|
5409
|
-
|
|
5881
|
+
* Creates a dropdown and stores it in the editor {@link module:ui/componentfactory~ComponentFactory}.
|
|
5882
|
+
*/ _createDropdown(dropdownConfig, definedStyles) {
|
|
5410
5883
|
const factory = this.editor.ui.componentFactory;
|
|
5411
5884
|
factory.add(dropdownConfig.name, (locale)=>{
|
|
5412
5885
|
let defaultButton;
|
|
@@ -5465,8 +5938,8 @@ class ImageStyleUI extends Plugin {
|
|
|
5465
5938
|
});
|
|
5466
5939
|
}
|
|
5467
5940
|
/**
|
|
5468
|
-
|
|
5469
|
-
|
|
5941
|
+
* Creates a button and stores it in the editor {@link module:ui/componentfactory~ComponentFactory}.
|
|
5942
|
+
*/ _createButton(buttonConfig) {
|
|
5470
5943
|
const buttonName = buttonConfig.name;
|
|
5471
5944
|
this.editor.ui.componentFactory.add(getUIComponentName(buttonName), (locale)=>{
|
|
5472
5945
|
const command = this.editor.commands.get('imageStyle');
|
|
@@ -5513,39 +5986,60 @@ class ImageStyleUI extends Plugin {
|
|
|
5513
5986
|
return (dropdownTitle ? dropdownTitle + ': ' : '') + buttonTitle;
|
|
5514
5987
|
}
|
|
5515
5988
|
|
|
5516
|
-
|
|
5989
|
+
/**
|
|
5990
|
+
* The image style plugin.
|
|
5991
|
+
*
|
|
5992
|
+
* For a detailed overview of the image styles feature, check the {@glink features/images/images-styles documentation}.
|
|
5993
|
+
*
|
|
5994
|
+
* This is a "glue" plugin which loads the following plugins:
|
|
5995
|
+
* * {@link module:image/imagestyle/imagestyleediting~ImageStyleEditing},
|
|
5996
|
+
* * {@link module:image/imagestyle/imagestyleui~ImageStyleUI}
|
|
5997
|
+
*
|
|
5998
|
+
* It provides a default configuration, which can be extended or overwritten.
|
|
5999
|
+
* Read more about the {@link module:image/imageconfig~ImageConfig#styles image styles configuration}.
|
|
6000
|
+
*/ class ImageStyle extends Plugin {
|
|
5517
6001
|
/**
|
|
5518
|
-
|
|
5519
|
-
|
|
6002
|
+
* @inheritDoc
|
|
6003
|
+
*/ static get requires() {
|
|
5520
6004
|
return [
|
|
5521
6005
|
ImageStyleEditing,
|
|
5522
6006
|
ImageStyleUI
|
|
5523
6007
|
];
|
|
5524
6008
|
}
|
|
5525
6009
|
/**
|
|
5526
|
-
|
|
5527
|
-
|
|
6010
|
+
* @inheritDoc
|
|
6011
|
+
*/ static get pluginName() {
|
|
5528
6012
|
return 'ImageStyle';
|
|
5529
6013
|
}
|
|
5530
6014
|
}
|
|
5531
6015
|
|
|
5532
|
-
|
|
6016
|
+
/**
|
|
6017
|
+
* The image toolbar plugin. It creates and manages the image toolbar (the toolbar displayed when an image is selected).
|
|
6018
|
+
*
|
|
6019
|
+
* For an overview, check the {@glink features/images/images-overview#image-contextual-toolbar image contextual toolbar} documentation.
|
|
6020
|
+
*
|
|
6021
|
+
* Instances of toolbar components (e.g. buttons) are created using the editor's
|
|
6022
|
+
* {@link module:ui/componentfactory~ComponentFactory component factory}
|
|
6023
|
+
* based on the {@link module:image/imageconfig~ImageConfig#toolbar `image.toolbar` configuration option}.
|
|
6024
|
+
*
|
|
6025
|
+
* The toolbar uses the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon}.
|
|
6026
|
+
*/ class ImageToolbar extends Plugin {
|
|
5533
6027
|
/**
|
|
5534
|
-
|
|
5535
|
-
|
|
6028
|
+
* @inheritDoc
|
|
6029
|
+
*/ static get requires() {
|
|
5536
6030
|
return [
|
|
5537
6031
|
WidgetToolbarRepository,
|
|
5538
6032
|
ImageUtils
|
|
5539
6033
|
];
|
|
5540
6034
|
}
|
|
5541
6035
|
/**
|
|
5542
|
-
|
|
5543
|
-
|
|
6036
|
+
* @inheritDoc
|
|
6037
|
+
*/ static get pluginName() {
|
|
5544
6038
|
return 'ImageToolbar';
|
|
5545
6039
|
}
|
|
5546
6040
|
/**
|
|
5547
|
-
|
|
5548
|
-
|
|
6041
|
+
* @inheritDoc
|
|
6042
|
+
*/ afterInit() {
|
|
5549
6043
|
const editor = this.editor;
|
|
5550
6044
|
const t = editor.t;
|
|
5551
6045
|
const widgetToolbarRepository = editor.plugins.get(WidgetToolbarRepository);
|
|
@@ -5564,23 +6058,75 @@ class ImageToolbar extends Plugin {
|
|
|
5564
6058
|
return config.map((item)=>isObject(item) ? item.name : item);
|
|
5565
6059
|
}
|
|
5566
6060
|
|
|
5567
|
-
|
|
6061
|
+
/**
|
|
6062
|
+
* This plugin enables the [`<picture>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture) element support in the editor.
|
|
6063
|
+
*
|
|
6064
|
+
* * It enables the `sources` model attribute on `imageBlock` and `imageInline` model elements
|
|
6065
|
+
* (brought by {@link module:image/imageblock~ImageBlock} and {@link module:image/imageinline~ImageInline}, respectively).
|
|
6066
|
+
* * It translates the `sources` model element to the view (also: data) structure that may look as follows:
|
|
6067
|
+
*
|
|
6068
|
+
* ```html
|
|
6069
|
+
* <p>Inline image using picture:
|
|
6070
|
+
* <picture>
|
|
6071
|
+
* <source media="(min-width: 800px)" srcset="image-large.webp" type="image/webp">
|
|
6072
|
+
* <source media="(max-width: 800px)" srcset="image-small.webp" type="image/webp">
|
|
6073
|
+
* <!-- Other sources as specified in the "sources" model attribute... -->
|
|
6074
|
+
* <img src="image.png" alt="An image using picture" />
|
|
6075
|
+
* </picture>
|
|
6076
|
+
* </p>
|
|
6077
|
+
*
|
|
6078
|
+
* <p>Block image using picture:</p>
|
|
6079
|
+
* <figure class="image">
|
|
6080
|
+
* <picture>
|
|
6081
|
+
* <source media="(min-width: 800px)" srcset="image-large.webp" type="image/webp">
|
|
6082
|
+
* <source media="(max-width: 800px)" srcset="image-small.webp" type="image/webp">
|
|
6083
|
+
* <!-- Other sources as specified in the "sources" model attribute... -->
|
|
6084
|
+
* <img src="image.png" alt="An image using picture" />
|
|
6085
|
+
* </picture>
|
|
6086
|
+
* <figcaption>Caption of the image</figcaption>
|
|
6087
|
+
* </figure>
|
|
6088
|
+
* ```
|
|
6089
|
+
*
|
|
6090
|
+
* **Note:** The value of the `sources` {@glink framework/architecture/editing-engine#changing-the-model model attribute}
|
|
6091
|
+
* in both examples equals:
|
|
6092
|
+
*
|
|
6093
|
+
* ```css
|
|
6094
|
+
* [
|
|
6095
|
+
* {
|
|
6096
|
+
* media: '(min-width: 800px)',
|
|
6097
|
+
* srcset: 'image-large.webp',
|
|
6098
|
+
* type: 'image/webp'
|
|
6099
|
+
* },
|
|
6100
|
+
* {
|
|
6101
|
+
* media: '(max-width: 800px)',
|
|
6102
|
+
* srcset: 'image-small.webp',
|
|
6103
|
+
* type: 'image/webp'
|
|
6104
|
+
* }
|
|
6105
|
+
* ]
|
|
6106
|
+
* ```
|
|
6107
|
+
*
|
|
6108
|
+
* * It integrates with the {@link module:image/imageupload~ImageUpload} plugin so images uploaded in the editor
|
|
6109
|
+
* automatically render using `<picture>` if the {@glink features/images/image-upload/image-upload upload adapter}
|
|
6110
|
+
* supports image sources and provides neccessary data.
|
|
6111
|
+
*
|
|
6112
|
+
* @private
|
|
6113
|
+
*/ class PictureEditing extends Plugin {
|
|
5568
6114
|
/**
|
|
5569
|
-
|
|
5570
|
-
|
|
6115
|
+
* @inheritDoc
|
|
6116
|
+
*/ static get requires() {
|
|
5571
6117
|
return [
|
|
5572
6118
|
ImageEditing,
|
|
5573
6119
|
ImageUtils
|
|
5574
6120
|
];
|
|
5575
6121
|
}
|
|
5576
6122
|
/**
|
|
5577
|
-
|
|
5578
|
-
|
|
6123
|
+
* @inheritDoc
|
|
6124
|
+
*/ static get pluginName() {
|
|
5579
6125
|
return 'PictureEditing';
|
|
5580
6126
|
}
|
|
5581
6127
|
/**
|
|
5582
|
-
|
|
5583
|
-
|
|
6128
|
+
* @inheritDoc
|
|
6129
|
+
*/ afterInit() {
|
|
5584
6130
|
const editor = this.editor;
|
|
5585
6131
|
if (editor.plugins.has('ImageBlockEditing')) {
|
|
5586
6132
|
editor.model.schema.extend('imageBlock', {
|
|
@@ -5600,9 +6146,9 @@ class PictureEditing extends Plugin {
|
|
|
5600
6146
|
this._setupImageUploadEditingIntegration();
|
|
5601
6147
|
}
|
|
5602
6148
|
/**
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
6149
|
+
* Configures conversion pipelines to support upcasting and downcasting images using the `<picture>` view element
|
|
6150
|
+
* and the model `sources` attribute.
|
|
6151
|
+
*/ _setupConversion() {
|
|
5606
6152
|
const editor = this.editor;
|
|
5607
6153
|
const conversion = editor.conversion;
|
|
5608
6154
|
const imageUtils = editor.plugins.get('ImageUtils');
|
|
@@ -5610,10 +6156,10 @@ class PictureEditing extends Plugin {
|
|
|
5610
6156
|
conversion.for('downcast').add(downcastSourcesAttribute(imageUtils));
|
|
5611
6157
|
}
|
|
5612
6158
|
/**
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
6159
|
+
* Makes it possible for uploaded images to get the `sources` model attribute and the `<picture>...</picture>`
|
|
6160
|
+
* view structure out-of-the-box if relevant data is provided along the
|
|
6161
|
+
* {@link module:image/imageupload/imageuploadediting~ImageUploadEditing#event:uploadComplete} event.
|
|
6162
|
+
*/ _setupImageUploadEditingIntegration() {
|
|
5617
6163
|
const editor = this.editor;
|
|
5618
6164
|
if (!editor.plugins.has('ImageUploadEditing')) {
|
|
5619
6165
|
return;
|