UNPKG

5.19 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/imagestyleediting
8 */
9
10import { Plugin } from 'ckeditor5/src/core';
11import ImageStyleCommand from './imagestylecommand';
12import ImageUtils from '../imageutils';
13import utils from './utils';
14import { viewToModelStyleAttribute, modelToViewStyleAttribute } from './converters';
15
16/**
17 * The image style engine plugin. It sets the default configuration, creates converters and registers
18 * {@link module:image/imagestyle/imagestylecommand~ImageStyleCommand ImageStyleCommand}.
19 *
20 * @extends module:core/plugin~Plugin
21 */
22export default class ImageStyleEditing extends Plugin {
23 /**
24 * @inheritDoc
25 */
26 static get pluginName() {
27 return 'ImageStyleEditing';
28 }
29
30 /**
31 * @inheritDoc
32 */
33 static get requires() {
34 return [ ImageUtils ];
35 }
36
37 /**
38 * @inheritDoc
39 */
40 init() {
41 const { normalizeStyles, getDefaultStylesConfiguration } = utils;
42 const editor = this.editor;
43 const isBlockPluginLoaded = editor.plugins.has( 'ImageBlockEditing' );
44 const isInlinePluginLoaded = editor.plugins.has( 'ImageInlineEditing' );
45
46 editor.config.define( 'image.styles', getDefaultStylesConfiguration( isBlockPluginLoaded, isInlinePluginLoaded ) );
47
48 /**
49 * It contains a list of the normalized and validated style options.
50 *
51 * * Each option contains a complete icon markup.
52 * * The style options not supported by any of the loaded image editing plugins (
53 * {@link module:image/image/imageinlineediting~ImageInlineEditing `ImageInlineEditing`} or
54 * {@link module:image/image/imageblockediting~ImageBlockEditing `ImageBlockEditing`}) are filtered out.
55 *
56 * @protected
57 * @readonly
58 * @type {module:image/imagestyle~ImageStyleConfig}
59 */
60 this.normalizedStyles = normalizeStyles( {
61 configuredStyles: editor.config.get( 'image.styles' ),
62 isBlockPluginLoaded,
63 isInlinePluginLoaded
64 } );
65
66 this._setupConversion( isBlockPluginLoaded, isInlinePluginLoaded );
67 this._setupPostFixer();
68
69 // Register imageStyle command.
70 editor.commands.add( 'imageStyle', new ImageStyleCommand( editor, this.normalizedStyles ) );
71 }
72
73 /**
74 * Sets the editor conversion taking the presence of
75 * {@link module:image/image/imageinlineediting~ImageInlineEditing `ImageInlineEditing`}
76 * and {@link module:image/image/imageblockediting~ImageBlockEditing `ImageBlockEditing`} plugins into consideration.
77 *
78 * @private
79 * @param {Boolean} isBlockPluginLoaded
80 * @param {Boolean} isInlinePluginLoaded
81 */
82 _setupConversion( isBlockPluginLoaded, isInlinePluginLoaded ) {
83 const editor = this.editor;
84 const schema = editor.model.schema;
85
86 const modelToViewConverter = modelToViewStyleAttribute( this.normalizedStyles );
87 const viewToModelConverter = viewToModelStyleAttribute( this.normalizedStyles );
88
89 editor.editing.downcastDispatcher.on( 'attribute:imageStyle', modelToViewConverter );
90 editor.data.downcastDispatcher.on( 'attribute:imageStyle', modelToViewConverter );
91
92 // Allow imageStyle attribute in image and imageInline.
93 // We could call it 'style' but https://github.com/ckeditor/ckeditor5-engine/issues/559.
94 if ( isBlockPluginLoaded ) {
95 schema.extend( 'imageBlock', { allowAttributes: 'imageStyle' } );
96
97 // Converter for figure element from view to model.
98 editor.data.upcastDispatcher.on( 'element:figure', viewToModelConverter, { priority: 'low' } );
99 }
100
101 if ( isInlinePluginLoaded ) {
102 schema.extend( 'imageInline', { allowAttributes: 'imageStyle' } );
103
104 // Converter for the img element from view to model.
105 editor.data.upcastDispatcher.on( 'element:img', viewToModelConverter, { priority: 'low' } );
106 }
107 }
108
109 /**
110 * Registers a post-fixer that will make sure that the style attribute value is correct for a specific image type (block vs inline).
111 *
112 * @private
113 */
114 _setupPostFixer() {
115 const editor = this.editor;
116 const document = editor.model.document;
117
118 const imageUtils = editor.plugins.get( ImageUtils );
119 const stylesMap = new Map( this.normalizedStyles.map( style => [ style.name, style ] ) );
120
121 // Make sure that style attribute is valid for the image type.
122 document.registerPostFixer( writer => {
123 let changed = false;
124
125 for ( const change of document.differ.getChanges() ) {
126 if ( change.type == 'insert' || change.type == 'attribute' && change.attributeKey == 'imageStyle' ) {
127 let element = change.type == 'insert' ? change.position.nodeAfter : change.range.start.nodeAfter;
128
129 if ( element && element.is( 'element', 'paragraph' ) && element.childCount > 0 ) {
130 element = element.getChild( 0 );
131 }
132
133 if ( !imageUtils.isImage( element ) ) {
134 continue;
135 }
136
137 const imageStyle = element.getAttribute( 'imageStyle' );
138
139 if ( !imageStyle ) {
140 continue;
141 }
142
143 const imageStyleDefinition = stylesMap.get( imageStyle );
144
145 if ( !imageStyleDefinition || !imageStyleDefinition.modelElements.includes( element.name ) ) {
146 writer.removeAttribute( 'imageStyle', element );
147 changed = true;
148 }
149 }
150 }
151
152 return changed;
153 } );
154 }
155}