ckeditor_font-8.x-1.x-dev/js/ckeditor5_plugins/ckeditor5-font/dist/index.js
js/ckeditor5_plugins/ckeditor5-font/dist/index.js
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
import { Command, Plugin } from '@ckeditor/ckeditor5-core/dist/index.js';
import { ColorSelectorView, createDropdown, addListToDropdown, MenuBarMenuView, MenuBarMenuListView, MenuBarMenuListItemView, MenuBarMenuListItemButtonView, ViewModel, normalizeColorOptions, getLocalizedColorOptions, focusChildOnDropdownOpen } from '@ckeditor/ckeditor5-ui/dist/index.js';
import { Collection, CKEditorError } from '@ckeditor/ckeditor5-utils/dist/index.js';
import { isLength, isPercentage, addBackgroundRules } from '@ckeditor/ckeditor5-engine/dist/index.js';
/**
* The base font command.
*/ class FontCommand extends Command {
/**
* A model attribute on which this command operates.
*/ attributeKey;
/**
* Creates an instance of the command.
*
* @param editor Editor instance.
* @param attributeKey The name of a model attribute on which this command operates.
*/ constructor(editor, attributeKey){
super(editor);
this.attributeKey = attributeKey;
}
/**
* @inheritDoc
*/ refresh() {
const model = this.editor.model;
const doc = model.document;
this.value = doc.selection.getAttribute(this.attributeKey);
this.isEnabled = model.schema.checkAttributeInSelection(doc.selection, this.attributeKey);
}
/**
* Executes the command. Applies the `value` of the {@link #attributeKey} to the selection.
* If no `value` is passed, it removes the attribute from the selection.
*
* @param options Options for the executed command.
* @param options.value The value to apply.
* @fires execute
*/ execute(options = {}) {
const model = this.editor.model;
const document = model.document;
const selection = document.selection;
const value = options.value;
const batch = options.batch;
const updateAttribute = (writer)=>{
if (selection.isCollapsed) {
if (value) {
writer.setSelectionAttribute(this.attributeKey, value);
} else {
writer.removeSelectionAttribute(this.attributeKey);
}
} else {
const ranges = model.schema.getValidRanges(selection.getRanges(), this.attributeKey);
for (const range of ranges){
if (value) {
writer.setAttribute(this.attributeKey, value, range);
} else {
writer.removeAttribute(this.attributeKey, range);
}
}
}
};
// In some scenarios, you may want to use a single undo step for multiple changes (e.g. in color picker).
if (batch) {
model.enqueueChange(batch, (writer)=>{
updateAttribute(writer);
});
} else {
model.change((writer)=>{
updateAttribute(writer);
});
}
}
}
/**
* The name of the font size plugin.
*/ const FONT_SIZE = 'fontSize';
/**
* The name of the font family plugin.
*/ const FONT_FAMILY = 'fontFamily';
/**
* The name of the font color plugin.
*/ const FONT_COLOR = 'fontColor';
/**
* The name of the font background color plugin.
*/ const FONT_BACKGROUND_COLOR = 'fontBackgroundColor';
/**
* Builds a proper converter definition out of input data.
*/ function buildDefinition(modelAttributeKey, options) {
const definition = {
model: {
key: modelAttributeKey,
values: []
},
view: {},
upcastAlso: {}
};
for (const option of options){
definition.model.values.push(option.model);
definition.view[option.model] = option.view;
if (option.upcastAlso) {
definition.upcastAlso[option.model] = option.upcastAlso;
}
}
return definition;
}
/**
* A {@link module:font/fontcolor~FontColor font color} and
* {@link module:font/fontbackgroundcolor~FontBackgroundColor font background color} helper
* responsible for upcasting data to the model.
*
* **Note**: The `styleAttr` parameter should be either `'color'` or `'background-color'`.
*/ function renderUpcastAttribute(styleAttr) {
return (viewElement)=>normalizeColorCode(viewElement.getStyle(styleAttr));
}
/**
* A {@link module:font/fontcolor~FontColor font color} and
* {@link module:font/fontbackgroundcolor~FontBackgroundColor font background color} helper
* responsible for downcasting a color attribute to a `<span>` element.
*
* **Note**: The `styleAttr` parameter should be either `'color'` or `'background-color'`.
*/ function renderDowncastElement(styleAttr) {
return (modelAttributeValue, { writer })=>writer.createAttributeElement('span', {
style: `${styleAttr}:${modelAttributeValue}`
}, {
priority: 7
});
}
/**
* A helper that adds {@link module:ui/colorselector/colorselectorview~ColorSelectorView} to the color dropdown with proper initial values.
*
* @param config.dropdownView The dropdown view to which a {@link module:ui/colorselector/colorselectorview~ColorSelectorView}
* will be added.
* @param config.colors An array with definitions representing colors to be displayed in the color selector.
* @param config.removeButtonLabel The label for the button responsible for removing the color.
* @param config.documentColorsLabel The label for the section with document colors.
* @param config.documentColorsCount The number of document colors inside the dropdown.
* @param config.colorPickerViewConfig Configuration of the color picker view.
* @returns The new color selector view.
*/ function addColorSelectorToDropdown({ dropdownView, colors, columns, removeButtonLabel, colorPickerLabel, documentColorsLabel, documentColorsCount, colorPickerViewConfig }) {
const locale = dropdownView.locale;
const colorSelectorView = new ColorSelectorView(locale, {
colors,
columns,
removeButtonLabel,
colorPickerLabel,
documentColorsLabel,
documentColorsCount,
colorPickerViewConfig
});
dropdownView.colorSelectorView = colorSelectorView;
dropdownView.panelView.children.add(colorSelectorView);
return colorSelectorView;
}
/**
* Fixes the color value string.
*/ function normalizeColorCode(value) {
return value.replace(/\s/g, '');
}
/**
* The font family command. It is used by {@link module:font/fontfamily/fontfamilyediting~FontFamilyEditing}
* to apply the font family.
*
* ```ts
* editor.execute( 'fontFamily', { value: 'Arial' } );
* ```
*
* **Note**: Executing the command without the value removes the attribute from the model.
*/ class FontFamilyCommand extends FontCommand {
/**
* @inheritDoc
*/ constructor(editor){
super(editor, FONT_FAMILY);
}
}
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/ /**
* @module font/fontfamily/utils
*/ /**
* Normalizes the {@link module:font/fontconfig~FontFamilyConfig#options configuration options}
* to the {@link module:font/fontconfig~FontFamilyOption} format.
*
* @param configuredOptions An array of options taken from the configuration.
*/ function normalizeOptions$1(configuredOptions) {
// Convert options to objects.
return configuredOptions.map(getOptionDefinition$1)// Filter out undefined values that `getOptionDefinition` might return.
.filter((option)=>option !== undefined);
}
/**
* Returns an option definition either created from string shortcut.
* If object is passed then this method will return it without alternating it. Returns undefined for item than cannot be parsed.
*
*/ function getOptionDefinition$1(option) {
// Treat any object as full item definition provided by user in configuration.
if (typeof option === 'object') {
return option;
}
// Handle 'default' string as a special case. It will be used to remove the fontFamily attribute.
if (option === 'default') {
return {
title: 'Default',
model: undefined
};
}
// Ignore values that we cannot parse to a definition.
if (typeof option !== 'string') {
return undefined;
}
// Return font family definition from font string.
return generateFontPreset(option);
}
/**
* Creates a predefined preset for pixel size. It deconstructs font-family like string into full configuration option.
* A font definition is passed as coma delimited set of font family names. Font names might be quoted.
*
* @param fontDefinition A font definition form configuration.
*/ function generateFontPreset(fontDefinition) {
// Remove quotes from font names. They will be normalized later.
const fontNames = fontDefinition.replace(/"|'/g, '').split(',');
// The first matched font name will be used as dropdown list item title and as model value.
const firstFontName = fontNames[0];
// CSS-compatible font names.
const cssFontNames = fontNames.map(normalizeFontNameForCSS).join(', ');
return {
title: firstFontName,
model: cssFontNames,
view: {
name: 'span',
styles: {
'font-family': cssFontNames
},
priority: 7
}
};
}
/**
* Normalizes font name for the style attribute. It adds braces (') if font name contains spaces.
*/ function normalizeFontNameForCSS(fontName) {
fontName = fontName.trim();
// Compound font names should be quoted.
if (fontName.indexOf(' ') > 0) {
fontName = `'${fontName}'`;
}
return fontName;
}
/**
* The font family editing feature.
*
* It introduces the {@link module:font/fontfamily/fontfamilycommand~FontFamilyCommand command} and
* the `fontFamily` attribute in the {@link module:engine/model/model~Model model} which renders
* in the {@link module:engine/view/view view} as an inline `<span>` element (`<span style="font-family: Arial">`),
* depending on the {@link module:font/fontconfig~FontFamilyConfig configuration}.
*/ class FontFamilyEditing extends Plugin {
/**
* @inheritDoc
*/ static get pluginName() {
return 'FontFamilyEditing';
}
/**
* @inheritDoc
*/ static get isOfficialPlugin() {
return true;
}
/**
* @inheritDoc
*/ constructor(editor){
super(editor);
// Define default configuration using font families shortcuts.
editor.config.define(FONT_FAMILY, {
options: [
'default',
'Arial, Helvetica, sans-serif',
'Courier New, Courier, monospace',
'Georgia, serif',
'Lucida Sans Unicode, Lucida Grande, sans-serif',
'Tahoma, Geneva, sans-serif',
'Times New Roman, Times, serif',
'Trebuchet MS, Helvetica, sans-serif',
'Verdana, Geneva, sans-serif'
],
supportAllValues: false
});
}
/**
* @inheritDoc
*/ init() {
const editor = this.editor;
// Allow fontFamily attribute on text nodes.
editor.model.schema.extend('$text', {
allowAttributes: FONT_FAMILY
});
editor.model.schema.setAttributeProperties(FONT_FAMILY, {
isFormatting: true,
copyOnEnter: true
});
// Get configured font family options without "default" option.
const options = normalizeOptions$1(editor.config.get('fontFamily.options')).filter((item)=>item.model);
const definition = buildDefinition(FONT_FAMILY, options);
// Set-up the two-way conversion.
if (editor.config.get('fontFamily.supportAllValues')) {
this._prepareAnyValueConverters();
this._prepareCompatibilityConverter();
} else {
editor.conversion.attributeToElement(definition);
}
editor.commands.add(FONT_FAMILY, new FontFamilyCommand(editor));
}
/**
* These converters enable keeping any value found as `style="font-family: *"` as a value of an attribute on a text even
* if it is not defined in the plugin configuration.
*/ _prepareAnyValueConverters() {
const editor = this.editor;
editor.conversion.for('downcast').attributeToElement({
model: FONT_FAMILY,
view: (attributeValue, { writer })=>{
return writer.createAttributeElement('span', {
style: 'font-family:' + attributeValue
}, {
priority: 7
});
}
});
editor.conversion.for('upcast').elementToAttribute({
model: {
key: FONT_FAMILY,
value: (viewElement)=>viewElement.getStyle('font-family')
},
view: {
name: 'span',
styles: {
'font-family': /.*/
}
}
});
}
/**
* Adds support for legacy `<font face="..">` formatting.
*/ _prepareCompatibilityConverter() {
const editor = this.editor;
editor.conversion.for('upcast').elementToAttribute({
view: {
name: 'font',
attributes: {
'face': /.*/
}
},
model: {
key: FONT_FAMILY,
value: (viewElement)=>viewElement.getAttribute('face')
}
});
}
}
var fontFamilyIcon = "<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M11.03 3h6.149a.75.75 0 1 1 0 1.5h-5.514L11.03 3zm1.27 3h4.879a.75.75 0 1 1 0 1.5h-4.244L12.3 6zm1.27 3h3.609a.75.75 0 1 1 0 1.5h-2.973L13.57 9zm-2.754 2.5L8.038 4.785 5.261 11.5h5.555zm.62 1.5H4.641l-1.666 4.028H1.312l5.789-14h1.875l5.789 14h-1.663L11.436 13z\"/></svg>";
/**
* The font family UI plugin. It introduces the `'fontFamily'` dropdown.
*/ class FontFamilyUI extends Plugin {
/**
* @inheritDoc
*/ static get pluginName() {
return 'FontFamilyUI';
}
/**
* @inheritDoc
*/ static get isOfficialPlugin() {
return true;
}
/**
* @inheritDoc
*/ init() {
const editor = this.editor;
const t = editor.t;
const options = this._getLocalizedOptions();
const command = editor.commands.get(FONT_FAMILY);
const accessibleLabel = t('Font Family');
const listOptions = _prepareListOptions$1(options, command);
// Register UI component.
editor.ui.componentFactory.add(FONT_FAMILY, (locale)=>{
const dropdownView = createDropdown(locale);
addListToDropdown(dropdownView, listOptions, {
role: 'menu',
ariaLabel: accessibleLabel
});
dropdownView.buttonView.set({
label: accessibleLabel,
icon: fontFamilyIcon,
tooltip: true
});
dropdownView.extendTemplate({
attributes: {
class: 'ck-font-family-dropdown'
}
});
dropdownView.bind('isEnabled').to(command);
// Execute command when an item from the dropdown is selected.
this.listenTo(dropdownView, 'execute', (evt)=>{
editor.execute(evt.source.commandName, {
value: evt.source.commandParam
});
editor.editing.view.focus();
});
return dropdownView;
});
editor.ui.componentFactory.add(`menuBar:${FONT_FAMILY}`, (locale)=>{
const menuView = new MenuBarMenuView(locale);
menuView.buttonView.set({
label: accessibleLabel,
icon: fontFamilyIcon
});
menuView.bind('isEnabled').to(command);
const listView = new MenuBarMenuListView(locale);
for (const definition of listOptions){
const listItemView = new MenuBarMenuListItemView(locale, menuView);
const buttonView = new MenuBarMenuListItemButtonView(locale);
buttonView.set({
role: 'menuitemradio',
isToggleable: true
});
buttonView.bind(...Object.keys(definition.model)).to(definition.model);
buttonView.delegate('execute').to(menuView);
buttonView.on('execute', ()=>{
editor.execute(definition.model.commandName, {
value: definition.model.commandParam
});
editor.editing.view.focus();
});
listItemView.children.add(buttonView);
listView.items.add(listItemView);
}
menuView.panelView.children.add(listView);
return menuView;
});
}
/**
* Returns options as defined in `config.fontFamily.options` but processed to account for
* editor localization, i.e. to display {@link module:font/fontconfig~FontFamilyOption}
* in the correct language.
*
* Note: The reason behind this method is that there is no way to use {@link module:utils/locale~Locale#t}
* when the user configuration is defined because the editor does not exist yet.
*/ _getLocalizedOptions() {
const editor = this.editor;
const t = editor.t;
const options = normalizeOptions$1(editor.config.get(FONT_FAMILY).options);
return options.map((option)=>{
// The only title to localize is "Default" others are font names.
if (option.title === 'Default') {
option.title = t('Default');
}
return option;
});
}
}
/**
* Prepares FontFamily dropdown items.
*/ function _prepareListOptions$1(options, command) {
const itemDefinitions = new Collection();
// Create dropdown items.
for (const option of options){
const def = {
type: 'button',
model: new ViewModel({
commandName: FONT_FAMILY,
commandParam: option.model,
label: option.title,
role: 'menuitemradio',
withText: true
})
};
def.model.bind('isOn').to(command, 'value', (value)=>{
// "Default" or check in strict font-family converters mode.
if (value === option.model) {
return true;
}
if (!value || !option.model) {
return false;
}
return value.split(',')[0].replace(/'/g, '').toLowerCase() === option.model.toLowerCase();
});
// Try to set a dropdown list item style.
if (option.view && typeof option.view !== 'string' && option.view.styles) {
def.model.set('labelStyle', `font-family: ${option.view.styles['font-family']}`);
}
itemDefinitions.add(def);
}
return itemDefinitions;
}
/**
* The font family plugin.
*
* For a detailed overview, check the {@glink features/font font feature} documentatiom
* and the {@glink api/font package page}.
*
* This is a "glue" plugin which loads the {@link module:font/fontfamily/fontfamilyediting~FontFamilyEditing} and
* {@link module:font/fontfamily/fontfamilyui~FontFamilyUI} features in the editor.
*/ class FontFamily extends Plugin {
/**
* @inheritDoc
*/ static get requires() {
return [
FontFamilyEditing,
FontFamilyUI
];
}
/**
* @inheritDoc
*/ static get pluginName() {
return 'FontFamily';
}
/**
* @inheritDoc
*/ static get isOfficialPlugin() {
return true;
}
}
/**
* The font size command. It is used by {@link module:font/fontsize/fontsizeediting~FontSizeEditing}
* to apply the font size.
*
* ```ts
* editor.execute( 'fontSize', { value: 'small' } );
* ```
*
* **Note**: Executing the command without the value removes the attribute from the model.
*/ class FontSizeCommand extends FontCommand {
/**
* @inheritDoc
*/ constructor(editor){
super(editor, FONT_SIZE);
}
}
/**
* Normalizes and translates the {@link module:font/fontconfig~FontSizeConfig#options configuration options}
* to the {@link module:font/fontconfig~FontSizeOption} format.
*
* @param configuredOptions An array of options taken from the configuration.
*/ function normalizeOptions(configuredOptions) {
// Convert options to objects.
return configuredOptions.map((item)=>getOptionDefinition(item))// Filter out undefined values that `getOptionDefinition` might return.
.filter((option)=>option !== undefined);
}
// Default named presets map. Always create a new instance of the preset object in order to avoid modifying references.
const namedPresets = {
get tiny () {
return {
title: 'Tiny',
model: 'tiny',
view: {
name: 'span',
classes: 'text-tiny',
priority: 7
}
};
},
get small () {
return {
title: 'Small',
model: 'small',
view: {
name: 'span',
classes: 'text-small',
priority: 7
}
};
},
get big () {
return {
title: 'Big',
model: 'big',
view: {
name: 'span',
classes: 'text-big',
priority: 7
}
};
},
get huge () {
return {
title: 'Huge',
model: 'huge',
view: {
name: 'span',
classes: 'text-huge',
priority: 7
}
};
}
};
/**
* Returns an option definition either from preset or creates one from number shortcut.
* If object is passed then this method will return it without alternating it. Returns undefined for item than cannot be parsed.
*/ function getOptionDefinition(option) {
if (typeof option === 'number') {
option = String(option);
}
// Check whether passed option is a full item definition provided by user in configuration.
if (typeof option === 'object' && isFullItemDefinition(option)) {
return attachPriority(option);
}
const preset = findPreset(option);
// Item is a named preset.
if (preset) {
return attachPriority(preset);
}
// 'Default' font size. It will be used to remove the fontSize attribute.
if (option === 'default') {
return {
model: undefined,
title: 'Default'
};
}
// At this stage we probably have numerical value to generate a preset so parse it's value.
// Discard any faulty values.
if (isNumericalDefinition(option)) {
return undefined;
}
// Return font size definition from size value.
return generatePixelPreset(option);
}
/**
* Creates a predefined preset for pixel size.
* @param definition Font size in pixels.
* @returns
*/ function generatePixelPreset(definition) {
// Extend a short (numeric value) definition.
if (typeof definition === 'string') {
definition = {
title: definition,
model: `${parseFloat(definition)}px`
};
}
definition.view = {
name: 'span',
styles: {
'font-size': definition.model
}
};
return attachPriority(definition);
}
/**
* Adds the priority to the view element definition if missing. It's required due to ckeditor/ckeditor5#2291
*/ function attachPriority(definition) {
if (definition.view && typeof definition.view !== 'string' && !definition.view.priority) {
definition.view.priority = 7;
}
return definition;
}
/**
* Returns a prepared preset definition. If passed an object, a name of preset should be defined as `model` value.
*
* @param definition.model A preset name.
*/ function findPreset(definition) {
return typeof definition === 'string' ? namedPresets[definition] : namedPresets[definition.model];
}
/**
* We treat `definition` as completed if it is an object that contains `title`, `model` and `view` values.
*/ function isFullItemDefinition(definition) {
return definition.title && definition.model && definition.view;
}
function isNumericalDefinition(definition) {
let numberValue;
if (typeof definition === 'object') {
if (!definition.model) {
/**
* Provided value as an option for {@link module:font/fontsize~FontSize} seems to invalid.
*
* See valid examples described in the {@link module:font/fontconfig~FontSizeConfig#options plugin configuration}.
*
* @error font-size-invalid-definition
*/ throw new CKEditorError('font-size-invalid-definition', null, definition);
} else {
numberValue = parseFloat(definition.model);
}
} else {
numberValue = parseFloat(definition);
}
return isNaN(numberValue);
}
// Mapping of `<font size="..">` styling to CSS's `font-size` values.
const styleFontSize = [
'x-small',
'x-small',
'small',
'medium',
'large',
'x-large',
'xx-large',
'xxx-large'
];
/**
* The font size editing feature.
*
* It introduces the {@link module:font/fontsize/fontsizecommand~FontSizeCommand command} and the `fontSize`
* attribute in the {@link module:engine/model/model~Model model} which renders in the {@link module:engine/view/view view}
* as a `<span>` element with either:
* * a style attribute (`<span style="font-size:12px">...</span>`),
* * or a class attribute (`<span class="text-small">...</span>`)
*
* depending on the {@link module:font/fontconfig~FontSizeConfig configuration}.
*/ class FontSizeEditing extends Plugin {
/**
* @inheritDoc
*/ static get pluginName() {
return 'FontSizeEditing';
}
/**
* @inheritDoc
*/ static get isOfficialPlugin() {
return true;
}
/**
* @inheritDoc
*/ constructor(editor){
super(editor);
// Define default configuration using named presets.
editor.config.define(FONT_SIZE, {
options: [
'tiny',
'small',
'default',
'big',
'huge'
],
supportAllValues: false
});
}
/**
* @inheritDoc
*/ init() {
const editor = this.editor;
// Allow fontSize attribute on text nodes.
editor.model.schema.extend('$text', {
allowAttributes: FONT_SIZE
});
editor.model.schema.setAttributeProperties(FONT_SIZE, {
isFormatting: true,
copyOnEnter: true
});
const supportAllValues = editor.config.get('fontSize.supportAllValues');
// Define view to model conversion.
const options = normalizeOptions(this.editor.config.get('fontSize.options')).filter((item)=>item.model);
const definition = buildDefinition(FONT_SIZE, options);
// Set-up the two-way conversion.
if (supportAllValues) {
this._prepareAnyValueConverters(definition);
this._prepareCompatibilityConverter();
} else {
editor.conversion.attributeToElement(definition);
}
// Add FontSize command.
editor.commands.add(FONT_SIZE, new FontSizeCommand(editor));
}
/**
* These converters enable keeping any value found as `style="font-size: *"` as a value of an attribute on a text even
* if it is not defined in the plugin configuration.
*
* @param definition Converter definition out of input data.
*/ _prepareAnyValueConverters(definition) {
const editor = this.editor;
// If `fontSize.supportAllValues=true`, we do not allow to use named presets in the plugin's configuration.
const presets = definition.model.values.filter((value)=>{
return !isLength(String(value)) && !isPercentage(String(value));
});
if (presets.length) {
/**
* If {@link module:font/fontconfig~FontSizeConfig#supportAllValues `config.fontSize.supportAllValues`} is `true`,
* you need to use numerical values as font size options.
*
* See valid examples described in the {@link module:font/fontconfig~FontSizeConfig#options plugin configuration}.
*
* @error font-size-invalid-use-of-named-presets
* @param {Array.<String>} presets Invalid values.
*/ throw new CKEditorError('font-size-invalid-use-of-named-presets', null, {
presets
});
}
editor.conversion.for('downcast').attributeToElement({
model: FONT_SIZE,
view: (attributeValue, { writer })=>{
if (!attributeValue) {
return;
}
return writer.createAttributeElement('span', {
style: 'font-size:' + attributeValue
}, {
priority: 7
});
}
});
editor.conversion.for('upcast').elementToAttribute({
model: {
key: FONT_SIZE,
value: (viewElement)=>viewElement.getStyle('font-size')
},
view: {
name: 'span',
styles: {
'font-size': /.*/
}
}
});
}
/**
* Adds support for legacy `<font size="..">` formatting.
*/ _prepareCompatibilityConverter() {
const editor = this.editor;
editor.conversion.for('upcast').elementToAttribute({
view: {
name: 'font',
attributes: {
// Documentation mentions sizes from 1 to 7. To handle old content we support all values
// up to 999 but clamp it to the valid range. Why 999? It should cover accidental values
// similar to percentage, e.g. 100%, 200% which could be the usual mistake for font size.
'size': /^[+-]?\d{1,3}$/
}
},
model: {
key: FONT_SIZE,
value: (viewElement)=>{
const value = viewElement.getAttribute('size');
const isRelative = value[0] === '-' || value[0] === '+';
let size = parseInt(value, 10);
if (isRelative) {
// Add relative size (which can be negative) to the default size = 3.
size = 3 + size;
}
const maxSize = styleFontSize.length - 1;
const clampedSize = Math.min(Math.max(size, 0), maxSize);
return styleFontSize[clampedSize];
}
}
});
}
}
var fontSizeIcon = "<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M9.816 11.5 7.038 4.785 4.261 11.5h5.555zm.62 1.5H3.641l-1.666 4.028H.312l5.789-14h1.875l5.789 14h-1.663L10.436 13zm7.55 2.279.779-.779.707.707-2.265 2.265-2.193-2.265.707-.707.765.765V4.825c0-.042 0-.083.002-.123l-.77.77-.707-.707L17.207 2.5l2.265 2.265-.707.707-.782-.782c.002.043.003.089.003.135v10.454z\"/></svg>";
/**
* The font size UI plugin. It introduces the `'fontSize'` dropdown.
*/ class FontSizeUI extends Plugin {
/**
* @inheritDoc
*/ static get pluginName() {
return 'FontSizeUI';
}
/**
* @inheritDoc
*/ static get isOfficialPlugin() {
return true;
}
/**
* @inheritDoc
*/ init() {
const editor = this.editor;
const t = editor.t;
const options = this._getLocalizedOptions();
const command = editor.commands.get(FONT_SIZE);
const accessibleLabel = t('Font Size');
const listOptions = _prepareListOptions(options, command);
// Register UI component.
editor.ui.componentFactory.add(FONT_SIZE, (locale)=>{
const dropdownView = createDropdown(locale);
addListToDropdown(dropdownView, listOptions, {
role: 'menu',
ariaLabel: accessibleLabel
});
// Create dropdown model.
dropdownView.buttonView.set({
label: accessibleLabel,
icon: fontSizeIcon,
tooltip: true
});
dropdownView.extendTemplate({
attributes: {
class: [
'ck-font-size-dropdown'
]
}
});
dropdownView.bind('isEnabled').to(command);
// Execute command when an item from the dropdown is selected.
this.listenTo(dropdownView, 'execute', (evt)=>{
editor.execute(evt.source.commandName, {
value: evt.source.commandParam
});
editor.editing.view.focus();
});
return dropdownView;
});
editor.ui.componentFactory.add(`menuBar:${FONT_SIZE}`, (locale)=>{
const menuView = new MenuBarMenuView(locale);
menuView.buttonView.set({
label: accessibleLabel,
icon: fontSizeIcon
});
menuView.bind('isEnabled').to(command);
const listView = new MenuBarMenuListView(locale);
for (const definition of listOptions){
const listItemView = new MenuBarMenuListItemView(locale, menuView);
const buttonView = new MenuBarMenuListItemButtonView(locale);
buttonView.set({
role: 'menuitemradio',
isToggleable: true
});
buttonView.bind(...Object.keys(definition.model)).to(definition.model);
buttonView.delegate('execute').to(menuView);
buttonView.on('execute', ()=>{
editor.execute(definition.model.commandName, {
value: definition.model.commandParam
});
editor.editing.view.focus();
});
listItemView.children.add(buttonView);
listView.items.add(listItemView);
}
menuView.panelView.children.add(listView);
return menuView;
});
}
/**
* Returns options as defined in `config.fontSize.options` but processed to account for
* editor localization, i.e. to display {@link module:font/fontconfig~FontSizeOption}
* in the correct language.
*
* Note: The reason behind this method is that there is no way to use {@link module:utils/locale~Locale#t}
* when the user configuration is defined because the editor does not exist yet.
*/ _getLocalizedOptions() {
const editor = this.editor;
const t = editor.t;
const localizedTitles = {
Default: t('Default'),
Tiny: t('Tiny'),
Small: t('Small'),
Big: t('Big'),
Huge: t('Huge')
};
const options = normalizeOptions(editor.config.get(FONT_SIZE).options);
return options.map((option)=>{
const title = localizedTitles[option.title];
if (title && title != option.title) {
// Clone the option to avoid altering the original `namedPresets` from `./utils.js`.
option = Object.assign({}, option, {
title
});
}
return option;
});
}
}
/**
* Prepares FontSize dropdown items.
*/ function _prepareListOptions(options, command) {
const itemDefinitions = new Collection();
for (const option of options){
const def = {
type: 'button',
model: new ViewModel({
commandName: FONT_SIZE,
commandParam: option.model,
label: option.title,
class: 'ck-fontsize-option',
role: 'menuitemradio',
withText: true
})
};
if (option.view && typeof option.view !== 'string') {
if (option.view.styles) {
def.model.set('labelStyle', `font-size:${option.view.styles['font-size']}`);
}
if (option.view.classes) {
def.model.set('class', `${def.model.class} ${option.view.classes}`);
}
}
def.model.bind('isOn').to(command, 'value', (value)=>value === option.model);
// Add the option to the collection.
itemDefinitions.add(def);
}
return itemDefinitions;
}
/**
* The font size plugin.
*
* For a detailed overview, check the {@glink features/font font feature} documentation
* and the {@glink api/font package page}.
*
* This is a "glue" plugin which loads the {@link module:font/fontsize/fontsizeediting~FontSizeEditing} and
* {@link module:font/fontsize/fontsizeui~FontSizeUI} features in the editor.
*/ class FontSize extends Plugin {
/**
* @inheritDoc
*/ static get requires() {
return [
FontSizeEditing,
FontSizeUI
];
}
/**
* @inheritDoc
*/ static get pluginName() {
return 'FontSize';
}
/**
* @inheritDoc
*/ static get isOfficialPlugin() {
return true;
}
/**
* Normalizes and translates the {@link module:font/fontconfig~FontSizeConfig#options configuration options}
* to the {@link module:font/fontconfig~FontSizeOption} format.
*
* @param configuredOptions An array of options taken from the configuration.
*/ normalizeSizeOptions(options) {
return normalizeOptions(options);
}
}
/**
* The font color command. It is used by {@link module:font/fontcolor/fontcolorediting~FontColorEditing}
* to apply the font color.
*
* ```ts
* editor.execute( 'fontColor', { value: 'rgb(250, 20, 20)' } );
* ```
*
* **Note**: Executing the command with the `null` value removes the attribute from the model.
*/ class FontColorCommand extends FontCommand {
/**
* @inheritDoc
*/ constructor(editor){
super(editor, FONT_COLOR);
}
}
/**
* The font color editing feature.
*
* It introduces the {@link module:font/fontcolor/fontcolorcommand~FontColorCommand command} and
* the `fontColor` attribute in the {@link module:engine/model/model~Model model} which renders
* in the {@link module:engine/view/view view} as a `<span>` element (`<span style="color: ...">`),
* depending on the {@link module:font/fontconfig~FontColorConfig configuration}.
*/ class FontColorEditing extends Plugin {
/**
* @inheritDoc
*/ static get pluginName() {
return 'FontColorEditing';
}
/**
* @inheritDoc
*/ static get isOfficialPlugin() {
return true;
}
/**
* @inheritDoc
*/ constructor(editor){
super(editor);
editor.config.define(FONT_COLOR, {
colors: [
{
color: 'hsl(0, 0%, 0%)',
label: 'Black'
},
{
color: 'hsl(0, 0%, 30%)',
label: 'Dim grey'
},
{
color: 'hsl(0, 0%, 60%)',
label: 'Grey'
},
{
color: 'hsl(0, 0%, 90%)',
label: 'Light grey'
},
{
color: 'hsl(0, 0%, 100%)',
label: 'White',
hasBorder: true
},
{
color: 'hsl(0, 75%, 60%)',
label: 'Red'
},
{
color: 'hsl(30, 75%, 60%)',
label: 'Orange'
},
{
color: 'hsl(60, 75%, 60%)',
label: 'Yellow'
},
{
color: 'hsl(90, 75%, 60%)',
label: 'Light green'
},
{
color: 'hsl(120, 75%, 60%)',
label: 'Green'
},
{
color: 'hsl(150, 75%, 60%)',
label: 'Aquamarine'
},
{
color: 'hsl(180, 75%, 60%)',
label: 'Turquoise'
},
{
color: 'hsl(210, 75%, 60%)',
label: 'Light blue'
},
{
color: 'hsl(240, 75%, 60%)',
label: 'Blue'
},
{
color: 'hsl(270, 75%, 60%)',
label: 'Purple'
}
],
columns: 5
});
editor.conversion.for('upcast').elementToAttribute({
view: {
name: 'span',
styles: {
'color': /[\s\S]+/
}
},
model: {
key: FONT_COLOR,
value: renderUpcastAttribute('color')
}
});
// Support legacy `<font color="..">` formatting.
editor.conversion.for('upcast').elementToAttribute({
view: {
name: 'font',
attributes: {
'color': /^#?\w+$/
}
},
model: {
key: FONT_COLOR,
value: (viewElement)=>viewElement.getAttribute('color')
}
});
editor.conversion.for('downcast').attributeToElement({
model: FONT_COLOR,
view: renderDowncastElement('color')
});
editor.commands.add(FONT_COLOR, new FontColorCommand(editor));
// Allow the font color attribute on text nodes.
editor.model.schema.extend('$text', {
allowAttributes: FONT_COLOR
});
editor.model.schema.setAttributeProperties(FONT_COLOR, {
isFormatting: true,
copyOnEnter: true
});
}
}
/**
* The color UI plugin which isolates the common logic responsible for displaying dropdowns with color grids.
*
* It is used to create the `'fontBackgroundColor'` and `'fontColor'` dropdowns, each hosting
* a {@link module:ui/colorselector/colorselectorview~ColorSelectorView}.
*/ class ColorUI extends Plugin {
/**
* The name of the command which will be executed when a color tile is clicked.
*/ commandName;
/**
* The name of this component in the {@link module:ui/componentfactory~ComponentFactory}.
* Also the configuration scope name in `editor.config`.
*/ componentName;
/**
* The SVG icon used by the dropdown.
*/ icon;
/**
* The label used by the dropdown.
*/ dropdownLabel;
/**
* The number of columns in the color grid.
*/ columns;
/**
* Creates a plugin which introduces a dropdown with a pre–configured
* {@link module:ui/colorselector/colorselectorview~ColorSelectorView}.
*
* @param config The configuration object.
* @param config.commandName The name of the command which will be executed when a color tile is clicked.
* @param config.componentName The name of the dropdown in the {@link module:ui/componentfactory~ComponentFactory}
* and the configuration scope name in `editor.config`.
* @param config.icon The SVG icon used by the dropdown.
* @param config.dropdownLabel The label used by the dropdown.
*/ constructor(editor, { commandName, componentName, icon, dropdownLabel }){
super(editor);
this.commandName = commandName;
this.componentName = componentName;
this.icon = icon;
this.dropdownLabel = dropdownLabel;
this.columns = editor.config.get(`${this.componentName}.columns`);
}
/**
* @inheritDoc
*/ init() {
const editor = this.editor;
const locale = editor.locale;
const t = locale.t;
const command = editor.commands.get(this.commandName);
const componentConfig = editor.config.get(this.componentName);
const colorsConfig = normalizeColorOptions(componentConfig.colors);
const localizedColors = getLocalizedColorOptions(locale, colorsConfig);
const documentColorsCount = componentConfig.documentColors;
const hasColorPicker = componentConfig.colorPicker !== false;
// Register the UI component.
editor.ui.componentFactory.add(this.componentName, (locale)=>{
const dropdownView = createDropdown(locale);
// Font color dropdown rendering is deferred once it gets open to improve performance (#6192).
let dropdownContentRendered = false;
const colorSelectorView = addColorSelectorToDropdown({
dropdownView,
colors: localizedColors.map((option)=>({
label: option.label,
color: option.model,
options: {
hasBorder: option.hasBorder
}
})),
columns: this.columns,
removeButtonLabel: t('Remove color'),
colorPickerLabel: t('Color picker'),
documentColorsLabel: documentColorsCount !== 0 ? t('Document colors') : '',
documentColorsCount: documentColorsCount === undefined ? this.columns : documentColorsCount,
colorPickerViewConfig: hasColorPicker ? componentConfig.colorPicker || {} : false
});
colorSelectorView.bind('selectedColor').to(command, 'value');
dropdownView.buttonView.set({
label: this.dropdownLabel,
icon: this.icon,
tooltip: true
});
dropdownView.extendTemplate({
attributes: {
class: 'ck-color-ui-dropdown'
}
});
dropdownView.bind('isEnabled').to(command);
colorSelectorView.on('execute', (evt, data)=>{
if (dropdownView.isOpen) {
editor.execute(this.commandName, {
value: data.value,
batch: this._undoStepBatch
});
}
if (data.source !== 'colorPicker') {
editor.editing.view.focus();
}
if (data.source === 'colorPickerSaveButton') {
dropdownView.isOpen = false;
}
});
colorSelectorView.on('colorPicker:show', ()=>{
this._undoStepBatch = editor.model.createBatch();
});
colorSelectorView.on('colorPicker:cancel', ()=>{
if (this._undoStepBatch.operations.length) {
// We need to close the dropdown before the undo batch.
// Otherwise, ColorUI treats undo as a selected color change,
// propagating the update to the whole selection.
// That's an issue if spans with various colors were selected.
dropdownView.isOpen = false;
editor.execute('undo', this._undoStepBatch);
}
editor.editing.view.focus();
});
dropdownView.on('change:isOpen', (evt, name, isVisible)=>{
if (!dropdownContentRendered) {
dropdownContentRendered = true;
dropdownView.colorSelectorView.appendUI();
}
if (isVisible) {
if (documentColorsCount !== 0) {
colorSelectorView.updateDocumentColors(editor.model, this.componentName);
}
colorSelectorView.updateSelectedColors();
colorSelectorView.showColorGridsFragment();
}
});
// Accessibility: focus the first active color when opening the dropdown.
focusChildOnDropdownOpen(dropdownView, ()=>dropdownView.colorSelectorView.colorGridsFragmentView.staticColorsGrid.items.find((item)=>item.isOn));
return dropdownView;
});
// Register menu bar button..
editor.ui.componentFactory.add(`menuBar:${this.componentName}`, (locale)=>{
const menuView = new MenuBarMenuView(locale);
menuView.buttonView.set({
label: this.dropdownLabel,
icon: this.icon
});
menuView.bind('isEnabled').to(command);
// Font color sub-menu rendering is deferred once it gets open to improve performance (#6192).
let contentRendered = false;
const colorSelectorView = new ColorSelectorView(locale, {
colors: localizedColors.map((option)=>({
label: option.label,
color: option.model,
options: {
hasBorder: option.hasBorder
}
})),
columns: this.columns,
removeButtonLabel: t('Remove color'),
colorPickerLabel: t('Color picker'),
documentColorsLabel: documentColorsCount !== 0 ? t('Document colors') : '',
documentColorsCount: documentColorsCount === undefined ? this.columns : documentColorsCount,
colorPickerViewConfig: false
});
colorSelectorView.bind('selectedColor').to(command, 'value');
colorSelectorView.delegate('execute').to(menuView);
colorSelectorView.on('execute', (evt, data)=>{
editor.execute(this.commandName, {
value: data.value,
batch: this._undoStepBatch
});
editor.editing.view.focus();
});
menuView.on('change:isOpen', (evt, name, isVisible)=>{
if (!contentRendered) {
contentRendered = true;
colorSelectorView.appendUI();
}
if (isVisible) {
if (documentColorsCount !== 0) {
colorSelectorView.updateDocumentColors(editor.model, this.componentName);
}
colorSelectorView.updateSelectedColors();
colorSelectorView.showColorGridsFragment();
}
});
menuView.panelView.children.add(colorSelectorView);
return menuView;
});
}
}
var fontColorIcon = "<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.4 10.3 10 4.5l-2.4 5.8h4.8zm.5 1.2H7.1L5.7 15H4.2l5-12h1.6l5 12h-1.5L13 11.5zm3.1 7H4a1 1 0 0 1 0-2h12a1 1 0 0 1 0 2z\"/></svg>";
/**
* The font color UI plugin. It introduces the `'fontColor'` dropdown.
*/ class FontColorUI extends ColorUI {
/**
* @inheritDoc
*/ constructor(editor){
const t = editor.locale.t;
super(editor, {
commandName: FONT_COLOR,
componentName: FONT_COLOR,
icon: fontColorIcon,
dropdownLabel: t('Font Color')
});
}
/**
* @inheritDoc
*/ static get pluginName() {
return 'FontColorUI';
}
}
/**
* The font color plugin.
*
* For a detailed overview, check the {@glink features/font font feature} documentation
* and the {@glink api/font package page}.
*
* This is a "glue" plugin which loads the {@link module:font/fontcolor/fontcolorediting~FontColorEditing} and
* {@link module:font/fontcolor/fontcolorui~FontColorUI} features in the editor.
*/ class FontColor extends Plugin {
/**
* @inheritDoc
*/ static get requires() {
return [
FontColorEditing,
FontColorUI
];
}
/**
* @inheritDoc
*/ static get pluginName() {
return 'FontColor';
}
/**
* @inheritDoc
*/ static get isOfficialPlugin() {
return true;
}
}
/**
* The font background color command. It is used by
* {@link module:font/fontbackgroundcolor/fontbackgroundcolorediting~FontBackgroundColorEditing}
* to apply the font background color.
*
* ```ts
* editor.execute( 'fontBackgroundColor', { value: 'rgb(250, 20, 20)' } );
* ```
*
* **Note**: Executing the command with the `null` value removes the attribute from the model.
*/ class FontBackgroundColorCommand extends FontCommand {
/**
* @inheritDoc
*/ constructor(editor){
super(editor, FONT_BACKGROUND_COLOR);
}
}
/**
* The font background color editing feature.
*
* It introduces the {@link module:font/fontbackgroundcolor/fontbackgroundcolorcommand~FontBackgroundColorCommand command} and
* the `fontBackgroundColor` attribute in the {@link module:engine/model/model~Model model} which renders
* in the {@link module:engine/view/view view} as a `<span>` element (`<span style="background-color: ...">`),
* depending on the {@link module:font/fontconfig~FontColorConfig configuration}.
*/ class FontBackgroundColorEditing extends Plugin {
/**
* @inheritDoc
*/ static get pluginName() {
return 'FontBackgroundColorEditing';
}
/**
* @inheritDoc
*/ static get isOfficialPlugin() {
return true;
}
/**
* @inheritDoc
*/ constructor(editor){
super(editor);
editor.config.define(FONT_BACKGROUND_COLOR, {
colors: [
{
color: 'hsl(0, 0%, 0%)',
label: 'Black'
},
{
color: 'hsl(0, 0%, 30%)',
label: 'Dim grey'
},
{
color: 'hsl(0, 0%, 60%)',
label: 'Grey'
},
{
color: 'hsl(0, 0%, 90%)',
label: 'Light grey'
},
{
color: 'hsl(0, 0%, 100%)',
label: 'White',
hasBorder: true
},
{
color: 'hsl(0, 75%, 60%)',
label: 'Red'
},
{
color: 'hsl(30, 75%, 60%)',
label: 'Orange'
},
{
color: 'hsl(60, 75%, 60%)',
label: 'Yellow'
},
{
color: 'hsl(90, 75%, 60%)',
label: 'Light green'
},
{
color: 'hsl(120, 75%, 60%)',
label: 'Green'
},
{
color: 'hsl(150, 75%, 60%)',
label: 'Aquamarine'
},
{
color: 'hsl(180, 75%, 60%)',
label: 'Turquoise'
},
{
color: 'hsl(210, 75%, 60%)',
label: 'Light blue'
},
{
color: 'hsl(240, 75%, 60%)',
label: 'Blue'
},
{
color: 'hsl(270, 75%, 60%)',
label: 'Purple'
}
],
columns: 5
});
editor.data.addStyleProcessorRules(addBackgroundRules);
editor.conversion.for('upcast').elementToAttribute({
view: {
name: 'span',
styles: {
'background-color': /[\s\S]+/
}
},
model: {
key: FONT_BACKGROUND_COLOR,
value: renderUpcastAttribute('background-color')
}
});
editor.conversion.for('downcast').attributeToElement({
model: FONT_BACKGROUND_COLOR,
view: renderDowncastElement('background-color')
});
editor.commands.add(FONT_BACKGROUND_COLOR, new FontBackgroundColorCommand(editor));
// Allow the font backgroundColor attribute on text nodes.
editor.model.schema.extend('$text', {
allowAttributes: FONT_BACKGROUND_COLOR
});
editor.model.schema.setAttributeProperties(FONT_BACKGROUND_COLOR, {
isFormatting: true,
copyOnEnter: true
});
}
}
var fontBackgroundColorIcon = "<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M4 2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2zm8.38 9.262H7.62L10 5.506l2.38 5.756zm.532 1.285L14.34 16h1.426L10.804 4H9.196L4.234 16H5.66l1.428-3.453h5.824z\"/></svg>";
/**
* The font background color UI plugin. It introduces the `'fontBackgroundColor'` dropdown.
*/ class FontBackgroundColorUI extends ColorUI {
/**
* @inheritDoc
*/ constructor(editor){
const t = editor.locale.t;
super(editor, {
commandName: FONT_BACKGROUND_COLOR,
componentName: FONT_BACKGROUND_COLOR,
icon: fontBackgroundColorIcon,
dropdownLabel: t('Font Background Color')
});
}
/**
* @inheritDoc
*/ static get pluginName() {
return 'FontBackgroundColorUI';
}
}
/**
* The font background color plugin.
*
* For a detailed overview, check the {@glink features/font font feature} documentation
* and the {@glink api/font package page}.
*
* This is a "glue" plugin which loads
* the {@link module:font/fontbackgroundcolor/fontbackgroundcolorediting~FontBackgroundColorEditing} and
* {@link module:font/fontbackgroundcolor/fontbackgroundcolorui~FontBackgroundColorUI} features in the editor.
*/ class FontBackgroundColor extends Plugin {
/**
* @inheritDoc
*/ static get requires() {
return [
FontBackgroundColorEditing,
FontBackgroundColorUI
];
}
/**
* @inheritDoc
*/ static get pluginName() {
return 'FontBackgroundColor';
}
/**
* @inheritDoc
*/ static get isOfficialPlugin() {
return true;
}
}
/**
* A plugin that enables a set of text styling features:
*
* * {@link module:font/fontsize~FontSize},
* * {@link module:font/fontfamily~FontFamily}.
* * {@link module:font/fontcolor~FontColor},
* * {@link module:font/fontbackgroundcolor~FontBackgroundColor}.
*
* For a detailed overview, check the {@glink features/font Font feature} documentation
* and the {@glink api/font package page}.
*/ class Font extends Plugin {
/**
* @inheritDoc
*/ static get requires() {
return [
FontFamily,
FontSize,
FontColor,
FontBackgroundColor
];
}
/**
* @inheritDoc
*/ static get pluginName() {
return 'Font';
}
/**
* @inheritDoc
*/ static get isOfficialPlugin() {
return true;
}
}
export { Font, FontBackgroundColor, FontBackgroundColorEditing, FontBackgroundColorUI, FontColor, FontColorEditing, FontColorUI, FontFamily, FontFamilyEditing, FontFamilyUI, FontSize, FontSizeEditing, FontSizeUI };
//# sourceMappingURL=index.js.map
