UNPKG

ckeditor5-image-upload-base64

Version:

The development environment of CKEditor 5 – the best browser-based rich text editor.

822 lines (702 loc) 21.5 kB
/** * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ /** * @module table/tablecellproperties/ui/tablecellpropertiesview */ import View from '@ckeditor/ckeditor5-ui/src/view'; import ViewCollection from '@ckeditor/ckeditor5-ui/src/viewcollection'; import submitHandler from '@ckeditor/ckeditor5-ui/src/bindings/submithandler'; import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler'; import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker'; import FocusCycler from '@ckeditor/ckeditor5-ui/src/focuscycler'; import LabeledFieldView from '@ckeditor/ckeditor5-ui/src/labeledfield/labeledfieldview'; import { createLabeledDropdown, createLabeledInputText } from '@ckeditor/ckeditor5-ui/src/labeledfield/utils'; import LabelView from '@ckeditor/ckeditor5-ui/src/label/labelview'; import { addListToDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils'; import ToolbarView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarview'; import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; import { fillToolbar, getBorderStyleDefinitions, getBorderStyleLabels, getLabeledColorInputCreator } from '../../utils/ui/table-properties'; import FormRowView from '../../ui/formrowview'; import FormHeaderView from '@ckeditor/ckeditor5-ui/src/formheader/formheaderview'; import checkIcon from '@ckeditor/ckeditor5-core/theme/icons/check.svg'; import cancelIcon from '@ckeditor/ckeditor5-core/theme/icons/cancel.svg'; import alignLeftIcon from '@ckeditor/ckeditor5-core/theme/icons/align-left.svg'; import alignRightIcon from '@ckeditor/ckeditor5-core/theme/icons/align-right.svg'; import alignCenterIcon from '@ckeditor/ckeditor5-core/theme/icons/align-center.svg'; import alignJustifyIcon from '@ckeditor/ckeditor5-core/theme/icons/align-justify.svg'; import alignTopIcon from '@ckeditor/ckeditor5-core/theme/icons/align-top.svg'; import alignMiddleIcon from '@ckeditor/ckeditor5-core/theme/icons/align-middle.svg'; import alignBottomIcon from '@ckeditor/ckeditor5-core/theme/icons/align-bottom.svg'; import '../../../theme/form.css'; import '../../../theme/tableform.css'; import '../../../theme/tablecellproperties.css'; const ALIGNMENT_ICONS = { left: alignLeftIcon, center: alignCenterIcon, right: alignRightIcon, justify: alignJustifyIcon, top: alignTopIcon, middle: alignMiddleIcon, bottom: alignBottomIcon }; /** * The class representing a table cell properties form, allowing users to customize * certain style aspects of a table cell, for instance, border, padding, text alignment, etc.. * * @extends module:ui/view~View */ export default class TableCellPropertiesView extends View { /** * @param {module:utils/locale~Locale} locale The {@link module:core/editor/editor~Editor#locale} instance. * @param {Object} options Additional configuration of the view. * @param {module:table/table~TableColorConfig} options.borderColors A configuration of the border * color palette used by the * {@link module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView#borderColorInput}. * @param {module:table/table~TableColorConfig} options.backgroundColors A configuration of the background * color palette used by the * {@link module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView#backgroundInput}. */ constructor( locale, options ) { super( locale ); this.set( { /** * The value of the cell border style. * * @observable * @default '' * @member #borderStyle */ borderStyle: '', /** * The value of the cell border width style. * * @observable * @default '' * @member #borderWidth */ borderWidth: '', /** * The value of the cell border color style. * * @observable * @default '' * @member #borderColor */ borderColor: '', /** * The value of the cell padding style. * * @observable * @default '' * @member #padding */ padding: '', /** * The value of the cell background color style. * * @observable * @default '' * @member #backgroundColor */ backgroundColor: '', /** * The value of the table cell width style. * * @observable * @default '' * @member #width */ width: '', /** * The value of the table cell height style. * * @observable * @default '' * @member #height */ height: '', /** * The value of the horizontal text alignment style. * * @observable * @default '' * @member #horizontalAlignment */ horizontalAlignment: '', /** * The value of the vertical text alignment style. * * @observable * @default '' * @member #verticalAlignment */ verticalAlignment: '' } ); /** * Options passed to the view. See {@link #constructor} to learn more. * * @member {Object} */ this.options = options; const { borderStyleDropdown, borderWidthInput, borderColorInput, borderRowLabel } = this._createBorderFields(); const { widthInput, operatorLabel, heightInput, dimensionsLabel } = this._createDimensionFields(); const { horizontalAlignmentToolbar, verticalAlignmentToolbar, alignmentLabel } = this._createAlignmentFields(); /** * Tracks information about the DOM focus in the form. * * @readonly * @member {module:utils/focustracker~FocusTracker} */ this.focusTracker = new FocusTracker(); /** * An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}. * * @readonly * @member {module:utils/keystrokehandler~KeystrokeHandler} */ this.keystrokes = new KeystrokeHandler(); /** * A collection of child views in the form. * * @readonly * @type {module:ui/viewcollection~ViewCollection} */ this.children = this.createCollection(); /** * A dropdown that allows selecting the style of the table cell border. * * @readonly * @member {module:ui/dropdown/dropdownview~DropdownView} */ this.borderStyleDropdown = borderStyleDropdown; /** * An input that allows specifying the width of the table cell border. * * @readonly * @member {module:ui/inputtext/inputtextview~InputTextView} */ this.borderWidthInput = borderWidthInput; /** * An input that allows specifying the color of the table cell border. * * @readonly * @member {module:table/ui/colorinputview~ColorInputView} */ this.borderColorInput = borderColorInput; /** * An input that allows specifying the table cell background color. * * @readonly * @member {module:table/ui/colorinputview~ColorInputView} */ this.backgroundInput = this._createBackgroundField(); /** * An input that allows specifying the table cell padding. * * @readonly * @member {module:ui/inputtext/inputtextview~InputTextView} */ this.paddingInput = this._createPaddingField(); /** * An input that allows specifying the table cell width. * * @readonly * @member {module:ui/inputtext/inputtextview~InputTextView} */ this.widthInput = widthInput; /** * An input that allows specifying the table cell height. * * @readonly * @member {module:ui/inputtext/inputtextview~InputTextView} */ this.heightInput = heightInput; /** * A toolbar with buttons that allow changing the horizontal text alignment in a table cell. * * @readonly * @member {module:ui/toolbar/toolbar~ToolbarView} */ this.horizontalAlignmentToolbar = horizontalAlignmentToolbar; /** * A toolbar with buttons that allow changing the vertical text alignment in a table cell. * * @readonly * @member {module:ui/toolbar/toolbar~ToolbarView} */ this.verticalAlignmentToolbar = verticalAlignmentToolbar; // Defer creating to make sure other fields are present and the Save button can // bind its #isEnabled to their error messages so there's no way to save unless all // fields are valid. const { saveButtonView, cancelButtonView } = this._createActionButtons(); /** * The "Save" button view. * * @member {module:ui/button/buttonview~ButtonView} */ this.saveButtonView = saveButtonView; /** * The "Cancel" button view. * * @member {module:ui/button/buttonview~ButtonView} */ this.cancelButtonView = cancelButtonView; /** * A collection of views that can be focused in the form. * * @readonly * @protected * @member {module:ui/viewcollection~ViewCollection} */ this._focusables = new ViewCollection(); /** * Helps cycling over {@link #_focusables} in the form. * * @readonly * @protected * @member {module:ui/focuscycler~FocusCycler} */ this._focusCycler = new FocusCycler( { focusables: this._focusables, focusTracker: this.focusTracker, keystrokeHandler: this.keystrokes, actions: { // Navigate form fields backwards using the Shift + Tab keystroke. focusPrevious: 'shift + tab', // Navigate form fields forwards using the Tab key. focusNext: 'tab' } } ); // Form header. this.children.add( new FormHeaderView( locale, { label: this.t( 'Cell properties' ) } ) ); // Border row. this.children.add( new FormRowView( locale, { labelView: borderRowLabel, children: [ borderRowLabel, borderStyleDropdown, borderColorInput, borderWidthInput ], class: 'ck-table-form__border-row' } ) ); // Background. this.children.add( new FormRowView( locale, { children: [ this.backgroundInput ] } ) ); // Dimensions row and padding. this.children.add( new FormRowView( locale, { children: [ // Dimensions row. new FormRowView( locale, { labelView: dimensionsLabel, children: [ dimensionsLabel, widthInput, operatorLabel, heightInput ], class: 'ck-table-form__dimensions-row' } ), // Padding row. new FormRowView( locale, { children: [ this.paddingInput ], class: 'ck-table-cell-properties-form__padding-row' } ) ] } ) ); // Text alignment row. this.children.add( new FormRowView( locale, { labelView: alignmentLabel, children: [ alignmentLabel, horizontalAlignmentToolbar, verticalAlignmentToolbar ], class: 'ck-table-cell-properties-form__alignment-row' } ) ); // Action row. this.children.add( new FormRowView( locale, { children: [ this.saveButtonView, this.cancelButtonView ], class: 'ck-table-form__action-row' } ) ); this.setTemplate( { tag: 'form', attributes: { class: [ 'ck', 'ck-form', 'ck-table-form', 'ck-table-cell-properties-form' ], // https://github.com/ckeditor/ckeditor5-link/issues/90 tabindex: '-1' }, children: this.children } ); } /** * @inheritDoc */ render() { super.render(); // Enable the "submit" event for this view. It can be triggered by the #saveButtonView // which is of the "submit" DOM "type". submitHandler( { view: this } ); [ this.borderStyleDropdown, this.borderColorInput, this.borderWidthInput, this.backgroundInput, this.widthInput, this.heightInput, this.paddingInput, this.horizontalAlignmentToolbar, this.verticalAlignmentToolbar, this.saveButtonView, this.cancelButtonView ].forEach( view => { // Register the view as focusable. this._focusables.add( view ); // Register the view in the focus tracker. this.focusTracker.add( view.element ); } ); // Mainly for closing using "Esc" and navigation using "Tab". this.keystrokes.listenTo( this.element ); } /** * Focuses the fist focusable field in the form. */ focus() { this._focusCycler.focusFirst(); } /** * Creates the following form fields: * * * {@link #borderStyleDropdown}, * * {@link #borderWidthInput}, * * {@link #borderColorInput}. * * @private * @returns {Object.<String,module:ui/view~View>} */ _createBorderFields() { const colorInputCreator = getLabeledColorInputCreator( { colorConfig: this.options.borderColors, columns: 5 } ); const locale = this.locale; const t = this.t; // -- Group label --------------------------------------------- const borderRowLabel = new LabelView( locale ); borderRowLabel.text = t( 'Border' ); // -- Style --------------------------------------------------- const styleLabels = getBorderStyleLabels( t ); const borderStyleDropdown = new LabeledFieldView( locale, createLabeledDropdown ); borderStyleDropdown.set( { label: t( 'Style' ), class: 'ck-table-form__border-style' } ); borderStyleDropdown.fieldView.buttonView.set( { isOn: false, withText: true, tooltip: t( 'Style' ) } ); borderStyleDropdown.fieldView.buttonView.bind( 'label' ).to( this, 'borderStyle', value => { return styleLabels[ value ? value : 'none' ]; } ); borderStyleDropdown.fieldView.on( 'execute', evt => { this.borderStyle = evt.source._borderStyleValue; } ); addListToDropdown( borderStyleDropdown.fieldView, getBorderStyleDefinitions( this ) ); // -- Width --------------------------------------------------- const borderWidthInput = new LabeledFieldView( locale, createLabeledInputText ); borderWidthInput.set( { label: t( 'Width' ), class: 'ck-table-form__border-width' } ); borderWidthInput.fieldView.bind( 'value' ).to( this, 'borderWidth' ); borderWidthInput.bind( 'isEnabled' ).to( this, 'borderStyle', isBorderStyleSet ); borderWidthInput.fieldView.on( 'input', () => { this.borderWidth = borderWidthInput.fieldView.element.value; } ); // -- Color --------------------------------------------------- const borderColorInput = new LabeledFieldView( locale, colorInputCreator ); borderColorInput.set( { label: t( 'Color' ), class: 'ck-table-form__border-color' } ); borderColorInput.fieldView.bind( 'value' ).to( this, 'borderColor' ); borderColorInput.bind( 'isEnabled' ).to( this, 'borderStyle', isBorderStyleSet ); borderColorInput.fieldView.on( 'input', () => { this.borderColor = borderColorInput.fieldView.value; } ); // Reset the border color and width fields when style is "none". // https://github.com/ckeditor/ckeditor5/issues/6227 this.on( 'change:borderStyle', ( evt, name, value ) => { if ( !isBorderStyleSet( value ) ) { this.borderColor = ''; this.borderWidth = ''; } } ); return { borderRowLabel, borderStyleDropdown, borderColorInput, borderWidthInput }; } /** * Creates the following form fields: * * * {@link #backgroundInput}. * * @private * @returns {module:ui/labeledfield/labeledfieldview~LabeledFieldView} */ _createBackgroundField() { const locale = this.locale; const t = this.t; const colorInputCreator = getLabeledColorInputCreator( { colorConfig: this.options.backgroundColors, columns: 5 } ); const backgroundInput = new LabeledFieldView( locale, colorInputCreator ); backgroundInput.set( { label: t( 'Background' ), class: 'ck-table-cell-properties-form__background' } ); backgroundInput.fieldView.bind( 'value' ).to( this, 'backgroundColor' ); backgroundInput.fieldView.on( 'input', () => { this.backgroundColor = backgroundInput.fieldView.value; } ); return backgroundInput; } /** * Creates the following form fields: * * * {@link #widthInput}. * * {@link #heightInput}. * * @private * @returns {module:ui/labeledfield/labeledfieldview~LabeledFieldView} */ _createDimensionFields() { const locale = this.locale; const t = this.t; // -- Label --------------------------------------------------- const dimensionsLabel = new LabelView( locale ); dimensionsLabel.text = t( 'Dimensions' ); // -- Width --------------------------------------------------- const widthInput = new LabeledFieldView( locale, createLabeledInputText ); widthInput.set( { label: t( 'Width' ), class: 'ck-table-form__dimensions-row__width' } ); widthInput.fieldView.bind( 'value' ).to( this, 'width' ); widthInput.fieldView.on( 'input', () => { this.width = widthInput.fieldView.element.value; } ); // -- Operator --------------------------------------------------- const operatorLabel = new View( locale ); operatorLabel.setTemplate( { tag: 'span', attributes: { class: [ 'ck-table-form__dimension-operator' ] }, children: [ { text: '×' } ] } ); // -- Height --------------------------------------------------- const heightInput = new LabeledFieldView( locale, createLabeledInputText ); heightInput.set( { label: t( 'Height' ), class: 'ck-table-form__dimensions-row__height' } ); heightInput.fieldView.bind( 'value' ).to( this, 'height' ); heightInput.fieldView.on( 'input', () => { this.height = heightInput.fieldView.element.value; } ); return { dimensionsLabel, widthInput, operatorLabel, heightInput }; } /** * Creates the following form fields: * * * {@link #paddingInput}. * * @private * @returns {module:ui/labeledfield/labeledfieldview~LabeledFieldView} */ _createPaddingField() { const locale = this.locale; const t = this.t; const paddingInput = new LabeledFieldView( locale, createLabeledInputText ); paddingInput.set( { label: t( 'Padding' ), class: 'ck-table-cell-properties-form__padding' } ); paddingInput.fieldView.bind( 'value' ).to( this, 'padding' ); paddingInput.fieldView.on( 'input', () => { this.padding = paddingInput.fieldView.element.value; } ); return paddingInput; } /** * Creates the following form fields: * * * {@link #horizontalAlignmentToolbar}, * * {@link #verticalAlignmentToolbar}. * * @private * @returns {Object.<String,module:ui/view~View>} */ _createAlignmentFields() { const locale = this.locale; const t = this.t; const alignmentLabel = new LabelView( locale ); alignmentLabel.text = t( 'Table cell text alignment' ); // -- Horizontal --------------------------------------------------- const horizontalAlignmentToolbar = new ToolbarView( locale ); const isContentRTL = this.locale.contentLanguageDirection === 'rtl'; horizontalAlignmentToolbar.set( { isCompact: true, ariaLabel: t( 'Horizontal text alignment toolbar' ) } ); fillToolbar( { view: this, icons: ALIGNMENT_ICONS, toolbar: horizontalAlignmentToolbar, labels: this._horizontalAlignmentLabels, propertyName: 'horizontalAlignment', nameToValue: name => { return name === ( isContentRTL ? 'right' : 'left' ) ? '' : name; } } ); // -- Vertical ----------------------------------------------------- const verticalAlignmentToolbar = new ToolbarView( locale ); verticalAlignmentToolbar.set( { isCompact: true, ariaLabel: t( 'Vertical text alignment toolbar' ) } ); fillToolbar( { view: this, icons: ALIGNMENT_ICONS, toolbar: verticalAlignmentToolbar, labels: this._verticalAlignmentLabels, propertyName: 'verticalAlignment', nameToValue: name => { return name === 'middle' ? '' : name; } } ); return { horizontalAlignmentToolbar, verticalAlignmentToolbar, alignmentLabel }; } /** * Creates the following form controls: * * * {@link #saveButtonView}, * * {@link #cancelButtonView}. * * @private * @returns {Object.<String,module:ui/view~View>} */ _createActionButtons() { const locale = this.locale; const t = this.t; const saveButtonView = new ButtonView( locale ); const cancelButtonView = new ButtonView( locale ); const fieldsThatShouldValidateToSave = [ this.borderWidthInput, this.borderColorInput, this.backgroundInput, this.paddingInput ]; saveButtonView.set( { label: t( 'Save' ), icon: checkIcon, class: 'ck-button-save', type: 'submit', withText: true } ); saveButtonView.bind( 'isEnabled' ).toMany( fieldsThatShouldValidateToSave, 'errorText', ( ...errorTexts ) => { return errorTexts.every( errorText => !errorText ); } ); cancelButtonView.set( { label: t( 'Cancel' ), icon: cancelIcon, class: 'ck-button-cancel', type: 'cancel', withText: true } ); cancelButtonView.delegate( 'execute' ).to( this, 'cancel' ); return { saveButtonView, cancelButtonView }; } /** * Provides localized labels for {@link #horizontalAlignmentToolbar} buttons. * * @private * @type {Object.<String,String>} */ get _horizontalAlignmentLabels() { const locale = this.locale; const t = this.t; const left = t( 'Align cell text to the left' ); const center = t( 'Align cell text to the center' ); const right = t( 'Align cell text to the right' ); const justify = t( 'Justify cell text' ); // Returns object with a proper order of labels. if ( locale.uiLanguageDirection === 'rtl' ) { return { right, center, left, justify }; } else { return { left, center, right, justify }; } } /** * Provides localized labels for {@link #verticalAlignmentToolbar} buttons. * * @private * @type {Object.<String,String>} */ get _verticalAlignmentLabels() { const t = this.t; return { top: t( 'Align cell text to the top' ), middle: t( 'Align cell text to the middle' ), bottom: t( 'Align cell text to the bottom' ) }; } } function isBorderStyleSet( value ) { return !!value; }