Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Commit b1e8975

Browse files
author
Piotr Jasiun
authored
Merge pull request #1578 from ckeditor/t/ckeditor5/1287
Fix: Corrected transformations for pasting and undo scenarios. Closes ckeditor/ckeditor5#1287.
2 parents b92a800 + 6a37874 commit b1e8975

File tree

3 files changed

+92
-16
lines changed

3 files changed

+92
-16
lines changed

src/model/model.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,6 @@ export default class Model {
349349
* @fires deleteContent
350350
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
351351
* Selection of which the content should be deleted.
352-
* @param {module:engine/model/batch~Batch} batch Batch to which the operations will be added.
353352
* @param {Object} [options]
354353
* @param {Boolean} [options.leaveUnmerged=false] Whether to merge elements after removing the content of the selection.
355354
*

src/model/operation/transform.js

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,12 @@ class ContextFactory {
505505

506506
break;
507507
}
508+
509+
case SplitOperation: {
510+
if ( opA.sourcePosition.isEqual( opB.splitPosition ) ) {
511+
this._setRelation( opA, opB, 'splitAtSource' );
512+
}
513+
}
508514
}
509515

510516
break;
@@ -1159,7 +1165,10 @@ setTransformation( MergeOperation, MergeOperation, ( a, b, context ) => {
11591165
//
11601166
// If neither or both operations point to graveyard, then let `aIsStrong` decide.
11611167
//
1162-
if ( a.sourcePosition.isEqual( b.sourcePosition ) && !a.targetPosition.isEqual( b.targetPosition ) && !context.bWasUndone ) {
1168+
if (
1169+
a.sourcePosition.isEqual( b.sourcePosition ) && !a.targetPosition.isEqual( b.targetPosition ) &&
1170+
!context.bWasUndone && context.abRelation != 'splitAtSource'
1171+
) {
11631172
const aToGraveyard = a.targetPosition.root.rootName == '$graveyard';
11641173
const bToGraveyard = b.targetPosition.root.rootName == '$graveyard';
11651174

@@ -1338,7 +1347,7 @@ setTransformation( MergeOperation, SplitOperation, ( a, b, context ) => {
13381347
// In this scenario the merge operation is now transformed by the split which has undone the previous merge operation.
13391348
// So now we are fixing situation which was skipped in `MergeOperation` x `MergeOperation` case.
13401349
//
1341-
if ( a.sourcePosition.isEqual( b.splitPosition ) && context.abRelation == 'mergeSameElement' ) {
1350+
if ( a.sourcePosition.isEqual( b.splitPosition ) && ( context.abRelation == 'mergeSameElement' || a.sourcePosition.offset > 0 ) ) {
13421351
a.sourcePosition = Position.createFromPosition( b.moveTargetPosition );
13431352
a.targetPosition = a.targetPosition._getTransformedBySplitOperation( b );
13441353

@@ -1396,9 +1405,9 @@ setTransformation( MoveOperation, MoveOperation, ( a, b, context ) => {
13961405
let insertBefore = !context.aIsStrong;
13971406

13981407
// If the relation is set, then use it to decide nodes order.
1399-
if ( context.abRelation == 'insertBefore' ) {
1408+
if ( context.abRelation == 'insertBefore' || context.baRelation == 'insertAfter' ) {
14001409
insertBefore = true;
1401-
} else if ( context.abRelation == 'insertAfter' ) {
1410+
} else if ( context.abRelation == 'insertAfter' || context.baRelation == 'insertBefore' ) {
14021411
insertBefore = false;
14031412
}
14041413

@@ -1683,7 +1692,16 @@ setTransformation( MoveOperation, MergeOperation, ( a, b, context ) => {
16831692
// removed nodes might be unexpected. This means that in this scenario we will reverse merging and remove the element.
16841693
//
16851694
if ( !context.aWasUndone ) {
1686-
return [ b.getReversed(), a ];
1695+
const gyMoveTarget = Position.createFromPosition( b.graveyardPosition );
1696+
const gyMove = new MoveOperation( b.graveyardPosition, 1, gyMoveTarget, 0 );
1697+
1698+
const targetPositionPath = b.graveyardPosition.path.slice();
1699+
targetPositionPath.push( 0 );
1700+
1701+
return [
1702+
gyMove,
1703+
new MoveOperation( b.targetPosition, b.howMany, new Position( a.targetPosition.root, targetPositionPath ), 0 )
1704+
];
16871705
}
16881706
} else {
16891707
// Case 2:
@@ -1911,11 +1929,25 @@ setTransformation( SplitOperation, MergeOperation, ( a, b, context ) => {
19111929
} );
19121930

19131931
setTransformation( SplitOperation, MoveOperation, ( a, b, context ) => {
1932+
const rangeToMove = Range.createFromPositionAndShift( b.sourcePosition, b.howMany );
1933+
19141934
if ( a.graveyardPosition ) {
1935+
// Case 1:
1936+
//
1937+
// Split operation graveyard node was moved. In this case move operation is stronger and the split insertion position
1938+
// should be corrected.
1939+
//
1940+
if ( rangeToMove.containsPosition( a.graveyardPosition ) || rangeToMove.start.isEqual( a.graveyardPosition ) ) {
1941+
a.insertionPosition = Position.createFromPosition( b.targetPosition );
1942+
a.splitPosition = a.splitPosition._getTransformedByMoveOperation( b );
1943+
1944+
return [ a ];
1945+
}
1946+
19151947
a.graveyardPosition = a.graveyardPosition._getTransformedByMoveOperation( b );
19161948
}
19171949

1918-
// Case 1:
1950+
// Case 2:
19191951
//
19201952
// If the split position is inside the moved range, we need to shift the split position to a proper place.
19211953
// The position cannot be moved together with moved range because that would result in splitting of an incorrect element.
@@ -1932,8 +1964,6 @@ setTransformation( SplitOperation, MoveOperation, ( a, b, context ) => {
19321964
// After split:
19331965
// <paragraph>A</paragraph><paragraph>d</paragraph><paragraph>Xbcyz</paragraph>
19341966
//
1935-
const rangeToMove = Range.createFromPositionAndShift( b.sourcePosition, b.howMany );
1936-
19371967
if ( a.splitPosition.hasSameParentAs( b.sourcePosition ) && rangeToMove.containsPosition( a.splitPosition ) ) {
19381968
const howManyRemoved = b.howMany - ( a.splitPosition.offset - b.sourcePosition.offset );
19391969
a.howMany -= howManyRemoved;
@@ -1948,7 +1978,7 @@ setTransformation( SplitOperation, MoveOperation, ( a, b, context ) => {
19481978
return [ a ];
19491979
}
19501980

1951-
// Case 2:
1981+
// Case 3:
19521982
//
19531983
// Split is at a position where nodes were moved.
19541984
//
@@ -1966,13 +1996,16 @@ setTransformation( SplitOperation, MoveOperation, ( a, b, context ) => {
19661996
}
19671997

19681998
// The default case.
1999+
// Don't change `howMany` if move operation does not really move anything.
19692000
//
1970-
if ( a.splitPosition.hasSameParentAs( b.sourcePosition ) && a.splitPosition.offset <= b.sourcePosition.offset ) {
1971-
a.howMany -= b.howMany;
1972-
}
2001+
if ( !b.sourcePosition.isEqual( b.targetPosition ) ) {
2002+
if ( a.splitPosition.hasSameParentAs( b.sourcePosition ) && a.splitPosition.offset <= b.sourcePosition.offset ) {
2003+
a.howMany -= b.howMany;
2004+
}
19732005

1974-
if ( a.splitPosition.hasSameParentAs( b.targetPosition ) && a.splitPosition.offset < b.targetPosition.offset ) {
1975-
a.howMany += b.howMany;
2006+
if ( a.splitPosition.hasSameParentAs( b.targetPosition ) && a.splitPosition.offset < b.targetPosition.offset ) {
2007+
a.howMany += b.howMany;
2008+
}
19762009
}
19772010

19782011
// Change position stickiness to force a correct transformation.

tests/model/operation/transform/undo.js

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ describe( 'transform', () => {
276276
expectClients( '<paragraph>Foo</paragraph>' );
277277
} );
278278

279-
it( 'undo pasting', () => {
279+
it( 'pasting on collapsed selection undo and redo', () => {
280280
john.setData( '<paragraph>Foo[]Bar</paragraph>' );
281281

282282
// Below simulates pasting.
@@ -297,8 +297,16 @@ describe( 'transform', () => {
297297
expectClients( '<paragraph>Foo1</paragraph><paragraph>2Bar</paragraph>' );
298298

299299
john.undo();
300+
expectClients( '<paragraph>FooBar</paragraph>' );
300301

302+
john.redo();
303+
expectClients( '<paragraph>Foo1</paragraph><paragraph>2Bar</paragraph>' );
304+
305+
john.undo();
301306
expectClients( '<paragraph>FooBar</paragraph>' );
307+
308+
john.redo();
309+
expectClients( '<paragraph>Foo1</paragraph><paragraph>2Bar</paragraph>' );
302310
} );
303311

304312
it( 'selection attribute setting: split, bold, merge, undo, undo, undo', () => {
@@ -344,4 +352,40 @@ describe( 'transform', () => {
344352
'<paragraph>X</paragraph><paragraph>A</paragraph><paragraph>B</paragraph><paragraph>C</paragraph><paragraph>D</paragraph>'
345353
);
346354
} );
355+
356+
// https://github.com/ckeditor/ckeditor5/issues/1287 TC1
357+
it( 'pasting on non-collapsed selection undo and redo', () => {
358+
john.setData( '<paragraph>Fo[o</paragraph><paragraph>B]ar</paragraph>' );
359+
360+
// Below simulates pasting.
361+
john.editor.model.change( () => {
362+
john.editor.model.deleteContent( john.document.selection );
363+
364+
john.setSelection( [ 0, 2 ] );
365+
john.split();
366+
367+
john.setSelection( [ 1 ] );
368+
john.insert( '<paragraph>1</paragraph>' );
369+
370+
john.setSelection( [ 1 ] );
371+
john.merge();
372+
373+
john.setSelection( [ 1 ] );
374+
john.insert( '<paragraph>2</paragraph>' );
375+
376+
john.setSelection( [ 2 ] );
377+
john.merge();
378+
} );
379+
380+
expectClients( '<paragraph>Fo1</paragraph><paragraph>2ar</paragraph>' );
381+
382+
john.undo();
383+
expectClients( '<paragraph>Foo</paragraph><paragraph>Bar</paragraph>' );
384+
385+
john.redo();
386+
expectClients( '<paragraph>Fo1</paragraph><paragraph>2ar</paragraph>' );
387+
388+
john.undo();
389+
expectClients( '<paragraph>Foo</paragraph><paragraph>Bar</paragraph>' );
390+
} );
347391
} );

0 commit comments

Comments
 (0)