UNPKG

13.4 kBJavaScriptView Raw
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
10import { icons } from 'ckeditor5/src/core';
11import { logWarning } from 'ckeditor5/src/utils';
12
13const {
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 */
41const 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 */
142const 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 */
166const 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 */
194function 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 */
218function 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 */
247function 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}}
261function 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
298function 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}
344function 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
358function 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
374export default {
375 normalizeStyles,
376 getDefaultStylesConfiguration,
377 getDefaultDropdownDefinitions,
378 warnInvalidStyle,
379 DEFAULT_OPTIONS,
380 DEFAULT_ICONS,
381 DEFAULT_DROPDOWN_DEFINITIONS
382};