1 | /**
|
2 | * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
|
3 | * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
4 | */
|
5 |
|
6 | /**
|
7 | * @module image/imagestyle/utils
|
8 | */
|
9 |
|
10 | import { icons } from 'ckeditor5/src/core';
|
11 | import { logWarning } from 'ckeditor5/src/utils';
|
12 |
|
13 | const {
|
14 | objectFullWidth,
|
15 | objectInline,
|
16 | objectLeft, objectRight, objectCenter,
|
17 | objectBlockLeft, objectBlockRight
|
18 | } = icons;
|
19 |
|
20 | /**
|
21 | * Default image style options provided by the plugin that can be referred in the {@link module:image/image~ImageConfig#styles}
|
22 | * configuration.
|
23 | *
|
24 | * There are available 5 styles focused on formatting:
|
25 | *
|
26 | * * **`'alignLeft'`** aligns the inline or block image to the left and wraps it with the text using the `image-style-align-left` class,
|
27 | * * **`'alignRight'`** aligns the inline or block image to the right and wraps it with the text using the `image-style-align-right` class,
|
28 | * * **`'alignCenter'`** centers the block image using the `image-style-align-center` class,
|
29 | * * **`'alignBlockLeft'`** aligns the block image to the left using the `image-style-block-align-left` class,
|
30 | * * **`'alignBlockRight'`** aligns the block image to the right using the `image-style-block-align-right` class,
|
31 | *
|
32 | * and 3 semantic styles:
|
33 | *
|
34 | * * **`'inline'`** is an inline image without any CSS class,
|
35 | * * **`'block'`** is a block image without any CSS class,
|
36 | * * **`'side'`** is a block image styled with the `image-style-side` CSS class.
|
37 | *
|
38 | * @readonly
|
39 | * @type {Object.<String,module:image/imagestyle~ImageStyleOptionDefinition>}
|
40 | */
|
41 | const DEFAULT_OPTIONS = {
|
42 | // This style represents an image placed in the line of text.
|
43 | get inline() {
|
44 | return {
|
45 | name: 'inline',
|
46 | title: 'In line',
|
47 | icon: objectInline,
|
48 | modelElements: [ 'imageInline' ],
|
49 | isDefault: true
|
50 | };
|
51 | },
|
52 |
|
53 | // This style represents an image aligned to the left and wrapped with text.
|
54 | get alignLeft() {
|
55 | return {
|
56 | name: 'alignLeft',
|
57 | title: 'Left aligned image',
|
58 | icon: objectLeft,
|
59 | modelElements: [ 'imageBlock', 'imageInline' ],
|
60 | className: 'image-style-align-left'
|
61 | };
|
62 | },
|
63 |
|
64 | // This style represents an image aligned to the left.
|
65 | get alignBlockLeft() {
|
66 | return {
|
67 | name: 'alignBlockLeft',
|
68 | title: 'Left aligned image',
|
69 | icon: objectBlockLeft,
|
70 | modelElements: [ 'imageBlock' ],
|
71 | className: 'image-style-block-align-left'
|
72 | };
|
73 | },
|
74 |
|
75 | // This style represents a centered image.
|
76 | get alignCenter() {
|
77 | return {
|
78 | name: 'alignCenter',
|
79 | title: 'Centered image',
|
80 | icon: objectCenter,
|
81 | modelElements: [ 'imageBlock' ],
|
82 | className: 'image-style-align-center'
|
83 | };
|
84 | },
|
85 |
|
86 | // This style represents an image aligned to the right and wrapped with text.
|
87 | get alignRight() {
|
88 | return {
|
89 | name: 'alignRight',
|
90 | title: 'Right aligned image',
|
91 | icon: objectRight,
|
92 | modelElements: [ 'imageBlock', 'imageInline' ],
|
93 | className: 'image-style-align-right'
|
94 | };
|
95 | },
|
96 |
|
97 | // This style represents an image aligned to the right.
|
98 | get alignBlockRight() {
|
99 | return {
|
100 | name: 'alignBlockRight',
|
101 | title: 'Right aligned image',
|
102 | icon: objectBlockRight,
|
103 | modelElements: [ 'imageBlock' ],
|
104 | className: 'image-style-block-align-right'
|
105 | };
|
106 | },
|
107 |
|
108 | // This option is equal to the situation when no style is applied.
|
109 | get block() {
|
110 | return {
|
111 | name: 'block',
|
112 | title: 'Centered image',
|
113 | icon: objectCenter,
|
114 | modelElements: [ 'imageBlock' ],
|
115 | isDefault: true
|
116 | };
|
117 | },
|
118 |
|
119 | // This represents a side image.
|
120 | get side() {
|
121 | return {
|
122 | name: 'side',
|
123 | title: 'Side image',
|
124 | icon: objectRight,
|
125 | modelElements: [ 'imageBlock' ],
|
126 | className: 'image-style-side'
|
127 | };
|
128 | }
|
129 | };
|
130 |
|
131 | /**
|
132 | * Default image style icons provided by the plugin that can be referred in the {@link module:image/image~ImageConfig#styles}
|
133 | * configuration.
|
134 | *
|
135 | * See {@link module:image/imagestyle~ImageStyleOptionDefinition#icon} to learn more.
|
136 | *
|
137 | * There are 7 default icons available: `'full'`, `'left'`, `'inlineLeft'`, `'center'`, `'right'`, `'inlineRight'`, and `'inline'`.
|
138 | *
|
139 | * @readonly
|
140 | * @type {Object.<String,String>}
|
141 | */
|
142 | const DEFAULT_ICONS = {
|
143 | full: objectFullWidth,
|
144 | left: objectBlockLeft,
|
145 | right: objectBlockRight,
|
146 | center: objectCenter,
|
147 | inlineLeft: objectLeft,
|
148 | inlineRight: objectRight,
|
149 | inline: objectInline
|
150 | };
|
151 |
|
152 | /**
|
153 | * Default drop-downs provided by the plugin that can be referred in the {@link module:image/image~ImageConfig#toolbar}
|
154 | * configuration. The drop-downs are containers for the {@link module:image/imagestyle~ImageStyleConfig#options image style options}.
|
155 | *
|
156 | * If both of the `ImageEditing` plugins are loaded, there are 2 predefined drop-downs available:
|
157 | *
|
158 | * * **`'imageStyle:wrapText'`**, which contains the `alignLeft` and `alignRight` options, that is,
|
159 | * those that wraps the text around the image,
|
160 | * * **`'imageStyle:breakText'`**, which contains the `alignBlockLeft`, `alignCenter` and `alignBlockRight` options, that is,
|
161 | * those that breaks the text around the image.
|
162 | *
|
163 | * @readonly
|
164 | * @type {Array.<module:image/imagestyle/imagestyleui~ImageStyleDropdownDefinition>}
|
165 | */
|
166 | const DEFAULT_DROPDOWN_DEFINITIONS = [ {
|
167 | name: 'imageStyle:wrapText',
|
168 | title: 'Wrap text',
|
169 | defaultItem: 'imageStyle:alignLeft',
|
170 | items: [ 'imageStyle:alignLeft', 'imageStyle:alignRight' ]
|
171 | }, {
|
172 | name: 'imageStyle:breakText',
|
173 | title: 'Break text',
|
174 | defaultItem: 'imageStyle:block',
|
175 | items: [ 'imageStyle:alignBlockLeft', 'imageStyle:block', 'imageStyle:alignBlockRight' ]
|
176 | } ];
|
177 |
|
178 | /**
|
179 | * Returns a list of the normalized and validated image style options.
|
180 | *
|
181 | * @protected
|
182 | * @param {Object} config
|
183 | * @param {Boolean} config.isInlinePluginLoaded
|
184 | * Determines whether the {@link module:image/image/imageblockediting~ImageBlockEditing `ImageBlockEditing`} plugin has been loaded.
|
185 | * @param {Boolean} config.isBlockPluginLoaded
|
186 | * Determines whether the {@link module:image/image/imageinlineediting~ImageInlineEditing `ImageInlineEditing`} plugin has been loaded.
|
187 | * @param {module:image/imagestyle~ImageStyleConfig} config.configuredStyles
|
188 | * The image styles configuration provided in the image styles {@link module:image/image~ImageConfig#styles configuration}
|
189 | * as a default or custom value.
|
190 | * @returns {module:image/imagestyle~ImageStyleConfig}
|
191 | * * Each of options contains a complete icon markup.
|
192 | * * The image style options not supported by any of the loaded plugins are filtered out.
|
193 | */
|
194 | function normalizeStyles( config ) {
|
195 | const configuredStyles = config.configuredStyles.options || [];
|
196 |
|
197 | const styles = configuredStyles
|
198 | .map( arrangement => normalizeDefinition( arrangement ) )
|
199 | .filter( arrangement => isValidOption( arrangement, config ) );
|
200 |
|
201 | return styles;
|
202 | }
|
203 |
|
204 | /**
|
205 | * Returns the default image styles configuration depending on the loaded image editing plugins.
|
206 | * @protected
|
207 | *
|
208 | * @param {Boolean} isInlinePluginLoaded
|
209 | * Determines whether the {@link module:image/image/imageblockediting~ImageBlockEditing `ImageBlockEditing`} plugin has been loaded.
|
210 | *
|
211 | * @param {Boolean} isBlockPluginLoaded
|
212 | * Determines whether the {@link module:image/image/imageinlineediting~ImageInlineEditing `ImageInlineEditing`} plugin has been loaded.
|
213 | *
|
214 | * @returns {Object<String,Array>}
|
215 | * It returns an object with the lists of the image style options and groups defined as strings related to the
|
216 | * {@link module:image/imagestyle/utils~DEFAULT_OPTIONS default options}
|
217 | */
|
218 | function getDefaultStylesConfiguration( isBlockPluginLoaded, isInlinePluginLoaded ) {
|
219 | if ( isBlockPluginLoaded && isInlinePluginLoaded ) {
|
220 | return {
|
221 | options: [
|
222 | 'inline', 'alignLeft', 'alignRight',
|
223 | 'alignCenter', 'alignBlockLeft', 'alignBlockRight',
|
224 | 'block', 'side'
|
225 | ]
|
226 | };
|
227 | } else if ( isBlockPluginLoaded ) {
|
228 | return {
|
229 | options: [ 'block', 'side' ]
|
230 | };
|
231 | } else if ( isInlinePluginLoaded ) {
|
232 | return {
|
233 | options: [ 'inline', 'alignLeft', 'alignRight' ]
|
234 | };
|
235 | }
|
236 |
|
237 | return {};
|
238 | }
|
239 |
|
240 | /**
|
241 | * Returns a list of the available predefined drop-downs' definitions depending on the loaded image editing plugins.
|
242 | * @protected
|
243 | *
|
244 | * @param {module:core/plugincollection~PluginCollection} pluginCollection
|
245 | * @returns {Array.<module:image/imagestyle/imagestyleui~ImageStyleDropdownDefinition>}
|
246 | */
|
247 | function getDefaultDropdownDefinitions( pluginCollection ) {
|
248 | if ( pluginCollection.has( 'ImageBlockEditing' ) && pluginCollection.has( 'ImageInlineEditing' ) ) {
|
249 | return [ ...DEFAULT_DROPDOWN_DEFINITIONS ];
|
250 | } else {
|
251 | return [];
|
252 | }
|
253 | }
|
254 |
|
255 | // Normalizes an image style option or group provided in the {@link module:image/image~ImageConfig#styles}
|
256 | // and returns it in a {@link module:image/imagestyle~ImageStyleOptionDefinition}/
|
257 | //
|
258 | // @param {Object|String} definition
|
259 | //
|
260 | // @returns {module:image/imagestyle~ImageStyleOptionDefinition}}
|
261 | function normalizeDefinition( definition ) {
|
262 | if ( typeof definition === 'string' ) {
|
263 | // Just the name of the style has been passed, but none of the defaults.
|
264 | if ( !DEFAULT_OPTIONS[ definition ] ) {
|
265 | // Normalize the style anyway to prevent errors.
|
266 | definition = { name: definition };
|
267 | }
|
268 | // Just the name of the style has been passed and it's one of the defaults, just use it.
|
269 | // Clone the style to avoid overriding defaults.
|
270 | else {
|
271 | definition = { ...DEFAULT_OPTIONS[ definition ] };
|
272 | }
|
273 | } else {
|
274 | // If an object style has been passed and if the name matches one of the defaults,
|
275 | // extend it with defaults – the user wants to customize a default style.
|
276 | // Note: Don't override the user–defined style object, clone it instead.
|
277 | definition = extendStyle( DEFAULT_OPTIONS[ definition.name ], definition );
|
278 | }
|
279 |
|
280 | // If an icon is defined as a string and correspond with a name
|
281 | // in default icons, use the default icon provided by the plugin.
|
282 | if ( typeof definition.icon === 'string' ) {
|
283 | definition.icon = DEFAULT_ICONS[ definition.icon ] || definition.icon;
|
284 | }
|
285 |
|
286 | return definition;
|
287 | }
|
288 |
|
289 | // Checks if the image style option is valid:
|
290 | // * if it has the modelElements fields defined and filled,
|
291 | // * if the defined modelElements are supported by any of the loaded image editing plugins.
|
292 | // It also displays a console warning these conditions are not met.
|
293 | //
|
294 | // @param {module:image/imagestyle~ImageStyleOptionDefinition} image style option
|
295 | // @param {Object.<String,Boolean>} { isBlockPluginLoaded, isInlinePluginLoaded }
|
296 | //
|
297 | // @returns Boolean
|
298 | function isValidOption( option, { isBlockPluginLoaded, isInlinePluginLoaded } ) {
|
299 | const { modelElements, name } = option;
|
300 |
|
301 | if ( !modelElements || !modelElements.length || !name ) {
|
302 | warnInvalidStyle( { style: option } );
|
303 |
|
304 | return false;
|
305 | } else {
|
306 | const supportedElements = [ isBlockPluginLoaded ? 'imageBlock' : null, isInlinePluginLoaded ? 'imageInline' : null ];
|
307 |
|
308 | // Check if the option is supported by any of the loaded plugins.
|
309 | if ( !modelElements.some( elementName => supportedElements.includes( elementName ) ) ) {
|
310 | /**
|
311 | * In order to work correctly, each image style {@link module:image/imagestyle~ImageStyleOptionDefinition option}
|
312 | * requires specific model elements (also: types of images) to be supported by the editor.
|
313 | *
|
314 | * Model element names to which the image style option can be applied are defined in the
|
315 | * {@link module:image/imagestyle~ImageStyleOptionDefinition#modelElements} property of the style option
|
316 | * definition.
|
317 | *
|
318 | * Explore the warning in the console to find out precisely which option is not supported and which editor plugins
|
319 | * are missing. Make sure these plugins are loaded in your editor to get this image style option working.
|
320 | *
|
321 | * @error image-style-missing-dependency
|
322 | * @param {String} [option] The name of the unsupported option.
|
323 | * @param {String} [missingPlugins] The names of the plugins one of which has to be loaded for the particular option.
|
324 | */
|
325 | logWarning( 'image-style-missing-dependency', {
|
326 | style: option,
|
327 | missingPlugins: modelElements.map( name => name === 'imageBlock' ? 'ImageBlockEditing' : 'ImageInlineEditing' )
|
328 | } );
|
329 |
|
330 | return false;
|
331 | }
|
332 | }
|
333 |
|
334 | return true;
|
335 | }
|
336 |
|
337 | // Extends the default style with a style provided by the developer.
|
338 | // Note: Don't override the custom–defined style object, clone it instead.
|
339 | //
|
340 | // @param {module:image/imagestyle~ImageStyleOptionDefinition} source
|
341 | // @param {Object} style
|
342 | //
|
343 | // @returns {module:image/imagestyle~ImageStyleOptionDefinition}
|
344 | function extendStyle( source, style ) {
|
345 | const extendedStyle = { ...style };
|
346 |
|
347 | for ( const prop in source ) {
|
348 | if ( !Object.prototype.hasOwnProperty.call( style, prop ) ) {
|
349 | extendedStyle[ prop ] = source[ prop ];
|
350 | }
|
351 | }
|
352 |
|
353 | return extendedStyle;
|
354 | }
|
355 |
|
356 | // Displays a console warning with the 'image-style-configuration-definition-invalid' error.
|
357 | // @param {Object} info
|
358 | function warnInvalidStyle( info ) {
|
359 | /**
|
360 | * The image style definition provided in the configuration is invalid.
|
361 | *
|
362 | * Please make sure the definition implements properly one of the following:
|
363 | *
|
364 | * * {@link module:image/imagestyle~ImageStyleOptionDefinition image style option definition},
|
365 | * * {@link module:image/imagestyle/imagestyleui~ImageStyleDropdownDefinition image style dropdown definition}
|
366 | *
|
367 | * @error image-style-configuration-definition-invalid
|
368 | * @param {String} [dropdown] The name of the invalid drop-down
|
369 | * @param {String} [style] The name of the invalid image style option
|
370 | */
|
371 | logWarning( 'image-style-configuration-definition-invalid', info );
|
372 | }
|
373 |
|
374 | export default {
|
375 | normalizeStyles,
|
376 | getDefaultStylesConfiguration,
|
377 | getDefaultDropdownDefinitions,
|
378 | warnInvalidStyle,
|
379 | DEFAULT_OPTIONS,
|
380 | DEFAULT_ICONS,
|
381 | DEFAULT_DROPDOWN_DEFINITIONS
|
382 | };
|