@ckeditor/ckeditor5-engine 37.0.0-alpha.2 → 37.0.0-rc.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/package.json +23 -23
- package/src/controller/datacontroller.d.ts +3 -0
- package/src/controller/datacontroller.js +16 -1
- package/src/index.d.ts +4 -2
- package/src/index.js +4 -0
- package/src/model/differ.d.ts +52 -8
- package/src/model/differ.js +104 -4
- package/src/model/document.d.ts +17 -7
- package/src/model/document.js +44 -5
- package/src/model/documentfragment.d.ts +4 -0
- package/src/model/documentfragment.js +6 -0
- package/src/model/node.d.ts +4 -4
- package/src/model/node.js +9 -5
- package/src/model/operation/attributeoperation.d.ts +1 -1
- package/src/model/operation/attributeoperation.js +1 -1
- package/src/model/operation/insertoperation.d.ts +1 -1
- package/src/model/operation/insertoperation.js +1 -1
- package/src/model/operation/mergeoperation.d.ts +1 -1
- package/src/model/operation/mergeoperation.js +1 -1
- package/src/model/operation/moveoperation.d.ts +1 -1
- package/src/model/operation/moveoperation.js +1 -1
- package/src/model/operation/operation.d.ts +1 -1
- package/src/model/operation/operation.js +1 -1
- package/src/model/operation/operationfactory.js +2 -0
- package/src/model/operation/rootattributeoperation.d.ts +7 -11
- package/src/model/operation/rootattributeoperation.js +6 -6
- package/src/model/operation/rootoperation.d.ts +75 -0
- package/src/model/operation/rootoperation.js +108 -0
- package/src/model/operation/splitoperation.d.ts +1 -1
- package/src/model/operation/splitoperation.js +1 -1
- package/src/model/operation/transform.js +8 -0
- package/src/model/rootelement.d.ts +15 -1
- package/src/model/rootelement.js +17 -1
- package/src/model/writer.d.ts +29 -1
- package/src/model/writer.js +74 -1
- package/src/view/matcher.d.ts +2 -2
- package/src/view/matcher.js +2 -2
- package/src/view/observer/arrowkeysobserver.d.ts +4 -0
- package/src/view/observer/arrowkeysobserver.js +4 -0
- package/src/view/observer/domeventobserver.d.ts +4 -0
- package/src/view/observer/domeventobserver.js +6 -0
- package/src/view/observer/fakeselectionobserver.d.ts +4 -0
- package/src/view/observer/fakeselectionobserver.js +4 -0
- package/src/view/observer/mutationobserver.d.ts +4 -0
- package/src/view/observer/mutationobserver.js +16 -2
- package/src/view/observer/observer.d.ts +7 -2
- package/src/view/observer/selectionobserver.d.ts +4 -0
- package/src/view/observer/selectionobserver.js +6 -0
- package/src/view/observer/tabobserver.d.ts +4 -0
- package/src/view/observer/tabobserver.js +4 -0
- package/src/view/placeholder.js +3 -3
- package/src/view/renderer.d.ts +4 -4
- package/src/view/renderer.js +17 -25
- package/src/view/view.d.ts +21 -3
- package/src/view/view.js +21 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ckeditor/ckeditor5-engine",
|
|
3
|
-
"version": "37.0.0-
|
|
3
|
+
"version": "37.0.0-rc.0",
|
|
4
4
|
"description": "The editing engine of CKEditor 5 – the best browser-based rich text editor.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"wysiwyg",
|
|
@@ -23,36 +23,36 @@
|
|
|
23
23
|
],
|
|
24
24
|
"main": "src/index.js",
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@ckeditor/ckeditor5-utils": "^37.0.0-
|
|
26
|
+
"@ckeditor/ckeditor5-utils": "^37.0.0-rc.0",
|
|
27
27
|
"lodash-es": "^4.17.15"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@ckeditor/ckeditor5-basic-styles": "^37.0.0-
|
|
31
|
-
"@ckeditor/ckeditor5-block-quote": "^37.0.0-
|
|
32
|
-
"@ckeditor/ckeditor5-clipboard": "^37.0.0-
|
|
33
|
-
"@ckeditor/ckeditor5-cloud-services": "^37.0.0-
|
|
34
|
-
"@ckeditor/ckeditor5-core": "^37.0.0-
|
|
35
|
-
"@ckeditor/ckeditor5-editor-classic": "^37.0.0-
|
|
36
|
-
"@ckeditor/ckeditor5-enter": "^37.0.0-
|
|
37
|
-
"@ckeditor/ckeditor5-essentials": "^37.0.0-
|
|
38
|
-
"@ckeditor/ckeditor5-heading": "^37.0.0-
|
|
39
|
-
"@ckeditor/ckeditor5-image": "^37.0.0-
|
|
40
|
-
"@ckeditor/ckeditor5-link": "^37.0.0-
|
|
41
|
-
"@ckeditor/ckeditor5-list": "^37.0.0-
|
|
42
|
-
"@ckeditor/ckeditor5-mention": "^37.0.0-
|
|
43
|
-
"@ckeditor/ckeditor5-paragraph": "^37.0.0-
|
|
44
|
-
"@ckeditor/ckeditor5-table": "^37.0.0-
|
|
45
|
-
"@ckeditor/ckeditor5-theme-lark": "^37.0.0-
|
|
46
|
-
"@ckeditor/ckeditor5-typing": "^37.0.0-
|
|
47
|
-
"@ckeditor/ckeditor5-ui": "^37.0.0-
|
|
48
|
-
"@ckeditor/ckeditor5-undo": "^37.0.0-
|
|
49
|
-
"@ckeditor/ckeditor5-widget": "^37.0.0-
|
|
30
|
+
"@ckeditor/ckeditor5-basic-styles": "^37.0.0-rc.0",
|
|
31
|
+
"@ckeditor/ckeditor5-block-quote": "^37.0.0-rc.0",
|
|
32
|
+
"@ckeditor/ckeditor5-clipboard": "^37.0.0-rc.0",
|
|
33
|
+
"@ckeditor/ckeditor5-cloud-services": "^37.0.0-rc.0",
|
|
34
|
+
"@ckeditor/ckeditor5-core": "^37.0.0-rc.0",
|
|
35
|
+
"@ckeditor/ckeditor5-editor-classic": "^37.0.0-rc.0",
|
|
36
|
+
"@ckeditor/ckeditor5-enter": "^37.0.0-rc.0",
|
|
37
|
+
"@ckeditor/ckeditor5-essentials": "^37.0.0-rc.0",
|
|
38
|
+
"@ckeditor/ckeditor5-heading": "^37.0.0-rc.0",
|
|
39
|
+
"@ckeditor/ckeditor5-image": "^37.0.0-rc.0",
|
|
40
|
+
"@ckeditor/ckeditor5-link": "^37.0.0-rc.0",
|
|
41
|
+
"@ckeditor/ckeditor5-list": "^37.0.0-rc.0",
|
|
42
|
+
"@ckeditor/ckeditor5-mention": "^37.0.0-rc.0",
|
|
43
|
+
"@ckeditor/ckeditor5-paragraph": "^37.0.0-rc.0",
|
|
44
|
+
"@ckeditor/ckeditor5-table": "^37.0.0-rc.0",
|
|
45
|
+
"@ckeditor/ckeditor5-theme-lark": "^37.0.0-rc.0",
|
|
46
|
+
"@ckeditor/ckeditor5-typing": "^37.0.0-rc.0",
|
|
47
|
+
"@ckeditor/ckeditor5-ui": "^37.0.0-rc.0",
|
|
48
|
+
"@ckeditor/ckeditor5-undo": "^37.0.0-rc.0",
|
|
49
|
+
"@ckeditor/ckeditor5-widget": "^37.0.0-rc.0",
|
|
50
50
|
"typescript": "^4.8.4",
|
|
51
51
|
"webpack": "^5.58.1",
|
|
52
52
|
"webpack-cli": "^4.9.0"
|
|
53
53
|
},
|
|
54
54
|
"engines": {
|
|
55
|
-
"node": ">=
|
|
55
|
+
"node": ">=16.0.0",
|
|
56
56
|
"npm": ">=5.7.1"
|
|
57
57
|
},
|
|
58
58
|
"author": "CKSource (http://cksource.com/)",
|
|
@@ -90,6 +90,9 @@ export default class DataController extends DataController_base {
|
|
|
90
90
|
* Returns the model's data converted by downcast dispatchers attached to {@link #downcastDispatcher} and
|
|
91
91
|
* formatted by the {@link #processor data processor}.
|
|
92
92
|
*
|
|
93
|
+
* A warning is logged when you try to retrieve data for a detached root, as most probably this is a mistake. A detached root should
|
|
94
|
+
* be treated like it is removed, and you should not save its data. Note, that the detached root data is always an empty string.
|
|
95
|
+
*
|
|
93
96
|
* @fires get
|
|
94
97
|
* @param options Additional configuration for the retrieved data. `DataController` provides two optional
|
|
95
98
|
* properties: `rootName` and `trim`. Other properties of this object are specified by various editor features.
|
|
@@ -17,6 +17,7 @@ import ViewDowncastWriter from '../view/downcastwriter';
|
|
|
17
17
|
import ModelRange from '../model/range';
|
|
18
18
|
import { autoParagraphEmptyRoots } from '../model/utils/autoparagraphing';
|
|
19
19
|
import HtmlDataProcessor from '../dataprocessor/htmldataprocessor';
|
|
20
|
+
import { logWarning } from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
|
|
20
21
|
/**
|
|
21
22
|
* Controller for the data pipeline. The data pipeline controls how data is retrieved from the document
|
|
22
23
|
* and set inside it. Hence, the controller features two methods which allow to {@link ~DataController#get get}
|
|
@@ -87,6 +88,9 @@ export default class DataController extends EmitterMixin() {
|
|
|
87
88
|
* Returns the model's data converted by downcast dispatchers attached to {@link #downcastDispatcher} and
|
|
88
89
|
* formatted by the {@link #processor data processor}.
|
|
89
90
|
*
|
|
91
|
+
* A warning is logged when you try to retrieve data for a detached root, as most probably this is a mistake. A detached root should
|
|
92
|
+
* be treated like it is removed, and you should not save its data. Note, that the detached root data is always an empty string.
|
|
93
|
+
*
|
|
90
94
|
* @fires get
|
|
91
95
|
* @param options Additional configuration for the retrieved data. `DataController` provides two optional
|
|
92
96
|
* properties: `rootName` and `trim`. Other properties of this object are specified by various editor features.
|
|
@@ -116,6 +120,17 @@ export default class DataController extends EmitterMixin() {
|
|
|
116
120
|
throw new CKEditorError('datacontroller-get-non-existent-root', this);
|
|
117
121
|
}
|
|
118
122
|
const root = this.model.document.getRoot(rootName);
|
|
123
|
+
if (!root.isAttached()) {
|
|
124
|
+
/**
|
|
125
|
+
* Retrieving document data for a detached root.
|
|
126
|
+
*
|
|
127
|
+
* This usually indicates an error as a detached root should be considered "removed" and should not be included in the
|
|
128
|
+
* document data.
|
|
129
|
+
*
|
|
130
|
+
* @error datacontroller-get-detached-root
|
|
131
|
+
*/
|
|
132
|
+
logWarning('datacontroller-get-detached-root', this);
|
|
133
|
+
}
|
|
119
134
|
if (trim === 'empty' && !this.model.hasContent(root, { ignoreWhitespaces: true })) {
|
|
120
135
|
return '';
|
|
121
136
|
}
|
|
@@ -387,7 +402,7 @@ export default class DataController extends EmitterMixin() {
|
|
|
387
402
|
*/
|
|
388
403
|
_checkIfRootsExists(rootNames) {
|
|
389
404
|
for (const rootName of rootNames) {
|
|
390
|
-
if (!this.model.document.
|
|
405
|
+
if (!this.model.document.getRoot(rootName)) {
|
|
391
406
|
return false;
|
|
392
407
|
}
|
|
393
408
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -24,8 +24,10 @@ export { default as MergeOperation } from './model/operation/mergeoperation';
|
|
|
24
24
|
export { default as SplitOperation } from './model/operation/splitoperation';
|
|
25
25
|
export { default as MarkerOperation } from './model/operation/markeroperation';
|
|
26
26
|
export { default as OperationFactory } from './model/operation/operationfactory';
|
|
27
|
-
export
|
|
28
|
-
export
|
|
27
|
+
export { default as AttributeOperation } from './model/operation/attributeoperation';
|
|
28
|
+
export { default as RenameOperation } from './model/operation/renameoperation';
|
|
29
|
+
export { default as RootAttributeOperation } from './model/operation/rootattributeoperation';
|
|
30
|
+
export { default as RootOperation } from './model/operation/rootoperation';
|
|
29
31
|
export { transformSets } from './model/operation/transform';
|
|
30
32
|
export { default as DocumentSelection, type DocumentSelectionChangeRangeEvent } from './model/documentselection';
|
|
31
33
|
export { default as Range } from './model/range';
|
package/src/index.js
CHANGED
|
@@ -18,6 +18,10 @@ export { default as MergeOperation } from './model/operation/mergeoperation';
|
|
|
18
18
|
export { default as SplitOperation } from './model/operation/splitoperation';
|
|
19
19
|
export { default as MarkerOperation } from './model/operation/markeroperation';
|
|
20
20
|
export { default as OperationFactory } from './model/operation/operationfactory';
|
|
21
|
+
export { default as AttributeOperation } from './model/operation/attributeoperation';
|
|
22
|
+
export { default as RenameOperation } from './model/operation/renameoperation';
|
|
23
|
+
export { default as RootAttributeOperation } from './model/operation/rootattributeoperation';
|
|
24
|
+
export { default as RootOperation } from './model/operation/rootoperation';
|
|
21
25
|
export { transformSets } from './model/operation/transform';
|
|
22
26
|
// Model.
|
|
23
27
|
export { default as DocumentSelection } from './model/documentselection';
|
package/src/model/differ.d.ts
CHANGED
|
@@ -45,6 +45,12 @@ export default class Differ {
|
|
|
45
45
|
* - `newMarkerData`.
|
|
46
46
|
*/
|
|
47
47
|
private readonly _changedMarkers;
|
|
48
|
+
/**
|
|
49
|
+
* A map that stores all roots that have been changed.
|
|
50
|
+
*
|
|
51
|
+
* The keys are the names of the roots while value represents the changes.
|
|
52
|
+
*/
|
|
53
|
+
private readonly _changedRoots;
|
|
48
54
|
/**
|
|
49
55
|
* Stores the number of changes that were processed. Used to order the changes chronologically. It is important
|
|
50
56
|
* when changes are sorted.
|
|
@@ -83,9 +89,6 @@ export default class Differ {
|
|
|
83
89
|
/**
|
|
84
90
|
* Buffers the given operation. An operation has to be buffered before it is executed.
|
|
85
91
|
*
|
|
86
|
-
* Operation type is checked and it is checked which nodes it will affect. These nodes are then stored in `Differ`
|
|
87
|
-
* in the state before the operation is executed.
|
|
88
|
-
*
|
|
89
92
|
* @param operationToBuffer An operation to buffer.
|
|
90
93
|
*/
|
|
91
94
|
bufferOperation(operationToBuffer: Operation): void;
|
|
@@ -132,6 +135,7 @@ export default class Differ {
|
|
|
132
135
|
*
|
|
133
136
|
* * model structure changes,
|
|
134
137
|
* * attribute changes,
|
|
138
|
+
* * a root is added or detached,
|
|
135
139
|
* * changes of markers which were defined as `affectsData`,
|
|
136
140
|
* * changes of markers' `affectsData` property.
|
|
137
141
|
*/
|
|
@@ -157,6 +161,12 @@ export default class Differ {
|
|
|
157
161
|
getChanges(options?: {
|
|
158
162
|
includeChangesInGraveyard?: boolean;
|
|
159
163
|
}): Array<DiffItem>;
|
|
164
|
+
/**
|
|
165
|
+
* Returns all roots that have changed (either were attached, or detached, or their attributes changed).
|
|
166
|
+
*
|
|
167
|
+
* @returns Diff between the old and the new roots state.
|
|
168
|
+
*/
|
|
169
|
+
getChangedRoots(): Array<DiffItemRoot>;
|
|
160
170
|
/**
|
|
161
171
|
* Returns a set of model items that were marked to get refreshed.
|
|
162
172
|
*/
|
|
@@ -165,6 +175,14 @@ export default class Differ {
|
|
|
165
175
|
* Resets `Differ`. Removes all buffered changes.
|
|
166
176
|
*/
|
|
167
177
|
reset(): void;
|
|
178
|
+
/**
|
|
179
|
+
* Buffers the root state change after the root was attached or detached
|
|
180
|
+
*/
|
|
181
|
+
private _bufferRootStateChange;
|
|
182
|
+
/**
|
|
183
|
+
* Buffers a root attribute change.
|
|
184
|
+
*/
|
|
185
|
+
private _bufferRootAttributeChange;
|
|
168
186
|
/**
|
|
169
187
|
* Marks the given `item` in differ to be "refreshed". It means that the item will be marked as removed and inserted
|
|
170
188
|
* in the differ changes set, so it will be effectively re-converted when the differ changes are handled by a dispatcher.
|
|
@@ -253,7 +271,7 @@ export default class Differ {
|
|
|
253
271
|
*/
|
|
254
272
|
export type DiffItem = DiffItemInsert | DiffItemRemove | DiffItemAttribute;
|
|
255
273
|
/**
|
|
256
|
-
*
|
|
274
|
+
* A single diff item for inserted nodes.
|
|
257
275
|
*/
|
|
258
276
|
export interface DiffItemInsert {
|
|
259
277
|
/**
|
|
@@ -273,12 +291,12 @@ export interface DiffItemInsert {
|
|
|
273
291
|
*/
|
|
274
292
|
position: Position;
|
|
275
293
|
/**
|
|
276
|
-
* The length of an inserted text node. For elements it is always 1 as each inserted element is counted as a one.
|
|
294
|
+
* The length of an inserted text node. For elements, it is always 1 as each inserted element is counted as a one.
|
|
277
295
|
*/
|
|
278
296
|
length: number;
|
|
279
297
|
}
|
|
280
298
|
/**
|
|
281
|
-
*
|
|
299
|
+
* A single diff item for removed nodes.
|
|
282
300
|
*/
|
|
283
301
|
export interface DiffItemRemove {
|
|
284
302
|
/**
|
|
@@ -298,12 +316,12 @@ export interface DiffItemRemove {
|
|
|
298
316
|
*/
|
|
299
317
|
position: Position;
|
|
300
318
|
/**
|
|
301
|
-
* The length of a removed text node. For elements it is always 1 as each removed element is counted as a one.
|
|
319
|
+
* The length of a removed text node. For elements, it is always 1, as each removed element is counted as a one.
|
|
302
320
|
*/
|
|
303
321
|
length: number;
|
|
304
322
|
}
|
|
305
323
|
/**
|
|
306
|
-
*
|
|
324
|
+
* A single diff item for attribute change.
|
|
307
325
|
*/
|
|
308
326
|
export interface DiffItemAttribute {
|
|
309
327
|
/**
|
|
@@ -327,3 +345,29 @@ export interface DiffItemAttribute {
|
|
|
327
345
|
*/
|
|
328
346
|
range: Range;
|
|
329
347
|
}
|
|
348
|
+
/**
|
|
349
|
+
* A single diff item for a changed root.
|
|
350
|
+
*/
|
|
351
|
+
export interface DiffItemRoot {
|
|
352
|
+
/**
|
|
353
|
+
* Name of the changed root.
|
|
354
|
+
*/
|
|
355
|
+
name: string;
|
|
356
|
+
/**
|
|
357
|
+
* Set accordingly if the root got attached or detached. Otherwise, not set.
|
|
358
|
+
*/
|
|
359
|
+
state?: 'attached' | 'detached';
|
|
360
|
+
/**
|
|
361
|
+
* Keeps all attribute changes that happened on the root.
|
|
362
|
+
*
|
|
363
|
+
* The keys are keys of the changed attributes. The values are objects containing the attribute value before the change
|
|
364
|
+
* (`oldValue`) and after the change (`newValue`).
|
|
365
|
+
*
|
|
366
|
+
* Note, that if the root state changed (`state` is set), then `attributes` property will not be set. All attributes should be
|
|
367
|
+
* handled together with the root being attached or detached.
|
|
368
|
+
*/
|
|
369
|
+
attributes?: Record<string, {
|
|
370
|
+
oldValue: unknown;
|
|
371
|
+
newValue: unknown;
|
|
372
|
+
}>;
|
|
373
|
+
}
|
package/src/model/differ.js
CHANGED
|
@@ -44,6 +44,12 @@ export default class Differ {
|
|
|
44
44
|
* - `newMarkerData`.
|
|
45
45
|
*/
|
|
46
46
|
this._changedMarkers = new Map();
|
|
47
|
+
/**
|
|
48
|
+
* A map that stores all roots that have been changed.
|
|
49
|
+
*
|
|
50
|
+
* The keys are the names of the roots while value represents the changes.
|
|
51
|
+
*/
|
|
52
|
+
this._changedRoots = new Map();
|
|
47
53
|
/**
|
|
48
54
|
* Stores the number of changes that were processed. Used to order the changes chronologically. It is important
|
|
49
55
|
* when changes are sorted.
|
|
@@ -75,14 +81,11 @@ export default class Differ {
|
|
|
75
81
|
* Informs whether there are any changes buffered in `Differ`.
|
|
76
82
|
*/
|
|
77
83
|
get isEmpty() {
|
|
78
|
-
return this._changesInElement.size == 0 && this._changedMarkers.size == 0;
|
|
84
|
+
return this._changesInElement.size == 0 && this._changedMarkers.size == 0 && this._changedRoots.size == 0;
|
|
79
85
|
}
|
|
80
86
|
/**
|
|
81
87
|
* Buffers the given operation. An operation has to be buffered before it is executed.
|
|
82
88
|
*
|
|
83
|
-
* Operation type is checked and it is checked which nodes it will affect. These nodes are then stored in `Differ`
|
|
84
|
-
* in the state before the operation is executed.
|
|
85
|
-
*
|
|
86
89
|
* @param operationToBuffer An operation to buffer.
|
|
87
90
|
*/
|
|
88
91
|
bufferOperation(operationToBuffer) {
|
|
@@ -174,6 +177,18 @@ export default class Differ {
|
|
|
174
177
|
}
|
|
175
178
|
break;
|
|
176
179
|
}
|
|
180
|
+
case 'detachRoot':
|
|
181
|
+
case 'addRoot': {
|
|
182
|
+
this._bufferRootStateChange(operation.rootName, operation.isAdd);
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
case 'addRootAttribute':
|
|
186
|
+
case 'removeRootAttribute':
|
|
187
|
+
case 'changeRootAttribute': {
|
|
188
|
+
const rootName = operation.root.rootName;
|
|
189
|
+
this._bufferRootAttributeChange(rootName, operation.key, operation.oldValue, operation.newValue);
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
177
192
|
}
|
|
178
193
|
// Clear cache after each buffered operation as it is no longer valid.
|
|
179
194
|
this._cachedChanges = null;
|
|
@@ -249,6 +264,7 @@ export default class Differ {
|
|
|
249
264
|
*
|
|
250
265
|
* * model structure changes,
|
|
251
266
|
* * attribute changes,
|
|
267
|
+
* * a root is added or detached,
|
|
252
268
|
* * changes of markers which were defined as `affectsData`,
|
|
253
269
|
* * changes of markers' `affectsData` property.
|
|
254
270
|
*/
|
|
@@ -256,6 +272,9 @@ export default class Differ {
|
|
|
256
272
|
if (this._changesInElement.size > 0) {
|
|
257
273
|
return true;
|
|
258
274
|
}
|
|
275
|
+
if (this._changedRoots.size > 0) {
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
259
278
|
for (const { newMarkerData, oldMarkerData } of this._changedMarkers.values()) {
|
|
260
279
|
if (newMarkerData.affectsData !== oldMarkerData.affectsData) {
|
|
261
280
|
return true;
|
|
@@ -429,6 +448,27 @@ export default class Differ {
|
|
|
429
448
|
return this._cachedChanges.slice();
|
|
430
449
|
}
|
|
431
450
|
}
|
|
451
|
+
/**
|
|
452
|
+
* Returns all roots that have changed (either were attached, or detached, or their attributes changed).
|
|
453
|
+
*
|
|
454
|
+
* @returns Diff between the old and the new roots state.
|
|
455
|
+
*/
|
|
456
|
+
getChangedRoots() {
|
|
457
|
+
return Array.from(this._changedRoots.values()).map(diffItem => {
|
|
458
|
+
const entry = { ...diffItem };
|
|
459
|
+
if (entry.state !== undefined) {
|
|
460
|
+
// The root was attached or detached -- do not return its attributes changes.
|
|
461
|
+
// If the root was attached, it should be handled as a whole, together with its attributes, the same way as model nodes.
|
|
462
|
+
// If the root was detached, its attributes should be discarded anyway.
|
|
463
|
+
//
|
|
464
|
+
// Keep in mind that filtering must happen on this stage (when retrieving changes). If filtering happens on-the-fly as
|
|
465
|
+
// the attributes change, it may lead to incorrect situation, e.g.: detach root, change attribute, re-attach root.
|
|
466
|
+
// In this case, attribute change cannot be filtered. After the root is re-attached, the attribute change must be kept.
|
|
467
|
+
delete entry.attributes;
|
|
468
|
+
}
|
|
469
|
+
return entry;
|
|
470
|
+
});
|
|
471
|
+
}
|
|
432
472
|
/**
|
|
433
473
|
* Returns a set of model items that were marked to get refreshed.
|
|
434
474
|
*/
|
|
@@ -442,9 +482,69 @@ export default class Differ {
|
|
|
442
482
|
this._changesInElement.clear();
|
|
443
483
|
this._elementSnapshots.clear();
|
|
444
484
|
this._changedMarkers.clear();
|
|
485
|
+
this._changedRoots.clear();
|
|
445
486
|
this._refreshedItems = new Set();
|
|
446
487
|
this._cachedChanges = null;
|
|
447
488
|
}
|
|
489
|
+
/**
|
|
490
|
+
* Buffers the root state change after the root was attached or detached
|
|
491
|
+
*/
|
|
492
|
+
_bufferRootStateChange(rootName, isAttached) {
|
|
493
|
+
if (!this._changedRoots.has(rootName)) {
|
|
494
|
+
this._changedRoots.set(rootName, { name: rootName, state: isAttached ? 'attached' : 'detached' });
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const diffItem = this._changedRoots.get(rootName);
|
|
498
|
+
if (diffItem.state !== undefined) {
|
|
499
|
+
// Root `state` can only toggle between of the values ('attached' or 'detached') and no value. It cannot be any other way,
|
|
500
|
+
// because if the root was originally attached it can only become detached. Then, if it is re-attached in the same batch of
|
|
501
|
+
// changes, it gets back to "no change" (which means no value). Same if the root was originally detached.
|
|
502
|
+
delete diffItem.state;
|
|
503
|
+
if (diffItem.attributes === undefined) {
|
|
504
|
+
// If there is no `state` change and no `attributes` change, remove the entry.
|
|
505
|
+
this._changedRoots.delete(rootName);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
diffItem.state = isAttached ? 'attached' : 'detached';
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Buffers a root attribute change.
|
|
514
|
+
*/
|
|
515
|
+
_bufferRootAttributeChange(rootName, key, oldValue, newValue) {
|
|
516
|
+
const diffItem = this._changedRoots.get(rootName) || { name: rootName };
|
|
517
|
+
const attrs = diffItem.attributes || {};
|
|
518
|
+
if (attrs[key]) {
|
|
519
|
+
// If this attribute or metadata was already changed earlier and is changed again, check to what value it is changed.
|
|
520
|
+
const attrEntry = attrs[key];
|
|
521
|
+
if (newValue === attrEntry.oldValue) {
|
|
522
|
+
// If it was changed back to the old value, remove the entry.
|
|
523
|
+
delete attrs[key];
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
// If it was changed to a different value, update the entry.
|
|
527
|
+
attrEntry.newValue = newValue;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
// If this attribute or metadata was not set earlier, add an entry.
|
|
532
|
+
attrs[key] = { oldValue, newValue };
|
|
533
|
+
}
|
|
534
|
+
if (Object.entries(attrs).length === 0) {
|
|
535
|
+
// If attributes or metadata changes set became empty, remove it from the diff item.
|
|
536
|
+
delete diffItem.attributes;
|
|
537
|
+
if (diffItem.state === undefined) {
|
|
538
|
+
// If there is no `state` change and no `attributes` change, remove the entry.
|
|
539
|
+
this._changedRoots.delete(rootName);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
// Make sure that, if a new object in the structure was created, it gets set.
|
|
544
|
+
diffItem.attributes = attrs;
|
|
545
|
+
this._changedRoots.set(rootName, diffItem);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
448
548
|
/**
|
|
449
549
|
* Marks the given `item` in differ to be "refreshed". It means that the item will be marked as removed and inserted
|
|
450
550
|
* in the differ changes set, so it will be effectively re-converted when the differ changes are handled by a dispatcher.
|
package/src/model/document.d.ts
CHANGED
|
@@ -48,8 +48,8 @@ export default class Document extends Document_base {
|
|
|
48
48
|
*/
|
|
49
49
|
readonly selection: DocumentSelection;
|
|
50
50
|
/**
|
|
51
|
-
* A list of roots that are owned and managed by this document. Use {@link #createRoot} and
|
|
52
|
-
* {@link #
|
|
51
|
+
* A list of roots that are owned and managed by this document. Use {@link #createRoot}, {@link #getRoot} and
|
|
52
|
+
* {@link #getRootNames} to manipulate it.
|
|
53
53
|
*/
|
|
54
54
|
readonly roots: Collection<RootElement>;
|
|
55
55
|
/**
|
|
@@ -61,7 +61,7 @@ export default class Document extends Document_base {
|
|
|
61
61
|
*/
|
|
62
62
|
private readonly _postFixers;
|
|
63
63
|
/**
|
|
64
|
-
* A
|
|
64
|
+
* A flag that indicates whether the selection has changed since last change block.
|
|
65
65
|
*/
|
|
66
66
|
private _hasSelectionChangedFromTheLastChangeBlock;
|
|
67
67
|
/**
|
|
@@ -87,8 +87,11 @@ export default class Document extends Document_base {
|
|
|
87
87
|
/**
|
|
88
88
|
* Creates a new root.
|
|
89
89
|
*
|
|
90
|
+
* **Note:** do not use this method after the editor has been initialized! If you want to dynamically add a root, use
|
|
91
|
+
* {@link module:engine/model/writer~Writer#addRoot `model.Writer#addRoot`} instead.
|
|
92
|
+
*
|
|
90
93
|
* @param elementName The element name. Defaults to `'$root'` which also has some basic schema defined
|
|
91
|
-
* (`$block`
|
|
94
|
+
* (e.g. `$block` elements are allowed inside the `$root`). Make sure to define a proper schema if you use a different name.
|
|
92
95
|
* @param rootName A unique root name.
|
|
93
96
|
* @returns The created root.
|
|
94
97
|
*/
|
|
@@ -100,16 +103,23 @@ export default class Document extends Document_base {
|
|
|
100
103
|
/**
|
|
101
104
|
* Returns a root by its name.
|
|
102
105
|
*
|
|
103
|
-
*
|
|
106
|
+
* Detached roots are returned by this method. This is to be able to operate on the detached root (for example, to be able to create
|
|
107
|
+
* a position inside such a root for undo feature purposes).
|
|
108
|
+
*
|
|
109
|
+
* @param name The root name of the root to return.
|
|
104
110
|
* @returns The root registered under a given name or `null` when there is no root with the given name.
|
|
105
111
|
*/
|
|
106
112
|
getRoot(name?: string): RootElement | null;
|
|
107
113
|
/**
|
|
108
|
-
* Returns an array with names of all roots (
|
|
114
|
+
* Returns an array with names of all roots added to the document (except the {@link #graveyard graveyard root}).
|
|
115
|
+
*
|
|
116
|
+
* Detached roots **are not** returned by this method by default. This is to make sure that all features or algorithms that operate
|
|
117
|
+
* on the document data know which roots are still a part of the document and should be processed.
|
|
109
118
|
*
|
|
119
|
+
* @param includeDetached Specified whether detached roots should be returned as well.
|
|
110
120
|
* @returns Roots names.
|
|
111
121
|
*/
|
|
112
|
-
getRootNames(): Array<string>;
|
|
122
|
+
getRootNames(includeDetached?: boolean): Array<string>;
|
|
113
123
|
/**
|
|
114
124
|
* Used to register a post-fixer callback. A post-fixer mechanism guarantees that the features
|
|
115
125
|
* will operate on a correct model state.
|
package/src/model/document.js
CHANGED
|
@@ -79,6 +79,33 @@ export default class Document extends EmitterMixin() {
|
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
81
|
});
|
|
82
|
+
// This is a solution for a problem that may occur during real-time editing. If one client detached a root and another added
|
|
83
|
+
// something there at the same moment, the OT does not solve this problem currently. In such situation, the added elements would
|
|
84
|
+
// stay in the detached root.
|
|
85
|
+
//
|
|
86
|
+
// This is incorrect, a detached root should be empty and all elements from it should be removed. To solve this, the post-fixer will
|
|
87
|
+
// remove any element that is left in a detached root.
|
|
88
|
+
//
|
|
89
|
+
// Similarly, markers that are created at the beginning or at the end of the detached root will not be removed as well.
|
|
90
|
+
//
|
|
91
|
+
// The drawback of this solution over the OT solution is that the elements removed by the post-fixer will never be brought back.
|
|
92
|
+
// If the root detachment gets undone (and the root is brought back), the removed elements will not be there.
|
|
93
|
+
this.registerPostFixer(writer => {
|
|
94
|
+
let result = false;
|
|
95
|
+
for (const root of this.roots) {
|
|
96
|
+
if (!root.isAttached() && !root.isEmpty) {
|
|
97
|
+
writer.remove(writer.createRangeIn(root));
|
|
98
|
+
result = true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
for (const marker of this.model.markers) {
|
|
102
|
+
if (!marker.getRange().root.isAttached()) {
|
|
103
|
+
writer.removeMarker(marker);
|
|
104
|
+
result = true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
});
|
|
82
109
|
}
|
|
83
110
|
/**
|
|
84
111
|
* The document version. Every applied operation increases the version number. It is used to
|
|
@@ -104,8 +131,11 @@ export default class Document extends EmitterMixin() {
|
|
|
104
131
|
/**
|
|
105
132
|
* Creates a new root.
|
|
106
133
|
*
|
|
134
|
+
* **Note:** do not use this method after the editor has been initialized! If you want to dynamically add a root, use
|
|
135
|
+
* {@link module:engine/model/writer~Writer#addRoot `model.Writer#addRoot`} instead.
|
|
136
|
+
*
|
|
107
137
|
* @param elementName The element name. Defaults to `'$root'` which also has some basic schema defined
|
|
108
|
-
* (`$block`
|
|
138
|
+
* (e.g. `$block` elements are allowed inside the `$root`). Make sure to define a proper schema if you use a different name.
|
|
109
139
|
* @param rootName A unique root name.
|
|
110
140
|
* @returns The created root.
|
|
111
141
|
*/
|
|
@@ -132,19 +162,28 @@ export default class Document extends EmitterMixin() {
|
|
|
132
162
|
/**
|
|
133
163
|
* Returns a root by its name.
|
|
134
164
|
*
|
|
135
|
-
*
|
|
165
|
+
* Detached roots are returned by this method. This is to be able to operate on the detached root (for example, to be able to create
|
|
166
|
+
* a position inside such a root for undo feature purposes).
|
|
167
|
+
*
|
|
168
|
+
* @param name The root name of the root to return.
|
|
136
169
|
* @returns The root registered under a given name or `null` when there is no root with the given name.
|
|
137
170
|
*/
|
|
138
171
|
getRoot(name = 'main') {
|
|
139
172
|
return this.roots.get(name);
|
|
140
173
|
}
|
|
141
174
|
/**
|
|
142
|
-
* Returns an array with names of all roots (
|
|
175
|
+
* Returns an array with names of all roots added to the document (except the {@link #graveyard graveyard root}).
|
|
176
|
+
*
|
|
177
|
+
* Detached roots **are not** returned by this method by default. This is to make sure that all features or algorithms that operate
|
|
178
|
+
* on the document data know which roots are still a part of the document and should be processed.
|
|
143
179
|
*
|
|
180
|
+
* @param includeDetached Specified whether detached roots should be returned as well.
|
|
144
181
|
* @returns Roots names.
|
|
145
182
|
*/
|
|
146
|
-
getRootNames() {
|
|
147
|
-
return Array.from(this.roots
|
|
183
|
+
getRootNames(includeDetached = false) {
|
|
184
|
+
return Array.from(this.roots)
|
|
185
|
+
.filter(root => root.rootName != graveyardName && (includeDetached || root.isAttached()))
|
|
186
|
+
.map(root => root.rootName);
|
|
148
187
|
}
|
|
149
188
|
/**
|
|
150
189
|
* Used to register a post-fixer callback. A post-fixer mechanism guarantees that the features
|
|
@@ -82,6 +82,10 @@ export default class DocumentFragment extends TypeCheckable implements Iterable<
|
|
|
82
82
|
* Artificial owner of `DocumentFragment`. Returns `null`. Added for compatibility reasons.
|
|
83
83
|
*/
|
|
84
84
|
get document(): null;
|
|
85
|
+
/**
|
|
86
|
+
* Returns `false` as `DocumentFragment` by definition is not attached to a document. Added for compatibility reasons.
|
|
87
|
+
*/
|
|
88
|
+
isAttached(): false;
|
|
85
89
|
/**
|
|
86
90
|
* Returns empty array. Added for compatibility reasons.
|
|
87
91
|
*/
|
|
@@ -100,6 +100,12 @@ export default class DocumentFragment extends TypeCheckable {
|
|
|
100
100
|
get document() {
|
|
101
101
|
return null;
|
|
102
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Returns `false` as `DocumentFragment` by definition is not attached to a document. Added for compatibility reasons.
|
|
105
|
+
*/
|
|
106
|
+
isAttached() {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
103
109
|
/**
|
|
104
110
|
* Returns empty array. Added for compatibility reasons.
|
|
105
111
|
*/
|
package/src/model/node.d.ts
CHANGED
|
@@ -66,15 +66,15 @@ export default abstract class Node extends TypeCheckable {
|
|
|
66
66
|
*/
|
|
67
67
|
get document(): Document | null;
|
|
68
68
|
/**
|
|
69
|
-
* Index of this node in
|
|
69
|
+
* Index of this node in its parent or `null` if the node has no parent.
|
|
70
70
|
*
|
|
71
71
|
* Accessing this property throws an error if this node's parent element does not contain it.
|
|
72
72
|
* This means that model tree got broken.
|
|
73
73
|
*/
|
|
74
74
|
get index(): number | null;
|
|
75
75
|
/**
|
|
76
|
-
* Offset at which this node starts in
|
|
77
|
-
* of all
|
|
76
|
+
* Offset at which this node starts in its parent. It is equal to the sum of {@link #offsetSize offsetSize}
|
|
77
|
+
* of all its previous siblings. Equals to `null` if node has no parent.
|
|
78
78
|
*
|
|
79
79
|
* Accessing this property throws an error if this node's parent element does not contain it.
|
|
80
80
|
* This means that model tree got broken.
|
|
@@ -107,7 +107,7 @@ export default abstract class Node extends TypeCheckable {
|
|
|
107
107
|
*/
|
|
108
108
|
get root(): Node | DocumentFragment;
|
|
109
109
|
/**
|
|
110
|
-
* Returns true if the node is
|
|
110
|
+
* Returns `true` if the node is inside a document root that is attached to the document.
|
|
111
111
|
*/
|
|
112
112
|
isAttached(): boolean;
|
|
113
113
|
/**
|