@ckeditor/ckeditor5-engine 38.2.0-alpha.0 → 39.0.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 +0 -1
- package/package.json +3 -4
- package/src/controller/editingcontroller.js +2 -2
- package/src/conversion/downcastdispatcher.d.ts +15 -0
- package/src/conversion/downcastdispatcher.js +28 -19
- package/src/conversion/downcasthelpers.d.ts +6 -6
- package/src/conversion/downcasthelpers.js +6 -6
- package/src/dev-utils/view.js +1 -1
- package/src/index.d.ts +1 -0
- package/src/index.js +1 -0
- package/src/model/differ.d.ts +14 -0
- package/src/model/differ.js +70 -11
- package/src/model/document.d.ts +9 -1
- package/src/model/document.js +14 -9
- package/src/model/documentselection.js +8 -2
- package/src/model/model.d.ts +0 -1
- package/src/model/model.js +0 -1
- package/src/model/operation/rootoperation.d.ts +0 -4
- package/src/model/operation/rootoperation.js +0 -24
- package/src/model/operation/transform.js +2 -2
- package/src/model/rootelement.d.ts +6 -0
- package/src/model/rootelement.js +6 -0
- package/src/model/schema.d.ts +10 -0
- package/src/model/schema.js +5 -0
- package/src/model/utils/autoparagraphing.js +1 -2
- package/src/view/domconverter.d.ts +43 -53
- package/src/view/domconverter.js +266 -214
- package/src/view/editableelement.d.ts +10 -0
- package/src/view/editableelement.js +1 -0
- package/src/view/filler.d.ts +2 -2
- package/src/view/filler.js +6 -4
- package/src/view/observer/selectionobserver.js +2 -2
- package/src/view/placeholder.d.ts +13 -5
- package/src/view/placeholder.js +21 -12
- package/src/view/renderer.js +1 -2
- package/src/view/view.d.ts +14 -7
- package/src/view/view.js +13 -1
package/README.md
CHANGED
|
@@ -4,7 +4,6 @@ CKEditor 5 editing engine
|
|
|
4
4
|
[](https://www.npmjs.com/package/@ckeditor/ckeditor5-engine)
|
|
5
5
|
[](https://coveralls.io/github/ckeditor/ckeditor5?branch=master)
|
|
6
6
|
[](https://app.travis-ci.com/github/ckeditor/ckeditor5)
|
|
7
|
-

|
|
8
7
|
|
|
9
8
|
The CKEditor 5 editing engine implements a flexible MVC-based architecture for creating rich text editing features.
|
|
10
9
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ckeditor/ckeditor5-engine",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "39.0.0",
|
|
4
4
|
"description": "The editing engine of CKEditor 5 – the best browser-based rich text editor.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"wysiwyg",
|
|
@@ -22,10 +22,9 @@
|
|
|
22
22
|
"ckeditor5-dll"
|
|
23
23
|
],
|
|
24
24
|
"main": "src/index.js",
|
|
25
|
-
"type": "module",
|
|
26
25
|
"dependencies": {
|
|
27
|
-
"@ckeditor/ckeditor5-utils": "
|
|
28
|
-
"lodash-es": "
|
|
26
|
+
"@ckeditor/ckeditor5-utils": "39.0.0",
|
|
27
|
+
"lodash-es": "4.17.21"
|
|
29
28
|
},
|
|
30
29
|
"engines": {
|
|
31
30
|
"node": ">=16.0.0",
|
|
@@ -10,7 +10,7 @@ import RootEditableElement from '../view/rooteditableelement';
|
|
|
10
10
|
import View from '../view/view';
|
|
11
11
|
import Mapper from '../conversion/mapper';
|
|
12
12
|
import DowncastDispatcher from '../conversion/downcastdispatcher';
|
|
13
|
-
import {
|
|
13
|
+
import { cleanSelection, convertCollapsedSelection, convertRangeSelection, insertAttributesAndChildren, insertText, remove } from '../conversion/downcasthelpers';
|
|
14
14
|
import { convertSelectionChange } from '../conversion/upcasthelpers';
|
|
15
15
|
import { tryFixingRange } from '../model/utils/selection-post-fixer';
|
|
16
16
|
// @if CK_DEBUG_ENGINE // const { dumpTrees, initDocumentDumping } = require( '../dev-utils/utils' );
|
|
@@ -67,7 +67,7 @@ export default class EditingController extends ObservableMixin() {
|
|
|
67
67
|
this.downcastDispatcher.on('insert', insertAttributesAndChildren(), { priority: 'lowest' });
|
|
68
68
|
this.downcastDispatcher.on('remove', remove(), { priority: 'low' });
|
|
69
69
|
// Attach default model selection converters.
|
|
70
|
-
this.downcastDispatcher.on('
|
|
70
|
+
this.downcastDispatcher.on('cleanSelection', cleanSelection());
|
|
71
71
|
this.downcastDispatcher.on('selection', convertRangeSelection(), { priority: 'low' });
|
|
72
72
|
this.downcastDispatcher.on('selection', convertCollapsedSelection(), { priority: 'low' });
|
|
73
73
|
// Binds {@link module:engine/view/document~Document#roots view roots collection} to
|
|
@@ -346,6 +346,9 @@ type EventMap<TItem = Item> = {
|
|
|
346
346
|
attributeOldValue: unknown;
|
|
347
347
|
attributeNewValue: unknown;
|
|
348
348
|
};
|
|
349
|
+
cleanSelection: {
|
|
350
|
+
selection: Selection | DocumentSelection;
|
|
351
|
+
};
|
|
349
352
|
selection: {
|
|
350
353
|
selection: Selection | DocumentSelection;
|
|
351
354
|
};
|
|
@@ -433,6 +436,18 @@ export type DowncastAttributeEvent<TItem = Item | Selection | DocumentSelection>
|
|
|
433
436
|
* to be used by callback, passed in `DowncastDispatcher` constructor.
|
|
434
437
|
*/
|
|
435
438
|
export type DowncastSelectionEvent = DowncastEvent<'selection'>;
|
|
439
|
+
/**
|
|
440
|
+
* Fired at the beginning of selection conversion, before
|
|
441
|
+
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:selection selection} events.
|
|
442
|
+
*
|
|
443
|
+
* Should be used to clean up the view state at the current selection position, before the selection is moved to another place.
|
|
444
|
+
*
|
|
445
|
+
* @eventName ~DowncastDispatcher#cleanSelection
|
|
446
|
+
* @param {module:engine/model/selection~Selection} selection Selection that is converted.
|
|
447
|
+
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi Conversion interface
|
|
448
|
+
* to be used by callback, passed in `DowncastDispatcher` constructor.
|
|
449
|
+
*/
|
|
450
|
+
export type DowncastCleanSelectionEvent = DowncastEvent<'cleanSelection'>;
|
|
436
451
|
/**
|
|
437
452
|
* Fired when a new marker is added to the model. Also fired when a collapsed model selection that is inside a marker is converted.
|
|
438
453
|
*
|
|
@@ -198,38 +198,47 @@ export default class DowncastDispatcher extends EmitterMixin() {
|
|
|
198
198
|
* @param writer View writer that should be used to modify the view document.
|
|
199
199
|
*/
|
|
200
200
|
convertSelection(selection, markers, writer) {
|
|
201
|
-
const markersAtSelection = Array.from(markers.getMarkersAtPosition(selection.getFirstPosition()));
|
|
202
201
|
const conversionApi = this._createConversionApi(writer);
|
|
202
|
+
// First perform a clean-up at the current position of the selection.
|
|
203
|
+
this.fire('cleanSelection', { selection }, conversionApi);
|
|
204
|
+
// Don't convert selection if it is in a model root that does not have a view root (for now this is only the graveyard root).
|
|
205
|
+
const modelRoot = selection.getFirstPosition().root;
|
|
206
|
+
if (!conversionApi.mapper.toViewElement(modelRoot)) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
// Now, perform actual selection conversion.
|
|
210
|
+
const markersAtSelection = Array.from(markers.getMarkersAtPosition(selection.getFirstPosition()));
|
|
203
211
|
this._addConsumablesForSelection(conversionApi.consumable, selection, markersAtSelection);
|
|
204
212
|
this.fire('selection', { selection }, conversionApi);
|
|
205
213
|
if (!selection.isCollapsed) {
|
|
206
214
|
return;
|
|
207
215
|
}
|
|
208
216
|
for (const marker of markersAtSelection) {
|
|
209
|
-
|
|
210
|
-
if (!shouldMarkerChangeBeConverted(selection.getFirstPosition(), marker, conversionApi.mapper)) {
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
const data = {
|
|
214
|
-
item: selection,
|
|
215
|
-
markerName: marker.name,
|
|
216
|
-
markerRange
|
|
217
|
-
};
|
|
217
|
+
// Do not fire event if the marker has been consumed.
|
|
218
218
|
if (conversionApi.consumable.test(selection, 'addMarker:' + marker.name)) {
|
|
219
|
+
const markerRange = marker.getRange();
|
|
220
|
+
if (!shouldMarkerChangeBeConverted(selection.getFirstPosition(), marker, conversionApi.mapper)) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const data = {
|
|
224
|
+
item: selection,
|
|
225
|
+
markerName: marker.name,
|
|
226
|
+
markerRange
|
|
227
|
+
};
|
|
219
228
|
this.fire(`addMarker:${marker.name}`, data, conversionApi);
|
|
220
229
|
}
|
|
221
230
|
}
|
|
222
231
|
for (const key of selection.getAttributeKeys()) {
|
|
223
|
-
const data = {
|
|
224
|
-
item: selection,
|
|
225
|
-
range: selection.getFirstRange(),
|
|
226
|
-
attributeKey: key,
|
|
227
|
-
attributeOldValue: null,
|
|
228
|
-
attributeNewValue: selection.getAttribute(key)
|
|
229
|
-
};
|
|
230
232
|
// Do not fire event if the attribute has been consumed.
|
|
231
|
-
if (conversionApi.consumable.test(selection, 'attribute:' +
|
|
232
|
-
|
|
233
|
+
if (conversionApi.consumable.test(selection, 'attribute:' + key)) {
|
|
234
|
+
const data = {
|
|
235
|
+
item: selection,
|
|
236
|
+
range: selection.getFirstRange(),
|
|
237
|
+
attributeKey: key,
|
|
238
|
+
attributeOldValue: null,
|
|
239
|
+
attributeNewValue: selection.getAttribute(key)
|
|
240
|
+
};
|
|
241
|
+
this.fire(`attribute:${key}:$text`, data, conversionApi);
|
|
233
242
|
}
|
|
234
243
|
}
|
|
235
244
|
}
|
|
@@ -765,7 +765,7 @@ export default class DowncastHelpers extends ConversionHelpers<DowncastDispatche
|
|
|
765
765
|
* ```ts
|
|
766
766
|
* // Using a custom function which is the same as the default conversion:
|
|
767
767
|
* editor.conversion.for( 'dataDowncast' ).markerToData( {
|
|
768
|
-
* model: 'comment'
|
|
768
|
+
* model: 'comment',
|
|
769
769
|
* view: markerName => ( {
|
|
770
770
|
* group: 'comment',
|
|
771
771
|
* name: markerName.substr( 8 ) // Removes 'comment:' part.
|
|
@@ -774,7 +774,7 @@ export default class DowncastHelpers extends ConversionHelpers<DowncastDispatche
|
|
|
774
774
|
*
|
|
775
775
|
* // Using the converter priority:
|
|
776
776
|
* editor.conversion.for( 'dataDowncast' ).markerToData( {
|
|
777
|
-
* model: 'comment'
|
|
777
|
+
* model: 'comment',
|
|
778
778
|
* view: markerName => ( {
|
|
779
779
|
* group: 'comment',
|
|
780
780
|
* name: markerName.substr( 8 ) // Removes 'comment:' part.
|
|
@@ -880,7 +880,7 @@ export declare function convertRangeSelection(): (evt: EventInfo, data: {
|
|
|
880
880
|
* converted, broken attributes might be merged again, or the position where the selection is may be wrapped
|
|
881
881
|
* with different, appropriate attribute elements.
|
|
882
882
|
*
|
|
883
|
-
* See also {@link module:engine/conversion/downcasthelpers~
|
|
883
|
+
* See also {@link module:engine/conversion/downcasthelpers~cleanSelection} which does a clean-up
|
|
884
884
|
* by merging attributes.
|
|
885
885
|
*
|
|
886
886
|
* @returns Selection converter.
|
|
@@ -889,7 +889,7 @@ export declare function convertCollapsedSelection(): (evt: EventInfo, data: {
|
|
|
889
889
|
selection: ModelSelection | ModelDocumentSelection;
|
|
890
890
|
}, conversionApi: DowncastConversionApi) => void;
|
|
891
891
|
/**
|
|
892
|
-
* Function factory that creates a converter which
|
|
892
|
+
* Function factory that creates a converter which cleans artifacts after the previous
|
|
893
893
|
* {@link module:engine/model/selection~Selection model selection} conversion. It removes all empty
|
|
894
894
|
* {@link module:engine/view/attributeelement~AttributeElement view attribute elements} and merges sibling attributes at all start and end
|
|
895
895
|
* positions of all ranges.
|
|
@@ -908,7 +908,7 @@ export declare function convertCollapsedSelection(): (evt: EventInfo, data: {
|
|
|
908
908
|
* This listener should be assigned before any converter for the new selection:
|
|
909
909
|
*
|
|
910
910
|
* ```ts
|
|
911
|
-
* modelDispatcher.on( '
|
|
911
|
+
* modelDispatcher.on( 'cleanSelection', cleanSelection() );
|
|
912
912
|
* ```
|
|
913
913
|
*
|
|
914
914
|
* See {@link module:engine/conversion/downcasthelpers~convertCollapsedSelection}
|
|
@@ -916,7 +916,7 @@ export declare function convertCollapsedSelection(): (evt: EventInfo, data: {
|
|
|
916
916
|
*
|
|
917
917
|
* @returns Selection converter.
|
|
918
918
|
*/
|
|
919
|
-
export declare function
|
|
919
|
+
export declare function cleanSelection(): (evt: EventInfo, data: unknown, conversionApi: DowncastConversionApi) => void;
|
|
920
920
|
/**
|
|
921
921
|
* Function factory that creates a converter which converts the set/change/remove attribute changes from the model to the view.
|
|
922
922
|
* It can also be used to convert selection attributes. In that case, an empty attribute element will be created and the
|
|
@@ -715,7 +715,7 @@ export default class DowncastHelpers extends ConversionHelpers {
|
|
|
715
715
|
* ```ts
|
|
716
716
|
* // Using a custom function which is the same as the default conversion:
|
|
717
717
|
* editor.conversion.for( 'dataDowncast' ).markerToData( {
|
|
718
|
-
* model: 'comment'
|
|
718
|
+
* model: 'comment',
|
|
719
719
|
* view: markerName => ( {
|
|
720
720
|
* group: 'comment',
|
|
721
721
|
* name: markerName.substr( 8 ) // Removes 'comment:' part.
|
|
@@ -724,7 +724,7 @@ export default class DowncastHelpers extends ConversionHelpers {
|
|
|
724
724
|
*
|
|
725
725
|
* // Using the converter priority:
|
|
726
726
|
* editor.conversion.for( 'dataDowncast' ).markerToData( {
|
|
727
|
-
* model: 'comment'
|
|
727
|
+
* model: 'comment',
|
|
728
728
|
* view: markerName => ( {
|
|
729
729
|
* group: 'comment',
|
|
730
730
|
* name: markerName.substr( 8 ) // Removes 'comment:' part.
|
|
@@ -876,7 +876,7 @@ export function convertRangeSelection() {
|
|
|
876
876
|
* converted, broken attributes might be merged again, or the position where the selection is may be wrapped
|
|
877
877
|
* with different, appropriate attribute elements.
|
|
878
878
|
*
|
|
879
|
-
* See also {@link module:engine/conversion/downcasthelpers~
|
|
879
|
+
* See also {@link module:engine/conversion/downcasthelpers~cleanSelection} which does a clean-up
|
|
880
880
|
* by merging attributes.
|
|
881
881
|
*
|
|
882
882
|
* @returns Selection converter.
|
|
@@ -898,7 +898,7 @@ export function convertCollapsedSelection() {
|
|
|
898
898
|
};
|
|
899
899
|
}
|
|
900
900
|
/**
|
|
901
|
-
* Function factory that creates a converter which
|
|
901
|
+
* Function factory that creates a converter which cleans artifacts after the previous
|
|
902
902
|
* {@link module:engine/model/selection~Selection model selection} conversion. It removes all empty
|
|
903
903
|
* {@link module:engine/view/attributeelement~AttributeElement view attribute elements} and merges sibling attributes at all start and end
|
|
904
904
|
* positions of all ranges.
|
|
@@ -917,7 +917,7 @@ export function convertCollapsedSelection() {
|
|
|
917
917
|
* This listener should be assigned before any converter for the new selection:
|
|
918
918
|
*
|
|
919
919
|
* ```ts
|
|
920
|
-
* modelDispatcher.on( '
|
|
920
|
+
* modelDispatcher.on( 'cleanSelection', cleanSelection() );
|
|
921
921
|
* ```
|
|
922
922
|
*
|
|
923
923
|
* See {@link module:engine/conversion/downcasthelpers~convertCollapsedSelection}
|
|
@@ -925,7 +925,7 @@ export function convertCollapsedSelection() {
|
|
|
925
925
|
*
|
|
926
926
|
* @returns Selection converter.
|
|
927
927
|
*/
|
|
928
|
-
export function
|
|
928
|
+
export function cleanSelection() {
|
|
929
929
|
return (evt, data, conversionApi) => {
|
|
930
930
|
const viewWriter = conversionApi.writer;
|
|
931
931
|
const viewSelection = viewWriter.document.selection;
|
package/src/dev-utils/view.js
CHANGED
|
@@ -820,7 +820,7 @@ class ViewStringify {
|
|
|
820
820
|
else if (attribute === 'style') {
|
|
821
821
|
attributeValue = [...element.getStyleNames()]
|
|
822
822
|
.sort()
|
|
823
|
-
.map(style => `${style}:${element.getStyle(style)}`)
|
|
823
|
+
.map(style => `${style}:${element.getStyle(style).replace(/"/g, '"')}`)
|
|
824
824
|
.join(';');
|
|
825
825
|
}
|
|
826
826
|
else {
|
package/src/index.d.ts
CHANGED
|
@@ -89,6 +89,7 @@ export { default as ClickObserver } from './view/observer/clickobserver';
|
|
|
89
89
|
export { default as DomEventObserver } from './view/observer/domeventobserver';
|
|
90
90
|
export { default as MouseObserver } from './view/observer/mouseobserver';
|
|
91
91
|
export { default as TabObserver } from './view/observer/tabobserver';
|
|
92
|
+
export { default as FocusObserver } from './view/observer/focusobserver';
|
|
92
93
|
export { default as DowncastWriter } from './view/downcastwriter';
|
|
93
94
|
export { default as UpcastWriter } from './view/upcastwriter';
|
|
94
95
|
export { default as Matcher, type MatcherPattern, type MatcherObjectPattern, type Match, type MatchResult } from './view/matcher';
|
package/src/index.js
CHANGED
|
@@ -63,6 +63,7 @@ export { default as ClickObserver } from './view/observer/clickobserver';
|
|
|
63
63
|
export { default as DomEventObserver } from './view/observer/domeventobserver';
|
|
64
64
|
export { default as MouseObserver } from './view/observer/mouseobserver';
|
|
65
65
|
export { default as TabObserver } from './view/observer/tabobserver';
|
|
66
|
+
export { default as FocusObserver } from './view/observer/focusobserver';
|
|
66
67
|
export { default as DowncastWriter } from './view/downcastwriter';
|
|
67
68
|
export { default as UpcastWriter } from './view/upcastwriter';
|
|
68
69
|
export { default as Matcher } from './view/matcher';
|
package/src/model/differ.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import Position from './position';
|
|
|
9
9
|
import Range from './range';
|
|
10
10
|
import type { default as MarkerCollection, MarkerData } from './markercollection';
|
|
11
11
|
import type Item from './item';
|
|
12
|
+
import type RootElement from './rootelement';
|
|
12
13
|
import type Operation from './operation/operation';
|
|
13
14
|
/**
|
|
14
15
|
* Calculates the difference between two model states.
|
|
@@ -191,6 +192,19 @@ export default class Differ {
|
|
|
191
192
|
* @param item Item to refresh.
|
|
192
193
|
*/
|
|
193
194
|
_refreshItem(item: Item): void;
|
|
195
|
+
/**
|
|
196
|
+
* Buffers all the data related to given root like it was all just added to the editor.
|
|
197
|
+
*
|
|
198
|
+
* Following changes are buffered:
|
|
199
|
+
*
|
|
200
|
+
* * root is attached,
|
|
201
|
+
* * all root content is inserted,
|
|
202
|
+
* * all root attributes are added,
|
|
203
|
+
* * all markers inside the root are added.
|
|
204
|
+
*
|
|
205
|
+
* @internal
|
|
206
|
+
*/
|
|
207
|
+
_bufferRootLoad(root: RootElement): void;
|
|
194
208
|
/**
|
|
195
209
|
* Saves and handles an insert change.
|
|
196
210
|
*/
|
package/src/model/differ.js
CHANGED
|
@@ -94,6 +94,9 @@ export default class Differ {
|
|
|
94
94
|
// Marking changes in them would cause a "double" changing then.
|
|
95
95
|
//
|
|
96
96
|
const operation = operationToBuffer;
|
|
97
|
+
// Note: an operation that happens inside a non-loaded root will be ignored. If the operation happens partially inside
|
|
98
|
+
// a non-loaded root, that part will be ignored (this may happen for move or marker operations).
|
|
99
|
+
//
|
|
97
100
|
switch (operation.type) {
|
|
98
101
|
case 'insert': {
|
|
99
102
|
if (this._isInInsertedElement(operation.position.parent)) {
|
|
@@ -179,12 +182,23 @@ export default class Differ {
|
|
|
179
182
|
}
|
|
180
183
|
case 'detachRoot':
|
|
181
184
|
case 'addRoot': {
|
|
185
|
+
const root = operation.affectedSelectable;
|
|
186
|
+
if (!root._isLoaded) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
// Don't buffer if the root state does not change.
|
|
190
|
+
if (root.isAttached() == operation.isAdd) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
182
193
|
this._bufferRootStateChange(operation.rootName, operation.isAdd);
|
|
183
194
|
break;
|
|
184
195
|
}
|
|
185
196
|
case 'addRootAttribute':
|
|
186
197
|
case 'removeRootAttribute':
|
|
187
198
|
case 'changeRootAttribute': {
|
|
199
|
+
if (!operation.root._isLoaded) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
188
202
|
const rootName = operation.root.rootName;
|
|
189
203
|
this._bufferRootAttributeChange(rootName, operation.key, operation.oldValue, operation.newValue);
|
|
190
204
|
break;
|
|
@@ -201,20 +215,24 @@ export default class Differ {
|
|
|
201
215
|
* @param newMarkerData Marker data after the change.
|
|
202
216
|
*/
|
|
203
217
|
bufferMarkerChange(markerName, oldMarkerData, newMarkerData) {
|
|
204
|
-
|
|
218
|
+
if (oldMarkerData.range && oldMarkerData.range.root.is('rootElement') && !oldMarkerData.range.root._isLoaded) {
|
|
219
|
+
oldMarkerData.range = null;
|
|
220
|
+
}
|
|
221
|
+
if (newMarkerData.range && newMarkerData.range.root.is('rootElement') && !newMarkerData.range.root._isLoaded) {
|
|
222
|
+
newMarkerData.range = null;
|
|
223
|
+
}
|
|
224
|
+
let buffered = this._changedMarkers.get(markerName);
|
|
205
225
|
if (!buffered) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
oldMarkerData
|
|
209
|
-
});
|
|
226
|
+
buffered = { newMarkerData, oldMarkerData };
|
|
227
|
+
this._changedMarkers.set(markerName, buffered);
|
|
210
228
|
}
|
|
211
229
|
else {
|
|
212
230
|
buffered.newMarkerData = newMarkerData;
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
231
|
+
}
|
|
232
|
+
if (buffered.oldMarkerData.range == null && newMarkerData.range == null) {
|
|
233
|
+
// The marker is going to be removed (`newMarkerData.range == null`) but it did not exist before the first buffered change
|
|
234
|
+
// (`buffered.oldMarkerData.range == null`). In this case, do not keep the marker in buffer at all.
|
|
235
|
+
this._changedMarkers.delete(markerName);
|
|
218
236
|
}
|
|
219
237
|
}
|
|
220
238
|
/**
|
|
@@ -496,7 +514,7 @@ export default class Differ {
|
|
|
496
514
|
}
|
|
497
515
|
const diffItem = this._changedRoots.get(rootName);
|
|
498
516
|
if (diffItem.state !== undefined) {
|
|
499
|
-
// Root `state` can only toggle between of the values
|
|
517
|
+
// Root `state` can only toggle between one of the values and no value. It cannot be any other way,
|
|
500
518
|
// because if the root was originally attached it can only become detached. Then, if it is re-attached in the same batch of
|
|
501
519
|
// changes, it gets back to "no change" (which means no value). Same if the root was originally detached.
|
|
502
520
|
delete diffItem.state;
|
|
@@ -567,10 +585,45 @@ export default class Differ {
|
|
|
567
585
|
// Clear cache after each buffered operation as it is no longer valid.
|
|
568
586
|
this._cachedChanges = null;
|
|
569
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
|
+
}
|
|
570
620
|
/**
|
|
571
621
|
* Saves and handles an insert change.
|
|
572
622
|
*/
|
|
573
623
|
_markInsert(parent, offset, howMany) {
|
|
624
|
+
if (parent.root.is('rootElement') && !parent.root._isLoaded) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
574
627
|
const changeItem = { type: 'insert', offset, howMany, count: this._changeCount++ };
|
|
575
628
|
this._markChange(parent, changeItem);
|
|
576
629
|
}
|
|
@@ -578,6 +631,9 @@ export default class Differ {
|
|
|
578
631
|
* Saves and handles a remove change.
|
|
579
632
|
*/
|
|
580
633
|
_markRemove(parent, offset, howMany) {
|
|
634
|
+
if (parent.root.is('rootElement') && !parent.root._isLoaded) {
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
581
637
|
const changeItem = { type: 'remove', offset, howMany, count: this._changeCount++ };
|
|
582
638
|
this._markChange(parent, changeItem);
|
|
583
639
|
this._removeAllNestedChanges(parent, offset, howMany);
|
|
@@ -586,6 +642,9 @@ export default class Differ {
|
|
|
586
642
|
* Saves and handles an attribute change.
|
|
587
643
|
*/
|
|
588
644
|
_markAttribute(item) {
|
|
645
|
+
if (item.root.is('rootElement') && !item.root._isLoaded) {
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
589
648
|
const changeItem = { type: 'attribute', offset: item.startOffset, howMany: item.offsetSize, count: this._changeCount++ };
|
|
590
649
|
this._markChange(item.parent, changeItem);
|
|
591
650
|
}
|
package/src/model/document.d.ts
CHANGED
|
@@ -125,9 +125,17 @@ export default class Document extends Document_base {
|
|
|
125
125
|
* on the document data know which roots are still a part of the document and should be processed.
|
|
126
126
|
*
|
|
127
127
|
* @param includeDetached Specified whether detached roots should be returned as well.
|
|
128
|
-
* @returns Roots names.
|
|
129
128
|
*/
|
|
130
129
|
getRootNames(includeDetached?: boolean): Array<string>;
|
|
130
|
+
/**
|
|
131
|
+
* Returns an array with all roots added to the document (except the {@link #graveyard graveyard root}).
|
|
132
|
+
*
|
|
133
|
+
* Detached roots **are not** returned by this method by default. This is to make sure that all features or algorithms that operate
|
|
134
|
+
* on the document data know which roots are still a part of the document and should be processed.
|
|
135
|
+
*
|
|
136
|
+
* @param includeDetached Specified whether detached roots should be returned as well.
|
|
137
|
+
*/
|
|
138
|
+
getRoots(includeDetached?: boolean): Array<RootElement>;
|
|
131
139
|
/**
|
|
132
140
|
* Used to register a post-fixer callback. A post-fixer mechanism guarantees that the features
|
|
133
141
|
* will operate on a correct model state.
|
package/src/model/document.js
CHANGED
|
@@ -179,12 +179,21 @@ export default class Document extends EmitterMixin() {
|
|
|
179
179
|
* on the document data know which roots are still a part of the document and should be processed.
|
|
180
180
|
*
|
|
181
181
|
* @param includeDetached Specified whether detached roots should be returned as well.
|
|
182
|
-
* @returns Roots names.
|
|
183
182
|
*/
|
|
184
183
|
getRootNames(includeDetached = false) {
|
|
184
|
+
return this.getRoots(includeDetached).map(root => root.rootName);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Returns an array with all roots added to the document (except the {@link #graveyard graveyard root}).
|
|
188
|
+
*
|
|
189
|
+
* Detached roots **are not** returned by this method by default. This is to make sure that all features or algorithms that operate
|
|
190
|
+
* on the document data know which roots are still a part of the document and should be processed.
|
|
191
|
+
*
|
|
192
|
+
* @param includeDetached Specified whether detached roots should be returned as well.
|
|
193
|
+
*/
|
|
194
|
+
getRoots(includeDetached = false) {
|
|
185
195
|
return Array.from(this.roots)
|
|
186
|
-
.filter(root => root
|
|
187
|
-
.map(root => root.rootName);
|
|
196
|
+
.filter(root => root != this.graveyard && (includeDetached || root.isAttached()) && root._isLoaded);
|
|
188
197
|
}
|
|
189
198
|
/**
|
|
190
199
|
* Used to register a post-fixer callback. A post-fixer mechanism guarantees that the features
|
|
@@ -283,12 +292,8 @@ export default class Document extends EmitterMixin() {
|
|
|
283
292
|
* @returns The default root for this document.
|
|
284
293
|
*/
|
|
285
294
|
_getDefaultRoot() {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
return root;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
return this.graveyard;
|
|
295
|
+
const roots = this.getRoots();
|
|
296
|
+
return roots.length ? roots[0] : this.graveyard;
|
|
292
297
|
}
|
|
293
298
|
/**
|
|
294
299
|
* Returns the default range for this selection. The default range is a collapsed range that starts and ends
|
|
@@ -866,14 +866,19 @@ class LiveSelection extends Selection {
|
|
|
866
866
|
_getSurroundingAttributes() {
|
|
867
867
|
const position = this.getFirstPosition();
|
|
868
868
|
const schema = this._model.schema;
|
|
869
|
+
if (position.root.rootName == '$graveyard') {
|
|
870
|
+
return null;
|
|
871
|
+
}
|
|
869
872
|
let attrs = null;
|
|
870
873
|
if (!this.isCollapsed) {
|
|
871
874
|
// 1. If selection is a range...
|
|
872
875
|
const range = this.getFirstRange();
|
|
873
876
|
// ...look for a first character node in that range and take attributes from it.
|
|
874
877
|
for (const value of range) {
|
|
875
|
-
// If the item is an object, we don't want to get attributes from its children
|
|
878
|
+
// If the item is an object, we don't want to get attributes from its children...
|
|
876
879
|
if (value.item.is('element') && schema.isObject(value.item)) {
|
|
880
|
+
// ...but collect attributes from inline object.
|
|
881
|
+
attrs = getTextAttributes(value.item, schema);
|
|
877
882
|
break;
|
|
878
883
|
}
|
|
879
884
|
if (value.type == 'text') {
|
|
@@ -957,7 +962,8 @@ function getTextAttributes(node, schema) {
|
|
|
957
962
|
const attributes = [];
|
|
958
963
|
// Collect all attributes that can be applied to the text node.
|
|
959
964
|
for (const [key, value] of node.getAttributes()) {
|
|
960
|
-
if (schema.checkAttribute('$text', key)
|
|
965
|
+
if (schema.checkAttribute('$text', key) &&
|
|
966
|
+
schema.getAttributeProperties(key).copyFromObject !== false) {
|
|
961
967
|
attributes.push([key, value]);
|
|
962
968
|
}
|
|
963
969
|
}
|
package/src/model/model.d.ts
CHANGED
|
@@ -769,7 +769,6 @@ export default class Model extends Model_base {
|
|
|
769
769
|
/**
|
|
770
770
|
* Common part of {@link module:engine/model/model~Model#change} and {@link module:engine/model/model~Model#enqueueChange}
|
|
771
771
|
* which calls callbacks and returns array of values returned by these callbacks.
|
|
772
|
-
*
|
|
773
772
|
*/
|
|
774
773
|
private _runPendingChanges;
|
|
775
774
|
}
|
package/src/model/model.js
CHANGED
|
@@ -792,7 +792,6 @@ export default class Model extends ObservableMixin() {
|
|
|
792
792
|
/**
|
|
793
793
|
* Common part of {@link module:engine/model/model~Model#change} and {@link module:engine/model/model~Model#enqueueChange}
|
|
794
794
|
* which calls callbacks and returns array of values returned by these callbacks.
|
|
795
|
-
*
|
|
796
795
|
*/
|
|
797
796
|
_runPendingChanges() {
|
|
798
797
|
const ret = [];
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
* @module engine/model/operation/rootoperation
|
|
7
7
|
*/
|
|
8
8
|
import Operation from './operation';
|
|
9
|
-
import { CKEditorError } from '@ckeditor/ckeditor5-utils';
|
|
10
9
|
/**
|
|
11
10
|
* Operation that creates (or attaches) or detaches a root element.
|
|
12
11
|
*/
|
|
@@ -59,29 +58,6 @@ export default class RootOperation extends Operation {
|
|
|
59
58
|
getReversed() {
|
|
60
59
|
return new RootOperation(this.rootName, this.elementName, !this.isAdd, this._document, this.baseVersion + 1);
|
|
61
60
|
}
|
|
62
|
-
/**
|
|
63
|
-
* @inheritDoc
|
|
64
|
-
*/
|
|
65
|
-
_validate() {
|
|
66
|
-
// Keep in mind that at this point the root will always exist as it was created in the `constructor()`, even for detach operation.
|
|
67
|
-
const root = this._document.getRoot(this.rootName);
|
|
68
|
-
if (root.isAttached() && this.isAdd) {
|
|
69
|
-
/**
|
|
70
|
-
* Trying to attach a root that is already attached.
|
|
71
|
-
*
|
|
72
|
-
* @error root-operation-root-attached
|
|
73
|
-
*/
|
|
74
|
-
throw new CKEditorError('root-operation-root-attached', this);
|
|
75
|
-
}
|
|
76
|
-
else if (!root.isAttached() && !this.isAdd) {
|
|
77
|
-
/**
|
|
78
|
-
* Trying to detach a root that is already detached.
|
|
79
|
-
*
|
|
80
|
-
* @error root-operation-root-detached
|
|
81
|
-
*/
|
|
82
|
-
throw new CKEditorError('root-operation-root-detached', this);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
61
|
/**
|
|
86
62
|
* @inheritDoc
|
|
87
63
|
*/
|
|
@@ -1641,8 +1641,8 @@ setTransformation(RootAttributeOperation, RootAttributeOperation, (a, b, context
|
|
|
1641
1641
|
return [a];
|
|
1642
1642
|
});
|
|
1643
1643
|
// -----------------------
|
|
1644
|
-
setTransformation(RootOperation, RootOperation, (a, b
|
|
1645
|
-
if (a.rootName === b.rootName && a.isAdd === b.isAdd
|
|
1644
|
+
setTransformation(RootOperation, RootOperation, (a, b) => {
|
|
1645
|
+
if (a.rootName === b.rootName && a.isAdd === b.isAdd) {
|
|
1646
1646
|
return [new NoOperation(0)];
|
|
1647
1647
|
}
|
|
1648
1648
|
return [a];
|