@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.
- package/LICENSE.md +17 -0
- package/README.md +30 -0
- package/package.json +70 -0
- package/src/controller/datacontroller.js +563 -0
- package/src/controller/editingcontroller.js +149 -0
- package/src/conversion/conversion.js +644 -0
- package/src/conversion/conversionhelpers.js +40 -0
- package/src/conversion/downcastdispatcher.js +914 -0
- package/src/conversion/downcasthelpers.js +1706 -0
- package/src/conversion/mapper.js +696 -0
- package/src/conversion/modelconsumable.js +329 -0
- package/src/conversion/upcastdispatcher.js +807 -0
- package/src/conversion/upcasthelpers.js +997 -0
- package/src/conversion/viewconsumable.js +623 -0
- package/src/dataprocessor/basichtmlwriter.js +32 -0
- package/src/dataprocessor/dataprocessor.jsdoc +64 -0
- package/src/dataprocessor/htmldataprocessor.js +159 -0
- package/src/dataprocessor/htmlwriter.js +22 -0
- package/src/dataprocessor/xmldataprocessor.js +161 -0
- package/src/dev-utils/model.js +482 -0
- package/src/dev-utils/operationreplayer.js +140 -0
- package/src/dev-utils/utils.js +103 -0
- package/src/dev-utils/view.js +1091 -0
- package/src/index.js +52 -0
- package/src/model/batch.js +82 -0
- package/src/model/differ.js +1282 -0
- package/src/model/document.js +483 -0
- package/src/model/documentfragment.js +390 -0
- package/src/model/documentselection.js +1261 -0
- package/src/model/element.js +438 -0
- package/src/model/history.js +138 -0
- package/src/model/item.jsdoc +14 -0
- package/src/model/liveposition.js +182 -0
- package/src/model/liverange.js +221 -0
- package/src/model/markercollection.js +553 -0
- package/src/model/model.js +934 -0
- package/src/model/node.js +507 -0
- package/src/model/nodelist.js +217 -0
- package/src/model/operation/attributeoperation.js +202 -0
- package/src/model/operation/detachoperation.js +103 -0
- package/src/model/operation/insertoperation.js +188 -0
- package/src/model/operation/markeroperation.js +154 -0
- package/src/model/operation/mergeoperation.js +216 -0
- package/src/model/operation/moveoperation.js +209 -0
- package/src/model/operation/nooperation.js +58 -0
- package/src/model/operation/operation.js +139 -0
- package/src/model/operation/operationfactory.js +49 -0
- package/src/model/operation/renameoperation.js +155 -0
- package/src/model/operation/rootattributeoperation.js +211 -0
- package/src/model/operation/splitoperation.js +254 -0
- package/src/model/operation/transform.js +2389 -0
- package/src/model/operation/utils.js +292 -0
- package/src/model/position.js +1164 -0
- package/src/model/range.js +1049 -0
- package/src/model/rootelement.js +111 -0
- package/src/model/schema.js +1851 -0
- package/src/model/selection.js +902 -0
- package/src/model/text.js +138 -0
- package/src/model/textproxy.js +279 -0
- package/src/model/treewalker.js +414 -0
- package/src/model/utils/autoparagraphing.js +77 -0
- package/src/model/utils/deletecontent.js +528 -0
- package/src/model/utils/getselectedcontent.js +150 -0
- package/src/model/utils/insertcontent.js +824 -0
- package/src/model/utils/modifyselection.js +229 -0
- package/src/model/utils/selection-post-fixer.js +297 -0
- package/src/model/writer.js +1574 -0
- package/src/view/attributeelement.js +274 -0
- package/src/view/containerelement.js +123 -0
- package/src/view/document.js +221 -0
- package/src/view/documentfragment.js +273 -0
- package/src/view/documentselection.js +387 -0
- package/src/view/domconverter.js +1437 -0
- package/src/view/downcastwriter.js +2121 -0
- package/src/view/editableelement.js +118 -0
- package/src/view/element.js +945 -0
- package/src/view/elementdefinition.jsdoc +59 -0
- package/src/view/emptyelement.js +119 -0
- package/src/view/filler.js +161 -0
- package/src/view/item.jsdoc +14 -0
- package/src/view/matcher.js +776 -0
- package/src/view/node.js +391 -0
- package/src/view/observer/arrowkeysobserver.js +58 -0
- package/src/view/observer/bubblingemittermixin.js +307 -0
- package/src/view/observer/bubblingeventinfo.js +71 -0
- package/src/view/observer/clickobserver.js +46 -0
- package/src/view/observer/compositionobserver.js +79 -0
- package/src/view/observer/domeventdata.js +82 -0
- package/src/view/observer/domeventobserver.js +99 -0
- package/src/view/observer/fakeselectionobserver.js +118 -0
- package/src/view/observer/focusobserver.js +106 -0
- package/src/view/observer/inputobserver.js +44 -0
- package/src/view/observer/keyobserver.js +83 -0
- package/src/view/observer/mouseobserver.js +56 -0
- package/src/view/observer/mutationobserver.js +345 -0
- package/src/view/observer/observer.js +118 -0
- package/src/view/observer/selectionobserver.js +242 -0
- package/src/view/placeholder.js +285 -0
- package/src/view/position.js +426 -0
- package/src/view/range.js +533 -0
- package/src/view/rawelement.js +148 -0
- package/src/view/renderer.js +1037 -0
- package/src/view/rooteditableelement.js +107 -0
- package/src/view/selection.js +718 -0
- package/src/view/styles/background.js +73 -0
- package/src/view/styles/border.js +362 -0
- package/src/view/styles/margin.js +41 -0
- package/src/view/styles/padding.js +40 -0
- package/src/view/styles/utils.js +277 -0
- package/src/view/stylesmap.js +938 -0
- package/src/view/text.js +147 -0
- package/src/view/textproxy.js +199 -0
- package/src/view/treewalker.js +496 -0
- package/src/view/uielement.js +238 -0
- package/src/view/upcastwriter.js +484 -0
- package/src/view/view.js +721 -0
- 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
|
+
}
|