@ckeditor/ckeditor5-image
Version:
Image feature for CKEditor 5.
193 lines (192 loc) • 7.86 kB
JavaScript
/**
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imagestyle/imagestyleui
*/
import { Plugin } from 'ckeditor5/src/core';
import { ButtonView, createDropdown, addToolbarToDropdown, SplitButtonView } from 'ckeditor5/src/ui';
import { isObject, identity } from 'lodash-es';
import ImageStyleEditing from './imagestyleediting';
import utils from './utils';
import '../../theme/imagestyle.css';
/**
* The image style UI plugin.
*
* It registers buttons corresponding to the {@link module:image/imageconfig~ImageConfig#styles} configuration.
* It also registers the {@link module:image/imagestyle/utils#DEFAULT_DROPDOWN_DEFINITIONS default drop-downs} and the
* custom drop-downs defined by the developer in the {@link module:image/imageconfig~ImageConfig#toolbar} configuration.
*/
export default class ImageStyleUI extends Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ImageStyleEditing];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageStyleUI';
}
/**
* Returns the default localized style titles provided by the plugin.
*
* The following localized titles corresponding with
* {@link module:image/imagestyle/utils#DEFAULT_OPTIONS} are available:
*
* * `'Wrap text'`,
* * `'Break text'`,
* * `'In line'`,
* * `'Full size image'`,
* * `'Side image'`,
* * `'Left aligned image'`,
* * `'Centered image'`,
* * `'Right aligned image'`
*/
get localizedDefaultStylesTitles() {
const t = this.editor.t;
return {
'Wrap text': t('Wrap text'),
'Break text': t('Break text'),
'In line': t('In line'),
'Full size image': t('Full size image'),
'Side image': t('Side image'),
'Left aligned image': t('Left aligned image'),
'Centered image': t('Centered image'),
'Right aligned image': t('Right aligned image')
};
}
/**
* @inheritDoc
*/
init() {
const plugins = this.editor.plugins;
const toolbarConfig = this.editor.config.get('image.toolbar') || [];
const imageStyleEditing = plugins.get('ImageStyleEditing');
const definedStyles = translateStyles(imageStyleEditing.normalizedStyles, this.localizedDefaultStylesTitles);
for (const styleConfig of definedStyles) {
this._createButton(styleConfig);
}
const definedDropdowns = translateStyles([
...toolbarConfig.filter(isObject),
...utils.getDefaultDropdownDefinitions(plugins)
], this.localizedDefaultStylesTitles);
for (const dropdownConfig of definedDropdowns) {
this._createDropdown(dropdownConfig, definedStyles);
}
}
/**
* Creates a dropdown and stores it in the editor {@link module:ui/componentfactory~ComponentFactory}.
*/
_createDropdown(dropdownConfig, definedStyles) {
const factory = this.editor.ui.componentFactory;
factory.add(dropdownConfig.name, locale => {
let defaultButton;
const { defaultItem, items, title } = dropdownConfig;
const buttonViews = items
.filter(itemName => definedStyles.find(({ name }) => getUIComponentName(name) === itemName))
.map(buttonName => {
const button = factory.create(buttonName);
if (buttonName === defaultItem) {
defaultButton = button;
}
return button;
});
if (items.length !== buttonViews.length) {
utils.warnInvalidStyle({ dropdown: dropdownConfig });
}
const dropdownView = createDropdown(locale, SplitButtonView);
const splitButtonView = dropdownView.buttonView;
const splitButtonViewArrow = splitButtonView.arrowView;
addToolbarToDropdown(dropdownView, buttonViews, { enableActiveItemFocusOnDropdownOpen: true });
splitButtonView.set({
label: getDropdownButtonTitle(title, defaultButton.label),
class: null,
tooltip: true
});
splitButtonViewArrow.unbind('label');
splitButtonViewArrow.set({
label: title
});
splitButtonView.bind('icon').toMany(buttonViews, 'isOn', (...areOn) => {
const index = areOn.findIndex(identity);
return (index < 0) ? defaultButton.icon : buttonViews[index].icon;
});
splitButtonView.bind('label').toMany(buttonViews, 'isOn', (...areOn) => {
const index = areOn.findIndex(identity);
return getDropdownButtonTitle(title, (index < 0) ? defaultButton.label : buttonViews[index].label);
});
splitButtonView.bind('isOn').toMany(buttonViews, 'isOn', (...areOn) => areOn.some(identity));
splitButtonView.bind('class')
.toMany(buttonViews, 'isOn', (...areOn) => areOn.some(identity) ? 'ck-splitbutton_flatten' : undefined);
splitButtonView.on('execute', () => {
if (!buttonViews.some(({ isOn }) => isOn)) {
defaultButton.fire('execute');
}
else {
dropdownView.isOpen = !dropdownView.isOpen;
}
});
dropdownView.bind('isEnabled')
.toMany(buttonViews, 'isEnabled', (...areEnabled) => areEnabled.some(identity));
// Focus the editable after executing the command.
// Overrides a default behaviour where the focus is moved to the dropdown button (#12125).
this.listenTo(dropdownView, 'execute', () => {
this.editor.editing.view.focus();
});
return dropdownView;
});
}
/**
* Creates a button and stores it in the editor {@link module:ui/componentfactory~ComponentFactory}.
*/
_createButton(buttonConfig) {
const buttonName = buttonConfig.name;
this.editor.ui.componentFactory.add(getUIComponentName(buttonName), locale => {
const command = this.editor.commands.get('imageStyle');
const view = new ButtonView(locale);
view.set({
label: buttonConfig.title,
icon: buttonConfig.icon,
tooltip: true,
isToggleable: true
});
view.bind('isEnabled').to(command, 'isEnabled');
view.bind('isOn').to(command, 'value', value => value === buttonName);
view.on('execute', this._executeCommand.bind(this, buttonName));
return view;
});
}
_executeCommand(name) {
this.editor.execute('imageStyle', { value: name });
this.editor.editing.view.focus();
}
}
/**
* Returns the translated `title` from the passed styles array.
*/
function translateStyles(styles, titles) {
for (const style of styles) {
// Localize the titles of the styles, if a title corresponds with
// a localized default provided by the plugin.
if (titles[style.title]) {
style.title = titles[style.title];
}
}
return styles;
}
/**
* Returns the image style component name with the "imageStyle:" prefix.
*/
function getUIComponentName(name) {
return `imageStyle:${name}`;
}
/**
* Returns title for the splitbutton containing the dropdown title and default action item title.
*/
function getDropdownButtonTitle(dropdownTitle, buttonTitle) {
return (dropdownTitle ? dropdownTitle + ': ' : '') + buttonTitle;
}