@ckeditor/ckeditor5-engine 30.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.
Files changed (117) hide show
  1. package/LICENSE.md +17 -0
  2. package/README.md +30 -0
  3. package/package.json +70 -0
  4. package/src/controller/datacontroller.js +563 -0
  5. package/src/controller/editingcontroller.js +149 -0
  6. package/src/conversion/conversion.js +644 -0
  7. package/src/conversion/conversionhelpers.js +40 -0
  8. package/src/conversion/downcastdispatcher.js +914 -0
  9. package/src/conversion/downcasthelpers.js +1706 -0
  10. package/src/conversion/mapper.js +696 -0
  11. package/src/conversion/modelconsumable.js +329 -0
  12. package/src/conversion/upcastdispatcher.js +807 -0
  13. package/src/conversion/upcasthelpers.js +997 -0
  14. package/src/conversion/viewconsumable.js +623 -0
  15. package/src/dataprocessor/basichtmlwriter.js +32 -0
  16. package/src/dataprocessor/dataprocessor.jsdoc +64 -0
  17. package/src/dataprocessor/htmldataprocessor.js +159 -0
  18. package/src/dataprocessor/htmlwriter.js +22 -0
  19. package/src/dataprocessor/xmldataprocessor.js +161 -0
  20. package/src/dev-utils/model.js +482 -0
  21. package/src/dev-utils/operationreplayer.js +140 -0
  22. package/src/dev-utils/utils.js +103 -0
  23. package/src/dev-utils/view.js +1091 -0
  24. package/src/index.js +52 -0
  25. package/src/model/batch.js +82 -0
  26. package/src/model/differ.js +1282 -0
  27. package/src/model/document.js +483 -0
  28. package/src/model/documentfragment.js +390 -0
  29. package/src/model/documentselection.js +1261 -0
  30. package/src/model/element.js +438 -0
  31. package/src/model/history.js +138 -0
  32. package/src/model/item.jsdoc +14 -0
  33. package/src/model/liveposition.js +182 -0
  34. package/src/model/liverange.js +221 -0
  35. package/src/model/markercollection.js +553 -0
  36. package/src/model/model.js +934 -0
  37. package/src/model/node.js +507 -0
  38. package/src/model/nodelist.js +217 -0
  39. package/src/model/operation/attributeoperation.js +202 -0
  40. package/src/model/operation/detachoperation.js +103 -0
  41. package/src/model/operation/insertoperation.js +188 -0
  42. package/src/model/operation/markeroperation.js +154 -0
  43. package/src/model/operation/mergeoperation.js +216 -0
  44. package/src/model/operation/moveoperation.js +209 -0
  45. package/src/model/operation/nooperation.js +58 -0
  46. package/src/model/operation/operation.js +139 -0
  47. package/src/model/operation/operationfactory.js +49 -0
  48. package/src/model/operation/renameoperation.js +155 -0
  49. package/src/model/operation/rootattributeoperation.js +211 -0
  50. package/src/model/operation/splitoperation.js +254 -0
  51. package/src/model/operation/transform.js +2389 -0
  52. package/src/model/operation/utils.js +292 -0
  53. package/src/model/position.js +1164 -0
  54. package/src/model/range.js +1049 -0
  55. package/src/model/rootelement.js +111 -0
  56. package/src/model/schema.js +1851 -0
  57. package/src/model/selection.js +902 -0
  58. package/src/model/text.js +138 -0
  59. package/src/model/textproxy.js +279 -0
  60. package/src/model/treewalker.js +414 -0
  61. package/src/model/utils/autoparagraphing.js +77 -0
  62. package/src/model/utils/deletecontent.js +528 -0
  63. package/src/model/utils/getselectedcontent.js +150 -0
  64. package/src/model/utils/insertcontent.js +824 -0
  65. package/src/model/utils/modifyselection.js +229 -0
  66. package/src/model/utils/selection-post-fixer.js +297 -0
  67. package/src/model/writer.js +1574 -0
  68. package/src/view/attributeelement.js +274 -0
  69. package/src/view/containerelement.js +123 -0
  70. package/src/view/document.js +221 -0
  71. package/src/view/documentfragment.js +273 -0
  72. package/src/view/documentselection.js +387 -0
  73. package/src/view/domconverter.js +1437 -0
  74. package/src/view/downcastwriter.js +2121 -0
  75. package/src/view/editableelement.js +118 -0
  76. package/src/view/element.js +945 -0
  77. package/src/view/elementdefinition.jsdoc +59 -0
  78. package/src/view/emptyelement.js +119 -0
  79. package/src/view/filler.js +161 -0
  80. package/src/view/item.jsdoc +14 -0
  81. package/src/view/matcher.js +776 -0
  82. package/src/view/node.js +391 -0
  83. package/src/view/observer/arrowkeysobserver.js +58 -0
  84. package/src/view/observer/bubblingemittermixin.js +307 -0
  85. package/src/view/observer/bubblingeventinfo.js +71 -0
  86. package/src/view/observer/clickobserver.js +46 -0
  87. package/src/view/observer/compositionobserver.js +79 -0
  88. package/src/view/observer/domeventdata.js +82 -0
  89. package/src/view/observer/domeventobserver.js +99 -0
  90. package/src/view/observer/fakeselectionobserver.js +118 -0
  91. package/src/view/observer/focusobserver.js +106 -0
  92. package/src/view/observer/inputobserver.js +44 -0
  93. package/src/view/observer/keyobserver.js +83 -0
  94. package/src/view/observer/mouseobserver.js +56 -0
  95. package/src/view/observer/mutationobserver.js +345 -0
  96. package/src/view/observer/observer.js +118 -0
  97. package/src/view/observer/selectionobserver.js +242 -0
  98. package/src/view/placeholder.js +285 -0
  99. package/src/view/position.js +426 -0
  100. package/src/view/range.js +533 -0
  101. package/src/view/rawelement.js +148 -0
  102. package/src/view/renderer.js +1037 -0
  103. package/src/view/rooteditableelement.js +107 -0
  104. package/src/view/selection.js +718 -0
  105. package/src/view/styles/background.js +73 -0
  106. package/src/view/styles/border.js +362 -0
  107. package/src/view/styles/margin.js +41 -0
  108. package/src/view/styles/padding.js +40 -0
  109. package/src/view/styles/utils.js +277 -0
  110. package/src/view/stylesmap.js +938 -0
  111. package/src/view/text.js +147 -0
  112. package/src/view/textproxy.js +199 -0
  113. package/src/view/treewalker.js +496 -0
  114. package/src/view/uielement.js +238 -0
  115. package/src/view/upcastwriter.js +484 -0
  116. package/src/view/view.js +721 -0
  117. package/theme/placeholder.css +27 -0
