@ckeditor/ckeditor5-engine 43.2.0 → 43.3.0-alpha.1

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.
@@ -14,7 +14,7 @@ import { CKEditorError, spliceArray } from '@ckeditor/ckeditor5-utils';
14
14
  */
15
15
  export default class NodeList {
16
16
  /**
17
- * Creates an empty node list.
17
+ * Creates a node list.
18
18
  *
19
19
  * @internal
20
20
  * @param nodes Nodes contained in this node list.
@@ -24,6 +24,14 @@ export default class NodeList {
24
24
  * Nodes contained in this node list.
25
25
  */
26
26
  this._nodes = [];
27
+ /**
28
+ * This array maps numbers (offsets) to node that is placed at that offset.
29
+ *
30
+ * This array is similar to `_nodes` with the difference that one node may occupy multiple consecutive items in the array.
31
+ *
32
+ * This array is needed to quickly retrieve a node that is placed at given offset.
33
+ */
34
+ this._offsetToNode = [];
27
35
  if (nodes) {
28
36
  this._insertNodes(0, nodes);
29
37
  }
@@ -46,7 +54,7 @@ export default class NodeList {
46
54
  * Sum of {@link module:engine/model/node~Node#offsetSize offset sizes} of all nodes contained inside this node list.
47
55
  */
48
56
  get maxOffset() {
49
- return this._nodes.reduce((sum, node) => sum + node.offsetSize, 0);
57
+ return this._offsetToNode.length;
50
58
  }
51
59
  /**
52
60
  * Gets the node at the given index. Returns `null` if incorrect index was passed.
@@ -55,32 +63,32 @@ export default class NodeList {
55
63
  return this._nodes[index] || null;
56
64
  }
57
65
  /**
58
- * Returns an index of the given node. Returns `null` if given node is not inside this node list.
66
+ * Gets the node at the given offset. Returns `null` if incorrect offset was passed.
67
+ */
68
+ getNodeAtOffset(offset) {
69
+ return this._offsetToNode[offset] || null;
70
+ }
71
+ /**
72
+ * Returns an index of the given node or `null` if given node does not have a parent.
73
+ *
74
+ * This is an alias to {@link module:engine/model/node~Node#index}.
59
75
  */
60
76
  getNodeIndex(node) {
61
- const index = this._nodes.indexOf(node);
62
- return index == -1 ? null : index;
77
+ return node.index;
63
78
  }
64
79
  /**
65
- * Returns the starting offset of given node. Starting offset is equal to the sum of
66
- * {@link module:engine/model/node~Node#offsetSize offset sizes} of all nodes that are before this node in this node list.
80
+ * Returns the offset at which given node is placed in its parent or `null` if given node does not have a parent.
81
+ *
82
+ * This is an alias to {@link module:engine/model/node~Node#startOffset}.
67
83
  */
68
84
  getNodeStartOffset(node) {
69
- const index = this.getNodeIndex(node);
70
- if (index === null) {
71
- return null;
72
- }
73
- let sum = 0;
74
- for (let i = 0; i < index; i++) {
75
- sum += this._nodes[i].offsetSize;
76
- }
77
- return sum;
85
+ return node.startOffset;
78
86
  }
79
87
  /**
80
88
  * Converts index to offset in node list.
81
89
  *
82
- * Returns starting offset of a node that is at given index. Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError}
83
- * `model-nodelist-index-out-of-bounds` if given index is less than `0` or more than {@link #length}.
90
+ * Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `model-nodelist-index-out-of-bounds` if given index is less
91
+ * than `0` or more than {@link #length}.
84
92
  */
85
93
  indexToOffset(index) {
86
94
  if (index == this._nodes.length) {
@@ -100,18 +108,15 @@ export default class NodeList {
100
108
  /**
101
109
  * Converts offset in node list to index.
102
110
  *
103
- * Returns index of a node that occupies given offset. Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError}
104
- * `model-nodelist-offset-out-of-bounds` if given offset is less than `0` or more than {@link #maxOffset}.
111
+ * Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `model-nodelist-offset-out-of-bounds` if given offset is less
112
+ * than `0` or more than {@link #maxOffset}.
105
113
  */
106
114
  offsetToIndex(offset) {
107
- let totalOffset = 0;
108
- for (const node of this._nodes) {
109
- if (offset >= totalOffset && offset < totalOffset + node.offsetSize) {
110
- return this.getNodeIndex(node);
111
- }
112
- totalOffset += node.offsetSize;
115
+ if (offset == this._offsetToNode.length) {
116
+ return this._nodes.length;
113
117
  }
114
- if (totalOffset != offset) {
118
+ const node = this._offsetToNode[offset];
119
+ if (!node) {
115
120
  /**
116
121
  * Given offset cannot be found in the node list.
117
122
  *
@@ -124,7 +129,7 @@ export default class NodeList {
124
129
  nodeList: this
125
130
  });
126
131
  }
127
- return this.length;
132
+ return this.getNodeIndex(node);
128
133
  }
129
134
  /**
130
135
  * Inserts given nodes at given index.
@@ -145,7 +150,18 @@ export default class NodeList {
145
150
  throw new CKEditorError('model-nodelist-insertnodes-not-node', this);
146
151
  }
147
152
  }
148
- this._nodes = spliceArray(this._nodes, Array.from(nodes), index, 0);
153
+ const nodesArray = Array.from(nodes);
154
+ const offsetsArray = makeOffsetsArray(nodesArray);
155
+ let offset = this.indexToOffset(index);
156
+ // Splice nodes array and offsets array into the nodelist.
157
+ this._nodes = spliceArray(this._nodes, nodesArray, index, 0);
158
+ this._offsetToNode = spliceArray(this._offsetToNode, offsetsArray, offset, 0);
159
+ // Refresh indexes and offsets for nodes inside this node list. We need to do this for all inserted nodes and all nodes after them.
160
+ for (let i = index; i < this._nodes.length; i++) {
161
+ this._nodes[i]._index = i;
162
+ this._nodes[i]._startOffset = offset;
163
+ offset += this._nodes[i].offsetSize;
164
+ }
149
165
  }
150
166
  /**
151
167
  * Removes one or more nodes starting at the given index.
@@ -156,7 +172,26 @@ export default class NodeList {
156
172
  * @returns Array containing removed nodes.
157
173
  */
158
174
  _removeNodes(indexStart, howMany = 1) {
159
- return this._nodes.splice(indexStart, howMany);
175
+ if (howMany == 0) {
176
+ return [];
177
+ }
178
+ // Remove nodes from this nodelist.
179
+ let offset = this.indexToOffset(indexStart);
180
+ const nodes = this._nodes.splice(indexStart, howMany);
181
+ const lastNode = nodes[nodes.length - 1];
182
+ const removedOffsetSum = lastNode.startOffset + lastNode.offsetSize - offset;
183
+ this._offsetToNode.splice(offset, removedOffsetSum);
184
+ // Reset index and start offset properties for the removed nodes -- they do not have a parent anymore.
185
+ for (const node of nodes) {
186
+ node._index = null;
187
+ node._startOffset = null;
188
+ }
189
+ for (let i = indexStart; i < this._nodes.length; i++) {
190
+ this._nodes[i]._index = i;
191
+ this._nodes[i]._startOffset = offset;
192
+ offset += this._nodes[i].offsetSize;
193
+ }
194
+ return nodes;
160
195
  }
161
196
  /**
162
197
  * Converts `NodeList` instance to an array containing nodes that were inserted in the node list. Nodes
@@ -168,3 +203,16 @@ export default class NodeList {
168
203
  return this._nodes.map(node => node.toJSON());
169
204
  }
170
205
  }
206
+ /**
207
+ * Creates an array of nodes in the format as in {@link module:engine/model/nodelist~NodeList#_offsetToNode}, i.e. one node will
208
+ * occupy multiple items if its offset size is greater than one.
209
+ */
210
+ function makeOffsetsArray(nodes) {
211
+ const offsets = [];
212
+ for (const node of nodes) {
213
+ const start = offsets.length;
214
+ offsets.length += node.offsetSize;
215
+ offsets.fill(node, start);
216
+ }
217
+ return offsets;
218
+ }
@@ -124,11 +124,11 @@ export default class Position extends TypeCheckable {
124
124
  */
125
125
  get textNode(): Text | null;
126
126
  /**
127
- * Node directly after this position or `null` if this position is in text node.
127
+ * Node directly after this position. Returns `null` if this position is at the end of its parent, or if it is in a text node.
128
128
  */
129
129
  get nodeAfter(): Node | null;
130
130
  /**
131
- * Node directly before this position or `null` if this position is in text node.
131
+ * Node directly before this position. Returns `null` if this position is at the start of its parent, or if it is in a text node.
132
132
  */
133
133
  get nodeBefore(): Node | null;
134
134
  /**
@@ -139,6 +139,10 @@ export default class Position extends TypeCheckable {
139
139
  * Is `true` if position is at the end of its {@link module:engine/model/position~Position#parent parent}, `false` otherwise.
140
140
  */
141
141
  get isAtEnd(): boolean;
142
+ /**
143
+ * Checks whether the position is valid in current model tree, that is whether it points to an existing place in the model.
144
+ */
145
+ isValid(): boolean;
142
146
  /**
143
147
  * Checks whether this position is before or after given position.
144
148
  *
@@ -496,6 +500,7 @@ export type PositionStickiness = 'toNone' | 'toNext' | 'toPrevious';
496
500
  * * {@link module:engine/model/position~getNodeAfterPosition}
497
501
  * * {@link module:engine/model/position~getNodeBeforePosition}
498
502
  *
503
+ * @param position
499
504
  * @param positionParent The parent of the given position.
500
505
  */
501
506
  export declare function getTextNodeAtPosition(position: Position, positionParent: Element | DocumentFragment): Text | null;
@@ -518,6 +523,7 @@ export declare function getTextNodeAtPosition(position: Position, positionParent
518
523
  * * {@link module:engine/model/position~getTextNodeAtPosition}
519
524
  * * {@link module:engine/model/position~getNodeBeforePosition}
520
525
  *
526
+ * @param position Position to check.
521
527
  * @param positionParent The parent of the given position.
522
528
  * @param textNode Text node at the given position.
523
529
  */
@@ -532,6 +538,7 @@ export declare function getNodeAfterPosition(position: Position, positionParent:
532
538
  * * {@link module:engine/model/position~getTextNodeAtPosition}
533
539
  * * {@link module:engine/model/position~getNodeAfterPosition}
534
540
  *
541
+ * @param position Position to check.
535
542
  * @param positionParent The parent of the given position.
536
543
  * @param textNode Text node at the given position.
537
544
  */
@@ -100,7 +100,7 @@ export default class Position extends TypeCheckable {
100
100
  get parent() {
101
101
  let parent = this.root;
102
102
  for (let i = 0; i < this.path.length - 1; i++) {
103
- parent = parent.getChild(parent.offsetToIndex(this.path[i]));
103
+ parent = parent.getChildAtOffset(this.path[i]);
104
104
  if (!parent) {
105
105
  /**
106
106
  * The position's path is incorrect. This means that a position does not point to
@@ -141,7 +141,7 @@ export default class Position extends TypeCheckable {
141
141
  return getTextNodeAtPosition(this, this.parent);
142
142
  }
143
143
  /**
144
- * Node directly after this position or `null` if this position is in text node.
144
+ * Node directly after this position. Returns `null` if this position is at the end of its parent, or if it is in a text node.
145
145
  */
146
146
  get nodeAfter() {
147
147
  // Cache the parent and reuse for performance reasons. See #6579 and #6582.
@@ -149,7 +149,7 @@ export default class Position extends TypeCheckable {
149
149
  return getNodeAfterPosition(this, parent, getTextNodeAtPosition(this, parent));
150
150
  }
151
151
  /**
152
- * Node directly before this position or `null` if this position is in text node.
152
+ * Node directly before this position. Returns `null` if this position is at the start of its parent, or if it is in a text node.
153
153
  */
154
154
  get nodeBefore() {
155
155
  // Cache the parent and reuse for performance reasons. See #6579 and #6582.
@@ -168,6 +168,22 @@ export default class Position extends TypeCheckable {
168
168
  get isAtEnd() {
169
169
  return this.offset == this.parent.maxOffset;
170
170
  }
171
+ /**
172
+ * Checks whether the position is valid in current model tree, that is whether it points to an existing place in the model.
173
+ */
174
+ isValid() {
175
+ if (this.offset < 0) {
176
+ return false;
177
+ }
178
+ let parent = this.root;
179
+ for (let i = 0; i < this.path.length - 1; i++) {
180
+ parent = parent.getChildAtOffset(this.path[i]);
181
+ if (!parent) {
182
+ return false;
183
+ }
184
+ }
185
+ return this.offset <= parent.maxOffset;
186
+ }
171
187
  /**
172
188
  * Checks whether this position is before or after given position.
173
189
  *
@@ -841,10 +857,11 @@ Position.prototype.is = function (type) {
841
857
  * * {@link module:engine/model/position~getNodeAfterPosition}
842
858
  * * {@link module:engine/model/position~getNodeBeforePosition}
843
859
  *
860
+ * @param position
844
861
  * @param positionParent The parent of the given position.
845
862
  */
846
863
  export function getTextNodeAtPosition(position, positionParent) {
847
- const node = positionParent.getChild(positionParent.offsetToIndex(position.offset));
864
+ const node = positionParent.getChildAtOffset(position.offset);
848
865
  if (node && node.is('$text') && node.startOffset < position.offset) {
849
866
  return node;
850
867
  }
@@ -869,6 +886,7 @@ export function getTextNodeAtPosition(position, positionParent) {
869
886
  * * {@link module:engine/model/position~getTextNodeAtPosition}
870
887
  * * {@link module:engine/model/position~getNodeBeforePosition}
871
888
  *
889
+ * @param position Position to check.
872
890
  * @param positionParent The parent of the given position.
873
891
  * @param textNode Text node at the given position.
874
892
  */
@@ -876,7 +894,7 @@ export function getNodeAfterPosition(position, positionParent, textNode) {
876
894
  if (textNode !== null) {
877
895
  return null;
878
896
  }
879
- return positionParent.getChild(positionParent.offsetToIndex(position.offset));
897
+ return positionParent.getChildAtOffset(position.offset);
880
898
  }
881
899
  /**
882
900
  * Returns the node before the given position.
@@ -888,6 +906,7 @@ export function getNodeAfterPosition(position, positionParent, textNode) {
888
906
  * * {@link module:engine/model/position~getTextNodeAtPosition}
889
907
  * * {@link module:engine/model/position~getNodeAfterPosition}
890
908
  *
909
+ * @param position Position to check.
891
910
  * @param positionParent The parent of the given position.
892
911
  * @param textNode Text node at the given position.
893
912
  */
@@ -77,7 +77,7 @@ export default class DomConverter {
77
77
  this.document = document;
78
78
  this.renderingMode = renderingMode;
79
79
  this.blockFillerMode = blockFillerMode || (renderingMode === 'editing' ? 'br' : 'nbsp');
80
- this.preElements = ['pre'];
80
+ this.preElements = ['pre', 'textarea'];
81
81
  this.blockElements = [
82
82
  'address', 'article', 'aside', 'blockquote', 'caption', 'center', 'dd', 'details', 'dir', 'div',
83
83
  'dl', 'dt', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header',
@@ -1097,6 +1097,8 @@ export default class DomConverter {
1097
1097
  // for inline objects can verify if the element is empty.
1098
1098
  if (this._isInlineObjectElement(viewElement)) {
1099
1099
  inlineNodes.push(viewElement);
1100
+ // Inline object content should be handled as a flow-root.
1101
+ this._processDomInlineNodes(null, nestedInlineNodes, options);
1100
1102
  }
1101
1103
  else {
1102
1104
  // It's an inline element that is not an object (like <b>, <i>) or a block element.