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

Commit 4c38683

Browse files
authored
Merge pull request #1368 from ckeditor/t/1365
Feature: Introduced `#isBefore` and `#isAfter` in `model.Node` and `view.Node`. Additionally, `model.Node#is` and `view.Node#is` and `view.Node#getPath` were added. Closes #1365.
2 parents f8dec1e + a163b72 commit 4c38683

File tree

18 files changed

+378
-69
lines changed

18 files changed

+378
-69
lines changed

src/model/element.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export default class Element extends Node {
109109
*/
110110
is( type, name = null ) {
111111
if ( !name ) {
112-
return type == 'element' || type == this.name;
112+
return type == 'element' || type == this.name || super.is( type );
113113
} else {
114114
return type == 'element' && name == this.name;
115115
}

src/model/node.js

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import toMap from '@ckeditor/ckeditor5-utils/src/tomap';
1111
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
12+
import compareArrays from '@ckeditor/ckeditor5-utils/src/comparearrays';
1213

1314
/**
1415
* Model node. Most basic structure of model tree.
@@ -273,6 +274,63 @@ export default class Node {
273274
return i === 0 ? null : ancestorsA[ i - 1 ];
274275
}
275276

277+
/**
278+
* Returns whether this node is before given node. `false` is returned if nodes are in different trees (for example,
279+
* in different {@link module:engine/model/documentfragment~DocumentFragment}s).
280+
*
281+
* @param {module:engine/model/node~Node} node Node to compare with.
282+
* @returns {Boolean}
283+
*/
284+
isBefore( node ) {
285+
// Given node is not before this node if they are same.
286+
if ( this == node ) {
287+
return false;
288+
}
289+
290+
// Return `false` if it is impossible to compare nodes.
291+
if ( this.root !== node.root ) {
292+
return false;
293+
}
294+
295+
const thisPath = this.getPath();
296+
const nodePath = node.getPath();
297+
298+
const result = compareArrays( thisPath, nodePath );
299+
300+
switch ( result ) {
301+
case 'prefix':
302+
return true;
303+
304+
case 'extension':
305+
return false;
306+
307+
default:
308+
return thisPath[ result ] < nodePath[ result ];
309+
}
310+
}
311+
312+
/**
313+
* Returns whether this node is after given node. `false` is returned if nodes are in different trees (for example,
314+
* in different {@link module:engine/model/documentfragment~DocumentFragment}s).
315+
*
316+
* @param {module:engine/model/node~Node} node Node to compare with.
317+
* @returns {Boolean}
318+
*/
319+
isAfter( node ) {
320+
// Given node is not before this node if they are same.
321+
if ( this == node ) {
322+
return false;
323+
}
324+
325+
// Return `false` if it is impossible to compare nodes.
326+
if ( this.root !== node.root ) {
327+
return false;
328+
}
329+
330+
// In other cases, just check if the `node` is before, and return the opposite.
331+
return !this.isBefore( node );
332+
}
333+
276334
/**
277335
* Checks if the node has an attribute with given key.
278336
*
@@ -401,7 +459,7 @@ export default class Node {
401459
* may return {@link module:engine/model/documentfragment~DocumentFragment} or {@link module:engine/model/node~Node}
402460
* that can be either text node or element. This method can be used to check what kind of object is returned.
403461
*
404-
* obj.is( 'node' ); // true for any node, false for document fragment
462+
* obj.is( 'node' ); // true for any node, false for document fragment and text fragment
405463
* obj.is( 'documentFragment' ); // true for document fragment, false for any node
406464
* obj.is( 'element' ); // true for any element, false for text node or document fragment
407465
* obj.is( 'element', 'paragraph' ); // true only for element which name is 'paragraph'
@@ -413,6 +471,9 @@ export default class Node {
413471
* @param {'element'|'rootElement'|'text'|'textProxy'|'documentFragment'} type
414472
* @returns {Boolean}
415473
*/
474+
is( type ) {
475+
return type == 'node';
476+
}
416477
}
417478

418479
/**

src/model/position.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -238,11 +238,7 @@ export default class Position {
238238
return 'after';
239239

240240
default:
241-
if ( this.path[ result ] < otherPosition.path[ result ] ) {
242-
return 'before';
243-
} else {
244-
return 'after';
245-
}
241+
return this.path[ result ] < otherPosition.path[ result ] ? 'before' : 'after';
246242
}
247243
}
248244

src/model/text.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export default class Text extends Node {
6565
* @inheritDoc
6666
*/
6767
is( type ) {
68-
return type == 'text';
68+
return type == 'text' || super.is( type );
6969
}
7070

7171
/**

src/view/element.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ export default class Element extends Node {
152152
*/
153153
is( type, name = null ) {
154154
if ( !name ) {
155-
return type == 'element' || type == this.name;
155+
return type == 'element' || type == this.name || super.is( type );
156156
} else {
157157
return type == 'element' && name == this.name;
158158
}

src/view/node.js

Lines changed: 104 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
1111
import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
1212
import mix from '@ckeditor/ckeditor5-utils/src/mix';
1313
import clone from '@ckeditor/ckeditor5-utils/src/lib/lodash/clone';
14+
import compareArrays from '@ckeditor/ckeditor5-utils/src/comparearrays';
1415

1516
/**
1617
* Abstract tree view node class.
@@ -118,6 +119,33 @@ export default class Node {
118119
}
119120
}
120121

122+
/**
123+
* Gets a path to the node. The path is an array containing indices of consecutive ancestors of this node,
124+
* beginning from {@link module:engine/view/node~Node#root root}, down to this node's index.
125+
*
126+
* const abc = new Text( 'abc' );
127+
* const foo = new Text( 'foo' );
128+
* const h1 = new Element( 'h1', null, new Text( 'header' ) );
129+
* const p = new Element( 'p', null, [ abc, foo ] );
130+
* const div = new Element( 'div', null, [ h1, p ] );
131+
* foo.getPath(); // Returns [ 1, 3 ]. `foo` is in `p` which is in `div`. `p` starts at offset 1, while `foo` at 3.
132+
* h1.getPath(); // Returns [ 0 ].
133+
* div.getPath(); // Returns [].
134+
*
135+
* @returns {Array.<Number>} The path.
136+
*/
137+
getPath() {
138+
const path = [];
139+
let node = this; // eslint-disable-line consistent-this
140+
141+
while ( node.parent ) {
142+
path.unshift( node.index );
143+
node = node.parent;
144+
}
145+
146+
return path;
147+
}
148+
121149
/**
122150
* Returns ancestors array of this node.
123151
*
@@ -162,6 +190,63 @@ export default class Node {
162190
return i === 0 ? null : ancestorsA[ i - 1 ];
163191
}
164192

193+
/**
194+
* Returns whether this node is before given node. `false` is returned if nodes are in different trees (for example,
195+
* in different {@link module:engine/view/documentfragment~DocumentFragment}s).
196+
*
197+
* @param {module:engine/view/node~Node} node Node to compare with.
198+
* @returns {Boolean}
199+
*/
200+
isBefore( node ) {
201+
// Given node is not before this node if they are same.
202+
if ( this == node ) {
203+
return false;
204+
}
205+
206+
// Return `false` if it is impossible to compare nodes.
207+
if ( this.root !== node.root ) {
208+
return false;
209+
}
210+
211+
const thisPath = this.getPath();
212+
const nodePath = node.getPath();
213+
214+
const result = compareArrays( thisPath, nodePath );
215+
216+
switch ( result ) {
217+
case 'prefix':
218+
return true;
219+
220+
case 'extension':
221+
return false;
222+
223+
default:
224+
return thisPath[ result ] < nodePath[ result ];
225+
}
226+
}
227+
228+
/**
229+
* Returns whether this node is after given node. `false` is returned if nodes are in different trees (for example,
230+
* in different {@link module:engine/view/documentfragment~DocumentFragment}s).
231+
*
232+
* @param {module:engine/view/node~Node} node Node to compare with.
233+
* @returns {Boolean}
234+
*/
235+
isAfter( node ) {
236+
// Given node is not before this node if they are same.
237+
if ( this == node ) {
238+
return false;
239+
}
240+
241+
// Return `false` if it is impossible to compare nodes.
242+
if ( this.root !== node.root ) {
243+
return false;
244+
}
245+
246+
// In other cases, just check if the `node` is before, and return the opposite.
247+
return !this.isBefore( node );
248+
}
249+
165250
/**
166251
* Removes node from parent.
167252
*
@@ -198,28 +283,14 @@ export default class Node {
198283
return json;
199284
}
200285

201-
/**
202-
* Clones this node.
203-
*
204-
* @method #clone
205-
* @returns {module:engine/view/node~Node} Clone of this node.
206-
*/
207-
208-
/**
209-
* Checks if provided node is similar to this node.
210-
*
211-
* @method #isSimilar
212-
* @returns {Boolean} True if nodes are similar.
213-
*/
214-
215286
/**
216287
* Checks whether given view tree object is of given type.
217288
*
218289
* This method is useful when processing view tree objects that are of unknown type. For example, a function
219290
* may return {@link module:engine/view/documentfragment~DocumentFragment} or {@link module:engine/view/node~Node}
220291
* that can be either text node or element. This method can be used to check what kind of object is returned.
221292
*
222-
* obj.is( 'node' ); // true for any node, false for document fragment
293+
* obj.is( 'node' ); // true for any node, false for document fragment and text fragment
223294
* obj.is( 'documentFragment' ); // true for document fragment, false for any node
224295
* obj.is( 'element' ); // true for any element, false for text node or document fragment
225296
* obj.is( 'element', 'p' ); // true only for element which name is 'p'
@@ -231,6 +302,24 @@ export default class Node {
231302
* 'rootElement'|'documentFragment'|'text'|'textProxy'} type
232303
* @returns {Boolean}
233304
*/
305+
is( type ) {
306+
return type == 'node';
307+
}
308+
309+
/**
310+
* Clones this node.
311+
*
312+
* @protected
313+
* @method #_clone
314+
* @returns {module:engine/view/node~Node} Clone of this node.
315+
*/
316+
317+
/**
318+
* Checks if provided node is similar to this node.
319+
*
320+
* @method #isSimilar
321+
* @returns {Boolean} True if nodes are similar.
322+
*/
234323
}
235324

236325
/**

src/view/position.js

Lines changed: 14 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -243,61 +243,35 @@ export default class Position {
243243
* @returns {module:engine/view/position~PositionRelation}
244244
*/
245245
compareWith( otherPosition ) {
246-
if ( this.isEqual( otherPosition ) ) {
247-
return 'same';
246+
if ( this.root !== otherPosition.root ) {
247+
return 'different';
248248
}
249249

250-
// If positions have same parent.
251-
if ( this.parent === otherPosition.parent ) {
252-
return this.offset - otherPosition.offset < 0 ? 'before' : 'after';
250+
if ( this.isEqual( otherPosition ) ) {
251+
return 'same';
253252
}
254253

255254
// Get path from root to position's parent element.
256-
const path = this.getAncestors();
257-
const otherPath = otherPosition.getAncestors();
255+
const thisPath = this.parent.is( 'node' ) ? this.parent.getPath() : [];
256+
const otherPath = otherPosition.parent.is( 'node' ) ? otherPosition.parent.getPath() : [];
258257

259-
// Compare both path arrays to find common ancestor.
260-
const result = compareArrays( path, otherPath );
258+
// Add the positions' offsets to the parents offsets.
259+
thisPath.push( this.offset );
260+
otherPath.push( otherPosition.offset );
261261

262-
let commonAncestorIndex;
262+
// Compare both path arrays to find common ancestor.
263+
const result = compareArrays( thisPath, otherPath );
263264

264265
switch ( result ) {
265-
case 0:
266-
// No common ancestors found.
267-
return 'different';
268-
269266
case 'prefix':
270-
commonAncestorIndex = path.length - 1;
271-
break;
267+
return 'before';
272268

273269
case 'extension':
274-
commonAncestorIndex = otherPath.length - 1;
275-
break;
270+
return 'after';
276271

277272
default:
278-
commonAncestorIndex = result - 1;
273+
return thisPath[ result ] < otherPath[ result ] ? 'before' : 'after';
279274
}
280-
281-
// Common ancestor of two positions.
282-
const commonAncestor = path[ commonAncestorIndex ];
283-
const nextAncestor1 = path[ commonAncestorIndex + 1 ];
284-
const nextAncestor2 = otherPath[ commonAncestorIndex + 1 ];
285-
286-
// Check if common ancestor is not one of the parents.
287-
if ( commonAncestor === this.parent ) {
288-
const index = this.offset - nextAncestor2.index;
289-
290-
return index <= 0 ? 'before' : 'after';
291-
} else if ( commonAncestor === otherPosition.parent ) {
292-
const index = nextAncestor1.index - otherPosition.offset;
293-
294-
return index < 0 ? 'before' : 'after';
295-
}
296-
297-
const index = nextAncestor1.index - nextAncestor2.index;
298-
299-
// Compare indexes of next ancestors inside common one.
300-
return index < 0 ? 'before' : 'after';
301275
}
302276

303277
/**

src/view/text.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export default class Text extends Node {
4242
* @inheritDoc
4343
*/
4444
is( type ) {
45-
return type == 'text';
45+
return type == 'text' || super.is( type );
4646
}
4747

4848
/**

tests/model/documentfragment.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ describe( 'DocumentFragment', () => {
7474
} );
7575

7676
it( 'should return false for other accept values', () => {
77+
expect( frag.is( 'node' ) ).to.be.false;
7778
expect( frag.is( 'text' ) ).to.be.false;
7879
expect( frag.is( 'textProxy' ) ).to.be.false;
7980
expect( frag.is( 'element' ) ).to.be.false;

tests/model/element.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ describe( 'Element', () => {
4646
element = new Element( 'paragraph' );
4747
} );
4848

49-
it( 'should return true for element, element with same name and element name', () => {
49+
it( 'should return true for node, element, element with same name and element name', () => {
50+
expect( element.is( 'node' ) ).to.be.true;
5051
expect( element.is( 'element' ) ).to.be.true;
5152
expect( element.is( 'element', 'paragraph' ) ).to.be.true;
5253
expect( element.is( 'paragraph' ) ).to.be.true;

0 commit comments

Comments
 (0)