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

Commit e0e961f

Browse files
authored
Merge pull request #1573 from ckeditor/t/ckeditor5/1278
Fix: `Range#_getTransformedBySplitOperation()` will expand the range if `insertionPosition` is equal to the range end. Modified transformations to align with that change. Closes ckeditor/ckeditor5#1278.
2 parents f6ad9af + fd6dd3e commit e0e961f

File tree

5 files changed

+100
-8
lines changed

5 files changed

+100
-8
lines changed

src/model/operation/transform.js

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,10 @@ class ContextFactory {
497497
this._setRelation( opA, opB, 'mergeTargetNotMoved' );
498498
}
499499

500+
if ( opA.sourcePosition.isEqual( opB.sourcePosition ) ) {
501+
this._setRelation( opA, opB, 'mergeSameElement' );
502+
}
503+
500504
break;
501505
}
502506
}
@@ -905,8 +909,10 @@ setTransformation( AttributeOperation, SplitOperation, ( a, b ) => {
905909
// After attribute change:
906910
// <listItem type="numbered">foo</listItem><listItem type="numbered">foo</listItem>
907911
//
908-
if ( a.range.end.isEqual( b.insertionPosition ) && !b.graveyardPosition ) {
909-
a.range.end.offset++;
912+
if ( a.range.end.isEqual( b.insertionPosition ) ) {
913+
if ( !b.graveyardPosition ) {
914+
a.range.end.offset++;
915+
}
910916

911917
return [ a ];
912918
}
@@ -1320,6 +1326,23 @@ setTransformation( MergeOperation, SplitOperation, ( a, b, context ) => {
13201326
}
13211327
}
13221328

1329+
// Case 2:
1330+
//
1331+
// Merge source is at the same position as split position. This sometimes happen during undo. This merge operation
1332+
// might have been earlier transformed by a merge operation which both merged the same element. See case in
1333+
// `MergeOperation` x `MergeOperation` transformation. In that case, if the merge operation has been undone, the special
1334+
// case is not applied.
1335+
//
1336+
// In this scenario the merge operation is now transformed by the split which has undone the previous merge operation.
1337+
// So now we are fixing situation which was skipped in `MergeOperation` x `MergeOperation` case.
1338+
//
1339+
if ( a.sourcePosition.isEqual( b.splitPosition ) && context.abRelation == 'mergeSameElement' ) {
1340+
a.sourcePosition = Position.createFromPosition( b.moveTargetPosition );
1341+
a.targetPosition = a.targetPosition._getTransformedBySplitOperation( b );
1342+
1343+
return [ a ];
1344+
}
1345+
13231346
// The default case.
13241347
//
13251348
if ( a.sourcePosition.hasSameParentAs( b.splitPosition ) ) {
@@ -1555,13 +1578,16 @@ setTransformation( MoveOperation, SplitOperation, ( a, b, context ) => {
15551578
//
15561579
// In this case the default range transformation will not work correctly as the element created by
15571580
// split operation would be outside the range. The range to move needs to be fixed manually.
1558-
// Do it only if this is a "natural" split, not a one that comes from undo.
1559-
// TODO: this should be done on relations
15601581
//
15611582
const moveRange = Range.createFromPositionAndShift( a.sourcePosition, a.howMany );
15621583

1563-
if ( moveRange.end.isEqual( b.insertionPosition ) && !b.graveyardPosition ) {
1564-
a.howMany++;
1584+
if ( moveRange.end.isEqual( b.insertionPosition ) ) {
1585+
// Do it only if this is a "natural" split, not a one that comes from undo.
1586+
// If this is undo split, only `targetPosition` needs to be changed (if the move is a remove).
1587+
if ( !b.graveyardPosition ) {
1588+
a.howMany++;
1589+
}
1590+
15651591
a.targetPosition = newTargetPosition;
15661592

15671593
return [ a ];

src/model/range.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,14 @@ export default class Range {
517517
*/
518518
_getTransformedBySplitOperation( operation ) {
519519
const start = this.start._getTransformedBySplitOperation( operation );
520-
const end = this.end._getTransformedBySplitOperation( operation );
520+
521+
let end;
522+
523+
if ( this.end.isEqual( operation.insertionPosition ) ) {
524+
end = this.end.getShiftedBy( 1 );
525+
} else {
526+
end = this.end._getTransformedBySplitOperation( operation );
527+
}
521528

522529
return new Range( start, end );
523530
}

tests/model/operation/transform/undo.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,4 +275,50 @@ describe( 'transform', () => {
275275
john.redo();
276276
expectClients( '<paragraph>Foo</paragraph>' );
277277
} );
278+
279+
it( 'undo pasting', () => {
280+
john.setData( '<paragraph>Foo[]Bar</paragraph>' );
281+
282+
// Below simulates pasting.
283+
john.editor.model.change( () => {
284+
john.split();
285+
john.setSelection( [ 1 ] );
286+
287+
john.insert( '<paragraph>1</paragraph>' );
288+
john.setSelection( [ 1 ] );
289+
john.merge();
290+
291+
john.setSelection( [ 1 ] );
292+
john.insert( '<paragraph>2</paragraph>' );
293+
john.setSelection( [ 2 ] );
294+
john.merge();
295+
} );
296+
297+
expectClients( '<paragraph>Foo1</paragraph><paragraph>2Bar</paragraph>' );
298+
299+
john.undo();
300+
301+
expectClients( '<paragraph>FooBar</paragraph>' );
302+
} );
303+
304+
it( 'selection attribute setting: split, bold, merge, undo, undo, undo', () => {
305+
// This test is ported from undo to keep 100% CC in engine.
306+
john.setData( '<paragraph>Foo[]</paragraph><paragraph>Bar</paragraph>' );
307+
308+
john.split();
309+
john.setSelection( [ 1, 0 ] );
310+
john._processExecute( 'bold' );
311+
john._processExecute( 'forwardDelete' );
312+
313+
expectClients( '<paragraph>Foo</paragraph><paragraph>Bar</paragraph>' );
314+
315+
john.undo();
316+
expectClients( '<paragraph>Foo</paragraph><paragraph selection:bold="true"></paragraph><paragraph>Bar</paragraph>' );
317+
318+
john.undo();
319+
expectClients( '<paragraph>Foo</paragraph><paragraph></paragraph><paragraph>Bar</paragraph>' );
320+
321+
john.undo();
322+
expectClients( '<paragraph>Foo</paragraph><paragraph>Bar</paragraph>' );
323+
} );
278324
} );

tests/model/operation/transform/utils.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor';
22

33
import ListEditing from '@ckeditor/ckeditor5-list/src/listediting';
4+
import BoldEditing from '@ckeditor/ckeditor5-basic-styles/src/bold/boldediting';
45
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
56
import Typing from '@ckeditor/ckeditor5-typing/src/typing';
67
import UndoEditing from '@ckeditor/ckeditor5-undo/src/undoediting';
@@ -30,7 +31,8 @@ export class Client {
3031
// Typing is needed for delete command.
3132
// UndoEditing is needed for undo command.
3233
// Block plugins are needed for proper data serializing.
33-
plugins: [ Typing, Paragraph, ListEditing, UndoEditing, BlockQuoteEditing, HeadingEditing ]
34+
// BoldEditing is needed for bold command.
35+
plugins: [ Typing, Paragraph, ListEditing, UndoEditing, BlockQuoteEditing, HeadingEditing, BoldEditing ]
3436
} ).then( editor => {
3537
this.editor = editor;
3638
this.document = editor.model.document;

tests/model/range.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,17 @@ describe( 'Range', () => {
10381038
expect( transformed[ 0 ].start.path ).to.deep.equal( [ 1, 3 ] );
10391039
expect( transformed[ 0 ].end.path ).to.deep.equal( [ 1, 3 ] );
10401040
} );
1041+
1042+
it( 'split element which is the last element in the range', () => {
1043+
range = new Range( new Position( root, [ 1 ] ), new Position( root, [ 3 ] ) );
1044+
1045+
const op = new SplitOperation( new Position( root, [ 2, 0 ] ), 6, gyPos, 1 );
1046+
const transformed = range.getTransformedByOperation( op );
1047+
1048+
expect( transformed.length ).to.equal( 1 );
1049+
expect( transformed[ 0 ].start.path ).to.deep.equal( [ 1 ] );
1050+
expect( transformed[ 0 ].end.path ).to.deep.equal( [ 4 ] );
1051+
} );
10411052
} );
10421053

10431054
describe( 'by MergeOperation', () => {

0 commit comments

Comments
 (0)