浏览代码

It should be possible to select table cells.

Maciej Gołaszewski 7 年之前
父节点
当前提交
6627797b49

+ 30 - 5
packages/ckeditor5-engine/src/model/utils/selection-post-fixer.js

@@ -119,7 +119,11 @@ function tryFixingRange( range, schema ) {
 function tryFixingCollapsedRange( range, schema ) {
 function tryFixingCollapsedRange( range, schema ) {
 	const originalPosition = range.start;
 	const originalPosition = range.start;
 
 
-	const nearestSelectionRange = schema.getNearestSelectionRange( originalPosition );
+	let nearestSelectionRange = schema.getNearestSelectionRange( originalPosition );
+
+	// console.log( '-------------------' );
+	// console.log( range.start.path, range.end.path );
+	// console.log( nearestSelectionRange.start.path, nearestSelectionRange.end.path );
 
 
 	// This might be null ie when editor data is empty.
 	// This might be null ie when editor data is empty.
 	// In such cases there is no need to fix the selection range.
 	// In such cases there is no need to fix the selection range.
@@ -127,7 +131,20 @@ function tryFixingCollapsedRange( range, schema ) {
 		return null;
 		return null;
 	}
 	}
 
 
-	const fixedPosition = nearestSelectionRange.start;
+	let fixedPosition = nearestSelectionRange.start;
+
+	const startLimitElement = schema.getLimitElement( originalPosition );
+
+	// TODO: beautify or unify with non-collapsed:
+	const shouldTryHarderFix = startLimitElement &&
+		fixedPosition.nodeAfter &&
+		!schema.checkChild( fixedPosition, '$text' ) &&
+		schema.isLimit( fixedPosition.nodeAfter );
+
+	if ( shouldTryHarderFix ) {
+		nearestSelectionRange = schema.getNearestSelectionRange( Position.createAt( fixedPosition.nodeAfter ) );
+		fixedPosition = nearestSelectionRange.start;
+	}
 
 
 	// Fixed position is the same as original - no need to return corrected range.
 	// Fixed position is the same as original - no need to return corrected range.
 	if ( originalPosition.isEqual( fixedPosition ) ) {
 	if ( originalPosition.isEqual( fixedPosition ) ) {
@@ -184,10 +201,18 @@ function tryFixingNonCollapsedRage( range, schema ) {
 	// At this point we eliminated valid positions on text nodes so if one of range positions is placed inside a limit element
 	// At this point we eliminated valid positions on text nodes so if one of range positions is placed inside a limit element
 	// then the range crossed limit element boundaries and needs to be fixed.
 	// then the range crossed limit element boundaries and needs to be fixed.
 	if ( isStartInLimit || isEndInLimit ) {
 	if ( isStartInLimit || isEndInLimit ) {
+		const isStartObject = start.nodeAfter && schema.isObject( start.nodeAfter );
+		const isEndObject = end.nodeBefore && schema.isObject( end.nodeBefore );
+
+		const bothInSameParent = ( !!start.nodeAfter && !!end.nodeBefore ) && start.nodeAfter === end.nodeBefore;
+
+		const expandStart = isStartInLimit && ( !bothInSameParent || !isStartObject );
+		const expandEnd = isEndInLimit && ( !bothInSameParent || !isEndObject );
+
 		// Although we've already found limit element on start/end positions we must find the outer-most limit element.
 		// Although we've already found limit element on start/end positions we must find the outer-most limit element.
 		// as limit elements might be nested directly inside (ie table > tableRow > tableCell).
 		// as limit elements might be nested directly inside (ie table > tableRow > tableCell).
-		const fixedStart = isStartInLimit ? expandSelectionOnIsLimitNode( Position.createAt( startLimitElement ), schema, 'start' ) : start;
-		const fixedEnd = isEndInLimit ? expandSelectionOnIsLimitNode( Position.createAt( endLimitElement ), schema, 'end' ) : end;
+		const fixedStart = expandStart ? expandSelectionOnIsLimitNode( Position.createAt( startLimitElement ), schema, 'start' ) : start;
+		const fixedEnd = expandEnd ? expandSelectionOnIsLimitNode( Position.createAt( endLimitElement ), schema, 'end' ) : end;
 
 
 		return new Range( fixedStart, fixedEnd );
 		return new Range( fixedStart, fixedEnd );
 	}
 	}
@@ -238,7 +263,7 @@ function mergeIntersectingRanges( ranges ) {
 	const deIntersected = [];
 	const deIntersected = [];
 
 
 	// First range will be always de-intersected.
 	// First range will be always de-intersected.
-	deIntersected.push( rangesCopy.pop() );
+	deIntersected.push( rangesCopy.shift() );
 
 
 	for ( const range of rangesCopy ) {
 	for ( const range of rangesCopy ) {
 		if ( range.isIntersecting( deIntersected[ deIntersected.length - 1 ] ) ) {
 		if ( range.isIntersecting( deIntersected[ deIntersected.length - 1 ] ) ) {

+ 95 - 85
packages/ckeditor5-engine/tests/model/utils/selection-post-fixer.js

@@ -30,7 +30,6 @@ describe( 'Selection post-fixer', () => {
 			model.schema.register( 'table', {
 			model.schema.register( 'table', {
 				allowWhere: '$block',
 				allowWhere: '$block',
 				allowAttributes: [ 'headingRows', 'headingColumns' ],
 				allowAttributes: [ 'headingRows', 'headingColumns' ],
-				isLimit: true,
 				isObject: true
 				isObject: true
 			} );
 			} );
 
 
@@ -42,7 +41,7 @@ describe( 'Selection post-fixer', () => {
 			model.schema.register( 'tableCell', {
 			model.schema.register( 'tableCell', {
 				allowIn: 'tableRow',
 				allowIn: 'tableRow',
 				allowAttributes: [ 'colspan', 'rowspan' ],
 				allowAttributes: [ 'colspan', 'rowspan' ],
-				isLimit: true
+				isObject: true
 			} );
 			} );
 
 
 			model.schema.extend( '$block', { allowIn: 'tableCell' } );
 			model.schema.extend( '$block', { allowIn: 'tableCell' } );
@@ -113,7 +112,7 @@ describe( 'Selection post-fixer', () => {
 				);
 				);
 			} );
 			} );
 
 
-			it( 'should fix #1', () => {
+			it( 'should fix #1 - range start outside table, end on table cell', () => {
 				// <paragraph>f[oo</paragraph><table><tableRow><tableCell></tableCell>]<tableCell>...
 				// <paragraph>f[oo</paragraph><table><tableRow><tableCell></tableCell>]<tableCell>...
 				model.change( writer => {
 				model.change( writer => {
 					writer.setSelection( ModelRange.createFromParentsAndOffsets(
 					writer.setSelection( ModelRange.createFromParentsAndOffsets(
@@ -134,7 +133,7 @@ describe( 'Selection post-fixer', () => {
 				);
 				);
 			} );
 			} );
 
 
-			it( 'should fix #2', () => {
+			it( 'should fix #2 - range start on table cell, end outside table', () => {
 				// ...<table><tableRow><tableCell></tableCell>[<tableCell></tableCell></tableRow></table><paragraph>b]ar</paragraph>
 				// ...<table><tableRow><tableCell></tableCell>[<tableCell></tableCell></tableRow></table><paragraph>b]ar</paragraph>
 				model.change( writer => {
 				model.change( writer => {
 					writer.setSelection( ModelRange.createFromParentsAndOffsets(
 					writer.setSelection( ModelRange.createFromParentsAndOffsets(
@@ -197,43 +196,6 @@ describe( 'Selection post-fixer', () => {
 				);
 				);
 			} );
 			} );
 
 
-			it( 'should fix #5', () => {
-				setModelData( model,
-					'<paragraph>foo</paragraph>' +
-					'<table>' +
-						'<tableRow>' +
-							'<tableCell><paragraph>aaa</paragraph></tableCell>' +
-							'<tableCell><paragraph>bbb</paragraph></tableCell>' +
-						'</tableRow>' +
-					'</table>' +
-					'[]' +
-					'<table>' +
-						'<tableRow>' +
-							'<tableCell><paragraph>xxx</paragraph></tableCell>' +
-							'<tableCell><paragraph>yyy</paragraph></tableCell>' +
-						'</tableRow>' +
-					'</table>' +
-					'<paragraph>baz</paragraph>'
-				);
-
-				expect( getModelData( model ) ).to.equal(
-					'<paragraph>foo</paragraph>' +
-					'[<table>' +
-						'<tableRow>' +
-							'<tableCell><paragraph>aaa</paragraph></tableCell>' +
-							'<tableCell><paragraph>bbb</paragraph></tableCell>' +
-						'</tableRow>' +
-					'</table>]' +
-					'<table>' +
-						'<tableRow>' +
-							'<tableCell><paragraph>xxx</paragraph></tableCell>' +
-							'<tableCell><paragraph>yyy</paragraph></tableCell>' +
-						'</tableRow>' +
-					'</table>' +
-					'<paragraph>baz</paragraph>'
-				);
-			} );
-
 			// There's a chance that this and the following test will not be up to date with
 			// There's a chance that this and the following test will not be up to date with
 			// how the table feature is really implemented once we'll introduce row/cells/columns selection
 			// how the table feature is really implemented once we'll introduce row/cells/columns selection
 			// in which case all these elements will need to be marked as objects.
 			// in which case all these elements will need to be marked as objects.
@@ -464,6 +426,44 @@ describe( 'Selection post-fixer', () => {
 				);
 				);
 			} );
 			} );
 
 
+			it( 'should not fix multiple ranges #1 - table selection', () => {
+				setModelData( model,
+					'<table>' +
+					'<tableRow>' +
+					'[<tableCell><paragraph>a</paragraph></tableCell>]' +
+					'[<tableCell><paragraph>b</paragraph></tableCell>]' +
+					'</tableRow>' +
+					'<tableRow>' +
+					'[<tableCell><paragraph>c</paragraph></tableCell>]' +
+					'<tableCell><paragraph>d</paragraph></tableCell>' +
+					'</tableRow>' +
+					'</table>'
+				);
+
+				// model.change( writer => {
+				// 	const ranges = [
+				// 		new ModelRange( new ModelPosition( modelRoot, [ 0, 1 ] ), new ModelPosition( modelRoot, [ 1, 0 ] ) ),
+				// 		new ModelRange( new ModelPosition( modelRoot, [ 1, 0, 0, 0 ] ), new ModelPosition( modelRoot, [ 2, 1 ] ) ),
+				// 		new ModelRange( new ModelPosition( modelRoot, [ 2, 2 ] ), new ModelPosition( modelRoot, [ 2, 3 ] ) )
+				// 	];
+				//
+				// 	writer.setSelection( ranges );
+				// } );
+
+				expect( getModelData( model ) ).to.equal(
+					'<table>' +
+					'<tableRow>' +
+					'[<tableCell><paragraph>a</paragraph></tableCell>]' +
+					'[<tableCell><paragraph>b</paragraph></tableCell>]' +
+					'</tableRow>' +
+					'<tableRow>' +
+					'[<tableCell><paragraph>c</paragraph></tableCell>]' +
+					'<tableCell><paragraph>d</paragraph></tableCell>' +
+					'</tableRow>' +
+					'</table>'
+				);
+			} );
+
 			it( 'should allow selection on block - limit element', () => {
 			it( 'should allow selection on block - limit element', () => {
 				model.schema.extend( '$block', { allowIn: 'tableCell' } );
 				model.schema.extend( '$block', { allowIn: 'tableCell' } );
 
 
@@ -806,79 +806,89 @@ describe( 'Selection post-fixer', () => {
 		} );
 		} );
 
 
 		describe( 'collapsed selection', () => {
 		describe( 'collapsed selection', () => {
-			beforeEach( () => {
+			it( 'should fix #1 - selection in limit element & before limit element', () => {
 				setModelData( model,
 				setModelData( model,
-					'<paragraph>[]foo</paragraph>' +
+					'<paragraph>foo</paragraph>' +
 					'<table>' +
 					'<table>' +
-					'<tableRow>' +
-					'<tableCell><paragraph>aaa</paragraph></tableCell>' +
-					'<tableCell><paragraph>bbb</paragraph></tableCell>' +
-					'</tableRow>' +
+						'[]<tableRow>' +
+							'<tableCell><paragraph>aaa</paragraph></tableCell>' +
+						'</tableRow>' +
 					'</table>' +
 					'</table>' +
 					'<paragraph>bar</paragraph>'
 					'<paragraph>bar</paragraph>'
 				);
 				);
-			} );
-
-			it( 'should fix #1', () => {
-				// <table>[]<tableRow>...
-				model.change( writer => {
-					writer.setSelection(
-						ModelRange.createFromParentsAndOffsets( modelRoot.getChild( 1 ), 0, modelRoot.getChild( 1 ), 0 )
-					);
-				} );
 
 
 				expect( getModelData( model ) ).to.equal(
 				expect( getModelData( model ) ).to.equal(
 					'<paragraph>foo[]</paragraph>' +
 					'<paragraph>foo[]</paragraph>' +
 					'<table>' +
 					'<table>' +
-					'<tableRow>' +
-					'<tableCell><paragraph>aaa</paragraph></tableCell>' +
-					'<tableCell><paragraph>bbb</paragraph></tableCell>' +
-					'</tableRow>' +
+						'<tableRow>' +
+							'<tableCell><paragraph>aaa</paragraph></tableCell>' +
+						'</tableRow>' +
 					'</table>' +
 					'</table>' +
 					'<paragraph>bar</paragraph>'
 					'<paragraph>bar</paragraph>'
 				);
 				);
 			} );
 			} );
 
 
-			it( 'should fix #2', () => {
-				// <table><tableRow>[]<tableCell>...
-				model.change( writer => {
-					const row = modelRoot.getChild( 1 ).getChild( 0 );
+			it( 'should fix #2 - selection in limit element & before limit+object element', () => {
+				setModelData( model,
+					'<paragraph>foo</paragraph>' +
+					'<table>' +
+						'<tableRow>' +
+							'[]<tableCell><paragraph>aaa</paragraph></tableCell>' +
+						'</tableRow>' +
+					'</table>' +
+					'<paragraph>bar</paragraph>'
+				);
 
 
-					writer.setSelection(
-						ModelRange.createFromParentsAndOffsets( row, 0, row, 0 )
-					);
-				} );
+				expect( getModelData( model ) ).to.equal(
+					'<paragraph>foo</paragraph>' +
+					'<table>' +
+						'<tableRow>' +
+							'<tableCell><paragraph>[]aaa</paragraph></tableCell>' +
+						'</tableRow>' +
+					'</table>' +
+					'<paragraph>bar</paragraph>'
+				);
+			} );
+
+			it( 'should fix #3 - selection in limit&object element & before object element', () => {
+				setModelData( model,
+					'<paragraph>foo</paragraph>' +
+					'<table>' +
+						'<tableRow>' +
+							'<tableCell>[]<paragraph>aaa</paragraph></tableCell>' +
+						'</tableRow>' +
+					'</table>' +
+					'<paragraph>bar</paragraph>'
+				);
 
 
 				expect( getModelData( model ) ).to.equal(
 				expect( getModelData( model ) ).to.equal(
 					'<paragraph>foo</paragraph>' +
 					'<paragraph>foo</paragraph>' +
 					'<table>' +
 					'<table>' +
-					'<tableRow>' +
-					'<tableCell><paragraph>[]aaa</paragraph></tableCell>' +
-					'<tableCell><paragraph>bbb</paragraph></tableCell>' +
-					'</tableRow>' +
+						'<tableRow>' +
+							'<tableCell><paragraph>[]aaa</paragraph></tableCell>' +
+						'</tableRow>' +
 					'</table>' +
 					'</table>' +
 					'<paragraph>bar</paragraph>'
 					'<paragraph>bar</paragraph>'
 				);
 				);
 			} );
 			} );
 
 
 			it( 'should not fix multiple ranges #1', () => {
 			it( 'should not fix multiple ranges #1', () => {
-				// []<paragraph>foo</paragraph>[]<table>...
-				model.change( writer => {
-					writer.setSelection(
-						[
-							ModelRange.createFromParentsAndOffsets( modelRoot, 0, modelRoot, 0 ),
-							ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 1 )
-						]
-					);
-				} );
+				setModelData( model,
+					'[]<paragraph>foo</paragraph>[]' +
+					'<table>' +
+						'<tableRow>' +
+							'<tableCell><paragraph>aaa</paragraph></tableCell>' +
+						'</tableRow>' +
+					'</table>' +
+					'<paragraph>bar</paragraph>'
+				);
 
 
 				expect( getModelData( model ) ).to.equal(
 				expect( getModelData( model ) ).to.equal(
 					'<paragraph>[]foo[]</paragraph>' +
 					'<paragraph>[]foo[]</paragraph>' +
 					'<table>' +
 					'<table>' +
-					'<tableRow>' +
-					'<tableCell><paragraph>aaa</paragraph></tableCell>' +
-					'<tableCell><paragraph>bbb</paragraph></tableCell>' +
-					'</tableRow>' +
+						'<tableRow>' +
+							'<tableCell><paragraph>aaa</paragraph></tableCell>' +
+						'</tableRow>' +
 					'</table>' +
 					'</table>' +
 					'<paragraph>bar</paragraph>'
 					'<paragraph>bar</paragraph>'
 				);
 				);