@ckeditor/ckeditor5-engine 32.0.0 → 34.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +2 -2
- package/README.md +2 -1
- package/package.json +22 -22
- package/src/controller/datacontroller.js +58 -66
- package/src/controller/editingcontroller.js +82 -5
- package/src/conversion/conversion.js +14 -13
- package/src/conversion/downcastdispatcher.js +297 -366
- package/src/conversion/downcasthelpers.js +859 -80
- package/src/conversion/mapper.js +104 -59
- package/src/conversion/modelconsumable.js +84 -34
- package/src/conversion/upcastdispatcher.js +33 -6
- package/src/conversion/upcasthelpers.js +21 -2
- package/src/dataprocessor/htmldataprocessor.js +1 -1
- package/src/dev-utils/model.js +13 -11
- package/src/index.js +12 -0
- package/src/model/batch.js +12 -12
- package/src/model/differ.js +114 -68
- package/src/model/document.js +32 -31
- package/src/model/history.js +160 -22
- package/src/model/markercollection.js +28 -4
- package/src/model/model.js +122 -4
- package/src/model/schema.js +79 -10
- package/src/model/treewalker.js +2 -3
- package/src/model/utils/deletecontent.js +15 -2
- package/src/model/utils/findoptimalinsertionrange.js +68 -0
- package/src/model/utils/insertobject.js +173 -0
- package/src/model/utils/modifyselection.js +14 -7
- package/src/model/writer.js +16 -26
- package/src/view/attributeelement.js +0 -10
- package/src/view/document.js +2 -1
- package/src/view/domconverter.js +47 -11
- package/src/view/downcastwriter.js +90 -49
- package/src/view/element.js +0 -27
- package/src/view/emptyelement.js +0 -3
- package/src/view/matcher.js +2 -2
- package/src/view/observer/clickobserver.js +0 -1
- package/src/view/observer/inputobserver.js +1 -1
- package/src/view/observer/tabobserver.js +68 -0
- package/src/view/placeholder.js +1 -1
- package/src/view/rawelement.js +0 -3
- package/src/view/uielement.js +0 -3
- package/src/view/view.js +4 -0
- package/theme/placeholder.css +9 -0
|
@@ -40,10 +40,7 @@ import mix from '@ckeditor/ckeditor5-utils/src/mix';
|
|
|
40
40
|
* The third parameter of the callback is an instance of {@link module:engine/conversion/upcastdispatcher~UpcastConversionApi}
|
|
41
41
|
* which provides additional tools for converters.
|
|
42
42
|
*
|
|
43
|
-
* You can read more about conversion in the
|
|
44
|
-
*
|
|
45
|
-
* * {@glink framework/guides/deep-dive/conversion/conversion-introduction Advanced conversion concepts — attributes}
|
|
46
|
-
* * {@glink framework/guides/deep-dive/conversion/custom-element-conversion Custom element conversion}
|
|
43
|
+
* You can read more about conversion in the {@glink framework/guides/deep-dive/conversion/upcast Upcast conversion} guide.
|
|
47
44
|
*
|
|
48
45
|
* Examples of event-based converters:
|
|
49
46
|
*
|
|
@@ -127,7 +124,7 @@ export default class UpcastDispatcher {
|
|
|
127
124
|
/**
|
|
128
125
|
* The list of elements that were created during splitting.
|
|
129
126
|
*
|
|
130
|
-
* After the conversion process the list is cleared.
|
|
127
|
+
* After the conversion process, the list is cleared.
|
|
131
128
|
*
|
|
132
129
|
* @private
|
|
133
130
|
* @type {Map.<module:engine/model/element~Element,Array.<module:engine/model/element~Element>>}
|
|
@@ -154,6 +151,16 @@ export default class UpcastDispatcher {
|
|
|
154
151
|
*/
|
|
155
152
|
this._modelCursor = null;
|
|
156
153
|
|
|
154
|
+
/**
|
|
155
|
+
* The list of elements that were created during the splitting but should not get removed on conversion end even if they are empty.
|
|
156
|
+
*
|
|
157
|
+
* The list is cleared after the conversion process.
|
|
158
|
+
*
|
|
159
|
+
* @private
|
|
160
|
+
* @type {Set.<module:engine/model/element~Element>}
|
|
161
|
+
*/
|
|
162
|
+
this._emptyElementsToKeep = new Set();
|
|
163
|
+
|
|
157
164
|
/**
|
|
158
165
|
* An interface passed by the dispatcher to the event callbacks.
|
|
159
166
|
*
|
|
@@ -170,6 +177,7 @@ export default class UpcastDispatcher {
|
|
|
170
177
|
// Advanced API - use only if custom position handling is needed.
|
|
171
178
|
this.conversionApi.splitToAllowedParent = this._splitToAllowedParent.bind( this );
|
|
172
179
|
this.conversionApi.getSplitParts = this._getSplitParts.bind( this );
|
|
180
|
+
this.conversionApi.keepEmptyElement = this._keepEmptyElement.bind( this );
|
|
173
181
|
}
|
|
174
182
|
|
|
175
183
|
/**
|
|
@@ -229,6 +237,7 @@ export default class UpcastDispatcher {
|
|
|
229
237
|
// Clear split elements & parents lists.
|
|
230
238
|
this._splitParts.clear();
|
|
231
239
|
this._cursorParents.clear();
|
|
240
|
+
this._emptyElementsToKeep.clear();
|
|
232
241
|
|
|
233
242
|
// Clear conversion API.
|
|
234
243
|
this.conversionApi.writer = null;
|
|
@@ -454,6 +463,15 @@ export default class UpcastDispatcher {
|
|
|
454
463
|
return parts;
|
|
455
464
|
}
|
|
456
465
|
|
|
466
|
+
/**
|
|
467
|
+
* Mark an element that were created during the splitting to not get removed on conversion end even if it is empty.
|
|
468
|
+
*
|
|
469
|
+
* @private
|
|
470
|
+
*/
|
|
471
|
+
_keepEmptyElement( element ) {
|
|
472
|
+
this._emptyElementsToKeep.add( element );
|
|
473
|
+
}
|
|
474
|
+
|
|
457
475
|
/**
|
|
458
476
|
* Checks if there are any empty elements created while splitting and removes them.
|
|
459
477
|
*
|
|
@@ -466,7 +484,7 @@ export default class UpcastDispatcher {
|
|
|
466
484
|
let anyRemoved = false;
|
|
467
485
|
|
|
468
486
|
for ( const element of this._splitParts.keys() ) {
|
|
469
|
-
if ( element.isEmpty ) {
|
|
487
|
+
if ( element.isEmpty && !this._emptyElementsToKeep.has( element ) ) {
|
|
470
488
|
this.conversionApi.writer.remove( element );
|
|
471
489
|
this._splitParts.delete( element );
|
|
472
490
|
|
|
@@ -760,6 +778,15 @@ function createContextTree( contextDefinition, writer ) {
|
|
|
760
778
|
* @returns {Array.<module:engine/model/element~Element>}
|
|
761
779
|
*/
|
|
762
780
|
|
|
781
|
+
/**
|
|
782
|
+
* Mark an element that was created during splitting to not get removed on conversion end even if it is empty.
|
|
783
|
+
*
|
|
784
|
+
* **Note:** This is an advanced method. For most cases you will not need to keep the split empty element.
|
|
785
|
+
*
|
|
786
|
+
* @method #keepEmptyElement
|
|
787
|
+
* @param {module:engine/model/element~Element} element
|
|
788
|
+
*/
|
|
789
|
+
|
|
763
790
|
/**
|
|
764
791
|
* Stores information about what parts of the processed view item are still waiting to be handled. After a piece of view item
|
|
765
792
|
* was converted, an appropriate consumable value should be
|
|
@@ -12,7 +12,7 @@ import priorities from '@ckeditor/ckeditor5-utils/src/priorities';
|
|
|
12
12
|
import { isParagraphable, wrapInParagraph } from '../model/utils/autoparagraphing';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Contains {@link module:engine/view/view view} to {@link module:engine/model/model model} converters for
|
|
15
|
+
* Contains the {@link module:engine/view/view view} to {@link module:engine/model/model model} converters for
|
|
16
16
|
* {@link module:engine/conversion/upcastdispatcher~UpcastDispatcher}.
|
|
17
17
|
*
|
|
18
18
|
* @module engine/conversion/upcasthelpers
|
|
@@ -21,6 +21,8 @@ import { isParagraphable, wrapInParagraph } from '../model/utils/autoparagraphin
|
|
|
21
21
|
/**
|
|
22
22
|
* Upcast conversion helper functions.
|
|
23
23
|
*
|
|
24
|
+
* Learn more about {@glink framework/guides/deep-dive/conversion/upcast upcast helpers}.
|
|
25
|
+
*
|
|
24
26
|
* @extends module:engine/conversion/conversionhelpers~ConversionHelpers
|
|
25
27
|
*/
|
|
26
28
|
export default class UpcastHelpers extends ConversionHelpers {
|
|
@@ -865,6 +867,13 @@ function prepareToAttributeConverter( config, shallow ) {
|
|
|
865
867
|
const matcher = new Matcher( config.view );
|
|
866
868
|
|
|
867
869
|
return ( evt, data, conversionApi ) => {
|
|
870
|
+
// Converting an attribute of an element that has not been converted to anything does not make sense
|
|
871
|
+
// because there will be nowhere to set that attribute on. At this stage, the element should've already
|
|
872
|
+
// been converted (https://github.com/ckeditor/ckeditor5/issues/11000).
|
|
873
|
+
if ( !data.modelRange && shallow ) {
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
|
|
868
877
|
const match = matcher.match( data.viewItem );
|
|
869
878
|
|
|
870
879
|
// If there is no match, this callback should not do anything.
|
|
@@ -875,7 +884,8 @@ function prepareToAttributeConverter( config, shallow ) {
|
|
|
875
884
|
if ( onlyViewNameIsDefined( config.view, data.viewItem ) ) {
|
|
876
885
|
match.match.name = true;
|
|
877
886
|
} else {
|
|
878
|
-
// Do not test
|
|
887
|
+
// Do not test `name` consumable because it could get consumed already while upcasting some other attribute
|
|
888
|
+
// on the same element (for example <span class="big" style="color: red">foo</span>).
|
|
879
889
|
delete match.match.name;
|
|
880
890
|
}
|
|
881
891
|
|
|
@@ -906,6 +916,15 @@ function prepareToAttributeConverter( config, shallow ) {
|
|
|
906
916
|
// It may happen that a converter will try to set an attribute that is not allowed in the given context.
|
|
907
917
|
// In such a situation we cannot consume the attribute. See: https://github.com/ckeditor/ckeditor5/pull/9249#issuecomment-815658459.
|
|
908
918
|
if ( attributeWasSet ) {
|
|
919
|
+
// Verify if the element itself wasn't consumed yet. It could be consumed already while upcasting some other attribute
|
|
920
|
+
// on the same element (for example <span class="big" style="color: red">foo</span>).
|
|
921
|
+
// We need to consume it so other features (especially GHS) won't try to convert it.
|
|
922
|
+
// Note that it's not tested by the other element-to-attribute converters whether an element was consumed before
|
|
923
|
+
// (in case of converters that the element itself is just a context and not the primary information to convert).
|
|
924
|
+
if ( conversionApi.consumable.test( data.viewItem, { name: true } ) ) {
|
|
925
|
+
match.match.name = true;
|
|
926
|
+
}
|
|
927
|
+
|
|
909
928
|
conversionApi.consumable.consume( data.viewItem, match.match );
|
|
910
929
|
}
|
|
911
930
|
};
|
|
@@ -114,7 +114,7 @@ export default class HtmlDataProcessor {
|
|
|
114
114
|
* @returns {DocumentFragment}
|
|
115
115
|
*/
|
|
116
116
|
_toDom( data ) {
|
|
117
|
-
// Wrap data with a <body> so leading non-layout nodes (like <script>, <style>, HTML comment)
|
|
117
|
+
// Wrap data with a <body> tag so leading non-layout nodes (like <script>, <style>, HTML comment)
|
|
118
118
|
// will be preserved in the body collection.
|
|
119
119
|
// Do it only for data that is not a full HTML document.
|
|
120
120
|
if ( !data.match( /<(?:html|body|head|meta)(?:\s[^>]*)?>/i ) ) {
|
package/src/dev-utils/model.js
CHANGED
|
@@ -31,6 +31,7 @@ import Mapper from '../conversion/mapper';
|
|
|
31
31
|
import {
|
|
32
32
|
convertCollapsedSelection,
|
|
33
33
|
convertRangeSelection,
|
|
34
|
+
insertAttributesAndChildren,
|
|
34
35
|
insertElement,
|
|
35
36
|
insertText,
|
|
36
37
|
insertUIElement,
|
|
@@ -233,6 +234,7 @@ export function stringify( node, selectionOrPositionOrRange = null, markers = nu
|
|
|
233
234
|
mapper.bindElements( node.root, viewRoot );
|
|
234
235
|
|
|
235
236
|
downcastDispatcher.on( 'insert:$text', insertText() );
|
|
237
|
+
downcastDispatcher.on( 'insert', insertAttributesAndChildren(), { priority: 'lowest' } );
|
|
236
238
|
downcastDispatcher.on( 'attribute', ( evt, data, conversionApi ) => {
|
|
237
239
|
if ( data.item instanceof ModelSelection || data.item instanceof DocumentSelection || data.item.is( '$textProxy' ) ) {
|
|
238
240
|
const converter = wrap( ( modelAttributeValue, { writer } ) => {
|
|
@@ -260,28 +262,28 @@ export function stringify( node, selectionOrPositionOrRange = null, markers = nu
|
|
|
260
262
|
return writer.createUIElement( name );
|
|
261
263
|
} ) );
|
|
262
264
|
|
|
265
|
+
const markersMap = new Map();
|
|
266
|
+
|
|
267
|
+
if ( markers ) {
|
|
268
|
+
// To provide stable results, sort markers by name.
|
|
269
|
+
for ( const marker of Array.from( markers ).sort( ( a, b ) => a.name < b.name ? 1 : -1 ) ) {
|
|
270
|
+
markersMap.set( marker.name, marker.getRange() );
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
263
274
|
// Convert model to view.
|
|
264
275
|
const writer = view._writer;
|
|
265
|
-
downcastDispatcher.
|
|
276
|
+
downcastDispatcher.convert( range, markersMap, writer );
|
|
266
277
|
|
|
267
278
|
// Convert model selection to view selection.
|
|
268
279
|
if ( selection ) {
|
|
269
280
|
downcastDispatcher.convertSelection( selection, markers || model.markers, writer );
|
|
270
281
|
}
|
|
271
282
|
|
|
272
|
-
if ( markers ) {
|
|
273
|
-
// To provide stable results, sort markers by name.
|
|
274
|
-
markers = Array.from( markers ).sort( ( a, b ) => a.name < b.name ? 1 : -1 );
|
|
275
|
-
|
|
276
|
-
for ( const marker of markers ) {
|
|
277
|
-
downcastDispatcher.convertMarkerAdd( marker.name, marker.getRange(), writer );
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
283
|
// Parse view to data string.
|
|
282
284
|
let data = viewStringify( viewRoot, viewDocument.selection, { sameSelectionCharacters: true } );
|
|
283
285
|
|
|
284
|
-
// Removing
|
|
286
|
+
// Removing unnecessary <div> and </div> added because `viewRoot` was also stringified alongside input data.
|
|
285
287
|
data = data.substr( 5, data.length - 11 );
|
|
286
288
|
|
|
287
289
|
view.destroy();
|
package/src/index.js
CHANGED
|
@@ -28,10 +28,22 @@ export { default as LivePosition } from './model/liveposition';
|
|
|
28
28
|
export { default as Model } from './model/model';
|
|
29
29
|
export { default as TreeWalker } from './model/treewalker';
|
|
30
30
|
export { default as Element } from './model/element';
|
|
31
|
+
export { default as Position } from './model/position';
|
|
32
|
+
export { default as DocumentFragment } from './model/documentfragment';
|
|
33
|
+
export { default as History } from './model/history';
|
|
34
|
+
export { default as Text } from './model/text';
|
|
31
35
|
|
|
32
36
|
export { default as DomConverter } from './view/domconverter';
|
|
33
37
|
export { default as Renderer } from './view/renderer';
|
|
34
38
|
export { default as ViewDocument } from './view/document';
|
|
39
|
+
export { default as ViewText } from './view/text';
|
|
40
|
+
export { default as ViewElement } from './view/element';
|
|
41
|
+
export { default as ViewContainerElement } from './view/containerelement';
|
|
42
|
+
export { default as ViewAttributeElement } from './view/attributeelement';
|
|
43
|
+
export { default as ViewEmptyElement } from './view/emptyelement';
|
|
44
|
+
export { default as ViewRawElement } from './view/rawelement';
|
|
45
|
+
export { default as ViewUIElement } from './view/uielement';
|
|
46
|
+
export { default as ViewDocumentFragment } from './view/documentfragment';
|
|
35
47
|
|
|
36
48
|
export { getFillerOffset } from './view/containerelement';
|
|
37
49
|
export { default as Observer } from './view/observer/observer';
|
package/src/model/batch.js
CHANGED
|
@@ -27,20 +27,20 @@ export default class Batch {
|
|
|
27
27
|
*
|
|
28
28
|
* @see module:engine/model/model~Model#enqueueChange
|
|
29
29
|
* @see module:engine/model/model~Model#change
|
|
30
|
-
* @param {Object} [type]
|
|
31
|
-
* encountering given `Batch` instance (for example, when a feature listens to applied operations).
|
|
32
|
-
* @param {Boolean} [type.isUndoable=true] Whether batch can be undone through undo feature.
|
|
33
|
-
* @param {Boolean} [type.isLocal=true] Whether batch includes operations created locally (`true`) or operations created on
|
|
30
|
+
* @param {Object} [type] A set of flags that specify the type of the batch. Batch type can alter how some of the features work
|
|
31
|
+
* when encountering a given `Batch` instance (for example, when a feature listens to applied operations).
|
|
32
|
+
* @param {Boolean} [type.isUndoable=true] Whether a batch can be undone through undo feature.
|
|
33
|
+
* @param {Boolean} [type.isLocal=true] Whether a batch includes operations created locally (`true`) or operations created on
|
|
34
34
|
* other, remote editors (`false`).
|
|
35
|
-
* @param {Boolean} [type.isUndo=false] Whether batch was created by the undo feature and undoes other operations.
|
|
36
|
-
* @param {Boolean} [type.isTyping=false] Whether batch includes operations connected with typing action.
|
|
35
|
+
* @param {Boolean} [type.isUndo=false] Whether a batch was created by the undo feature and undoes other operations.
|
|
36
|
+
* @param {Boolean} [type.isTyping=false] Whether a batch includes operations connected with a typing action.
|
|
37
37
|
*/
|
|
38
38
|
constructor( type = {} ) {
|
|
39
39
|
if ( typeof type === 'string' ) {
|
|
40
40
|
type = type === 'transparent' ? { isUndoable: false } : {};
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
* The string value for `type` property of the `Batch` constructor has been deprecated and will be removed in the near future.
|
|
43
|
+
* The string value for a `type` property of the `Batch` constructor has been deprecated and will be removed in the near future.
|
|
44
44
|
* Please refer to the {@link module:engine/model/batch~Batch#constructor `Batch` constructor API documentation} for more
|
|
45
45
|
* information.
|
|
46
46
|
*
|
|
@@ -60,7 +60,7 @@ export default class Batch {
|
|
|
60
60
|
this.operations = [];
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
* Whether batch can be undone through the undo feature.
|
|
63
|
+
* Whether the batch can be undone through the undo feature.
|
|
64
64
|
*
|
|
65
65
|
* @readonly
|
|
66
66
|
* @type {Boolean}
|
|
@@ -68,7 +68,7 @@ export default class Batch {
|
|
|
68
68
|
this.isUndoable = isUndoable;
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
|
-
* Whether batch includes operations created locally (`true`) or operations created on other, remote editors (`false`).
|
|
71
|
+
* Whether the batch includes operations created locally (`true`) or operations created on other, remote editors (`false`).
|
|
72
72
|
*
|
|
73
73
|
* @readonly
|
|
74
74
|
* @type {Boolean}
|
|
@@ -76,7 +76,7 @@ export default class Batch {
|
|
|
76
76
|
this.isLocal = isLocal;
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
|
-
* Whether batch was created by the undo feature and undoes other operations.
|
|
79
|
+
* Whether the batch was created by the undo feature and undoes other operations.
|
|
80
80
|
*
|
|
81
81
|
* @readonly
|
|
82
82
|
* @type {Boolean}
|
|
@@ -84,7 +84,7 @@ export default class Batch {
|
|
|
84
84
|
this.isUndo = isUndo;
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
|
-
* Whether batch includes operations connected with typing.
|
|
87
|
+
* Whether the batch includes operations connected with typing.
|
|
88
88
|
*
|
|
89
89
|
* @readonly
|
|
90
90
|
* @type {Boolean}
|
|
@@ -95,7 +95,7 @@ export default class Batch {
|
|
|
95
95
|
/**
|
|
96
96
|
* The type of the batch.
|
|
97
97
|
*
|
|
98
|
-
* **This property has been deprecated and is always set to `'default'` value.**
|
|
98
|
+
* **This property has been deprecated and is always set to the `'default'` value.**
|
|
99
99
|
*
|
|
100
100
|
* It can be one of the following values:
|
|
101
101
|
* * `'default'` – All "normal" batches. This is the most commonly used type.
|
package/src/model/differ.js
CHANGED
|
@@ -58,11 +58,12 @@ export default class Differ {
|
|
|
58
58
|
* A map that stores all changed markers.
|
|
59
59
|
*
|
|
60
60
|
* The keys of the map are marker names.
|
|
61
|
-
* The values of the map are objects with the
|
|
62
|
-
*
|
|
61
|
+
* The values of the map are objects with the following properties:
|
|
62
|
+
* - `oldMarkerData`,
|
|
63
|
+
* - `newMarkerData`.
|
|
63
64
|
*
|
|
64
65
|
* @private
|
|
65
|
-
* @type {Map}
|
|
66
|
+
* @type {Map.<String, Object>}
|
|
66
67
|
*/
|
|
67
68
|
this._changedMarkers = new Map();
|
|
68
69
|
|
|
@@ -98,6 +99,14 @@ export default class Differ {
|
|
|
98
99
|
* @type {Array.<Object>|null}
|
|
99
100
|
*/
|
|
100
101
|
this._cachedChangesWithGraveyard = null;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Set of model items that were marked to get refreshed in {@link #_refreshItem}.
|
|
105
|
+
*
|
|
106
|
+
* @private
|
|
107
|
+
* @type {Set.<module:engine/model/item~Item>}
|
|
108
|
+
*/
|
|
109
|
+
this._refreshedItems = new Set();
|
|
101
110
|
}
|
|
102
111
|
|
|
103
112
|
/**
|
|
@@ -110,32 +119,6 @@ export default class Differ {
|
|
|
110
119
|
return this._changesInElement.size == 0 && this._changedMarkers.size == 0;
|
|
111
120
|
}
|
|
112
121
|
|
|
113
|
-
/**
|
|
114
|
-
* Marks given `item` in differ to be "refreshed". It means that the item will be marked as removed and inserted in the differ changes
|
|
115
|
-
* set, so it will be effectively re-converted when differ changes will be handled by a dispatcher.
|
|
116
|
-
*
|
|
117
|
-
* @param {module:engine/model/item~Item} item Item to refresh.
|
|
118
|
-
*/
|
|
119
|
-
refreshItem( item ) {
|
|
120
|
-
if ( this._isInInsertedElement( item.parent ) ) {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
this._markRemove( item.parent, item.startOffset, item.offsetSize );
|
|
125
|
-
this._markInsert( item.parent, item.startOffset, item.offsetSize );
|
|
126
|
-
|
|
127
|
-
const range = Range._createOn( item );
|
|
128
|
-
|
|
129
|
-
for ( const marker of this._markerCollection.getMarkersIntersectingRange( range ) ) {
|
|
130
|
-
const markerRange = marker.getRange();
|
|
131
|
-
|
|
132
|
-
this.bufferMarkerChange( marker.name, markerRange, markerRange, marker.affectsData );
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Clear cache after each buffered operation as it is no longer valid.
|
|
136
|
-
this._cachedChanges = null;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
122
|
/**
|
|
140
123
|
* Buffers the given operation. An operation has to be buffered before it is executed.
|
|
141
124
|
*
|
|
@@ -208,9 +191,9 @@ export default class Differ {
|
|
|
208
191
|
const range = Range._createFromPositionAndShift( operation.position, 1 );
|
|
209
192
|
|
|
210
193
|
for ( const marker of this._markerCollection.getMarkersIntersectingRange( range ) ) {
|
|
211
|
-
const
|
|
194
|
+
const markerData = marker.getData();
|
|
212
195
|
|
|
213
|
-
this.bufferMarkerChange( marker.name,
|
|
196
|
+
this.bufferMarkerChange( marker.name, markerData, markerData );
|
|
214
197
|
}
|
|
215
198
|
|
|
216
199
|
break;
|
|
@@ -267,27 +250,23 @@ export default class Differ {
|
|
|
267
250
|
* Buffers a marker change.
|
|
268
251
|
*
|
|
269
252
|
* @param {String} markerName The name of the marker that changed.
|
|
270
|
-
* @param {module:engine/model/
|
|
271
|
-
*
|
|
272
|
-
* @param {module:engine/model/range~Range|null} newRange Marker range after the change or `null` if the marker was removed.
|
|
273
|
-
* @param {Boolean} affectsData Flag indicating whether marker affects the editor data.
|
|
253
|
+
* @param {module:engine/model/markercollection~MarkerData} oldMarkerData Marker data before the change.
|
|
254
|
+
* @param {module:engine/model/markercollection~MarkerData} newMarkerData Marker data after the change.
|
|
274
255
|
*/
|
|
275
|
-
bufferMarkerChange( markerName,
|
|
256
|
+
bufferMarkerChange( markerName, oldMarkerData, newMarkerData ) {
|
|
276
257
|
const buffered = this._changedMarkers.get( markerName );
|
|
277
258
|
|
|
278
259
|
if ( !buffered ) {
|
|
279
260
|
this._changedMarkers.set( markerName, {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
affectsData
|
|
261
|
+
newMarkerData,
|
|
262
|
+
oldMarkerData
|
|
283
263
|
} );
|
|
284
264
|
} else {
|
|
285
|
-
buffered.
|
|
286
|
-
buffered.affectsData = affectsData;
|
|
265
|
+
buffered.newMarkerData = newMarkerData;
|
|
287
266
|
|
|
288
|
-
if ( buffered.
|
|
289
|
-
// The marker is going to be removed (`
|
|
290
|
-
// (`buffered.
|
|
267
|
+
if ( buffered.oldMarkerData.range == null && newMarkerData.range == null ) {
|
|
268
|
+
// The marker is going to be removed (`newMarkerData.range == null`) but it did not exist before the first buffered change
|
|
269
|
+
// (`buffered.oldMarkerData.range == null`). In this case, do not keep the marker in buffer at all.
|
|
291
270
|
this._changedMarkers.delete( markerName );
|
|
292
271
|
}
|
|
293
272
|
}
|
|
@@ -302,8 +281,8 @@ export default class Differ {
|
|
|
302
281
|
const result = [];
|
|
303
282
|
|
|
304
283
|
for ( const [ name, change ] of this._changedMarkers ) {
|
|
305
|
-
if ( change.
|
|
306
|
-
result.push( { name, range: change.
|
|
284
|
+
if ( change.oldMarkerData.range != null ) {
|
|
285
|
+
result.push( { name, range: change.oldMarkerData.range } );
|
|
307
286
|
}
|
|
308
287
|
}
|
|
309
288
|
|
|
@@ -319,8 +298,8 @@ export default class Differ {
|
|
|
319
298
|
const result = [];
|
|
320
299
|
|
|
321
300
|
for ( const [ name, change ] of this._changedMarkers ) {
|
|
322
|
-
if ( change.
|
|
323
|
-
result.push( { name, range: change.
|
|
301
|
+
if ( change.newMarkerData.range != null ) {
|
|
302
|
+
result.push( { name, range: change.newMarkerData.range } );
|
|
324
303
|
}
|
|
325
304
|
}
|
|
326
305
|
|
|
@@ -333,12 +312,12 @@ export default class Differ {
|
|
|
333
312
|
* @returns {Array.<Object>}
|
|
334
313
|
*/
|
|
335
314
|
getChangedMarkers() {
|
|
336
|
-
return Array.from( this._changedMarkers ).map(
|
|
315
|
+
return Array.from( this._changedMarkers ).map( ( [ name, change ] ) => (
|
|
337
316
|
{
|
|
338
|
-
name
|
|
317
|
+
name,
|
|
339
318
|
data: {
|
|
340
|
-
oldRange:
|
|
341
|
-
newRange:
|
|
319
|
+
oldRange: change.oldMarkerData.range,
|
|
320
|
+
newRange: change.newMarkerData.range
|
|
342
321
|
}
|
|
343
322
|
}
|
|
344
323
|
) );
|
|
@@ -351,19 +330,33 @@ export default class Differ {
|
|
|
351
330
|
*
|
|
352
331
|
* * model structure changes,
|
|
353
332
|
* * attribute changes,
|
|
354
|
-
* * changes of markers which were defined as `
|
|
333
|
+
* * changes of markers which were defined as `affectsData`,
|
|
334
|
+
* * changes of markers' `affectsData` property.
|
|
355
335
|
*
|
|
356
336
|
* @returns {Boolean}
|
|
357
337
|
*/
|
|
358
338
|
hasDataChanges() {
|
|
359
|
-
|
|
360
|
-
|
|
339
|
+
if ( this._changesInElement.size > 0 ) {
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
for ( const { newMarkerData, oldMarkerData } of this._changedMarkers.values() ) {
|
|
344
|
+
if ( newMarkerData.affectsData !== oldMarkerData.affectsData ) {
|
|
361
345
|
return true;
|
|
362
346
|
}
|
|
347
|
+
|
|
348
|
+
if ( newMarkerData.affectsData ) {
|
|
349
|
+
const markerAdded = newMarkerData.range && !oldMarkerData.range;
|
|
350
|
+
const markerRemoved = !newMarkerData.range && oldMarkerData.range;
|
|
351
|
+
const markerChanged = newMarkerData.range && oldMarkerData.range && !newMarkerData.range.isEqual( oldMarkerData.range );
|
|
352
|
+
|
|
353
|
+
if ( markerAdded || markerRemoved || markerChanged ) {
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
363
357
|
}
|
|
364
358
|
|
|
365
|
-
|
|
366
|
-
return this._changesInElement.size > 0;
|
|
359
|
+
return false;
|
|
367
360
|
}
|
|
368
361
|
|
|
369
362
|
/**
|
|
@@ -430,12 +423,12 @@ export default class Differ {
|
|
|
430
423
|
for ( const action of actions ) {
|
|
431
424
|
if ( action === 'i' ) {
|
|
432
425
|
// Generate diff item for this element and insert it into the diff set.
|
|
433
|
-
diffSet.push( this._getInsertDiff( element, i, elementChildren[ i ]
|
|
426
|
+
diffSet.push( this._getInsertDiff( element, i, elementChildren[ i ] ) );
|
|
434
427
|
|
|
435
428
|
i++;
|
|
436
429
|
} else if ( action === 'r' ) {
|
|
437
430
|
// Generate diff item for this element and insert it into the diff set.
|
|
438
|
-
diffSet.push( this._getRemoveDiff( element, i, snapshotChildren[ j ]
|
|
431
|
+
diffSet.push( this._getRemoveDiff( element, i, snapshotChildren[ j ] ) );
|
|
439
432
|
|
|
440
433
|
j++;
|
|
441
434
|
} else if ( action === 'a' ) {
|
|
@@ -540,16 +533,25 @@ export default class Differ {
|
|
|
540
533
|
this._changeCount = 0;
|
|
541
534
|
|
|
542
535
|
// Cache changes.
|
|
543
|
-
this._cachedChangesWithGraveyard = diffSet
|
|
536
|
+
this._cachedChangesWithGraveyard = diffSet;
|
|
544
537
|
this._cachedChanges = diffSet.filter( _changesInGraveyardFilter );
|
|
545
538
|
|
|
546
539
|
if ( options.includeChangesInGraveyard ) {
|
|
547
|
-
return this._cachedChangesWithGraveyard;
|
|
540
|
+
return this._cachedChangesWithGraveyard.slice();
|
|
548
541
|
} else {
|
|
549
|
-
return this._cachedChanges;
|
|
542
|
+
return this._cachedChanges.slice();
|
|
550
543
|
}
|
|
551
544
|
}
|
|
552
545
|
|
|
546
|
+
/**
|
|
547
|
+
* Returns a set of model items that were marked to get refreshed.
|
|
548
|
+
*
|
|
549
|
+
* @return {Set.<module:engine/model/item~Item>}
|
|
550
|
+
*/
|
|
551
|
+
getRefreshedItems() {
|
|
552
|
+
return new Set( this._refreshedItems );
|
|
553
|
+
}
|
|
554
|
+
|
|
553
555
|
/**
|
|
554
556
|
* Resets `Differ`. Removes all buffered changes.
|
|
555
557
|
*/
|
|
@@ -557,6 +559,36 @@ export default class Differ {
|
|
|
557
559
|
this._changesInElement.clear();
|
|
558
560
|
this._elementSnapshots.clear();
|
|
559
561
|
this._changedMarkers.clear();
|
|
562
|
+
this._refreshedItems = new Set();
|
|
563
|
+
this._cachedChanges = null;
|
|
564
|
+
}
|
|
565
|
+
|
|
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
|
+
* @protected
|
|
571
|
+
* @param {module:engine/model/item~Item} item Item to refresh.
|
|
572
|
+
*/
|
|
573
|
+
_refreshItem( item ) {
|
|
574
|
+
if ( this._isInInsertedElement( item.parent ) ) {
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
this._markRemove( item.parent, item.startOffset, item.offsetSize );
|
|
579
|
+
this._markInsert( item.parent, item.startOffset, item.offsetSize );
|
|
580
|
+
|
|
581
|
+
this._refreshedItems.add( item );
|
|
582
|
+
|
|
583
|
+
const range = Range._createOn( item );
|
|
584
|
+
|
|
585
|
+
for ( const marker of this._markerCollection.getMarkersIntersectingRange( range ) ) {
|
|
586
|
+
const markerData = marker.getData();
|
|
587
|
+
|
|
588
|
+
this.bufferMarkerChange( marker.name, markerData, markerData );
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Clear cache after each buffered operation as it is no longer valid.
|
|
560
592
|
this._cachedChanges = null;
|
|
561
593
|
}
|
|
562
594
|
|
|
@@ -897,14 +929,15 @@ export default class Differ {
|
|
|
897
929
|
* @private
|
|
898
930
|
* @param {module:engine/model/element~Element} parent The element in which the change happened.
|
|
899
931
|
* @param {Number} offset The offset at which change happened.
|
|
900
|
-
* @param {
|
|
932
|
+
* @param {Object} elementSnapshot The snapshot of the removed element a character.
|
|
901
933
|
* @returns {Object} The diff item.
|
|
902
934
|
*/
|
|
903
|
-
_getInsertDiff( parent, offset,
|
|
935
|
+
_getInsertDiff( parent, offset, elementSnapshot ) {
|
|
904
936
|
return {
|
|
905
937
|
type: 'insert',
|
|
906
938
|
position: Position._createAt( parent, offset ),
|
|
907
|
-
name,
|
|
939
|
+
name: elementSnapshot.name,
|
|
940
|
+
attributes: new Map( elementSnapshot.attributes ),
|
|
908
941
|
length: 1,
|
|
909
942
|
changeCount: this._changeCount++
|
|
910
943
|
};
|
|
@@ -916,14 +949,15 @@ export default class Differ {
|
|
|
916
949
|
* @private
|
|
917
950
|
* @param {module:engine/model/element~Element} parent The element in which change happened.
|
|
918
951
|
* @param {Number} offset The offset at which change happened.
|
|
919
|
-
* @param {
|
|
952
|
+
* @param {Object} elementSnapshot The snapshot of the removed element a character.
|
|
920
953
|
* @returns {Object} The diff item.
|
|
921
954
|
*/
|
|
922
|
-
_getRemoveDiff( parent, offset,
|
|
955
|
+
_getRemoveDiff( parent, offset, elementSnapshot ) {
|
|
923
956
|
return {
|
|
924
957
|
type: 'remove',
|
|
925
958
|
position: Position._createAt( parent, offset ),
|
|
926
|
-
name,
|
|
959
|
+
name: elementSnapshot.name,
|
|
960
|
+
attributes: new Map( elementSnapshot.attributes ),
|
|
927
961
|
length: 1,
|
|
928
962
|
changeCount: this._changeCount++
|
|
929
963
|
};
|
|
@@ -1201,6 +1235,12 @@ function _changesInGraveyardFilter( entry ) {
|
|
|
1201
1235
|
* @member {String} module:engine/model/differ~DiffItemInsert#name
|
|
1202
1236
|
*/
|
|
1203
1237
|
|
|
1238
|
+
/**
|
|
1239
|
+
* Map of attributes that were set on the item while it was inserted.
|
|
1240
|
+
*
|
|
1241
|
+
* @member {Map.<String,*>} module:engine/model/differ~DiffItemInsert#attributes
|
|
1242
|
+
*/
|
|
1243
|
+
|
|
1204
1244
|
/**
|
|
1205
1245
|
* The position where the node was inserted.
|
|
1206
1246
|
*
|
|
@@ -1232,6 +1272,12 @@ function _changesInGraveyardFilter( entry ) {
|
|
|
1232
1272
|
* @member {String} module:engine/model/differ~DiffItemRemove#name
|
|
1233
1273
|
*/
|
|
1234
1274
|
|
|
1275
|
+
/**
|
|
1276
|
+
* Map of attributes that were set on the item while it was removed.
|
|
1277
|
+
*
|
|
1278
|
+
* @member {Map.<String,*>} module:engine/model/differ~DiffItemRemove#attributes
|
|
1279
|
+
*/
|
|
1280
|
+
|
|
1235
1281
|
/**
|
|
1236
1282
|
* The position where the node was removed.
|
|
1237
1283
|
*
|