@ckeditor/ckeditor5-engine 47.6.1 → 48.0.0-alpha.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 (258) hide show
  1. package/LICENSE.md +1 -1
  2. package/{src → dist}/engineconfig.d.ts +6 -15
  3. package/dist/index-editor.css +38 -15
  4. package/dist/index.css +37 -37
  5. package/dist/index.css.map +1 -1
  6. package/{src → dist}/index.d.ts +0 -1
  7. package/dist/index.js +588 -94
  8. package/dist/index.js.map +1 -1
  9. package/{src → dist}/model/model.d.ts +10 -4
  10. package/{src → dist}/model/selection.d.ts +1 -1
  11. package/{src → dist}/view/downcastwriter.d.ts +3 -2
  12. package/{src → dist}/view/element.d.ts +2 -2
  13. package/{src → dist}/view/matcher.d.ts +4 -2
  14. package/dist/view/styles/background.d.ts +18 -0
  15. package/{src → dist}/view/styles/border.d.ts +0 -12
  16. package/{src → dist}/view/styles/margin.d.ts +0 -13
  17. package/{src → dist}/view/styles/padding.d.ts +0 -13
  18. package/{src → dist}/view/styles/utils.d.ts +12 -0
  19. package/package.json +20 -39
  20. package/src/controller/datacontroller.js +0 -522
  21. package/src/controller/editingcontroller.js +0 -181
  22. package/src/conversion/conversion.js +0 -606
  23. package/src/conversion/conversionhelpers.js +0 -33
  24. package/src/conversion/downcastdispatcher.js +0 -563
  25. package/src/conversion/downcasthelpers.js +0 -2160
  26. package/src/conversion/mapper.js +0 -1050
  27. package/src/conversion/modelconsumable.js +0 -331
  28. package/src/conversion/upcastdispatcher.js +0 -470
  29. package/src/conversion/upcasthelpers.js +0 -952
  30. package/src/conversion/viewconsumable.js +0 -541
  31. package/src/dataprocessor/basichtmlwriter.js +0 -22
  32. package/src/dataprocessor/dataprocessor.js +0 -5
  33. package/src/dataprocessor/htmldataprocessor.js +0 -107
  34. package/src/dataprocessor/htmlwriter.js +0 -5
  35. package/src/dataprocessor/xmldataprocessor.js +0 -127
  36. package/src/dev-utils/model.js +0 -396
  37. package/src/dev-utils/operationreplayer.js +0 -116
  38. package/src/dev-utils/utils.js +0 -122
  39. package/src/dev-utils/view.js +0 -990
  40. package/src/engineconfig.js +0 -5
  41. package/src/index.js +0 -134
  42. package/src/legacyerrors.js +0 -17
  43. package/src/model/batch.js +0 -98
  44. package/src/model/differ.js +0 -1288
  45. package/src/model/document.js +0 -398
  46. package/src/model/documentfragment.js +0 -332
  47. package/src/model/documentselection.js +0 -1026
  48. package/src/model/element.js +0 -323
  49. package/src/model/history.js +0 -206
  50. package/src/model/item.js +0 -5
  51. package/src/model/liveposition.js +0 -93
  52. package/src/model/liverange.js +0 -121
  53. package/src/model/markercollection.js +0 -436
  54. package/src/model/model.js +0 -866
  55. package/src/model/node.js +0 -371
  56. package/src/model/nodelist.js +0 -244
  57. package/src/model/operation/attributeoperation.js +0 -172
  58. package/src/model/operation/detachoperation.js +0 -87
  59. package/src/model/operation/insertoperation.js +0 -153
  60. package/src/model/operation/markeroperation.js +0 -136
  61. package/src/model/operation/mergeoperation.js +0 -184
  62. package/src/model/operation/moveoperation.js +0 -179
  63. package/src/model/operation/nooperation.js +0 -48
  64. package/src/model/operation/operation.js +0 -78
  65. package/src/model/operation/operationfactory.js +0 -44
  66. package/src/model/operation/renameoperation.js +0 -128
  67. package/src/model/operation/rootattributeoperation.js +0 -173
  68. package/src/model/operation/rootoperation.js +0 -106
  69. package/src/model/operation/splitoperation.js +0 -214
  70. package/src/model/operation/transform.js +0 -2211
  71. package/src/model/operation/utils.js +0 -217
  72. package/src/model/position.js +0 -1041
  73. package/src/model/range.js +0 -880
  74. package/src/model/rootelement.js +0 -82
  75. package/src/model/schema.js +0 -1542
  76. package/src/model/selection.js +0 -814
  77. package/src/model/text.js +0 -92
  78. package/src/model/textproxy.js +0 -202
  79. package/src/model/treewalker.js +0 -313
  80. package/src/model/typecheckable.js +0 -16
  81. package/src/model/utils/autoparagraphing.js +0 -63
  82. package/src/model/utils/deletecontent.js +0 -509
  83. package/src/model/utils/getselectedcontent.js +0 -126
  84. package/src/model/utils/insertcontent.js +0 -750
  85. package/src/model/utils/insertobject.js +0 -135
  86. package/src/model/utils/modifyselection.js +0 -187
  87. package/src/model/utils/selection-post-fixer.js +0 -264
  88. package/src/model/writer.js +0 -1318
  89. package/src/view/attributeelement.js +0 -220
  90. package/src/view/containerelement.js +0 -91
  91. package/src/view/datatransfer.js +0 -106
  92. package/src/view/document.js +0 -139
  93. package/src/view/documentfragment.js +0 -251
  94. package/src/view/documentselection.js +0 -270
  95. package/src/view/domconverter.js +0 -1661
  96. package/src/view/downcastwriter.js +0 -1589
  97. package/src/view/editableelement.js +0 -74
  98. package/src/view/element.js +0 -1053
  99. package/src/view/elementdefinition.js +0 -5
  100. package/src/view/emptyelement.js +0 -83
  101. package/src/view/filler.js +0 -161
  102. package/src/view/item.js +0 -5
  103. package/src/view/matcher.js +0 -437
  104. package/src/view/node.js +0 -238
  105. package/src/view/observer/arrowkeysobserver.js +0 -40
  106. package/src/view/observer/bubblingemittermixin.js +0 -215
  107. package/src/view/observer/bubblingeventinfo.js +0 -49
  108. package/src/view/observer/clickobserver.js +0 -26
  109. package/src/view/observer/compositionobserver.js +0 -64
  110. package/src/view/observer/domeventdata.js +0 -63
  111. package/src/view/observer/domeventobserver.js +0 -81
  112. package/src/view/observer/fakeselectionobserver.js +0 -95
  113. package/src/view/observer/focusobserver.js +0 -166
  114. package/src/view/observer/inputobserver.js +0 -236
  115. package/src/view/observer/keyobserver.js +0 -36
  116. package/src/view/observer/mouseobserver.js +0 -26
  117. package/src/view/observer/mutationobserver.js +0 -219
  118. package/src/view/observer/observer.js +0 -92
  119. package/src/view/observer/pointerobserver.js +0 -26
  120. package/src/view/observer/selectionobserver.js +0 -318
  121. package/src/view/observer/tabobserver.js +0 -42
  122. package/src/view/observer/touchobserver.js +0 -26
  123. package/src/view/placeholder.js +0 -285
  124. package/src/view/position.js +0 -341
  125. package/src/view/range.js +0 -451
  126. package/src/view/rawelement.js +0 -115
  127. package/src/view/renderer.js +0 -1148
  128. package/src/view/rooteditableelement.js +0 -78
  129. package/src/view/selection.js +0 -594
  130. package/src/view/styles/background.d.ts +0 -33
  131. package/src/view/styles/background.js +0 -74
  132. package/src/view/styles/border.js +0 -316
  133. package/src/view/styles/margin.js +0 -34
  134. package/src/view/styles/padding.js +0 -34
  135. package/src/view/styles/utils.js +0 -219
  136. package/src/view/stylesmap.js +0 -941
  137. package/src/view/text.js +0 -110
  138. package/src/view/textproxy.js +0 -136
  139. package/src/view/tokenlist.js +0 -194
  140. package/src/view/treewalker.js +0 -389
  141. package/src/view/typecheckable.js +0 -19
  142. package/src/view/uielement.js +0 -194
  143. package/src/view/upcastwriter.js +0 -363
  144. package/src/view/view.js +0 -579
  145. package/theme/placeholder.css +0 -36
  146. package/theme/renderer.css +0 -9
  147. /package/{src → dist}/controller/datacontroller.d.ts +0 -0
  148. /package/{src → dist}/controller/editingcontroller.d.ts +0 -0
  149. /package/{src → dist}/conversion/conversion.d.ts +0 -0
  150. /package/{src → dist}/conversion/conversionhelpers.d.ts +0 -0
  151. /package/{src → dist}/conversion/downcastdispatcher.d.ts +0 -0
  152. /package/{src → dist}/conversion/downcasthelpers.d.ts +0 -0
  153. /package/{src → dist}/conversion/mapper.d.ts +0 -0
  154. /package/{src → dist}/conversion/modelconsumable.d.ts +0 -0
  155. /package/{src → dist}/conversion/upcastdispatcher.d.ts +0 -0
  156. /package/{src → dist}/conversion/upcasthelpers.d.ts +0 -0
  157. /package/{src → dist}/conversion/viewconsumable.d.ts +0 -0
  158. /package/{src → dist}/dataprocessor/basichtmlwriter.d.ts +0 -0
  159. /package/{src → dist}/dataprocessor/dataprocessor.d.ts +0 -0
  160. /package/{src → dist}/dataprocessor/htmldataprocessor.d.ts +0 -0
  161. /package/{src → dist}/dataprocessor/htmlwriter.d.ts +0 -0
  162. /package/{src → dist}/dataprocessor/xmldataprocessor.d.ts +0 -0
  163. /package/{src → dist}/dev-utils/model.d.ts +0 -0
  164. /package/{src → dist}/dev-utils/operationreplayer.d.ts +0 -0
  165. /package/{src → dist}/dev-utils/utils.d.ts +0 -0
  166. /package/{src → dist}/dev-utils/view.d.ts +0 -0
  167. /package/{src → dist}/legacyerrors.d.ts +0 -0
  168. /package/{src → dist}/model/batch.d.ts +0 -0
  169. /package/{src → dist}/model/differ.d.ts +0 -0
  170. /package/{src → dist}/model/document.d.ts +0 -0
  171. /package/{src → dist}/model/documentfragment.d.ts +0 -0
  172. /package/{src → dist}/model/documentselection.d.ts +0 -0
  173. /package/{src → dist}/model/element.d.ts +0 -0
  174. /package/{src → dist}/model/history.d.ts +0 -0
  175. /package/{src → dist}/model/item.d.ts +0 -0
  176. /package/{src → dist}/model/liveposition.d.ts +0 -0
  177. /package/{src → dist}/model/liverange.d.ts +0 -0
  178. /package/{src → dist}/model/markercollection.d.ts +0 -0
  179. /package/{src → dist}/model/node.d.ts +0 -0
  180. /package/{src → dist}/model/nodelist.d.ts +0 -0
  181. /package/{src → dist}/model/operation/attributeoperation.d.ts +0 -0
  182. /package/{src → dist}/model/operation/detachoperation.d.ts +0 -0
  183. /package/{src → dist}/model/operation/insertoperation.d.ts +0 -0
  184. /package/{src → dist}/model/operation/markeroperation.d.ts +0 -0
  185. /package/{src → dist}/model/operation/mergeoperation.d.ts +0 -0
  186. /package/{src → dist}/model/operation/moveoperation.d.ts +0 -0
  187. /package/{src → dist}/model/operation/nooperation.d.ts +0 -0
  188. /package/{src → dist}/model/operation/operation.d.ts +0 -0
  189. /package/{src → dist}/model/operation/operationfactory.d.ts +0 -0
  190. /package/{src → dist}/model/operation/renameoperation.d.ts +0 -0
  191. /package/{src → dist}/model/operation/rootattributeoperation.d.ts +0 -0
  192. /package/{src → dist}/model/operation/rootoperation.d.ts +0 -0
  193. /package/{src → dist}/model/operation/splitoperation.d.ts +0 -0
  194. /package/{src → dist}/model/operation/transform.d.ts +0 -0
  195. /package/{src → dist}/model/operation/utils.d.ts +0 -0
  196. /package/{src → dist}/model/position.d.ts +0 -0
  197. /package/{src → dist}/model/range.d.ts +0 -0
  198. /package/{src → dist}/model/rootelement.d.ts +0 -0
  199. /package/{src → dist}/model/schema.d.ts +0 -0
  200. /package/{src → dist}/model/text.d.ts +0 -0
  201. /package/{src → dist}/model/textproxy.d.ts +0 -0
  202. /package/{src → dist}/model/treewalker.d.ts +0 -0
  203. /package/{src → dist}/model/typecheckable.d.ts +0 -0
  204. /package/{src → dist}/model/utils/autoparagraphing.d.ts +0 -0
  205. /package/{src → dist}/model/utils/deletecontent.d.ts +0 -0
  206. /package/{src → dist}/model/utils/getselectedcontent.d.ts +0 -0
  207. /package/{src → dist}/model/utils/insertcontent.d.ts +0 -0
  208. /package/{src → dist}/model/utils/insertobject.d.ts +0 -0
  209. /package/{src → dist}/model/utils/modifyselection.d.ts +0 -0
  210. /package/{src → dist}/model/utils/selection-post-fixer.d.ts +0 -0
  211. /package/{src → dist}/model/writer.d.ts +0 -0
  212. /package/{src → dist}/view/attributeelement.d.ts +0 -0
  213. /package/{src → dist}/view/containerelement.d.ts +0 -0
  214. /package/{src → dist}/view/datatransfer.d.ts +0 -0
  215. /package/{src → dist}/view/document.d.ts +0 -0
  216. /package/{src → dist}/view/documentfragment.d.ts +0 -0
  217. /package/{src → dist}/view/documentselection.d.ts +0 -0
  218. /package/{src → dist}/view/domconverter.d.ts +0 -0
  219. /package/{src → dist}/view/editableelement.d.ts +0 -0
  220. /package/{src → dist}/view/elementdefinition.d.ts +0 -0
  221. /package/{src → dist}/view/emptyelement.d.ts +0 -0
  222. /package/{src → dist}/view/filler.d.ts +0 -0
  223. /package/{src → dist}/view/item.d.ts +0 -0
  224. /package/{src → dist}/view/node.d.ts +0 -0
  225. /package/{src → dist}/view/observer/arrowkeysobserver.d.ts +0 -0
  226. /package/{src → dist}/view/observer/bubblingemittermixin.d.ts +0 -0
  227. /package/{src → dist}/view/observer/bubblingeventinfo.d.ts +0 -0
  228. /package/{src → dist}/view/observer/clickobserver.d.ts +0 -0
  229. /package/{src → dist}/view/observer/compositionobserver.d.ts +0 -0
  230. /package/{src → dist}/view/observer/domeventdata.d.ts +0 -0
  231. /package/{src → dist}/view/observer/domeventobserver.d.ts +0 -0
  232. /package/{src → dist}/view/observer/fakeselectionobserver.d.ts +0 -0
  233. /package/{src → dist}/view/observer/focusobserver.d.ts +0 -0
  234. /package/{src → dist}/view/observer/inputobserver.d.ts +0 -0
  235. /package/{src → dist}/view/observer/keyobserver.d.ts +0 -0
  236. /package/{src → dist}/view/observer/mouseobserver.d.ts +0 -0
  237. /package/{src → dist}/view/observer/mutationobserver.d.ts +0 -0
  238. /package/{src → dist}/view/observer/observer.d.ts +0 -0
  239. /package/{src → dist}/view/observer/pointerobserver.d.ts +0 -0
  240. /package/{src → dist}/view/observer/selectionobserver.d.ts +0 -0
  241. /package/{src → dist}/view/observer/tabobserver.d.ts +0 -0
  242. /package/{src → dist}/view/observer/touchobserver.d.ts +0 -0
  243. /package/{src → dist}/view/placeholder.d.ts +0 -0
  244. /package/{src → dist}/view/position.d.ts +0 -0
  245. /package/{src → dist}/view/range.d.ts +0 -0
  246. /package/{src → dist}/view/rawelement.d.ts +0 -0
  247. /package/{src → dist}/view/renderer.d.ts +0 -0
  248. /package/{src → dist}/view/rooteditableelement.d.ts +0 -0
  249. /package/{src → dist}/view/selection.d.ts +0 -0
  250. /package/{src → dist}/view/stylesmap.d.ts +0 -0
  251. /package/{src → dist}/view/text.d.ts +0 -0
  252. /package/{src → dist}/view/textproxy.d.ts +0 -0
  253. /package/{src → dist}/view/tokenlist.d.ts +0 -0
  254. /package/{src → dist}/view/treewalker.d.ts +0 -0
  255. /package/{src → dist}/view/typecheckable.d.ts +0 -0
  256. /package/{src → dist}/view/uielement.d.ts +0 -0
  257. /package/{src → dist}/view/upcastwriter.d.ts +0 -0
  258. /package/{src → dist}/view/view.d.ts +0 -0
