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

Commit f5d68f4

Browse files
authored
Merge pull request #54 from ckeditor/t/53
Fix: Fixed integration with undo. Closes #53.
2 parents 62e2627 + 9024f2b commit f5d68f4

File tree

5 files changed

+258
-110
lines changed

5 files changed

+258
-110
lines changed

src/blockautoformatediting.js

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -64,34 +64,35 @@ export default class BlockAutoformatEditing {
6464
}
6565

6666
editor.model.document.on( 'change', () => {
67-
const changes = editor.model.document.differ.getChanges();
67+
const changes = Array.from( editor.model.document.differ.getChanges() );
68+
const entry = changes[ 0 ];
6869

69-
for ( const entry of changes ) {
70-
if ( entry.type == 'insert' && entry.name == '$text' ) {
71-
const item = entry.position.textNode || entry.position.nodeAfter;
70+
// Typing is represented by only a single change.
71+
if ( changes.length != 1 || entry.type !== 'insert' || entry.name != '$text' || entry.length != 1 ) {
72+
return;
73+
}
74+
const item = entry.position.textNode || entry.position.nodeAfter;
7275

73-
if ( !item.parent.is( 'paragraph' ) ) {
74-
continue;
75-
}
76+
if ( !item.parent.is( 'paragraph' ) ) {
77+
return;
78+
}
7679

77-
const match = pattern.exec( item.data );
80+
const match = pattern.exec( item.data );
7881

79-
if ( !match ) {
80-
continue;
81-
}
82+
if ( !match ) {
83+
return;
84+
}
8285

83-
// Use enqueueChange to create new batch to separate typing batch from the auto-format changes.
84-
editor.model.enqueueChange( writer => {
85-
// Matched range.
86-
const range = Range.createFromParentsAndOffsets( item.parent, 0, item.parent, match[ 0 ].length );
86+
// Use enqueueChange to create new batch to separate typing batch from the auto-format changes.
87+
editor.model.enqueueChange( writer => {
88+
// Matched range.
89+
const range = Range.createFromParentsAndOffsets( item.parent, 0, item.parent, match[ 0 ].length );
8790

88-
// Remove matched text.
89-
writer.remove( range );
91+
// Remove matched text.
92+
writer.remove( range );
9093

91-
callback( { match } );
92-
} );
93-
}
94-
}
94+
callback( { match } );
95+
} );
9596
} );
9697
}
9798
}

src/inlineautoformatediting.js

Lines changed: 42 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @module autoformat/inlineautoformatediting
88
*/
99

10-
import LiveRange from '@ckeditor/ckeditor5-engine/src/model/liverange';
10+
import ModelRange from '@ckeditor/ckeditor5-engine/src/model/range';
1111

