@ckeditor/ckeditor5-engine 41.4.1 → 42.0.0-alpha.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/README.md +6 -0
- package/dist/index.js +15151 -13052
- package/dist/index.js.map +1 -1
- package/dist/types/controller/datacontroller.d.ts +1 -1
- package/dist/types/controller/editingcontroller.d.ts +1 -1
- package/dist/types/conversion/downcastdispatcher.d.ts +1 -1
- package/dist/types/conversion/mapper.d.ts +1 -1
- package/dist/types/conversion/upcastdispatcher.d.ts +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/model/differ.d.ts +134 -42
- package/dist/types/model/document.d.ts +1 -1
- package/dist/types/model/documentselection.d.ts +1 -1
- package/dist/types/model/liveposition.d.ts +1 -1
- package/dist/types/model/liverange.d.ts +1 -1
- package/dist/types/model/markercollection.d.ts +2 -2
- package/dist/types/model/model.d.ts +1 -1
- package/dist/types/model/schema.d.ts +32 -6
- package/dist/types/model/selection.d.ts +1 -1
- package/dist/types/view/document.d.ts +1 -1
- package/dist/types/view/documentfragment.d.ts +1 -1
- package/dist/types/view/documentselection.d.ts +1 -1
- package/dist/types/view/domconverter.d.ts +9 -0
- package/dist/types/view/editableelement.d.ts +1 -1
- package/dist/types/view/node.d.ts +1 -1
- package/dist/types/view/observer/observer.d.ts +1 -1
- package/dist/types/view/renderer.d.ts +1 -1
- package/dist/types/view/selection.d.ts +1 -1
- package/dist/types/view/view.d.ts +1 -1
- package/package.json +2 -2
- package/src/controller/datacontroller.d.ts +1 -1
- package/src/controller/datacontroller.js +1 -1
- package/src/controller/editingcontroller.d.ts +1 -1
- package/src/controller/editingcontroller.js +1 -1
- package/src/conversion/downcastdispatcher.d.ts +1 -1
- package/src/conversion/downcastdispatcher.js +1 -1
- package/src/conversion/mapper.d.ts +1 -1
- package/src/conversion/mapper.js +1 -1
- package/src/conversion/upcastdispatcher.d.ts +1 -1
- package/src/conversion/upcastdispatcher.js +1 -1
- package/src/index.d.ts +2 -1
- package/src/index.js +1 -0
- package/src/model/differ.d.ts +134 -42
- package/src/model/differ.js +247 -125
- package/src/model/document.d.ts +1 -1
- package/src/model/document.js +1 -1
- package/src/model/documentselection.d.ts +1 -1
- package/src/model/documentselection.js +1 -1
- package/src/model/liveposition.d.ts +1 -1
- package/src/model/liveposition.js +1 -1
- package/src/model/liverange.d.ts +1 -1
- package/src/model/liverange.js +1 -1
- package/src/model/markercollection.d.ts +2 -2
- package/src/model/markercollection.js +2 -2
- package/src/model/model.d.ts +1 -1
- package/src/model/model.js +1 -1
- package/src/model/schema.d.ts +32 -6
- package/src/model/schema.js +208 -101
- package/src/model/selection.d.ts +1 -1
- package/src/model/selection.js +1 -1
- package/src/view/document.d.ts +1 -1
- package/src/view/document.js +1 -1
- package/src/view/documentfragment.d.ts +1 -1
- package/src/view/documentfragment.js +1 -1
- package/src/view/documentselection.d.ts +1 -1
- package/src/view/documentselection.js +1 -1
- package/src/view/domconverter.d.ts +9 -0
- package/src/view/domconverter.js +27 -5
- package/src/view/editableelement.d.ts +1 -1
- package/src/view/editableelement.js +1 -1
- package/src/view/node.d.ts +1 -1
- package/src/view/node.js +1 -1
- package/src/view/observer/observer.d.ts +1 -1
- package/src/view/observer/observer.js +1 -1
- package/src/view/renderer.d.ts +1 -1
- package/src/view/renderer.js +1 -1
- package/src/view/selection.d.ts +1 -1
- package/src/view/selection.js +1 -1
- package/src/view/view.d.ts +1 -1
- package/src/view/view.js +1 -1
package/src/model/differ.js
CHANGED
|
@@ -15,7 +15,7 @@ import Range from './range.js';
|
|
|
15
15
|
* changed elements, after all changes are applied on the model document. Calculates the diff between saved
|
|
16
16
|
* elements and new ones and returns a change set.
|
|
17
17
|
*/
|
|
18
|
-
|
|
18
|
+
class Differ {
|
|
19
19
|
/**
|
|
20
20
|
* Creates a `Differ` instance.
|
|
21
21
|
*
|
|
@@ -30,18 +30,48 @@ export default class Differ {
|
|
|
30
30
|
*/
|
|
31
31
|
this._changesInElement = new Map();
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
33
|
+
* Stores a snapshot for these model nodes that might have changed.
|
|
34
|
+
*
|
|
35
|
+
* This complements {@link ~Differ#_elementChildrenSnapshots `_elementChildrenSnapshots`}.
|
|
36
|
+
*
|
|
37
|
+
* See also {@link ~DifferSnapshot}.
|
|
38
|
+
*/
|
|
39
|
+
this._elementsSnapshots = new Map();
|
|
40
|
+
/**
|
|
41
|
+
* For each element or document fragment inside which there was a change, it stores a snapshot of the child nodes list (an array
|
|
42
|
+
* of children snapshots that represent the state in the element / fragment before any change has happened).
|
|
43
|
+
*
|
|
44
|
+
* This complements {@link ~Differ#_elementsSnapshots `_elementsSnapshots`}.
|
|
45
|
+
*
|
|
46
|
+
* See also {@link ~DifferSnapshot}.
|
|
47
|
+
*/
|
|
48
|
+
this._elementChildrenSnapshots = new Map();
|
|
49
|
+
/**
|
|
50
|
+
* Keeps the state for a given element, describing how the element was changed so far. It is used to evaluate the `action` property
|
|
51
|
+
* of diff items returned by {@link ~Differ#getChanges}.
|
|
52
|
+
*
|
|
53
|
+
* Possible values, in the order from the lowest priority to the highest priority:
|
|
54
|
+
*
|
|
55
|
+
* * `'refresh'` - element was refreshed,
|
|
56
|
+
* * `'rename'` - element was renamed,
|
|
57
|
+
* * `'move'` - element was moved (or, usually, removed, that is moved to the graveyard).
|
|
58
|
+
*
|
|
59
|
+
* Element that was refreshed, may change its state to `'rename'` if it was later renamed, or to `'move'` if it was removed.
|
|
60
|
+
* But the element cannot change its state from `'move'` to `'rename'`, or from `'rename'` to `'refresh'`.
|
|
61
|
+
*
|
|
62
|
+
* Only already existing elements are registered in `_elementState`. If a new element was inserted as a result of a buffered operation,
|
|
63
|
+
* it is not be registered in `_elementState`.
|
|
36
64
|
*/
|
|
37
|
-
this.
|
|
65
|
+
this._elementState = new Map();
|
|
38
66
|
/**
|
|
39
67
|
* A map that stores all changed markers.
|
|
40
68
|
*
|
|
41
69
|
* The keys of the map are marker names.
|
|
70
|
+
*
|
|
42
71
|
* The values of the map are objects with the following properties:
|
|
43
|
-
*
|
|
44
|
-
*
|
|
72
|
+
*
|
|
73
|
+
* * `oldMarkerData`,
|
|
74
|
+
* * `newMarkerData`.
|
|
45
75
|
*/
|
|
46
76
|
this._changedMarkers = new Map();
|
|
47
77
|
/**
|
|
@@ -84,7 +114,7 @@ export default class Differ {
|
|
|
84
114
|
return this._changesInElement.size == 0 && this._changedMarkers.size == 0 && this._changedRoots.size == 0;
|
|
85
115
|
}
|
|
86
116
|
/**
|
|
87
|
-
* Buffers the given operation. An operation has to be buffered before it is executed
|
|
117
|
+
* Buffers the given operation. **An operation has to be buffered before it is executed.**
|
|
88
118
|
*
|
|
89
119
|
* @param operationToBuffer An operation to buffer.
|
|
90
120
|
*/
|
|
@@ -133,6 +163,11 @@ export default class Differ {
|
|
|
133
163
|
if (!targetParentInserted) {
|
|
134
164
|
this._markInsert(operation.targetPosition.parent, operation.getMovedRangeStart().offset, operation.howMany);
|
|
135
165
|
}
|
|
166
|
+
// Remember -- operation is buffered before it is executed. So, it was not executed yet.
|
|
167
|
+
const range = Range._createFromPositionAndShift(operation.sourcePosition, operation.howMany);
|
|
168
|
+
for (const node of range.getItems({ shallow: true })) {
|
|
169
|
+
this._setElementState(node, 'move');
|
|
170
|
+
}
|
|
136
171
|
break;
|
|
137
172
|
}
|
|
138
173
|
case 'rename': {
|
|
@@ -146,6 +181,7 @@ export default class Differ {
|
|
|
146
181
|
const markerData = marker.getData();
|
|
147
182
|
this.bufferMarkerChange(marker.name, markerData, markerData);
|
|
148
183
|
}
|
|
184
|
+
this._setElementState(operation.position.nodeAfter, 'rename');
|
|
149
185
|
break;
|
|
150
186
|
}
|
|
151
187
|
case 'split': {
|
|
@@ -153,6 +189,11 @@ export default class Differ {
|
|
|
153
189
|
// Mark that children of the split element were removed.
|
|
154
190
|
if (!this._isInInsertedElement(splitElement)) {
|
|
155
191
|
this._markRemove(splitElement, operation.splitPosition.offset, operation.howMany);
|
|
192
|
+
// Remember -- operation is buffered before it is executed. So, it was not executed yet.
|
|
193
|
+
const range = Range._createFromPositionAndShift(operation.splitPosition, operation.howMany);
|
|
194
|
+
for (const node of range.getItems({ shallow: true })) {
|
|
195
|
+
this._setElementState(node, 'move');
|
|
196
|
+
}
|
|
156
197
|
}
|
|
157
198
|
// Mark that the new element (split copy) was inserted.
|
|
158
199
|
if (!this._isInInsertedElement(operation.insertionPosition.parent)) {
|
|
@@ -161,6 +202,7 @@ export default class Differ {
|
|
|
161
202
|
// If the split took the element from the graveyard, mark that the element from the graveyard was removed.
|
|
162
203
|
if (operation.graveyardPosition) {
|
|
163
204
|
this._markRemove(operation.graveyardPosition.parent, operation.graveyardPosition.offset, 1);
|
|
205
|
+
this._setElementState(operation.graveyardPosition.nodeAfter, 'move');
|
|
164
206
|
}
|
|
165
207
|
break;
|
|
166
208
|
}
|
|
@@ -173,10 +215,16 @@ export default class Differ {
|
|
|
173
215
|
// Mark that the merged element was inserted into graveyard.
|
|
174
216
|
const graveyardParent = operation.graveyardPosition.parent;
|
|
175
217
|
this._markInsert(graveyardParent, operation.graveyardPosition.offset, 1);
|
|
218
|
+
this._setElementState(mergedElement, 'move');
|
|
176
219
|
// Mark that children of merged element were inserted at new parent.
|
|
177
220
|
const mergedIntoElement = operation.targetPosition.parent;
|
|
178
221
|
if (!this._isInInsertedElement(mergedIntoElement)) {
|
|
179
222
|
this._markInsert(mergedIntoElement, operation.targetPosition.offset, mergedElement.maxOffset);
|
|
223
|
+
// Remember -- operation is buffered before it is executed. So, it was not executed yet.
|
|
224
|
+
const range = Range._createFromPositionAndShift(operation.sourcePosition, operation.howMany);
|
|
225
|
+
for (const node of range.getItems({ shallow: true })) {
|
|
226
|
+
this._setElementState(node, 'move');
|
|
227
|
+
}
|
|
180
228
|
}
|
|
181
229
|
break;
|
|
182
230
|
}
|
|
@@ -338,9 +386,9 @@ export default class Differ {
|
|
|
338
386
|
}
|
|
339
387
|
// Will contain returned results.
|
|
340
388
|
let diffSet = [];
|
|
341
|
-
// Check all changed elements.
|
|
389
|
+
// Check all changed elements/roots.
|
|
342
390
|
for (const element of this._changesInElement.keys()) {
|
|
343
|
-
// Get changes
|
|
391
|
+
// Get changes inside this element/root and sort them.
|
|
344
392
|
const changes = this._changesInElement.get(element).sort((a, b) => {
|
|
345
393
|
if (a.offset === b.offset) {
|
|
346
394
|
if (a.type != b.type) {
|
|
@@ -354,31 +402,34 @@ export default class Differ {
|
|
|
354
402
|
return a.offset < b.offset ? -1 : 1;
|
|
355
403
|
});
|
|
356
404
|
// Get children of this element before any change was applied on it.
|
|
357
|
-
const
|
|
405
|
+
const childrenBefore = this._elementChildrenSnapshots.get(element);
|
|
358
406
|
// Get snapshot of current element's children.
|
|
359
|
-
const
|
|
360
|
-
// Generate
|
|
361
|
-
const
|
|
362
|
-
let i = 0; // Iterator in `
|
|
363
|
-
let j = 0; // Iterator in `
|
|
407
|
+
const childrenAfter = _getChildrenSnapshots(element.getChildren());
|
|
408
|
+
// Generate diff instructions based on changes done in the element/root.
|
|
409
|
+
const diffInstructions = _generateDiffInstructionsFromChanges(childrenBefore.length, changes);
|
|
410
|
+
let i = 0; // Iterator in `childrenAfter` array -- iterates through current children of element.
|
|
411
|
+
let j = 0; // Iterator in `childrenBefore` array -- iterates through old children of element.
|
|
364
412
|
// Process every action.
|
|
365
|
-
for (const
|
|
366
|
-
if (
|
|
367
|
-
|
|
368
|
-
|
|
413
|
+
for (const instruction of diffInstructions) {
|
|
414
|
+
if (instruction === 'i') {
|
|
415
|
+
const action = this._getDiffActionForNode(childrenAfter[i].node, 'insert');
|
|
416
|
+
const childSnapshotBefore = this._elementsSnapshots.get(childrenAfter[i].node);
|
|
417
|
+
const diffItem = this._getInsertDiff(element, i, action, childrenAfter[i], childSnapshotBefore);
|
|
418
|
+
diffSet.push(diffItem);
|
|
369
419
|
i++;
|
|
370
420
|
}
|
|
371
|
-
else if (
|
|
372
|
-
|
|
373
|
-
|
|
421
|
+
else if (instruction === 'r') {
|
|
422
|
+
const action = this._getDiffActionForNode(childrenBefore[j].node, 'remove');
|
|
423
|
+
const diffItem = this._getRemoveDiff(element, i, action, childrenBefore[j]);
|
|
424
|
+
diffSet.push(diffItem);
|
|
374
425
|
j++;
|
|
375
426
|
}
|
|
376
|
-
else if (
|
|
427
|
+
else if (instruction === 'a') {
|
|
377
428
|
// Take attributes from saved and current children.
|
|
378
|
-
const
|
|
379
|
-
const
|
|
429
|
+
const beforeAttributes = childrenBefore[j].attributes;
|
|
430
|
+
const afterAttributes = childrenAfter[i].attributes;
|
|
380
431
|
let range;
|
|
381
|
-
if (
|
|
432
|
+
if (childrenAfter[i].name == '$text') {
|
|
382
433
|
range = new Range(Position._createAt(element, i), Position._createAt(element, i + 1));
|
|
383
434
|
}
|
|
384
435
|
else {
|
|
@@ -387,7 +438,8 @@ export default class Differ {
|
|
|
387
438
|
}
|
|
388
439
|
// Generate diff items for this change (there might be multiple attributes changed and
|
|
389
440
|
// there is a single diff for each of them) and insert them into the diff set.
|
|
390
|
-
|
|
441
|
+
const diffItems = this._getAttributesDiff(range, beforeAttributes, afterAttributes);
|
|
442
|
+
diffSet.push(...diffItems);
|
|
391
443
|
i++;
|
|
392
444
|
j++;
|
|
393
445
|
}
|
|
@@ -498,12 +550,69 @@ export default class Differ {
|
|
|
498
550
|
*/
|
|
499
551
|
reset() {
|
|
500
552
|
this._changesInElement.clear();
|
|
501
|
-
this.
|
|
553
|
+
this._elementChildrenSnapshots.clear();
|
|
554
|
+
this._elementsSnapshots.clear();
|
|
555
|
+
this._elementState.clear();
|
|
502
556
|
this._changedMarkers.clear();
|
|
503
557
|
this._changedRoots.clear();
|
|
504
|
-
this._refreshedItems
|
|
558
|
+
this._refreshedItems.clear();
|
|
559
|
+
this._cachedChanges = null;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Marks the given `item` in differ to be "refreshed". It means that the item will be marked as removed and inserted
|
|
563
|
+
* in the differ changes set, so it will be effectively re-converted when the differ changes are handled by a dispatcher.
|
|
564
|
+
*
|
|
565
|
+
* @internal
|
|
566
|
+
* @param item Item to refresh.
|
|
567
|
+
*/
|
|
568
|
+
_refreshItem(item) {
|
|
569
|
+
if (this._isInInsertedElement(item.parent)) {
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
this._markRemove(item.parent, item.startOffset, item.offsetSize);
|
|
573
|
+
this._markInsert(item.parent, item.startOffset, item.offsetSize);
|
|
574
|
+
this._refreshedItems.add(item);
|
|
575
|
+
this._setElementState(item, 'refresh');
|
|
576
|
+
const range = Range._createOn(item);
|
|
577
|
+
for (const marker of this._markerCollection.getMarkersIntersectingRange(range)) {
|
|
578
|
+
const markerData = marker.getData();
|
|
579
|
+
this.bufferMarkerChange(marker.name, markerData, markerData);
|
|
580
|
+
}
|
|
581
|
+
// Clear cache after each buffered operation as it is no longer valid.
|
|
505
582
|
this._cachedChanges = null;
|
|
506
583
|
}
|
|
584
|
+
/**
|
|
585
|
+
* Buffers all the data related to given root like it was all just added to the editor.
|
|
586
|
+
*
|
|
587
|
+
* Following changes are buffered:
|
|
588
|
+
*
|
|
589
|
+
* * root is attached,
|
|
590
|
+
* * all root content is inserted,
|
|
591
|
+
* * all root attributes are added,
|
|
592
|
+
* * all markers inside the root are added.
|
|
593
|
+
*
|
|
594
|
+
* @internal
|
|
595
|
+
*/
|
|
596
|
+
_bufferRootLoad(root) {
|
|
597
|
+
if (!root.isAttached()) {
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
this._bufferRootStateChange(root.rootName, true);
|
|
601
|
+
this._markInsert(root, 0, root.maxOffset);
|
|
602
|
+
// Buffering root attribute changes makes sense and is actually needed, even though we buffer root state change above.
|
|
603
|
+
// Because the root state change is buffered, the root attributes changes are not returned by the differ.
|
|
604
|
+
// But, if the root attribute is removed in the same change block, or the root is detached, then the differ results would be wrong.
|
|
605
|
+
//
|
|
606
|
+
for (const key of root.getAttributeKeys()) {
|
|
607
|
+
this._bufferRootAttributeChange(root.rootName, key, null, root.getAttribute(key));
|
|
608
|
+
}
|
|
609
|
+
for (const marker of this._markerCollection) {
|
|
610
|
+
if (marker.getRange().root == root) {
|
|
611
|
+
const markerData = marker.getData();
|
|
612
|
+
this.bufferMarkerChange(marker.name, { ...markerData, range: null }, markerData);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
507
616
|
/**
|
|
508
617
|
* Buffers the root state change after the root was attached or detached
|
|
509
618
|
*/
|
|
@@ -563,60 +672,6 @@ export default class Differ {
|
|
|
563
672
|
this._changedRoots.set(rootName, diffItem);
|
|
564
673
|
}
|
|
565
674
|
}
|
|
566
|
-
/**
|
|
567
|
-
* Marks the given `item` in differ to be "refreshed". It means that the item will be marked as removed and inserted
|
|
568
|
-
* in the differ changes set, so it will be effectively re-converted when the differ changes are handled by a dispatcher.
|
|
569
|
-
*
|
|
570
|
-
* @internal
|
|
571
|
-
* @param item Item to refresh.
|
|
572
|
-
*/
|
|
573
|
-
_refreshItem(item) {
|
|
574
|
-
if (this._isInInsertedElement(item.parent)) {
|
|
575
|
-
return;
|
|
576
|
-
}
|
|
577
|
-
this._markRemove(item.parent, item.startOffset, item.offsetSize);
|
|
578
|
-
this._markInsert(item.parent, item.startOffset, item.offsetSize);
|
|
579
|
-
this._refreshedItems.add(item);
|
|
580
|
-
const range = Range._createOn(item);
|
|
581
|
-
for (const marker of this._markerCollection.getMarkersIntersectingRange(range)) {
|
|
582
|
-
const markerData = marker.getData();
|
|
583
|
-
this.bufferMarkerChange(marker.name, markerData, markerData);
|
|
584
|
-
}
|
|
585
|
-
// Clear cache after each buffered operation as it is no longer valid.
|
|
586
|
-
this._cachedChanges = null;
|
|
587
|
-
}
|
|
588
|
-
/**
|
|
589
|
-
* Buffers all the data related to given root like it was all just added to the editor.
|
|
590
|
-
*
|
|
591
|
-
* Following changes are buffered:
|
|
592
|
-
*
|
|
593
|
-
* * root is attached,
|
|
594
|
-
* * all root content is inserted,
|
|
595
|
-
* * all root attributes are added,
|
|
596
|
-
* * all markers inside the root are added.
|
|
597
|
-
*
|
|
598
|
-
* @internal
|
|
599
|
-
*/
|
|
600
|
-
_bufferRootLoad(root) {
|
|
601
|
-
if (!root.isAttached()) {
|
|
602
|
-
return;
|
|
603
|
-
}
|
|
604
|
-
this._bufferRootStateChange(root.rootName, true);
|
|
605
|
-
this._markInsert(root, 0, root.maxOffset);
|
|
606
|
-
// Buffering root attribute changes makes sense and is actually needed, even though we buffer root state change above.
|
|
607
|
-
// Because the root state change is buffered, the root attributes changes are not returned by the differ.
|
|
608
|
-
// But, if the root attribute is removed in the same change block, or the root is detached, then the differ results would be wrong.
|
|
609
|
-
//
|
|
610
|
-
for (const key of root.getAttributeKeys()) {
|
|
611
|
-
this._bufferRootAttributeChange(root.rootName, key, null, root.getAttribute(key));
|
|
612
|
-
}
|
|
613
|
-
for (const marker of this._markerCollection) {
|
|
614
|
-
if (marker.getRange().root == root) {
|
|
615
|
-
const markerData = marker.getData();
|
|
616
|
-
this.bufferMarkerChange(marker.name, { ...markerData, range: null }, markerData);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
675
|
/**
|
|
621
676
|
* Saves and handles an insert change.
|
|
622
677
|
*/
|
|
@@ -652,8 +707,8 @@ export default class Differ {
|
|
|
652
707
|
* Saves and handles a model change.
|
|
653
708
|
*/
|
|
654
709
|
_markChange(parent, changeItem) {
|
|
655
|
-
// First, make a snapshot of
|
|
656
|
-
this.
|
|
710
|
+
// First, make a snapshot of the parent and its children (it will be made only if it was not made before).
|
|
711
|
+
this._makeSnapshots(parent);
|
|
657
712
|
// Then, get all changes that already were done on the element (empty array if this is the first change).
|
|
658
713
|
const changes = this._getChangesForElement(parent);
|
|
659
714
|
// Then, look through all the changes, and transform them or the new change.
|
|
@@ -669,6 +724,49 @@ export default class Differ {
|
|
|
669
724
|
}
|
|
670
725
|
}
|
|
671
726
|
}
|
|
727
|
+
/**
|
|
728
|
+
* Tries to set given state for given item.
|
|
729
|
+
*
|
|
730
|
+
* This method does simple validation (it sets the state only for model elements, not for text proxy nodes). It also follows state
|
|
731
|
+
* setting rules, that is, `'refresh'` cannot overwrite `'rename'`, and `'rename'` cannot overwrite `'move'`.
|
|
732
|
+
*/
|
|
733
|
+
_setElementState(node, state) {
|
|
734
|
+
if (!node.is('element')) {
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
const currentStatePriority = Differ._statesPriority.indexOf(this._elementState.get(node));
|
|
738
|
+
const newStatePriority = Differ._statesPriority.indexOf(state);
|
|
739
|
+
if (newStatePriority > currentStatePriority) {
|
|
740
|
+
this._elementState.set(node, state);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Returns a value for {@link ~DifferItemAction `action`} property for diff items returned by {@link ~Differ#getChanges}.
|
|
745
|
+
* This method aims to return `'rename'` or `'refresh'` when it should, and `diffItemType` ("default action") in all other cases.
|
|
746
|
+
*
|
|
747
|
+
* It bases on a few factors:
|
|
748
|
+
*
|
|
749
|
+
* * for text nodes, the method always returns `diffItemType`,
|
|
750
|
+
* * for newly inserted element, the method returns `diffItemType`,
|
|
751
|
+
* * if {@link ~Differ#_elementState element state} was not recorded, the method returns `diffItemType`,
|
|
752
|
+
* * if state was recorded, and it was `'move'` (default action), the method returns `diffItemType`,
|
|
753
|
+
* * finally, if state was `'refresh'` or `'rename'`, the method returns the state value.
|
|
754
|
+
*/
|
|
755
|
+
_getDiffActionForNode(node, diffItemType) {
|
|
756
|
+
if (!node.is('element')) {
|
|
757
|
+
// Text node.
|
|
758
|
+
return diffItemType;
|
|
759
|
+
}
|
|
760
|
+
if (!this._elementsSnapshots.has(node)) {
|
|
761
|
+
// Newly inserted element.
|
|
762
|
+
return diffItemType;
|
|
763
|
+
}
|
|
764
|
+
const state = this._elementState.get(node);
|
|
765
|
+
if (!state || state == 'move') {
|
|
766
|
+
return diffItemType;
|
|
767
|
+
}
|
|
768
|
+
return state;
|
|
769
|
+
}
|
|
672
770
|
/**
|
|
673
771
|
* Gets an array of changes that have already been saved for a given element.
|
|
674
772
|
*/
|
|
@@ -684,11 +782,16 @@ export default class Differ {
|
|
|
684
782
|
return changes;
|
|
685
783
|
}
|
|
686
784
|
/**
|
|
687
|
-
*
|
|
785
|
+
* Creates and saves a snapshot for all children of the given element.
|
|
688
786
|
*/
|
|
689
|
-
|
|
690
|
-
if (
|
|
691
|
-
|
|
787
|
+
_makeSnapshots(element) {
|
|
788
|
+
if (this._elementChildrenSnapshots.has(element)) {
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
const childrenSnapshots = _getChildrenSnapshots(element.getChildren());
|
|
792
|
+
this._elementChildrenSnapshots.set(element, childrenSnapshots);
|
|
793
|
+
for (const snapshot of childrenSnapshots) {
|
|
794
|
+
this._elementsSnapshots.set(snapshot.node, snapshot);
|
|
692
795
|
}
|
|
693
796
|
}
|
|
694
797
|
/**
|
|
@@ -906,37 +1009,47 @@ export default class Differ {
|
|
|
906
1009
|
*
|
|
907
1010
|
* @param parent The element in which the change happened.
|
|
908
1011
|
* @param offset The offset at which change happened.
|
|
909
|
-
* @param
|
|
1012
|
+
* @param action Further specifies what kind of action led to generating this change.
|
|
1013
|
+
* @param elementSnapshot Snapshot of the inserted node after changes.
|
|
1014
|
+
* @param elementSnapshotBefore Snapshot of the inserted node before changes.
|
|
910
1015
|
* @returns The diff item.
|
|
911
1016
|
*/
|
|
912
|
-
_getInsertDiff(parent, offset, elementSnapshot) {
|
|
913
|
-
|
|
1017
|
+
_getInsertDiff(parent, offset, action, elementSnapshot, elementSnapshotBefore) {
|
|
1018
|
+
const diffItem = {
|
|
914
1019
|
type: 'insert',
|
|
915
1020
|
position: Position._createAt(parent, offset),
|
|
916
1021
|
name: elementSnapshot.name,
|
|
917
1022
|
attributes: new Map(elementSnapshot.attributes),
|
|
918
1023
|
length: 1,
|
|
919
1024
|
changeCount: this._changeCount++,
|
|
920
|
-
|
|
1025
|
+
action
|
|
921
1026
|
};
|
|
1027
|
+
if (action != 'insert' && elementSnapshotBefore) {
|
|
1028
|
+
diffItem.before = {
|
|
1029
|
+
name: elementSnapshotBefore.name,
|
|
1030
|
+
attributes: new Map(elementSnapshotBefore.attributes)
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
return diffItem;
|
|
922
1034
|
}
|
|
923
1035
|
/**
|
|
924
1036
|
* Returns an object with a single remove change description.
|
|
925
1037
|
*
|
|
926
1038
|
* @param parent The element in which change happened.
|
|
927
1039
|
* @param offset The offset at which change happened.
|
|
928
|
-
* @param
|
|
1040
|
+
* @param action Further specifies what kind of action led to generating this change.
|
|
1041
|
+
* @param elementSnapshot The snapshot of the removed node before changes.
|
|
929
1042
|
* @returns The diff item.
|
|
930
1043
|
*/
|
|
931
|
-
_getRemoveDiff(parent, offset, elementSnapshot) {
|
|
1044
|
+
_getRemoveDiff(parent, offset, action, elementSnapshot) {
|
|
932
1045
|
return {
|
|
933
1046
|
type: 'remove',
|
|
1047
|
+
action,
|
|
934
1048
|
position: Position._createAt(parent, offset),
|
|
935
1049
|
name: elementSnapshot.name,
|
|
936
1050
|
attributes: new Map(elementSnapshot.attributes),
|
|
937
1051
|
length: 1,
|
|
938
|
-
changeCount: this._changeCount
|
|
939
|
-
_element: elementSnapshot.element
|
|
1052
|
+
changeCount: this._changeCount++
|
|
940
1053
|
};
|
|
941
1054
|
}
|
|
942
1055
|
/**
|
|
@@ -1016,42 +1129,51 @@ export default class Differ {
|
|
|
1016
1129
|
const range = new Range(Position._createAt(parent, offset), Position._createAt(parent, offset + howMany));
|
|
1017
1130
|
for (const item of range.getItems({ shallow: true })) {
|
|
1018
1131
|
if (item.is('element')) {
|
|
1019
|
-
this._elementSnapshots.delete(item);
|
|
1020
1132
|
this._changesInElement.delete(item);
|
|
1021
1133
|
this._removeAllNestedChanges(item, 0, item.maxOffset);
|
|
1022
1134
|
}
|
|
1023
1135
|
}
|
|
1024
1136
|
}
|
|
1025
1137
|
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Priority of the {@link ~Differ#_elementState element states}. States on higher indexes of the array can overwrite states on the lower
|
|
1140
|
+
* indexes.
|
|
1141
|
+
*/
|
|
1142
|
+
Differ._statesPriority = [undefined, 'refresh', 'rename', 'move'];
|
|
1143
|
+
export default Differ;
|
|
1144
|
+
/**
|
|
1145
|
+
* Returns a snapshot for the specified child node. Text node snapshots have the `name` property set to `$text`.
|
|
1146
|
+
*/
|
|
1147
|
+
function _getSingleNodeSnapshot(node) {
|
|
1148
|
+
return {
|
|
1149
|
+
node,
|
|
1150
|
+
name: node.is('$text') ? '$text' : node.name,
|
|
1151
|
+
attributes: new Map(node.getAttributes())
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1026
1154
|
/**
|
|
1027
1155
|
* Returns an array that is a copy of passed child list with the exception that text nodes are split to one or more
|
|
1028
1156
|
* objects, each representing one character and attributes set on that character.
|
|
1029
1157
|
*/
|
|
1030
|
-
function
|
|
1031
|
-
const
|
|
1158
|
+
function _getChildrenSnapshots(children) {
|
|
1159
|
+
const snapshots = [];
|
|
1032
1160
|
for (const child of children) {
|
|
1033
1161
|
if (child.is('$text')) {
|
|
1034
|
-
for (let i = 0; i < child.data.length; i
|
|
1035
|
-
|
|
1036
|
-
name: '$text',
|
|
1037
|
-
attributes: new Map(child.getAttributes())
|
|
1038
|
-
});
|
|
1162
|
+
for (let i = 0; i < child.data.length; ++i) {
|
|
1163
|
+
snapshots.push(_getSingleNodeSnapshot(child));
|
|
1039
1164
|
}
|
|
1040
1165
|
}
|
|
1041
1166
|
else {
|
|
1042
|
-
|
|
1043
|
-
name: child.name,
|
|
1044
|
-
attributes: new Map(child.getAttributes()),
|
|
1045
|
-
element: child
|
|
1046
|
-
});
|
|
1167
|
+
snapshots.push(_getSingleNodeSnapshot(child));
|
|
1047
1168
|
}
|
|
1048
1169
|
}
|
|
1049
|
-
return
|
|
1170
|
+
return snapshots;
|
|
1050
1171
|
}
|
|
1051
1172
|
/**
|
|
1052
|
-
* Generates array of
|
|
1053
|
-
*
|
|
1173
|
+
* Generates array of diff instructions for given changes set.
|
|
1174
|
+
*
|
|
1054
1175
|
* Generated actions are:
|
|
1176
|
+
*
|
|
1055
1177
|
* - 'e' for 'equal' - when item at that position did not change,
|
|
1056
1178
|
* - 'i' for 'insert' - when item at that position was inserted,
|
|
1057
1179
|
* - 'r' for 'remove' - when item at that position was removed,
|
|
@@ -1066,9 +1188,9 @@ function _getChildrenSnapshot(children) {
|
|
|
1066
1188
|
* type: insert, offset: 2, howMany: 2
|
|
1067
1189
|
* type: attribute, offset: 4, howMany: 1
|
|
1068
1190
|
*
|
|
1069
|
-
*
|
|
1191
|
+
* Expected actions: equal (f), remove (o), equal (o), insert (x), insert (y), attribute (b), equal (A), equal (R)
|
|
1070
1192
|
*
|
|
1071
|
-
*
|
|
1193
|
+
* Steps taken by the script:
|
|
1072
1194
|
*
|
|
1073
1195
|
* 1. change = "type: remove, offset: 1, howMany: 1"; offset = 0; oldChildrenHandled = 0
|
|
1074
1196
|
* 1.1 between this change and the beginning is one not-changed node, fill with one equal action, one old child has been handled
|
|
@@ -1095,8 +1217,8 @@ function _getChildrenSnapshot(children) {
|
|
|
1095
1217
|
*
|
|
1096
1218
|
* The result actions are: equal, remove, equal, insert, insert, attribute, equal, equal.
|
|
1097
1219
|
*/
|
|
1098
|
-
function
|
|
1099
|
-
const
|
|
1220
|
+
function _generateDiffInstructionsFromChanges(oldChildrenLength, changes) {
|
|
1221
|
+
const diff = [];
|
|
1100
1222
|
let offset = 0;
|
|
1101
1223
|
let oldChildrenHandled = 0;
|
|
1102
1224
|
// Go through all buffered changes.
|
|
@@ -1104,21 +1226,21 @@ function _generateActionsFromChanges(oldChildrenLength, changes) {
|
|
|
1104
1226
|
// First, fill "holes" between changes with "equal" actions.
|
|
1105
1227
|
if (change.offset > offset) {
|
|
1106
1228
|
for (let i = 0; i < change.offset - offset; i++) {
|
|
1107
|
-
|
|
1229
|
+
diff.push('e');
|
|
1108
1230
|
}
|
|
1109
1231
|
oldChildrenHandled += change.offset - offset;
|
|
1110
1232
|
}
|
|
1111
1233
|
// Then, fill up actions accordingly to change type.
|
|
1112
1234
|
if (change.type == 'insert') {
|
|
1113
1235
|
for (let i = 0; i < change.howMany; i++) {
|
|
1114
|
-
|
|
1236
|
+
diff.push('i');
|
|
1115
1237
|
}
|
|
1116
1238
|
// The last handled offset is after inserted range.
|
|
1117
1239
|
offset = change.offset + change.howMany;
|
|
1118
1240
|
}
|
|
1119
1241
|
else if (change.type == 'remove') {
|
|
1120
1242
|
for (let i = 0; i < change.howMany; i++) {
|
|
1121
|
-
|
|
1243
|
+
diff.push('r');
|
|
1122
1244
|
}
|
|
1123
1245
|
// The last handled offset is at the position where the nodes were removed.
|
|
1124
1246
|
offset = change.offset;
|
|
@@ -1126,7 +1248,7 @@ function _generateActionsFromChanges(oldChildrenLength, changes) {
|
|
|
1126
1248
|
oldChildrenHandled += change.howMany;
|
|
1127
1249
|
}
|
|
1128
1250
|
else {
|
|
1129
|
-
|
|
1251
|
+
diff.push(...'a'.repeat(change.howMany).split(''));
|
|
1130
1252
|
// The last handled offset is at the position after the changed range.
|
|
1131
1253
|
offset = change.offset + change.howMany;
|
|
1132
1254
|
// We changed `howMany` old nodes, update `oldChildrenHandled`.
|
|
@@ -1137,13 +1259,13 @@ function _generateActionsFromChanges(oldChildrenLength, changes) {
|
|
|
1137
1259
|
// has not been changed / removed at the end of their parent.
|
|
1138
1260
|
if (oldChildrenHandled < oldChildrenLength) {
|
|
1139
1261
|
for (let i = 0; i < oldChildrenLength - oldChildrenHandled - offset; i++) {
|
|
1140
|
-
|
|
1262
|
+
diff.push('e');
|
|
1141
1263
|
}
|
|
1142
1264
|
}
|
|
1143
|
-
return
|
|
1265
|
+
return diff;
|
|
1144
1266
|
}
|
|
1145
1267
|
/**
|
|
1146
|
-
* Filter callback for Array.filter that filters out change entries that are in graveyard.
|
|
1268
|
+
* Filter callback for `Array.filter` that filters out change entries that are in graveyard.
|
|
1147
1269
|
*/
|
|
1148
1270
|
function _changesInGraveyardFilter(entry) {
|
|
1149
1271
|
const posInGy = 'position' in entry && entry.position.root.rootName == '$graveyard';
|
package/src/model/document.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ declare const Document_base: {
|
|
|
36
36
|
* However, the document may contain multiple roots – e.g. when the editor has multiple editable areas
|
|
37
37
|
* (e.g. a title and a body of a message).
|
|
38
38
|
*/
|
|
39
|
-
export default class Document extends Document_base {
|
|
39
|
+
export default class Document extends /* #__PURE__ */ Document_base {
|
|
40
40
|
/**
|
|
41
41
|
* The {@link module:engine/model/model~Model model} that the document is a part of.
|
|
42
42
|
*/
|
package/src/model/document.js
CHANGED
|
@@ -29,7 +29,7 @@ const graveyardName = '$graveyard';
|
|
|
29
29
|
* However, the document may contain multiple roots – e.g. when the editor has multiple editable areas
|
|
30
30
|
* (e.g. a title and a body of a message).
|
|
31
31
|
*/
|
|
32
|
-
export default class Document extends EmitterMixin() {
|
|
32
|
+
export default class Document extends /* #__PURE__ */ EmitterMixin() {
|
|
33
33
|
/**
|
|
34
34
|
* Creates an empty document instance with no {@link #roots} (other than
|
|
35
35
|
* the {@link #graveyard graveyard root}).
|
|
@@ -39,7 +39,7 @@ declare const DocumentSelection_base: import("@ckeditor/ckeditor5-utils").Mixed<
|
|
|
39
39
|
* If you need to represent a selection in document fragment,
|
|
40
40
|
* use {@link module:engine/model/selection~Selection Selection class} instead.
|
|
41
41
|
*/
|
|
42
|
-
export default class DocumentSelection extends DocumentSelection_base {
|
|
42
|
+
export default class DocumentSelection extends /* #__PURE__ */ DocumentSelection_base {
|
|
43
43
|
/**
|
|
44
44
|
* Selection used internally by that class (`DocumentSelection` is a proxy to that selection).
|
|
45
45
|
*/
|
|
@@ -36,7 +36,7 @@ const storePrefix = 'selection:';
|
|
|
36
36
|
* If you need to represent a selection in document fragment,
|
|
37
37
|
* use {@link module:engine/model/selection~Selection Selection class} instead.
|
|
38
38
|
*/
|
|
39
|
-
export default class DocumentSelection extends EmitterMixin(TypeCheckable) {
|
|
39
|
+
export default class DocumentSelection extends /* #__PURE__ */ EmitterMixin(TypeCheckable) {
|
|
40
40
|
/**
|
|
41
41
|
* Creates an empty live selection for given {@link module:engine/model/document~Document}.
|
|
42
42
|
*
|
|
@@ -23,7 +23,7 @@ declare const LivePosition_base: import("@ckeditor/ckeditor5-utils").Mixed<typeo
|
|
|
23
23
|
* have to be unbound.
|
|
24
24
|
* Use {@link module:engine/model/liveposition~LivePosition#detach} whenever you don't need `LivePosition` anymore.
|
|
25
25
|
*/
|
|
26
|
-
export default class LivePosition extends LivePosition_base {
|
|
26
|
+
export default class LivePosition extends /* #__PURE__ */ LivePosition_base {
|
|
27
27
|
/**
|
|
28
28
|
* Root of the position path.
|
|
29
29
|
*/
|