@ckeditor/ckeditor5-engine 34.2.0 → 35.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +823 -0
- package/LICENSE.md +4 -0
- package/package.json +32 -25
- package/src/controller/datacontroller.js +467 -561
- package/src/controller/editingcontroller.js +168 -204
- package/src/conversion/conversion.js +541 -565
- package/src/conversion/conversionhelpers.js +24 -28
- package/src/conversion/downcastdispatcher.js +457 -686
- package/src/conversion/downcasthelpers.js +1583 -1965
- package/src/conversion/mapper.js +518 -707
- package/src/conversion/modelconsumable.js +240 -283
- package/src/conversion/upcastdispatcher.js +372 -718
- package/src/conversion/upcasthelpers.js +707 -818
- package/src/conversion/viewconsumable.js +524 -581
- package/src/dataprocessor/basichtmlwriter.js +12 -16
- package/src/dataprocessor/dataprocessor.js +5 -0
- package/src/dataprocessor/htmldataprocessor.js +101 -117
- package/src/dataprocessor/htmlwriter.js +1 -18
- package/src/dataprocessor/xmldataprocessor.js +117 -138
- package/src/dev-utils/model.js +260 -352
- package/src/dev-utils/operationreplayer.js +106 -126
- package/src/dev-utils/utils.js +34 -51
- package/src/dev-utils/view.js +632 -753
- package/src/index.js +0 -11
- package/src/model/batch.js +111 -127
- package/src/model/differ.js +988 -1233
- package/src/model/document.js +340 -449
- package/src/model/documentfragment.js +327 -364
- package/src/model/documentselection.js +996 -1189
- package/src/model/element.js +306 -410
- package/src/model/history.js +224 -262
- package/src/model/item.js +5 -0
- package/src/model/liveposition.js +84 -145
- package/src/model/liverange.js +108 -185
- package/src/model/markercollection.js +379 -480
- package/src/model/model.js +883 -1034
- package/src/model/node.js +419 -463
- package/src/model/nodelist.js +175 -201
- package/src/model/operation/attributeoperation.js +153 -182
- package/src/model/operation/detachoperation.js +64 -83
- package/src/model/operation/insertoperation.js +135 -166
- package/src/model/operation/markeroperation.js +114 -140
- package/src/model/operation/mergeoperation.js +163 -191
- package/src/model/operation/moveoperation.js +157 -187
- package/src/model/operation/nooperation.js +28 -38
- package/src/model/operation/operation.js +106 -125
- package/src/model/operation/operationfactory.js +30 -34
- package/src/model/operation/renameoperation.js +109 -135
- package/src/model/operation/rootattributeoperation.js +155 -188
- package/src/model/operation/splitoperation.js +196 -232
- package/src/model/operation/transform.js +1833 -2204
- package/src/model/operation/utils.js +140 -204
- package/src/model/position.js +899 -1053
- package/src/model/range.js +910 -1028
- package/src/model/rootelement.js +77 -97
- package/src/model/schema.js +1189 -1835
- package/src/model/selection.js +745 -862
- package/src/model/text.js +90 -114
- package/src/model/textproxy.js +204 -240
- package/src/model/treewalker.js +316 -397
- package/src/model/typecheckable.js +16 -0
- package/src/model/utils/autoparagraphing.js +32 -44
- package/src/model/utils/deletecontent.js +334 -418
- package/src/model/utils/findoptimalinsertionrange.js +25 -36
- package/src/model/utils/getselectedcontent.js +96 -118
- package/src/model/utils/insertcontent.js +654 -773
- package/src/model/utils/insertobject.js +96 -119
- package/src/model/utils/modifyselection.js +120 -158
- package/src/model/utils/selection-post-fixer.js +153 -201
- package/src/model/writer.js +1305 -1474
- package/src/view/attributeelement.js +189 -225
- package/src/view/containerelement.js +75 -85
- package/src/view/document.js +172 -215
- package/src/view/documentfragment.js +200 -249
- package/src/view/documentselection.js +338 -367
- package/src/view/domconverter.js +1371 -1613
- package/src/view/downcastwriter.js +1747 -2076
- package/src/view/editableelement.js +81 -97
- package/src/view/element.js +739 -890
- package/src/view/elementdefinition.js +5 -0
- package/src/view/emptyelement.js +82 -92
- package/src/view/filler.js +35 -50
- package/src/view/item.js +5 -0
- package/src/view/matcher.js +260 -559
- package/src/view/node.js +274 -360
- package/src/view/observer/arrowkeysobserver.js +19 -28
- package/src/view/observer/bubblingemittermixin.js +120 -263
- package/src/view/observer/bubblingeventinfo.js +47 -55
- package/src/view/observer/clickobserver.js +7 -13
- package/src/view/observer/compositionobserver.js +14 -24
- package/src/view/observer/domeventdata.js +57 -67
- package/src/view/observer/domeventobserver.js +40 -64
- package/src/view/observer/fakeselectionobserver.js +81 -96
- package/src/view/observer/focusobserver.js +45 -61
- package/src/view/observer/inputobserver.js +7 -13
- package/src/view/observer/keyobserver.js +17 -27
- package/src/view/observer/mouseobserver.js +7 -14
- package/src/view/observer/mutationobserver.js +220 -315
- package/src/view/observer/observer.js +81 -102
- package/src/view/observer/selectionobserver.js +191 -246
- package/src/view/observer/tabobserver.js +23 -36
- package/src/view/placeholder.js +128 -173
- package/src/view/position.js +350 -401
- package/src/view/range.js +453 -513
- package/src/view/rawelement.js +85 -112
- package/src/view/renderer.js +874 -1014
- package/src/view/rooteditableelement.js +80 -90
- package/src/view/selection.js +608 -689
- package/src/view/styles/background.js +43 -44
- package/src/view/styles/border.js +220 -276
- package/src/view/styles/margin.js +8 -17
- package/src/view/styles/padding.js +8 -16
- package/src/view/styles/utils.js +127 -160
- package/src/view/stylesmap.js +728 -905
- package/src/view/text.js +102 -126
- package/src/view/textproxy.js +144 -170
- package/src/view/treewalker.js +383 -479
- package/src/view/typecheckable.js +19 -0
- package/src/view/uielement.js +166 -187
- package/src/view/upcastwriter.js +395 -449
- package/src/view/view.js +569 -664
- package/src/dataprocessor/dataprocessor.jsdoc +0 -64
- package/src/model/item.jsdoc +0 -14
- package/src/view/elementdefinition.jsdoc +0 -59
- package/src/view/item.jsdoc +0 -14
package/src/model/position.js
CHANGED
|
@@ -2,18 +2,15 @@
|
|
|
2
2
|
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
5
|
/**
|
|
7
6
|
* @module engine/model/position
|
|
8
7
|
*/
|
|
9
|
-
|
|
8
|
+
import TypeCheckable from './typecheckable';
|
|
10
9
|
import TreeWalker from './treewalker';
|
|
11
10
|
import compareArrays from '@ckeditor/ckeditor5-utils/src/comparearrays';
|
|
12
11
|
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
|
|
13
|
-
|
|
14
12
|
// To check if component is loaded more than once.
|
|
15
13
|
import '@ckeditor/ckeditor5-utils/src/version';
|
|
16
|
-
|
|
17
14
|
/**
|
|
18
15
|
* Represents a position in the model tree.
|
|
19
16
|
*
|
|
@@ -41,1040 +38,895 @@ import '@ckeditor/ckeditor5-utils/src/version';
|
|
|
41
38
|
*
|
|
42
39
|
* In most cases, position with wrong path is caused by an error in code, but it is sometimes needed, as described above.
|
|
43
40
|
*/
|
|
44
|
-
export default class Position {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
if ( offset == 'end' ) {
|
|
916
|
-
offset = node.maxOffset;
|
|
917
|
-
} else if ( offset == 'before' ) {
|
|
918
|
-
return this._createBefore( node, stickiness );
|
|
919
|
-
} else if ( offset == 'after' ) {
|
|
920
|
-
return this._createAfter( node, stickiness );
|
|
921
|
-
} else if ( offset !== 0 && !offset ) {
|
|
922
|
-
/**
|
|
923
|
-
* {@link module:engine/model/model~Model#createPositionAt `Model#createPositionAt()`}
|
|
924
|
-
* requires the offset to be specified when the first parameter is a model item.
|
|
925
|
-
*
|
|
926
|
-
* @error model-createpositionat-offset-required
|
|
927
|
-
*/
|
|
928
|
-
throw new CKEditorError( 'model-createpositionat-offset-required', [ this, itemOrPosition ] );
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
if ( !node.is( 'element' ) && !node.is( 'documentFragment' ) ) {
|
|
932
|
-
/**
|
|
933
|
-
* Position parent have to be a model element or model document fragment.
|
|
934
|
-
*
|
|
935
|
-
* @error model-position-parent-incorrect
|
|
936
|
-
*/
|
|
937
|
-
throw new CKEditorError(
|
|
938
|
-
'model-position-parent-incorrect',
|
|
939
|
-
[ this, itemOrPosition ]
|
|
940
|
-
);
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
const path = node.getPath();
|
|
944
|
-
|
|
945
|
-
path.push( offset );
|
|
946
|
-
|
|
947
|
-
return new this( node.root, path, stickiness );
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
/**
|
|
952
|
-
* Creates a new position, after given {@link module:engine/model/item~Item model item}.
|
|
953
|
-
*
|
|
954
|
-
* @param {module:engine/model/item~Item} item Item after which the position should be placed.
|
|
955
|
-
* @param {module:engine/model/position~PositionStickiness} [stickiness='toNone'] Position stickiness.
|
|
956
|
-
* @returns {module:engine/model/position~Position}
|
|
957
|
-
* @protected
|
|
958
|
-
*/
|
|
959
|
-
static _createAfter( item, stickiness ) {
|
|
960
|
-
if ( !item.parent ) {
|
|
961
|
-
/**
|
|
962
|
-
* You can not make a position after a root element.
|
|
963
|
-
*
|
|
964
|
-
* @error model-position-after-root
|
|
965
|
-
* @param {module:engine/model/item~Item} root
|
|
966
|
-
*/
|
|
967
|
-
throw new CKEditorError(
|
|
968
|
-
'model-position-after-root',
|
|
969
|
-
[ this, item ],
|
|
970
|
-
{ root: item }
|
|
971
|
-
);
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
return this._createAt( item.parent, item.endOffset, stickiness );
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
/**
|
|
978
|
-
* Creates a new position, before the given {@link module:engine/model/item~Item model item}.
|
|
979
|
-
*
|
|
980
|
-
* @param {module:engine/model/item~Item} item Item before which the position should be placed.
|
|
981
|
-
* @param {module:engine/model/position~PositionStickiness} [stickiness='toNone'] Position stickiness.
|
|
982
|
-
* @returns {module:engine/model/position~Position}
|
|
983
|
-
* @protected
|
|
984
|
-
*/
|
|
985
|
-
static _createBefore( item, stickiness ) {
|
|
986
|
-
if ( !item.parent ) {
|
|
987
|
-
/**
|
|
988
|
-
* You can not make a position before a root element.
|
|
989
|
-
*
|
|
990
|
-
* @error model-position-before-root
|
|
991
|
-
* @param {module:engine/model/item~Item} root
|
|
992
|
-
*/
|
|
993
|
-
throw new CKEditorError(
|
|
994
|
-
'model-position-before-root',
|
|
995
|
-
item,
|
|
996
|
-
{ root: item }
|
|
997
|
-
);
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
return this._createAt( item.parent, item.startOffset, stickiness );
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
/**
|
|
1004
|
-
* Creates a `Position` instance from given plain object (i.e. parsed JSON string).
|
|
1005
|
-
*
|
|
1006
|
-
* @param {Object} json Plain object to be converted to `Position`.
|
|
1007
|
-
* @param {module:engine/model/document~Document} doc Document object that will be position owner.
|
|
1008
|
-
* @returns {module:engine/model/position~Position} `Position` instance created using given plain object.
|
|
1009
|
-
*/
|
|
1010
|
-
static fromJSON( json, doc ) {
|
|
1011
|
-
if ( json.root === '$graveyard' ) {
|
|
1012
|
-
const pos = new Position( doc.graveyard, json.path );
|
|
1013
|
-
pos.stickiness = json.stickiness;
|
|
1014
|
-
|
|
1015
|
-
return pos;
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
if ( !doc.getRoot( json.root ) ) {
|
|
1019
|
-
/**
|
|
1020
|
-
* Cannot create position for document. Root with specified name does not exist.
|
|
1021
|
-
*
|
|
1022
|
-
* @error model-position-fromjson-no-root
|
|
1023
|
-
* @param {String} rootName
|
|
1024
|
-
*/
|
|
1025
|
-
throw new CKEditorError(
|
|
1026
|
-
'model-position-fromjson-no-root',
|
|
1027
|
-
doc,
|
|
1028
|
-
{ rootName: json.root }
|
|
1029
|
-
);
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
return new Position( doc.getRoot( json.root ), json.path, json.stickiness );
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
// @if CK_DEBUG_ENGINE // toString() {
|
|
1036
|
-
// @if CK_DEBUG_ENGINE // return `${ this.root } [ ${ this.path.join( ', ' ) } ]`;
|
|
1037
|
-
// @if CK_DEBUG_ENGINE // }
|
|
1038
|
-
|
|
1039
|
-
// @if CK_DEBUG_ENGINE // log() {
|
|
1040
|
-
// @if CK_DEBUG_ENGINE // console.log( 'ModelPosition: ' + this );
|
|
1041
|
-
// @if CK_DEBUG_ENGINE // }
|
|
41
|
+
export default class Position extends TypeCheckable {
|
|
42
|
+
/**
|
|
43
|
+
* Creates a position.
|
|
44
|
+
*
|
|
45
|
+
* @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} root Root of the position.
|
|
46
|
+
* @param {Array.<Number>} path Position path. See {@link module:engine/model/position~Position#path}.
|
|
47
|
+
* @param {module:engine/model/position~PositionStickiness} [stickiness='toNone'] Position stickiness.
|
|
48
|
+
* See {@link module:engine/model/position~PositionStickiness}.
|
|
49
|
+
*/
|
|
50
|
+
constructor(root, path, stickiness = 'toNone') {
|
|
51
|
+
super();
|
|
52
|
+
if (!root.is('element') && !root.is('documentFragment')) {
|
|
53
|
+
/**
|
|
54
|
+
* Position root is invalid.
|
|
55
|
+
*
|
|
56
|
+
* Positions can only be anchored in elements or document fragments.
|
|
57
|
+
*
|
|
58
|
+
* @error model-position-root-invalid
|
|
59
|
+
*/
|
|
60
|
+
throw new CKEditorError('model-position-root-invalid', root);
|
|
61
|
+
}
|
|
62
|
+
if (!(path instanceof Array) || path.length === 0) {
|
|
63
|
+
/**
|
|
64
|
+
* Position path must be an array with at least one item.
|
|
65
|
+
*
|
|
66
|
+
* @error model-position-path-incorrect-format
|
|
67
|
+
* @param path
|
|
68
|
+
*/
|
|
69
|
+
throw new CKEditorError('model-position-path-incorrect-format', root, { path });
|
|
70
|
+
}
|
|
71
|
+
// Normalize the root and path when element (not root) is passed.
|
|
72
|
+
if (root.is('rootElement')) {
|
|
73
|
+
path = path.slice();
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
path = [...root.getPath(), ...path];
|
|
77
|
+
root = root.root;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Root of the position path.
|
|
81
|
+
*
|
|
82
|
+
* @readonly
|
|
83
|
+
* @member {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment}
|
|
84
|
+
* module:engine/model/position~Position#root
|
|
85
|
+
*/
|
|
86
|
+
this.root = root;
|
|
87
|
+
/**
|
|
88
|
+
* Position of the node in the tree. **Path contains offsets, not indexes.**
|
|
89
|
+
*
|
|
90
|
+
* Position can be placed before, after or in a {@link module:engine/model/node~Node node} if that node has
|
|
91
|
+
* {@link module:engine/model/node~Node#offsetSize} greater than `1`. Items in position path are
|
|
92
|
+
* {@link module:engine/model/node~Node#startOffset starting offsets} of position ancestors, starting from direct root children,
|
|
93
|
+
* down to the position offset in it's parent.
|
|
94
|
+
*
|
|
95
|
+
* ROOT
|
|
96
|
+
* |- P before: [ 0 ] after: [ 1 ]
|
|
97
|
+
* |- UL before: [ 1 ] after: [ 2 ]
|
|
98
|
+
* |- LI before: [ 1, 0 ] after: [ 1, 1 ]
|
|
99
|
+
* | |- foo before: [ 1, 0, 0 ] after: [ 1, 0, 3 ]
|
|
100
|
+
* |- LI before: [ 1, 1 ] after: [ 1, 2 ]
|
|
101
|
+
* |- bar before: [ 1, 1, 0 ] after: [ 1, 1, 3 ]
|
|
102
|
+
*
|
|
103
|
+
* `foo` and `bar` are representing {@link module:engine/model/text~Text text nodes}. Since text nodes has offset size
|
|
104
|
+
* greater than `1` you can place position offset between their start and end:
|
|
105
|
+
*
|
|
106
|
+
* ROOT
|
|
107
|
+
* |- P
|
|
108
|
+
* |- UL
|
|
109
|
+
* |- LI
|
|
110
|
+
* | |- f^o|o ^ has path: [ 1, 0, 1 ] | has path: [ 1, 0, 2 ]
|
|
111
|
+
* |- LI
|
|
112
|
+
* |- b^a|r ^ has path: [ 1, 1, 1 ] | has path: [ 1, 1, 2 ]
|
|
113
|
+
*
|
|
114
|
+
* @readonly
|
|
115
|
+
* @member {Array.<Number>} module:engine/model/position~Position#path
|
|
116
|
+
*/
|
|
117
|
+
this.path = path;
|
|
118
|
+
/**
|
|
119
|
+
* Position stickiness. See {@link module:engine/model/position~PositionStickiness}.
|
|
120
|
+
*
|
|
121
|
+
* @member {module:engine/model/position~PositionStickiness} module:engine/model/position~Position#stickiness
|
|
122
|
+
*/
|
|
123
|
+
this.stickiness = stickiness;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Offset at which this position is located in its {@link module:engine/model/position~Position#parent parent}. It is equal
|
|
127
|
+
* to the last item in position {@link module:engine/model/position~Position#path path}.
|
|
128
|
+
*
|
|
129
|
+
* @type {Number}
|
|
130
|
+
*/
|
|
131
|
+
get offset() {
|
|
132
|
+
return this.path[this.path.length - 1];
|
|
133
|
+
}
|
|
134
|
+
set offset(newOffset) {
|
|
135
|
+
this.path[this.path.length - 1] = newOffset;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Parent element of this position.
|
|
139
|
+
*
|
|
140
|
+
* Keep in mind that `parent` value is calculated when the property is accessed.
|
|
141
|
+
* If {@link module:engine/model/position~Position#path position path}
|
|
142
|
+
* leads to a non-existing element, `parent` property will throw error.
|
|
143
|
+
*
|
|
144
|
+
* Also it is a good idea to cache `parent` property if it is used frequently in an algorithm (i.e. in a long loop).
|
|
145
|
+
*
|
|
146
|
+
* @readonly
|
|
147
|
+
* @type {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment}
|
|
148
|
+
*/
|
|
149
|
+
get parent() {
|
|
150
|
+
let parent = this.root;
|
|
151
|
+
for (let i = 0; i < this.path.length - 1; i++) {
|
|
152
|
+
parent = parent.getChild(parent.offsetToIndex(this.path[i]));
|
|
153
|
+
if (!parent) {
|
|
154
|
+
/**
|
|
155
|
+
* The position's path is incorrect. This means that a position does not point to
|
|
156
|
+
* a correct place in the tree and hence, some of its methods and getters cannot work correctly.
|
|
157
|
+
*
|
|
158
|
+
* **Note**: Unlike DOM and view positions, in the model, the
|
|
159
|
+
* {@link module:engine/model/position~Position#parent position's parent} is always an element or a document fragment.
|
|
160
|
+
* The last offset in the {@link module:engine/model/position~Position#path position's path} is the point in this element
|
|
161
|
+
* where this position points.
|
|
162
|
+
*
|
|
163
|
+
* Read more about model positions and offsets in
|
|
164
|
+
* the {@glink framework/guides/architecture/editing-engine#indexes-and-offsets Editing engine architecture guide}.
|
|
165
|
+
*
|
|
166
|
+
* @error model-position-path-incorrect
|
|
167
|
+
* @param {module:engine/model/position~Position} position The incorrect position.
|
|
168
|
+
*/
|
|
169
|
+
throw new CKEditorError('model-position-path-incorrect', this, { position: this });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (parent.is('$text')) {
|
|
173
|
+
throw new CKEditorError('model-position-path-incorrect', this, { position: this });
|
|
174
|
+
}
|
|
175
|
+
return parent;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Position {@link module:engine/model/position~Position#offset offset} converted to an index in position's parent node. It is
|
|
179
|
+
* equal to the {@link module:engine/model/node~Node#index index} of a node after this position. If position is placed
|
|
180
|
+
* in text node, position index is equal to the index of that text node.
|
|
181
|
+
*
|
|
182
|
+
* @readonly
|
|
183
|
+
* @type {Number}
|
|
184
|
+
*/
|
|
185
|
+
get index() {
|
|
186
|
+
return this.parent.offsetToIndex(this.offset);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Returns {@link module:engine/model/text~Text text node} instance in which this position is placed or `null` if this
|
|
190
|
+
* position is not in a text node.
|
|
191
|
+
*
|
|
192
|
+
* @readonly
|
|
193
|
+
* @type {module:engine/model/text~Text|null}
|
|
194
|
+
*/
|
|
195
|
+
get textNode() {
|
|
196
|
+
return getTextNodeAtPosition(this, this.parent);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Node directly after this position or `null` if this position is in text node.
|
|
200
|
+
*
|
|
201
|
+
* @readonly
|
|
202
|
+
* @type {module:engine/model/node~Node|null}
|
|
203
|
+
*/
|
|
204
|
+
get nodeAfter() {
|
|
205
|
+
// Cache the parent and reuse for performance reasons. See #6579 and #6582.
|
|
206
|
+
const parent = this.parent;
|
|
207
|
+
return getNodeAfterPosition(this, parent, getTextNodeAtPosition(this, parent));
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Node directly before this position or `null` if this position is in text node.
|
|
211
|
+
*
|
|
212
|
+
* @readonly
|
|
213
|
+
* @type {module:engine/model/node~Node|null}
|
|
214
|
+
*/
|
|
215
|
+
get nodeBefore() {
|
|
216
|
+
// Cache the parent and reuse for performance reasons. See #6579 and #6582.
|
|
217
|
+
const parent = this.parent;
|
|
218
|
+
return getNodeBeforePosition(this, parent, getTextNodeAtPosition(this, parent));
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Is `true` if position is at the beginning of its {@link module:engine/model/position~Position#parent parent}, `false` otherwise.
|
|
222
|
+
*
|
|
223
|
+
* @readonly
|
|
224
|
+
* @type {Boolean}
|
|
225
|
+
*/
|
|
226
|
+
get isAtStart() {
|
|
227
|
+
return this.offset === 0;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Is `true` if position is at the end of its {@link module:engine/model/position~Position#parent parent}, `false` otherwise.
|
|
231
|
+
*
|
|
232
|
+
* @readonly
|
|
233
|
+
* @type {Boolean}
|
|
234
|
+
*/
|
|
235
|
+
get isAtEnd() {
|
|
236
|
+
return this.offset == this.parent.maxOffset;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Checks whether this position is before or after given position.
|
|
240
|
+
*
|
|
241
|
+
* This method is safe to use it on non-existing positions (for example during operational transformation).
|
|
242
|
+
*
|
|
243
|
+
* @param {module:engine/model/position~Position} otherPosition Position to compare with.
|
|
244
|
+
* @returns {module:engine/model/position~PositionRelation}
|
|
245
|
+
*/
|
|
246
|
+
compareWith(otherPosition) {
|
|
247
|
+
if (this.root != otherPosition.root) {
|
|
248
|
+
return 'different';
|
|
249
|
+
}
|
|
250
|
+
const result = compareArrays(this.path, otherPosition.path);
|
|
251
|
+
switch (result) {
|
|
252
|
+
case 'same':
|
|
253
|
+
return 'same';
|
|
254
|
+
case 'prefix':
|
|
255
|
+
return 'before';
|
|
256
|
+
case 'extension':
|
|
257
|
+
return 'after';
|
|
258
|
+
default:
|
|
259
|
+
return this.path[result] < otherPosition.path[result] ? 'before' : 'after';
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Gets the farthest position which matches the callback using
|
|
264
|
+
* {@link module:engine/model/treewalker~TreeWalker TreeWalker}.
|
|
265
|
+
*
|
|
266
|
+
* For example:
|
|
267
|
+
*
|
|
268
|
+
* getLastMatchingPosition( value => value.type == 'text' );
|
|
269
|
+
* // <paragraph>[]foo</paragraph> -> <paragraph>foo[]</paragraph>
|
|
270
|
+
*
|
|
271
|
+
* getLastMatchingPosition( value => value.type == 'text', { direction: 'backward' } );
|
|
272
|
+
* // <paragraph>foo[]</paragraph> -> <paragraph>[]foo</paragraph>
|
|
273
|
+
*
|
|
274
|
+
* getLastMatchingPosition( value => false );
|
|
275
|
+
* // Do not move the position.
|
|
276
|
+
*
|
|
277
|
+
* @param {Function} skip Callback function. Gets {@link module:engine/model/treewalker~TreeWalkerValue} and should
|
|
278
|
+
* return `true` if the value should be skipped or `false` if not.
|
|
279
|
+
* @param {Object} options Object with configuration options. See {@link module:engine/model/treewalker~TreeWalker}.
|
|
280
|
+
*
|
|
281
|
+
* @returns {module:engine/model/position~Position} The position after the last item which matches the `skip` callback test.
|
|
282
|
+
*/
|
|
283
|
+
getLastMatchingPosition(skip, options = {}) {
|
|
284
|
+
options.startPosition = this;
|
|
285
|
+
const treeWalker = new TreeWalker(options);
|
|
286
|
+
treeWalker.skip(skip);
|
|
287
|
+
return treeWalker.position;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Returns a path to this position's parent. Parent path is equal to position {@link module:engine/model/position~Position#path path}
|
|
291
|
+
* but without the last item.
|
|
292
|
+
*
|
|
293
|
+
* This method is safe to use it on non-existing positions (for example during operational transformation).
|
|
294
|
+
*
|
|
295
|
+
* @returns {Array.<Number>} Path to the parent.
|
|
296
|
+
*/
|
|
297
|
+
getParentPath() {
|
|
298
|
+
return this.path.slice(0, -1);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Returns ancestors array of this position, that is this position's parent and its ancestors.
|
|
302
|
+
*
|
|
303
|
+
* @returns {Array.<module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment>} Array with ancestors.
|
|
304
|
+
*/
|
|
305
|
+
getAncestors() {
|
|
306
|
+
const parent = this.parent;
|
|
307
|
+
if (parent.is('documentFragment')) {
|
|
308
|
+
return [parent];
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
return parent.getAncestors({ includeSelf: true });
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Returns the parent element of the given name. Returns null if the position is not inside the desired parent.
|
|
316
|
+
*
|
|
317
|
+
* @param {String} parentName The name of the parent element to find.
|
|
318
|
+
* @returns {module:engine/model/element~Element|null}
|
|
319
|
+
*/
|
|
320
|
+
findAncestor(parentName) {
|
|
321
|
+
const parent = this.parent;
|
|
322
|
+
if (parent.is('element')) {
|
|
323
|
+
return parent.findAncestor(parentName, { includeSelf: true });
|
|
324
|
+
}
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Returns the slice of two position {@link #path paths} which is identical. The {@link #root roots}
|
|
329
|
+
* of these two paths must be identical.
|
|
330
|
+
*
|
|
331
|
+
* This method is safe to use it on non-existing positions (for example during operational transformation).
|
|
332
|
+
*
|
|
333
|
+
* @param {module:engine/model/position~Position} position The second position.
|
|
334
|
+
* @returns {Array.<Number>} The common path.
|
|
335
|
+
*/
|
|
336
|
+
getCommonPath(position) {
|
|
337
|
+
if (this.root != position.root) {
|
|
338
|
+
return [];
|
|
339
|
+
}
|
|
340
|
+
// We find on which tree-level start and end have the lowest common ancestor
|
|
341
|
+
const cmp = compareArrays(this.path, position.path);
|
|
342
|
+
// If comparison returned string it means that arrays are same.
|
|
343
|
+
const diffAt = (typeof cmp == 'string') ? Math.min(this.path.length, position.path.length) : cmp;
|
|
344
|
+
return this.path.slice(0, diffAt);
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Returns an {@link module:engine/model/element~Element} or {@link module:engine/model/documentfragment~DocumentFragment}
|
|
348
|
+
* which is a common ancestor of both positions. The {@link #root roots} of these two positions must be identical.
|
|
349
|
+
*
|
|
350
|
+
* @param {module:engine/model/position~Position} position The second position.
|
|
351
|
+
* @returns {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment|null}
|
|
352
|
+
*/
|
|
353
|
+
getCommonAncestor(position) {
|
|
354
|
+
const ancestorsA = this.getAncestors();
|
|
355
|
+
const ancestorsB = position.getAncestors();
|
|
356
|
+
let i = 0;
|
|
357
|
+
while (ancestorsA[i] == ancestorsB[i] && ancestorsA[i]) {
|
|
358
|
+
i++;
|
|
359
|
+
}
|
|
360
|
+
return i === 0 ? null : ancestorsA[i - 1];
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Returns a new instance of `Position`, that has same {@link #parent parent} but it's offset
|
|
364
|
+
* is shifted by `shift` value (can be a negative value).
|
|
365
|
+
*
|
|
366
|
+
* This method is safe to use it on non-existing positions (for example during operational transformation).
|
|
367
|
+
*
|
|
368
|
+
* @param {Number} shift Offset shift. Can be a negative value.
|
|
369
|
+
* @returns {module:engine/model/position~Position} Shifted position.
|
|
370
|
+
*/
|
|
371
|
+
getShiftedBy(shift) {
|
|
372
|
+
const shifted = this.clone();
|
|
373
|
+
const offset = shifted.offset + shift;
|
|
374
|
+
shifted.offset = offset < 0 ? 0 : offset;
|
|
375
|
+
return shifted;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Checks whether this position is after given position.
|
|
379
|
+
*
|
|
380
|
+
* This method is safe to use it on non-existing positions (for example during operational transformation).
|
|
381
|
+
*
|
|
382
|
+
* @see module:engine/model/position~Position#isBefore
|
|
383
|
+
* @param {module:engine/model/position~Position} otherPosition Position to compare with.
|
|
384
|
+
* @returns {Boolean} True if this position is after given position.
|
|
385
|
+
*/
|
|
386
|
+
isAfter(otherPosition) {
|
|
387
|
+
return this.compareWith(otherPosition) == 'after';
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Checks whether this position is before given position.
|
|
391
|
+
*
|
|
392
|
+
* **Note:** watch out when using negation of the value returned by this method, because the negation will also
|
|
393
|
+
* be `true` if positions are in different roots and you might not expect this. You should probably use
|
|
394
|
+
* `a.isAfter( b ) || a.isEqual( b )` or `!a.isBefore( p ) && a.root == b.root` in most scenarios. If your
|
|
395
|
+
* condition uses multiple `isAfter` and `isBefore` checks, build them so they do not use negated values, i.e.:
|
|
396
|
+
*
|
|
397
|
+
* if ( a.isBefore( b ) && c.isAfter( d ) ) {
|
|
398
|
+
* // do A.
|
|
399
|
+
* } else {
|
|
400
|
+
* // do B.
|
|
401
|
+
* }
|
|
402
|
+
*
|
|
403
|
+
* or, if you have only one if-branch:
|
|
404
|
+
*
|
|
405
|
+
* if ( !( a.isBefore( b ) && c.isAfter( d ) ) {
|
|
406
|
+
* // do B.
|
|
407
|
+
* }
|
|
408
|
+
*
|
|
409
|
+
* rather than:
|
|
410
|
+
*
|
|
411
|
+
* if ( !a.isBefore( b ) || && !c.isAfter( d ) ) {
|
|
412
|
+
* // do B.
|
|
413
|
+
* } else {
|
|
414
|
+
* // do A.
|
|
415
|
+
* }
|
|
416
|
+
*
|
|
417
|
+
* This method is safe to use it on non-existing positions (for example during operational transformation).
|
|
418
|
+
*
|
|
419
|
+
* @param {module:engine/model/position~Position} otherPosition Position to compare with.
|
|
420
|
+
* @returns {Boolean} True if this position is before given position.
|
|
421
|
+
*/
|
|
422
|
+
isBefore(otherPosition) {
|
|
423
|
+
return this.compareWith(otherPosition) == 'before';
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Checks whether this position is equal to given position.
|
|
427
|
+
*
|
|
428
|
+
* This method is safe to use it on non-existing positions (for example during operational transformation).
|
|
429
|
+
*
|
|
430
|
+
* @param {module:engine/model/position~Position} otherPosition Position to compare with.
|
|
431
|
+
* @returns {Boolean} True if positions are same.
|
|
432
|
+
*/
|
|
433
|
+
isEqual(otherPosition) {
|
|
434
|
+
return this.compareWith(otherPosition) == 'same';
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Checks whether this position is touching given position. Positions touch when there are no text nodes
|
|
438
|
+
* or empty nodes in a range between them. Technically, those positions are not equal but in many cases
|
|
439
|
+
* they are very similar or even indistinguishable.
|
|
440
|
+
*
|
|
441
|
+
* @param {module:engine/model/position~Position} otherPosition Position to compare with.
|
|
442
|
+
* @returns {Boolean} True if positions touch.
|
|
443
|
+
*/
|
|
444
|
+
isTouching(otherPosition) {
|
|
445
|
+
let left = null;
|
|
446
|
+
let right = null;
|
|
447
|
+
const compare = this.compareWith(otherPosition);
|
|
448
|
+
switch (compare) {
|
|
449
|
+
case 'same':
|
|
450
|
+
return true;
|
|
451
|
+
case 'before':
|
|
452
|
+
left = Position._createAt(this);
|
|
453
|
+
right = Position._createAt(otherPosition);
|
|
454
|
+
break;
|
|
455
|
+
case 'after':
|
|
456
|
+
left = Position._createAt(otherPosition);
|
|
457
|
+
right = Position._createAt(this);
|
|
458
|
+
break;
|
|
459
|
+
default:
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
// Cached for optimization purposes.
|
|
463
|
+
let leftParent = left.parent;
|
|
464
|
+
while (left.path.length + right.path.length) {
|
|
465
|
+
if (left.isEqual(right)) {
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
468
|
+
if (left.path.length > right.path.length) {
|
|
469
|
+
if (left.offset !== leftParent.maxOffset) {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
left.path = left.path.slice(0, -1);
|
|
473
|
+
leftParent = leftParent.parent;
|
|
474
|
+
left.offset++;
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
if (right.offset !== 0) {
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
right.path = right.path.slice(0, -1);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
// TypeScript compiler thinks that the flow can reach the end of this function without `return` and requires `undefined` or `void`
|
|
484
|
+
// as the return type. This `throw` convinces the compiler that the flow won't pass till the end.
|
|
485
|
+
/* istanbul ignore next */
|
|
486
|
+
throw new Error('unreachable code');
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Checks if two positions are in the same parent.
|
|
490
|
+
*
|
|
491
|
+
* This method is safe to use it on non-existing positions (for example during operational transformation).
|
|
492
|
+
*
|
|
493
|
+
* @param {module:engine/model/position~Position} position Position to compare with.
|
|
494
|
+
* @returns {Boolean} `true` if positions have the same parent, `false` otherwise.
|
|
495
|
+
*/
|
|
496
|
+
hasSameParentAs(position) {
|
|
497
|
+
if (this.root !== position.root) {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
const thisParentPath = this.getParentPath();
|
|
501
|
+
const posParentPath = position.getParentPath();
|
|
502
|
+
return compareArrays(thisParentPath, posParentPath) == 'same';
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Returns a copy of this position that is transformed by given `operation`.
|
|
506
|
+
*
|
|
507
|
+
* The new position's parameters are updated accordingly to the effect of the `operation`.
|
|
508
|
+
*
|
|
509
|
+
* For example, if `n` nodes are inserted before the position, the returned position {@link ~Position#offset} will be
|
|
510
|
+
* increased by `n`. If the position was in a merged element, it will be accordingly moved to the new element, etc.
|
|
511
|
+
*
|
|
512
|
+
* This method is safe to use it on non-existing positions (for example during operational transformation).
|
|
513
|
+
*
|
|
514
|
+
* @param {module:engine/model/operation/operation~Operation} operation Operation to transform by.
|
|
515
|
+
* @returns {module:engine/model/position~Position} Transformed position.
|
|
516
|
+
*/
|
|
517
|
+
getTransformedByOperation(operation) {
|
|
518
|
+
let result;
|
|
519
|
+
switch (operation.type) {
|
|
520
|
+
case 'insert':
|
|
521
|
+
result = this._getTransformedByInsertOperation(operation);
|
|
522
|
+
break;
|
|
523
|
+
case 'move':
|
|
524
|
+
case 'remove':
|
|
525
|
+
case 'reinsert':
|
|
526
|
+
result = this._getTransformedByMoveOperation(operation);
|
|
527
|
+
break;
|
|
528
|
+
case 'split':
|
|
529
|
+
result = this._getTransformedBySplitOperation(operation);
|
|
530
|
+
break;
|
|
531
|
+
case 'merge':
|
|
532
|
+
result = this._getTransformedByMergeOperation(operation);
|
|
533
|
+
break;
|
|
534
|
+
default:
|
|
535
|
+
result = Position._createAt(this);
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
538
|
+
return result;
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Returns a copy of this position transformed by an insert operation.
|
|
542
|
+
*
|
|
543
|
+
* @internal
|
|
544
|
+
* @protected
|
|
545
|
+
* @param {module:engine/model/operation/insertoperation~InsertOperation} operation
|
|
546
|
+
* @returns {module:engine/model/position~Position}
|
|
547
|
+
*/
|
|
548
|
+
_getTransformedByInsertOperation(operation) {
|
|
549
|
+
return this._getTransformedByInsertion(operation.position, operation.howMany);
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Returns a copy of this position transformed by a move operation.
|
|
553
|
+
*
|
|
554
|
+
* @internal
|
|
555
|
+
* @protected
|
|
556
|
+
* @param {module:engine/model/operation/moveoperation~MoveOperation} operation
|
|
557
|
+
* @returns {module:engine/model/position~Position}
|
|
558
|
+
*/
|
|
559
|
+
_getTransformedByMoveOperation(operation) {
|
|
560
|
+
return this._getTransformedByMove(operation.sourcePosition, operation.targetPosition, operation.howMany);
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Returns a copy of this position transformed by a split operation.
|
|
564
|
+
*
|
|
565
|
+
* @internal
|
|
566
|
+
* @protected
|
|
567
|
+
* @param {module:engine/model/operation/splitoperation~SplitOperation} operation
|
|
568
|
+
* @returns {module:engine/model/position~Position}
|
|
569
|
+
*/
|
|
570
|
+
_getTransformedBySplitOperation(operation) {
|
|
571
|
+
const movedRange = operation.movedRange;
|
|
572
|
+
const isContained = movedRange.containsPosition(this) ||
|
|
573
|
+
(movedRange.start.isEqual(this) && this.stickiness == 'toNext');
|
|
574
|
+
if (isContained) {
|
|
575
|
+
return this._getCombined(operation.splitPosition, operation.moveTargetPosition);
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
if (operation.graveyardPosition) {
|
|
579
|
+
return this._getTransformedByMove(operation.graveyardPosition, operation.insertionPosition, 1);
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
return this._getTransformedByInsertion(operation.insertionPosition, 1);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Returns a copy of this position transformed by merge operation.
|
|
588
|
+
*
|
|
589
|
+
* @internal
|
|
590
|
+
* @protected
|
|
591
|
+
* @param {module:engine/model/operation/mergeoperation~MergeOperation} operation
|
|
592
|
+
* @returns {module:engine/model/position~Position}
|
|
593
|
+
*/
|
|
594
|
+
_getTransformedByMergeOperation(operation) {
|
|
595
|
+
const movedRange = operation.movedRange;
|
|
596
|
+
const isContained = movedRange.containsPosition(this) || movedRange.start.isEqual(this);
|
|
597
|
+
let pos;
|
|
598
|
+
if (isContained) {
|
|
599
|
+
pos = this._getCombined(operation.sourcePosition, operation.targetPosition);
|
|
600
|
+
if (operation.sourcePosition.isBefore(operation.targetPosition)) {
|
|
601
|
+
// Above happens during OT when the merged element is moved before the merged-to element.
|
|
602
|
+
pos = pos._getTransformedByDeletion(operation.deletionPosition, 1);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
else if (this.isEqual(operation.deletionPosition)) {
|
|
606
|
+
pos = Position._createAt(operation.deletionPosition);
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
pos = this._getTransformedByMove(operation.deletionPosition, operation.graveyardPosition, 1);
|
|
610
|
+
}
|
|
611
|
+
return pos;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Returns a copy of this position that is updated by removing `howMany` nodes starting from `deletePosition`.
|
|
615
|
+
* It may happen that this position is in a removed node. If that is the case, `null` is returned instead.
|
|
616
|
+
*
|
|
617
|
+
* @internal
|
|
618
|
+
* @protected
|
|
619
|
+
* @param {module:engine/model/position~Position} deletePosition Position before the first removed node.
|
|
620
|
+
* @param {Number} howMany How many nodes are removed.
|
|
621
|
+
* @returns {module:engine/model/position~Position|null} Transformed position or `null`.
|
|
622
|
+
*/
|
|
623
|
+
_getTransformedByDeletion(deletePosition, howMany) {
|
|
624
|
+
const transformed = Position._createAt(this);
|
|
625
|
+
// This position can't be affected if deletion was in a different root.
|
|
626
|
+
if (this.root != deletePosition.root) {
|
|
627
|
+
return transformed;
|
|
628
|
+
}
|
|
629
|
+
if (compareArrays(deletePosition.getParentPath(), this.getParentPath()) == 'same') {
|
|
630
|
+
// If nodes are removed from the node that is pointed by this position...
|
|
631
|
+
if (deletePosition.offset < this.offset) {
|
|
632
|
+
// And are removed from before an offset of that position...
|
|
633
|
+
if (deletePosition.offset + howMany > this.offset) {
|
|
634
|
+
// Position is in removed range, it's no longer in the tree.
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
else {
|
|
638
|
+
// Decrement the offset accordingly.
|
|
639
|
+
transformed.offset -= howMany;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
else if (compareArrays(deletePosition.getParentPath(), this.getParentPath()) == 'prefix') {
|
|
644
|
+
// If nodes are removed from a node that is on a path to this position...
|
|
645
|
+
const i = deletePosition.path.length - 1;
|
|
646
|
+
if (deletePosition.offset <= this.path[i]) {
|
|
647
|
+
// And are removed from before next node of that path...
|
|
648
|
+
if (deletePosition.offset + howMany > this.path[i]) {
|
|
649
|
+
// If the next node of that path is removed return null
|
|
650
|
+
// because the node containing this position got removed.
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
// Otherwise, decrement index on that path.
|
|
655
|
+
transformed.path[i] -= howMany;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
return transformed;
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Returns a copy of this position that is updated by inserting `howMany` nodes at `insertPosition`.
|
|
663
|
+
*
|
|
664
|
+
* @internal
|
|
665
|
+
* @protected
|
|
666
|
+
* @param {module:engine/model/position~Position} insertPosition Position where nodes are inserted.
|
|
667
|
+
* @param {Number} howMany How many nodes are inserted.
|
|
668
|
+
* @returns {module:engine/model/position~Position} Transformed position.
|
|
669
|
+
*/
|
|
670
|
+
_getTransformedByInsertion(insertPosition, howMany) {
|
|
671
|
+
const transformed = Position._createAt(this);
|
|
672
|
+
// This position can't be affected if insertion was in a different root.
|
|
673
|
+
if (this.root != insertPosition.root) {
|
|
674
|
+
return transformed;
|
|
675
|
+
}
|
|
676
|
+
if (compareArrays(insertPosition.getParentPath(), this.getParentPath()) == 'same') {
|
|
677
|
+
// If nodes are inserted in the node that is pointed by this position...
|
|
678
|
+
if (insertPosition.offset < this.offset || (insertPosition.offset == this.offset && this.stickiness != 'toPrevious')) {
|
|
679
|
+
// And are inserted before an offset of that position...
|
|
680
|
+
// "Push" this positions offset.
|
|
681
|
+
transformed.offset += howMany;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
else if (compareArrays(insertPosition.getParentPath(), this.getParentPath()) == 'prefix') {
|
|
685
|
+
// If nodes are inserted in a node that is on a path to this position...
|
|
686
|
+
const i = insertPosition.path.length - 1;
|
|
687
|
+
if (insertPosition.offset <= this.path[i]) {
|
|
688
|
+
// And are inserted before next node of that path...
|
|
689
|
+
// "Push" the index on that path.
|
|
690
|
+
transformed.path[i] += howMany;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
return transformed;
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Returns a copy of this position that is updated by moving `howMany` nodes from `sourcePosition` to `targetPosition`.
|
|
697
|
+
*
|
|
698
|
+
* @internal
|
|
699
|
+
* @protected
|
|
700
|
+
* @param {module:engine/model/position~Position} sourcePosition Position before the first element to move.
|
|
701
|
+
* @param {module:engine/model/position~Position} targetPosition Position where moved elements will be inserted.
|
|
702
|
+
* @param {Number} howMany How many consecutive nodes to move, starting from `sourcePosition`.
|
|
703
|
+
* @returns {module:engine/model/position~Position} Transformed position.
|
|
704
|
+
*/
|
|
705
|
+
_getTransformedByMove(sourcePosition, targetPosition, howMany) {
|
|
706
|
+
// Update target position, as it could be affected by nodes removal.
|
|
707
|
+
targetPosition = targetPosition._getTransformedByDeletion(sourcePosition, howMany);
|
|
708
|
+
if (sourcePosition.isEqual(targetPosition)) {
|
|
709
|
+
// If `targetPosition` is equal to `sourcePosition` this isn't really any move. Just return position as it is.
|
|
710
|
+
return Position._createAt(this);
|
|
711
|
+
}
|
|
712
|
+
// Moving a range removes nodes from their original position. We acknowledge this by proper transformation.
|
|
713
|
+
const transformed = this._getTransformedByDeletion(sourcePosition, howMany);
|
|
714
|
+
const isMoved = transformed === null ||
|
|
715
|
+
(sourcePosition.isEqual(this) && this.stickiness == 'toNext') ||
|
|
716
|
+
(sourcePosition.getShiftedBy(howMany).isEqual(this) && this.stickiness == 'toPrevious');
|
|
717
|
+
if (isMoved) {
|
|
718
|
+
// This position is inside moved range (or sticks to it).
|
|
719
|
+
// In this case, we calculate a combination of this position, move source position and target position.
|
|
720
|
+
return this._getCombined(sourcePosition, targetPosition);
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
// This position is not inside a removed range.
|
|
724
|
+
//
|
|
725
|
+
// In next step, we simply reflect inserting `howMany` nodes, which might further affect the position.
|
|
726
|
+
return transformed._getTransformedByInsertion(targetPosition, howMany);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Returns a new position that is a combination of this position and given positions.
|
|
731
|
+
*
|
|
732
|
+
* The combined position is a copy of this position transformed by moving a range starting at `source` position
|
|
733
|
+
* to the `target` position. It is expected that this position is inside the moved range.
|
|
734
|
+
*
|
|
735
|
+
* Example:
|
|
736
|
+
*
|
|
737
|
+
* let original = model.createPositionFromPath( root, [ 2, 3, 1 ] );
|
|
738
|
+
* let source = model.createPositionFromPath( root, [ 2, 2 ] );
|
|
739
|
+
* let target = model.createPositionFromPath( otherRoot, [ 1, 1, 3 ] );
|
|
740
|
+
* original._getCombined( source, target ); // path is [ 1, 1, 4, 1 ], root is `otherRoot`
|
|
741
|
+
*
|
|
742
|
+
* Explanation:
|
|
743
|
+
*
|
|
744
|
+
* We have a position `[ 2, 3, 1 ]` and move some nodes from `[ 2, 2 ]` to `[ 1, 1, 3 ]`. The original position
|
|
745
|
+
* was inside moved nodes and now should point to the new place. The moved nodes will be after
|
|
746
|
+
* positions `[ 1, 1, 3 ]`, `[ 1, 1, 4 ]`, `[ 1, 1, 5 ]`. Since our position was in the second moved node,
|
|
747
|
+
* the transformed position will be in a sub-tree of a node at `[ 1, 1, 4 ]`. Looking at original path, we
|
|
748
|
+
* took care of `[ 2, 3 ]` part of it. Now we have to add the rest of the original path to the transformed path.
|
|
749
|
+
* Finally, the transformed position will point to `[ 1, 1, 4, 1 ]`.
|
|
750
|
+
*
|
|
751
|
+
* @internal
|
|
752
|
+
* @protected
|
|
753
|
+
* @param {module:engine/model/position~Position} source Beginning of the moved range.
|
|
754
|
+
* @param {module:engine/model/position~Position} target Position where the range is moved.
|
|
755
|
+
* @returns {module:engine/model/position~Position} Combined position.
|
|
756
|
+
*/
|
|
757
|
+
_getCombined(source, target) {
|
|
758
|
+
const i = source.path.length - 1;
|
|
759
|
+
// The first part of a path to combined position is a path to the place where nodes were moved.
|
|
760
|
+
const combined = Position._createAt(target);
|
|
761
|
+
combined.stickiness = this.stickiness;
|
|
762
|
+
// Then we have to update the rest of the path.
|
|
763
|
+
// Fix the offset because this position might be after `from` position and we have to reflect that.
|
|
764
|
+
combined.offset = combined.offset + this.path[i] - source.offset;
|
|
765
|
+
// Then, add the rest of the path.
|
|
766
|
+
// If this position is at the same level as `from` position nothing will get added.
|
|
767
|
+
combined.path = [...combined.path, ...this.path.slice(i + 1)];
|
|
768
|
+
return combined;
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* @inheritDoc
|
|
772
|
+
*/
|
|
773
|
+
toJSON() {
|
|
774
|
+
return {
|
|
775
|
+
root: this.root.toJSON(),
|
|
776
|
+
path: Array.from(this.path),
|
|
777
|
+
stickiness: this.stickiness
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Returns a new position that is equal to current position.
|
|
782
|
+
*
|
|
783
|
+
* @returns {module:engine/model/position~Position}
|
|
784
|
+
*/
|
|
785
|
+
clone() {
|
|
786
|
+
return new this.constructor(this.root, this.path, this.stickiness);
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Creates position at the given location. The location can be specified as:
|
|
790
|
+
*
|
|
791
|
+
* * a {@link module:engine/model/position~Position position},
|
|
792
|
+
* * parent element and offset (offset defaults to `0`),
|
|
793
|
+
* * parent element and `'end'` (sets position at the end of that element),
|
|
794
|
+
* * {@link module:engine/model/item~Item model item} and `'before'` or `'after'` (sets position before or after given model item).
|
|
795
|
+
*
|
|
796
|
+
* This method is a shortcut to other factory methods such as:
|
|
797
|
+
*
|
|
798
|
+
* * {@link module:engine/model/position~Position._createBefore},
|
|
799
|
+
* * {@link module:engine/model/position~Position._createAfter}.
|
|
800
|
+
*
|
|
801
|
+
* @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition
|
|
802
|
+
* @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when the
|
|
803
|
+
* first parameter is a {@link module:engine/model/item~Item model item}.
|
|
804
|
+
* @param {module:engine/model/position~PositionStickiness} [stickiness='toNone'] Position stickiness. Used only when the
|
|
805
|
+
* first parameter is a {@link module:engine/model/item~Item model item}.
|
|
806
|
+
* @protected
|
|
807
|
+
* @internal
|
|
808
|
+
*/
|
|
809
|
+
static _createAt(itemOrPosition, offset, stickiness = 'toNone') {
|
|
810
|
+
if (itemOrPosition instanceof Position) {
|
|
811
|
+
return new Position(itemOrPosition.root, itemOrPosition.path, itemOrPosition.stickiness);
|
|
812
|
+
}
|
|
813
|
+
else {
|
|
814
|
+
const node = itemOrPosition;
|
|
815
|
+
if (offset == 'end') {
|
|
816
|
+
offset = node.maxOffset;
|
|
817
|
+
}
|
|
818
|
+
else if (offset == 'before') {
|
|
819
|
+
return this._createBefore(node, stickiness);
|
|
820
|
+
}
|
|
821
|
+
else if (offset == 'after') {
|
|
822
|
+
return this._createAfter(node, stickiness);
|
|
823
|
+
}
|
|
824
|
+
else if (offset !== 0 && !offset) {
|
|
825
|
+
/**
|
|
826
|
+
* {@link module:engine/model/model~Model#createPositionAt `Model#createPositionAt()`}
|
|
827
|
+
* requires the offset to be specified when the first parameter is a model item.
|
|
828
|
+
*
|
|
829
|
+
* @error model-createpositionat-offset-required
|
|
830
|
+
*/
|
|
831
|
+
throw new CKEditorError('model-createpositionat-offset-required', [this, itemOrPosition]);
|
|
832
|
+
}
|
|
833
|
+
if (!node.is('element') && !node.is('documentFragment')) {
|
|
834
|
+
/**
|
|
835
|
+
* Position parent have to be a model element or model document fragment.
|
|
836
|
+
*
|
|
837
|
+
* @error model-position-parent-incorrect
|
|
838
|
+
*/
|
|
839
|
+
throw new CKEditorError('model-position-parent-incorrect', [this, itemOrPosition]);
|
|
840
|
+
}
|
|
841
|
+
const path = node.getPath();
|
|
842
|
+
path.push(offset);
|
|
843
|
+
return new this(node.root, path, stickiness);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Creates a new position, after given {@link module:engine/model/item~Item model item}.
|
|
848
|
+
*
|
|
849
|
+
* @param {module:engine/model/item~Item} item Item after which the position should be placed.
|
|
850
|
+
* @param {module:engine/model/position~PositionStickiness} [stickiness='toNone'] Position stickiness.
|
|
851
|
+
* @returns {module:engine/model/position~Position}
|
|
852
|
+
* @protected
|
|
853
|
+
* @internal
|
|
854
|
+
*/
|
|
855
|
+
static _createAfter(item, stickiness) {
|
|
856
|
+
if (!item.parent) {
|
|
857
|
+
/**
|
|
858
|
+
* You can not make a position after a root element.
|
|
859
|
+
*
|
|
860
|
+
* @error model-position-after-root
|
|
861
|
+
* @param {module:engine/model/item~Item} root
|
|
862
|
+
*/
|
|
863
|
+
throw new CKEditorError('model-position-after-root', [this, item], { root: item });
|
|
864
|
+
}
|
|
865
|
+
return this._createAt(item.parent, item.endOffset, stickiness);
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Creates a new position, before the given {@link module:engine/model/item~Item model item}.
|
|
869
|
+
*
|
|
870
|
+
* @param {module:engine/model/item~Item} item Item before which the position should be placed.
|
|
871
|
+
* @param {module:engine/model/position~PositionStickiness} [stickiness='toNone'] Position stickiness.
|
|
872
|
+
* @returns {module:engine/model/position~Position}
|
|
873
|
+
* @protected
|
|
874
|
+
* @internal
|
|
875
|
+
*/
|
|
876
|
+
static _createBefore(item, stickiness) {
|
|
877
|
+
if (!item.parent) {
|
|
878
|
+
/**
|
|
879
|
+
* You can not make a position before a root element.
|
|
880
|
+
*
|
|
881
|
+
* @error model-position-before-root
|
|
882
|
+
* @param {module:engine/model/item~Item} root
|
|
883
|
+
*/
|
|
884
|
+
throw new CKEditorError('model-position-before-root', item, { root: item });
|
|
885
|
+
}
|
|
886
|
+
return this._createAt(item.parent, item.startOffset, stickiness);
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Creates a `Position` instance from given plain object (i.e. parsed JSON string).
|
|
890
|
+
*
|
|
891
|
+
* @param {Object} json Plain object to be converted to `Position`.
|
|
892
|
+
* @param {module:engine/model/document~Document} doc Document object that will be position owner.
|
|
893
|
+
* @returns {module:engine/model/position~Position} `Position` instance created using given plain object.
|
|
894
|
+
*/
|
|
895
|
+
static fromJSON(json, doc) {
|
|
896
|
+
if (json.root === '$graveyard') {
|
|
897
|
+
const pos = new Position(doc.graveyard, json.path);
|
|
898
|
+
pos.stickiness = json.stickiness;
|
|
899
|
+
return pos;
|
|
900
|
+
}
|
|
901
|
+
if (!doc.getRoot(json.root)) {
|
|
902
|
+
/**
|
|
903
|
+
* Cannot create position for document. Root with specified name does not exist.
|
|
904
|
+
*
|
|
905
|
+
* @error model-position-fromjson-no-root
|
|
906
|
+
* @param {String} rootName
|
|
907
|
+
*/
|
|
908
|
+
throw new CKEditorError('model-position-fromjson-no-root', doc, { rootName: json.root });
|
|
909
|
+
}
|
|
910
|
+
return new Position(doc.getRoot(json.root), json.path, json.stickiness);
|
|
911
|
+
}
|
|
1042
912
|
}
|
|
1043
|
-
|
|
1044
913
|
/**
|
|
1045
|
-
*
|
|
1046
|
-
* If positions are in different roots `'different'` flag is returned.
|
|
914
|
+
* Checks whether this object is of the given.
|
|
1047
915
|
*
|
|
1048
|
-
*
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
/**
|
|
1052
|
-
* Represents how position is "sticking" with neighbour nodes. Used to define how position should be transformed (moved)
|
|
1053
|
-
* in edge cases. Possible values: `'toNone'`, `'toNext'`, `'toPrevious'`.
|
|
1054
|
-
*
|
|
1055
|
-
* Examples:
|
|
1056
|
-
*
|
|
1057
|
-
* Insert. Position is at | and nodes are inserted at the same position, marked as ^:
|
|
1058
|
-
*
|
|
1059
|
-
* - sticks to none: <p>f^|oo</p> -> <p>fbar|oo</p>
|
|
1060
|
-
* - sticks to next node: <p>f^|oo</p> -> <p>fbar|oo</p>
|
|
1061
|
-
* - sticks to previous node: <p>f|^oo</p> -> <p>f|baroo</p>
|
|
1062
|
-
*
|
|
1063
|
-
*
|
|
1064
|
-
* Move. Position is at | and range [oo] is moved to position ^:
|
|
1065
|
-
*
|
|
1066
|
-
* - sticks to none: <p>f|[oo]</p><p>b^ar</p> -> <p>f|</p><p>booar</p>
|
|
1067
|
-
* - sticks to none: <p>f[oo]|</p><p>b^ar</p> -> <p>f|</p><p>booar</p>
|
|
916
|
+
* position.is( 'position' ); // -> true
|
|
917
|
+
* position.is( 'model:position' ); // -> true
|
|
1068
918
|
*
|
|
1069
|
-
*
|
|
1070
|
-
*
|
|
919
|
+
* position.is( 'view:position' ); // -> false
|
|
920
|
+
* position.is( 'documentSelection' ); // -> false
|
|
1071
921
|
*
|
|
1072
|
-
*
|
|
1073
|
-
* - sticks to previous node: <p>f[oo]|</p><p>b^ar</p> -> <p>f</p><p>boo|ar</p>
|
|
922
|
+
* {@link module:engine/model/node~Node#is Check the entire list of model objects} which implement the `is()` method.
|
|
1074
923
|
*
|
|
1075
|
-
* @
|
|
924
|
+
* @param {String} type
|
|
925
|
+
* @returns {Boolean}
|
|
1076
926
|
*/
|
|
1077
|
-
|
|
927
|
+
Position.prototype.is = function (type) {
|
|
928
|
+
return type === 'position' || type === 'model:position';
|
|
929
|
+
};
|
|
1078
930
|
/**
|
|
1079
931
|
* Returns a text node at the given position.
|
|
1080
932
|
*
|
|
@@ -1096,16 +948,13 @@ export default class Position {
|
|
|
1096
948
|
* given position.
|
|
1097
949
|
* @returns {module:engine/model/text~Text|null}
|
|
1098
950
|
*/
|
|
1099
|
-
export function getTextNodeAtPosition(
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
return null;
|
|
951
|
+
export function getTextNodeAtPosition(position, positionParent) {
|
|
952
|
+
const node = positionParent.getChild(positionParent.offsetToIndex(position.offset));
|
|
953
|
+
if (node && node.is('$text') && node.startOffset < position.offset) {
|
|
954
|
+
return node;
|
|
955
|
+
}
|
|
956
|
+
return null;
|
|
1107
957
|
}
|
|
1108
|
-
|
|
1109
958
|
/**
|
|
1110
959
|
* Returns the node after the given position.
|
|
1111
960
|
*
|
|
@@ -1131,14 +980,12 @@ export function getTextNodeAtPosition( position, positionParent ) {
|
|
|
1131
980
|
* @param {module:engine/model/text~Text|null} textNode Text node at the given position.
|
|
1132
981
|
* @returns {module:engine/model/node~Node|null}
|
|
1133
982
|
*/
|
|
1134
|
-
export function getNodeAfterPosition(
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
return positionParent.getChild( positionParent.offsetToIndex( position.offset ) );
|
|
983
|
+
export function getNodeAfterPosition(position, positionParent, textNode) {
|
|
984
|
+
if (textNode !== null) {
|
|
985
|
+
return null;
|
|
986
|
+
}
|
|
987
|
+
return positionParent.getChild(positionParent.offsetToIndex(position.offset));
|
|
1140
988
|
}
|
|
1141
|
-
|
|
1142
989
|
/**
|
|
1143
990
|
* Returns the node before the given position.
|
|
1144
991
|
*
|
|
@@ -1155,10 +1002,9 @@ export function getNodeAfterPosition( position, positionParent, textNode ) {
|
|
|
1155
1002
|
* @param {module:engine/model/text~Text|null} textNode Text node at the given position.
|
|
1156
1003
|
* @returns {module:engine/model/node~Node|null}
|
|
1157
1004
|
*/
|
|
1158
|
-
export function getNodeBeforePosition(
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
return positionParent.getChild( positionParent.offsetToIndex( position.offset ) - 1 );
|
|
1005
|
+
export function getNodeBeforePosition(position, positionParent, textNode) {
|
|
1006
|
+
if (textNode !== null) {
|
|
1007
|
+
return null;
|
|
1008
|
+
}
|
|
1009
|
+
return positionParent.getChild(positionParent.offsetToIndex(position.offset) - 1);
|
|
1164
1010
|
}
|