|
|
@@ -5,11 +5,7 @@
|
|
|
|
|
|
import Feature from '../core/feature.js';
|
|
|
import ImageEngine from './imageengine.js';
|
|
|
-import ViewRange from '../engine/view/range.js';
|
|
|
-import ModelLiveRange from '../engine/model/liverange.js';
|
|
|
import ModelRange from '../engine/model/range.js';
|
|
|
-import ViewPosition from '../engine/view/position.js';
|
|
|
-import ViewSelection from '../engine/view/selection.js';
|
|
|
import MouseDownObserver from './mousedownobserver.js';
|
|
|
|
|
|
export default class Image extends Feature {
|
|
|
@@ -21,135 +17,59 @@ export default class Image extends Feature {
|
|
|
}
|
|
|
|
|
|
init() {
|
|
|
- const editor = this.editor;
|
|
|
- const modelDocument = editor.document;
|
|
|
- const viewDocument = editor.editing.view;
|
|
|
- const editingPipeline = editor.editing;
|
|
|
- const selected = new Set();
|
|
|
+ const viewDocument = this.editor.editing.view;
|
|
|
|
|
|
- // Prevent selection change on mousedown.
|
|
|
+ // If mouse down is pressed create selection over whole view element.
|
|
|
viewDocument.addObserver( MouseDownObserver );
|
|
|
- viewDocument.on( 'mousedown', ( eventInfo, domEventData ) => {
|
|
|
- const target = domEventData.target;
|
|
|
-
|
|
|
- if ( isImageWidget( target ) ) {
|
|
|
- domEventData.preventDefault();
|
|
|
-
|
|
|
- if ( !viewDocument.isFocused ) {
|
|
|
- viewDocument.focus();
|
|
|
- }
|
|
|
-
|
|
|
- const viewRange = ViewRange.createOn( target );
|
|
|
- const modelRange = ModelLiveRange.createFromRange( editingPipeline.mapper.toModelRange( viewRange ) );
|
|
|
-
|
|
|
- modelDocument.enqueueChanges( ( ) => {
|
|
|
- modelDocument.selection.setRanges( [ modelRange ] );
|
|
|
- } );
|
|
|
- }
|
|
|
- } );
|
|
|
-
|
|
|
- // Handle selection placing inside figure - convert it to selecting whole image widget.
|
|
|
- viewDocument.on( 'selectionChange', ( eventInfo, data ) => {
|
|
|
- const newSelection = data.newSelection;
|
|
|
-
|
|
|
- if ( newSelection.rangeCount && newSelection.getFirstPosition().parent.name == 'figure' ) {
|
|
|
- eventInfo.stop();
|
|
|
-
|
|
|
- const viewRange = ViewRange.createOn( newSelection.getFirstPosition().parent );
|
|
|
- const modelRange = ModelLiveRange.createFromRange( editingPipeline.mapper.toModelRange( viewRange ) );
|
|
|
-
|
|
|
- modelDocument.enqueueChanges( ( ) => {
|
|
|
- modelDocument.selection.setRanges( [ modelRange ] );
|
|
|
- } );
|
|
|
- }
|
|
|
- }, { priority: 'high' } );
|
|
|
-
|
|
|
- viewDocument.on( 'keydown', ( eventInfo, data ) => {
|
|
|
- const viewSelection = viewDocument.selection;
|
|
|
-
|
|
|
- if ( viewSelection.isFake && !viewSelection.isCollapsed && viewSelection.rangeCount == 1 ) {
|
|
|
- const nodeAfter = viewSelection.anchor.nodeAfter;
|
|
|
- const nodeBefore = viewSelection.focus.nodeBefore;
|
|
|
-
|
|
|
- if ( nodeAfter === nodeBefore && isImageWidget( nodeAfter ) ) {
|
|
|
- // Stop to prevent default fake selection handling.
|
|
|
- eventInfo.stop();
|
|
|
-
|
|
|
- // Prevent selection change.
|
|
|
- data.preventDefault();
|
|
|
-
|
|
|
- if ( data.keyCode == 39 || data.keyCode == 40 ) {
|
|
|
- // TODO: Check if sibling exists.
|
|
|
- // const modelElement = editingPipeline.mapper.toModelElement( nodeAfter.nextSibling );
|
|
|
- // const modelPosition = ModelPosition.createAt( modelElement );
|
|
|
- // const modelRange = new ModelLiveRange( modelPosition );
|
|
|
- //
|
|
|
- // modelDocument.enqueueChanges( () => {
|
|
|
- // modelDocument.selection.setRanges( [ modelRange ] );
|
|
|
- // } );
|
|
|
- const viewPosition = ViewPosition.createAt( nodeAfter.nextSibling );
|
|
|
- const viewRange = new ViewRange( viewPosition );
|
|
|
- const newSelection = new ViewSelection();
|
|
|
-
|
|
|
- newSelection.addRange( viewRange );
|
|
|
-
|
|
|
- viewDocument.fire( 'selectionChange', {
|
|
|
- oldSelection: viewDocument.selection,
|
|
|
- newSelection: newSelection,
|
|
|
- domSelection: null
|
|
|
- } );
|
|
|
- }
|
|
|
-
|
|
|
- if ( data.keyCode == 37 || data.keyCode == 38 ) {
|
|
|
- // const modelElement = editingPipeline.mapper.toModelElement( nodeAfter.previousSibling );
|
|
|
- // const modelPosition = ModelPosition.createAt( modelElement, 'end' );
|
|
|
- // const modelRange = new ModelLiveRange( modelPosition );
|
|
|
- //
|
|
|
- // modelDocument.enqueueChanges( () => {
|
|
|
- // modelDocument.selection.setRanges( [ modelRange ] );
|
|
|
- // } );
|
|
|
-
|
|
|
- const viewPosition = ViewPosition.createAt( nodeAfter.previousSibling, 'end' );
|
|
|
- const viewRange = new ViewRange( viewPosition );
|
|
|
- const newSelection = new ViewSelection();
|
|
|
-
|
|
|
- newSelection.addRange( viewRange );
|
|
|
+ this.listenTo( viewDocument, 'mousedown', this._onMouseDown, { context: this } );
|
|
|
+ }
|
|
|
|
|
|
- viewDocument.fire( 'selectionChange', {
|
|
|
- oldSelection: viewDocument.selection,
|
|
|
- newSelection: newSelection,
|
|
|
- domSelection: null
|
|
|
- } );
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- } );
|
|
|
+ /**
|
|
|
+ * Handles `mousedown` event. If image widget is event's target then new selection in model is created around that
|
|
|
+ * element.
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ * @param {utils.EventInfo} eventInfo
|
|
|
+ * @param {envine.view.observer.DomEventData} domEventData
|
|
|
+ */
|
|
|
+ _onMouseDown( eventInfo, domEventData ) {
|
|
|
+ const target = domEventData.target;
|
|
|
+ const viewDocument = this.editor.editing.view;
|
|
|
|
|
|
- // Handle selection conversion.
|
|
|
- editingPipeline.modelToView.on( 'selection', ( evt, data, consumable, conversionApi ) => {
|
|
|
- const viewSelection = conversionApi.viewSelection;
|
|
|
- const range = data.selection.getFirstRange();
|
|
|
- const nodeAfterStart = range.start.nodeAfter;
|
|
|
+ if ( isImageWidget( target ) ) {
|
|
|
+ domEventData.preventDefault();
|
|
|
|
|
|
- // remove selection from all selected widgets
|
|
|
- for ( let viewElement of selected ) {
|
|
|
- viewElement.removeClass( 'selected' );
|
|
|
+ // Focus editor if is not focused already.
|
|
|
+ if ( !viewDocument.isFocused ) {
|
|
|
+ viewDocument.focus();
|
|
|
}
|
|
|
|
|
|
- // This could be just one element instead of set.
|
|
|
- selected.clear();
|
|
|
-
|
|
|
- if ( !data.selection.isCollapsed && nodeAfterStart && nodeAfterStart.name == 'image' && ModelRange.createOn( nodeAfterStart ).isEqual( range ) ) {
|
|
|
- viewSelection.setFake( true, { label: 'image fake selection' } );
|
|
|
- const viewElement = conversionApi.mapper.toViewElement( nodeAfterStart );
|
|
|
- viewElement.addClass( 'selected' );
|
|
|
- selected.add( viewElement );
|
|
|
- }
|
|
|
- }, { priority: 'low' } );
|
|
|
+ createModelSelectionOverElement( this.editor, target );
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// Checks if provided {@link engine.view.Element} is instance of image widget.
|
|
|
+//
|
|
|
+// @private
|
|
|
+// @param {engine.view.Element} viewElement
|
|
|
+// @returns {Boolean}
|
|
|
function isImageWidget( viewElement ) {
|
|
|
- // TODO: check CSS class.
|
|
|
return viewElement.isWidget && viewElement.name == 'figure' && viewElement.hasClass( 'image' );
|
|
|
}
|
|
|
+
|
|
|
+// Creates model selection over provided {@link engine.view.Element view Element} and calls
|
|
|
+// {@link engine.model.document#engueueChanges} to set new selection in model.
|
|
|
+//
|
|
|
+// @private
|
|
|
+// @param {core.editor.Editor} editor
|
|
|
+// @param {core.view.Element} viewElement
|
|
|
+function createModelSelectionOverElement( editor, viewElement ) {
|
|
|
+ const document = editor.document;
|
|
|
+ const modelElement = editor.editing.mapper.toModelElement( viewElement );
|
|
|
+ const modelRange = ModelRange.createOn( modelElement );
|
|
|
+
|
|
|
+ document.enqueueChanges( ( ) => {
|
|
|
+ document.selection.setRanges( [ modelRange ] );
|
|
|
+ } );
|
|
|
+}
|