| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 |
- /**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
- /**
- * @module image/widget/widget
- */
- import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
- import WidgetEngine from './widgetengine';
- import MouseObserver from '@ckeditor/ckeditor5-engine/src/view/observer/mouseobserver';
- import ModelRange from '@ckeditor/ckeditor5-engine/src/model/range';
- import ModelSelection from '@ckeditor/ckeditor5-engine/src/model/selection';
- import ModelElement from '@ckeditor/ckeditor5-engine/src/model/element';
- import { isWidget } from './utils';
- import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
- /**
- * The widget plugin.
- * Adds default {@link module:engine/view/document~Document#event:mousedown mousedown} handling on widget elements.
- *
- * @extends module:core/plugin~Plugin.
- */
- export default class Widget extends Plugin {
- /**
- * @inheritDoc
- */
- static get requires() {
- return [ WidgetEngine ];
- }
- /**
- * @inheritDoc
- */
- init() {
- const viewDocument = this.editor.editing.view;
- // If mouse down is pressed on widget - create selection over whole widget.
- viewDocument.addObserver( MouseObserver );
- this.listenTo( viewDocument, 'mousedown', ( ...args ) => this._onMousedown( ...args ) );
- // Handle custom keydown behaviour.
- this.listenTo( viewDocument, 'keydown', ( ...args ) => this._onKeydown( ...args ), { priority: 'high' } );
- }
- /**
- * Handles {@link module:engine/view/document~Document#event:mousedown mousedown} events on widget elements.
- *
- * @private
- * @param {module:utils/eventinfo~EventInfo} eventInfo
- * @param {module:engine/view/observer/domeventdata~DomEventData} domEventData
- */
- _onMousedown( eventInfo, domEventData ) {
- let widgetElement = domEventData.target;
- const editor = this.editor;
- const viewDocument = editor.editing.view;
- // If target is not a widget element - check if one of the ancestors is.
- if ( !isWidget( widgetElement ) ) {
- widgetElement = widgetElement.findAncestor( element => isWidget( element ) );
- if ( !widgetElement ) {
- return;
- }
- }
- domEventData.preventDefault();
- // Focus editor if is not focused already.
- if ( !viewDocument.isFocused ) {
- viewDocument.focus();
- }
- // Create model selection over widget.
- const modelElement = editor.editing.mapper.toModelElement( widgetElement );
- editor.document.enqueueChanges( ( ) => {
- this._setSelectionOverElement( modelElement );
- } );
- }
- /**
- * Handles {@link module:engine/view/document~Document#event:keydown keydown} events.
- *
- * @private
- * @param {module:utils/eventinfo~EventInfo} eventInfo
- * @param {module:engine/view/observer/domeventdata~DomEventData} domEventData
- */
- _onKeydown( eventInfo, domEventData ) {
- const keyCode = domEventData.keyCode;
- const isForward = keyCode == keyCodes.delete || keyCode == keyCodes.arrowdown || keyCode == keyCodes.arrowright;
- // Checks if delete/backspace or arrow keys were handled and then prevents default event behaviour and stops
- // event propagation.
- if ( ( isDeleteKeyCode( keyCode ) && this._handleDelete( isForward ) ) ||
- ( isArrowKeyCode( keyCode ) && this._handleArrowKeys( isForward ) ) ) {
- domEventData.preventDefault();
- eventInfo.stop();
- }
- }
- /**
- * Handles delete keys: backspace and delete.
- *
- * @private
- * @param {Boolean} isForward Set to true if delete was performed in forward direction.
- * @returns {Boolean|undefined} Returns `true` if keys were handled correctly.
- */
- _handleDelete( isForward ) {
- const modelDocument = this.editor.document;
- const modelSelection = modelDocument.selection;
- // Do nothing on non-collapsed selection.
- if ( !modelSelection.isCollapsed ) {
- return;
- }
- const objectElement = this._getObjectElementNextToSelection( isForward );
- if ( objectElement ) {
- modelDocument.enqueueChanges( () => {
- // Remove previous element if empty.
- const previousNode = modelSelection.anchor.parent;
- if ( previousNode.isEmpty ) {
- const batch = modelDocument.batch();
- batch.remove( previousNode );
- }
- this._setSelectionOverElement( objectElement );
- } );
- return true;
- }
- }
- /**
- * Handles arrow keys.
- *
- * @param {Boolean} isForward Set to true if arrow key should be handled in forward direction.
- * @returns {Boolean|undefined} Returns `true` if keys were handled correctly.
- */
- _handleArrowKeys( isForward ) {
- const modelDocument = this.editor.document;
- const schema = modelDocument.schema;
- const modelSelection = modelDocument.selection;
- const objectElement = modelSelection.getSelectedElement();
- // if object element is selected.
- if ( objectElement && schema.objects.has( objectElement.name ) ) {
- const position = isForward ? modelSelection.getLastPosition() : modelSelection.getFirstPosition();
- const newRange = modelDocument.getNearestSelectionRange( position, isForward ? 'forward' : 'backward' );
- if ( newRange ) {
- modelDocument.enqueueChanges( () => {
- modelSelection.setRanges( [ newRange ] );
- } );
- }
- return true;
- }
- // If selection is next to object element.
- // Return if not collapsed.
- if ( !modelSelection.isCollapsed ) {
- return;
- }
- const objectElement2 = this._getObjectElementNextToSelection( isForward );
- if ( objectElement2 instanceof ModelElement && modelDocument.schema.objects.has( objectElement2.name ) ) {
- modelDocument.enqueueChanges( () => {
- this._setSelectionOverElement( objectElement2 );
- } );
- return true;
- }
- }
- /**
- * Sets {@link module:engine/model/selection~Selection document's selection} over given element.
- *
- * @private
- * @param {module:engine/model/element~Element} element
- */
- _setSelectionOverElement( element ) {
- this.editor.document.selection.setRanges( [ ModelRange.createOn( element ) ] );
- }
- /**
- * Checks if {@link module:engine/model/element~Element element} placed next to the current
- * {@link module:engine/model/selection~Selection model selection} exists and is marked in
- * {@link module:engine/model/schema~Schema schema} as `object`.
- *
- * @private
- * @param {Boolean} forward Direction of checking.
- * @returns {module:engine/model/element~Element|null}
- */
- _getObjectElementNextToSelection( forward ) {
- const modelDocument = this.editor.document;
- const schema = modelDocument.schema;
- const modelSelection = modelDocument.selection;
- const dataController = this.editor.data;
- // Clone current selection to use it as a probe. We must leave default selection as it is so it can return
- // to its current state after undo.
- const probe = ModelSelection.createFromSelection( modelSelection );
- dataController.modifySelection( probe, { direction: forward ? 'forward' : 'backward' } );
- const objectElement = forward ? probe.focus.nodeBefore : probe.focus.nodeAfter;
- if ( objectElement instanceof ModelElement && schema.objects.has( objectElement.name ) ) {
- return objectElement;
- }
- return null;
- }
- }
- // Returns 'true' if provided key code represents one of the arrow keys.
- //
- // @param {Number} keyCode
- // @returns {Boolean}
- function isArrowKeyCode( keyCode ) {
- return keyCode == keyCodes.arrowright ||
- keyCode == keyCodes.arrowleft ||
- keyCode == keyCodes.arrowup ||
- keyCode == keyCodes.arrowdown;
- }
- //Returns 'true' if provided key code represents one of the delete keys: delete or backspace.
- //
- //@param {Number} keyCode
- //@returns {Boolean}
- function isDeleteKeyCode( keyCode ) {
- return keyCode == keyCodes.delete || keyCode == keyCodes.backspace;
- }
|