@@ -0,0 +1,217 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+
6
+ /**
7
+ * @module engine/model/nodelist
8
+ */
9
+
10
+ import Node from './node';
11
+ import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
12
+
13
+ /**
14
+ * Provides an interface to operate on a list of {@link module:engine/model/node~Node nodes}. `NodeList` is used internally
15
+ * in classes like {@link module:engine/model/element~Element Element}
16
+ * or {@link module:engine/model/documentfragment~DocumentFragment DocumentFragment}.
17
+ */
18
+ export default class NodeList {
19
+ /**
20
+ * Creates an empty node list.
21
+ *
22
+ * @protected
23
+ * @param {Iterable.<module:engine/model/node~Node>} nodes Nodes contained in this node list.
24
+ */
25
+ constructor( nodes ) {
26
+ /**
27
+ * Nodes contained in this node list.
28
+ *
29
+ * @private
30
+ * @member {Array.<module:engine/model/node~Node>}
31
+ */
32
+ this._nodes = [];
33
+
34
+ if ( nodes ) {
35
+ this._insertNodes( 0, nodes );
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Iterable interface.
41
+ *
42
+ * Iterates over all nodes contained inside this node list.
43
+ *
44
+ * @returns {Iterable.<module:engine/model/node~Node>}
45
+ */
46
+ [ Symbol.iterator ]() {
47
+ return this._nodes[ Symbol.iterator ]();
48
+ }
49
+
50
+ /**
51
+ * Number of nodes contained inside this node list.
52
+ *
53
+ * @readonly
54
+ * @type {Number}
55
+ */
56
+ get length() {
57
+ return this._nodes.length;
58
+ }
59
+
60
+ /**
61
+ * Sum of {@link module:engine/model/node~Node#offsetSize offset sizes} of all nodes contained inside this node list.
62
+ *
63
+ * @readonly
64
+ * @type {Number}
65
+ */
66
+ get maxOffset() {
67
+ return this._nodes.reduce( ( sum, node ) => sum + node.offsetSize, 0 );
68
+ }
69
+
70
+ /**
71
+ * Gets the node at the given index. Returns `null` if incorrect index was passed.
72
+ *
73
+ * @param {Number} index Index of node.
74
+ * @returns {module:engine/model/node~Node|null} Node at given index.
75
+ */
76
+ getNode( index ) {
77
+ return this._nodes[ index ] || null;
78
+ }
79
+
80
+ /**
81
+ * Returns an index of the given node. Returns `null` if given node is not inside this node list.
82
+ *
83
+ * @param {module:engine/model/node~Node} node Child node to look for.
84
+ * @returns {Number|null} Child node's index.
85
+ */
86
+ getNodeIndex( node ) {
87
+ const index = this._nodes.indexOf( node );
88
+
89
+ return index == -1 ? null : index;
90
+ }
91
+
92
+ /**
93
+ * Returns the starting offset of given node. Starting offset is equal to the sum of
94
+ * {@link module:engine/model/node~Node#offsetSize offset sizes} of all nodes that are before this node in this node list.
95
+ *
96
+ * @param {module:engine/model/node~Node} node Node to look for.
97
+ * @returns {Number|null} Node's starting offset.
98
+ */
99
+ getNodeStartOffset( node ) {
100
+ const index = this.getNodeIndex( node );
101
+
102
+ return index === null ? null : this._nodes.slice( 0, index ).reduce( ( sum, node ) => sum + node.offsetSize, 0 );
103
+ }
104
+
105
+ /**
106
+ * Converts index to offset in node list.
107
+ *
108
+ * Returns starting offset of a node that is at given index. Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError}
109
+ * `model-nodelist-index-out-of-bounds` if given index is less than `0` or more than {@link #length}.
110
+ *
111
+ * @param {Number} index Node's index.
112
+ * @returns {Number} Node's starting offset.
113
+ */
114
+ indexToOffset( index ) {
115
+ if ( index == this._nodes.length ) {
116
+ return this.maxOffset;
117
+ }
118
+
119
+ const node = this._nodes[ index ];
120
+
121
+ if ( !node ) {
122
+ /**
123
+ * Given index cannot be found in the node list.
124
+ *
125
+ * @error model-nodelist-index-out-of-bounds
126
+ */
127
+ throw new CKEditorError( 'model-nodelist-index-out-of-bounds', this );
128
+ }
129
+
130
+ return this.getNodeStartOffset( node );
131
+ }
132
+
133
+ /**
134
+ * Converts offset in node list to index.
135
+ *
136
+ * Returns index of a node that occupies given offset. Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError}
137
+ * `model-nodelist-offset-out-of-bounds` if given offset is less than `0` or more than {@link #maxOffset}.
138
+ *
139
+ * @param {Number} offset Offset to look for.
140
+ * @returns {Number} Index of a node that occupies given offset.
141
+ */
142
+ offsetToIndex( offset ) {
143
+ let totalOffset = 0;
144
+
145
+ for ( const node of this._nodes ) {
146
+ if ( offset >= totalOffset && offset < totalOffset + node.offsetSize ) {
147
+ return this.getNodeIndex( node );
148
+ }
149
+
150
+ totalOffset += node.offsetSize;
151
+ }
152
+
153
+ if ( totalOffset != offset ) {
154
+ /**
155
+ * Given offset cannot be found in the node list.
156
+ *
157
+ * @error model-nodelist-offset-out-of-bounds
158
+ * @param {Number} offset
159
+ * @param {module:engine/model/nodelist~NodeList} nodeList Stringified node list.
160
+ */
161
+ throw new CKEditorError( 'model-nodelist-offset-out-of-bounds',
162
+ this,
163
+ {
164
+ offset,
165
+ nodeList: this
166
+ }
167
+ );
168
+ }
169
+
170
+ return this.length;
171
+ }
172
+
173
+ /**
174
+ * Inserts given nodes at given index.
175
+ *
176
+ * @protected
177
+ * @param {Number} index Index at which nodes should be inserted.
178
+ * @param {Iterable.<module:engine/model/node~Node>} nodes Nodes to be inserted.
179
+ */
180
+ _insertNodes( index, nodes ) {
181
+ // Validation.
182
+ for ( const node of nodes ) {
183
+ if ( !( node instanceof Node ) ) {
184
+ /**
185
+ * Trying to insert an object which is not a Node instance.
186
+ *
187
+ * @error model-nodelist-insertnodes-not-node
188
+ */
189
+ throw new CKEditorError( 'model-nodelist-insertnodes-not-node', this );
190
+ }
191
+ }
192
+
193
+ this._nodes.splice( index, 0, ...nodes );
194
+ }
195
+
196
+ /**
197
+ * Removes one or more nodes starting at the given index.
198
+ *
199
+ * @protected
200
+ * @param {Number} indexStart Index of the first node to remove.
201
+ * @param {Number} [howMany=1] Number of nodes to remove.
202
+ * @returns {Array.<module:engine/model/node~Node>} Array containing removed nodes.
203
+ */
204
+ _removeNodes( indexStart, howMany = 1 ) {
205
+ return this._nodes.splice( indexStart, howMany );
206
+ }
207
+
208
+ /**
209
+ * Converts `NodeList` instance to an array containing nodes that were inserted in the node list. Nodes
210
+ * are also converted to their plain object representation.
211
+ *
212
+ * @returns {Array.<module:engine/model/node~Node>} `NodeList` instance converted to `Array`.
213
+ */
214
+ toJSON() {
215
+ return this._nodes.map( node => node.toJSON() );
216
+ }
217
+ }
@@ -0,0 +1,202 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+
6
+ /**
7
+ * @module engine/model/operation/attributeoperation
8
+ */
9
+
10
+ import Operation from './operation';
11
+ import Range from '../range';
12
+ import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
13
+ import { _setAttribute } from './utils';
14
+ import { isEqual } from 'lodash-es';
15
+
16
+ /**
17
+ * Operation to change nodes' attribute.
18
+ *
19
+ * Using this class you can add, remove or change value of the attribute.
20
+ *
21
+ * @extends module:engine/model/operation/operation~Operation
22
+ */
23
+ export default class AttributeOperation extends Operation {
24
+ /**
25
+ * Creates an operation that changes, removes or adds attributes.
26
+ *
27
+ * If only `newValue` is set, attribute will be added on a node. Note that all nodes in operation's range must not
28
+ * have an attribute with the same key as the added attribute.
29
+ *
30
+ * If only `oldValue` is set, then attribute with given key will be removed. Note that all nodes in operation's range
31
+ * must have an attribute with that key added.
32
+ *
33
+ * If both `newValue` and `oldValue` are set, then the operation will change the attribute value. Note that all nodes in
34
+ * operation's ranges must already have an attribute with given key and `oldValue` as value
35
+ *
36
+ * @param {module:engine/model/range~Range} range Range on which the operation should be applied. Must be a flat range.
37
+ * @param {String} key Key of an attribute to change or remove.
38
+ * @param {*} oldValue Old value of the attribute with given key or `null`, if attribute was not set before.
39
+ * @param {*} newValue New value of the attribute with given key or `null`, if operation should remove attribute.
40
+ * @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation
41
+ * can be applied or `null` if the operation operates on detached (non-document) tree.
42
+ */
43
+ constructor( range, key, oldValue, newValue, baseVersion ) {
44
+ super( baseVersion );
45
+
46
+ /**
47
+ * Range on which operation should be applied.
48
+ *
49
+ * @readonly
50
+ * @member {module:engine/model/range~Range}
51
+ */
52
+ this.range = range.clone();
53
+
54
+ /**
55
+ * Key of an attribute to change or remove.
56
+ *
57
+ * @readonly
58
+ * @member {String}
59
+ */
60
+ this.key = key;
61
+
62
+ /**
63
+ * Old value of the attribute with given key or `null`, if attribute was not set before.
64
+ *
65
+ * @readonly
66
+ * @member {*}
67
+ */
68
+ this.oldValue = oldValue === undefined ? null : oldValue;
69
+
70
+ /**
71
+ * New value of the attribute with given key or `null`, if operation should remove attribute.
72
+ *
73
+ * @readonly
74
+ * @member {*}
75
+ */
76
+ this.newValue = newValue === undefined ? null : newValue;
77
+ }
78
+
79
+ /**
80
+ * @inheritDoc
81
+ */
82
+ get type() {
83
+ if ( this.oldValue === null ) {
84
+ return 'addAttribute';
85
+ } else if ( this.newValue === null ) {
86
+ return 'removeAttribute';
87
+ } else {
88
+ return 'changeAttribute';
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Creates and returns an operation that has the same parameters as this operation.
94
+ *
95
+ * @returns {module:engine/model/operation/attributeoperation~AttributeOperation} Clone of this operation.
96
+ */
97
+ clone() {
98
+ return new AttributeOperation( this.range, this.key, this.oldValue, this.newValue, this.baseVersion );
99
+ }
100
+
101
+ /**
102
+ * See {@link module:engine/model/operation/operation~Operation#getReversed `Operation#getReversed()`}.
103
+ *
104
+ * @returns {module:engine/model/operation/attributeoperation~AttributeOperation}
105
+ */
106
+ getReversed() {
107
+ return new AttributeOperation( this.range, this.key, this.newValue, this.oldValue, this.baseVersion + 1 );
108
+ }
109
+
110
+ /**
111
+ * @inheritDoc
112
+ */
113
+ toJSON() {
114
+ const json = super.toJSON();
115
+
116
+ json.range = this.range.toJSON();
117
+
118
+ return json;
119
+ }
120
+
121
+ /**
122
+ * @inheritDoc
123
+ */
124
+ _validate() {
125
+ if ( !this.range.isFlat ) {
126
+ /**
127
+ * The range to change is not flat.
128
+ *
129
+ * @error attribute-operation-range-not-flat
130
+ */
131
+ throw new CKEditorError( 'attribute-operation-range-not-flat', this );
132
+ }
133
+
134
+ for ( const item of this.range.getItems( { shallow: true } ) ) {
135
+ if ( this.oldValue !== null && !isEqual( item.getAttribute( this.key ), this.oldValue ) ) {
136
+ /**
137
+ * Changed node has different attribute value than operation's old attribute value.
138
+ *
139
+ * @error attribute-operation-wrong-old-value
140
+ * @param {module:engine/model/item~Item} item
141
+ * @param {String} key
142
+ * @param {*} value
143
+ */
144
+ throw new CKEditorError(
145
+ 'attribute-operation-wrong-old-value',
146
+ this,
147
+ { item, key: this.key, value: this.oldValue }
148
+ );
149
+ }
150
+
151
+ if ( this.oldValue === null && this.newValue !== null && item.hasAttribute( this.key ) ) {
152
+ /**
153
+ * The attribute with given key already exists for the given node.
154
+ *
155
+ * @error attribute-operation-attribute-exists
156
+ * @param {module:engine/model/node~Node} node
157
+ * @param {String} key
158
+ */
159
+ throw new CKEditorError(
160
+ 'attribute-operation-attribute-exists',
161
+ this,
162
+ { node: item, key: this.key }
163
+ );
164
+ }
165
+ }
166
+ }
167
+
168
+ /**
169
+ * @inheritDoc
170
+ */
171
+ _execute() {
172
+ // If value to set is same as old value, don't do anything.
173
+ if ( !isEqual( this.oldValue, this.newValue ) ) {
174
+ // Execution.
175
+ _setAttribute( this.range, this.key, this.newValue );
176
+ }
177
+ }
178
+
179
+ /**
180
+ * @inheritDoc
181
+ */
182
+ static get className() {
183
+ return 'AttributeOperation';
184
+ }
185
+
186
+ /**
187
+ * Creates `AttributeOperation` object from deserilized object, i.e. from parsed JSON string.
188
+ *
189
+ * @param {Object} json Deserialized JSON object.
190
+ * @param {module:engine/model/document~Document} document Document on which this operation will be applied.
191
+ * @returns {module:engine/model/operation/attributeoperation~AttributeOperation}
192
+ */
193
+ static fromJSON( json, document ) {
194
+ return new AttributeOperation( Range.fromJSON( json.range, document ), json.key, json.oldValue, json.newValue, json.baseVersion );
195
+ }
196
+
197
+ // @if CK_DEBUG_ENGINE // toString() {
198
+ // @if CK_DEBUG_ENGINE // return `AttributeOperation( ${ this.baseVersion } ): ` +
199
+ // @if CK_DEBUG_ENGINE // `"${ this.key }": ${ JSON.stringify( this.oldValue ) }` +
200
+ // @if CK_DEBUG_ENGINE // ` -> ${ JSON.stringify( this.newValue ) }, ${ this.range }`;
201
+ // @if CK_DEBUG_ENGINE // }
202
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+
6
+ /**
7
+ * @module engine/model/operation/detachoperation
8
+ */
9
+
10
+ import Operation from './operation';
11
+ import Range from '../range';
12
+ import { _remove } from './utils';
13
+ import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
14
+
15
+ // @if CK_DEBUG_ENGINE // const ModelRange = require( '../range' ).default;
16
+
17
+ /**
18
+ * Operation to permanently remove node from detached root.
19
+ * Note this operation is only a local operation and won't be send to the other clients.
20
+ *
21
+ * @extends module:engine/model/operation/operation~Operation
22
+ */
23
+ export default class DetachOperation extends Operation {
24
+ /**
25
+ * Creates an insert operation.
26
+ *
27
+ * @param {module:engine/model/position~Position} sourcePosition
28
+ * Position before the first {@link module:engine/model/item~Item model item} to move.
29
+ * @param {Number} howMany Offset size of moved range. Moved range will start from `sourcePosition` and end at
30
+ * `sourcePosition` with offset shifted by `howMany`.
31
+ */
32
+ constructor( sourcePosition, howMany ) {
33
+ super( null );
34
+
35
+ /**
36
+ * Position before the first {@link module:engine/model/item~Item model item} to detach.
37
+ *
38
+ * @member {module:engine/model/position~Position} #sourcePosition
39
+ */
40
+ this.sourcePosition = sourcePosition.clone();
41
+
42
+ /**
43
+ * Offset size of moved range.
44
+ *
45
+ * @member {Number} #howMany
46
+ */
47
+ this.howMany = howMany;
48
+ }
49
+
50
+ /**
51
+ * @inheritDoc
52
+ */
53
+ get type() {
54
+ return 'detach';
55
+ }
56
+
57
+ /**
58
+ * @inheritDoc
59
+ */
60
+ toJSON() {
61
+ const json = super.toJSON();
62
+
63
+ json.sourcePosition = this.sourcePosition.toJSON();
64
+
65
+ return json;
66
+ }
67
+
68
+ /**
69
+ * @inheritDoc
70
+ */
71
+ _validate() {
72
+ if ( this.sourcePosition.root.document ) {
73
+ /**
74
+ * Cannot detach document node.
75
+ *
76
+ * @error detach-operation-on-document-node
77
+ */
78
+ throw new CKEditorError( 'detach-operation-on-document-node', this );
79
+ }
80
+ }
81
+
82
+ /**
83
+ * @inheritDoc
84
+ */
85
+ _execute() {
86
+ _remove( Range._createFromPositionAndShift( this.sourcePosition, this.howMany ) );
87
+ }
88
+
89
+ /**
90
+ * @inheritDoc
91
+ */
92
+ static get className() {
93
+ return 'DetachOperation';
94
+ }
95
+
96
+ // @if CK_DEBUG_ENGINE // toString() {
97
+ // @if CK_DEBUG_ENGINE // const range = ModelRange._createFromPositionAndShift( this.sourcePosition, this.howMany );
98
+ // @if CK_DEBUG_ENGINE // const nodes = Array.from( range.getItems() );
99
+ // @if CK_DEBUG_ENGINE // const nodeString = nodes.length > 1 ? `[ ${ nodes.length } ]` : nodes[ 0 ];
100
+
101
+ // @if CK_DEBUG_ENGINE // return `DetachOperation( ${ this.baseVersion } ): ${ nodeString } -> ${ range }`;
102
+ // @if CK_DEBUG_ENGINE // }
103
+ }
@@ -0,0 +1,188 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+
6
+ /**
7
+ * @module engine/model/operation/insertoperation
8
+ */
9
+
10
+ import Operation from './operation';
11
+ import Position from '../position';
12
+ import NodeList from '../nodelist';
13
+ import MoveOperation from './moveoperation';
14
+ import { _insert, _normalizeNodes } from './utils';
15
+ import Text from '../text';
16
+ import Element from '../element';
17
+ import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
18
+
19
+ /**
20
+ * Operation to insert one or more nodes at given position in the model.
21
+ *
22
+ * @extends module:engine/model/operation/operation~Operation
23
+ */
24
+ export default class InsertOperation extends Operation {
25
+ /**
26
+ * Creates an insert operation.
27
+ *
28
+ * @param {module:engine/model/position~Position} position Position of insertion.
29
+ * @param {module:engine/model/node~NodeSet} nodes The list of nodes to be inserted.
30
+ * @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation
31
+ * can be applied or `null` if the operation operates on detached (non-document) tree.
32
+ */
33
+ constructor( position, nodes, baseVersion ) {
34
+ super( baseVersion );
35
+
36
+ /**
37
+ * Position of insertion.
38
+ *
39
+ * @readonly
40
+ * @member {module:engine/model/position~Position} module:engine/model/operation/insertoperation~InsertOperation#position
41
+ */
42
+ this.position = position.clone();
43
+ this.position.stickiness = 'toNone';
44
+
45
+ /**
46
+ * List of nodes to insert.
47
+ *
48
+ * @readonly
49
+ * @member {module:engine/model/nodelist~NodeList} module:engine/model/operation/insertoperation~InsertOperation#nodeList
50
+ */
51
+ this.nodes = new NodeList( _normalizeNodes( nodes ) );
52
+
53
+ /**
54
+ * Flag deciding how the operation should be transformed. If set to `true`, nodes might get additional attributes
55
+ * during operational transformation. This happens when the operation insertion position is inside of a range
56
+ * where attributes have changed.
57
+ *
58
+ * @member {Boolean} module:engine/model/operation/insertoperation~InsertOperation#shouldReceiveAttributes
59
+ */
60
+ this.shouldReceiveAttributes = false;
61
+ }
62
+
63
+ /**
64
+ * @inheritDoc
65
+ */
66
+ get type() {
67
+ return 'insert';
68
+ }
69
+
70
+ /**
71
+ * Total offset size of inserted nodes.
72
+ *
73
+ * @returns {Number}
74
+ */
75
+ get howMany() {
76
+ return this.nodes.maxOffset;
77
+ }
78
+
79
+ /**
80
+ * Creates and returns an operation that has the same parameters as this operation.
81
+ *
82
+ * @returns {module:engine/model/operation/insertoperation~InsertOperation} Clone of this operation.
83
+ */
84
+ clone() {
85
+ const nodes = new NodeList( [ ...this.nodes ].map( node => node._clone( true ) ) );
86
+ const insert = new InsertOperation( this.position, nodes, this.baseVersion );
87
+
88
+ insert.shouldReceiveAttributes = this.shouldReceiveAttributes;
89
+
90
+ return insert;
91
+ }
92
+
93
+ /**
94
+ * See {@link module:engine/model/operation/operation~Operation#getReversed `Operation#getReversed()`}.
95
+ *
96
+ * @returns {module:engine/model/operation/moveoperation~MoveOperation}
97
+ */
98
+ getReversed() {
99
+ const graveyard = this.position.root.document.graveyard;
100
+ const gyPosition = new Position( graveyard, [ 0 ] );
101
+
102
+ return new MoveOperation( this.position, this.nodes.maxOffset, gyPosition, this.baseVersion + 1 );
103
+ }
104
+
105
+ /**
106
+ * @inheritDoc
107
+ */
108
+ _validate() {
109
+ const targetElement = this.position.parent;
110
+
111
+ if ( !targetElement || targetElement.maxOffset < this.position.offset ) {
112
+ /**
113
+ * Insertion position is invalid.
114
+ *
115
+ * @error insert-operation-position-invalid
116
+ */
117
+ throw new CKEditorError(
118
+ 'insert-operation-position-invalid',
119
+ this
120
+ );
121
+ }
122
+ }
123
+
124
+ /**
125
+ * @inheritDoc
126
+ */
127
+ _execute() {
128
+ // What happens here is that we want original nodes be passed to writer because we want original nodes
129
+ // to be inserted to the model. But in InsertOperation, we want to keep those nodes as they were added
130
+ // to the operation, not modified. For example, text nodes can get merged or cropped while Elements can
131
+ // get children. It is important that InsertOperation has the copy of original nodes in intact state.
132
+ const originalNodes = this.nodes;
133
+ this.nodes = new NodeList( [ ...originalNodes ].map( node => node._clone( true ) ) );
134
+
135
+ _insert( this.position, originalNodes );
136
+ }
137
+
138
+ /**
139
+ * @inheritDoc
140
+ */
141
+ toJSON() {
142
+ const json = super.toJSON();
143
+
144
+ json.position = this.position.toJSON();
145
+ json.nodes = this.nodes.toJSON();
146
+
147
+ return json;
148
+ }
149
+
150
+ /**
151
+ * @inheritDoc
152
+ */
153
+ static get className() {
154
+ return 'InsertOperation';
155
+ }
156
+
157
+ /**
158
+ * Creates `InsertOperation` object from deserilized object, i.e. from parsed JSON string.
159
+ *
160
+ * @param {Object} json Deserialized JSON object.
161
+ * @param {module:engine/model/document~Document} document Document on which this operation will be applied.
162
+ * @returns {module:engine/model/operation/insertoperation~InsertOperation}
163
+ */
164
+ static fromJSON( json, document ) {
165
+ const children = [];
166
+
167
+ for ( const child of json.nodes ) {
168
+ if ( child.name ) {
169
+ // If child has name property, it is an Element.
170
+ children.push( Element.fromJSON( child ) );
171
+ } else {
172
+ // Otherwise, it is a Text node.
173
+ children.push( Text.fromJSON( child ) );
174
+ }
175
+ }
176
+
177
+ const insert = new InsertOperation( Position.fromJSON( json.position, document ), children, json.baseVersion );
178
+ insert.shouldReceiveAttributes = json.shouldReceiveAttributes;
179
+
180
+ return insert;
181
+ }
182
+
183
+ // @if CK_DEBUG_ENGINE // toString() {
184
+ // @if CK_DEBUG_ENGINE // const nodeString = this.nodes.length > 1 ? `[ ${ this.nodes.length } ]` : this.nodes.getNode( 0 );
185
+
186
+ // @if CK_DEBUG_ENGINE // return `InsertOperation( ${ this.baseVersion } ): ${ nodeString } -> ${ this.position }`;
187
+ // @if CK_DEBUG_ENGINE // }
188
+ }