1212
/**
1313
* The inline autoformatting engine. It allows to format various inline patterns. For example,
@@ -141,74 +141,43 @@ export default class InlineAutoformatEditing {
141141
} );
142142

143143
editor.model.document.on( 'change', () => {
144-
const changes = editor.model.document.differ.getChanges();
144+
const selection = editor.model.document.selection;
145145

146-
for ( const entry of changes ) {
147-
if ( entry.type != 'insert' || entry.name != '$text' ) {
148-
continue;
149-
}
150-
151-
const selection = editor.model.document.selection;
146+
// Do nothing if selection is not collapsed.
147+
if ( !selection.isCollapsed ) {
148+
return;
149+
}
152150

153-
if ( !selection.isCollapsed || !selection.focus || !selection.focus.parent ) {
154-
continue;
155-
}
151+
const changes = Array.from( editor.model.document.differ.getChanges() );
152+
const entry = changes[ 0 ];
156153

157-
const block = selection.focus.parent;
158-
const text = getText( block ).slice( 0, selection.focus.offset );
159-
const ranges = testCallback( text );
160-
const rangesToFormat = [];
161-
162-
// Apply format before deleting text.
163-
ranges.format.forEach( range => {
164-
if ( range[ 0 ] === undefined || range[ 1 ] === undefined ) {
165-
return;
166-
}
167-
168-
rangesToFormat.push( LiveRange.createFromParentsAndOffsets(
169-
block, range[ 0 ],
170-
block, range[ 1 ]
171-
) );
172-
} );
173-
174-
const rangesToRemove = [];
175-
176-
// Reverse order to not mix the offsets while removing.
177-
ranges.remove.slice().reverse().forEach( range => {
178-
if ( range[ 0 ] === undefined || range[ 1 ] === undefined ) {
179-
return;
180-
}
181-
182-
rangesToRemove.push( LiveRange.createFromParentsAndOffsets(
183-
block, range[ 0 ],
184-
block, range[ 1 ]
185-
) );
186-
} );
187-
188-
if ( !( rangesToFormat.length && rangesToRemove.length ) ) {
189-
continue;
190-
}
154+
// Typing is represented by only a single change.
155+
if ( changes.length != 1 || entry.type !== 'insert' || entry.name != '$text' || entry.length != 1 ) {
156+
return;
157+
}
191158

192-
// Use enqueueChange to create new batch to separate typing batch from the auto-format changes.
193-
editor.model.enqueueChange( writer => {
194-
const validRanges = editor.model.schema.getValidRanges( rangesToFormat, attributeKey );
159+
const block = selection.focus.parent;
160+
const text = getText( block ).slice( 0, selection.focus.offset );
161+
const testOutput = testCallback( text );
162+
const rangesToFormat = testOutputToRanges( block, testOutput.format );
163+
const rangesToRemove = testOutputToRanges( block, testOutput.remove );
195164

196-
// Apply format.
197-
formatCallback( writer, validRanges );
165+
if ( !( rangesToFormat.length && rangesToRemove.length ) ) {
166+
return;
167+
}
198168

199-
// Detach ranges used to apply Autoformat. Prevents memory leaks. #39
200-
rangesToFormat.forEach( range => range.detach() );
169+
// Use enqueueChange to create new batch to separate typing batch from the auto-format changes.
170+
editor.model.enqueueChange( writer => {
171+
const validRanges = editor.model.schema.getValidRanges( rangesToFormat, attributeKey );
201172

202-
// Remove delimiters.
203-
for ( const range of rangesToRemove ) {
204-
writer.remove( range );
173+
// Apply format.
174+
formatCallback( writer, validRanges );
205175

206-
// Prevents memory leaks.
207-
// https://github.com/ckeditor/ckeditor5-autoformat/issues/39
208-
range.detach();
209-
}
210-
} );
211-
}
176+
// Remove delimiters - use reversed order to not mix the offsets while removing.
177+
for ( const range of rangesToRemove.reverse() ) {
178+
writer.remove( range );
179+
}
180+
} );
212181
} );
213182
}
214183
}
@@ -221,3 +190,15 @@ export default class InlineAutoformatEditing {
221190
function getText( element ) {
222191
return Array.from( element.getChildren() ).reduce( ( a, b ) => a + b.data, '' );
223192
}
193+
194+
// Converts output of the test function provided to the InlineAutoformatEditing and converts it to the model ranges
195+
// inside provided block.
196+
//
197+
// @private
198+
// @param {module:engine/model/element~Element} block
199+
// @param {Array.<Array>} arrays
200+
function testOutputToRanges( block, arrays ) {
201+
return arrays
202+
.filter( array => ( array[ 0 ] !== undefined && array[ 1 ] !== undefined ) )
203+
.map( array => ModelRange.createFromParentsAndOffsets( block, array[ 0 ], block, array[ 1 ] ) );
204+
}

tests/autoformat.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ describe( 'Autoformat', () => {
190190
} );
191191

192192
describe( 'Block quote', () => {
193-
it( 'should replace greater-than character with heading', () => {
193+
it( 'should replace greater-than character with block quote', () => {
194194
setData( model, '<paragraph>>[]</paragraph>' );
195195
model.change( writer => {
196196
writer.insertText( ' ', doc.selection.getFirstPosition() );

tests/inlineautoformatediting.js

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -115,32 +115,5 @@ describe( 'InlineAutoformatEditing', () => {
115115

116116
sinon.assert.notCalled( formatSpy );
117117
} );
118-
119-
it( 'should detach removed ranges', () => {
120-
const detachSpies = [];
121-
const callback = fixBatch => testUtils.sinon.stub( fixBatch, 'remove' ).callsFake( saveDetachSpy );
122-
testUtils.sinon.stub( editor.model.schema, 'getValidRanges' )
123-
.callThrough()
124-
.callsFake( ranges => ranges.map( saveDetachSpy ) );
125-
126-
new InlineAutoformatEditing( editor, /(\*)(.+?)(\*)/g, callback ); // eslint-disable-line no-new
127-
128-
setData( model, '<paragraph>*foobar[]</paragraph>' );
129-
130-
model.change( writer => {
131-
writer.insertText( '*', doc.selection.getFirstPosition() );
132-
} );
133-
134-
// There should be two removed ranges and one range used to apply autoformat.
135-
expect( detachSpies ).to.have.length( 3 );
136-
137-
for ( const spy of detachSpies ) {
138-
testUtils.sinon.assert.calledOnce( spy );
139-
}
140-
141-
function saveDetachSpy( range ) {
142-
detachSpies.push( testUtils.sinon.spy( range, 'detach' ) );
143-
}
144-
} );
145118
} );
146119
} );

0 commit comments

Comments
 (0)