UNPKG

ckeditor5-image-upload-base64

Version:

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

229 lines (194 loc) 6.13 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 image/imagetextalternative/imagetextalternativeui */ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; import clickOutsideHandler from '@ckeditor/ckeditor5-ui/src/bindings/clickoutsidehandler'; import TextAlternativeFormView from './ui/textalternativeformview'; import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon'; import textAlternativeIcon from '@ckeditor/ckeditor5-core/theme/icons/low-vision.svg'; import { repositionContextualBalloon, getBalloonPositionData } from '../image/ui/utils'; import { getSelectedImageWidget } from '../image/utils'; /** * The image text alternative UI plugin. * * The plugin uses the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon}. * * @extends module:core/plugin~Plugin */ export default class ImageTextAlternativeUI extends Plugin { /** * @inheritDoc */ static get requires() { return [ ContextualBalloon ]; } /** * @inheritDoc */ static get pluginName() { return 'ImageTextAlternativeUI'; } /** * @inheritDoc */ init() { this._createButton(); this._createForm(); } /** * @inheritDoc */ destroy() { super.destroy(); // Destroy created UI components as they are not automatically destroyed (see ckeditor5#1341). this._form.destroy(); } /** * Creates a button showing the balloon panel for changing the image text alternative and * registers it in the editor {@link module:ui/componentfactory~ComponentFactory ComponentFactory}. * * @private */ _createButton() { const editor = this.editor; const t = editor.t; editor.ui.componentFactory.add( 'imageTextAlternative', locale => { const command = editor.commands.get( 'imageTextAlternative' ); const view = new ButtonView( locale ); view.set( { label: t( 'Change image text alternative' ), icon: textAlternativeIcon, tooltip: true } ); view.bind( 'isEnabled' ).to( command, 'isEnabled' ); this.listenTo( view, 'execute', () => { this._showForm(); } ); return view; } ); } /** * Creates the {@link module:image/imagetextalternative/ui/textalternativeformview~TextAlternativeFormView} * form. * * @private */ _createForm() { const editor = this.editor; const view = editor.editing.view; const viewDocument = view.document; /** * The contextual balloon plugin instance. * * @private * @member {module:ui/panel/balloon/contextualballoon~ContextualBalloon} */ this._balloon = this.editor.plugins.get( 'ContextualBalloon' ); /** * A form containing a textarea and buttons, used to change the `alt` text value. * * @member {module:image/imagetextalternative/ui/textalternativeformview~TextAlternativeFormView} */ this._form = new TextAlternativeFormView( editor.locale ); // Render the form so its #element is available for clickOutsideHandler. this._form.render(); this.listenTo( this._form, 'submit', () => { editor.execute( 'imageTextAlternative', { newValue: this._form.labeledInput.fieldView.element.value } ); this._hideForm( true ); } ); this.listenTo( this._form, 'cancel', () => { this._hideForm( true ); } ); // Close the form on Esc key press. this._form.keystrokes.set( 'Esc', ( data, cancel ) => { this._hideForm( true ); cancel(); } ); // Reposition the balloon or hide the form if an image widget is no longer selected. this.listenTo( editor.ui, 'update', () => { if ( !getSelectedImageWidget( viewDocument.selection ) ) { this._hideForm( true ); } else if ( this._isVisible ) { repositionContextualBalloon( editor ); } } ); // Close on click outside of balloon panel element. clickOutsideHandler( { emitter: this._form, activator: () => this._isVisible, contextElements: [ this._balloon.view.element ], callback: () => this._hideForm() } ); } /** * Shows the {@link #_form} in the {@link #_balloon}. * * @private */ _showForm() { if ( this._isVisible ) { return; } const editor = this.editor; const command = editor.commands.get( 'imageTextAlternative' ); const labeledInput = this._form.labeledInput; if ( !this._isInBalloon ) { this._balloon.add( { view: this._form, position: getBalloonPositionData( editor ) } ); } // Make sure that each time the panel shows up, the field remains in sync with the value of // the command. If the user typed in the input, then canceled the balloon (`labeledInput#value` // stays unaltered) and re-opened it without changing the value of the command, they would see the // old value instead of the actual value of the command. // https://github.com/ckeditor/ckeditor5-image/issues/114 labeledInput.fieldView.value = labeledInput.fieldView.element.value = command.value || ''; this._form.labeledInput.fieldView.select(); } /** * Removes the {@link #_form} from the {@link #_balloon}. * * @param {Boolean} [focusEditable=false] Controls whether the editing view is focused afterwards. * @private */ _hideForm( focusEditable ) { if ( !this._isInBalloon ) { return; } // Blur the input element before removing it from DOM to prevent issues in some browsers. // See https://github.com/ckeditor/ckeditor5/issues/1501. if ( this._form.focusTracker.isFocused ) { this._form.saveButtonView.focus(); } this._balloon.remove( this._form ); if ( focusEditable ) { this.editor.editing.view.focus(); } } /** * Returns `true` when the {@link #_form} is the visible view in the {@link #_balloon}. * * @private * @type {Boolean} */ get _isVisible() { return this._balloon.visibleView === this._form; } /** * Returns `true` when the {@link #_form} is in the {@link #_balloon}. * * @private * @type {Boolean} */ get _isInBalloon() { return this._balloon.hasView( this._form ); } }