Browse Source

Implemented getTableCellsContainingSelection() and getSelectionAffectedTableCells() helpers.

Aleksander Nowodzinski 5 years ago
parent
commit
2f027e53a8

+ 3 - 26
packages/ckeditor5-table/src/tablecellproperties/commands/tablecellpropertycommand.js

@@ -8,9 +8,7 @@
  */
 
 import Command from '@ckeditor/ckeditor5-core/src/command';
-
-import { getSelectedTableCells } from '../../utils';
-import { findAncestor } from '../../commands/utils';
+import { getSelectionAffectedTableCells } from '../../utils';
 
 /**
  * The table cell attribute command.
@@ -37,7 +35,7 @@ export default class TableCellPropertyCommand extends Command {
 	 */
 	refresh() {
 		const editor = this.editor;
-		const selectedTableCells = getSelectedOrSelectionContainingTableCells( editor.model.document.selection );
+		const selectedTableCells = getSelectionAffectedTableCells( editor.model.document.selection );
 
 		this.isEnabled = !!selectedTableCells.length;
 		this.value = this._getSingleValue( selectedTableCells );
@@ -56,7 +54,7 @@ export default class TableCellPropertyCommand extends Command {
 	execute( options = {} ) {
 		const { value, batch } = options;
 		const model = this.editor.model;
-		const tableCells = getSelectedOrSelectionContainingTableCells( model.document.selection );
+		const tableCells = getSelectionAffectedTableCells( model.document.selection );
 		const valueToSet = this._getValueToSet( value );
 
 		model.enqueueChange( batch || 'default', writer => {
@@ -110,24 +108,3 @@ export default class TableCellPropertyCommand extends Command {
 		return everyCellHasAttribute ? firstCellValue : undefined;
 	}
 }
-
-// For the given selection, it returns an array made of:
-//
-// * table cells selected entirely,
-// * a table cell the selection is anchored to (if no cells are selected entirely).
-//
-// @param {module:engine/model/selection~Selection} selection
-// @returns {Array.<module:engine/model/element~Element>}
-function getSelectedOrSelectionContainingTableCells( selection ) {
-	const tableCells = getSelectedTableCells( selection );
-
-	if ( !tableCells.length ) {
-		const cellWithSelection = findAncestor( 'tableCell', selection.getFirstPosition() );
-
-		if ( cellWithSelection ) {
-			tableCells.push( cellWithSelection );
-		}
-	}
-
-	return tableCells;
-}

+ 50 - 1
packages/ckeditor5-table/src/utils.js

@@ -69,7 +69,11 @@ export function getTableWidgetAncestor( selection ) {
 }
 
 /**
- * Returns all model cells within the provided model selection.
+ * Returns all model table cells that are fully selected (from the outside)
+ * within the provided model selection's ranges.
+ *
+ * To obtain the cells selected from the inside, use
+ * {@link module:table/utils~getTableCellsContainingSelection}.
  *
  * @param {module:engine/model/selection~Selection} selection
  * @returns {Array.<module:engine/model/element~Element>}
@@ -87,3 +91,48 @@ export function getSelectedTableCells( selection ) {
 
 	return cells;
 }
+
+/**
+ * Returns all model table cells the provided model selection's ranges
+ * {@link module:engine/model/range~Range#start} inside.
+ *
+ * To obtain the cells selected from the outside, use
+ * {@link module:table/utils~getSelectedTableCells}.
+ *
+ * @param {module:engine/model/selection~Selection} selection
+ * @returns {Array.<module:engine/model/element~Element>}
+ */
+export function getTableCellsContainingSelection( selection ) {
+	const cells = [];
+
+	for ( const range of selection.getRanges() ) {
+		const cellWithSelection = findAncestor( 'tableCell', range.start );
+
+		if ( cellWithSelection ) {
+			cells.push( cellWithSelection );
+		}
+	}
+
+	return cells;
+}
+
+/**
+ * Returns all model table cells that are either completely selected
+ * by selection ranges or host selection range
+ * {@link module:engine/model/range~Range#start start positions} inside them.
+ *
+ * Combines {@link module:table/utils~getTableCellsContainingSelection} and
+ * {@link module:table/utils~getSelectedTableCells}.
+ *
+ * @param {module:engine/model/selection~Selection} selection
+ * @returns {Array.<module:engine/model/element~Element>}
+ */
+export function getSelectionAffectedTableCells( selection ) {
+	const selectedCells = getSelectedTableCells( selection );
+
+	if ( selectedCells.length ) {
+		return selectedCells;
+	}
+
+	return getTableCellsContainingSelection( selection );
+}

+ 169 - 9
packages/ckeditor5-table/tests/utils.js

@@ -10,7 +10,11 @@ import TableEditing from '../src/tableediting';
 
 import { setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 import { modelTable } from './_utils/utils';
-import { getSelectedTableCells } from '../src/utils';
+import {
+	getSelectedTableCells,
+	getTableCellsContainingSelection,
+	getSelectionAffectedTableCells
+} from '../src/utils';
 
 describe( 'table utils', () => {
 	let editor, model, tableSelection, modelRoot;
@@ -49,7 +53,7 @@ describe( 'table utils', () => {
 				writer.setSelection( writer.createRange( writer.createPositionAt( firstCell, 0 ) ) );
 			} );
 
-			expect( getSelectedTableCells( selection ) ).to.deep.equal( [] );
+			expect( getSelectedTableCells( selection ) ).to.be.empty;
 		} );
 
 		it( 'should return an empty array when a non-collapsed selection is anchored in a cell', () => {
@@ -59,7 +63,7 @@ describe( 'table utils', () => {
 				writer.setSelection( writer.createRangeIn( firstCell ) );
 			} );
 
-			expect( getSelectedTableCells( selection ) ).to.deep.equal( [] );
+			expect( getSelectedTableCells( selection ) ).to.be.empty;
 		} );
 
 		it( 'should return an empty array when a non-cell node is selected', () => {
@@ -71,7 +75,7 @@ describe( 'table utils', () => {
 				writer.setSelection( writer.createRangeOn( paragraph ) );
 			} );
 
-			expect( getSelectedTableCells( selection ) ).to.deep.equal( [] );
+			expect( getSelectedTableCells( selection ) ).to.be.empty;
 		} );
 
 		it( 'should return an empty array when an entire table is selected', () => {
@@ -81,7 +85,7 @@ describe( 'table utils', () => {
 				writer.setSelection( writer.createRangeOn( table ) );
 			} );
 
-			expect( getSelectedTableCells( selection ) ).to.deep.equal( [] );
+			expect( getSelectedTableCells( selection ) ).to.be.empty;
 		} );
 
 		it( 'should return two table cells', () => {
@@ -90,7 +94,7 @@ describe( 'table utils', () => {
 
 			tableSelection._setCellSelection( firstCell, lastCell );
 
-			expect( Array.from( getSelectedTableCells( selection ) ) ).to.deep.equal( [
+			expect( Array.from( getSelectedTableCells( selection ) ) ).to.have.ordered.members( [
 				firstCell, lastCell
 			] );
 		} );
@@ -101,7 +105,7 @@ describe( 'table utils', () => {
 
 			tableSelection._setCellSelection( firstCell, lastCell );
 
-			expect( Array.from( getSelectedTableCells( selection ) ) ).to.deep.equal( [
+			expect( Array.from( getSelectedTableCells( selection ) ) ).to.have.ordered.members( [
 				firstCell,
 				modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
 				modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
@@ -115,7 +119,7 @@ describe( 'table utils', () => {
 
 			tableSelection._setCellSelection( firstCell, lastCell );
 
-			expect( Array.from( getSelectedTableCells( selection ) ) ).to.deep.equal( [
+			expect( Array.from( getSelectedTableCells( selection ) ) ).to.have.ordered.members( [
 				firstCell,
 				modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
 				lastCell
@@ -128,11 +132,167 @@ describe( 'table utils', () => {
 
 			tableSelection._setCellSelection( firstCell, lastCell );
 
-			expect( Array.from( getSelectedTableCells( selection ) ) ).to.deep.equal( [
+			expect( Array.from( getSelectedTableCells( selection ) ) ).to.have.ordered.members( [
 				firstCell,
 				modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
 				lastCell
 			] );
 		} );
 	} );
+
+	describe( 'getTableCellsContainingSelection()', () => {
+		let selection;
+
+		beforeEach( () => {
+			selection = model.document.selection;
+		} );
+
+		it( 'should return an array with a cell when a selection is anchored in it', () => {
+			const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
+
+			model.change( writer => {
+				writer.setSelection( writer.createRange( writer.createPositionAt( firstCell, 0 ) ) );
+			} );
+
+			expect( getTableCellsContainingSelection( selection ) ).to.have.ordered.members( [ firstCell ] );
+		} );
+
+		it( 'should return an array with a cell when a selection range is anchored in its descendant', () => {
+			const cell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
+			const paragraph = modelRoot.getNodeByPath( [ 0, 0, 0, 0 ] );
+
+			model.change( writer => {
+				writer.setSelection( writer.createRange(
+					writer.createPositionAt( paragraph, 0 ),
+					writer.createPositionAt( paragraph, 1 ),
+				) );
+			} );
+
+			expect( getTableCellsContainingSelection( selection ) ).to.have.ordered.members( [
+				cell
+			] );
+		} );
+
+		it( 'should return an array with cells when multiple collapsed selection ranges are anchored in them', () => {
+			const cellA = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
+			const cellB = modelRoot.getNodeByPath( [ 0, 1, 0 ] );
+
+			model.change( writer => {
+				writer.setSelection( [
+					writer.createRange( writer.createPositionAt( cellA, 0 ) ),
+					writer.createRange( writer.createPositionAt( cellB, 0 ) )
+				] );
+			} );
+
+			expect( getTableCellsContainingSelection( selection ) ).to.have.ordered.members( [
+				cellA,
+				cellB
+			] );
+		} );
+
+		it( 'should return an array with cells when multiple non–collapsed selection ranges are anchored in them', () => {
+			const cellA = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
+			const cellB = modelRoot.getNodeByPath( [ 0, 1, 0 ] );
+
+			model.change( writer => {
+				writer.setSelection( [
+					writer.createRangeIn( cellA ),
+					writer.createRangeIn( cellB )
+				] );
+			} );
+
+			expect( getTableCellsContainingSelection( selection ) ).to.have.ordered.members( [
+				cellA,
+				cellB
+			] );
+		} );
+
+		it( 'should return an empty array when an entire cell is selected', () => {
+			const cell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
+
+			model.change( writer => {
+				writer.setSelection( writer.createRangeOn( cell ) );
+			} );
+
+			expect( getTableCellsContainingSelection( selection ) ).to.be.empty;
+		} );
+
+		it( 'should return an empty array when an entire table is selected', () => {
+			const table = modelRoot.getNodeByPath( [ 0 ] );
+
+			model.change( writer => {
+				writer.setSelection( writer.createRangeOn( table ) );
+			} );
+
+			expect( getTableCellsContainingSelection( selection ) ).to.be.empty;
+		} );
+
+		it( 'should return an empty array when unrelated elements host selection ranges', () => {
+			setModelData( model, '<paragraph>foo</paragraph>' );
+
+			const paragraph = modelRoot.getNodeByPath( [ 0 ] );
+
+			model.change( writer => {
+				writer.setSelection( writer.createRange( writer.createPositionAt( paragraph, 1 ) ) );
+			} );
+
+			expect( getTableCellsContainingSelection( selection ) ).to.be.empty;
+		} );
+	} );
+
+	describe( 'getSelectionAffectedTableCells()', () => {
+		let selection;
+
+		beforeEach( () => {
+			selection = model.document.selection;
+		} );
+
+		it( 'should return completely selected cells (if there are any)', () => {
+			const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
+			const lastCell = modelRoot.getNodeByPath( [ 0, 0, 1 ] );
+
+			tableSelection._setCellSelection( firstCell, lastCell );
+
+			expect( Array.from( getSelectionAffectedTableCells( selection ) ) ).to.have.ordered.members( [
+				firstCell, lastCell
+			] );
+		} );
+
+		it( 'should return cells when selection ranges are starting in them', () => {
+			const cellA = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
+			const cellB = modelRoot.getNodeByPath( [ 0, 1, 0 ] );
+
+			model.change( writer => {
+				writer.setSelection( [
+					writer.createRange( writer.createPositionAt( cellA, 0 ) ),
+					writer.createRange( writer.createPositionAt( cellB, 0 ) )
+				] );
+			} );
+
+			expect( getSelectionAffectedTableCells( selection ) ).to.have.ordered.members( [
+				cellA,
+				cellB
+			] );
+		} );
+
+		it( 'should return an empty array if no cells are selected and no selection ranges start in any cell', () => {
+			const table = modelRoot.getNodeByPath( [ 0 ] );
+
+			model.change( writer => {
+				writer.setSelection( writer.createRangeOn( table ) );
+			} );
+
+			expect( getSelectionAffectedTableCells( selection ) ).to.be.empty;
+
+			setModelData( model, '<paragraph>foo</paragraph>' );
+
+			const paragraph = modelRoot.getNodeByPath( [ 0 ] );
+
+			model.change( writer => {
+				writer.setSelection( writer.createRange( writer.createPositionAt( paragraph, 1 ) ) );
+			} );
+
+			expect( getSelectionAffectedTableCells( selection ) ).to.be.empty;
+		} );
+	} );
 } );