@ckeditor/ckeditor5-image 40.0.0 → 40.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/CHANGELOG.md +25 -25
  2. package/LICENSE.md +3 -3
  3. package/build/image.js +2 -2
  4. package/build/translations/ar.js +1 -1
  5. package/build/translations/ast.js +1 -1
  6. package/build/translations/az.js +1 -1
  7. package/build/translations/bg.js +1 -1
  8. package/build/translations/bn.js +1 -1
  9. package/build/translations/bs.js +1 -1
  10. package/build/translations/ca.js +1 -1
  11. package/build/translations/cs.js +1 -1
  12. package/build/translations/da.js +1 -1
  13. package/build/translations/de-ch.js +1 -1
  14. package/build/translations/de.js +1 -1
  15. package/build/translations/el.js +1 -1
  16. package/build/translations/en-au.js +1 -1
  17. package/build/translations/en-gb.js +1 -1
  18. package/build/translations/eo.js +1 -1
  19. package/build/translations/es.js +1 -1
  20. package/build/translations/et.js +1 -1
  21. package/build/translations/eu.js +1 -1
  22. package/build/translations/fa.js +1 -1
  23. package/build/translations/fi.js +1 -1
  24. package/build/translations/fr.js +1 -1
  25. package/build/translations/gl.js +1 -1
  26. package/build/translations/he.js +1 -1
  27. package/build/translations/hi.js +1 -1
  28. package/build/translations/hr.js +1 -1
  29. package/build/translations/hu.js +1 -1
  30. package/build/translations/id.js +1 -1
  31. package/build/translations/it.js +1 -1
  32. package/build/translations/ja.js +1 -1
  33. package/build/translations/jv.js +1 -1
  34. package/build/translations/km.js +1 -1
  35. package/build/translations/kn.js +1 -1
  36. package/build/translations/ko.js +1 -1
  37. package/build/translations/ku.js +1 -1
  38. package/build/translations/lt.js +1 -1
  39. package/build/translations/lv.js +1 -1
  40. package/build/translations/ms.js +1 -1
  41. package/build/translations/nb.js +1 -1
  42. package/build/translations/ne.js +1 -1
  43. package/build/translations/nl.js +1 -1
  44. package/build/translations/no.js +1 -1
  45. package/build/translations/pl.js +1 -1
  46. package/build/translations/pt-br.js +1 -1
  47. package/build/translations/pt.js +1 -1
  48. package/build/translations/ro.js +1 -1
  49. package/build/translations/ru.js +1 -1
  50. package/build/translations/si.js +1 -1
  51. package/build/translations/sk.js +1 -1
  52. package/build/translations/sq.js +1 -1
  53. package/build/translations/sr-latn.js +1 -1
  54. package/build/translations/sr.js +1 -1
  55. package/build/translations/sv.js +1 -1
  56. package/build/translations/th.js +1 -1
  57. package/build/translations/tk.js +1 -1
  58. package/build/translations/tr.js +1 -1
  59. package/build/translations/tt.js +1 -1
  60. package/build/translations/ug.js +1 -1
  61. package/build/translations/uk.js +1 -1
  62. package/build/translations/ur.js +1 -1
  63. package/build/translations/uz.js +1 -1
  64. package/build/translations/vi.js +1 -1
  65. package/build/translations/zh-cn.js +1 -1
  66. package/build/translations/zh.js +1 -1
  67. package/ckeditor5-metadata.json +3 -3
  68. package/lang/contexts.json +5 -0
  69. package/lang/translations/ar.po +20 -0
  70. package/lang/translations/ast.po +20 -0
  71. package/lang/translations/az.po +20 -0
  72. package/lang/translations/bg.po +20 -0
  73. package/lang/translations/bn.po +20 -0
  74. package/lang/translations/bs.po +20 -0
  75. package/lang/translations/ca.po +20 -0
  76. package/lang/translations/cs.po +20 -0
  77. package/lang/translations/da.po +20 -0
  78. package/lang/translations/de-ch.po +20 -0
  79. package/lang/translations/de.po +20 -0
  80. package/lang/translations/el.po +20 -0
  81. package/lang/translations/en-au.po +20 -0
  82. package/lang/translations/en-gb.po +20 -0
  83. package/lang/translations/en.po +20 -0
  84. package/lang/translations/eo.po +20 -0
  85. package/lang/translations/es.po +20 -0
  86. package/lang/translations/et.po +20 -0
  87. package/lang/translations/eu.po +20 -0
  88. package/lang/translations/fa.po +20 -0
  89. package/lang/translations/fi.po +20 -0
  90. package/lang/translations/fr.po +20 -0
  91. package/lang/translations/gl.po +20 -0
  92. package/lang/translations/he.po +20 -0
  93. package/lang/translations/hi.po +20 -0
  94. package/lang/translations/hr.po +20 -0
  95. package/lang/translations/hu.po +20 -0
  96. package/lang/translations/id.po +20 -0
  97. package/lang/translations/it.po +20 -0
  98. package/lang/translations/ja.po +20 -0
  99. package/lang/translations/jv.po +20 -0
  100. package/lang/translations/km.po +20 -0
  101. package/lang/translations/kn.po +20 -0
  102. package/lang/translations/ko.po +20 -0
  103. package/lang/translations/ku.po +20 -0
  104. package/lang/translations/lt.po +20 -0
  105. package/lang/translations/lv.po +20 -0
  106. package/lang/translations/ms.po +20 -0
  107. package/lang/translations/nb.po +20 -0
  108. package/lang/translations/ne.po +20 -0
  109. package/lang/translations/nl.po +20 -0
  110. package/lang/translations/no.po +20 -0
  111. package/lang/translations/pl.po +20 -0
  112. package/lang/translations/pt-br.po +20 -0
  113. package/lang/translations/pt.po +20 -0
  114. package/lang/translations/ro.po +20 -0
  115. package/lang/translations/ru.po +20 -0
  116. package/lang/translations/si.po +20 -0
  117. package/lang/translations/sk.po +20 -0
  118. package/lang/translations/sq.po +20 -0
  119. package/lang/translations/sr-latn.po +20 -0
  120. package/lang/translations/sr.po +20 -0
  121. package/lang/translations/sv.po +20 -0
  122. package/lang/translations/th.po +20 -0
  123. package/lang/translations/tk.po +20 -0
  124. package/lang/translations/tr.po +20 -0
  125. package/lang/translations/tt.po +20 -0
  126. package/lang/translations/ug.po +21 -1
  127. package/lang/translations/uk.po +20 -0
  128. package/lang/translations/ur.po +20 -0
  129. package/lang/translations/uz.po +20 -0
  130. package/lang/translations/vi.po +20 -0
  131. package/lang/translations/zh-cn.po +20 -0
  132. package/lang/translations/zh.po +20 -0
  133. package/package.json +3 -3
  134. package/src/augmentation.d.ts +56 -56
  135. package/src/augmentation.js +5 -5
  136. package/src/autoimage.d.ts +52 -52
  137. package/src/autoimage.js +132 -132
  138. package/src/image/converters.d.ts +66 -66
  139. package/src/image/converters.js +232 -232
  140. package/src/image/imageblockediting.d.ts +59 -58
  141. package/src/image/imageblockediting.js +153 -152
  142. package/src/image/imageediting.d.ts +30 -30
  143. package/src/image/imageediting.js +63 -63
  144. package/src/image/imageinlineediting.d.ts +60 -59
  145. package/src/image/imageinlineediting.js +177 -176
  146. package/src/image/imageloadobserver.d.ts +48 -48
  147. package/src/image/imageloadobserver.js +52 -52
  148. package/src/image/imageplaceholder.d.ts +39 -0
  149. package/src/image/imageplaceholder.js +113 -0
  150. package/src/image/imagetypecommand.d.ts +44 -44
  151. package/src/image/imagetypecommand.js +80 -80
  152. package/src/image/insertimagecommand.d.ts +66 -66
  153. package/src/image/insertimagecommand.js +120 -120
  154. package/src/image/replaceimagesourcecommand.d.ts +51 -34
  155. package/src/image/replaceimagesourcecommand.js +75 -44
  156. package/src/image/ui/utils.d.ts +25 -25
  157. package/src/image/ui/utils.js +44 -44
  158. package/src/image/utils.d.ts +64 -64
  159. package/src/image/utils.js +121 -121
  160. package/src/image.d.ts +34 -34
  161. package/src/image.js +38 -38
  162. package/src/imageblock.d.ts +34 -33
  163. package/src/imageblock.js +38 -37
  164. package/src/imagecaption/imagecaptionediting.d.ts +89 -89
  165. package/src/imagecaption/imagecaptionediting.js +225 -225
  166. package/src/imagecaption/imagecaptionui.d.ts +26 -26
  167. package/src/imagecaption/imagecaptionui.js +61 -61
  168. package/src/imagecaption/imagecaptionutils.d.ts +38 -38
  169. package/src/imagecaption/imagecaptionutils.js +62 -62
  170. package/src/imagecaption/toggleimagecaptioncommand.d.ts +66 -66
  171. package/src/imagecaption/toggleimagecaptioncommand.js +138 -138
  172. package/src/imagecaption.d.ts +26 -26
  173. package/src/imagecaption.js +30 -30
  174. package/src/imageconfig.d.ts +712 -713
  175. package/src/imageconfig.js +5 -5
  176. package/src/imageinline.d.ts +34 -33
  177. package/src/imageinline.js +38 -37
  178. package/src/imageinsert/imageinsertui.d.ts +72 -44
  179. package/src/imageinsert/imageinsertui.js +174 -141
  180. package/src/imageinsert/imageinsertviaurlui.d.ts +44 -0
  181. package/src/imageinsert/imageinsertviaurlui.js +122 -0
  182. package/src/imageinsert/ui/imageinsertformview.d.ts +56 -0
  183. package/src/imageinsert/ui/imageinsertformview.js +112 -0
  184. package/src/imageinsert/ui/imageinserturlview.d.ts +107 -0
  185. package/src/imageinsert/ui/imageinserturlview.js +156 -0
  186. package/src/imageinsert.d.ts +33 -33
  187. package/src/imageinsert.js +37 -37
  188. package/src/imageinsertviaurl.d.ts +31 -30
  189. package/src/imageinsertviaurl.js +35 -34
  190. package/src/imageresize/imageresizebuttons.d.ts +67 -67
  191. package/src/imageresize/imageresizebuttons.js +217 -217
  192. package/src/imageresize/imageresizeediting.d.ts +37 -37
  193. package/src/imageresize/imageresizeediting.js +165 -165
  194. package/src/imageresize/imageresizehandles.d.ts +31 -31
  195. package/src/imageresize/imageresizehandles.js +114 -114
  196. package/src/imageresize/resizeimagecommand.d.ts +42 -42
  197. package/src/imageresize/resizeimagecommand.js +63 -63
  198. package/src/imageresize.d.ts +27 -27
  199. package/src/imageresize.js +31 -31
  200. package/src/imagesizeattributes.d.ts +34 -34
  201. package/src/imagesizeattributes.js +142 -143
  202. package/src/imagestyle/converters.d.ts +24 -24
  203. package/src/imagestyle/converters.js +79 -79
  204. package/src/imagestyle/imagestylecommand.d.ts +68 -68
  205. package/src/imagestyle/imagestylecommand.js +107 -107
  206. package/src/imagestyle/imagestyleediting.d.ts +50 -50
  207. package/src/imagestyle/imagestyleediting.js +108 -108
  208. package/src/imagestyle/imagestyleui.d.ts +56 -56
  209. package/src/imagestyle/imagestyleui.js +192 -192
  210. package/src/imagestyle/utils.d.ts +101 -101
  211. package/src/imagestyle/utils.js +329 -329
  212. package/src/imagestyle.d.ts +32 -32
  213. package/src/imagestyle.js +36 -36
  214. package/src/imagetextalternative/imagetextalternativecommand.d.ts +34 -34
  215. package/src/imagetextalternative/imagetextalternativecommand.js +44 -44
  216. package/src/imagetextalternative/imagetextalternativeediting.d.ts +28 -28
  217. package/src/imagetextalternative/imagetextalternativeediting.js +35 -35
  218. package/src/imagetextalternative/imagetextalternativeui.d.ts +68 -68
  219. package/src/imagetextalternative/imagetextalternativeui.js +173 -173
  220. package/src/imagetextalternative/ui/textalternativeformview.d.ts +90 -72
  221. package/src/imagetextalternative/ui/textalternativeformview.js +121 -121
  222. package/src/imagetextalternative.d.ts +29 -29
  223. package/src/imagetextalternative.js +33 -33
  224. package/src/imagetoolbar.d.ts +35 -35
  225. package/src/imagetoolbar.js +57 -57
  226. package/src/imageupload/imageuploadediting.d.ts +111 -111
  227. package/src/imageupload/imageuploadediting.js +337 -337
  228. package/src/imageupload/imageuploadprogress.d.ts +42 -42
  229. package/src/imageupload/imageuploadprogress.js +211 -211
  230. package/src/imageupload/imageuploadui.d.ts +23 -23
  231. package/src/imageupload/imageuploadui.js +81 -57
  232. package/src/imageupload/uploadimagecommand.d.ts +60 -60
  233. package/src/imageupload/uploadimagecommand.js +100 -100
  234. package/src/imageupload/utils.d.ts +33 -33
  235. package/src/imageupload/utils.js +112 -112
  236. package/src/imageupload.d.ts +32 -32
  237. package/src/imageupload.js +36 -36
  238. package/src/imageutils.d.ts +125 -125
  239. package/src/imageutils.js +306 -306
  240. package/src/index.d.ts +48 -48
  241. package/src/index.js +39 -39
  242. package/src/pictureediting.d.ts +88 -88
  243. package/src/pictureediting.js +130 -130
  244. package/theme/imageinsert.css +5 -17
  245. package/theme/imageplaceholder.css +10 -0
  246. package/build/image.js.map +0 -1
  247. package/src/imageinsert/ui/imageinsertformrowview.d.ts +0 -61
  248. package/src/imageinsert/ui/imageinsertformrowview.js +0 -54
  249. package/src/imageinsert/ui/imageinsertpanelview.d.ts +0 -106
  250. package/src/imageinsert/ui/imageinsertpanelview.js +0 -161
  251. package/src/imageinsert/utils.d.ts +0 -25
  252. package/src/imageinsert/utils.js +0 -58
  253. package/theme/imageinsertformrowview.css +0 -36
