Explorar o código

Added: LiveSelection should automatically update in which markers it is placed.

Szymon Cofalik %!s(int64=9) %!d(string=hai) anos
pai
achega
3ea99c128a

+ 61 - 13
packages/ckeditor5-engine/src/model/liveselection.js

@@ -29,17 +29,19 @@ const attrOpTypes = new Set(
  * {@link module:engine/model/document~Document document} and has it ranges updated accordingly. Internal implementation of this
  * {@link module:engine/model/document~Document document} and has it ranges updated accordingly. Internal implementation of this
  * mechanism bases on {@link module:engine/model/liverange~LiveRange live ranges}.
  * mechanism bases on {@link module:engine/model/liverange~LiveRange live ranges}.
  *
  *
- * Differences between {@link module:engine/model/selection~Selection} and `LiveSelection` are two:
+ * Differences between {@link module:engine/model/selection~Selection} and `LiveSelection` are:
  * * there is always a range in `LiveSelection` - even if no ranges were added there is a
  * * there is always a range in `LiveSelection` - even if no ranges were added there is a
  * {@link module:engine/model/liveselection~LiveSelection#_getDefaultRange "default range"} present in the selection,
  * {@link module:engine/model/liveselection~LiveSelection#_getDefaultRange "default range"} present in the selection,
- * * ranges added to this selection updates automatically when the document changes.
+ * * ranges added to this selection updates automatically when the document changes,
+ * * attributes of `LiveSelection` are updated automatically according to selection ranges,
+ * * markers containing `LiveSelection` are updated automatically according to selection ranges.
  *
  *
  * Since `LiveSelection` uses {@link module:engine/model/liverange~LiveRange live ranges}
  * Since `LiveSelection` uses {@link module:engine/model/liverange~LiveRange live ranges}
  * and is updated when {@link module:engine/model/document~Document document}
  * and is updated when {@link module:engine/model/document~Document document}
  * changes, it cannot be set on {@link module:engine/model/node~Node nodes}
  * changes, it cannot be set on {@link module:engine/model/node~Node nodes}
  * that are inside {@link module:engine/model/documentfragment~DocumentFragment document fragment}.
  * that are inside {@link module:engine/model/documentfragment~DocumentFragment document fragment}.
  * If you need to represent a selection in document fragment,
  * If you need to represent a selection in document fragment,
- * use {@link module:engine/model/selection~Selection "normal" selection} instead.
+ * use {@link module:engine/model/selection~Selection Selection class} instead.
  *
  *
  * @extends module:engine/model/selection~Selection
  * @extends module:engine/model/selection~Selection
  */
  */
@@ -72,12 +74,36 @@ export default class LiveSelection extends Selection {
 		 */
 		 */
 		this._attributePriority = new Map();
 		this._attributePriority = new Map();
 
 
-		// Whenever attribute operation is performed on document, update attributes. This is not the most efficient
-		// way to update selection attributes, but should be okay for now. `_updateAttributes` will be fired too often,
-		// but it won't change attributes or fire `change:attribute` event if not needed.
+		/**
+		 * Keeps names of markers in which selection is placed.
+		 *
+		 * @private
+		 * @member {Set} module:engine/model/liveselection~LiveSelection#_markers
+		 */
+		this._markers = new Set();
+
+		// Whenever attribute operation is performed on document, update attributes and markers that contains this selection.
+		// This is not the most efficient way to update selection attributes, but should be okay for now.
 		this.listenTo( this._document, 'change', ( evt, type ) => {
 		this.listenTo( this._document, 'change', ( evt, type ) => {
 			if ( attrOpTypes.has( type ) ) {
 			if ( attrOpTypes.has( type ) ) {
 				this._updateAttributes( false );
 				this._updateAttributes( false );
+			} else if ( type == 'move' ) {
+				// The only case when `LiveSelection` range may end up in marker after document change
+				// is when the document fragment containing selection is moved into (or outside of) a marker.
+				this._updateMarkers();
+			}
+		} );
+
+		// Whenever marker is added or removed, update list of markers that contains this selection.
+		this.listenTo( this._document.markers, 'add', ( evt, marker ) => {
+			if ( marker.getRange().containsPosition( this.getFirstPosition() ) ) {
+				this.addMarker( marker.name );
+			}
+		} );
+
+		this.listenTo( this._document.markers, 'remove', ( evt, marker ) => {
+			if ( marker.getRange().containsPosition( this.getFirstPosition() ) ) {
+				this.removeMarker( marker.name );
 			}
 			}
 		} );
 		} );
 	}
 	}
@@ -153,7 +179,7 @@ export default class LiveSelection extends Selection {
 	 */
 	 */
 	addRange( range, isBackward = false ) {
 	addRange( range, isBackward = false ) {
 		super.addRange( range, isBackward );
 		super.addRange( range, isBackward );
-		this.refreshAttributes();
+		this.refresh();
 	}
 	}
 
 
 	/**
 	/**
@@ -161,7 +187,7 @@ export default class LiveSelection extends Selection {
 	 */
 	 */
 	removeAllRanges() {
 	removeAllRanges() {
 		super.removeAllRanges();
 		super.removeAllRanges();
-		this.refreshAttributes();
+		this.refresh();
 	}
 	}
 
 
 	/**
 	/**
@@ -169,7 +195,7 @@ export default class LiveSelection extends Selection {
 	 */
 	 */
 	setRanges( newRanges, isLastBackward = false ) {
 	setRanges( newRanges, isLastBackward = false ) {
 		super.setRanges( newRanges, isLastBackward );
 		super.setRanges( newRanges, isLastBackward );
-		this.refreshAttributes();
+		this.refresh();
 	}
 	}
 
 
 	/**
 	/**
@@ -231,10 +257,12 @@ export default class LiveSelection extends Selection {
 	}
 	}
 
 
 	/**
 	/**
-	 * Removes all attributes from the selection and sets attributes according to the surrounding nodes.
+	 * Removes all attributes from the selection and sets attributes according to the surrounding nodes. Refreshes list
+	 * of names of markers containing this selection.
 	 */
 	 */
-	refreshAttributes() {
+	refresh() {
 		this._updateAttributes( true );
 		this._updateAttributes( true );
+		this._updateMarkers();
 	}
 	}
 
 
 	/**
 	/**
@@ -364,6 +392,26 @@ export default class LiveSelection extends Selection {
 		}
 		}
 	}
 	}
 
 
+	/**
+	 * Updates list of markers which contains this selection.
+	 *
+	 * A marker contains this selection if {@link module:engine/model/selection~Selection#getFirstPosition first position}
+	 * is inside that marker range.
+	 *
+	 * @protected
+	 */
+	_updateMarkers() {
+		this.clearMarkers();
+
+		const pos = this.getFirstPosition();
+
+		for ( let marker of this._document.markers ) {
+			if ( marker.getRange().containsPosition( pos ) ) {
+				this.addMarker( marker.name );
+			}
+		}
+	}
+
 	/**
 	/**
 	 * Generates and returns an attribute key for selection attributes store, basing on original attribute key.
 	 * Generates and returns an attribute key for selection attributes store, basing on original attribute key.
 	 *
 	 *
@@ -638,9 +686,9 @@ export default class LiveSelection extends Selection {
 
 
 		const newRange = this._prepareRange( selectionRange );
 		const newRange = this._prepareRange( selectionRange );
 		const index = this._ranges.indexOf( gyRange );
 		const index = this._ranges.indexOf( gyRange );
-		this._ranges.splice( index, 1, newRange );
-
 		gyRange.detach();
 		gyRange.detach();
+
+		this._ranges.splice( index, 1, newRange );
 	}
 	}
 }
 }
 
 

+ 46 - 0
packages/ckeditor5-engine/tests/model/liveselection.js

@@ -790,6 +790,52 @@ describe( 'LiveSelection', () => {
 		} );
 		} );
 	} );
 	} );
 
 
+	describe( 'update list of names of markers that contain selection', () => {
+		it( 'on range change', () => {
+			doc.markers.set( 'name', Range.createFromParentsAndOffsets( root, 1, root, 4 ) );
+			doc.markers.set( 'name2', Range.createFromParentsAndOffsets( root, 4, root, 5 ) );
+
+			expect( Array.from( selection.getMarkers() ) ).to.deep.equal( [] );
+
+			selection.setRanges( [ new Range( new Position( root, [ 2, 3 ] ) ) ] );
+
+			expect( Array.from( selection.getMarkers() ) ).to.deep.equal( [ 'name' ] );
+
+			selection.setRanges( [ new Range( new Position( root, [ 0 ] ) ) ] );
+
+			expect( Array.from( selection.getMarkers() ) ).to.deep.equal( [] );
+		} );
+
+		it( 'on marker change', () => {
+			selection.setRanges( [ new Range( new Position( root, [ 2, 3 ] ) ) ] );
+
+			doc.markers.set( 'name', Range.createFromParentsAndOffsets( root, 1, root, 4 ) );
+
+			expect( Array.from( selection.getMarkers() ) ).to.deep.equal( [ 'name' ] );
+
+			doc.markers.remove( 'name' );
+
+			expect( Array.from( selection.getMarkers() ) ).to.deep.equal( [] );
+		} );
+
+		it( 'on document change', () => {
+			doc.markers.set( 'name', Range.createFromParentsAndOffsets( root, 3, root, 5 ) );
+			selection.setRanges( [ new Range( new Position( root, [ 2, 3 ] ) ) ] );
+
+			doc.enqueueChanges( () => {
+				doc.batch().move( root.getChild( 2 ), Position.createAt( root, 4 ) );
+			} );
+
+			expect( Array.from( selection.getMarkers() ) ).to.deep.equal( [ 'name' ] );
+
+			doc.enqueueChanges( () => {
+				doc.batch().move( root.getChild( 3 ), Position.createAt( root, 0 ) );
+			} );
+
+			expect( Array.from( selection.getMarkers() ) ).to.deep.equal( [] );
+		} );
+	} );
+
 	describe( '_getStoredAttributes', () => {
 	describe( '_getStoredAttributes', () => {
 		it( 'should return no values if there are no ranges in selection', () => {
 		it( 'should return no values if there are no ranges in selection', () => {
 			let values = Array.from( selection._getStoredAttributes() );
 			let values = Array.from( selection._getStoredAttributes() );