Jelajahi Sumber

Merge pull request #682 from ckeditor/t/681

T/681
Piotrek Koszuliński 9 tahun lalu
induk
melakukan
efcfcaec87

+ 20 - 0
packages/ckeditor5-engine/src/view/selection.js

@@ -8,6 +8,7 @@ import Range from './range.js';
 import Position from './position.js';
 import mix from '../../utils/mix.js';
 import EmitterMixin from '../../utils/emittermixin.js';
+import Element from './element.js';
 
 /**
  * Class representing selection in tree view.
@@ -460,6 +461,25 @@ export default class Selection {
 		}
 	}
 
+	/**
+	 * Returns the selected element. {@link engine.view.Element Element} is considered as selected if there is only
+	 * one range in the selection, and that range contains exactly one element.
+	 * Returns `null` if there is no selected element.
+	 *
+	 * @returns {engine.view.Element|null}
+	 */
+	getSelectedElement() {
+		if ( this.rangeCount !== 1 ) {
+			return null;
+		}
+
+		const range = this.getFirstRange();
+		const nodeAfterStart = range.start.nodeAfter;
+		const nodeBeforeEnd = range.end.nodeBefore;
+
+		return ( nodeAfterStart instanceof Element && nodeAfterStart == nodeBeforeEnd ) ? nodeAfterStart : null;
+	}
+
 	/**
 	 * Creates and returns an instance of `Selection` that is a clone of given selection, meaning that it has same
 	 * ranges and same direction as this selection.

+ 38 - 0
packages/ckeditor5-engine/tests/view/selection.js

@@ -13,6 +13,7 @@ import Text from 'ckeditor5/engine/view/text.js';
 import Position from 'ckeditor5/engine/view/position.js';
 import CKEditorError from 'ckeditor5/utils/ckeditorerror.js';
 import count from 'ckeditor5/utils/count.js';
+import { parse } from 'ckeditor5/engine/dev-utils/view.js';
 
 describe( 'Selection', () => {
 	let selection;
@@ -489,6 +490,12 @@ describe( 'Selection', () => {
 
 			expect( selection.isEqual( otherSelection ) ).to.be.false;
 		} );
+
+		it( 'should return true if both selections are empty', () => {
+			const otherSelection = new Selection();
+
+			expect( selection.isEqual( otherSelection ) ).to.be.true;
+		} );
 	} );
 
 	describe( 'removeAllRanges', () => {
@@ -778,4 +785,35 @@ describe( 'Selection', () => {
 			selection.setFake( true, { label: 'foo bar baz' } );
 		} );
 	} );
+
+	describe( 'getSelectedElement', () => {
+		it( 'should return selected element', () => {
+			const { selection, view } = parse( 'foo [<b>bar</b>] baz' );
+			const b = view.getChild( 1 );
+
+			expect( selection.getSelectedElement() ).to.equal( b );
+		} );
+
+		it( 'should return null if there is more than one range', () => {
+			const { selection } = parse( 'foo [<b>bar</b>] [<i>baz</i>]' );
+
+			expect( selection.getSelectedElement() ).to.be.null;
+		} );
+
+		it( 'should return null if there is no selection', () => {
+			expect( selection.getSelectedElement() ).to.be.null;
+		} );
+
+		it( 'should return null if selection is not over single element #1', () => {
+			const { selection } = parse( 'foo [<b>bar</b> ba}z' );
+
+			expect( selection.getSelectedElement() ).to.be.null;
+		} );
+
+		it( 'should return null if selection is not over single element #2', () => {
+			const { selection } = parse( 'foo <b>{bar}</b> baz' );
+
+			expect( selection.getSelectedElement() ).to.be.null;
+		} );
+	} );
 } );