package/src/imageutils.js CHANGED
@@ -1,306 +1,306 @@
1
- /**
2
- * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
- */
5
- import { Plugin } from 'ckeditor5/src/core';
6
- import { findOptimalInsertionRange, isWidget, toWidget } from 'ckeditor5/src/widget';
7
- import { determineImageTypeForInsertionAtSelection } from './image/utils';
8
- import { DomEmitterMixin, global } from 'ckeditor5/src/utils';
9
- const IMAGE_WIDGETS_CLASSES_MATCH_REGEXP = /^(image|image-inline)$/;
10
- /**
11
- * A set of helpers related to images.
12
- */
13
- export default class ImageUtils extends Plugin {
14
- constructor() {
15
- super(...arguments);
16
- /**
17
- * DOM Emitter.
18
- */
19
- this._domEmitter = new (DomEmitterMixin())();
20
- }
21
- /**
22
- * @inheritDoc
23
- */
24
- static get pluginName() {
25
- return 'ImageUtils';
26
- }
27
- /**
28
- * Checks if the provided model element is an `image` or `imageInline`.
29
- */
30
- isImage(modelElement) {
31
- return this.isInlineImage(modelElement) || this.isBlockImage(modelElement);
32
- }
33
- /**
34
- * Checks if the provided view element represents an inline image.
35
- *
36
- * Also, see {@link module:image/imageutils~ImageUtils#isImageWidget}.
37
- */
38
- isInlineImageView(element) {
39
- return !!element && element.is('element', 'img');
40
- }
41
- /**
42
- * Checks if the provided view element represents a block image.
43
- *
44
- * Also, see {@link module:image/imageutils~ImageUtils#isImageWidget}.
45
- */
46
- isBlockImageView(element) {
47
- return !!element && element.is('element', 'figure') && element.hasClass('image');
48
- }
49
- /**
50
- * Handles inserting single file. This method unifies image insertion using {@link module:widget/utils~findOptimalInsertionRange}
51
- * method.
52
- *
53
- * ```ts
54
- * const imageUtils = editor.plugins.get( 'ImageUtils' );
55
- *
56
- * imageUtils.insertImage( { src: 'path/to/image.jpg' } );
57
- * ```
58
- *
59
- * @param attributes Attributes of the inserted image.
60
- * This method filters out the attributes which are disallowed by the {@link module:engine/model/schema~Schema}.
61
- * @param selectable Place to insert the image. If not specified,
62
- * the {@link module:widget/utils~findOptimalInsertionRange} logic will be applied for the block images
63
- * and `model.document.selection` for the inline images.
64
- *
65
- * **Note**: If `selectable` is passed, this helper will not be able to set selection attributes (such as `linkHref`)
66
- * and apply them to the new image. In this case, make sure all selection attributes are passed in `attributes`.
67
- *
68
- * @param imageType Image type of inserted image. If not specified,
69
- * it will be determined automatically depending of editor config or place of the insertion.
70
- * @param options.setImageSizes Specifies whether the image `width` and `height` attributes should be set automatically.
71
- * The default is `true`.
72
- * @return The inserted model image element.
73
- */
74
- insertImage(attributes = {}, selectable = null, imageType = null, options = {}) {
75
- const editor = this.editor;
76
- const model = editor.model;
77
- const selection = model.document.selection;
78
- imageType = determineImageTypeForInsertion(editor, selectable || selection, imageType);
79
- // Mix declarative attributes with selection attributes because the new image should "inherit"
80
- // the latter for best UX. For instance, inline images inserted into existing links
81
- // should not split them. To do that, they need to have "linkHref" inherited from the selection.
82
- attributes = {
83
- ...Object.fromEntries(selection.getAttributes()),
84
- ...attributes
85
- };
86
- for (const attributeName in attributes) {
87
- if (!model.schema.checkAttribute(imageType, attributeName)) {
88
- delete attributes[attributeName];
89
- }
90
- }
91
- return model.change(writer => {
92
- const { setImageSizes = true } = options;
93
- const imageElement = writer.createElement(imageType, attributes);
94
- model.insertObject(imageElement, selectable, null, {
95
- setSelection: 'on',
96
- // If we want to insert a block image (for whatever reason) then we don't want to split text blocks.
97
- // This applies only when we don't have the selectable specified (i.e., we insert multiple block images at once).
98
- findOptimalPosition: !selectable && imageType != 'imageInline' ? 'auto' : undefined
99
- });
100
- // Inserting an image might've failed due to schema regulations.
101
- if (imageElement.parent) {
102
- if (setImageSizes) {
103
- this.setImageNaturalSizeAttributes(imageElement);
104
- }
105
- return imageElement;
106
- }
107
- return null;
108
- });
109
- }
110
- /**
111
- * Reads original image sizes and sets them as `width` and `height`.
112
- *
113
- * The `src` attribute may not be available if the user is using an upload adapter. In such a case,
114
- * this method is called again after the upload process is complete and the `src` attribute is available.
115
- */
116
- setImageNaturalSizeAttributes(imageElement) {
117
- const src = imageElement.getAttribute('src');
118
- if (!src) {
119
- return;
120
- }
121
- if (imageElement.getAttribute('width') || imageElement.getAttribute('height')) {
122
- return;
123
- }
124
- this.editor.model.change(writer => {
125
- const img = new global.window.Image();
126
- this._domEmitter.listenTo(img, 'load', () => {
127
- if (!imageElement.getAttribute('width') && !imageElement.getAttribute('height')) {
128
- // We use writer.batch to be able to undo (in a single step) width and height setting
129
- // along with any change that triggered this action (e.g. image resize or image style change).
130
- this.editor.model.enqueueChange(writer.batch, writer => {
131
- writer.setAttribute('width', img.naturalWidth, imageElement);
132
- writer.setAttribute('height', img.naturalHeight, imageElement);
133
- });
134
- }
135
- this._domEmitter.stopListening(img, 'load');
136
- });
137
- img.src = src;
138
- });
139
- }
140
- /**
141
- * Returns an image widget editing view element if one is selected or is among the selection's ancestors.
142
- */
143
- getClosestSelectedImageWidget(selection) {
144
- const selectionPosition = selection.getFirstPosition();
145
- if (!selectionPosition) {
146
- return null;
147
- }
148
- const viewElement = selection.getSelectedElement();
149
- if (viewElement && this.isImageWidget(viewElement)) {
150
- return viewElement;
151
- }
152
- let parent = selectionPosition.parent;
153
- while (parent) {
154
- if (parent.is('element') && this.isImageWidget(parent)) {
155
- return parent;
156
- }
157
- parent = parent.parent;
158
- }
159
- return null;
160
- }
161
- /**
162
- * Returns a image model element if one is selected or is among the selection's ancestors.
163
- */
164
- getClosestSelectedImageElement(selection) {
165
- const selectedElement = selection.getSelectedElement();
166
- return this.isImage(selectedElement) ? selectedElement : selection.getFirstPosition().findAncestor('imageBlock');
167
- }
168
- /**
169
- * Returns an image widget editing view based on the passed image view.
170
- */
171
- getImageWidgetFromImageView(imageView) {
172
- return imageView.findAncestor({ classes: IMAGE_WIDGETS_CLASSES_MATCH_REGEXP });
173
- }
174
- /**
175
- * Checks if image can be inserted at current model selection.
176
- *
177
- * @internal
178
- */
179
- isImageAllowed() {
180
- const model = this.editor.model;
181
- const selection = model.document.selection;
182
- return isImageAllowedInParent(this.editor, selection) && isNotInsideImage(selection);
183
- }
184
- /**
185
- * Converts a given {@link module:engine/view/element~Element} to an image widget:
186
- * * Adds a {@link module:engine/view/element~Element#_setCustomProperty custom property} allowing to recognize the image widget
187
- * element.
188
- * * Calls the {@link module:widget/utils~toWidget} function with the proper element's label creator.
189
- *
190
- * @param writer An instance of the view writer.
191
- * @param label The element's label. It will be concatenated with the image `alt` attribute if one is present.
192
- */
193
- toImageWidget(viewElement, writer, label) {
194
- writer.setCustomProperty('image', true, viewElement);
195
- const labelCreator = () => {
196
- const imgElement = this.findViewImgElement(viewElement);
197
- const altText = imgElement.getAttribute('alt');
198
- return altText ? `${altText} ${label}` : label;
199
- };
200
- return toWidget(viewElement, writer, { label: labelCreator });
201
- }
202
- /**
203
- * Checks if a given view element is an image widget.
204
- */
205
- isImageWidget(viewElement) {
206
- return !!viewElement.getCustomProperty('image') && isWidget(viewElement);
207
- }
208
- /**
209
- * Checks if the provided model element is an `image`.
210
- */
211
- isBlockImage(modelElement) {
212
- return !!modelElement && modelElement.is('element', 'imageBlock');
213
- }
214
- /**
215
- * Checks if the provided model element is an `imageInline`.
216
- */
217
- isInlineImage(modelElement) {
218
- return !!modelElement && modelElement.is('element', 'imageInline');
219
- }
220
- /**
221
- * Get the view `<img>` from another view element, e.g. a widget (`<figure class="image">`), a link (`<a>`).
222
- *
223
- * The `<img>` can be located deep in other elements, so this helper performs a deep tree search.
224
- */
225
- findViewImgElement(figureView) {
226
- if (this.isInlineImageView(figureView)) {
227
- return figureView;
228
- }
229
- const editingView = this.editor.editing.view;
230
- for (const { item } of editingView.createRangeIn(figureView)) {
231
- if (this.isInlineImageView(item)) {
232
- return item;
233
- }
234
- }
235
- }
236
- /**
237
- * @inheritDoc
238
- */
239
- destroy() {
240
- this._domEmitter.stopListening();
241
- return super.destroy();
242
- }
243
- }
244
- /**
245
- * Checks if image is allowed by schema in optimal insertion parent.
246
- */
247
- function isImageAllowedInParent(editor, selection) {
248
- const imageType = determineImageTypeForInsertion(editor, selection, null);
249
- if (imageType == 'imageBlock') {
250
- const parent = getInsertImageParent(selection, editor.model);
251
- if (editor.model.schema.checkChild(parent, 'imageBlock')) {
252
- return true;
253
- }
254
- }
255
- else if (editor.model.schema.checkChild(selection.focus, 'imageInline')) {
256
- return true;
257
- }
258
- return false;
259
- }
260
- /**
261
- * Checks if selection is not placed inside an image (e.g. its caption).
262
- */
263
- function isNotInsideImage(selection) {
264
- return [...selection.focus.getAncestors()].every(ancestor => !ancestor.is('element', 'imageBlock'));
265
- }
266
- /**
267
- * Returns a node that will be used to insert image with `model.insertContent`.
268
- */
269
- function getInsertImageParent(selection, model) {
270
- const insertionRange = findOptimalInsertionRange(selection, model);
271
- const parent = insertionRange.start.parent;
272
- if (parent.isEmpty && !parent.is('element', '$root')) {
273
- return parent.parent;
274
- }
275
- return parent;
276
- }
277
- /**
278
- * Determine image element type name depending on editor config or place of insertion.
279
- *
280
- * @param imageType Image element type name. Used to force return of provided element name,
281
- * but only if there is proper plugin enabled.
282
- */
283
- function determineImageTypeForInsertion(editor, selectable, imageType) {
284
- const schema = editor.model.schema;
285
- const configImageInsertType = editor.config.get('image.insert.type');
286
- if (!editor.plugins.has('ImageBlockEditing')) {
287
- return 'imageInline';
288
- }
289
- if (!editor.plugins.has('ImageInlineEditing')) {
290
- return 'imageBlock';
291
- }
292
- if (imageType) {
293
- return imageType;
294
- }
295
- if (configImageInsertType === 'inline') {
296
- return 'imageInline';
297
- }
298
- if (configImageInsertType === 'block') {
299
- return 'imageBlock';
300
- }
301
- // Try to replace the selected widget (e.g. another image).
302
- if (selectable.is('selection')) {
303
- return determineImageTypeForInsertionAtSelection(schema, selectable);
304
- }
305
- return schema.checkChild(selectable, 'imageInline') ? 'imageInline' : 'imageBlock';
306
- }
1
+ /**
2
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ import { Plugin } from 'ckeditor5/src/core';
6
+ import { findOptimalInsertionRange, isWidget, toWidget } from 'ckeditor5/src/widget';
7
+ import { determineImageTypeForInsertionAtSelection } from './image/utils';
8
+ import { DomEmitterMixin, global } from 'ckeditor5/src/utils';
9
+ const IMAGE_WIDGETS_CLASSES_MATCH_REGEXP = /^(image|image-inline)$/;
10
+ /**
11
+ * A set of helpers related to images.
12
+ */
13
+ export default class ImageUtils extends Plugin {
14
+ constructor() {
15
+ super(...arguments);
16
+ /**
17
+ * DOM Emitter.
18
+ */
19
+ this._domEmitter = new (DomEmitterMixin())();
20
+ }
21
+ /**
22
+ * @inheritDoc
23
+ */
24
+ static get pluginName() {
25
+ return 'ImageUtils';
26
+ }
27
+ /**
28
+ * Checks if the provided model element is an `image` or `imageInline`.
29
+ */
30
+ isImage(modelElement) {
31
+ return this.isInlineImage(modelElement) || this.isBlockImage(modelElement);
32
+ }
33
+ /**
34
+ * Checks if the provided view element represents an inline image.
35
+ *
36
+ * Also, see {@link module:image/imageutils~ImageUtils#isImageWidget}.
37
+ */
38
+ isInlineImageView(element) {
39
+ return !!element && element.is('element', 'img');
40
+ }
41
+ /**
42
+ * Checks if the provided view element represents a block image.
43
+ *
44
+ * Also, see {@link module:image/imageutils~ImageUtils#isImageWidget}.
45
+ */
46
+ isBlockImageView(element) {
47
+ return !!element && element.is('element', 'figure') && element.hasClass('image');
48
+ }
49
+ /**
50
+ * Handles inserting single file. This method unifies image insertion using {@link module:widget/utils~findOptimalInsertionRange}
51
+ * method.
52
+ *
53
+ * ```ts
54
+ * const imageUtils = editor.plugins.get( 'ImageUtils' );
55
+ *
56
+ * imageUtils.insertImage( { src: 'path/to/image.jpg' } );
57
+ * ```
58
+ *
59
+ * @param attributes Attributes of the inserted image.
60
+ * This method filters out the attributes which are disallowed by the {@link module:engine/model/schema~Schema}.
61
+ * @param selectable Place to insert the image. If not specified,
62
+ * the {@link module:widget/utils~findOptimalInsertionRange} logic will be applied for the block images
63
+ * and `model.document.selection` for the inline images.
64
+ *
65
+ * **Note**: If `selectable` is passed, this helper will not be able to set selection attributes (such as `linkHref`)
66
+ * and apply them to the new image. In this case, make sure all selection attributes are passed in `attributes`.
67
+ *
68
+ * @param imageType Image type of inserted image. If not specified,
69
+ * it will be determined automatically depending of editor config or place of the insertion.
70
+ * @param options.setImageSizes Specifies whether the image `width` and `height` attributes should be set automatically.
71
+ * The default is `true`.
72
+ * @return The inserted model image element.
73
+ */
74
+ insertImage(attributes = {}, selectable = null, imageType = null, options = {}) {
75
+ const editor = this.editor;
76
+ const model = editor.model;
77
+ const selection = model.document.selection;
78
+ const determinedImageType = determineImageTypeForInsertion(editor, selectable || selection, imageType);
79
+ // Mix declarative attributes with selection attributes because the new image should "inherit"
80
+ // the latter for best UX. For instance, inline images inserted into existing links
81
+ // should not split them. To do that, they need to have "linkHref" inherited from the selection.
82
+ attributes = {
83
+ ...Object.fromEntries(selection.getAttributes()),
84
+ ...attributes
85
+ };
86
+ for (const attributeName in attributes) {
87
+ if (!model.schema.checkAttribute(determinedImageType, attributeName)) {
88
+ delete attributes[attributeName];
89
+ }
90
+ }
91
+ return model.change(writer => {
92
+ const { setImageSizes = true } = options;
93
+ const imageElement = writer.createElement(determinedImageType, attributes);
94
+ model.insertObject(imageElement, selectable, null, {
95
+ setSelection: 'on',
96
+ // If we want to insert a block image (for whatever reason) then we don't want to split text blocks.
97
+ // This applies only when we don't have the selectable specified (i.e., we insert multiple block images at once).
98
+ findOptimalPosition: !selectable && determinedImageType != 'imageInline' ? 'auto' : undefined
99
+ });
100
+ // Inserting an image might've failed due to schema regulations.
101
+ if (imageElement.parent) {
102
+ if (setImageSizes) {
103
+ this.setImageNaturalSizeAttributes(imageElement);
104
+ }
105
+ return imageElement;
106
+ }
107
+ return null;
108
+ });
109
+ }
110
+ /**
111
+ * Reads original image sizes and sets them as `width` and `height`.
112
+ *
113
+ * The `src` attribute may not be available if the user is using an upload adapter. In such a case,
114
+ * this method is called again after the upload process is complete and the `src` attribute is available.
115
+ */
116
+ setImageNaturalSizeAttributes(imageElement) {
117
+ const src = imageElement.getAttribute('src');
118
+ if (!src) {
119
+ return;
120
+ }
121
+ if (imageElement.getAttribute('width') || imageElement.getAttribute('height')) {
122
+ return;
123
+ }
124
+ this.editor.model.change(writer => {
125
+ const img = new global.window.Image();
126
+ this._domEmitter.listenTo(img, 'load', () => {
127
+ if (!imageElement.getAttribute('width') && !imageElement.getAttribute('height')) {
128
+ // We use writer.batch to be able to undo (in a single step) width and height setting
129
+ // along with any change that triggered this action (e.g. image resize or image style change).
130
+ this.editor.model.enqueueChange(writer.batch, writer => {
131
+ writer.setAttribute('width', img.naturalWidth, imageElement);
132
+ writer.setAttribute('height', img.naturalHeight, imageElement);
133
+ });
134
+ }
135
+ this._domEmitter.stopListening(img, 'load');
136
+ });
137
+ img.src = src;
138
+ });
139
+ }
140
+ /**
141
+ * Returns an image widget editing view element if one is selected or is among the selection's ancestors.
142
+ */
143
+ getClosestSelectedImageWidget(selection) {
144
+ const selectionPosition = selection.getFirstPosition();
145
+ if (!selectionPosition) {
146
+ return null;
147
+ }
148
+ const viewElement = selection.getSelectedElement();
149
+ if (viewElement && this.isImageWidget(viewElement)) {
150
+ return viewElement;
151
+ }
152
+ let parent = selectionPosition.parent;
153
+ while (parent) {
154
+ if (parent.is('element') && this.isImageWidget(parent)) {
155
+ return parent;
156
+ }
157
+ parent = parent.parent;
158
+ }
159
+ return null;
160
+ }
161
+ /**
162
+ * Returns a image model element if one is selected or is among the selection's ancestors.
163
+ */
164
+ getClosestSelectedImageElement(selection) {
165
+ const selectedElement = selection.getSelectedElement();
166
+ return this.isImage(selectedElement) ? selectedElement : selection.getFirstPosition().findAncestor('imageBlock');
167
+ }
168
+ /**
169
+ * Returns an image widget editing view based on the passed image view.
170
+ */
171
+ getImageWidgetFromImageView(imageView) {
172
+ return imageView.findAncestor({ classes: IMAGE_WIDGETS_CLASSES_MATCH_REGEXP });
173
+ }
174
+ /**
175
+ * Checks if image can be inserted at current model selection.
176
+ *
177
+ * @internal
178
+ */
179
+ isImageAllowed() {
180
+ const model = this.editor.model;
181
+ const selection = model.document.selection;
182
+ return isImageAllowedInParent(this.editor, selection) && isNotInsideImage(selection);
183
+ }
184
+ /**
185
+ * Converts a given {@link module:engine/view/element~Element} to an image widget:
186
+ * * Adds a {@link module:engine/view/element~Element#_setCustomProperty custom property} allowing to recognize the image widget
187
+ * element.
188
+ * * Calls the {@link module:widget/utils~toWidget} function with the proper element's label creator.
189
+ *
190
+ * @param writer An instance of the view writer.
191
+ * @param label The element's label. It will be concatenated with the image `alt` attribute if one is present.
192
+ */
193
+ toImageWidget(viewElement, writer, label) {
194
+ writer.setCustomProperty('image', true, viewElement);
195
+ const labelCreator = () => {
196
+ const imgElement = this.findViewImgElement(viewElement);
197
+ const altText = imgElement.getAttribute('alt');
198
+ return altText ? `${altText} ${label}` : label;
199
+ };
200
+ return toWidget(viewElement, writer, { label: labelCreator });
201
+ }
202
+ /**
203
+ * Checks if a given view element is an image widget.
204
+ */
205
+ isImageWidget(viewElement) {
206
+ return !!viewElement.getCustomProperty('image') && isWidget(viewElement);
207
+ }
208
+ /**
209
+ * Checks if the provided model element is an `image`.
210
+ */
211
+ isBlockImage(modelElement) {
212
+ return !!modelElement && modelElement.is('element', 'imageBlock');
213
+ }
214
+ /**
215
+ * Checks if the provided model element is an `imageInline`.
216
+ */
217
+ isInlineImage(modelElement) {
218
+ return !!modelElement && modelElement.is('element', 'imageInline');
219
+ }
220
+ /**
221
+ * Get the view `<img>` from another view element, e.g. a widget (`<figure class="image">`), a link (`<a>`).
222
+ *
223
+ * The `<img>` can be located deep in other elements, so this helper performs a deep tree search.
224
+ */
225
+ findViewImgElement(figureView) {
226
+ if (this.isInlineImageView(figureView)) {
227
+ return figureView;
228
+ }
229
+ const editingView = this.editor.editing.view;
230
+ for (const { item } of editingView.createRangeIn(figureView)) {
231
+ if (this.isInlineImageView(item)) {
232
+ return item;
233
+ }
234
+ }
235
+ }
236
+ /**
237
+ * @inheritDoc
238
+ */
239
+ destroy() {
240
+ this._domEmitter.stopListening();
241
+ return super.destroy();
242
+ }
243
+ }
244
+ /**
245
+ * Checks if image is allowed by schema in optimal insertion parent.
246
+ */
247
+ function isImageAllowedInParent(editor, selection) {
248
+ const imageType = determineImageTypeForInsertion(editor, selection, null);
249
+ if (imageType == 'imageBlock') {
250
+ const parent = getInsertImageParent(selection, editor.model);
251
+ if (editor.model.schema.checkChild(parent, 'imageBlock')) {
252
+ return true;
253
+ }
254
+ }
255
+ else if (editor.model.schema.checkChild(selection.focus, 'imageInline')) {
256
+ return true;
257
+ }
258
+ return false;
259
+ }
260
+ /**
261
+ * Checks if selection is not placed inside an image (e.g. its caption).
262
+ */
263
+ function isNotInsideImage(selection) {
264
+ return [...selection.focus.getAncestors()].every(ancestor => !ancestor.is('element', 'imageBlock'));
265
+ }
266
+ /**
267
+ * Returns a node that will be used to insert image with `model.insertContent`.
268
+ */
269
+ function getInsertImageParent(selection, model) {
270
+ const insertionRange = findOptimalInsertionRange(selection, model);
271
+ const parent = insertionRange.start.parent;
272
+ if (parent.isEmpty && !parent.is('element', '$root')) {
273
+ return parent.parent;
274
+ }
275
+ return parent;
276
+ }
277
+ /**
278
+ * Determine image element type name depending on editor config or place of insertion.
279
+ *
280
+ * @param imageType Image element type name. Used to force return of provided element name,
281
+ * but only if there is proper plugin enabled.
282
+ */
283
+ function determineImageTypeForInsertion(editor, selectable, imageType) {
284
+ const schema = editor.model.schema;
285
+ const configImageInsertType = editor.config.get('image.insert.type');
286
+ if (!editor.plugins.has('ImageBlockEditing')) {
287
+ return 'imageInline';
288
+ }
289
+ if (!editor.plugins.has('ImageInlineEditing')) {
290
+ return 'imageBlock';
291
+ }
292
+ if (imageType) {
293
+ return imageType;
294
+ }
295
+ if (configImageInsertType === 'inline') {
296
+ return 'imageInline';
297
+ }
298
+ if (configImageInsertType !== 'auto') {
299
+ return 'imageBlock';
300
+ }
301
+ // Try to replace the selected widget (e.g. another image).
302
+ if (selectable.is('selection')) {
303
+ return determineImageTypeForInsertionAtSelection(schema, selectable);
304
+ }
305
+ return schema.checkChild(selectable, 'imageInline') ? 'imageInline' : 'imageBlock';
306
+ }