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
JavaScript
/**
* @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 );
}
}