@ckeditor/ckeditor5-alignment
Version:
Text alignment feature for CKEditor 5.
154 lines (153 loc) • 4.96 kB
JavaScript
/**
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module alignment/alignmentediting
*/
import { Plugin } from 'ckeditor5/src/core.js';
import AlignmentCommand from './alignmentcommand.js';
import { isDefault, isSupported, normalizeAlignmentOptions, supportedOptions } from './utils.js';
/**
* The alignment editing feature. It introduces the {@link module:alignment/alignmentcommand~AlignmentCommand command} and adds
* the `alignment` attribute for block elements in the {@link module:engine/model/model~Model model}.
*/
export default class AlignmentEditing extends Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'AlignmentEditing';
}
/**
* @inheritDoc
*/
static get isOfficialPlugin() {
return true;
}
/**
* @inheritDoc
*/
constructor(editor) {
super(editor);
editor.config.define('alignment', {
options: supportedOptions.map(option => ({ name: option }))
});
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const locale = editor.locale;
const schema = editor.model.schema;
const options = normalizeAlignmentOptions(editor.config.get('alignment.options'));
// Filter out unsupported options and those that are redundant, e.g. `left` in LTR / `right` in RTL mode.
const optionsToConvert = options.filter(option => isSupported(option.name) && !isDefault(option.name, locale));
// Once there is at least one `className` defined, we switch to alignment with classes.
const shouldUseClasses = optionsToConvert.some(option => !!option.className);
// Allow alignment attribute on all blocks.
schema.extend('$block', { allowAttributes: 'alignment' });
editor.model.schema.setAttributeProperties('alignment', { isFormatting: true });
if (shouldUseClasses) {
editor.conversion.attributeToAttribute(buildClassDefinition(optionsToConvert));
}
else {
// Downcast inline styles.
editor.conversion.for('downcast').attributeToAttribute(buildDowncastInlineDefinition(optionsToConvert));
}
const upcastInlineDefinitions = buildUpcastInlineDefinitions(optionsToConvert);
// Always upcast from inline styles.
for (const definition of upcastInlineDefinitions) {
editor.conversion.for('upcast').attributeToAttribute(definition);
}
const upcastCompatibilityDefinitions = buildUpcastCompatibilityDefinitions(optionsToConvert);
// Always upcast from deprecated `align` attribute.
for (const definition of upcastCompatibilityDefinitions) {
editor.conversion.for('upcast').attributeToAttribute(definition);
}
editor.commands.add('alignment', new AlignmentCommand(editor));
}
}
/**
* Prepare downcast conversion definition for inline alignment styling.
*/
function buildDowncastInlineDefinition(options) {
const view = {};
for (const { name } of options) {
view[name] = {
key: 'style',
value: {
'text-align': name
}
};
}
const definition = {
model: {
key: 'alignment',
values: options.map(option => option.name)
},
view
};
return definition;
}
/**
* Prepare upcast definitions for inline alignment styles.
*/
function buildUpcastInlineDefinitions(options) {
const definitions = [];
for (const { name } of options) {
definitions.push({
view: {
key: 'style',
value: {
'text-align': name
}
},
model: {
key: 'alignment',
value: name
}
});
}
return definitions;
}
/**
* Prepare upcast definitions for deprecated `align` attribute.
*/
function buildUpcastCompatibilityDefinitions(options) {
const definitions = [];
for (const { name } of options) {
definitions.push({
view: {
key: 'align',
value: name
},
model: {
key: 'alignment',
value: name
}
});
}
return definitions;
}
/**
* Prepare conversion definitions for upcast and downcast alignment with classes.
*/
function buildClassDefinition(options) {
const view = {};
for (const option of options) {
view[option.name] = {
key: 'class',
value: option.className
};
}
const definition = {
model: {
key: 'alignment',
values: options.map(option => option.name)
},
view
};
return definition;
}