@@ -1,1288 +0,0 @@
1
- /**
2
- * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
3
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4
- */
5
- /**
6
- * @module engine/model/differ
7
- */
8
- import { ModelPosition } from './position.js';
9
- import { ModelRange } from './range.js';
10
- /**
11
- * Calculates the difference between two model states.
12
- *
13
- * Receives operations that are to be applied on the model document. Marks parts of the model document tree which
14
- * are changed and saves the state of these elements before the change. Then, it compares saved elements with the
15
- * changed elements, after all changes are applied on the model document. Calculates the diff between saved
16
- * elements and new ones and returns a change set.
17
- */
18
- export class Differ {
19
- /**
20
- * Priority of the {@link ~Differ#_elementState element states}. States on higher indexes of the array can overwrite states on the lower
21
- * indexes.
22
- */
23
- static _statesPriority = [undefined, 'refresh', 'rename', 'move'];
24
- /**
25
- * Reference to the model's marker collection.
26
- */
27
- _markerCollection;
28
- /**
29
- * A map that stores changes that happened in a given element.
30
- *
31
- * The keys of the map are references to the model elements.
32
- * The values of the map are arrays with changes that were done on this element.
33
- */
34
- _changesInElement = new Map();
35
- /**
36
- * Stores a snapshot for these model nodes that might have changed.
37
- *
38
- * This complements {@link ~Differ#_elementChildrenSnapshots `_elementChildrenSnapshots`}.
39
- *
40
- * See also {@link ~DifferSnapshot}.
41
- */
42
- _elementsSnapshots = new Map();
43
- /**
44
- * For each element or document fragment inside which there was a change, it stores a snapshot of the child nodes list (an array
45
- * of children snapshots that represent the state in the element / fragment before any change has happened).
46
- *
47
- * This complements {@link ~Differ#_elementsSnapshots `_elementsSnapshots`}.
48
- *
49
- * See also {@link ~DifferSnapshot}.
50
- */
51
- _elementChildrenSnapshots = new Map();
52
- /**
53
- * Keeps the state for a given element, describing how the element was changed so far. It is used to evaluate the `action` property
54
- * of diff items returned by {@link ~Differ#getChanges}.
55
- *
56
- * Possible values, in the order from the lowest priority to the highest priority:
57
- *
58
- * * `'refresh'` - element was refreshed,
59
- * * `'rename'` - element was renamed,
60
- * * `'move'` - element was moved (or, usually, removed, that is moved to the graveyard).
61
- *
62
- * Element that was refreshed, may change its state to `'rename'` if it was later renamed, or to `'move'` if it was removed.
63
- * But the element cannot change its state from `'move'` to `'rename'`, or from `'rename'` to `'refresh'`.
64
- *
65
- * Only already existing elements are registered in `_elementState`. If a new element was inserted as a result of a buffered operation,
66
- * it is not be registered in `_elementState`.
67
- */
68
- _elementState = new Map();
69
- /**
70
- * A map that stores all changed markers.
71
- *
72
- * The keys of the map are marker names.
73
- *
74
- * The values of the map are objects with the following properties:
75
- *
76
- * * `oldMarkerData`,
77
- * * `newMarkerData`.
78
- */
79
- _changedMarkers = new Map();
80
- /**
81
- * A map that stores all roots that have been changed.
82
- *
83
- * The keys are the names of the roots while value represents the changes.
84
- */
85
- _changedRoots = new Map();
86
- /**
87
- * Stores the number of changes that were processed. Used to order the changes chronologically. It is important
88
- * when changes are sorted.
89
- */
90
- _changeCount = 0;
91
- /**
92
- * For efficiency purposes, `Differ` stores the change set returned by the differ after {@link #getChanges} call.
93
- * Cache is reset each time a new operation is buffered. If the cache has not been reset, {@link #getChanges} will
94
- * return the cached value instead of calculating it again.
95
- *
96
- * This property stores those changes that did not take place in graveyard root.
97
- */
98
- _cachedChanges = null;
99
- /**
100
- * For efficiency purposes, `Differ` stores the change set returned by the differ after the {@link #getChanges} call.
101
- * The cache is reset each time a new operation is buffered. If the cache has not been reset, {@link #getChanges} will
102
- * return the cached value instead of calculating it again.
103
- *
104
- * This property stores all changes evaluated by `Differ`, including those that took place in the graveyard.
105
- */
106
- _cachedChangesWithGraveyard = null;
107
- /**
108
- * Set of model items that were marked to get refreshed in {@link #_refreshItem}.
109
- */
110
- _refreshedItems = new Set();
111
- /**
112
- * Creates a `Differ` instance.
113
- *
114
- * @param markerCollection Model's marker collection.
115
- */
116
- constructor(markerCollection) {
117
- this._markerCollection = markerCollection;
118
- }
119
- /**
120
- * Informs whether there are any changes buffered in `Differ`.
121
- */
122
- get isEmpty() {
123
- return this._changesInElement.size == 0 && this._changedMarkers.size == 0 && this._changedRoots.size == 0;
124
- }
125
- /**
126
- * Buffers the given operation. **An operation has to be buffered before it is executed.**
127
- *
128
- * @param operationToBuffer An operation to buffer.
129
- */
130
- bufferOperation(operationToBuffer) {
131
- // Below we take an operation, check its type, then use its parameters in marking (private) methods.
132
- // The general rule is to not mark elements inside inserted element. All inserted elements are re-rendered.
133
- // Marking changes in them would cause a "double" changing then.
134
- //
135
- const operation = operationToBuffer;
136
- // Note: an operation that happens inside a non-loaded root will be ignored. If the operation happens partially inside
137
- // a non-loaded root, that part will be ignored (this may happen for move or marker operations).
138
- //
139
- switch (operation.type) {
140
- case 'insert': {
141
- if (this._isInInsertedElement(operation.position.parent)) {
142
- return;
143
- }
144
- this._markInsert(operation.position.parent, operation.position.offset, operation.nodes.maxOffset);
145
- break;
146
- }
147
- case 'addAttribute':
148
- case 'removeAttribute':
149
- case 'changeAttribute': {
150
- for (const item of operation.range.getItems({ shallow: true })) {
151
- if (this._isInInsertedElement(item.parent)) {
152
- continue;
153
- }
154
- this._markAttribute(item);
155
- }
156
- break;
157
- }
158
- case 'remove':
159
- case 'move':
160
- case 'reinsert': {
161
- // When range is moved to the same position then not mark it as a change.
162
- // See: https://github.com/ckeditor/ckeditor5-engine/issues/1664.
163
- if (operation.sourcePosition.isEqual(operation.targetPosition) ||
164
- operation.sourcePosition.getShiftedBy(operation.howMany).isEqual(operation.targetPosition)) {
165
- return;
166
- }
167
- const sourceParentInserted = this._isInInsertedElement(operation.sourcePosition.parent);
168
- const targetParentInserted = this._isInInsertedElement(operation.targetPosition.parent);
169
- if (!sourceParentInserted) {
170
- this._markRemove(operation.sourcePosition.parent, operation.sourcePosition.offset, operation.howMany);
171
- }
172
- if (!targetParentInserted) {
173
- this._markInsert(operation.targetPosition.parent, operation.getMovedRangeStart().offset, operation.howMany);
174
- }
175
- // Remember -- operation is buffered before it is executed. So, it was not executed yet.
176
- const range = ModelRange._createFromPositionAndShift(operation.sourcePosition, operation.howMany);
177
- for (const node of range.getItems({ shallow: true })) {
178
- this._setElementState(node, 'move');
179
- }
180
- break;
181
- }
182
- case 'rename': {
183
- if (this._isInInsertedElement(operation.position.parent)) {
184
- return;
185
- }
186
- this._markRemove(operation.position.parent, operation.position.offset, 1);
187
- this._markInsert(operation.position.parent, operation.position.offset, 1);
188
- const range = ModelRange._createFromPositionAndShift(operation.position, 1);
189
- for (const marker of this._markerCollection.getMarkersIntersectingRange(range)) {
190
- const markerData = marker.getData();
191
- this.bufferMarkerChange(marker.name, markerData, markerData);
192
- }
193
- this._setElementState(operation.position.nodeAfter, 'rename');
194
- break;
195
- }
196
- case 'split': {
197
- const splitElement = operation.splitPosition.parent;
198
- // Mark that children of the split element were removed.
199
- if (!this._isInInsertedElement(splitElement)) {
200
- this._markRemove(splitElement, operation.splitPosition.offset, operation.howMany);
201
- // Remember -- operation is buffered before it is executed. So, it was not executed yet.
202
- const range = ModelRange._createFromPositionAndShift(operation.splitPosition, operation.howMany);
203
- for (const node of range.getItems({ shallow: true })) {
204
- this._setElementState(node, 'move');
205
- }
206
- }
207
- // Mark that the new element (split copy) was inserted.
208
- if (!this._isInInsertedElement(operation.insertionPosition.parent)) {
209
- this._markInsert(operation.insertionPosition.parent, operation.insertionPosition.offset, 1);
210
- }
211
- // If the split took the element from the graveyard, mark that the element from the graveyard was removed.
212
- if (operation.graveyardPosition) {
213
- this._markRemove(operation.graveyardPosition.parent, operation.graveyardPosition.offset, 1);
214
- this._setElementState(operation.graveyardPosition.nodeAfter, 'move');
215
- }
216
- break;
217
- }
218
- case 'merge': {
219
- // Mark that the merged element was removed.
220
- const mergedElement = operation.sourcePosition.parent;
221
- if (!this._isInInsertedElement(mergedElement.parent)) {
222
- this._markRemove(mergedElement.parent, mergedElement.startOffset, 1);
223
- }
224
- // Mark that the merged element was inserted into graveyard.
225
- const graveyardParent = operation.graveyardPosition.parent;
226
- this._markInsert(graveyardParent, operation.graveyardPosition.offset, 1);
227
- this._setElementState(mergedElement, 'move');
228
- // Mark that children of merged element were inserted at new parent.
229
- const mergedIntoElement = operation.targetPosition.parent;
230
- if (!this._isInInsertedElement(mergedIntoElement)) {
231
- this._markInsert(mergedIntoElement, operation.targetPosition.offset, mergedElement.maxOffset);
232
- // Remember -- operation is buffered before it is executed. So, it was not executed yet.
233
- const range = ModelRange._createFromPositionAndShift(operation.sourcePosition, operation.howMany);
234
- for (const node of range.getItems({ shallow: true })) {
235
- this._setElementState(node, 'move');
236
- }
237
- }
238
- break;
239
- }
240
- case 'detachRoot':
241
- case 'addRoot': {
242
- const root = operation.affectedSelectable;
243
- if (!root._isLoaded) {
244
- return;
245
- }
246
- // Don't buffer if the root state does not change.
247
- if (root.isAttached() == operation.isAdd) {
248
- return;
249
- }
250
- this._bufferRootStateChange(operation.rootName, operation.isAdd);
251
- break;
252
- }
253
- case 'addRootAttribute':
254
- case 'removeRootAttribute':
255
- case 'changeRootAttribute': {
256
- if (!operation.root._isLoaded) {
257
- return;
258
- }
259
- const rootName = operation.root.rootName;
260
- this._bufferRootAttributeChange(rootName, operation.key, operation.oldValue, operation.newValue);
261
- break;
262
- }
263
- }
264
- // Clear cache after each buffered operation as it is no longer valid.
265
- this._cachedChanges = null;
266
- }
267
- /**
268
- * Buffers a marker change.
269
- *
270
- * @param markerName The name of the marker that changed.
271
- * @param oldMarkerData Marker data before the change.
272
- * @param newMarkerData Marker data after the change.
273
- */
274
- bufferMarkerChange(markerName, oldMarkerData, newMarkerData) {
275
- if (oldMarkerData.range && oldMarkerData.range.root.is('rootElement') && !oldMarkerData.range.root._isLoaded) {
276
- oldMarkerData.range = null;
277
- }
278
- if (newMarkerData.range && newMarkerData.range.root.is('rootElement') && !newMarkerData.range.root._isLoaded) {
279
- newMarkerData.range = null;
280
- }
281
- let buffered = this._changedMarkers.get(markerName);
282
- if (!buffered) {
283
- buffered = { newMarkerData, oldMarkerData };
284
- this._changedMarkers.set(markerName, buffered);
285
- }
286
- else {
287
- buffered.newMarkerData = newMarkerData;
288
- }
289
- if (buffered.oldMarkerData.range == null && newMarkerData.range == null) {
290
- // The marker is going to be removed (`newMarkerData.range == null`) but it did not exist before the first buffered change
291
- // (`buffered.oldMarkerData.range == null`). In this case, do not keep the marker in buffer at all.
292
- this._changedMarkers.delete(markerName);
293
- }
294
- }
295
- /**
296
- * Returns all markers that should be removed as a result of buffered changes.
297
- *
298
- * @returns Markers to remove. Each array item is an object containing the `name` and `range` properties.
299
- */
300
- getMarkersToRemove() {
301
- const result = [];
302
- for (const [name, change] of this._changedMarkers) {
303
- if (change.oldMarkerData.range != null) {
304
- result.push({ name, range: change.oldMarkerData.range });
305
- }
306
- }
307
- return result;
308
- }
309
- /**
310
- * Returns all markers which should be added as a result of buffered changes.
311
- *
312
- * @returns Markers to add. Each array item is an object containing the `name` and `range` properties.
313
- */
314
- getMarkersToAdd() {
315
- const result = [];
316
- for (const [name, change] of this._changedMarkers) {
317
- if (change.newMarkerData.range != null) {
318
- result.push({ name, range: change.newMarkerData.range });
319
- }
320
- }
321
- return result;
322
- }
323
- /**
324
- * Returns all markers which changed.
325
- */
326
- getChangedMarkers() {
327
- return Array.from(this._changedMarkers).map(([name, change]) => ({
328
- name,
329
- data: {
330
- oldRange: change.oldMarkerData.range,
331
- newRange: change.newMarkerData.range
332
- }
333
- }));
334
- }
335
- /**
336
- * Checks whether some of the buffered changes affect the editor data.
337
- *
338
- * Types of changes which affect the editor data:
339
- *
340
- * * model structure changes,
341
- * * attribute changes,
342
- * * a root is added or detached,
343
- * * changes of markers which were defined as `affectsData`,
344
- * * changes of markers' `affectsData` property.
345
- */
346
- hasDataChanges() {
347
- if (this.getChanges().length) {
348
- return true;
349
- }
350
- if (this._changedRoots.size > 0) {
351
- return true;
352
- }
353
- for (const { newMarkerData, oldMarkerData } of this._changedMarkers.values()) {
354
- if (newMarkerData.affectsData !== oldMarkerData.affectsData) {
355
- return true;
356
- }
357
- if (newMarkerData.affectsData) {
358
- const markerAdded = newMarkerData.range && !oldMarkerData.range;
359
- const markerRemoved = !newMarkerData.range && oldMarkerData.range;
360
- const markerChanged = newMarkerData.range && oldMarkerData.range && !newMarkerData.range.isEqual(oldMarkerData.range);
361
- if (markerAdded || markerRemoved || markerChanged) {
362
- return true;
363
- }
364
- }
365
- }
366
- return false;
367
- }
368
- /**
369
- * Calculates the diff between the old model tree state (the state before the first buffered operations since the last {@link #reset}
370
- * call) and the new model tree state (actual one). It should be called after all buffered operations are executed.
371
- *
372
- * The diff set is returned as an array of {@link module:engine/model/differ~DifferItem diff items}, each describing a change done
373
- * on the model. The items are sorted by the position on which the change happened. If a position
374
- * {@link module:engine/model/position~ModelPosition#isBefore is before} another one, it will be on an earlier index in the diff set.
375
- *
376
- * **Note**: Elements inside inserted element will not have a separate diff item, only the top most element change will be reported.
377
- *
378
- * Because calculating the diff is a costly operation, the result is cached. If no new operation was buffered since the
379
- * previous {@link #getChanges} call, the next call will return the cached value.
380
- *
381
- * @param options Additional options.
382
- * @param options.includeChangesInGraveyard If set to `true`, also changes that happened
383
- * in the graveyard root will be returned. By default, changes in the graveyard root are not returned.
384
- * @returns Diff between the old and the new model tree state.
385
- */
386
- getChanges(options = {}) {
387
- // If there are cached changes, just return them instead of calculating changes again.
388
- if (this._cachedChanges) {
389
- if (options.includeChangesInGraveyard) {
390
- return this._cachedChangesWithGraveyard.slice();
391
- }
392
- else {
393
- return this._cachedChanges.slice();
394
- }
395
- }
396
- // Will contain returned results.
397
- let diffSet = [];
398
- // Check all changed elements/roots.
399
- for (const element of this._changesInElement.keys()) {
400
- // Get changes inside this element/root and sort them.
401
- const changes = this._changesInElement.get(element).sort((a, b) => {
402
- if (a.offset === b.offset) {
403
- if (a.type != b.type) {
404
- // If there are multiple changes at the same position, "remove" change should be first.
405
- // If the order is different, for example, we would first add some nodes and then removed them
406
- // (instead of the nodes that we should remove).
407
- return a.type == 'remove' ? -1 : 1;
408
- }
409
- return 0;
410
- }
411
- return a.offset < b.offset ? -1 : 1;
412
- });
413
- // Get children of this element before any change was applied on it.
414
- const childrenBefore = this._elementChildrenSnapshots.get(element);
415
- // Get snapshot of current element's children.
416
- const childrenAfter = _getChildrenSnapshots(element.getChildren());
417
- // Generate diff instructions based on changes done in the element/root.
418
- const diffInstructions = _generateDiffInstructionsFromChanges(childrenBefore.length, changes);
419
- let i = 0; // Iterator in `childrenAfter` array -- iterates through current children of element.
420
- let j = 0; // Iterator in `childrenBefore` array -- iterates through old children of element.
421
- // Process every action.
422
- for (const instruction of diffInstructions) {
423
- if (instruction === 'i') {
424
- const action = this._getDiffActionForNode(childrenAfter[i].node, 'insert');
425
- const childSnapshotBefore = this._elementsSnapshots.get(childrenAfter[i].node);
426
- const diffItem = this._getInsertDiff(element, i, action, childrenAfter[i], childSnapshotBefore);
427
- diffSet.push(diffItem);
428
- i++;
429
- }
430
- else if (instruction === 'r') {
431
- const action = this._getDiffActionForNode(childrenBefore[j].node, 'remove');
432
- const diffItem = this._getRemoveDiff(element, i, action, childrenBefore[j]);
433
- diffSet.push(diffItem);
434
- j++;
435
- }
436
- else if (instruction === 'a') {
437
- // Take attributes from saved and current children.
438
- const beforeAttributes = childrenBefore[j].attributes;
439
- const afterAttributes = childrenAfter[i].attributes;
440
- let range;
441
- if (childrenAfter[i].name == '$text') {
442
- range = new ModelRange(ModelPosition._createAt(element, i), ModelPosition._createAt(element, i + 1));
443
- }
444
- else {
445
- const index = element.offsetToIndex(i);
446
- range = new ModelRange(ModelPosition._createAt(element, i), ModelPosition._createAt(element.getChild(index), 0));
447
- }
448
- // Generate diff items for this change (there might be multiple attributes changed and
449
- // there is a single diff for each of them) and insert them into the diff set.
450
- const diffItems = this._getAttributesDiff(range, beforeAttributes, afterAttributes);
451
- diffSet.push(...diffItems);
452
- i++;
453
- j++;
454
- }
455
- else {
456
- // `action` is 'equal'. Child not changed.
457
- i++;
458
- j++;
459
- }
460
- }
461
- }
462
- // Then, sort the changes by the position (change at position before other changes is first).
463
- diffSet.sort((a, b) => {
464
- // If the change is in different root, we don't care much, but we'd like to have all changes in given
465
- // root "together" in the array. So let's just sort them by the root name. It does not matter which root
466
- // will be processed first.
467
- if (a.position.root != b.position.root) {
468
- return a.position.root.rootName < b.position.root.rootName ? -1 : 1;
469
- }
470
- // If change happens at the same position...
471
- if (a.position.isEqual(b.position)) {
472
- // Keep chronological order of operations.
473
- return a.changeCount - b.changeCount;
474
- }
475
- // If positions differ, position "on the left" should be earlier in the result.
476
- return a.position.isBefore(b.position) ? -1 : 1;
477
- });
478
- // Glue together multiple changes (mostly on text nodes).
479
- for (let i = 1, prevIndex = 0; i < diffSet.length; i++) {
480
- const prevDiff = diffSet[prevIndex];
481
- const thisDiff = diffSet[i];
482
- // Glue remove changes if they happen on text on same position.
483
- const isConsecutiveTextRemove = prevDiff.type == 'remove' && thisDiff.type == 'remove' &&
484
- prevDiff.name == '$text' && thisDiff.name == '$text' &&
485
- prevDiff.position.isEqual(thisDiff.position);
486
- // Glue insert changes if they happen on text on consecutive fragments.
487
- const isConsecutiveTextAdd = prevDiff.type == 'insert' && thisDiff.type == 'insert' &&
488
- prevDiff.name == '$text' && thisDiff.name == '$text' &&
489
- prevDiff.position.parent == thisDiff.position.parent &&
490
- prevDiff.position.offset + prevDiff.length == thisDiff.position.offset;
491
- // Glue attribute changes if they happen on consecutive fragments and have same key, old value and new value.
492
- const isConsecutiveAttributeChange = prevDiff.type == 'attribute' && thisDiff.type == 'attribute' &&
493
- prevDiff.position.parent == thisDiff.position.parent &&
494
- prevDiff.range.isFlat && thisDiff.range.isFlat &&
495
- (prevDiff.position.offset + prevDiff.length) == thisDiff.position.offset &&
496
- prevDiff.attributeKey == thisDiff.attributeKey &&
497
- prevDiff.attributeOldValue == thisDiff.attributeOldValue &&
498
- prevDiff.attributeNewValue == thisDiff.attributeNewValue;
499
- if (isConsecutiveTextRemove || isConsecutiveTextAdd || isConsecutiveAttributeChange) {
500
- prevDiff.length++;
501
- if (isConsecutiveAttributeChange) {
502
- prevDiff.range.end = prevDiff.range.end.getShiftedBy(1);
503
- }
504
- diffSet[i] = null;
505
- }
506
- else {
507
- prevIndex = i;
508
- }
509
- }
510
- diffSet = diffSet.filter(v => v);
511
- // Remove `changeCount` property from diff items. It is used only for sorting and is internal thing.
512
- for (const item of diffSet) {
513
- delete item.changeCount;
514
- if (item.type == 'attribute') {
515
- delete item.position;
516
- delete item.length;
517
- }
518
- }
519
- this._changeCount = 0;
520
- // Cache changes.
521
- this._cachedChangesWithGraveyard = diffSet;
522
- this._cachedChanges = diffSet.filter(_changesInGraveyardFilter);
523
- if (options.includeChangesInGraveyard) {
524
- return this._cachedChangesWithGraveyard.slice();
525
- }
526
- else {
527
- return this._cachedChanges.slice();
528
- }
529
- }
530
- /**
531
- * Returns all roots that have changed (either were attached, or detached, or their attributes changed).
532
- *
533
- * @returns Diff between the old and the new roots state.
534
- */
535
- getChangedRoots() {
536
- return Array.from(this._changedRoots.values()).map(diffItem => {
537
- const entry = { ...diffItem };
538
- if (entry.state !== undefined) {
539
- // The root was attached or detached -- do not return its attributes changes.
540
- // If the root was attached, it should be handled as a whole, together with its attributes, the same way as model nodes.
541
- // If the root was detached, its attributes should be discarded anyway.
542
- //
543
- // Keep in mind that filtering must happen on this stage (when retrieving changes). If filtering happens on-the-fly as
544
- // the attributes change, it may lead to incorrect situation, e.g.: detach root, change attribute, re-attach root.
545
- // In this case, attribute change cannot be filtered. After the root is re-attached, the attribute change must be kept.
546
- delete entry.attributes;
547
- }
548
- return entry;
549
- });
550
- }
551
- /**
552
- * Returns a set of model items that were marked to get refreshed.
553
- */
554
- getRefreshedItems() {
555
- return new Set(this._refreshedItems);
556
- }
557
- /**
558
- * Resets `Differ`. Removes all buffered changes.
559
- */
560
- reset() {
561
- this._changesInElement.clear();
562
- this._elementChildrenSnapshots.clear();
563
- this._elementsSnapshots.clear();
564
- this._elementState.clear();
565
- this._changedMarkers.clear();
566
- this._changedRoots.clear();
567
- this._refreshedItems.clear();
568
- this._cachedChanges = null;
569
- }
570
- /**
571
- * Marks the given `item` in differ to be "refreshed". It means that the item will be marked as removed and inserted
572
- * in the differ changes set, so it will be effectively re-converted when the differ changes are handled by a dispatcher.
573
- *
574
- * @internal
575
- * @param item Item to refresh.
576
- */
577
- _refreshItem(item) {
578
- if (this._isInInsertedElement(item.parent)) {
579
- return;
580
- }
581
- this._markRemove(item.parent, item.startOffset, item.offsetSize);
582
- this._markInsert(item.parent, item.startOffset, item.offsetSize);
583
- this._refreshedItems.add(item);
584
- this._setElementState(item, 'refresh');
585
- const range = ModelRange._createOn(item);
586
- for (const marker of this._markerCollection.getMarkersIntersectingRange(range)) {
587
- const markerData = marker.getData();
588
- this.bufferMarkerChange(marker.name, markerData, markerData);
589
- }
590
- // Clear cache after each buffered operation as it is no longer valid.
591
- this._cachedChanges = null;
592
- }
593
- /**
594
- * Buffers all the data related to given root like it was all just added to the editor.
595
- *
596
- * Following changes are buffered:
597
- *
598
- * * root is attached,
599
- * * all root content is inserted,
600
- * * all root attributes are added,
601
- * * all markers inside the root are added.
602
- *
603
- * @internal
604
- */
605
- _bufferRootLoad(root) {
606
- if (!root.isAttached()) {
607
- return;
608
- }
609
- this._bufferRootStateChange(root.rootName, true);
610
- this._markInsert(root, 0, root.maxOffset);
611
- // Buffering root attribute changes makes sense and is actually needed, even though we buffer root state change above.
612
- // Because the root state change is buffered, the root attributes changes are not returned by the differ.
613
- // But, if the root attribute is removed in the same change block, or the root is detached, then the differ results would be wrong.
614
- //
615
- for (const key of root.getAttributeKeys()) {
616
- this._bufferRootAttributeChange(root.rootName, key, null, root.getAttribute(key));
617
- }
618
- for (const marker of this._markerCollection) {
619
- if (marker.getRange().root == root) {
620
- const markerData = marker.getData();
621
- this.bufferMarkerChange(marker.name, { ...markerData, range: null }, markerData);
622
- }
623
- }
624
- }
625
- /**
626
- * Buffers the root state change after the root was attached or detached
627
- */
628
- _bufferRootStateChange(rootName, isAttached) {
629
- if (!this._changedRoots.has(rootName)) {
630
- this._changedRoots.set(rootName, { name: rootName, state: isAttached ? 'attached' : 'detached' });
631
- return;
632
- }
633
- const diffItem = this._changedRoots.get(rootName);
634
- if (diffItem.state !== undefined) {
635
- // Root `state` can only toggle between one of the values and no value. It cannot be any other way,
636
- // because if the root was originally attached it can only become detached. Then, if it is re-attached in the same batch of
637
- // changes, it gets back to "no change" (which means no value). Same if the root was originally detached.
638
- delete diffItem.state;
639
- if (diffItem.attributes === undefined) {
640
- // If there is no `state` change and no `attributes` change, remove the entry.
641
- this._changedRoots.delete(rootName);
642
- }
643
- }
644
- else {
645
- diffItem.state = isAttached ? 'attached' : 'detached';
646
- }
647
- }
648
- /**
649
- * Buffers a root attribute change.
650
- */
651
- _bufferRootAttributeChange(rootName, key, oldValue, newValue) {
652
- const diffItem = this._changedRoots.get(rootName) || { name: rootName };
653
- const attrs = diffItem.attributes || {};
654
- if (attrs[key]) {
655
- // If this attribute or metadata was already changed earlier and is changed again, check to what value it is changed.
656
- const attrEntry = attrs[key];
657
- if (newValue === attrEntry.oldValue) {
658
- // If it was changed back to the old value, remove the entry.
659
- delete attrs[key];
660
- }
661
- else {
662
- // If it was changed to a different value, update the entry.
663
- attrEntry.newValue = newValue;
664
- }
665
- }
666
- else {
667
- // If this attribute or metadata was not set earlier, add an entry.
668
- attrs[key] = { oldValue, newValue };
669
- }
670
- if (Object.entries(attrs).length === 0) {
671
- // If attributes or metadata changes set became empty, remove it from the diff item.
672
- delete diffItem.attributes;
673
- if (diffItem.state === undefined) {
674
- // If there is no `state` change and no `attributes` change, remove the entry.
675
- this._changedRoots.delete(rootName);
676
- }
677
- }
678
- else {
679
- // Make sure that, if a new object in the structure was created, it gets set.
680
- diffItem.attributes = attrs;
681
- this._changedRoots.set(rootName, diffItem);
682
- }
683
- }
684
- /**
685
- * Saves and handles an insert change.
686
- */
687
- _markInsert(parent, offset, howMany) {
688
- if (parent.root.is('rootElement') && !parent.root._isLoaded) {
689
- return;
690
- }
691
- const changeItem = { type: 'insert', offset, howMany, count: this._changeCount++ };
692
- this._markChange(parent, changeItem);
693
- }
694
- /**
695
- * Saves and handles a remove change.
696
- */
697
- _markRemove(parent, offset, howMany) {
698
- if (parent.root.is('rootElement') && !parent.root._isLoaded) {
699
- return;
700
- }
701
- const changeItem = { type: 'remove', offset, howMany, count: this._changeCount++ };
702
- this._markChange(parent, changeItem);
703
- this._removeAllNestedChanges(parent, offset, howMany);
704
- }
705
- /**
706
- * Saves and handles an attribute change.
707
- */
708
- _markAttribute(item) {
709
- if (item.root.is('rootElement') && !item.root._isLoaded) {
710
- return;
711
- }
712
- const changeItem = { type: 'attribute', offset: item.startOffset, howMany: item.offsetSize, count: this._changeCount++ };
713
- this._markChange(item.parent, changeItem);
714
- }
715
- /**
716
- * Saves and handles a model change.
717
- */
718
- _markChange(parent, changeItem) {
719
- // First, make a snapshot of the parent and its children (it will be made only if it was not made before).
720
- this._makeSnapshots(parent);
721
- // Then, get all changes that already were done on the element (empty array if this is the first change).
722
- const changes = this._getChangesForElement(parent);
723
- // Then, look through all the changes, and transform them or the new change.
724
- this._handleChange(changeItem, changes);
725
- // Add the new change.
726
- changes.push(changeItem);
727
- // Remove incorrect changes. During transformation some change might be, for example, included in another.
728
- // In that case, the change will have `howMany` property set to `0` or less. We need to remove those changes.
729
- for (let i = 0; i < changes.length; i++) {
730
- if (changes[i].howMany < 1) {
731
- changes.splice(i, 1);
732
- i--;
733
- }
734
- }
735
- }
736
- /**
737
- * Tries to set given state for given item.
738
- *
739
- * This method does simple validation (it sets the state only for model elements, not for text proxy nodes). It also follows state
740
- * setting rules, that is, `'refresh'` cannot overwrite `'rename'`, and `'rename'` cannot overwrite `'move'`.
741
- */
742
- _setElementState(node, state) {
743
- if (!node.is('element')) {
744
- return;
745
- }
746
- const currentStatePriority = Differ._statesPriority.indexOf(this._elementState.get(node));
747
- const newStatePriority = Differ._statesPriority.indexOf(state);
748
- if (newStatePriority > currentStatePriority) {
749
- this._elementState.set(node, state);
750
- }
751
- }
752
- /**
753
- * Returns a value for {@link ~DifferItemAction `action`} property for diff items returned by {@link ~Differ#getChanges}.
754
- * This method aims to return `'rename'` or `'refresh'` when it should, and `diffItemType` ("default action") in all other cases.
755
- *
756
- * It bases on a few factors:
757
- *
758
- * * for text nodes, the method always returns `diffItemType`,
759
- * * for newly inserted element, the method returns `diffItemType`,
760
- * * if {@link ~Differ#_elementState element state} was not recorded, the method returns `diffItemType`,
761
- * * if state was recorded, and it was `'move'` (default action), the method returns `diffItemType`,
762
- * * finally, if state was `'refresh'` or `'rename'`, the method returns the state value.
763
- */
764
- _getDiffActionForNode(node, diffItemType) {
765
- if (!node.is('element')) {
766
- // Text node.
767
- return diffItemType;
768
- }
769
- if (!this._elementsSnapshots.has(node)) {
770
- // Newly inserted element.
771
- return diffItemType;
772
- }
773
- const state = this._elementState.get(node);
774
- if (!state || state == 'move') {
775
- return diffItemType;
776
- }
777
- return state;
778
- }
779
- /**
780
- * Gets an array of changes that have already been saved for a given element.
781
- */
782
- _getChangesForElement(element) {
783
- let changes;
784
- if (this._changesInElement.has(element)) {
785
- changes = this._changesInElement.get(element);
786
- }
787
- else {
788
- changes = [];
789
- this._changesInElement.set(element, changes);
790
- }
791
- return changes;
792
- }
793
- /**
794
- * Creates and saves a snapshot for all children of the given element.
795
- */
796
- _makeSnapshots(element) {
797
- if (this._elementChildrenSnapshots.has(element)) {
798
- return;
799
- }
800
- const childrenSnapshots = _getChildrenSnapshots(element.getChildren());
801
- this._elementChildrenSnapshots.set(element, childrenSnapshots);
802
- for (const snapshot of childrenSnapshots) {
803
- this._elementsSnapshots.set(snapshot.node, snapshot);
804
- }
805
- }
806
- /**
807
- * For a given newly saved change, compares it with a change already done on the element and modifies the incoming
808
- * change and/or the old change.
809
- *
810
- * @param inc Incoming (new) change.
811
- * @param changes An array containing all the changes done on that element.
812
- */
813
- _handleChange(inc, changes) {
814
- // We need a helper variable that will store how many nodes are to be still handled for this change item.
815
- // `nodesToHandle` (how many nodes still need to be handled) and `howMany` (how many nodes were affected)
816
- // needs to be differentiated.
817
- //
818
- // This comes up when there are multiple changes that are affected by `inc` change item.
819
- //
820
- // For example: assume two insert changes: `{ offset: 2, howMany: 1 }` and `{ offset: 5, howMany: 1 }`.
821
- // Assume that `inc` change is remove `{ offset: 2, howMany: 2, nodesToHandle: 2 }`.
822
- //
823
- // Then, we:
824
- // - "forget" about first insert change (it is "eaten" by remove),
825
- // - because of that, at the end we will want to remove only one node (`nodesToHandle = 1`),
826
- // - but still we have to change offset of the second insert change from `5` to `3`!
827
- //
828
- // So, `howMany` does not change throughout items transformation and keeps information about how many nodes were affected,
829
- // while `nodesToHandle` means how many nodes need to be handled after the change item is transformed by other changes.
830
- inc.nodesToHandle = inc.howMany;
831
- for (const old of changes) {
832
- const incEnd = inc.offset + inc.howMany;
833
- const oldEnd = old.offset + old.howMany;
834
- if (inc.type == 'insert') {
835
- if (old.type == 'insert') {
836
- if (inc.offset <= old.offset) {
837
- old.offset += inc.howMany;
838
- }
839
- else if (inc.offset < oldEnd) {
840
- old.howMany += inc.nodesToHandle;
841
- inc.nodesToHandle = 0;
842
- }
843
- }
844
- if (old.type == 'remove') {
845
- if (inc.offset < old.offset) {
846
- old.offset += inc.howMany;
847
- }
848
- }
849
- if (old.type == 'attribute') {
850
- if (inc.offset <= old.offset) {
851
- old.offset += inc.howMany;
852
- }
853
- else if (inc.offset < oldEnd) {
854
- // This case is more complicated, because attribute change has to be split into two.
855
- // Example (assume that uppercase and lowercase letters mean different attributes):
856
- //
857
- // initial state: abcxyz
858
- // attribute change: aBCXYz
859
- // incoming insert: aBCfooXYz
860
- //
861
- // Change ranges cannot intersect because each item has to be described exactly (it was either
862
- // not changed, inserted, removed, or its attribute was changed). That's why old attribute
863
- // change has to be split and both parts has to be handled separately from now on.
864
- const howMany = old.howMany;
865
- old.howMany = inc.offset - old.offset;
866
- // Add the second part of attribute change to the beginning of processed array so it won't
867
- // be processed again in this loop.
868
- changes.unshift({
869
- type: 'attribute',
870
- offset: incEnd,
871
- howMany: howMany - old.howMany,
872
- count: this._changeCount++
873
- });
874
- }
875
- }
876
- }
877
- if (inc.type == 'remove') {
878
- if (old.type == 'insert') {
879
- if (incEnd <= old.offset) {
880
- old.offset -= inc.howMany;
881
- }
882
- else if (incEnd <= oldEnd) {
883
- if (inc.offset < old.offset) {
884
- const intersectionLength = incEnd - old.offset;
885
- old.offset = inc.offset;
886
- old.howMany -= intersectionLength;
887
- inc.nodesToHandle -= intersectionLength;
888
- }
889
- else {
890
- old.howMany -= inc.nodesToHandle;
891
- inc.nodesToHandle = 0;
892
- }
893
- }
894
- else {
895
- if (inc.offset <= old.offset) {
896
- inc.nodesToHandle -= old.howMany;
897
- old.howMany = 0;
898
- }
899
- else if (inc.offset < oldEnd) {
900
- const intersectionLength = oldEnd - inc.offset;
901
- old.howMany -= intersectionLength;
902
- inc.nodesToHandle -= intersectionLength;
903
- }
904
- }
905
- }
906
- if (old.type == 'remove') {
907
- if (incEnd <= old.offset) {
908
- old.offset -= inc.howMany;
909
- }
910
- else if (inc.offset < old.offset) {
911
- inc.nodesToHandle += old.howMany;
912
- old.howMany = 0;
913
- }
914
- }
915
- if (old.type == 'attribute') {
916
- if (incEnd <= old.offset) {
917
- old.offset -= inc.howMany;
918
- }
919
- else if (inc.offset < old.offset) {
920
- const intersectionLength = incEnd - old.offset;
921
- old.offset = inc.offset;
922
- old.howMany -= intersectionLength;
923
- }
924
- else if (inc.offset < oldEnd) {
925
- if (incEnd <= oldEnd) {
926
- // On first sight in this case we don't need to split attribute operation into two.
927
- // However the changes set is later converted to actions (see `_generateActionsFromChanges`).
928
- // For that reason, no two changes may intersect.
929
- // So we cannot have an attribute change that "contains" remove change.
930
- // Attribute change needs to be split.
931
- const howMany = old.howMany;
932
- old.howMany = inc.offset - old.offset;
933
- const howManyAfter = howMany - old.howMany - inc.nodesToHandle;
934
- // Add the second part of attribute change to the beginning of processed array so it won't
935
- // be processed again in this loop.
936
- changes.unshift({
937
- type: 'attribute',
938
- offset: inc.offset,
939
- howMany: howManyAfter,
940
- count: this._changeCount++
941
- });
942
- }
943
- else {
944
- old.howMany -= oldEnd - inc.offset;
945
- }
946
- }
947
- }
948
- }
949
- if (inc.type == 'attribute') {
950
- // In case of attribute change, `howMany` should be kept same as `nodesToHandle`. It's not an error.
951
- if (old.type == 'insert') {
952
- if (inc.offset < old.offset && incEnd > old.offset) {
953
- if (incEnd > oldEnd) {
954
- // This case is similar to a case described when incoming change was insert and old change was attribute.
955
- // See comment above.
956
- //
957
- // This time incoming change is attribute. We need to split incoming change in this case too.
958
- // However this time, the second part of the attribute change needs to be processed further
959
- // because there might be other changes that it collides with.
960
- const attributePart = {
961
- type: 'attribute',
962
- offset: oldEnd,
963
- howMany: incEnd - oldEnd,
964
- count: this._changeCount++
965
- };
966
- this._handleChange(attributePart, changes);
967
- changes.push(attributePart);
968
- }
969
- inc.nodesToHandle = old.offset - inc.offset;
970
- inc.howMany = inc.nodesToHandle;
971
- }
972
- else if (inc.offset >= old.offset && inc.offset < oldEnd) {
973
- if (incEnd > oldEnd) {
974
- inc.nodesToHandle = incEnd - oldEnd;
975
- inc.offset = oldEnd;
976
- }
977
- else {
978
- inc.nodesToHandle = 0;
979
- }
980
- }
981
- }
982
- if (old.type == 'remove') {
983
- // This is a case when attribute change "contains" remove change.
984
- // The attribute change needs to be split into two because changes cannot intersect.
985
- if (inc.offset < old.offset && incEnd > old.offset) {
986
- const attributePart = {
987
- type: 'attribute',
988
- offset: old.offset,
989
- howMany: incEnd - old.offset,
990
- count: this._changeCount++
991
- };
992
- this._handleChange(attributePart, changes);
993
- changes.push(attributePart);
994
- inc.nodesToHandle = old.offset - inc.offset;
995
- inc.howMany = inc.nodesToHandle;
996
- }
997
- }
998
- if (old.type == 'attribute') {
999
- // There are only two conflicting scenarios possible here:
1000
- if (inc.offset >= old.offset && incEnd <= oldEnd) {
1001
- // `old` change includes `inc` change, or they are the same.
1002
- inc.nodesToHandle = 0;
1003
- inc.howMany = 0;
1004
- inc.offset = 0;
1005
- }
1006
- else if (inc.offset <= old.offset && incEnd >= oldEnd) {
1007
- // `inc` change includes `old` change.
1008
- old.howMany = 0;
1009
- }
1010
- }
1011
- }
1012
- }
1013
- inc.howMany = inc.nodesToHandle;
1014
- delete inc.nodesToHandle;
1015
- }
1016
- /**
1017
- * Returns an object with a single insert change description.
1018
- *
1019
- * @param parent The element in which the change happened.
1020
- * @param offset The offset at which change happened.
1021
- * @param action Further specifies what kind of action led to generating this change.
1022
- * @param elementSnapshot Snapshot of the inserted node after changes.
1023
- * @param elementSnapshotBefore Snapshot of the inserted node before changes.
1024
- * @returns The diff item.
1025
- */
1026
- _getInsertDiff(parent, offset, action, elementSnapshot, elementSnapshotBefore) {
1027
- const diffItem = {
1028
- type: 'insert',
1029
- position: ModelPosition._createAt(parent, offset),
1030
- name: elementSnapshot.name,
1031
- attributes: new Map(elementSnapshot.attributes),
1032
- length: 1,
1033
- changeCount: this._changeCount++,
1034
- action
1035
- };
1036
- if (action != 'insert' && elementSnapshotBefore) {
1037
- diffItem.before = {
1038
- name: elementSnapshotBefore.name,
1039
- attributes: new Map(elementSnapshotBefore.attributes)
1040
- };
1041
- }
1042
- return diffItem;
1043
- }
1044
- /**
1045
- * Returns an object with a single remove change description.
1046
- *
1047
- * @param parent The element in which change happened.
1048
- * @param offset The offset at which change happened.
1049
- * @param action Further specifies what kind of action led to generating this change.
1050
- * @param elementSnapshot The snapshot of the removed node before changes.
1051
- * @returns The diff item.
1052
- */
1053
- _getRemoveDiff(parent, offset, action, elementSnapshot) {
1054
- return {
1055
- type: 'remove',
1056
- action,
1057
- position: ModelPosition._createAt(parent, offset),
1058
- name: elementSnapshot.name,
1059
- attributes: new Map(elementSnapshot.attributes),
1060
- length: 1,
1061
- changeCount: this._changeCount++
1062
- };
1063
- }
1064
- /**
1065
- * Returns an array of objects where each one is a single attribute change description.
1066
- *
1067
- * @param range The range where the change happened.
1068
- * @param oldAttributes A map, map iterator or compatible object that contains attributes before the change.
1069
- * @param newAttributes A map, map iterator or compatible object that contains attributes after the change.
1070
- * @returns An array containing one or more diff items.
1071
- */
1072
- _getAttributesDiff(range, oldAttributes, newAttributes) {
1073
- // Results holder.
1074
- const diffs = [];
1075
- // Clone new attributes as we will be performing changes on this object.
1076
- newAttributes = new Map(newAttributes);
1077
- // Look through old attributes.
1078
- for (const [key, oldValue] of oldAttributes) {
1079
- // Check what is the new value of the attribute (or if it was removed).
1080
- const newValue = newAttributes.has(key) ? newAttributes.get(key) : null;
1081
- // If values are different (or attribute was removed)...
1082
- if (newValue !== oldValue) {
1083
- // Add diff item.
1084
- diffs.push({
1085
- type: 'attribute',
1086
- position: range.start,
1087
- range: range.clone(),
1088
- length: 1,
1089
- attributeKey: key,
1090
- attributeOldValue: oldValue,
1091
- attributeNewValue: newValue,
1092
- changeCount: this._changeCount++
1093
- });
1094
- }
1095
- // Prevent returning two diff items for the same change.
1096
- newAttributes.delete(key);
1097
- }
1098
- // Look through new attributes that weren't handled above.
1099
- for (const [key, newValue] of newAttributes) {
1100
- // Each of them is a new attribute. Add diff item.
1101
- diffs.push({
1102
- type: 'attribute',
1103
- position: range.start,
1104
- range: range.clone(),
1105
- length: 1,
1106
- attributeKey: key,
1107
- attributeOldValue: null,
1108
- attributeNewValue: newValue,
1109
- changeCount: this._changeCount++
1110
- });
1111
- }
1112
- return diffs;
1113
- }
1114
- /**
1115
- * Checks whether given element or any of its parents is an element that is buffered as an inserted element.
1116
- */
1117
- _isInInsertedElement(element) {
1118
- const parent = element.parent;
1119
- if (!parent) {
1120
- return false;
1121
- }
1122
- const changes = this._changesInElement.get(parent);
1123
- const offset = element.startOffset;
1124
- if (changes) {
1125
- for (const change of changes) {
1126
- if (change.type == 'insert' && offset >= change.offset && offset < change.offset + change.howMany) {
1127
- return true;
1128
- }
1129
- }
1130
- }
1131
- return this._isInInsertedElement(parent);
1132
- }
1133
- /**
1134
- * Removes deeply all buffered changes that are registered in elements from range specified by `parent`, `offset`
1135
- * and `howMany`.
1136
- */
1137
- _removeAllNestedChanges(parent, offset, howMany) {
1138
- const range = new ModelRange(ModelPosition._createAt(parent, offset), ModelPosition._createAt(parent, offset + howMany));
1139
- for (const item of range.getItems({ shallow: true })) {
1140
- if (item.is('element')) {
1141
- this._changesInElement.delete(item);
1142
- this._removeAllNestedChanges(item, 0, item.maxOffset);
1143
- }
1144
- }
1145
- }
1146
- }
1147
- /**
1148
- * Returns a snapshot for the specified child node. Text node snapshots have the `name` property set to `$text`.
1149
- */
1150
- function _getSingleNodeSnapshot(node) {
1151
- return {
1152
- node,
1153
- name: node.is('$text') ? '$text' : node.name,
1154
- attributes: new Map(node.getAttributes())
1155
- };
1156
- }
1157
- /**
1158
- * Returns an array that is a copy of passed child list with the exception that text nodes are split to one or more
1159
- * objects, each representing one character and attributes set on that character.
1160
- */
1161
- function _getChildrenSnapshots(children) {
1162
- const snapshots = [];
1163
- for (const child of children) {
1164
- if (child.is('$text')) {
1165
- for (let i = 0; i < child.data.length; ++i) {
1166
- snapshots.push(_getSingleNodeSnapshot(child));
1167
- }
1168
- }
1169
- else {
1170
- snapshots.push(_getSingleNodeSnapshot(child));
1171
- }
1172
- }
1173
- return snapshots;
1174
- }
1175
- /**
1176
- * Generates array of diff instructions for given changes set.
1177
- *
1178
- * Generated actions are:
1179
- *
1180
- * - 'e' for 'equal' - when item at that position did not change,
1181
- * - 'i' for 'insert' - when item at that position was inserted,
1182
- * - 'r' for 'remove' - when item at that position was removed,
1183
- * - 'a' for 'attribute' - when item at that position has it attributes changed.
1184
- *
1185
- * Example (assume that uppercase letters have bold attribute, compare with function code):
1186
- *
1187
- * children before: fooBAR
1188
- * children after: foxybAR
1189
- *
1190
- * changes: type: remove, offset: 1, howMany: 1
1191
- * type: insert, offset: 2, howMany: 2
1192
- * type: attribute, offset: 4, howMany: 1
1193
- *
1194
- * Expected actions: equal (f), remove (o), equal (o), insert (x), insert (y), attribute (b), equal (A), equal (R)
1195
- *
1196
- * Steps taken by the script:
1197
- *
1198
- * 1. change = "type: remove, offset: 1, howMany: 1"; offset = 0; oldChildrenHandled = 0
1199
- * 1.1 between this change and the beginning is one not-changed node, fill with one equal action, one old child has been handled
1200
- * 1.2 this change removes one node, add one remove action
1201
- * 1.3 change last visited `offset` to 1
1202
- * 1.4 since an old child has been removed, one more old child has been handled
1203
- * 1.5 actions at this point are: equal, remove
1204
- *
1205
- * 2. change = "type: insert, offset: 2, howMany: 2"; offset = 1; oldChildrenHandled = 2
1206
- * 2.1 between this change and previous change is one not-changed node, add equal action, another one old children has been handled
1207
- * 2.2 this change inserts two nodes, add two insert actions
1208
- * 2.3 change last visited offset to the end of the inserted range, that is 4
1209
- * 2.4 actions at this point are: equal, remove, equal, insert, insert
1210
- *
1211
- * 3. change = "type: attribute, offset: 4, howMany: 1"; offset = 4, oldChildrenHandled = 3
1212
- * 3.1 between this change and previous change are no not-changed nodes
1213
- * 3.2 this change changes one node, add one attribute action
1214
- * 3.3 change last visited `offset` to the end of change range, that is 5
1215
- * 3.4 since an old child has been changed, one more old child has been handled
1216
- * 3.5 actions at this point are: equal, remove, equal, insert, insert, attribute
1217
- *
1218
- * 4. after loop oldChildrenHandled = 4, oldChildrenLength = 6 (fooBAR is 6 characters)
1219
- * 4.1 fill up with two equal actions
1220
- *
1221
- * The result actions are: equal, remove, equal, insert, insert, attribute, equal, equal.
1222
- */
1223
- function _generateDiffInstructionsFromChanges(oldChildrenLength, changes) {
1224
- const diff = [];
1225
- let offset = 0;
1226
- let oldChildrenHandled = 0;
1227
- // Go through all buffered changes.
1228
- for (const change of changes) {
1229
- // First, fill "holes" between changes with "equal" actions.
1230
- if (change.offset > offset) {
1231
- for (let i = 0; i < change.offset - offset; i++) {
1232
- diff.push('e');
1233
- }
1234
- oldChildrenHandled += change.offset - offset;
1235
- }
1236
- // Then, fill up actions accordingly to change type.
1237
- if (change.type == 'insert') {
1238
- for (let i = 0; i < change.howMany; i++) {
1239
- diff.push('i');
1240
- }
1241
- // The last handled offset is after inserted range.
1242
- offset = change.offset + change.howMany;
1243
- }
1244
- else if (change.type == 'remove') {
1245
- for (let i = 0; i < change.howMany; i++) {
1246
- diff.push('r');
1247
- }
1248
- // The last handled offset is at the position where the nodes were removed.
1249
- offset = change.offset;
1250
- // We removed `howMany` old nodes, update `oldChildrenHandled`.
1251
- oldChildrenHandled += change.howMany;
1252
- }
1253
- else {
1254
- // Total maximum amount of arguments that can be passed to `Array.prototype.push` may be limited so we need to
1255
- // add them manually one by one to avoid this limit. However loop might be a bit slower than `push` method on
1256
- // smaller changesets so we need to decide which method to use based on the size of the change.
1257
- // See: https://github.com/ckeditor/ckeditor5/issues/16819
1258
- if (change.howMany > 1500) {
1259
- for (let i = 0; i < change.howMany; i++) {
1260
- diff.push('a');
1261
- }
1262
- }
1263
- else {
1264
- diff.push(...'a'.repeat(change.howMany).split(''));
1265
- }
1266
- // The last handled offset is at the position after the changed range.
1267
- offset = change.offset + change.howMany;
1268
- // We changed `howMany` old nodes, update `oldChildrenHandled`.
1269
- oldChildrenHandled += change.howMany;
1270
- }
1271
- }
1272
- // Fill "equal" actions at the end of actions set. Use `oldChildrenHandled` to see how many children
1273
- // has not been changed / removed at the end of their parent.
1274
- if (oldChildrenHandled < oldChildrenLength) {
1275
- for (let i = 0; i < oldChildrenLength - oldChildrenHandled - offset; i++) {
1276
- diff.push('e');
1277
- }
1278
- }
1279
- return diff;
1280
- }
1281
- /**
1282
- * Filter callback for `Array.filter` that filters out change entries that are in graveyard.
1283
- */
1284
- function _changesInGraveyardFilter(entry) {
1285
- const posInGy = 'position' in entry && entry.position.root.rootName == '$graveyard';
1286
- const rangeInGy = 'range' in entry && entry.range.root.rootName == '$graveyard';
1287
- return !posInGy && !rangeInGy;
1288
- }