UNPKG

7.86 kBJavaScriptView Raw
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/**
6 * @module image/imagestyle/imagestyleui
7 */
8import { Plugin } from 'ckeditor5/src/core';
9import { ButtonView, createDropdown, addToolbarToDropdown, SplitButtonView } from 'ckeditor5/src/ui';
10import { isObject, identity } from 'lodash-es';
11import ImageStyleEditing from './imagestyleediting';
12import utils from './utils';
13import '../../theme/imagestyle.css';
14/**
15 * The image style UI plugin.
16 *
17 * It registers buttons corresponding to the {@link module:image/imageconfig~ImageConfig#styles} configuration.
18 * It also registers the {@link module:image/imagestyle/utils#DEFAULT_DROPDOWN_DEFINITIONS default drop-downs} and the
19 * custom drop-downs defined by the developer in the {@link module:image/imageconfig~ImageConfig#toolbar} configuration.
20 */
21export default class ImageStyleUI extends Plugin {
22 /**
23 * @inheritDoc
24 */
25 static get requires() {
26 return [ImageStyleEditing];
27 }
28 /**
29 * @inheritDoc
30 */
31 static get pluginName() {
32 return 'ImageStyleUI';
33 }
34 /**
35 * Returns the default localized style titles provided by the plugin.
36 *
37 * The following localized titles corresponding with
38 * {@link module:image/imagestyle/utils#DEFAULT_OPTIONS} are available:
39 *
40 * * `'Wrap text'`,
41 * * `'Break text'`,
42 * * `'In line'`,
43 * * `'Full size image'`,
44 * * `'Side image'`,
45 * * `'Left aligned image'`,
46 * * `'Centered image'`,
47 * * `'Right aligned image'`
48 */
49 get localizedDefaultStylesTitles() {
50 const t = this.editor.t;
51 return {
52 'Wrap text': t('Wrap text'),
53 'Break text': t('Break text'),
54 'In line': t('In line'),
55 'Full size image': t('Full size image'),
56 'Side image': t('Side image'),
57 'Left aligned image': t('Left aligned image'),
58 'Centered image': t('Centered image'),
59 'Right aligned image': t('Right aligned image')
60 };
61 }
62 /**
63 * @inheritDoc
64 */
65 init() {
66 const plugins = this.editor.plugins;
67 const toolbarConfig = this.editor.config.get('image.toolbar') || [];
68 const imageStyleEditing = plugins.get('ImageStyleEditing');
69 const definedStyles = translateStyles(imageStyleEditing.normalizedStyles, this.localizedDefaultStylesTitles);
70 for (const styleConfig of definedStyles) {
71 this._createButton(styleConfig);
72 }
73 const definedDropdowns = translateStyles([
74 ...toolbarConfig.filter(isObject),
75 ...utils.getDefaultDropdownDefinitions(plugins)
76 ], this.localizedDefaultStylesTitles);
77 for (const dropdownConfig of definedDropdowns) {
78 this._createDropdown(dropdownConfig, definedStyles);
79 }
80 }
81 /**
82 * Creates a dropdown and stores it in the editor {@link module:ui/componentfactory~ComponentFactory}.
83 */
84 _createDropdown(dropdownConfig, definedStyles) {
85 const factory = this.editor.ui.componentFactory;
86 factory.add(dropdownConfig.name, locale => {
87 let defaultButton;
88 const { defaultItem, items, title } = dropdownConfig;
89 const buttonViews = items
90 .filter(itemName => definedStyles.find(({ name }) => getUIComponentName(name) === itemName))
91 .map(buttonName => {
92 const button = factory.create(buttonName);
93 if (buttonName === defaultItem) {
94 defaultButton = button;
95 }
96 return button;
97 });
98 if (items.length !== buttonViews.length) {
99 utils.warnInvalidStyle({ dropdown: dropdownConfig });
100 }
101 const dropdownView = createDropdown(locale, SplitButtonView);
102 const splitButtonView = dropdownView.buttonView;
103 const splitButtonViewArrow = splitButtonView.arrowView;
104 addToolbarToDropdown(dropdownView, buttonViews, { enableActiveItemFocusOnDropdownOpen: true });
105 splitButtonView.set({
106 label: getDropdownButtonTitle(title, defaultButton.label),
107 class: null,
108 tooltip: true
109 });
110 splitButtonViewArrow.unbind('label');
111 splitButtonViewArrow.set({
112 label: title
113 });
114 splitButtonView.bind('icon').toMany(buttonViews, 'isOn', (...areOn) => {
115 const index = areOn.findIndex(identity);
116 return (index < 0) ? defaultButton.icon : buttonViews[index].icon;
117 });
118 splitButtonView.bind('label').toMany(buttonViews, 'isOn', (...areOn) => {
119 const index = areOn.findIndex(identity);
120 return getDropdownButtonTitle(title, (index < 0) ? defaultButton.label : buttonViews[index].label);
121 });
122 splitButtonView.bind('isOn').toMany(buttonViews, 'isOn', (...areOn) => areOn.some(identity));
123 splitButtonView.bind('class')
124 .toMany(buttonViews, 'isOn', (...areOn) => areOn.some(identity) ? 'ck-splitbutton_flatten' : undefined);
125 splitButtonView.on('execute', () => {
126 if (!buttonViews.some(({ isOn }) => isOn)) {
127 defaultButton.fire('execute');
128 }
129 else {
130 dropdownView.isOpen = !dropdownView.isOpen;
131 }
132 });
133 dropdownView.bind('isEnabled')
134 .toMany(buttonViews, 'isEnabled', (...areEnabled) => areEnabled.some(identity));
135 // Focus the editable after executing the command.
136 // Overrides a default behaviour where the focus is moved to the dropdown button (#12125).
137 this.listenTo(dropdownView, 'execute', () => {
138 this.editor.editing.view.focus();
139 });
140 return dropdownView;
141 });
142 }
143 /**
144 * Creates a button and stores it in the editor {@link module:ui/componentfactory~ComponentFactory}.
145 */
146 _createButton(buttonConfig) {
147 const buttonName = buttonConfig.name;
148 this.editor.ui.componentFactory.add(getUIComponentName(buttonName), locale => {
149 const command = this.editor.commands.get('imageStyle');
150 const view = new ButtonView(locale);
151 view.set({
152 label: buttonConfig.title,
153 icon: buttonConfig.icon,
154 tooltip: true,
155 isToggleable: true
156 });
157 view.bind('isEnabled').to(command, 'isEnabled');
158 view.bind('isOn').to(command, 'value', value => value === buttonName);
159 view.on('execute', this._executeCommand.bind(this, buttonName));
160 return view;
161 });
162 }
163 _executeCommand(name) {
164 this.editor.execute('imageStyle', { value: name });
165 this.editor.editing.view.focus();
166 }
167}
168/**
169 * Returns the translated `title` from the passed styles array.
170 */
171function translateStyles(styles, titles) {
172 for (const style of styles) {
173 // Localize the titles of the styles, if a title corresponds with
174 // a localized default provided by the plugin.
175 if (titles[style.title]) {
176 style.title = titles[style.title];
177 }
178 }
179 return styles;
180}
181/**
182 * Returns the image style component name with the "imageStyle:" prefix.
183 */
184function getUIComponentName(name) {
185 return `imageStyle:${name}`;
186}
187/**
188 * Returns title for the splitbutton containing the dropdown title and default action item title.
189 */
190function getDropdownButtonTitle(dropdownTitle, buttonTitle) {
191 return (dropdownTitle ? dropdownTitle + ': ' : '') + buttonTitle;
192}